Deploy web Studio / deploy (push) Successful in 3m53s
On isokit.buildfor.life, entering https://inference.p0.contact/v1 in the agent window's settings made the page call the inference server directly, which browsers refuse (its nginx answers the CORS preflight with 401): "The browser could not reach ... (Failed to fetch)". - the relay meta names its upstream (data-upstream; build_web.py --agent-upstream, the deploy passes the inference server): that URL now goes through the relay and is saved as "follow the site's default" - an unreachable endpoint says what to do: the relay's URL on a site with one, else isokit studio or isokit.buildfor.life - check_web.py: relayed upstream and the unreachable message
158 lines
7.0 KiB
YAML
158 lines
7.0 KiB
YAML
name: Deploy web Studio
|
|
|
|
# Builds the web Studio (tools/build_web.py -> dist/web) and publishes it to
|
|
# https://isokit.buildfor.life/ on the buildfor.life web server (s1): the
|
|
# same Caddy, deploy user and two-directory swap as the blog and cnxvinyl.lol.
|
|
# One-time server setup (Caddy vhost, web root): deploy/Caddyfile.
|
|
#
|
|
# Secrets (repo or org level), the same ones the blog and cnx_vinyl use:
|
|
# VPS_HOST, VPS_USER, VPS_SSH_KEY required
|
|
# VPS_PORT optional, default 22
|
|
# VPS_KNOWN_HOSTS optional pinned host key line(s);
|
|
# without it the key is scanned per run
|
|
|
|
on:
|
|
push:
|
|
branches: [master]
|
|
paths:
|
|
- "crates/**"
|
|
- "studio/**"
|
|
- "viewer/**"
|
|
- "web/**"
|
|
- "examples/demo/**"
|
|
- "tools/build_web.py"
|
|
- "Cargo.toml"
|
|
- "Cargo.lock"
|
|
- ".gitea/workflows/deploy-web.yml"
|
|
workflow_dispatch:
|
|
|
|
# A newer push supersedes a running deploy. Safe to cancel mid-run: rsync
|
|
# writes the non-live directory and the rename swap happens last.
|
|
concurrency:
|
|
group: deploy-isokit-web
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
VPS_HOST: ${{ secrets.VPS_HOST }}
|
|
VPS_USER: ${{ secrets.VPS_USER }}
|
|
VPS_PORT: ${{ secrets.VPS_PORT }}
|
|
SITE_DIR: /var/www/isokit.buildfor.life
|
|
SITE_URL: https://isokit.buildfor.life
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install curl, python3, rsync if missing
|
|
run: |
|
|
missing=""
|
|
for c in curl python3 rsync; do
|
|
command -v "$c" > /dev/null || missing="$missing $c"
|
|
done
|
|
if [ -n "$missing" ]; then
|
|
sudo=""
|
|
[ "$(id -u)" = 0 ] || sudo=sudo
|
|
# shellcheck disable=SC2086
|
|
$sudo apt-get update && $sudo apt-get install -y $missing
|
|
fi
|
|
|
|
- name: Install Rust (rustup, wasm32 target) and the wasm-bindgen CLI
|
|
# build_web.py wants rustup's cargo and the wasm-bindgen CLI at the
|
|
# version in Cargo.lock; the CLI comes prebuilt from its GitHub release.
|
|
run: |
|
|
cargo_home=${CARGO_HOME:-$HOME/.cargo}
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
|
|
sh -s -- -y --profile minimal --default-toolchain stable --target wasm32-unknown-unknown --no-modify-path
|
|
"$cargo_home/bin/rustup" target add wasm32-unknown-unknown
|
|
echo "$cargo_home/bin" >> "$GITHUB_PATH"
|
|
ver=$(grep -A1 '^name = "wasm-bindgen"$' Cargo.lock | sed -n 's/^version = "\(.*\)"$/\1/p')
|
|
[ -n "$ver" ] || { echo "::error::no wasm-bindgen version in Cargo.lock"; exit 1; }
|
|
tmp=$(mktemp -d)
|
|
curl -sSfL "https://github.com/wasm-bindgen/wasm-bindgen/releases/download/$ver/wasm-bindgen-$ver-x86_64-unknown-linux-musl.tar.gz" |
|
|
tar xz -C "$tmp"
|
|
install -m 755 "$tmp/wasm-bindgen-$ver-x86_64-unknown-linux-musl/wasm-bindgen" "$cargo_home/bin/"
|
|
"$cargo_home/bin/rustc" --version
|
|
"$cargo_home/bin/wasm-bindgen" --version
|
|
|
|
- name: Build dist/web
|
|
run: |
|
|
# the agent window's default endpoint: the site's relay to B4L's
|
|
# inference server (deploy/Caddyfile, handle_path /llm/*)
|
|
python3 tools/build_web.py --agent-url /llm/v1 --agent-upstream https://inference.p0.contact/v1
|
|
echo "$GITHUB_SHA" > dist/web/version.txt
|
|
|
|
- name: Set up SSH
|
|
env:
|
|
VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
|
|
VPS_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
|
|
run: |
|
|
if [ -z "$VPS_HOST" ] || [ -z "$VPS_USER" ] || [ -z "$VPS_SSH_KEY" ]; then
|
|
echo "::error::VPS_HOST, VPS_USER and VPS_SSH_KEY must be set as Actions secrets (the blog's / cnx_vinyl's values)"
|
|
exit 1
|
|
fi
|
|
port=${VPS_PORT:-22}
|
|
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
|
printf '%s\n' "$VPS_SSH_KEY" | tr -d '\r' > ~/.ssh/deploy_key
|
|
chmod 600 ~/.ssh/deploy_key
|
|
# Fail here, not at the server, when the secret is not a usable key,
|
|
# and log its (public) fingerprint for comparison with
|
|
# `ssh-keygen -lf ~deploy/.ssh/authorized_keys` on the server.
|
|
if ! ssh-keygen -y -P "" -f ~/.ssh/deploy_key > ~/.ssh/deploy_key.pub 2> /dev/null; then
|
|
echo "::error::VPS_SSH_KEY does not load as an unencrypted private key; paste the whole OpenSSH-format key including the BEGIN/END lines"
|
|
exit 1
|
|
fi
|
|
echo "key in VPS_SSH_KEY: $(ssh-keygen -lf ~/.ssh/deploy_key.pub)"
|
|
if [ -n "$VPS_KNOWN_HOSTS" ]; then
|
|
printf '%s\n' "$VPS_KNOWN_HOSTS" >> ~/.ssh/known_hosts
|
|
else
|
|
for i in 1 2 3; do
|
|
ssh-keyscan -T 10 -p "$port" "$VPS_HOST" > ~/.ssh/keyscan.out && [ -s ~/.ssh/keyscan.out ] && break
|
|
echo "ssh-keyscan attempt $i failed; retrying in 15 s" >&2
|
|
sleep 15
|
|
done
|
|
if [ ! -s ~/.ssh/keyscan.out ]; then
|
|
echo "::error::ssh-keyscan could not reach $VPS_HOST:$port (server down or runner IP blocked?)"
|
|
exit 1
|
|
fi
|
|
cat ~/.ssh/keyscan.out >> ~/.ssh/known_hosts
|
|
fi
|
|
{
|
|
echo "Host webhost"
|
|
echo " HostName $VPS_HOST"
|
|
echo " User $VPS_USER"
|
|
echo " Port $port"
|
|
echo " IdentityFile ~/.ssh/deploy_key"
|
|
echo " IdentitiesOnly yes"
|
|
echo " BatchMode yes"
|
|
} > ~/.ssh/config
|
|
|
|
- name: Deploy (rsync to the idle copy, then rename swap)
|
|
# Two persistent directories alternate as live and previous: rsync
|
|
# only ever writes the one that is not live, and the swap is three
|
|
# same-filesystem renames, so Caddy never serves a half-written tree.
|
|
# The previous build stays on disk for rollback.
|
|
run: |
|
|
# SC2029: the paths are meant to expand on the runner side
|
|
# shellcheck disable=SC2029
|
|
ssh webhost "mkdir -p '$SITE_DIR' '$SITE_DIR-prev' && rm -rf '$SITE_DIR-tmp'"
|
|
rsync -az --delete dist/web/ "webhost:$SITE_DIR-prev/"
|
|
# shellcheck disable=SC2029
|
|
ssh webhost "mv '$SITE_DIR' '$SITE_DIR-tmp' && mv '$SITE_DIR-prev' '$SITE_DIR' && mv '$SITE_DIR-tmp' '$SITE_DIR-prev'"
|
|
# shellcheck disable=SC2029
|
|
live=$(ssh webhost "cat '$SITE_DIR/version.txt'")
|
|
echo "live on server: $live"
|
|
[ "$live" = "$GITHUB_SHA" ] || { echo "::error::server holds $live, expected $GITHUB_SHA"; exit 1; }
|
|
|
|
- name: Check the public URL
|
|
# Warning only: DNS, Cloudflare or a login can make this fail while
|
|
# the files on the server are already correct.
|
|
run: |
|
|
got=$(curl -sS --max-time 20 "$SITE_URL/version.txt?nocache=$GITHUB_SHA" || true)
|
|
if [ "$got" = "$GITHUB_SHA" ]; then
|
|
echo "$SITE_URL serves $GITHUB_SHA"
|
|
else
|
|
echo "::warning::$SITE_URL/version.txt returned '${got:0:80}', expected $GITHUB_SHA"
|
|
fi
|