From fc620342900dcdec72bae79e11d9c45cd494796c Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 23 Sep 2026 13:17:09 -0400 Subject: [PATCH] Production: the tafl droplet at 209.97.148.244, tafl.kestrelsnest.social, and the Sentry project Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG --- .claude/skills/hnefatafl-pulse/SKILL.md | 2 +- .claude/skills/hnefatafl-reports/SKILL.md | 10 +++++----- .claude/skills/hnefatafl-visitors/SKILL.md | 2 +- deploy/README.md | 12 ++++++------ deploy/hnefatafl-rollup.sh | 2 +- deploy/hnefatafl.service | 2 +- deploy/pull-reports.sh | 2 +- deploy/sentry-slack-alert.sh | 4 ++-- deploy/setup-droplet.sh | 6 ++++-- deploy/verify-ledgers.sh | 2 +- deploy/visitors.sh | 2 +- server/src/index.ts | 2 +- src/app.html | 8 ++++---- 13 files changed, 29 insertions(+), 27 deletions(-) diff --git a/.claude/skills/hnefatafl-pulse/SKILL.md b/.claude/skills/hnefatafl-pulse/SKILL.md index 21ebc5c..f12de01 100644 --- a/.claude/skills/hnefatafl-pulse/SKILL.md +++ b/.claude/skills/hnefatafl-pulse/SKILL.md @@ -7,7 +7,7 @@ description: The weekly Hnefatafl operations pulse — game server health, error One command on the droplet prints everything; read it as a short report. - ssh root@__IP__ hnefatafl-pulse.sh + ssh root@209.97.148.244 hnefatafl-pulse.sh - **game server**: whether the service and Caddy are up, restarts and journal errors in the last seven days with the last three error lines. diff --git a/.claude/skills/hnefatafl-reports/SKILL.md b/.claude/skills/hnefatafl-reports/SKILL.md index 59fb8c2..7dddcf6 100644 --- a/.claude/skills/hnefatafl-reports/SKILL.md +++ b/.claude/skills/hnefatafl-reports/SKILL.md @@ -7,7 +7,7 @@ description: Work the Hnefatafl reports desk — fetch players' bug reports, sho Players file reports from the Report button in a room's masthead (seated or from the gallery). Each lands in `/var/lib/hnefatafl/feedback.jsonl` -on the droplet (__IP__) pinned with `roomId`, `turn` (the turn +on the droplet (209.97.148.244) pinned with `roomId`, `turn` (the turn being written when it was filed) and `seq` (the ledger's length then), enough to replay the game to the moment. Replies live in the same file and appear under the report in the player's hall. This desk fetches, @@ -34,7 +34,7 @@ Every report and every player's answer rings a Sentry issue (project hnefatafl, fingerprinted per report or per line); the desk's own replies ring nothing. -Fetch: `ssh root@__IP__ 'cat /var/lib/hnefatafl/feedback.jsonl'` +Fetch: `ssh root@209.97.148.244 'cat /var/lib/hnefatafl/feedback.jsonl'` ## a) Fetch and b) display @@ -47,7 +47,7 @@ to hear his players' voices; never compress their words into a table. ## c) Process an unanswered report -1. **Replay to the pin.** `scp root@__IP__:/var/lib/hnefatafl/rooms/.jsonl /` +1. **Replay to the pin.** `scp root@209.97.148.244:/var/lib/hnefatafl/rooms/.jsonl /` and replay it with the engine as `deploy/replay-ledgers.ts` does: `game.create(names, start.seed, start.rules ?? 1)` then `game.resolve(state, line.inputs)` per turn line, printing the state @@ -70,8 +70,8 @@ to hear his players' voices; never compress their words into a table. passing `rules` to `createGame`). Old ledgers then replay as their players saw them; new games get the fix. Either way add a test in `src/lib/game/*.test.ts` pinning the - corrected behaviour. Deploy with `deploy/deploy.sh __IP__`. -5. **Reply.** `bash deploy/report-reply.sh __IP__ "text"`. + corrected behaviour. Deploy with `deploy/deploy.sh 209.97.148.244`. +5. **Reply.** `bash deploy/report-reply.sh 209.97.148.244 "text"`. Pass the id as ONE clean argument. Write to the PLAYER: name what you replayed, cite the rule, and say plainly what was wrong or why nothing was. The desk's voice is warm and specific. diff --git a/.claude/skills/hnefatafl-visitors/SKILL.md b/.claude/skills/hnefatafl-visitors/SKILL.md index 09d404d..8981880 100644 --- a/.claude/skills/hnefatafl-visitors/SKILL.md +++ b/.claude/skills/hnefatafl-visitors/SKILL.md @@ -10,7 +10,7 @@ host glancing over the hall. ## Gather - ssh root@__IP__ hnefatafl-visitors.sh [YYYY-MM-DD] + ssh root@209.97.148.244 hnefatafl-visitors.sh [YYYY-MM-DD] Default is today, UTC; Eric is in US Eastern, so an evening at his desk spills into the next UTC day. Run yesterday too when the hour is early. diff --git a/deploy/README.md b/deploy/README.md index 7f45df2..e457111 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -21,13 +21,13 @@ uptime and upgrades independent of anything else. ## Current production -- Droplet: `hnefatafl` (nyc3, s-1vcpu-512mb-10gb, tag `hnefatafl`), IP __IP__ -- URLs: https://hnefatafl.kestrelsnest.social (A record at Hover, where kestrelsnest.social's - DNS lives) and https://hnefatafl.__IP__.sslip.io (always works, zero DNS). -- Everyday deploy: `deploy/deploy.sh __IP__` +- Droplet: `hnefatafl` (nyc3, s-1vcpu-512mb-10gb, tag `hnefatafl`), IP 209.97.148.244 +- URLs: https://tafl.kestrelsnest.social (A record at Hover, where kestrelsnest.social's + DNS lives) and https://hnefatafl.209.97.148.244.sslip.io (always works, zero DNS). +- Everyday deploy: `deploy/deploy.sh 209.97.148.244` - Players' reports: `deploy/pull-reports.sh` mirrors /var/lib/hnefatafl/feedback.jsonl and the screenshots to ~/Desktop/hnefatafl-reports with a digest; - `deploy/report-reply.sh __IP__ "text"` answers one. + `deploy/report-reply.sh 209.97.148.244 "text"` answers one. ## New droplet from scratch @@ -35,7 +35,7 @@ uptime and upgrades independent of anything else. --size s-1vcpu-512mb-10gb --image ubuntu-24-04-x64 \ --ssh-keys --tag-name hnefatafl --wait` 2. `scp deploy/setup-droplet.sh deploy/Caddyfile.tmpl root@:/root/ && ssh root@ \ - "bash /root/setup-droplet.sh 'hnefatafl.kestrelsnest.social, hnefatafl..sslip.io'"` + "bash /root/setup-droplet.sh 'tafl.kestrelsnest.social, hnefatafl..sslip.io'"` (point the A record at the new IP first, or leave the real name out until it is). 3. `scp deploy/setup-server.sh deploy/Caddyfile.tmpl root@:/root/ && ssh root@ "bash /root/setup-server.sh"` 4. `deploy/deploy.sh ` diff --git a/deploy/hnefatafl-rollup.sh b/deploy/hnefatafl-rollup.sh index 63840e3..d81ffe8 100755 --- a/deploy/hnefatafl-rollup.sh +++ b/deploy/hnefatafl-rollup.sh @@ -57,7 +57,7 @@ for f in sorted(glob.glob("/var/lib/caddy/access*.log*")): elif path == "/guide": pages["guide"] += 1 elif path.startswith("/join/"): joins[path.split("/")[2].upper()] += 1 ref = " ".join(h.get("Referer", [""])) - if ref and "hnefatafl.kestrelsnest.social" not in ref: + if ref and "tafl.kestrelsnest.social" not in ref: refs[re.sub(r"^https?://", "", ref).split("/")[0]] += 1 m = re.search(r"[?&](ref|utm_source|fbclid)=([^&]*)", uri) if m and (ip, m.group(1)) not in seen_campaign: diff --git a/deploy/hnefatafl.service b/deploy/hnefatafl.service index a89afe4..1279ce5 100644 --- a/deploy/hnefatafl.service +++ b/deploy/hnefatafl.service @@ -13,7 +13,7 @@ Environment=KEEPER_TZ=America/New_York Environment=HOST=127.0.0.1 Environment=DATA_DIR=/var/lib/hnefatafl/rooms # Create the Sentry project (the Sentry MCP can) and paste its DSN here; empty means no error reporting. -Environment=SENTRY_DSN=__SENTRY_DSN__ +Environment=SENTRY_DSN=https://2e3589c41ea45a58049740d3af64face@o4509525984149504.ingest.us.sentry.io/4512136791130112 ExecStart=/opt/hnefatafl/app/server/node_modules/.bin/tsx src/index.ts Restart=always RestartSec=3 diff --git a/deploy/pull-reports.sh b/deploy/pull-reports.sh index baddd01..a5b00e7 100755 --- a/deploy/pull-reports.sh +++ b/deploy/pull-reports.sh @@ -3,7 +3,7 @@ # to a local folder, and write a digest beside them for reading. # deploy/pull-reports.sh [host] [folder] (default ~/Desktop/hnefatafl-reports) set -euo pipefail -HOST="${1:-__IP__}" +HOST="${1:-209.97.148.244}" OUT="${2:-$HOME/Desktop/hnefatafl-reports}" mkdir -p "$OUT/images" scp -q "root@$HOST:/var/lib/hnefatafl/feedback.jsonl" "$OUT/feedback.jsonl" 2>/dev/null || : > "$OUT/feedback.jsonl" diff --git a/deploy/sentry-slack-alert.sh b/deploy/sentry-slack-alert.sh index 12a0853..5cbc629 100755 --- a/deploy/sentry-slack-alert.sh +++ b/deploy/sentry-slack-alert.sh @@ -19,9 +19,9 @@ AUTH="Authorization: Bearer $SENTRY_TOKEN" # The Slack action takes the channel's ID as well as its name; the # project's error detector is looked up by project id; a workflow is bound # to its detectors by PUT after creation. -SLACK_ID="${SENTRY_SLACK_INTEGRATION:-__SENTRY_SLACK_INTEGRATION__}" +SLACK_ID="${SENTRY_SLACK_INTEGRATION:-345334}" CHANNEL_ID="${SLACK_CHANNEL_ID:-__SLACK_CHANNEL_ID__}" -PROJECT_ID="${SENTRY_PROJECT_ID:-__SENTRY_PROJECT_ID__}" +PROJECT_ID="${SENTRY_PROJECT_ID:-4512136791130112}" echo "Slack integration $SLACK_ID; project $PROJECT_ID; channel $CHANNEL ($CHANNEL_ID)" DETECTOR_ID=$(curl -s "$API/detectors/?project=$PROJECT_ID" -H "$AUTH" | python3 -c ' diff --git a/deploy/setup-droplet.sh b/deploy/setup-droplet.sh index dc85f6b..c600a89 100755 --- a/deploy/setup-droplet.sh +++ b/deploy/setup-droplet.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # One-time droplet setup. Copy this script and Caddyfile.tmpl to the droplet # and run ON the droplet as root: -# bash setup-droplet.sh 'hnefatafl.kestrelsnest.social, hnefatafl..sslip.io' +# bash setup-droplet.sh 'tafl.kestrelsnest.social, hnefatafl..sslip.io' # The argument is the Caddy site address line: one name, or several # separated by commas. Every name must already resolve to this droplet. # Hnefatafl is a static site: Caddy serves the built files and terminates @@ -28,7 +28,9 @@ chmod -R g+rX /opt/hnefatafl # Caddy: the site and the game server's paths, from the template beside this script. sed "s|__HOST__|$HOST|" "$(dirname "$0")/Caddyfile.tmpl" > /etc/caddy/Caddyfile caddy validate --config /etc/caddy/Caddyfile -systemctl reload caddy +# Validation as root touches the access log; Caddy runs as caddy and must own it. +chown -R caddy:caddy /var/lib/caddy +systemctl restart caddy # Firewall: ssh + web only. ufw allow OpenSSH diff --git a/deploy/verify-ledgers.sh b/deploy/verify-ledgers.sh index 15ce278..e8ca33d 100755 --- a/deploy/verify-ledgers.sh +++ b/deploy/verify-ledgers.sh @@ -6,7 +6,7 @@ # deploy/verify-ledgers.sh [https://site] set -euo pipefail HOST="${1:?usage: verify-ledgers.sh [https://site]}" -SITE="${2:-https://hnefatafl.kestrelsnest.social}" +SITE="${2:-https://tafl.kestrelsnest.social}" DIR="$(mktemp -d)" trap 'rm -rf "$DIR"' EXIT rsync -az "root@$HOST:/var/lib/hnefatafl/rooms/" "$DIR/" diff --git a/deploy/visitors.sh b/deploy/visitors.sh index 74b2ffe..036b1b2 100755 --- a/deploy/visitors.sh +++ b/deploy/visitors.sh @@ -57,7 +57,7 @@ for path in sorted(glob.glob("/var/lib/caddy/access.log*")): elif path_only.startswith("/join/"): joins[path_only.split("/")[2].upper()] += 1 ref = " ".join(req.get("headers", {}).get("Referer", [""])) - if ref and "hnefatafl.kestrelsnest.social" not in ref: + if ref and "tafl.kestrelsnest.social" not in ref: referrers[re.sub(r"^https?://", "", ref).split("/")[0]] += 1 m = re.search(r"[?&](ref|utm_source|fbclid)=([^&]*)", uri) if m: diff --git a/server/src/index.ts b/server/src/index.ts index 63a7ef4..5df08bc 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -31,7 +31,7 @@ import { Store } from './store'; const PORT = Number(process.env.PORT ?? '8789'); const HOST = process.env.HOST ?? '127.0.0.1'; const DATA_DIR = process.env.DATA_DIR ?? '../data/rooms'; -const PUBLIC_URL = (process.env.PUBLIC_URL ?? 'https://hnefatafl.kestrelsnest.social').replace(/\/$/, ''); +const PUBLIC_URL = (process.env.PUBLIC_URL ?? 'https://tafl.kestrelsnest.social').replace(/\/$/, ''); /** The keeper of the site, whom a table may call to a seat, and where they sleep. */ const KEEPER = process.env.KEEPER ?? 'the keeper'; const KEEPER_TZ = process.env.KEEPER_TZ ?? 'America/New_York'; diff --git a/src/app.html b/src/app.html index 7ee3cc1..7bff649 100644 --- a/src/app.html +++ b/src/app.html @@ -7,20 +7,20 @@ Hnefatafl - + - - + + - + %sveltekit.head%