2026-09-12 19:34:25 -04:00
2026-09-12 19:34:25 -04:00

Electric Cherry

Web app that manages a fleet of T5 and T6 Plutonium Zombies servers running under Komodo.

Fleet membership lives in SQLite. EC registers servers during creation and removes them during destruction; startup does not discover or import server folders. Existing database registrations are preserved, and missing mounts do not remove servers from the fleet. The legacy seedServerRegistry helper is an explicit import utility only.

Status refreshes check Komodo container readiness before polling RCON. Starting or restarting containers show Starting, without old RCON errors or player data. Stopped, unhealthy, or unverifiable containers are not polled. Containers without a Docker health check use their running state as the readiness signal.

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, campaigns, server 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

Betas support configurable gameplay, participation, stability and manual-review requirements, saved presets, coverage rules and staged rollout. See Beta test plans for usage and the session telemetry contract used by round and custom-feature checks.

File changes are staged sequentially. Their activation uses rolling restarts: by default only one server across the fleet can restart or recover at a time. Promotions, rollbacks, drift reconciliation, and beta restarts share this limit. Set DEPLOYMENT_RESTART_CONCURRENCY to a positive integer to raise it.

A background sweep runs every 15s. It checks recovering servers first, then admits queued deployments in oldest-first order when there is capacity. Busy or unreachable servers stay queued without taking a slot, so other empty servers can proceed. A successful Komodo restart request retains its slot until the container is healthy and RCON answers. Health checks start no sooner than 15 seconds after the request returns, avoiding an immediate check of the old process. An unhealthy server keeps its slot rather than letting failures spread across the fleet. A timed-out restart also keeps its slot because Komodo may have accepted the request.

Slots persist in SQLite across app restarts, and overlapping timer/API triggers share the active run. Beta options to skip health or match gates still work; skipping a beta gate does not release the fleet's recovery slot early. New-server creation is separate from deployment restarts and still starts its container directly.

No token, no session, no auth freeze. If Komodo or RCON aren't configured, the sweep marks pending rows manual so nothing hangs forever.

Deployment blueprints and launch previews

Deployments now includes a blueprint library and a two-step launch composer. Preview a promoted game/map setup, save its managed files as a reusable blueprint, or launch again from a recorded deployment. Previews check configuration and available capacity; every queued creation retains its own launch recipe. The progress page explains waiting conditions, shows elapsed time, and can resume health verification of an interrupted creation when its original server remains. See Deployment blueprints for the workflow, reproducibility boundaries, and API details.

Saved server keys

Use Keys to add, replace, or remove a Plutonium key for an exact server ID such as t5_zombie_theater_2. IDs use a supported map slug and a lobby number from 1 to 20; the server does not need to be deployed first. During deployment, choose the map. The lowest available lobby is assigned automatically, with the exact server ID shown before deployment. Its saved key is reused automatically, and any newly entered key is saved for that ID, including if deployment fails. Destroying a server preserves its saved key. Updating or removing a saved key does not change a running server; changes apply to future deployments.

Keys are stored in the application's SQLite database at DB_PATH, independently of server records. Preserve that database across container replacements. Key values are not returned by the management API, but are stored unencrypted in SQLite, so database files and backups contain credentials. Existing deployment keys are not automatically imported from Komodo; add them once to retain them.

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.

Map selection and configs

The map selected when deploying a T5 zombies server controls its startup rotation automatically. No map-choice config overlay is required. New servers use the promoted game baseline and matching overlays when available; otherwise Plutainer seeds its usual defaults and Electric Cherry applies the selected map through a generated configs/electric_cherry_map.cfg, loaded after the main config and before map rotation. This file is tracked and restored by drift reconciliation; operators do not need to create or promote it. The launch order uses Plutainer's extra arguments and map-rotation controls. Plutainer's default main config is preserved until a game baseline is promoted.

Beta previews, promotion, and config drift repair all apply the server's stored catalog map after composing the baseline and overlays. An old sv_maprotation overlay cannot override that selection, but its other settings still apply. Existing servers receive this behavior on their next config deployment or drift reconciliation; this change does not immediately rewrite or restart them. Servers with an unknown map retain their existing config behavior. Rollback restores the exact prior config bytes.

T6 Zombies (Black Ops II)

Choose Black Ops II (T6) in Deploy new server. T6 uses t6_zombie_<slug>_<lobby> IDs, its own saved Plutonium keys, PLUTAINER_GAME=t6zm, and configs/dedicated_zm.cfg. Existing T5 deployments and environment settings keep their current paths. API clients can send game: "t6"; omitting it selects T5.

