Files
archy/scripts/verify-aiui-deploy.sh
T
archipelagoandClaude Opus 5 3756ebffc0 feat(13-09): verify-aiui-deploy.sh fetches live chunks via sw.js, never disk
Post-deploy check for AIUI: resolves the LIVE chunk set by fetching the
service worker's precache manifest (sw.js — vite-plugin-pwa's
generateSW-mode workbox.precacheAndRoute([{url:...}]) array) over HTTP,
fetches each live chunk, and greps the fetched bytes for a marker string.
Exits non-zero when the marker is absent from every live chunk.

This exists because the node's assets/ directory is a never-pruned
graveyard (feedback_node_side_frontend_verify_stale_chunks): a disk grep
reports "deployed" before the deploy actually happened, because a dead
chunk from an old build still contains the old string. Never opens a
remote shell onto the node and never greps the node's filesystem directly
— every check is an HTTP fetch, exactly what a browser session would do.

Verified locally against a real AIUI build served over HTTP: a marker
actually present in a live-precached chunk (index.html) passes (exit 0,
2 chunks checked before the match); a nonexistent marker correctly fails
(exit 1) as the negative control — not a check that always passes.

Wired into deploy-to-target.sh's primary AIUI deploy path in the prior
commit (073bf6f3), which already calls this script by name after the copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 04:22:52 -04:00

86 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# verify-aiui-deploy.sh — post-deploy check that a marker string is actually
# reachable by a browser loading AIUI, not merely present somewhere on disk.
#
# The node's /opt/archipelago/web-ui/aiui/assets/ directory is a
# never-pruned graveyard: nginx never deletes an old build's chunk files
# when a new build lands (only the tar+rsync step below AIUI_DIST replaces
# what's there, and even that has left orphaned files in past incidents —
# see feedback_node_side_frontend_verify_stale_chunks). A disk grep over
# assets/ can therefore report "deployed" before the deploy actually
# happened, because a DEAD chunk from an old build still contains the old
# string. The only honest check fetches what a browser would actually load:
# resolve the LIVE chunk set via the service worker's precache manifest
# (sw.js), fetch each live chunk over HTTP, and grep the fetched bytes.
#
# This script never opens a remote shell session onto the node and never
# greps the node's filesystem directly — every check here is an HTTP
# fetch, exactly what a browser session would do.
#
# Usage:
# ./verify-aiui-deploy.sh <node-host> "<marker string>"
#
# Exit 0 = the marker was found in at least one live chunk fetched over HTTP.
# Exit 1 = the marker was not found in any live chunk (negative control
# should also produce this — a check that always passes is not a
# check).
set -uo pipefail
HOST="${1:?usage: verify-aiui-deploy.sh <node-host> \"<marker string>\"}"
MARKER="${2:?usage: verify-aiui-deploy.sh <node-host> \"<marker string>\"}"
# Accept a bare host or a host:port; default to plain HTTP on :80, matching
# how neode-ui/AIUI are actually served on a node (nginx terminates TLS
# elsewhere; the lifecycle gate and other production-quality scripts in
# this directory talk to nodes over plain HTTP the same way).
BASE="http://${HOST}"
SW_URL="${BASE}/aiui/sw.js"
timestamp() { echo "[$(date +%H:%M:%S)]"; }
echo "$(timestamp) Fetching service worker manifest: $SW_URL"
SW_BODY="$(curl -sf -m 15 "$SW_URL" 2>/dev/null || true)"
if [ -z "$SW_BODY" ]; then
echo "FATAL: could not fetch $SW_URL — is AIUI deployed and nginx up on $HOST?" >&2
exit 1
fi
# vite-plugin-pwa's generateSW mode emits sw.js with a
# workbox.precacheAndRoute([{url:"...",revision:"..."|null}, ...]) call —
# a JS array literal (unquoted keys), not JSON. Extract every url:"..."
# value without a full JS parser.
mapfile -t LIVE_PATHS < <(
grep -oE 'url:"[^"]*"' <<<"$SW_BODY" | sed -E 's/^url:"//; s/"$//'
)
if [ "${#LIVE_PATHS[@]}" -eq 0 ]; then
echo "FATAL: $SW_URL fetched but no precache entries found — cannot resolve live chunks." >&2
exit 1
fi
echo "$(timestamp) Resolved ${#LIVE_PATHS[@]} live chunk(s) from the precache manifest."
FOUND=0
CHECKED=0
for path in "${LIVE_PATHS[@]}"; do
[ -z "$path" ] && continue
CHECKED=$((CHECKED + 1))
url="${BASE}/aiui/${path}"
if curl -sf -m 15 "$url" 2>/dev/null | grep -q -- "$MARKER"; then
echo "$(timestamp) MATCH: $path"
FOUND=1
break
fi
done
echo "$(timestamp) Checked $CHECKED live chunk(s) fetched over HTTP for marker: $MARKER"
if [ "$FOUND" -eq 1 ]; then
echo "$(timestamp) PASS — marker found in a live, browser-fetchable chunk."
exit 0
else
echo "$(timestamp) FAIL — marker not found in any live chunk (fetched via sw.js manifest, not a disk grep)." >&2
exit 1
fi