Files
isokit/.gitea/workflows/deploy-web.yml
T
grabowski 95ea4a8512
Deploy web Studio / deploy (push) Successful in 3m27s
Build your own house: walls, windows, doors, floors, roof, stairs, furniture
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.
2026-10-24 17:39:04 +07:00

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