A fleet gets one package, and exactly one box per site should host the server.
Defaulting to on would mean every player that ever received this package
started listening on 8181, and the mistake would stay invisible until two of
them fought over the same displays.
st-config.json on the storage root, {"server": 1}, switches it on. Absent,
unreadable, unparseable, or anything other than an affirmative value leaves it
off - there is no reading of a broken config file that should end with a
device deciding to host a server. It sits at the root rather than in data/
because that is where an operator drops it over the DWS, and autozip never
writes it, so a re-provision cannot silently flip a site either way. The
package ships st-config.example.json, never st-config.json, for the same
reason.
With the server off, NOTHING listens: roNodeJs is never created, so there is
no 8181 and no 8182. The page is told through its URL rather than discovering
it, because "nothing is answering" would otherwise render as a fault and send
someone looking for a server that was never meant to exist. It now has a
fourth state that says so and offers the one line of JSON that changes it.
Separately: the status listener was bound to every interface, so anything on
the customer's LAN could read the install log, disk usage, the device's own
address and a tail of the server's console - that last one carries whatever
the server printed most recently. Its only consumer is a page on the same
device. Now 127.0.0.1 only, confirmed against /proc/net/tcp rather than by
probing, after a first attempt at verifying it fell back to loopback and
reported that as the LAN result.
Claude-Session: https://claude.ai/code/session_014kfhrUPit5MCqxeTQyqr56
Co-authored-by: Dan Walters <dan.walters@bytetinker.net>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The box is now both server and player, so its screen has to be one or the
other at any moment. Three states, and the transitions are the point:
installing / down / failed diagnostics, so the fault is visible
up, but no account yet diagnostics plus the address to create one
up, and an account exists the player, full screen
A fresh install has nothing to play and nobody to play it for, so it stays on
the configuration screen until someone has signed up. Hiding that address
would leave the device unsetuppable: it has no keyboard.
⚠️ THE PLAYER IS AN IFRAME LAYER, NOT A NAVIGATION. Setting location.href
would replace the document and take the poller with it - and that poller is
the only thing able to notice the server failing later. As a layer, the
diagnostics are one style change away from being back on screen, which is
exactly what should happen when a server that has been playing for weeks
throws at 3am. A test asserts location.href is never assigned, so this cannot
be quietly simplified back.
Whether an account exists is asked by the wrapper, not the page:
/api/auth/config is public, but the page is loaded from file:// - origin
"null" - and the server sets no CORS headers on its own API, while this
process is already talking to it. The answer is three-valued. null means the
probe has not replied yet and is deliberately NOT treated as false: guessing
would flip a fresh box to an empty player and take the sign-up address off
the screen while someone was reading it.
Verified against a real server rather than by inspection - install, sign up,
watch it flip:
BEFORE signup : needsSetup=null -> diagnostics
POST /api/auth/register -> HTTP 201
AFTER signup : needsSetup=false -> player
Claude-Session: https://claude.ai/code/session_014kfhrUPit5MCqxeTQyqr56
Co-authored-by: Dan Walters <dan.walters@bytetinker.net>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>