From d82ff229b6d9601e1725ae55c0002d04777cf573 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Fri, 11 Sep 2026 12:08:38 -0400 Subject: [PATCH] session-server: the box watches for its own changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lith redeploys the moment a push lands, because a GitHub webhook calls it. This one has no such door — it sits behind DigitalOcean with nothing listening but the game socket — so deploying it was something a person had to remember. They did not always remember: a Mongo credential rotation once sat undeployed long enough that chat died days later, and "is it deployed?" was a question you could only answer by asking somebody. So it watches instead of being told. Every minute: fetch, and if main moved AND the move touched session-server/ or shared/, run the same deploy-remote.sh the hand path streams over SSH — same npm ci, same 150s health gate on :8889, same automatic rollback. Everything else that lands in a monorepo is none of this process's business, and restarting for a piece or a site change would drop every live chat and match for nothing. flock -n rather than a queue: a hand deploy and a timer tick must never both be mid-restart, and the loser can simply give up — whatever the winner lands is at or past this tip, and the next tick re-checks. Up to a minute here on top of up to a minute of lith's knot→GitHub mirror, since this box pulls from the GitHub side. Two minutes from compush to a live relay, worst case, with no key shared anywhere and no inbound port opened. `npm run session:publish` is unchanged — the timer removes the obligation, not the option. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01B9umtQx4AjyNQ6TBYPRGSr --- session-server/autopublish/README.md | 70 +++++++++++++++++++ session-server/autopublish/autopublish.sh | 49 +++++++++++++ .../autopublish/session-autopublish.service | 14 ++++ .../autopublish/session-autopublish.timer | 11 +++ 4 files changed, 144 insertions(+) create mode 100644 session-server/autopublish/README.md create mode 100755 session-server/autopublish/autopublish.sh create mode 100644 session-server/autopublish/session-autopublish.service create mode 100644 session-server/autopublish/session-autopublish.timer diff --git a/session-server/autopublish/README.md b/session-server/autopublish/README.md new file mode 100644 index 0000000000..e71de0048f --- /dev/null +++ b/session-server/autopublish/README.md @@ -0,0 +1,70 @@ +# session-server autopublish + +The session server redeploying itself when its own code lands on `main`. + +lith redeploys the moment a push arrives, because a GitHub webhook calls it. +This box has no such door — it sits behind DigitalOcean with nothing listening +but the game socket — so deploying it was a thing somebody had to remember to +do by hand. They did not always remember: a Mongo credential rotation once sat +undeployed long enough that chat died days later, and "is it deployed?" was a +question you could only answer by asking a person. + +So the box watches instead of being told. + +## What it does, every 60 seconds + +1. `git fetch origin main`. +2. Nothing moved → exit. +3. Something moved but nothing under `session-server/` or `shared/` → exit. + The repo is a monorepo and most of what lands in it has nothing to do with + the process running here; restarting for a piece or a site change would drop + every live chat and match for nothing. `shared/` counts because + `session.mjs` imports it. +4. Otherwise run [`../deploy-remote.sh`](../deploy-remote.sh) — the same script + `npm run session:publish` streams over SSH, with the same `npm ci`, the same + 150-second health gate on `:8889`, and the same automatic rollback to the + previous commit if the server does not come up. + +`flock -n` means a hand deploy and a timer tick never both restart the service: +the loser gives up rather than queueing, because whatever the winner lands will +be at or past this tip anyway and the next tick re-checks. + +## How long it takes + +Up to a minute of polling here, on top of up to a minute of lith's +knot→GitHub mirror ([`lith/mirror`](../../lith/mirror)), because this box pulls +from the GitHub side. **Two minutes from `compush` to a live relay, worst +case.** No key is shared anywhere and no inbound port is opened. + +## Deploying by hand still works + +`npm run session:publish` is unchanged and is still the right thing for a +deploy you want to watch, or for a ref that is not `main`. The timer only +removes the obligation, not the option. + +## First-time setup + +On the droplet (`root@157.245.134.225`): + +```bash +cd /home/aesthetic-computer && git pull +install -m 644 session-server/autopublish/session-autopublish.service \ + /etc/systemd/system/session-autopublish.service +install -m 644 session-server/autopublish/session-autopublish.timer \ + /etc/systemd/system/session-autopublish.timer +systemctl daemon-reload +systemctl enable --now session-autopublish.timer +``` + +## Reading it + +```bash +systemctl list-timers session-autopublish # when it last ran, when next +journalctl -u session-autopublish -n 50 # what it decided and did +systemctl disable --now session-autopublish.timer # stop watching +``` + +A tick with nothing to do logs nothing. A tick that deploys logs the same +`RESULT=` line the hand path prints — `ok:`, `rolledback:`, or `fail:stale*` +when the GitHub mirror has not caught up yet, which is not an unwell server, +just an early look. diff --git a/session-server/autopublish/autopublish.sh b/session-server/autopublish/autopublish.sh new file mode 100755 index 0000000000..89c764b211 --- /dev/null +++ b/session-server/autopublish/autopublish.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# autopublish.sh — the session server watching for its own changes. +# +# lith redeploys itself the moment a push lands, because a GitHub webhook calls +# it. This box has no such door: it sits behind DigitalOcean with nothing +# listening but the game socket, so somebody had to remember to run +# `npm run session:publish` by hand. They did not always remember — a Mongo +# credential rotation once sat undeployed long enough that chat died days later +# — and "it is deployed" was a thing you could only find out by asking. +# +# So the box watches instead of being told. Every minute: fetch, and if `main` +# has moved AND the move touched this server's own code, run the same deploy +# the hand path runs, health gate and auto-rollback included. +# +# The delay is up to a minute of polling on top of up to a minute of lith's +# knot→GitHub mirror, because this box pulls from the GitHub side. Two minutes +# from `compush` to a live relay, worst case, with no key shared anywhere and +# no inbound port opened. +set -uo pipefail + +REMOTE=/home/aesthetic-computer +NODE_BIN=/root/.local/share/fnm/aliases/default/bin +BRANCH=main +BOOT_BUDGET=150 + +cd "$REMOTE" || { echo "$(date -Iseconds) ✗ no checkout at $REMOTE" >&2; exit 1; } + +git fetch origin --quiet "$BRANCH" || { + echo "$(date -Iseconds) ✗ fetch failed" >&2; exit 1; } + +here=$(git rev-parse HEAD) +there=$(git rev-parse "origin/$BRANCH") +[ "$here" = "$there" ] && exit 0 + +# Only this server's own code. The repo is a monorepo and most of what lands in +# it — pieces, the site, the game — has nothing to do with the process running +# here, and restarting for those would drop every live chat and match for +# nothing. `shared/` counts because session.mjs imports it. +if ! git diff --name-only "$here" "$there" | grep -qE '^(session-server|shared)/'; then + exit 0 +fi + +# A hand deploy and a timer must never both be mid-restart. `flock -n` means the +# loser gives up rather than queueing: whatever the winner lands will be at or +# past this tip anyway, and the next tick re-checks. +exec flock -n /run/session-autopublish.lock \ + env NODE_BIN="$NODE_BIN" REMOTE="$REMOTE" REF="origin/$BRANCH" \ + EXPECT="$there" BOOT_BUDGET="$BOOT_BUDGET" \ + bash "$REMOTE/session-server/deploy-remote.sh" diff --git a/session-server/autopublish/session-autopublish.service b/session-server/autopublish/session-autopublish.service new file mode 100644 index 0000000000..ff0bfb50a8 --- /dev/null +++ b/session-server/autopublish/session-autopublish.service @@ -0,0 +1,14 @@ +[Unit] +Description=Redeploy the session server when its own code changes on main +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/home/aesthetic-computer/session-server/autopublish/autopublish.sh +# 0 = nothing to do or deployed cleanly. 1 = the deploy failed and rolled +# itself back, which deploy-remote.sh has already said loudly in the log. +SuccessExitStatus=0 + +[Install] +WantedBy=multi-user.target diff --git a/session-server/autopublish/session-autopublish.timer b/session-server/autopublish/session-autopublish.timer new file mode 100644 index 0000000000..ffdc15921f --- /dev/null +++ b/session-server/autopublish/session-autopublish.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Check main for session-server changes every 60 seconds + +[Timer] +OnBootSec=90 +OnUnitActiveSec=60 +AccuracySec=10 +Unit=session-autopublish.service + +[Install] +WantedBy=timers.target -- 2.51.2