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.
123 lines
4.3 KiB
Markdown
123 lines
4.3 KiB
Markdown
# electric-cherry
|
|
|
|
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 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 / 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
|
|
|
|
## Layout
|
|
|
|
- `src/lib/server/db/` — schema, migrations, better-sqlite3 handle
|
|
- `src/lib/server/fleet/` — file walking, push/beta engines
|
|
- `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)
|
|
- `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.
|
|
|
|
```bash
|
|
npm install
|
|
npm run seed:fixture
|
|
|
|
# Point env at the fixture (PowerShell)
|
|
$env:SHARED_SCRIPTS = "$PWD/test-fixtures/zombies/shared/scripts"
|
|
$env:SERVERS_ROOT = "$PWD/test-fixtures/zombies/t5"
|
|
$env:DB_PATH = "$PWD/data/cherry.db"
|
|
|
|
npm run dev
|
|
# open http://localhost:3000
|
|
```
|
|
|
|
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
|
|
|
|
```bash
|
|
npm test
|
|
```
|
|
|
|
Covers push (add/update/skip), beta lifecycle (open → rollback, open →
|
|
promote, conflict blocks), sweep (integrations-off manual branch), and
|
|
RCON parsing (fake UDP socket).
|
|
|
|
## Deploy to the Komodo host
|
|
|
|
1. Build the image once locally or on the host:
|
|
|
|
```bash
|
|
docker compose build
|
|
```
|
|
|
|
2. Import `komodo/stack.toml` into Komodo (or point Komodo at the
|
|
`docker-compose.yml` at repo root).
|
|
|
|
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, `entrypoint.sh` runs `scripts/import-legacy.ts` before starting
|
|
the server. The source JSON gets renamed to `.migrated`.
|
|
|
|
5. Load the app on the LAN or over tailscale (no web-app auth by design).
|
|
|
|
## What still lives in the shell script
|
|
|
|
Nothing that's needed at runtime. Move `push_scripts.sh` to `archive/` once
|
|
you've run a side-by-side parity check between the shell's dry-run and the
|
|
app's `POST /api/push { dryRun: true }`.
|
|
|
|
## Non-goals
|
|
|
|
- **Editing GSC files in-browser.** Do that in your editor of choice; the
|
|
web app only cares about what's on disk in `SHARED_SCRIPTS`.
|
|
- **Multi-user auth.** LAN/tailscale scoping is the security model.
|
|
- **Two-way sync with `script_metadata.json`.** The importer runs once. If
|
|
the shell script is ever used again after that, it'll write to the JSON
|
|
file and the web app won't see it.
|
|
|
|
## References into the old script
|
|
|
|
Every port carries a `// Ports push_scripts.sh:NN` comment pointing back to
|
|
the original for the parts that had non-obvious behavior. Grep those if a
|
|
behavior seems wrong — the shell script is the reference implementation
|
|
until the parity check passes.
|