joshandClaude Opus 4.7 d21e8187ff Roster parser: header-driven fixed-width, handle Plutonium T5's 9 columns
The raw hex dump showed Plutonium T5's `status` reply is a 9-column
format, not the 5-column shape the parser assumed:

  num score ping guid    name           lastmsg address              qport rate
  --- ----- ---- ------- ---------------- ------- --------------------- ----- -----
    0  3040   36 2741626 JoshWright^7          0 68.235.46.100:28960  -14733 25000

Three bugs in the old regex: it required 16+ hex chars for guid (T5
uses 7-digit decimal IDs), it expected name to run to end-of-line
(4 trailing columns get glued on), and it left Plutonium color codes
(^0..^9) in the rendered name.

Rewrote parsePlayerRoster to detect column boundaries from the header
line word positions instead — column N runs from word N's start to
word N+1's start, matching how CoD's status output actually lays out
(values borrow the padding between adjacent columns). Same code path
handles the old 5-column format and the T5 9-column format cleanly.

stripColorCodes() removes ^\d sequences from names in both dialects.
PlayerRoster component only appends `…` when the guid is actually
long enough to need truncation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-12 11:37:25 -04:00

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.

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

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:

    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.

S
Description
Electric Cherry manages T5 and T6 Plutonium Zombies servers through its own host agents.
Readme
30 MiB
Languages
TypeScript 65.7%
Svelte 23.2%
Python 9.9%
JavaScript 0.5%
Shell 0.5%
Other 0.2%