docs(09-04): resolve arena decisions — plain-HTTP :9100 endpoint (no DNS/TLS), full DB copy seed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6a112797a5
commit
de022b7b4e
1
.planning/phases/09-botfights-platform-upgrade/.gitkeep
Normal file
1
.planning/phases/09-botfights-platform-upgrade/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
|
||||
@ -7,26 +7,20 @@ depends_on: []
|
||||
files_modified:
|
||||
- /home/archipelago/Projects/botfight/docker-compose.arena.yml
|
||||
- /home/archipelago/Projects/botfight/docs/arena-deployment.md
|
||||
autonomous: false
|
||||
autonomous: true
|
||||
requirements: [BOT-03]
|
||||
user_setup:
|
||||
- service: godaddy-dns
|
||||
why: "The canonical arena needs a public hostname; archipelago-foundation.org is on GoDaddy nameservers (ns29/ns30.domaincontrol.com) with no wildcard record, so a new A record must be created by the domain owner"
|
||||
dashboard_config:
|
||||
- task: "Create an A record for the arena subdomain pointing at 146.59.87.168"
|
||||
location: "GoDaddy DNS management for archipelago-foundation.org"
|
||||
- service: nginx-proxy-manager
|
||||
why: "TLS termination and public routing on VPS2 live in NPM's own database, not in any git repo"
|
||||
dashboard_config:
|
||||
- task: "Add a Proxy Host: the arena subdomain -> http 146.59.87.168:9100, websockets enabled, SSL forced, request a new Let's Encrypt certificate"
|
||||
location: "http://146.59.87.168:81 (admin lfg2025@proton.me)"
|
||||
# user_setup RESOLVED 2026-07-30: user chose NO DNS/TLS — canonical arena URL is
|
||||
# http://146.59.87.168:9100 (plain HTTP on the raw port). The node→arena hop is
|
||||
# server-side so there is no mixed-content issue; a TLS subdomain can be added later
|
||||
# by changing only ARENA_UPSTREAM_URL (env), no code change. Data seed: FULL COPY
|
||||
# of archi-dev-box's 351 MB botfights.db (user decision, option-c data).
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "One canonical BotFights arena runs on VPS2 (146.59.87.168) in standalone mode — it owns the only real match/fighter database (D-03/BOT-03)"
|
||||
- "The arena answers /api/health on the VPS2 host before any DNS or TLS work begins, so a later public failure is unambiguously a routing problem and not an app problem"
|
||||
- "The arena's JWT signing secret is generated on the VPS2 host, stored 0600 outside git, and never appears in a tracked file or in a log"
|
||||
- "The arena trusts the forwarding headers nginx-proxy-manager sets, so per-IP rate limiting sees real client IPs and not one bucket for the whole internet"
|
||||
- "The arena rate-limits on the direct socket peer IP (no reverse proxy in front — TRUSTED_PROXY deliberately unset), so per-IP limits see real client IPs"
|
||||
- "Lightning/cashu payment features are left unconfigured on the public arena — they are explicitly out of this phase's scope"
|
||||
- "How to redeploy this arena from scratch is written down in the repo, because NPM's routing and the host .env are not git-tracked artifacts"
|
||||
artifacts:
|
||||
@ -37,15 +31,16 @@ must_haves:
|
||||
provides: "The runbook: host paths, port, NPM proxy-host values, DNS record, secret handling, rollback"
|
||||
contains: "146.59.87.168"
|
||||
key_links:
|
||||
- from: nginx-proxy-manager on VPS2 (:80/:443)
|
||||
to: the canonical arena container on 146.59.87.168:9100
|
||||
via: "an NPM proxy host forwarding http to 146.59.87.168:9100 with websockets on and SSL forced — the same shape as the existing demo/source/fips/companion hosts"
|
||||
- from: public internet (node instances + cloud bots)
|
||||
to: the canonical arena container on VPS2
|
||||
via: "direct http://146.59.87.168:9100 (user-chosen: no DNS/NPM/TLS; port 9100 published by docker and open through the host firewall)"
|
||||
pattern: "9100"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Stand up the one canonical BotFights arena on VPS2 and give it a public, TLS-terminated hostname —
|
||||
the shared endpoint every node's instance will proxy to (D-03/BOT-03).
|
||||
Stand up the one canonical BotFights arena on VPS2 at `http://146.59.87.168:9100` —
|
||||
the shared endpoint every node's instance will proxy to (D-03/BOT-03). (User decision 2026-07-30:
|
||||
no DNS/TLS; plain HTTP on the raw port, TLS is a later env-only upgrade.)
|
||||
|
||||
Decision IDs map to `09-CONTEXT.md` **Locked Decisions**: D-01 = BOT-01 signer login,
|
||||
D-02 = BOT-02 unified prompt, D-03 = BOT-03 shared public match endpoint on VPS2,
|
||||
@ -94,8 +89,8 @@ the rootless-podman invariant applies to Archipelago nodes, not to this host.
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:decision" gate="blocking">
|
||||
<name>Task 1: Decide the arena hostname and what data the public arena starts with</name>
|
||||
<task type="checkpoint:decision" gate="blocking" resolved="2026-07-30">
|
||||
<name>Task 1: Decide the arena hostname and what data the public arena starts with — RESOLVED: no hostname (http://146.59.87.168:9100 direct), full database copy</name>
|
||||
<decision>What hostname does the canonical arena use, and what data does it start with?</decision>
|
||||
<context>
|
||||
Two choices must be made before the arena is deployed, because both are baked into the compose
|
||||
@ -146,7 +141,7 @@ the rootless-podman invariant applies to Archipelago nodes, not to this host.
|
||||
<cons>Must be decided now — it is written into the signed catalog in plan 09-06</cons>
|
||||
</option>
|
||||
</options>
|
||||
<resume-signal>Reply with the chosen option id (and the hostname if option-d), e.g. "option-b"</resume-signal>
|
||||
<resume-signal>RESOLVED by user 2026-07-30 via AskUserQuestion: (1) hostname — "can we do it a different way without needing to do that?" → no DNS record; the canonical arena URL is `http://146.59.87.168:9100` directly (server-side proxy hop, no mixed content; TLS subdomain is a later env-only upgrade). (2) data — "Full copy" → copy the entire 351 MB botfights.db from archi-dev-box (read-only export; source file untouched). Do NOT re-ask.</resume-signal>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
@ -180,8 +175,10 @@ the rootless-podman invariant applies to Archipelago nodes, not to this host.
|
||||
built and pushed) and keep the tag in one place so the roll is a one-line edit;
|
||||
- bind `9100:9100`, `restart: unless-stopped`, container name `botfights-arena`, named volume
|
||||
`botfights-arena-data:/app/server/data`;
|
||||
- set `NODE_ENV=production`, `PORT=9100`, `TRUSTED_PROXY=1`, `FIGHT_LOOP_ENABLED=true`,
|
||||
`PUBLIC_ARENA_URL=https://<chosen hostname>`, `JWT_SECRET=${JWT_SECRET}` and
|
||||
- set `NODE_ENV=production`, `PORT=9100`, `FIGHT_LOOP_ENABLED=true`,
|
||||
`PUBLIC_ARENA_URL=http://146.59.87.168:9100`, `JWT_SECRET=${JWT_SECRET}` and
|
||||
OMIT `TRUSTED_PROXY` (no NPM in front — clients hit :9100 directly, so the arena must
|
||||
use the socket peer IP for rate limiting, not forwarded headers), and
|
||||
`BOTFIGHTS_CREATOR_PUBKEYS=${BOTFIGHTS_CREATOR_PUBKEYS:-da5e0c1b646bdb13c2300f805b0ca3e5afe5b052c594ce78bac8978d21c3fa39}`;
|
||||
- deliberately omit `ARENA_UPSTREAM_URL` (this instance IS the upstream) and omit every
|
||||
`BOTFIGHTS_WALLET_ENCRYPTION_KEY` / `BOTFIGHTS_NWC_URL` / `BOTFIGHTS_CASHU_MINT_URL` /
|
||||
@ -219,7 +216,7 @@ the rootless-podman invariant applies to Archipelago nodes, not to this host.
|
||||
- `ssh debian@146.59.87.168 'curl -fsS http://127.0.0.1:9100/api/health'` returns the ok payload.
|
||||
- `ssh debian@146.59.87.168 'sudo docker ps --filter name=botfights-arena --format "{{.Status}}"'` shows `Up` and healthy.
|
||||
- `ssh debian@146.59.87.168 'stat -c %a /opt/botfights-arena/.env'` prints `600`.
|
||||
- `ssh debian@146.59.87.168 'sudo docker inspect botfights-arena --format "{{json .Config.Env}}"' | grep -q 'TRUSTED_PROXY=1'` succeeds and the same output contains no `ARENA_UPSTREAM_URL` entry.
|
||||
- `ssh debian@146.59.87.168 'sudo docker inspect botfights-arena --format "{{json .Config.Env}}"'` contains no `ARENA_UPSTREAM_URL` entry and no `TRUSTED_PROXY` entry (direct exposure — rate limit on socket peer IP).
|
||||
- `grep -c 'BOTFIGHTS_WALLET_ENCRYPTION_KEY\|BOTFIGHTS_NWC_URL\|BOTFIGHTS_CASHU_MINT_URL' /home/archipelago/Projects/botfight/docker-compose.arena.yml` counts only commented lines, and no such variable is present in the container's env output.
|
||||
- `git -C /home/archipelago/Projects/botfight grep -c 'JWT_SECRET=' -- docker-compose.arena.yml` shows only the `${JWT_SECRET}` indirection, never a literal value.
|
||||
- `test -f /home/archipelago/Projects/botfight/docs/arena-deployment.md` and it contains the chosen hostname, `9100`, and the NPM field values.
|
||||
@ -228,50 +225,32 @@ the rootless-podman invariant applies to Archipelago nodes, not to this host.
|
||||
<done>A canonical arena container is running and healthy on VPS2 with a host-generated secret, correct proxy-trust settings, no payment configuration, and the agreed starting data.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-action" gate="blocking">
|
||||
<name>Task 3: Create the arena DNS record and the nginx-proxy-manager host with a Let's Encrypt certificate</name>
|
||||
<what-built>
|
||||
The canonical BotFights arena is live on VPS2 at `http://146.59.87.168:9100` and answering
|
||||
`/api/health`. Two things remain that have no CLI or API path available to Claude: the DNS
|
||||
record at the registrar, and the nginx-proxy-manager routing + certificate.
|
||||
</what-built>
|
||||
<task type="auto">
|
||||
<name>Task 3: Prove the arena is internet-reachable at http://146.59.87.168:9100 (no DNS/TLS — user decision 2026-07-30)</name>
|
||||
<precondition>Task 2 shows the arena healthy on-host.</precondition>
|
||||
<reversibility rating="reversible">Firewall rule (if one is needed) is a single ufw/iptables/OVH
|
||||
entry that can be removed; nothing else changes.</reversibility>
|
||||
<action>
|
||||
Human-only: create the DNS A record for the arena subdomain at the registrar, and create the
|
||||
nginx-proxy-manager proxy host + Let's Encrypt certificate that fronts `146.59.87.168:9100`.
|
||||
Neither has a CLI or credentialed API path available to Claude — the domain is on GoDaddy
|
||||
nameservers and the NPM admin password is not held by the agent.
|
||||
The user chose to skip DNS + NPM + Let's Encrypt entirely: the canonical arena URL IS
|
||||
`http://146.59.87.168:9100`. This works because the node→arena hop is a server-side proxy
|
||||
(no browser mixed-content) and cloud bots speak server-to-server. TLS can be added later by
|
||||
fronting the same port with an NPM host and changing only `ARENA_UPSTREAM_URL` — no code change.
|
||||
|
||||
Verify reachability from OFF the VPS: from archi-dev-box, `curl -fsS --max-time 10
|
||||
http://146.59.87.168:9100/api/health`. If it fails, check and fix host-level firewalling on
|
||||
VPS2 (`sudo ufw status`, iptables, and note OVH network-level firewall may need the user).
|
||||
Record the final reachable URL + any firewall change in `docs/arena-deployment.md` and in
|
||||
09-04-SUMMARY.md — plans 09-05/09-06/09-07 read the arena URL from there.
|
||||
</action>
|
||||
<instructions>
|
||||
Please do these two things, then reply.
|
||||
|
||||
**1. DNS (GoDaddy — `archipelago-foundation.org` uses ns29/ns30.domaincontrol.com).**
|
||||
Add an `A` record: host = the chosen arena subdomain label (e.g. `arena`), value =
|
||||
`146.59.87.168`, TTL 600. There is no wildcard record on this domain, so this step cannot be
|
||||
skipped.
|
||||
|
||||
**2. nginx-proxy-manager (http://146.59.87.168:81, admin `lfg2025@proton.me`).**
|
||||
Hosts → Proxy Hosts → Add Proxy Host, matching the existing entries exactly:
|
||||
- Domain Names: the full arena hostname
|
||||
- Scheme: `http`; Forward Hostname/IP: `146.59.87.168`; Forward Port: `9100`
|
||||
- Enable **Websockets Support** (the live fight stream needs it) and Block Common Exploits
|
||||
- SSL tab: request a new Let's Encrypt certificate, enable **Force SSL** and HTTP/2, agree to the ToS
|
||||
|
||||
If you can instead give Claude the NPM admin password, it can create the proxy host through the
|
||||
NPM API and only the DNS record needs doing by hand.
|
||||
|
||||
**If the DNS record is not possible before the demo**, say so — the fallback is to run the demo
|
||||
against `http://146.59.87.168:9100` directly. A cloud bot works fine over plain HTTP; the cost
|
||||
is that credentials cross the internet unencrypted, so it is a demo-only contingency and the
|
||||
manifest in plan 09-06 would carry the plain-HTTP URL until DNS lands.
|
||||
</instructions>
|
||||
<verification>
|
||||
Once you reply, Claude confirms it automatically:
|
||||
`dig +short <arena-host> A` returns `146.59.87.168`, and
|
||||
`curl -fsSI https://<arena-host>/api/health` returns 200 over a valid certificate chain (no
|
||||
`--insecure`). Both are re-asserted as the precondition of plan 09-05 Task 1, which will not
|
||||
proceed until they pass.
|
||||
</verification>
|
||||
<resume-signal>Reply "dns+proxy done" (or "fallback: plain http", or paste the NPM admin password for Claude to automate the proxy host)</resume-signal>
|
||||
<verify>
|
||||
<automated>curl -fsS --max-time 10 http://146.59.87.168:9100/api/health | grep -q '"status":"ok"'</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `curl -fsS http://146.59.87.168:9100/api/health` succeeds from archi-dev-box (off-host).
|
||||
- `curl -fsS http://146.59.87.168:9100/api/bots` returns the seeded fighters (full-copy decision: expect 115 bots).
|
||||
- `docs/arena-deployment.md` records the plain-HTTP decision and the later-TLS upgrade path.
|
||||
</acceptance_criteria>
|
||||
<done>The canonical arena is publicly reachable at http://146.59.87.168:9100 with the full data copy live.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@ -290,7 +269,7 @@ the rootless-podman invariant applies to Archipelago nodes, not to this host.
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-09-16 | Information disclosure | credentials over plain HTTP on the raw port | high | mitigate | NPM terminates TLS with Force SSL on; the raw port exists for host-level verification and as an explicit, user-acknowledged demo fallback recorded in the runbook |
|
||||
| T-09-16 | Information disclosure | credentials over plain HTTP on the raw port | high | accept (user decision 2026-07-30) | User explicitly chose plain HTTP on :9100 over DNS/TLS setup; recorded in runbook with the env-only TLS upgrade path for later |
|
||||
| T-09-17 | Information disclosure | `JWT_SECRET` leaking into git, logs or `docker inspect` transcripts | high | mitigate | Generated on the host into a 0600 `.env` outside any repo; the compose file only carries `${JWT_SECRET}`; the value is never echoed |
|
||||
| T-09-18 | Spoofing | forged `X-Forwarded-For` from a direct caller to :9100 bypassing per-IP limits | medium | accept | `TRUSTED_PROXY` is required for the arena to honour those headers and NPM overwrites `X-Real-IP` for traffic through 443; a direct caller can already choose its own source IP cheaply, so this adds no meaningful capability |
|
||||
| T-09-19 | Denial of service | anonymous bot-registration flooding the public arena | medium | mitigate | Existing `rateLimit(3600_000, 5)` on `POST /api/bots`, now meaningful per real client IP thanks to `TRUSTED_PROXY` |
|
||||
@ -303,8 +282,8 @@ the rootless-podman invariant applies to Archipelago nodes, not to this host.
|
||||
- `ssh debian@146.59.87.168 'curl -fsS http://127.0.0.1:9100/api/health'` — ok payload.
|
||||
- `curl -fsS http://146.59.87.168:9100/api/health` from archi-dev-box — ok payload (or a recorded
|
||||
firewall finding).
|
||||
- After the checkpoint: `curl -fsSI https://<arena-host>/api/health` returns 200 with a valid
|
||||
certificate chain — this is asserted in plan 09-05 Task 1, which gates the rest of the demo path.
|
||||
- `curl -fsS http://146.59.87.168:9100/api/health` returns 200 from off-host — this is asserted
|
||||
again as plan 09-05 Task 1's precondition, which gates the rest of the demo path.
|
||||
- Supports `09-VALIDATION.md`'s manual-only item "Cross-node fighter visibility" by providing the
|
||||
shared endpoint that item depends on.
|
||||
</verification>
|
||||
@ -312,8 +291,8 @@ the rootless-podman invariant applies to Archipelago nodes, not to this host.
|
||||
<success_criteria>
|
||||
- One canonical arena container runs on VPS2 with a host-generated secret and no payment config.
|
||||
- Its compose definition and full runbook are committed to the botfight repo.
|
||||
- The hostname and starting-data decisions are recorded in the SUMMARY.
|
||||
- DNS + NPM proxy host + certificate are done, or the plain-HTTP contingency is explicitly accepted.
|
||||
- The full-copy data seed is live (115 bots visible via /api/bots) and the source DB on archi-dev-box is untouched.
|
||||
- The arena is internet-reachable at http://146.59.87.168:9100 (user-accepted plain HTTP; TLS is a later env-only upgrade).
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user