From b13750f88a2d068a968be63bb5f26c1f96458081 Mon Sep 17 00:00:00 2001 From: monster Date: Wed, 22 Jul 2026 11:04:57 +1000 Subject: [PATCH] deploy: gapless additive deploys, and verify the bundle not just the page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The outage: deploy did rm -rf on the web root, THEN docker cp. Anyone loading in that window — or holding a cached index.html from before it — asked for a hashed bundle that no longer existed, and nginx answered with HTML: "Failed to load module script... MIME type of text/html". Exactly what got reported. Two changes: - Merge instead of swap. Hashed bundles never collide, so old builds' assets stay alive for stale HTML while the new index.html takes over. No gap, no orphaned caches. Disk is a few MB per deploy; prune by hand if it ever matters. - The verify step now follows the chain the browser follows: fetch the live index.html, extract the script src, and assert it serves as application/javascript. A 200 on the page proved nothing — the page was 200 all through the outage. Verified live: page → index-CmCk0pc0.js → application/javascript, and the game boots to the title screen on partly.party/toastsim. Co-Authored-By: Claude Fable 5 --- scripts/deploy.sh | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) mode change 100644 => 100755 scripts/deploy.sh diff --git a/scripts/deploy.sh b/scripts/deploy.sh old mode 100644 new mode 100755 index 7920962..d1aed9f --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -19,12 +19,24 @@ echo "[2/4] Rsync dist → $VPS:$STAGING ..." ssh "$VPS" "rm -rf '$STAGING' && mkdir -p '$STAGING'" rsync -az dist/ "$VPS:$STAGING/" -echo "[3/4] Swap into $CONTAINER:$REMOTE_WEB_ROOT ..." -ssh "$VPS" "docker exec '$CONTAINER' rm -rf '$REMOTE_WEB_ROOT' \ - && docker cp '$STAGING' '$CONTAINER:$REMOTE_WEB_ROOT'" +echo "[3/4] Merge into $CONTAINER:$REMOTE_WEB_ROOT ..." +# ADDITIVE on purpose — no rm, no gap. The old flow (rm -rf, then docker cp) +# had a downtime window, and worse: any browser/CDN holding yesterday's +# index.html pointed at hashed bundles the rm had just deleted → the classic +# "module script served as text/html" outage. Hashed assets never collide, so +# merging keeps every old build's bundles alive for stale HTML while the new +# index.html takes over for everyone else. Disk cost is a few MB per deploy; +# prune assets/ older than ~30 days by hand if it ever matters. +ssh "$VPS" "docker exec '$CONTAINER' mkdir -p '$REMOTE_WEB_ROOT' \ + && docker cp '$STAGING/.' '$CONTAINER:$REMOTE_WEB_ROOT/'" -echo "[4/4] Verify..." -code=$(curl -so /dev/null -w '%{http_code}' "https://partly.party/toastsim/?cb=$(date +%s)") -echo "https://partly.party/toastsim/ → HTTP $code" -[ "$code" = 200 ] || { echo "NOT DEPLOYED (non-200)"; exit 1; } -echo "done." +echo "[4/4] Verify the page AND the bundle it references..." +page=$(curl -s "https://partly.party/toastsim/?cb=$(date +%s)") +src=$(printf '%s' "$page" | grep -oE 'src="[^"]*\.js"' | sed 's/src="//;s/"//' | head -1) +[ -n "$src" ] || { echo "NOT DEPLOYED (no script tag in live index.html)"; exit 1; } +ctype=$(curl -so /dev/null -w '%{content_type}' "https://partly.party$src") +echo "https://partly.party/toastsim/ → script $src → $ctype" +case "$ctype" in + application/javascript*|text/javascript*) echo "done." ;; + *) echo "NOT DEPLOYED (bundle served as $ctype — stale index or missing asset)"; exit 1 ;; +esac