Files
firmware/docs/notes
Oleksandr ShyshatskyiandClaude Opus 5 a430aeefc2 Moonraker is supervised, and ready means the API is answering
The third and last service of this phase, written as a sibling of nginx and
the camera rather than as a new idea: a run script that execs moonraker in
the foreground under $FF_PYTHON with the old start-stop-daemon arguments and
the -b removed, a shipped `down` file because MOD_WEB is read at runtime while
the payload is built once, and S62moonraker kept as a thin wrapper so the
verbs and the vocabulary the gate greps for do not change.

Readiness is :7125 in TCP_LISTEN, with the port parsed out of the [server]
section of moonraker.conf so a user who moved it does not get a healthy
printer reporting itself broken. The two alternatives are argued in the file
and rejected: "forked" is what the pidfile already measured, badly, and
"klippy_state is ready" is wrong -- moonraker reporting klippy_state=error is
moonraker doing its job, so that definition would fail to fire on exactly the
broken printer whose UI you need to reach. The gate proves the distinction
rather than assuming it: s6-svwait -U was still blocking while s6-svstat
already said up, and returned at 46s against a 40s stand-in delay, after
which /server/info answered.

With no scanner moonraker falls back to starting directly, as nginx does and
unlike the camera. MOD_WEB is one switch across both scripts, so nginx up
with moonraker refusing invents a third state nobody chose and is the worst
of the three to debug -- Mainsail loads and blames Klipper. And ff-startup.py
drives the whole boot off moonraker's API, so refusing would turn a failed s6
build into a printer that cannot bring itself up or say why. Starting twice
is not possible: the `down` file plus the run script's sweep of any leftover
moonraker see to that.

The deferred config move is why -d /usr/data is still here, marked as the one
line phase 7 changes. klipperDaemon is FlashForge's script and hardcodes
KLIPPER_CONF=/usr/data/config/printer.cfg, so the directory cannot move until
we own the launch; docs/notes/80-s6-migration.md now says so and carries the
argument for moving it into phase 7a rather than losing it.

Four bugs in the gate itself, found by running it and fixed there, each now
commented. s6-svstat -p hands over the pid at FORK, so a check that read
/proc/PID/cmdline saw `/bin/sh ./run` and not the interpreter -- it waits for
the exec now, and a run script that never execs is a failure rather than a
race. The same fork-is-not-ready gap made a supervised nginx look dead. A
`start | sed` blocked for 43 seconds because the detached readiness reporter
holds the pipe open. And a churning service reports pid -1 at every sample,
so "the pid keeps changing" read as "the pid is stable" -- churn is measured
by maximum age from s6-svstat now, which is what the negative control needed
to mean anything.

gates.moonraker() hands over the real s6 when one exists but DEGRADES rather
than skipping when one does not, which is the opposite of the nginx and
camera gates and deliberate. Those two are about supervision and have nothing
to say without a supervisor. This one is the only gate that runs the shipped
Moonraker at all, so skipping it in a checkout that has not built s6 would
take the component-import checks with it and let a broken pin through.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:21:34 +03:00
..