The keeper can bring back a seat a browser lost: a reseat line on the ledger gives that wizard's seat a new token and the old one opens nothing after, wizwar-reseat.sh on the host asks for it through a keeper door that listens on the loopback alone and opens only to a key the deploy makes once, and the link it prints carries the seat in the fragment, which leaves the address bar at once and is kept only when no seat held in this browser still opens and the server takes it; a gallery watching a game in play is told both ways back, the front page asks before forgetting a game not yet finished, and the server's tests now run with the rest
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MbtUPJ24P6vc6tE1x6RS3m
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1b9acfd7c4
commit
5ea0d4fdec
@@ -52,4 +52,15 @@ longer replays becomes unreachable after restart, so this is not optional.
|
||||
`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) writes 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`
|
||||
|
||||
@@ -25,6 +25,14 @@ ssh "root@$HOST" '
|
||||
install -m 755 /opt/wizwar/deploy/wizwar-backup.sh /usr/local/bin/wizwar-backup.sh
|
||||
install -m 755 /opt/wizwar/deploy/wizwar-rollup.sh /usr/local/bin/wizwar-rollup.sh
|
||||
install -m 755 /opt/wizwar/deploy/wizwar-visitors.sh /usr/local/bin/wizwar-visitors.sh
|
||||
install -m 755 /opt/wizwar/deploy/wizwar-reseat.sh /usr/local/bin/wizwar-reseat.sh
|
||||
# The key to the keeper door: made once, readable by root (the reseat
|
||||
# script) and the wizwar user (the server) alone.
|
||||
if [ ! -s /etc/wizwar/keeper.key ]; then
|
||||
install -d -m 750 -o root -g wizwar /etc/wizwar
|
||||
(umask 037; openssl rand -hex 32 > /etc/wizwar/keeper.key)
|
||||
chown root:wizwar /etc/wizwar/keeper.key
|
||||
fi
|
||||
# On a host the hall shares, its hall-nightly.sh runs the rollup and backup
|
||||
# of every game and checks in with Sentry once for all; a game on a box of
|
||||
# its own schedules its own.
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# A way back for a player whose browser lost their seat: the seat gets a new
|
||||
# token, the old one stops working, and the link printed carries the new one.
|
||||
# Whether the one asking holds that seat is the keeper's call; send the link
|
||||
# only to them. Runs ON the droplet, through the game server's keeper door,
|
||||
# which listens on the loopback alone.
|
||||
# wizwar-reseat.sh ROOM who sits at the table, and when each person last moved or spoke
|
||||
# wizwar-reseat.sh ROOM NAME re-seat the wizard NAME and print the link
|
||||
set -euo pipefail
|
||||
ROOM="${1:?usage: wizwar-reseat.sh ROOM [NAME]}"
|
||||
DOOR="http://127.0.0.1:${KEEPER_PORT:-9787}/keeper/rooms/${ROOM^^}"
|
||||
KEY_FILE="${KEEPER_KEY_FILE:-/etc/wizwar/keeper.key}"
|
||||
[ -s "$KEY_FILE" ] || { echo "No keeper's key at $KEY_FILE; a deploy makes it." >&2; exit 1; }
|
||||
# The key goes as a header read from a file descriptor, never on a command line another user could see.
|
||||
KEY="@/dev/fd/3"
|
||||
exec 3< <(printf 'x-keeper-key: %s\n' "$(cat "$KEY_FILE")")
|
||||
if [ $# -lt 2 ]; then
|
||||
curl -s -H "$KEY" "$DOOR" | python3 -c '
|
||||
import json, sys
|
||||
r = json.load(sys.stdin)
|
||||
if "error" in r: sys.exit(r["error"])
|
||||
print("room %s, %s" % (r["roomId"], "finished" if r["finished"] else "in play" if r["started"] else "not begun"))
|
||||
for s in r["seats"]:
|
||||
when = "automaton" if s["bot"] else "never moved or spoke" if s["lastActed"] is None else "last acted " + s["lastActed"][:16].replace("T", " ") + " UTC"
|
||||
print(" %-24s %-5s %s" % (s["name"], "host" if s["host"] else "", when))
|
||||
'
|
||||
else
|
||||
python3 -c 'import json, sys; print(json.dumps({"seat": sys.argv[1]}))' "$2" \
|
||||
| curl -s -X POST -H "$KEY" -H 'content-type: application/json' --data-binary @- "$DOOR/reseat" | python3 -c '
|
||||
import json, sys
|
||||
r = json.load(sys.stdin)
|
||||
if "error" in r: sys.exit(r["error"])
|
||||
print("%s has a new token; their old one opens nothing now. Send them this link:" % r["name"])
|
||||
print(r["link"])
|
||||
'
|
||||
fi
|
||||
@@ -9,6 +9,9 @@ WorkingDirectory=/opt/wizwar/packages/server
|
||||
Environment=PORT=8787
|
||||
# Caddy terminates TLS; the plaintext port must not face the internet.
|
||||
Environment=HOST=127.0.0.1
|
||||
# The keeper's door (deploy/wizwar-reseat.sh) binds the loopback whatever HOST says.
|
||||
Environment=KEEPER_PORT=9787
|
||||
Environment=KEEPER_KEY_FILE=/etc/wizwar/keeper.key
|
||||
Environment=WIZWAR_DATA_DIR=/var/lib/wizwar/rooms
|
||||
Environment=WIZWAR_STATIC_DIR=/opt/wizwar/packages/web/dist
|
||||
Environment=WIZWAR_KEEPER_TZ=America/New_York
|
||||
|
||||
Reference in New Issue
Block a user