icon.png replaces the scaffold placeholder — resized to 512x512 and palette-quantized (1.9MB source -> 52.9KB) to stay close to the packaging guide's 40 KiB guidance with no visible quality loss at icon size. Verified station-master_x86_64.s9pk builds clean with a single icon.* file (the packer errors on more than one) and `s9pk inspect` confirms icon.png is packed correctly. The `ui` interface now opens at `/play.html` instead of `/`. Root serves index.html, the game's marketing splash — identical to the public static solitaire site, and with no lobby or Multiplayer button on it; both live on play.html (src/web/main.ts's `start()`, reached from index.html's door link). That page is right for a stranger landing on the public site, wrong for someone who just installed a dedicated multiplayer server and found what looked like the same solitaire page with no way to host or join a game. Verified: fetching /play.html from inside the container and over the service's public address both return the page with the Multiplayer button present. Reinstalled on phoenix.local after each change.
6.9 KiB
Station Master on StartOS
Everything not listed in this document should behave the same as upstream Station Master. If a feature, setting, or behavior is not mentioned here, the upstream documentation is accurate and fully applicable — see the Documentation section of
instructions.mdfor links.
Station Master is a railroad operations board game with an authoritative multiplayer server; solitaire needs no server and is not what this package is for. This package runs that server — the browser client, the lobby, and the intent/SSE API — as a single StartOS service.
Table of Contents
- Image and Container Runtime
- Volume and Data Layout
- File Models
- Dependencies
- Network Access and Interfaces
- Installation and First-Run Flow
- Actions
- Tasks
- Health Checks
- Backups and Restore
- Limitations and Differences
- Quick Reference for AI Consumers
Image and Container Runtime
Built from source with a custom Dockerfile — there is no published Station Master image.
| What to Document | Value |
|---|---|
| Image source | Custom multi-stage Dockerfile: node:*-slim builder runs npm run build:web to produce the static client, then a second node:*-slim stage copies only package.json, src/, and the built dist/ — no node_modules in the runtime stage, since the server has zero runtime dependencies |
| Architectures | x86_64, aarch64 |
| Entrypoint | node src/server/index.ts — runs straight from TypeScript source; no compile step, see Limitations |
One subcontainer: station-master-sub, running the single daemon server.
Volume and Data Layout
One volume, data, mounted at /data (DATA_DIR).
| What to Document | Value |
|---|---|
| Volume names | data |
| Mount points | /data |
| StartOS files | store.json — holds the join secret (see File Models) |
| Database | None — flat files. Each game is games/<gameId>/game.json ({ engineVersion, seed, config, playerNames, history, status, createdAt }) plus turn-timings.json, an index.json naming every game, and lobby state for games not yet started |
File Models
One StartOS-managed file, store.json, on the data volume.
store.json— JSON, holdsjoinSecret(a string). Seeded on install with a random 24-character value (init/generateJoinSecret.ts) — the daemon will not start without one, so it is never left unset. Rewritten only by the Get Join Secret action, which generates a new value on every run; the value is otherwise never touched, so a hand edit to it survives until the action is next run.main.tsreads it reactively, so a rewrite restarts the daemon with the new value automatically.
Everything else under /data — games/, index.json, lobby and session state — is the
application's own persistence, written directly by the server process, not by a StartOS file
model.
Dependencies
None.
Network Access and Interfaces
One interface, ui, on the single port the daemon listens on. It serves the browser client, the
lobby and intent HTTP API, and the per-seat SSE game stream — all from the same origin, which is
what lets a player reach the server from a LAN address and another player reach it from a
different one in the same game.
Installation and First-Run Flow
No setup wizard. On install, a join secret is generated and stored immediately (see File Models), and a critical task is raised pointing at Get Join Secret — the service starts and is usable the moment the daemon is healthy, but a player cannot create or join a game until the join secret has been retrieved and shared with them.
Actions
- Get Join Secret (
get-join-secret) — run this any time you want to read the current join secret, or to invalidate it and issue a new one. Every run generates a fresh secret, overwrites the stored one, and restarts the daemon with it — there is no read-only mode. Rotating does not disconnect players already seated in a running game (D14's join secret gates the lobby door, not an in-progress session); it only invalidates the old value for anyone who has not yet joined or created a game. Completes in a few seconds, safe to repeat.
Tasks
- Get the join secret to share with players — raised on install, severity
critical. Points at the Get Join Secret action. Clears the moment that action is run for the first time; it does not return afterward (running it again to rotate does not re-raise it).
Health Checks
- Multiplayer Server —
checkPortListeningagainst the daemon's port. "Not ready" means the process has not yet bound its port; on a normal start this clears within a second or two; on a cold container start it briefly reflects the daemon starting up.
Backups and Restore
Strategy: the entire data volume is backed up wholesale (ofVolumes) — every file copied
exactly as it sits on disk, nothing dumped and replayed.
That includes every game's full intent history, so a restore can resume any in-progress game
exactly where it left off, and the join secret in store.json, so previously shared join links
keep working after a restore. Nothing needs to be rebuilt or re-entered after a restore; the
server resumes every saved game on boot the same way it does after an ordinary restart.
Limitations and Differences
- No compile step. The image runs the server directly from TypeScript source using Node's
native type stripping, rather than building a
dist/for the server the way the browser client is built. This is upstream's own deployment shape (docs/architecture/deployment.mdin the game repo), not something this package changed. - The join secret has no per-player identity. It is a single server-wide value (D14) that gates who may create or join a game — it is not a username or password, and StartOS has no visibility into who a player is once they've joined.
- No accounts, and one game at a time per person is expected but not enforced — a deliberate
upstream design decision (
docs/architecture/multiplayer.md§11 D13), not a StartOS-specific limitation.
Quick Reference for AI Consumers
package_id: station-master
image: built from source (Dockerfile)
architectures: [x86_64, aarch64]
subcontainers: [station-master-sub]
volumes:
data: /data
file_models:
- store.json
startos_managed_env_vars:
- DATA_DIR
- JOIN_SECRET
dependencies: none
interfaces:
ui: { type: ui, port: 8081 }
actions:
- get-join-secret
tasks:
- { action: get-join-secret, severity: critical }
health_checks:
- Multiplayer Server