Before the first deployment, populate the Docker volume t6_game_files on the Komodo host with your Black Ops II game files, including the DLC maps you intend to host. Set PLUTAINER_T6_GAMEFILES_VOLUME to use another volume or an absolute host directory. It is mounted read-only at /home/plutainer/gamefiles. Plutainer supplies its runtime and config seeds, not the base game files. Use a Plutonium server key created for T6 Zombies.

Server state defaults to /root/zombies/t6, or the sibling t6 directory beside SERVERS_ROOT; override with T6_SERVERS_ROOT. Electric Cherry and the Komodo host must see this directory at the same absolute path. The supplied Compose mount already covers both games. GSC files go to runtime/plutonium/storage/t6/scripts/zm (no raw/), and game logs use logs/games_zm.log. Optional overrides are T6_REL_DEST_SCRIPT and T6_CONFIG_TARGET_FILE; a custom config filename needs a promoted baseline or a file supplied at that name, since Plutainer seeds dedicated_zm.cfg.

The catalog includes the classic maps, Survival, Grief, and Turned selections. Rotations use execgts <ruleset>.cfg map <engine-map>; Plutainer seeds the required storage/t6/gamesettings files. Keep seeding enabled, including with a promoted baseline. Town, Farm, and Bus Depot share zm_transit, so EC keeps their selection in map_slug through creation, beta, promotion, and drift repair. Map-scoped artifacts target the engine map and therefore apply to all its variants. Creation health checks verify the engine map reported by RCON; this does not independently verify the start location within that map.

Both games share the fleet's port allocator (SERVER_PORT_MIN/SERVER_PORT_MAX) to prevent collisions. EC explicitly sets PLUTAINER_PORT, so T6 need not use its upstream default of 4976. Forward the allocated UDP ports. Use a current Plutainer v2 image: EC relies on PLUTAINER_RCON_PASSWORD, PLUTAINER_MAP_ROTATE=false, and extra arguments loaded after the main config. Current Plutainer automatically whitelists the Docker gateway for T5/T6 RCON; an admin client on another host needs its address allowed in the server config.

Implementation references (checked September 2026):

Round telemetry experiment

Send POST /api/telemetry with Content-Type: application/json to the Electric Cherry host (port 3000 in local development):

curl http://localhost:3000/api/telemetry \
  -H 'Content-Type: application/json' \
  -d '{"event":"round_change","round":17,"server":"t5_zombie_prototype_1","players":4}'

The server must already be registered in Electric Cherry. round and players are required nonnegative integers. Success returns HTTP 200 with { "ok": true, "telemetry": { "server": "...", "round": 17, "players": 4, "receivedAt": "..." } }. Invalid payloads return 400, unknown/destroyed servers 404, and non-JSON content types 415. This endpoint uses the app's existing LAN/Tailscale access model; there is no API key requirement.

The latest report is saved across app restarts and streamed immediately to the dashboard and server detail page, including after browser reconnection. The UI labels it as the last reported round with its age: round-change events alone cannot detect a stopped script or a match ending. Send the initial round on each new match, then each round change. The latest received request wins (including lower rounds for new matches); there is no event ordering or history yet. Telemetry player counts are displayed with the report and never replace RCON occupancy or affect automated restart gates.

Running 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/T6 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).

Campaigns and server deployments

Campaigns (/campaigns) contains script/config rollouts, drift reconciliation, restart progress, file diffs, and rollbacks. Existing rollout records are preserved in the original database tables. The campaign API lives at /api/campaigns; legacy rollout detail API endpoints remain available for compatibility.

Deployments (/deployments, /api/deployments) now tracks server creations and destructions with live stages and persistent history, including failures, warnings, and interrupted operations. History starts with operations performed after this update and survives server removal. New creation requests return HTTP 202 with a deployment ID and open the deployment detail page. Creation is complete only when Komodo reports healthy, RCON responds successfully, and the RCON map exactly matches the selected map. Queued requests reserve their server name and port; queued creation and pending health checks survive application restarts. The detail page records startup progress and explains any health or map mismatch that is still blocking completion.

Each server deployment opens a detail page with its flow and timestamped activity. Destroy uses an in-app confirmation modal and immediately returns a queued deployment (DELETE /api/servers/:name returns HTTP 202 with deploymentId). The worker waits for a fresh RCON player count of zero before teardown; occupied or unreachable servers remain queued and are checked each worker tick. Waiting requests survive application restarts, while interrupted teardown is marked for inspection. Completed destruction stays on the deployment detail page.

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%