2026-04-17 14:54:14 -07:00
#!/usr/bin/env bash
2026-06-10 03:38:03 -07:00
# Deploy commands: guide:next (dev guide to mc.next.black.lan).
2026-04-17 14:54:14 -07:00
#
# This module is Tourguide-owned (see .project/team-leads/tourguide.md).
#
# Host configuration (from .env or shell rc):
2026-06-10 03:38:03 -07:00
# NEXT_DEPLOY_HOST — SSH target for mc.next.black.lan static root.
# Default: "lilith@black.lan".
2026-04-17 14:54:14 -07:00
# NEXT_DEPLOY_PATH — destination directory on the remote host.
# Default: "/bigdisk/next/mc/".
#
# The target host-nginx vhost + bind-mount are set up in
# `/bigdisk/nginx/nginx.conf` + `/bigdisk/nginx/docker-compose.yml` on
2026-06-10 03:38:03 -07:00
# black.lan. Infra + objective: p1-15.
2026-04-17 14:54:14 -07:00
2026-06-10 03:38:03 -07:00
: " ${ NEXT_DEPLOY_HOST : =lilith@black.lan } "
2026-04-17 14:54:14 -07:00
: " ${ NEXT_DEPLOY_PATH : =/bigdisk/next/mc/ } "
2026-04-17 15:36:01 -07:00
# Bake-simcache scenarios. Empty = skip bake step entirely in `deploy:guide:next`.
# Comma-separated list = bake those scenarios after `pnpm build`. `all` = every
# canonical scenario (1.1 GB × 6 ≈ 6.6 GB; minutes to bake). Default: empty
# (deploy ships only the client-WASM fallback path).
: " ${ DEPLOY_BAKE_SCENARIOS : = } "
cmd_bake_simcache( ) {
# `./run bake:simcache [ids…]` — pre-compute sim-cache frames into dist/.
# Pass scenario ids (space- or comma-separated) or the string `all`.
# Used standalone (post-`pnpm build`) or invoked from cmd_deploy_guide_next.
local scenarios = " ${ 1 :- all } " ; shift || true
if [ " $scenarios " = "all" ] ; then
scenarios = "" # empty arg list = bake-simcache defaults to ALL_SCENARIO_IDS
else
scenarios = " ${ scenarios //,/ } "
fi
if [ ! -d " $GUIDE_DIR /dist " ] ; then
echo -e " ${ RED } ✗ $GUIDE_DIR /dist missing — run \`pnpm build\` first, then \`./run bake:simcache\`. ${ NC } "
return 1
fi
echo -e " ${ BLUE } Baking sim-cache frames into $GUIDE_DIR /dist/__sim-cache/ ${ NC } "
# shellcheck disable=SC2086
( cd " $GUIDE_DIR " && node --import tsx/esm tools/bake-simcache.ts $scenarios )
}
2026-04-17 14:54:14 -07:00
cmd_deploy_guide_next( ) {
# Build the dev bundle (all EpisodeGate subtrees visible) + rsync to black.
# Safe to run repeatedly — rsync --delete replaces the target dir with dist/.
2026-04-17 16:31:20 -07:00
#
# **Bake delegation**: when DEPLOY_BAKE_SCENARIOS is set AND this is NOT
# already running on apricot, SSH-delegate the entire pipeline to apricot
# so the heavy sim compute happens on the run host (CPU to spare, WASM
# already built). Apricot pulls latest main from forge, then invokes this
# same script there — which runs locally (no further delegation) and
# rsyncs to black via apricot's `Host black` SSH config alias.
local local_hostname
local_hostname = " $( hostname -s 2>/dev/null || hostname) "
if [ -n " $DEPLOY_BAKE_SCENARIOS " ] && [ " $local_hostname " != "apricot" ] ; then
echo -e " ${ BLUE } [delegate] DEPLOY_BAKE_SCENARIOS= $DEPLOY_BAKE_SCENARIOS — offloading bake+deploy to $AUTOPLAY_HOST (apricot has processing power to spare) ${ NC } "
if ! ssh -o ConnectTimeout = 5 " $AUTOPLAY_HOST " " test -d $PROJECT_ROOT_REMOTE /.git " 2>/dev/null; then
echo -e " ${ RED } ✗ $AUTOPLAY_HOST : $PROJECT_ROOT_REMOTE is not a git clone — check PROJECT_ROOT_REMOTE in .env. ${ NC } "
return 1
fi
ssh " $AUTOPLAY_HOST " "
set -e
cd $PROJECT_ROOT_REMOTE
git fetch --prune origin
git checkout main
git reset --hard origin/main
export DEPLOY_BAKE_SCENARIOS = '$DEPLOY_BAKE_SCENARIOS'
export NEXT_DEPLOY_HOST = '${NEXT_DEPLOY_HOST:-black}'
export NEXT_DEPLOY_PATH = '$NEXT_DEPLOY_PATH'
# Keep WASM fresh — apricot is the canonical build host.
( cd src/simulator && bash build-wasm.sh)
./run deploy:guide:next
"
return $?
fi
2026-04-17 14:54:14 -07:00
# Prerequisite: WASM artifact present locally. The Vite build imports from
# the @magic-civ/physics-rs alias which resolves to .local/build/wasm/.
if [ ! -f " $REPO_ROOT /.local/build/wasm/magic_civ_physics.js " ] ; then
echo -e " ${ RED } ✗ .local/build/wasm/magic_civ_physics.js missing — can't build guide. ${ NC } "
echo -e " ${ RED } Build locally: (cd src/simulator && bash build-wasm.sh) ${ NC } "
echo -e " ${ RED } Or rsync from apricot: ${ NC } "
echo -e " ${ RED } rsync -a \"\$AUTOPLAY_HOST\":\"\$PROJECT_ROOT_REMOTE\"/.local/build/wasm/ .local/build/wasm/ ${ NC } "
return 1
fi
2026-04-17 15:36:01 -07:00
echo -e " ${ BLUE } [1/5] Building dev bundle (VITE_DEV_GUIDE=1 pnpm build)... ${ NC } "
2026-04-17 14:54:14 -07:00
if ! ( cd " $GUIDE_DIR " && VITE_DEV_GUIDE = 1 pnpm build 2>& 1) ; then
echo -e " ${ RED } ✗ pnpm build failed ${ NC } "
return 1
fi
local dist = " $GUIDE_DIR /dist "
if [ ! -f " $dist /index.html " ] ; then
echo -e " ${ RED } ✗ $dist /index.html not produced — build reported success but emitted no bundle. ${ NC } "
return 1
fi
local size
size = " $( du -sh " $dist " | cut -f1) "
echo -e " ${ GREEN } ✓ dist/ ready ( $size ) ${ NC } "
2026-04-17 15:36:01 -07:00
if [ -n " $DEPLOY_BAKE_SCENARIOS " ] ; then
echo -e " ${ BLUE } [2/5] Baking sim-cache scenarios: $DEPLOY_BAKE_SCENARIOS ${ NC } "
if ! cmd_bake_simcache " $DEPLOY_BAKE_SCENARIOS " ; then
echo -e " ${ RED } ✗ bake-simcache failed ${ NC } "
return 1
fi
size = " $( du -sh " $dist " | cut -f1) "
echo -e " ${ GREEN } ✓ dist/ with baked frames ( $size ) ${ NC } "
else
echo -e " ${ YELLOW } [2/5] DEPLOY_BAKE_SCENARIOS unset — skipping sim-cache bake (client-WASM fallback in browser). ${ NC } "
fi
echo -e " ${ BLUE } [3/5] Verifying SSH to $NEXT_DEPLOY_HOST ... ${ NC } "
2026-04-17 14:54:14 -07:00
if ! ssh -o ConnectTimeout = 5 -o BatchMode = yes " $NEXT_DEPLOY_HOST " " test -d $NEXT_DEPLOY_PATH && echo ok " >/dev/null 2>& 1; then
echo -e " ${ RED } ✗ can't reach $NEXT_DEPLOY_HOST : $NEXT_DEPLOY_PATH (VPN or permissions). ${ NC } "
return 1
fi
echo -e " ${ GREEN } ✓ reachable ${ NC } "
2026-04-17 15:36:01 -07:00
echo -e " ${ BLUE } [4/5] Rsyncing dist/ → $NEXT_DEPLOY_HOST : $NEXT_DEPLOY_PATH ${ NC } "
2026-04-17 14:54:14 -07:00
if ! rsync -az --delete " $dist / " " $NEXT_DEPLOY_HOST : $NEXT_DEPLOY_PATH " ; then
echo -e " ${ RED } ✗ rsync failed ${ NC } "
return 1
fi
echo -e " ${ GREEN } ✓ deployed ${ NC } "
2026-06-10 03:38:03 -07:00
echo -e " ${ BLUE } [5/5] Probing https://mc.next.black.lan ... ${ NC } "
2026-04-17 14:54:14 -07:00
local http_status
2026-06-10 03:38:03 -07:00
http_status = " $( curl -sk -o /dev/null -w "%{http_code}" --max-time 10 https://mc.next.black.lan) "
2026-04-17 14:54:14 -07:00
if [ " $http_status " = "200" ] ; then
2026-06-10 03:38:03 -07:00
echo -e " ${ GREEN } ✓ https://mc.next.black.lan → HTTP 200 ${ NC } "
2026-04-17 14:54:14 -07:00
else
2026-06-10 03:38:03 -07:00
echo -e " ${ YELLOW } ! https://mc.next.black.lan → HTTP $http_status (expected 200) ${ NC } "
2026-04-17 14:54:14 -07:00
echo -e " ${ YELLOW } Deploy may still be correct; check the vhost config on $NEXT_DEPLOY_HOST . ${ NC } "
return 1
fi
2026-04-17 23:50:11 -07:00
# MIME sanity: if the vhost's http{} block is missing `include mime.types;`
# nginx serves .js as text/plain and browsers refuse to run the ES-module
# shell (blank page, "disallowed MIME type" console errors). Catch that
# regression here rather than on the next user load.
local js_asset js_mime
js_asset = " $( ls " $dist " /assets/index-*.js 2>/dev/null | head -1) "
if [ -n " $js_asset " ] ; then
2026-06-10 03:38:03 -07:00
js_mime = " $( curl -sk --max-time 10 -o /dev/null -w '%{content_type}' " https://mc.next.black.lan/assets/ $( basename " $js_asset " ) " ) "
2026-04-17 23:50:11 -07:00
case " $js_mime " in
application/javascript*| text/javascript*)
echo -e " ${ GREEN } ✓ JS MIME: $js_mime ${ NC } " ; ;
*)
echo -e " ${ RED } ✗ JS asset served as ' $js_mime ' (expected application/javascript). ${ NC } "
echo -e " ${ RED } Add 'include /etc/nginx/mime.types; default_type application/octet-stream;' to the http{} block of /bigdisk/nginx/nginx.conf on $NEXT_DEPLOY_HOST , then: ${ NC } "
echo -e " ${ RED } docker exec host-nginx nginx -t && docker exec host-nginx nginx -s reload ${ NC } "
return 1 ; ;
esac
fi
2026-06-10 03:38:03 -07:00
echo -e " ${ GREEN } Deployed dev guide to https://mc.next.black.lan ${ NC } "
2026-04-17 14:54:14 -07:00
}