Deploy web Studio / deploy (push) Successful in 3m27s
House parts (stdlib isokit/house): house_wall drawn along a line, with ends that adapt at corners, T-junctions and straight runs so no two walls overlap; window and door that sit on a wall and cut its opening (door styles panel, glass, double, sliding, opening; swing and hinge by rotation); floor (boards, tiles, carpet, stone); roof (hip, gable, flat, optional solar); stairs. Furniture (stdlib isokit/furniture): chair, sofa / armchair, table with chairs, desk with monitor and chair, TV, bed, cupboard, bookshelf, kitchen run, bathtub, shower, toilet, washbasin, plant, rug, floor lamp. Cut-open levels: on gf / 1f the walls in front of a room are cut low (rooms are found by their floors), the roof becomes an outline on the floor, and storeys above are left out. A part's storey comes from z (storey_h, default 3 m). Scripting: objects_of, objects_at, objects_on, mount_of, mounted, frame, to_world, to_local, local_of. Prefab meta: shape (line / rect), mount, storey. Studio: drag to draw lines and rectangles (walls, roads, fences, hedges, floors, areas, buildings, roofs). The Room tool draws a floor plus walls, without doubling walls that already stand on an edge. Windows and doors snap onto walls with a hover preview, and they move, turn, copy and delete with their wall in one undo step. New parts land on the storey the view shows. Palette categories collapse, and the palette scrolls apart from the object list. The MCP instructions explain house building, and list_prefabs shows sits_on. examples/house is a furnished bungalow; the web Studio gets a House demo button. Tests: wall corners and junctions, zero depth-sort cycles in every view, cut levels, and the house demo in every view; check_studio covers the room / window / door / storey flow, check_web the House demo.
159 lines
7.0 KiB
YAML
159 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/**"
|
|
- "examples/house/**"
|
|
- "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
|