Replace IW4MAdmin with direct RCON for player counts

IW4M kept hitting a network wall (container -> host LAN IP -> IW4M
loopback bind) that we couldn't cleanly punch through, and the two-minute
!rt token added enough operational friction that the pause-all-deployments
gate never got a fair test. RCON gives us the same fact — how many
players are on a server right now — direct from each game server over
UDP, with no session, no token, no proxy.

- Add integrations/rcon.ts: dgram UDP client. rconGetPlayerCount() and
  rconGetStatus() parse both `status` (print + scoreboard) and
  `statusResponse` (info line + rows) dialects. Timeout via
  RCON_TIMEOUT_MS.
- Add komodoResolveGamePort() + extractUdpPort(). Reads Komodo's read
  API for a server, walks both docker-compose-style port objects and
  docker-run-style "host:container/udp" strings, caches into
  servers.game_port on first success.
- Replace pipelineReady() guard in sweep with a simple
  integrations-configured check. Same held/failed/manual outcomes,
  but keyed on RCON+Komodo instead of IW4M+auth-state.
- Worker's status refresh fans out per-server over RCON at concurrency=4
  so a 15-server fleet doesn't spike UDP.
- Schema: drop iw4m_match/iw4m_value, add game_port + rcon_host.
- Delete every auth artifact: TokenModal, auth store, auth-state module,
  /api/auth/token, /api/events/auth, iw4m.test.ts. Layout no longer
  mounts the modal, pipeline store no longer tracks a frozen state.
- README + .env.example + docker-compose.yml + komodo/stack.toml swap
  IW4M_* env for RCON_PASSWORD / RCON_HOST / RCON_TIMEOUT_MS. Same
  host.docker.internal:host-gateway extra_hosts entry, repurposed
  for RCON reach.
- Tests: add rcon.test.ts and komodo.test.ts, update sweep.test.ts to
  drop the auth-freeze case. 23/23 passing.
This commit is contained in:
2026-09-12 01:33:06 -04:00
parent f118c179cd
commit 854f5532a9
27 changed files with 530 additions and 730 deletions
+39 -18
View File
@@ -2,15 +2,17 @@
Web app that manages a fleet of T5 Plutonium servers running under Komodo.
Successor to `push_scripts.sh` — same job (push shared GSC scripts, run beta
tests, restart empty servers, track deployments) with a live UI, real audit
diffs, and a hard pause when the IW4MAdmin `!rt` token goes stale.
tests, restart empty servers, track deployments) with a live UI and real
audit diffs.
## Stack
- SvelteKit (adapter-node) + TypeScript
- SQLite via better-sqlite3 (replaces `script_metadata.json` as the source of
truth)
- Server-sent events for live pipeline / auth-state / server-status streams
- Server-sent events for live pipeline / server-status streams
- RCON (UDP) direct-to-game-server for player counts
- Komodo API for restarts and per-server game-port discovery
- Runs as a single Komodo-managed container on the same host as the game
servers, with `/root/zombies` bind-mounted in
@@ -18,15 +20,28 @@ diffs, and a hard pause when the IW4MAdmin `!rt` token goes stale.
- `src/lib/server/db/` — schema, migrations, better-sqlite3 handle
- `src/lib/server/fleet/` — file walking, push/beta engines
- `src/lib/server/integrations/` — Komodo, IW4MAdmin, `!rt` token state
- `src/lib/server/integrations/` — Komodo, RCON
- `src/lib/server/pipeline/` — sweep loop, event bus, worker singleton
- `src/routes/api/` — REST + SSE endpoints
- `src/routes/` — UI pages (dashboard, deployments, betas, per-server)
- `docker/` — Dockerfile, entrypoint, docker-compose
- `Dockerfile`, `docker-compose.yml`, `entrypoint.sh` — container build
- `komodo/` — Komodo stack definition
- `scripts/` — legacy JSON importer, fixture seeder
- `tests/` — vitest unit tests
## How restarts stay safe
Every push (or beta open) creates a `deployment` row and marks each affected
server `pending`. A background sweep runs every 15s and:
1. Asks each server over RCON: how many players are on right now?
2. If 0 → tell Komodo to restart the server → row goes `done`.
3. If >0 → hold the row `pending` with the player count; try again next tick.
4. If RCON unreachable → hold `pending` with `error='rcon unreachable'`.
No token, no session, no auth freeze. If Komodo or RCON aren't configured,
the sweep marks pending rows `manual` so nothing hangs forever.
## Local dev
Windows or macOS — no `/root/zombies` on disk, so seed a fixture tree first.
@@ -44,10 +59,9 @@ npm run dev
# open http://localhost:3000
```
The `!rt` modal will block deployments until you paste a valid token. In
local dev you can bypass it for exploration by leaving `IW4M_ADDRESS` unset
and calling `sweep()` in a test that force-transitions the auth state (see
`tests/unit/sweep.test.ts`).
Without RCON_PASSWORD / KOMODO_* set, the pipeline marks pending rows
`manual` after a sweep — the UI still works, you just don't get real
restarts. Enough to develop the push/beta flow.
## Tests
@@ -56,8 +70,8 @@ npm test
```
Covers push (add/update/skip), beta lifecycle (open → rollback, open →
promote, conflict blocks), sweep (frozen / manual branches), and IW4M
login/status parsing (fetch mocked).
promote, conflict blocks), sweep (integrations-off manual branch), and
RCON parsing (fake UDP socket).
## Deploy to the Komodo host
@@ -67,16 +81,23 @@ login/status parsing (fetch mocked).
docker compose build
```
2. Import the shape of the stack into Komodo using `komodo/stack.toml` (or
copy the compose file into a Komodo stack).
2. Import `komodo/stack.toml` into Komodo (or point Komodo at the
`docker-compose.yml` at repo root).
3. First boot triggers the legacy import: if
3. Set the env vars in Komodo:
```
KOMODO_ADDRESS, KOMODO_API_KEY, KOMODO_API_SECRET
RCON_PASSWORD # single shared password across every T5 server
RCON_HOST # optional; defaults to host.docker.internal
```
4. First boot triggers the legacy import: if
`/root/zombies/script_metadata.json` exists and `/data/cherry.db` is
empty, `docker/entrypoint.sh` runs `scripts/import-legacy.ts` before
starting the server. The source JSON gets renamed to `.migrated`.
empty, `entrypoint.sh` runs `scripts/import-legacy.ts` before starting
the server. The source JSON gets renamed to `.migrated`.
4. Load the app on the LAN or over tailscale (no web-app auth by design).
Paste an `!rt` token to unfreeze the pipeline.
5. Load the app on the LAN or over tailscale (no web-app auth by design).
## What still lives in the shell script