diff --git a/session-server/deploy-remote.sh b/session-server/deploy-remote.sh index 6e721d20a..5a0e31934 100644 --- a/session-server/deploy-remote.sh +++ b/session-server/deploy-remote.sh @@ -5,6 +5,12 @@ set -uo pipefail : "${REMOTE:?REMOTE is required}" : "${REF:?REF is required}" : "${BOOT_BUDGET:?BOOT_BUDGET is required}" +# EXPECT is the commit the CALLER resolved before dialing in. This box fetches +# from a mirror, and a mirror that has fallen behind still checks out cleanly, +# still passes every health gate, and still reports ok — the failure of +# 2026-08-08 deployed a commit four hours stale and called itself healthy. +# Optional so an older caller still works. +EXPECT="${EXPECT:-}" export PATH="$NODE_BIN:$PATH" cd "$REMOTE" || { echo 'RESULT=fail:cd'; exit 1; } @@ -17,6 +23,23 @@ git fetch origin --quiet || { echo 'RESULT=fail:fetch'; exit 1; } git reset --hard "$REF" --quiet || { echo 'RESULT=fail:reset'; exit 1; } echo " now at $(git rev-parse --short HEAD) ($(git log -1 --format=%s | head -c 55))" +# Refuse a stale checkout BEFORE npm ci and the restart, so a lagging mirror +# costs nothing: the running server is left untouched rather than bounced onto +# code the caller never asked for. +if [ -n "$EXPECT" ]; then + if ! git cat-file -e "$EXPECT^{commit}" 2>/dev/null; then + echo " ✗ $EXPECT is not on this box — its remote has not caught up" + echo "RESULT=fail:stale-missing:$(git rev-parse --short HEAD)" + exit 1 + fi + if ! git merge-base --is-ancestor "$EXPECT" HEAD; then + echo " ✗ checkout does not contain $(git rev-parse --short "$EXPECT") — refusing to deploy behind the caller" + echo "RESULT=fail:stale:$(git rev-parse --short HEAD)" + exit 1 + fi + echo " ✓ contains $(git rev-parse --short "$EXPECT")" +fi + cd session-server || { echo 'RESULT=fail:cd-ss'; exit 1; } deploy_and_check() { diff --git a/session-server/deploy.fish b/session-server/deploy.fish index bad654e24..9a445e99a 100755 --- a/session-server/deploy.fish +++ b/session-server/deploy.fish @@ -39,6 +39,16 @@ end echo "🚀 Deploying session server → $HOST (ref: $REF)" +# Resolve the ref HERE, against knot, and make the box prove it landed on it. +# The droplet fetches from the GitHub mirror, so its idea of origin/main can be +# hours behind knot's while every other signal still reads green. +set -l EXPECT (git rev-parse --verify --quiet $REF) +if test -z "$EXPECT" + echo "❌ cannot resolve $REF locally — run `git fetch origin` first." + exit 1 +end +echo " expecting (string sub -l 9 $EXPECT) from knot" + # The whole remote deploy runs as one Bash script so PRE (the rollback point) # and the health gate share state. Fish cannot parse Bash heredocs, so keep the # remote program in its own syntax-checked file and stream it over SSH. @@ -46,8 +56,9 @@ set -l node_bin_q (string escape -- $NODE_BIN) set -l remote_q (string escape -- $REMOTE) set -l ref_q (string escape -- $REF) set -l boot_budget_q (string escape -- $BOOT_BUDGET) +set -l expect_q (string escape -- $EXPECT) ssh -i $KEY -o ConnectTimeout=15 $HOST \ - "env NODE_BIN=$node_bin_q REMOTE=$remote_q REF=$ref_q BOOT_BUDGET=$boot_budget_q bash -s" \ + "env NODE_BIN=$node_bin_q REMOTE=$remote_q REF=$ref_q EXPECT=$expect_q BOOT_BUDGET=$boot_budget_q bash -s" \ < $REMOTE_SCRIPT set -l ssh_status $status @@ -61,6 +72,9 @@ if test $ssh_status -eq 0; and test "$code" = "200" echo "✅ deployed and healthy." else echo "⚠️ deploy did not end healthy (ssh=$ssh_status, http=$code) — see the RESULT line above." + echo " RESULT=fail:stale* means the box's remote is behind knot, NOT that the" + echo " server is unwell — it was left running untouched. Advance the mirror" + echo " (git push github main) and redeploy." echo " Logs: ssh -i $KEY $HOST 'tail -40 /tmp/session-server.log'" exit 1 end