Production: the tafl droplet at 209.97.148.244, tafl.kestrelsnest.social, and the Sentry project

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-23 13:17:09 -04:00
co-authored by Claude Fable 5.1
parent 0d2f54fe02
commit fc62034290
13 changed files with 29 additions and 27 deletions
+6 -6
View File
@@ -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__ <id> <status> "text"` answers one.
`deploy/report-reply.sh 209.97.148.244 <id> <status> "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 <your-key-ids> --tag-name hnefatafl --wait`
2. `scp deploy/setup-droplet.sh deploy/Caddyfile.tmpl root@<ip>:/root/ && ssh root@<ip> \
"bash /root/setup-droplet.sh 'hnefatafl.kestrelsnest.social, hnefatafl.<ip>.sslip.io'"`
"bash /root/setup-droplet.sh 'tafl.kestrelsnest.social, hnefatafl.<ip>.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@<ip>:/root/ && ssh root@<ip> "bash /root/setup-server.sh"`
4. `deploy/deploy.sh <ip>`
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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"
+2 -2
View File
@@ -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 '
+4 -2
View File
@@ -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.<droplet-ip>.sslip.io'
# bash setup-droplet.sh 'tafl.kestrelsnest.social, hnefatafl.<droplet-ip>.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
+1 -1
View File
@@ -6,7 +6,7 @@
# deploy/verify-ledgers.sh <droplet-ip-or-host> [https://site]
set -euo pipefail
HOST="${1:?usage: verify-ledgers.sh <droplet-ip-or-host> [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/"
+1 -1
View File
@@ -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: