Engine: the facade re-exports the board in one block; describe() and the dead letters preference, void ply, the King-taken line, the identity maps and the unused imports are gone; SIDE_NAMES is the one spelling of a side; the drake's hunter comment no longer claims only four kinds reach the air. Prose: no shatranj and no sitting duck, which Gygax never wrote; the Thief statistic and the Basilisk's "one trick" are gone; lessons say what the board shows instead of repeating the panel; a Thief's leap counts four. Browser: one keyboard walk, in BoardArtwork; the lesson board and page share one game and one inspected piece; the chronicle's possessive is told, not patched by a regex; the chronicle colours and the reading layout are global rules instead of three copies; the guide and the about panel say what the code and the article actually do. Server and docs: the room server states that it is the kit's, kept whole; the skills, the deploy README and the share card name files that exist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
5.6 KiB
Deploying Wyrm
Every game is a room on a small Node process that keeps it as an
append-only ledger of moves in /var/lib/wyrm/rooms. Production is the
shared Kestrel's Hall host: Caddy terminates TLS with automatic
certificates, serves the static build from /opt/wyrm/build, and proxies
/api and /ws to the game server on port 8791, which runs as the
wyrm user under systemd from /opt/wyrm/app.
The shared host
The games of Kestrel's Hall share one droplet, hall (nyc3, s-1vcpu-1gb,
$6 a month, tag kestrels-hall, IP 159.203.115.252). Each game keeps its
own Caddy site file in /etc/caddy/sites/.caddy, its own port,
service user, /opt/ and /var/lib/ trees, access log
(/var/lib/caddy/-access.log), cron file and ops scripts;
/etc/caddy/Caddyfile only says import sites/*.caddy. A game server is one
small Node process capped at 300 MB by its unit file, and a room is a few
kilobytes of ledger, so the host carries several. Each game needs a port
of its own on the host: wizwar 8787, waving-hands 8788, hnefatafl 8789,
dip 8790, wyrm 8791.
Current production
- Host: 159.203.115.252
- URLs: https://wyrm.kestrelsnest.social (A record at Hover, where kestrelsnest.social's DNS lives) and https://wyrm.159.203.115.252.sslip.io (always works, zero DNS).
- Everyday deploy:
deploy/deploy.sh 159.203.115.252 - Players' reports:
deploy/pull-reports.shmirrors /var/lib/wyrm/feedback.jsonl and the screenshots to ~/Desktop/wyrm-reports with a digest;deploy/report-reply.sh 159.203.115.252 <id> <status> "text"answers one.
Adding this game to the host
scp deploy/setup-droplet.sh deploy/Caddyfile.tmpl root@<ip>:/root/ && ssh root@<ip> \ "bash /root/setup-droplet.sh 'wyrm.kestrelsnest.social, wyrm.<ip>.sslip.io'"It installs Caddy and Node if the host lacks them, writes this game's site file, and touches nothing of another game's. Point the A record at the host first, or leave the real name out until it is; running it again with the new line is how a name is added later.deploy/deploy.sh <ip>
A fresh host, when one is needed: doctl compute droplet create hall --region nyc3 \ --size s-1vcpu-1gb --image ubuntu-24-04-x64 --ssh-keys <your-key-ids> \ --tag-name kestrels-hall --wait, then step 1.
Operations scripts on the droplet
deploy.sh installs these to /usr/local/bin and the traffic.py they share
to /usr/local/lib/wyrm on every deploy, so the live copies are the repo
copies. The cron file goes to /etc/cron.d/wyrm only on a box of the game's
own: on the hall's shared host, hall-nightly.sh (in the kit's gamehall/ops)
runs every game's rollup and backup from /etc/cron.d/gamehall.
wyrm-visitors.sh [day]: who is here now and who came that day.wyrm-reseat.sh ROOM [SEAT]: who sits at a table; with a seat, a new token for a player locked out of it, as a link to send them.wyrm-say.sh ROOM "text": a word from the keeper in a table's talk, signed as the keeper, through the same door; it is heard whether or not the gallery may talk, so a call that came too late can be answered.wyrm-pulse.sh: the weekly health check (service, errors, rollup trend, backup, box).wyrm-rollup.sh [day]: one JSON line per day of counts, run nightly at 00:12 UTC into /var/lib/wyrm/rollup.jsonl.wyrm-backup.sh: nightly at 07:23 UTC, mirrors /var/lib/wyrm to thekestrel-wizwar-backupsSpace underwyrm/(a current copy less the rollup, and dated snapshots kept 90 days). It needs rclone with the Spaces credentials in /root/.config/rclone/rclone.conf, which the shared host has; a game on a box of its own copies them over first, and until then the script logs "skipped".
Errors from the game server go to Sentry, project wyrm in the
locallygrownnet organisation; the DSN is in the unit file. On the shared host
the hall's nightly job checks in with Sentry Crons once for every game's
rollup and once for every backup (monitors hall-rollup and hall-backup in
the gamehall project), and each game's script reports by its exit status.
A game on its own box checks in for itself when /root/.wyrm-sentry-cron-rollup
and /root/.wyrm-sentry-cron hold check-in URLs (the ingest URL with the
project's cron path and public key); absent, it sends nothing.
To have every new issue, regression and reappearance posted to Slack the way
wizwar's are, run deploy/sentry-slack-alert.sh [#channel] once from your own
shell with SENTRY_TOKEN (an org auth token with alerts:write) and
SLACK_CHANNEL_ID (the channel's Slack ID) set; the token never leaves the
shell.
Before every deploy, deploy/verify-ledgers.sh <ip> fetches every production
ledger and replays it with the local engine, comparing each room with what the
server shows. A ledger the new engine refuses or replays differently stops the
deploy: the server would rewrite that game on restart.
Everyday deploys
deploy/deploy.sh <ip>
Runs the type-checks, the tests and the build locally, rsyncs build/ to
the droplet keeping the previous week's hashed assets, rsyncs the server and
engine sources, installs dependencies, and restarts the game server, then
waits up to twenty seconds for it to answer HTTP and fails with the tail of
its journal if it does not. A room is replayed from its ledger when the
server comes back, which takes a few seconds; Caddy holds requests that
land in the gap.
Operations
- Logs:
ssh root@<ip> journalctl -u wyrm -ffor the game server,journalctl -u caddy -fand /var/lib/caddy/wyrm-access.log for the web side. - Restart:
ssh root@<ip> systemctl restart wyrm - Rooms:
/var/lib/wyrm/rooms/<CODE>.jsonl, one ledger per game; the nightly backup keeps them in the Space.