Files
wizwar6e/deploy
Eric WagonerandClaude Fable 5.1 ecfee5d9c2 Seats are banked in a cookie the server sets, and a lost seat comes back by name to the address it was last used from
A seat lived in the browser's storage alone, and Safari clears that after a week away, or at a word from the person on another device. Now every seat taken is banked as well in an HttpOnly cookie the server writes, which such clearing leaves alone, and the wallet takes its seats back from the bank on arrival. For a browser that lost even the cookie, the ledger notes the address each seat was last used from, as a salted hash, at most once in ten minutes, and a name given in the lobby or from the gallery gets its seats back from the same address, with a new token; where the address differs, two people at the table share it, or the host gave the seat to the clockwork, the table is only counted, and the host's phrase or the keeper's link is the way back. A name refused as taken is looked for the same way before the refusal shows. The keeper's script finds a name across tables. The seat endpoints answer only this site's own pages, under a limit of their own.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
2026-11-10 21:57:20 -05:00
..

Deploying Wiz-War

The game runs on a single DigitalOcean droplet: one Node process serves the built client and the websocket on port 8787; Caddy in front terminates TLS with automatic certificates. Room files persist in /var/lib/wizwar/rooms and survive deploys and reboots.

Current production

  • Host: hall (nyc3, tag kestrels-hall), IP 159.203.115.252, shared with the other games of Kestrel's Hall since 2026-09-29; Wiz-War keeps its own service user, port 8787, site file and access log there.
  • URLs: https://wizwar.kestrelsnest.social and https://wizwar.159.203.115.252.sslip.io (always works, zero DNS). Caddy serves both; NOTE: kestrelsnest.social's authoritative DNS is at HOVER (ns1/ns2.hover.com), not DigitalOcean — records must be added there. A matching record also sits in the DO zone in case nameservers ever move.

Everyday deploys

deploy/deploy.sh 159.203.115.252

Builds the client locally, rsyncs the repo (minus node_modules, data/, .git, research), installs dependencies on the droplet, and restarts the service. Games in progress survive: state lives in room files, and clients reconnect automatically.

Before any deploy that touches the engine, run the determinism gate:

deploy/verify-ledgers.sh 159.203.115.252

It fetches every production ledger and strictly replays it against the local engine; a single refused command fails the check. A room whose ledger no longer replays becomes unreachable after restart, so this is not optional.

New droplet from scratch

  1. doctl compute droplet create wizwar --region nyc3 --size s-1vcpu-1gb \ --image ubuntu-24-04-x64 --ssh-keys <your-key-ids> --tag-name wizwar --wait
  2. scp deploy/setup-droplet.sh deploy/Caddyfile.tmpl root@<ip>:/root/ && ssh root@<ip> \ "bash /root/setup-droplet.sh 'wizwar.<ip>.sslip.io'" (the host's Caddyfile gathers each game's site from /etc/caddy/sites/, so the host can be shared)
  3. deploy/deploy.sh <ip>

Operations

  • Logs: ssh root@<ip> journalctl -u wizwar -f
  • Restart: ssh root@<ip> systemctl restart wizwar
  • Nightly backups: wizwar-backup.sh runs from root's crontab at 07:17 UTC, pushing /var/lib/wizwar to the kestrel-wizwar-backups Space (nyc3) via rclone. It is installed at /usr/local/bin/wizwar-backup.sh on the droplet; setup-droplet.sh does NOT install it — on a fresh droplet, copy the script, configure rclone (the script refuses to run while the config still holds its CHANGE_ME placeholder), and add the cron entry by hand.
  • A player whose browser lost their seat (Safari's seven days, an app's own browser, cleared data) asks the host, whose "seats" button makes a four-word phrase that re-seats them on a new token. The same panel lets the host give a seat to the clockwork in a game under way, for a player who cannot take their turns; a standIn line on the ledger records it, and a later reseat gives the seat back. Failing the host, they write to the keeper. On the droplet, wizwar-reseat.sh ROOM lists who sits at the table and when each last moved or spoke; wizwar-reseat.sh ROOM NAME gives that wizard's seat a new token and prints a link carrying it in the fragment. The old token opens nothing after. The script answers through the game server's keeper door, a listener of its own on 127.0.0.1:9787 that Caddy never reaches, which opens only to the key in /etc/wizwar/keeper.key (root and the wizwar user alone can read it, so the other services on the host cannot use the door). The first deploy makes the key, and every deploy installs the script at /usr/local/bin.
  • One-off backup: scp -r root@<ip>:/var/lib/wizwar/rooms ./rooms-backup