Commit Graph
3 Commits
Author SHA1 Message Date
Oleksandr ShyshatskyiandClaude Opus 5 7ffb39a730 The last ten files still named run-pre.sh and run-append.sh
Both were deleted three commits ago and the sweep that followed missed these:
the recipes, the s6-rc service scripts, printer.base.cfg's header and three qa
modules all still pointed at them by name. Renamed to runFirmwareExe.sh, which
is the file that actually does each thing described.

Two needed more than a rename.

anvil-link-prog.sh's ORDERING note explained itself in terms of stock run.sh's
line numbers -- "distributes the software component at its lines 107-179,
before the payload is extracted at line 180" -- and none of that is the shape
of an install any more. What the note is really for is why the links cannot
ship as files, which is unchanged, and it now says that instead. The paragraph
about the component no longer carrying firmwareExe is replaced by the reason
that matters: no component ships at all, so the version directory under
/usr/prog/PROGRAM/software stays FlashForge's and app_startup.sh's watchdog
has their binary to fall back on.

test_moddir_paths.py's ALLOWED set excused two paths that no longer exist.
anvil.conf went when the settings it held moved into the services, and
.prev-root-hash is not written any more -- this installer only deletes the
stale ones. Both were dead weight in a list whose whole job is to say "not
shipped, and correctly so": an entry for a file nobody guards on excuses
nothing and hides the next real one.

Gates: 112 static tests, shellcheck clean over the edited payload scripts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CH7Yb4wuPhnppHAB25UX7e
2026-08-30 15:38:25 +03:00
Oleksandr ShyshatskyiandClaude Opus 5 b98552386b No FlashForge component ships; patch.sh is payload.sh
The software component existed to give the mod one exec hook. It is not needed
for that any more, so a release now carries our installer and our payload and
nothing else: 26MB smaller, 21MB of which was FlashForge's own firmwareExe
travelling back to the partition it was already on, along with their klippy
tree, their wifi module and their configs.

WHAT IT GIVES BACK. /usr/prog/PROGRAM/software is no longer rewritten, so the
version directory there stays FlashForge's with their genuine firmwareExe
inside it -- and app_startup.sh's five-second watchdog, which copies one out of
that directory when nothing by that name is running, has something to copy
again. A printer whose payload fails to install now comes up on the stock UI
instead of a blank screen. That recovery has been broken since we started
deleting their binary out of a component we shipped; docs/how-it-works.md
claimed it worked throughout.

THE ROOT PASSWORD moves with it, because it used to ride in the component's
shadow file. bin/pack.sh bakes ROOT_PW_HASH into the installer, single-quoted
-- an unquoted $6$... would have the printer's shell expand $6 as a positional
parameter and ship a truncated hash -- and the installer writes it to
/usr/prog/etc/shadow. The random-password path asks one question instead of
four: FlashForge ship the same root hash on every machine, so a live shadow
still carrying it means nobody has set a password and it is ours to set. The
.prev-root-hash recording, the shipped-vs-live comparison and the restore-it-
afterwards step were all machinery for surviving a file that nothing overwrites
any more, and are gone. pack.sh checks the hardcoded constant against the stock
package it built from, because getting it wrong is silent and total: every
printer would read as "already set" and none would ever get a password.

bin/patch.sh is bin/payload.sh. It patched nothing any more -- no splice, no
shadow edit, no firmwareExe deletion -- and what is left is the payload: the
feed installed by the printer's own opkg, the seeded config, the manifest. Its
sections are renumbered 1-5 with no gaps, and the five tombstone comments
marking where removed sections used to be are gone with them.

test_recipe_layout.py loses six of its nine tests. Each compared the tree
against a description of the tree -- no fourth directory kind, each kind still
used, 3rdparty carries no files of ours, installer/ holds exactly two named
files -- and none could go red for a reason a printer would notice, while all
six went red the moment the layout legitimately changed. The two that bite are
kept: a payload/ path escaping $MODDIR, and a recipe name at both levels, which
makes pkg_dir silently build the wrong package.

SW_VER is deleted from config.env.example and both workflows: it stamped a
component that is not built.

Gates: 112 static tests pass. The generated installer is verified after
substitution -- all four lines present, and `sh -n` over the result, because a
hash with a stray character in it would otherwise reach a printer as a syntax
error whose only symptom is a package that installs nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CH7Yb4wuPhnppHAB25UX7e
2026-08-30 15:38:25 +03:00
Oleksandr ShyshatskyiandClaude Opus 5 9aece5b890 klipperDaemon was never replaced on any printer
installer/run-append.sh guards the klipperDaemon replacement with

    if [ -f $MODDIR/bin/klipperDaemon ] && [ -d /usr/prog/klipper ]; then

and the file ships at $MODDIR/prog/klipperDaemon. It moved there in 057a3a1
("The boot was seven scripts in filename order. It is one compiled graph now")
and this guard did not, so the test has been false on every printer since: the
whole block was skipped in silence, and every release kept FlashForge's own
klipperDaemon.

What that means on a machine: stock `klipperDaemon start` forks a second,
UNSUPERVISED klippy beside the s6-supervised one -- two processes fighting for
/dev/ttyS4 and /tmp/uds. Our shim exists to make that `start` a no-op, and it
has not been installed. The UI's restart-firmware path and a habit over ssh both
call it.

Nothing could have caught it. sh -n parses it, shellcheck likes it, and both
lanes stay green because the branch never runs. The niceness grep in
etc/s6-rc/source/klipper/run kept working by accident: it reads
/usr/prog/klipper/klipperDaemon, which is still FlashForge's, and FlashForge's
number is what it wanted.

So the fix comes with the gate it needed. qa/static/test_moddir_paths.py asserts
that every literal `[ -f $MODDIR/... ]` existence guard in installer/ and in the
payload scripts names a path some recipe actually ships -- the shell half of
test_no_undefined_names, and the same class of failure: a name that resolves
nowhere, on a path that only runs on a printer. Three exemptions, each with a
reason: anvil.conf (templated, user state), .prev-root-hash (written at
runtime), and helix-launcher.sh (inside anvil-helixscreen's upstream tarball,
shipped but not committed).

Verified by reintroducing the bug: the test fails with
"installer/run-append.sh:218  $MODDIR/bin/klipperDaemon".

Found while confirming that firmwareExe and start.sh are anvil-core's and get
symlinked by anvil-link-prog.sh -- they are, since 214a4e5. klipperDaemon is the
third file in payload/prog/ and the only one still hand-copied by the installer
rather than linked, which is why it could drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La5MaDKqTW675k5fsXsP6S
2026-08-30 00:50:43 +03:00