mirror of
https://github.com/Klipper4FlashForge/firmware.git
synced 2026-10-22 05:12:16 -05:00
worktree-timelapse-test-gcode
116
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d3dd609e84 |
Merge pull request #5 from Klipper4FlashForge/worktree-reforge-start-img
Ship our own start.img instead of FlashForge's |
||
|
|
58cfd00415 |
FlashForge's ffmpeg cannot render a rotated webcam, so we build our own
moonraker-timelapse emits -vf 'transpose=1', 'hflip,vflip' and friends straight from the webcam's rotation and flip settings. The stock ffmpeg is configured --disable-everything with a narrow enable list, and asking the binary itself for -filters returns format, null, scale and trim -- so every one of those renders fails on a printer whose camera is not upright. So: ffmpeg 4.4.5 and x264, cross-built, static, one 1.8M binary at $MODDIR/bin/ffmpeg. The enable list is FlashForge's plus the filters they left out, since flash is scarce and nothing else is wanted. There is no hardware encoder to use instead, and it is written down in pkgs/3rdparty/ffmpeg/pkg.conf so nobody re-derives it: the X2600's device tree declares jpege (a JPEG encoder), jpegd and felix, and felix is an H.264 DECODER -- the die has no H.264 encode block at all. Only felix has a driver in the stock kernel; jpege and the rotate block have DT nodes and clock gates and nothing bound to them. Software x264 is the only route to an H.264 mp4 here, which is why moonraker.conf tunes it with -preset ultrafast rather than leaving the default. x264 is named anvil-x264-dev outright, as anvil-zlib-dev is: every file in it is a header or an archive, so a PKG_DEV_FILES split would leave an empty anvil-x264 beside it that no printer would ever install. bzip2 joins the build image because x264's only distributed form is a .tar.bz2 and tar could not unpack it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSmXJv47Wwq2J7FfgCgpCe |
||
|
|
3462bc5f01 |
Warn against power-off on the start.img splash
A first-boot migration can run a couple of minutes with nothing else on screen; the DO NOT POWER OFF note is the same warning the later ffscreen.py frames already carry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSRJrZwsmud2y346iyCyaB |
||
|
|
1ceaa09b46 |
Ship our own start.img instead of FlashForge's
Rendered once with ffscreen.py (the same tool that draws every later boot frame) at the panel's native 480x800@32 raw framebuffer geometry, then committed like runFirmwareExe.sh -- ours now, not stock. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSRJrZwsmud2y346iyCyaB |
||
|
|
d898d105e0 |
The Moonraker pin was frozen by a missing _sqlite3 we now build ourselves
MOONRAKER_VERSION was 9d0d09d, the last commit before 80c7620 moved the
database from lmdb to sqlite, because FlashForge's python 3.8.2 has no
_sqlite3 and every release after it died loading the database component.
This repo builds and ships its own CPython 3.13 now, and it has one. The pin
is v0.11.0 -- a release tag, not a sha, so PKG_VERSION is just 0.11.0 and no
longer the Debian-style 0.8.0+git<sha> snapshot string. GitHub strips the
leading "v" from the directory inside /archive/<tag>.tar.gz, which is what
${MOONRAKER_VERSION#v} in the recipe is for.
The dependency set is the part that actually moved. Read against 0.11.0's
CORE_COMPONENTS plus the sections moonraker.conf enables:
dbus-next became dbus-fast. dbus_manager is a core component and imports
it at module scope, so this is not optional whatever [machine] provider
says. Its sdist cythonises nine modules; SKIP_CYTHON=1 ships the python
they were generated from and keeps the package ARCH all. The wheel still
comes out platform-tagged -- poetry-core marks the distribution impure
for declaring a build script at all -- but it holds 29 .py and no .so,
and the content is what ships.
importlib-metadata is new, imported at module scope by
utils/source_info.py, and brings zipp. Both build through setuptools_scm,
so PYPKG_HOST_LIST grew packaging and setuptools-scm. That list is now
install order: pkg_buildpython pip-installs each with --no-deps against
no index, flit-core builds packaging, setuptools_scm imports it.
pyserial-asyncio is gone. Nothing in 0.11.0 imports serial_asyncio;
utils/async_serial.py drives pyserial over asyncio itself. pyserial stays
-- klippy needs it and declares it.
LMDB STAYS, for a reason that is not the old one. 0.11.0 converts an
existing data.mdb to sqlite on first start and imports lmdb to read it. With
the module missing it tries to pip install one, and a printer has no index
to install from -- the old database would be dropped in silence. It is the
upgrade path now, not the database, and the comment on WANTED_MODULES says
so.
inotify-simple STAYS AT 1.3.5 though 0.11.0 pins ==2.0.1: 2.0.1 declares its
licence the PEP 639 way and needs setuptools>=77 to build, against the 75.6.0
pinned here for lmdb's sake. It fails at metadata generation, not at import,
which is the kind of thing that reads as a broken recipe later. 1.3.5 carries
every name 0.11.0 asks of it, and moonraker.conf sets file_system_observer:
none, so the import is all that is asked. jinja2 goes to 3.1.6 to match.
Not taken: paho-mqtt, zeroconf, apprise, ldap3 and python-periphery, whose
components this printer does not enable, and msgspec and uvloop, which
Moonraker imports under contextlib.suppress(ImportError).
Verified: every changed sha256 downloads and matches at the URL
fetch-assets.sh builds; all five affected wheels build offline
(--no-index --no-build-isolation) on a real 3.13 with the host backends
installed in the declared order; qa/static 122 passed, including the gates
that cross-check PYPKG_LIST against the recipes and every PKG_DEPENDS
against the feed. The mipsel cross-build and `make qa-replica` have not run
yet, and versions.env still says a bump is gated on the latter.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oRdgKM7scrEsr6waAByaY
|
||
|
|
6291eb5dfd |
The comment trim took pypkg_tgz out with its comment
|
||
|
|
1b89782ac9 | Merge remote-tracking branch 'origin/master' | ||
|
|
2e9f2214a0 |
Merge master into the comment trim
Master moved twelve commits while this branch ran: patch.sh became
payload.sh, run-pre.sh and run-append.sh became runFirmwareExe.sh, the
install manifest and anvil.conf are gone, and versions.env was trimmed
harder there than here (53 comment lines against 185).
Resolved so the merge contributes comments and nothing else. Verified
mechanically: every one of the 270 files in the tree now carries
exactly master's code, and no file this branch touched is resurrected.
- master's deletions accepted: bin/patch.sh, bin/verify.sh,
installer/run-{pre,append}.sh, anvil.conf.in
- versions.env: master's, which is the better trim
- 26 files master only renamed through: this branch's trim kept, with
patch.sh -> payload.sh applied
- 14 files master rewrote to describe the new installer: master's
text taken wholesale, to be re-trimmed separately
- stale references to the manifest and run-append.sh corrected in
pkgs/3rdparty/busybox/build.sh and pkgs/moonraker/build.sh
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMMpQTajN7RDJySrhQ2cTE
|
||
|
|
3003760af9 |
opkg is under /releases/opkg, not /releases/opkgs/3rdparty
That path 404s, and because the 404 is a transport failure curl treats as retryable, the vendor lane burns all five --retry attempts and 25 seconds before reporting it. The directory it was reaching for does not exist on downloads.yoctoproject.org at all; the release has always been filed one level up, alongside every other opkg version. The tarball there is byte-identical to what OPKG_SHA256 already pins -- d973fd0f...4567, unchanged -- so this is the URL alone. It only surfaced now because zlib failed earlier in the same lane and nothing reached opkg. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Svq98gthbGuzWp7HB5DBGH |
||
|
|
fd258eadf7 |
zlib.net answers 200 with a spinner page, and only the sha256 says so
Its anti-bot interstitial serves a 12KB HTML page under the tarball's URL. curl -f accepts it -- it is a 200 -- so the download "succeeds" and the failure surfaces one step later as a checksum mismatch, which is the report reserved for a tampered file. Nobody bumping a pin should have to rule out an attack first. madler publishes the release as a GitHub asset, and it is byte-identical: sha256 9a93b2b7...df23, the pin already in versions.env, unchanged. tools/python/build.sh fetched the same file from the same host and moves with it, so neither path is left on the URL that lies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149y4iyLpEwYKh2PS4fmf4u |
||
|
|
bf7d0f7e06 |
The printer has no clock and nothing was setting it
There is no RTC and no battery, so every boot starts at whatever the kernel was built with and stays there: job timestamps, log ordering and certificate validity are all wrong on a machine that has been unplugged. Nothing on the stock rootfs could fix it -- FlashForge's busybox is v1.31.1 built without the ntpd applet, and its rdate speaks RFC 868. anvil-sntpd is troglobit's sntpd, a fork of Doolittle's ntpclient: one small binary, servers and interval on the command line, so there is no config file, no privsep user and no chroot to place on a read-only rootfs. It ships $MODDIR/sbin/sntpd and nothing else. A LONGRUN, NOT A HOOK ON THE WIFI ONESHOT. That oneshot runs once, at boot, and returns before wlan0 has associated -- so a sync hung off its end would fire too early, never retry, and never fire at all for the owner who joins a network from the HelixScreen panel an hour later. And with no RTC a clock is not set once but maintained. sntpd keeps its schedule with the network down instead of exiting, which is what lets this service need no wifi dependency -- just as well, since nothing may depend on that oneshot. The poll interval is 300 rather than upstream's 600 because it is also the ceiling on the down-network retry backoff, and so bounds how long a printer that joined wifi late keeps the wrong time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149y4iyLpEwYKh2PS4fmf4u |
||
|
|
c3a64e0394 |
The stock firmware is a pinned download like everything else
config.env.example opened with DESKTOP="$HOME/Desktop", and four settings hung off it. Nothing in bin/, pkgs/, qa/ or installer/ ever read it: three of the four were commented-out overrides for files `make vendor` already fetches, and the fourth, BUSYBOX_BIN, names a binary with no source at all. What DESKTOP really did was carry the stock FlashForge .tgz -- the one input a person had to go and find before they could build anything. It did not have to be. .github/workflows/ci.yml has been fetching that exact file for as long as printer-sim has run, from ghzserg/FF, pinned by sha256, with its own get(), its own cache and its own secrets -- a second spelling of "download the firmware and check the hash" that no human path shared. The pins here are those pins, verified byte-for-byte against a fresh download of both assets. So versions.env pins the stock package the way it pins everything else, bin/fetch-assets.sh downloads it into vendor/, and bin/common.sh defaults STOCK_TGZ_* there. A cold checkout is now `cp config.env.example config.env && make build`, with nothing to edit. --stock is a separate flag from --all, not folded into it. The packaging lane wants no FlashForge package -- that is most of the point of it being runnable in CI on a bare checkout -- so `--all` stays "every payload piece, no firmware", `make build` asks for the one model it is building, and `make vendor` (--all --stock) takes both. The one download that will not overwrite an override. HELIX_TGZ and friends name a public release anyone can re-fetch in a minute; STOCK_TGZ_* names 93MB of proprietary firmware someone downloaded by hand, and clobbering it on a hash mismatch would destroy the only copy on the machine. It is skipped instead, and unchecked -- their file, their problem. CI loses its duplicate fetcher, its stock/ cache and the STOCK_URL_* secrets. A fork testing its own firmware re-pins versions.env, which works locally too; the secrets only ever worked in CI. qa/lib/config.py reads versions.env for the pinned filenames so stock_for() looks where the download actually lands. An explicit STOCK_TGZ_* still wins. An existing config.env keeps working untouched: its paths are now overrides, so the build skips the download and uses the file it always used. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPU6RR4UJH2Qf9DR5hb2gg |
||
|
|
5d7754285e |
Wipe $MODDIR and extract, instead of pruning by a manifest
The installer deletes /usr/data/anvil outright and extracts into the empty directory. The installed set is the shipped set by construction, so a renamed file cannot survive beside its replacement and no list has to be shipped, read or trusted for that to hold. What that costs is everything else under $MODDIR, and the audit of what was actually there is docs/notes/86-wipe-and-extract.md. backup/stock had no restore path in any script and omitted the one file a restore would need. .prev-root-hash was already dead state. config-installed was a twelve-file snapshot serving one md5, for a compare whose stated rationale -- "there is no include-and-override seam" -- stopped being true when moonraker-custom.conf became the last include, and which inverted its own purpose by keeping the webcam block and the API lockdown off any printer that tripped it. So moonraker.conf is overwritten like every ff-*.cfg. HelixScreen's /tmp stash is untouched and is now the only preservation the installer does, so the gate asserts it with a value that could only have come from the printer. Gone with it: both delete passes, the forged-path guard, the rmdir pass, the pre-manifest branch, the unconditional init.d/anvil-service/ anvil.conf removal, the etc/s6 scandir sweep and the $MODDIR __pycache__ sweep -- the last two existed only because a manifest cannot see paths made at runtime. firmwareExe already makes the scandir itself. test_upgrade.py rewritten: the negative control is inverted rather than deleted, and the legacy fixture goes -- there is one path now. qa/static green (112). The replica lane needs a package in work/out, which needs config.env with FF_KEY and STOCK_TGZ; not run here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXKk3zz8LAUPUDZkVEZ9fR |
||
|
|
83a4600df8 |
Stop naming things that are not there any more
A sweep for comments and messages that describe machinery this tree no longer
has. Each one read as current and was not.
* bin/verify.sh, retired two commits before this branch, was still named as
the thing `make release` and docs/building.md were explaining the absence
of. The absence needs no explaining -- `make qa-replica` is the gate.
* `make test-recovery` does not exist. docs/hardware-testing.md told owners
it "used to" prove the stock rollback; the useful half of that -- nothing
tests the rollback today, treat it as unverified -- is kept and the dead
target name is not. runFirmwareExe.sh pointed at it too.
* run-append.sh in bin/build-payload.py, case-install.sh in
docs/printer-replica.md: both files are gone.
* `make test`/`make test-py` over test/, and the note that both suites run
in CI "until a case-*.sh has a green replacement" -- test/ was deleted and
there are no case scripts left to replace.
* docs/building.md still said a package carries the software component.
CI's shellcheck globs are the one real defect this turned up. Two of the four
-- pkgs/*/payload/init.d/S* and pkgs/*/prog/*.sh -- match nothing: the recipes
stopped using both paths, and payload/prog/ is where those scripts live now.
shellcheck given a pattern that expands to nothing writes usage to stderr and
exits 1, so that step has been failing for its globs rather than for the code.
Fixed to the paths that exist, with a pointer to
qa/static/test_shell_syntax.py, which addresses the same files and refuses a
glob that matches nothing -- the guard this step did not have.
Verified: the corrected invocation runs clean over the 21 files it now
matches, and 112 static tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CH7Yb4wuPhnppHAB25UX7e
|
||
|
|
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 |
||
|
|
2a6331a831 |
We own runFirmwareExe.sh; the install is no longer spliced into run.sh
app_startup.sh runs whatever it finds under that name in the package it just decrypted. That is the entire contract -- plus the exit code, where 0 means "flashed, power-cycle me" and non-zero means "carry on booting" -- so owning the name is all it takes to own the install. We were not using it. We shipped FlashForge's copy verbatim and reached the printer through the one exec hook their file offers: the software component's run.sh, into which bin/patch.sh spliced two marked blocks with 40 lines of regex. installer/runFirmwareExe.sh is that file now. run-pre.sh and run-append.sh are folded into it unchanged in substance -- the manifest delete, the HelixScreen settings rescue, the s6 scandir sweep, the config merge, the password dance are all the same code -- and the two orderings that mattered are kept explicitly rather than by where a regex put them: the backups run BEFORE any component is installed, because the software component ships /usr/prog's shadow and passwd and its run.sh copies them over the live ones. The four component installers become one function. Their semantics are kept exactly, including that a component failing md5sum.list is discarded rather than half-installed, and that every previous version directory is wiped before the new one lands. What is NOT kept is the third of that file which never ran: create_key(), defined and never called, and built its paths without a slash so it would have written /usr/progprivate.pem; SSID_NAME/SSID_PASS, assigned and never read; the commented-out kernel version gate; the screwflag factory bypass of the model check, which we ship no screwflag for. bin/pack.sh stages it, rewriting MACHINE=, PID= and MOD_PW_AUTO= and then GREPPING for all three -- a package whose gate still said Creator5Pro because a sed missed would install on the wrong machine. MOD_PW_AUTO is 0 in the checkout, which is the safe default and not an arbitrary one: test_upgrade.py runs the unsubstituted file, and 1 would have it reroll the replica's root password and drop a password file on /mnt every run. The software component still ships, unchanged, and its run.sh is left exactly as FlashForge wrote it. Dropping it is the next commit; this one moves the vehicle without changing what reaches /usr/prog. Gates: qa/static/test_shell_syntax.py already covered installer/*.sh under both bash -n and the printer's ash plus shellcheck, so the new file was checked the day it landed -- 91 pass. test_upgrade.py runs the real installer over two synthetic payloads and is repointed at it. Two doc claims corrected while their sections were open, both false before this change rather than because of it: how-it-works.md said app_startup.sh's firmwareExe recovery "still works" -- it cannot, because it copies out of the version directory and ours has had firmwareExe deleted from it -- and it described a Moonraker preflight-and-rollback that went with the /usr/prog copy; moonraker-preflight.py exists nowhere in the tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CH7Yb4wuPhnppHAB25UX7e |
||
|
|
56b5b6c334 |
The printer image is the only replica; make rootfs was a second way nothing took
`make rootfs` unsquashed rootfs.squashfs out of the stock package into
work/rootfs, and a thin container bind-mounted it with PROG_DUMP supplying
/usr/prog from the factory image. Every caller set PRINTER_IMAGE anyway --
both workflows wrote it into test.env BEFORE running `make rootfs`, so CI
unpacked a 93MB package, unsquashed a rootfs and downloaded a 182MB factory
image on every push and read none of it. build-printer-image.sh never used
work/rootfs either: Dockerfile.full does its own extraction from the same
public firmware and needs no stock package at all.
So the fallback went, and PROG_DUMP with it -- it was only ever read when the
replica was built locally, and both implementations already passed it empty
otherwise. Gone: the target, extract-rootfs.py, gates.extract_rootfs and its
two tar helpers, the thin printer/Dockerfile, the `prebuilt` flag threaded
through both Replica implementations, and Replica.start's want_output, whose
only message was that the local build was about to be slow.
PRINTER_IMAGE is pinned to 1.9.7-1.2.9-20260810 rather than :latest. It is
now the entire substrate the brick-safety gates run on and the only input
nothing checksums, so a floating tag would let it change under a green suite
-- the drift the sha256 pins in both workflows exist to prevent.
THREE THINGS THAT COULD NOT HAVE PASSED, found while doing it:
* Neither workflow built the feed. bin/patch.sh has refused to start
without one since
|
||
|
|
9b5b562a22 |
Comments: keep the contract and the trap, drop the narration
A sweep over every commented file in the tree. What is kept is what a reader needs to change the code safely: what a thing is, its signature, the invariants other files depend on, and the traps that fail silently (find's non-zero exit under set -e, s6's baked-in libexecdir, the -lm that decides whether _sqlite3 exists, opkg's compile-time prefix). What goes is the narration around it: incident histories, arguments against alternatives already rejected, restatements of the code below, and paragraphs re-explaining a rule three files already state. One paragraph in pkgs/3rdparty/opkg/build.sh was duplicated outright. Comment lines 7971 -> 6158. Every change is comment-only: no non-comment line differs anywhere in the 86 files, checked mechanically. Commented-out config directives (#fast_feed: 30000 and friends) are documentation of defaults and are kept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NMMpQTajN7RDJySrhQ2cTE |
||
|
|
a406d7a75a |
run.sh injection: one loop over two blocks, and it is finally idempotent
Section 11 spliced the pre- and post-blocks into FlashForge's run.sh through a temp file, a sed, and two hand-written insertion paths that shared nothing. The blocks are now a two-row table -- name, source, anchor -- walked by one loop, and MOD_PW_AUTO is substituted in Python, so work/.run-post.sh and the sed that wrote it are gone along with the rm that cleaned it up. The header has claimed "Idempotent" all along and section 11 was not: the strip regex removed the marked block but not the blank line it had been padded with, so re-patching a tree that was already patched grew run.sh by three lines a pass. Both anchors now land on line boundaries and the block carries its own padding, so the strip takes back exactly what the insert put in -- measured: three further passes change no byte, and stripping a patched run.sh reproduces the pristine one exactly. Two behaviour changes worth naming. The post-block's markers were "anvil begin/end" against the pre-block's "anvil pre"; both are "anvil <name>" now, and the strip matches by shape rather than by name so a tree carrying the old markers is still cleaned. And an anchor must match exactly once, where the post-block used to take the last of however many matched and fall back to end-of-file when none did -- a stock run.sh with two candidate lines is one this script has no business guessing about. Nothing outside this section reads the markers. Output is unchanged but for one blank line: the generated run.sh is otherwise byte-identical with a hash baked in and with none, and still parses as POSIX sh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKmSZwVorhBS1xfyHtpgvU |
||
|
|
fbb3182e68 |
bin/verify.sh is retired; four of its forty checks move to qa/replica
verify.sh described itself as "simulate every check the printer performs,
against a built package". The word carrying that sentence is SIMULATE, and it
is why thirty-six of the forty are deleted rather than moved.
THE REPLICA LANE DOES NOT SIMULATE THE INSTALL. install-package.sh puts the
package on a genuine FAT filesystem at /dev/sda1 and runs the machine's own
/usr/prog/app_startup.sh over it, verbatim, under qemu. So these are
PRECONDITIONS of the lane now rather than assertions in it -- break any and
installed_image() raises with the install log and the whole lane goes red:
* the decrypt with the firmware key (the printer's unTar does it)
* runFirmwareExe.sh present, components plain tar not xz (bare `tar -xvf`)
* md5sum.list verifying (the installer aborts and deletes the payload)
* the MACHINE= gate (runFirmwareExe.sh refuses a foreign package)
* the /mnt/<Model>-*.tgz glob (app_startup.sh finds the file by it)
A host-side re-reading of those rules is a second implementation, and a second
implementation can agree with itself while disagreeing with the printer. That
is the defect qa/replica/conftest.py records this repo learning twice about
hand-placed payloads.
A second block was asking what the suite already answers, of an installed
filesystem rather than of a tar listing: the klippy tree and c_helper.so
(test_install, test_abi), the s6-rc database and the oneshot runner's execline
(test_s6rc), s6 and s6-ftrigrd (test_supervisor), the .install-manifest
(test_upgrade), libsodium's bare .so symlink and the chamber configs
(static/test_ipk), and every shipped script's syntax
(static/test_shell_syntax).
THE FOUR THAT MOVED are the ones nothing else asks, and each is stronger for
the move -- a `tar -t` listing cannot tell you that a shared object loads, or
what a name resolves to on a PATH:
* the interpreter IMPORTS sqlite3, lmdb and _cffi_backend, rather than a
grep finding three .so files. This is what catches a cross build that
resolved to an x86_64 manylinux wheel: present on disk, raises on import.
* nothing in $MODDIR/bin shadows FlashForge's python3 -- asked with
`command -v` after sourcing anvil-env.sh, which is the file that PREPENDS
$MODDIR/bin to PATH and so creates the hazard in the first place.
* CPython's dev half (headers, pkgconfig, config-3.13-*) did not ship.
* the ship boundary: no file on the installed printer is byte-identical to
one under bin/ or docker/. Asked of the machine rather than of the .tgz,
so it also sees whatever a maintainer script wrote as root at install
time. The sweep filters on exact size before hashing, which is what keeps
a walk of /usr/prog through qemu cheap.
WHAT GENUINELY LEFT, recorded in docs/qa-migration.md rather than only here:
there is no longer a check you can run on the build host, in seconds and
without docker, against a package you are about to flash. Nothing warns you
before you copy the file to a stick that it was built from the wrong model's
stock package. The printer still refuses it and `make qa-replica` still
catches it, but the fast host-side warning the hardware checklist used is
gone. `make verify` is removed and `make release` no longer calls it.
VERIFIED. All four ran against a real replica (the cached installed image,
PRINTER_IMAGE=monstrofil/creator5-printer:latest): 4 passed in 17s. Then each
was DRIVEN RED on purpose with a throwaway module, which is this repo's own
standard from
|
||
|
|
6dc89469ac |
patch.sh asserts nothing about the payload it just installed
Two gates go, both of the same shape: a pkg_die run against $PAYLOAD_DIR a few
lines after bin/build-payload.py filled it, asking whether opkg had installed
the packages it was handed.
* section 1: a klippy tree at $MODDIR/klipper/klippy, and a c_helper.so
inside it
* section 5b-2: etc/s6-rc/compiled/current resolving
Neither was ever this script's question. patch.sh no longer builds either
subject -- anvil-klipper installs the klippy tree, and case-build-payload.sh
compiles the database in the replica with the s6-rc-compile anvil-s6-rc ships
-- so a gate here can only report that a package manager did what it said it
did, on a staging directory, on the build host.
Both are already asked on the two vehicles that actually reach a printer:
* bin/verify.sh reads the PACKED artefact -- the klippy tree, c_helper.so,
and compiled/current resolving, plus the one thing about the database a
host can genuinely tell that patch.sh never asked: the execline named by
the oneshot runner's #!.
* qa/replica asks an INSTALLED filesystem. test_install.py's
test_klippy_is_present is the klippy question by name, test_abi.py sweeps
c_helper.so for the ABI, and test_s6rc.py boots the thing -- s6-rc-init
reads the database, the boot set comes up, and a database moved aside is
reported as a negative control.
The two `say` lines went with them. Both existed to narrate a gate; the
Klipper one printed a pin that pkgs/klipper/pkg.conf already stamps into
PKG_VERSION, and the s6-rc one readlink'd the path the gate had just tested.
KEPT, and it is the same call
|
||
|
|
57c776ee63 |
test/ is deleted; qa/ is the only suite
The eight files were already gone from the working tree. What this commit does
is everything that had to follow them, because nothing else had been updated
and three of the four things pointing at test/ were on a build or release path.
WHAT WAS STILL WIRED TO IT
pkgs/klipper/build.sh ran test/test-chelper.py under `|| pkg_die`. With the
file gone `make packages` would have died on every
klipper build.
bin/verify.sh ran the same script and read a non-zero exit as
"c_helper.so does not match the shipped klippy tree
(stale build)" -- a false red on every release verify
rather than an error about a missing file. Its
ship-boundary check also globbed `find bin test
docker`; that is `find bin docker` now.
pytest.ini testpaths = test, so a bare `pytest` collected
nothing at all. It is `qa` now, which is what the
comment above it was already describing.
qa/static/ PY_GLOBS listed test/*.py and test/integration/*.py,
test_shell_syntax.py and _files() makes an empty glob a collection error
on purpose -- so the whole static lane failed to
collect, taking 93 unrelated tests with it.
Also dropped: the `pytest -- the host-side tree` step in ci.yml, the `test-py`
target with its .PHONY entry and two help lines, and qa/conftest.py's opening
paragraph explaining why qa/ sat beside test/ rather than inside it.
THE CHELPER SYMBOL CHECK IS GONE, NOT MOVED
It compared the .so's dynamic symbols against what klippy cdefs, and it was the
only check for a c_helper.so built from older sources than the klippy tree
beside it -- the failure that shipped once and bricked klippy startup on
hardware, because cffi resolves lazily and a stale .so imports cleanly.
qa/replica/test_install.py covers the ELF arch and nan2008 and nothing else.
What stands in its place is structural rather than a gate: pkgs/klipper
compiles the .so from the chelper sources of the very tree it ships, so the
mismatch is not something the recipe can produce. The comments in
pkgs/klipper/build.sh and docs/building.md now say that is the whole defence.
DOCS
docs/testing.md's "what is left in test/" section is now "test/ is gone", and
it names what went uncovered rather than leaving it to be found: the tool-frame
arithmetic in ff_toolchange, the chamber macros' per-model branching, the stamp
discipline in ff-startup.py, and the verification G-code against the shipped
macros. docs/hardware-testing.md said `make test-py` keeps the two
gcode/creator5-*.gcode files honest; it now says nothing does, and to read them
by hand when a macro is renamed. Paths corrected in docs/building.md,
docs/printer-replica.md and docs/qa-migration.md's intro.
Verified: `pytest` collects 366; qa/static is 136 passed, up from 43 because
the lane could not collect before this. Its 110 errors are this checkout's
missing vendor/opkg-utils, the same cause before and after. bin/verify.sh,
pkgs/klipper/build.sh and the Makefile parse; ci.yml is valid YAML.
Not touched, and stale before this commit: tools/python/README.md and
tools/supervisor/README.md still document test/integration/printer/case-*.sh
and a printer-exec.py that earlier commits removed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bLGYpVAeVzAX6NeAxcU42
|
||
|
|
c92b3c9e86 |
anvil.conf is gone; a component runs because it is installed
The file held three unrelated things behind one name: gates deciding which services boot, switches for the splash / the startup wait / the first-boot import, and tunables for the camera and the nice values. Every one of them defaulted to on, and what each offered was a half-installed printer -- a state nobody chose on purpose and everything downstream carried a second answer for. firmwareExe no longer expands the ok-all bundle and filters it, it asks for the bundle: BOOT_SET=ok-all. A printer that should not run a component does not have it -- `opkg remove`, or a stock FlashForge package. The settings worth keeping are each stated once now, in the service that uses them: the camera's resolution in its run script, nice 10 there and nice 5 in moonraker's (the values every install was getting from the template, not the 0 the inline fallback said). ff-startup's `up` stops passing --timeout and --klipper-tries at all, because 240 and 6 are already ff-startup.py's own defaults and two statements of a default can disagree. Nothing sources the file, so nothing reads it, so it must not survive an upgrade looking editable. run-append.sh stops preserving it and removes it unconditionally beside init.d and anvil-service.sh -- unconditionally because the pre-manifest sweep removes seven DIRECTORIES and would leave this file sitting at the top of $MODDIR. The two upgrade tests that asserted the edit survived now assert the file does not. seed/ leaves anvil-core entirely, which takes with it the recipe's one exception to "every sibling directory is shipped", the seed mount in the replica plumbing, and its exemption in the .ipk allowlist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015E7armHFDcnZFZwdAbqkbt |
||
|
|
a534b4a744 |
klipperDaemon is a link too; its nice value moves to anvil.conf
The third file in $MODDIR/prog was the only one anvil-link-prog.sh did not own. It could not be a link because its CONTENT was machine-specific: the installer seded FlashForge's KLIPPER_NICENESS into it on every flash, off the file it was replacing. That is the whole reason it was hand-copied, and the hand-copy is what silently broke -- its guard named a path the file had stopped shipping at, so no printer got the shim at all. We do not carry FlashForge's number forward. NICE_KLIPPER is ours now, in anvil.conf beside NICE_MOONRAKER and NICE_CAM, templated from config.env by patch.sh section 10 like the other two, and read by etc/s6-rc/source/klipper/run the way camera/run and moonraker/run read theirs. Default 0 -- normal priority, which is what stock shipped and almost certainly what you want: klipper_pri.sh already puts klippy's main thread on SCHED_FIFO 50 and its serial reader thread calls os.nice(-20) itself, so this knob only matters when the chrt did not take. With nothing machine-specific left in it, klipperDaemon is one more link_one. Whether the stock software carries a klipperDaemon does not matter: link_one replaces whatever it finds, and it runs after the payload has landed. The 14 lines of sed/mv/chmod in run-append.sh are gone, and an `opkg upgrade anvil-core` now changes what the printer runs for all three files instead of two. Gates: the replica symlink test covers all three paths, and a new one RUNS `klipperDaemon start` -- it must exit 0, say klipper is supervised, and leave no second klippy behind. The file being in place said nothing about what it does, which is how the old breakage hid. Verified: the nice branch over an explicit 0, a real value, and no anvil.conf at all; and the anvil.conf substitution with NICE_KLIPPER set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01La5MaDKqTW675k5fsXsP6S |
||
|
|
7dda2fee0d |
ABI gate: one check, on the filesystem the kernel loads from
mips_abi_gate answered "did this recipe stage the right ABI", once per recipe, over a PKG_ROOT. Four implementations of that one rule had grown up around it -- the function in bin/common.sh, the call at the packaging boundary, two recipes that re-ran it on their own output, and a readelf|grep nan2008 in bin/verify.sh reading a single .so -- and between them they still could not see the largest binary we ship: bin/patch.sh unpacks HelixScreen into /usr/prog and no gate ever read that tree. pkgs/helixscreen/pkg.conf had carried a note saying exactly that. qa/replica/test_abi.py replaces all of it. It walks the installed filesystem on the printer's own CPython -- one qemu process, ~24000 files, 8s -- and reads the ELF header of everything it finds. That is the only place where what the recipes staged, what opkg unpacked, what patch.sh copied and what FlashForge shipped are all visible at once, and it is the thing the question is actually about: the kernel does not read a PKG_ROOT. MEASURED, not assumed: 603 ELF objects on a real install. 569 the loader handles and 568 of those conform. The exemptions are the other two, both narrow. ET_REL objects (33 stock .ko files, plus a stray python.o in FlashForge's CPython 3.8 tree) are not asked about the FP ABI, because insmod and the linker read them rather than the ELF loader and kernel code has no FP ABI to agree about -- by e_type, so the python.o needs no case of its own. And /usr/prog/PROGRAM/ffstartup-arm is an ARM binary FlashForge ships that nothing in the stock tree names; exempt by path, so an ARM binary anywhere else still fails. The architecture is settled before anything is classified, and that ordering came from planting a counterexample rather than from reasoning: a big-endian object decodes its own e_type big-endian, so ET_DYN reads back as 768, lands in neither bucket, and an earlier draft looked straight at it and passed. Every gate here was driven red on purpose before it was kept -- an x86-64 .so in $MODDIR, a real mipsel object with EF_MIPS_NAN2008 cleared, an ARM binary outside the exempt path, a big-endian object, an x86-64 object among the .ko files, and a truncated sweep. test_install.py's two chelper header tests go with it: they read one file out of the several hundred on the same fixture, a strict subset of what now runs. Whether the package ships a chelper at all stays test_install.py's question and is not asked twice. test_ipk.py's assertion that the gate exists becomes an assertion that a second one has not grown back beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NpH87TJJEjDyRhQPu18pgp |
||
|
|
965cf67d93 |
Compile the s6-rc database where the packages are installed
The replica already installs the feed with the printer's own opkg, and anvil-s6-rc already ships s6-rc-compile -- pkgs/3rdparty/s6-rc/build.sh keeps it deliberately, "the only way to recover a printer whose database is wrong without rebuilding a package on a laptop". So the compiler was sitting in the container the whole time, three lines after the install that puts the source tree there. bin/patch.sh section 5b-2 built a second, NATIVE skalibs+execline+s6+s6-rc instead -- four ./configure && make passes cached in work/.s6-native -- purely to get an s6-rc-compile that runs on x86. 110 lines, and its entire hazard was self-inflicted: s6-rc-compile bakes the #! of the execline the COMPILER was linked against into the oneshot runner, so a native stack configured with a different --prefix ships a database whose every oneshot ENOENTs on the printer. Compiling with the compiler we ship cannot get that wrong. The output is the same object. docs/notes/80-s6-migration.md records the measurement from 2026-08-28: one source tree, the host compiler and the target one under qemu, byte-identical db, n and resolve.cdb and identical servicedirs. That measurement was made to justify compiling on the host; it works just as well in the other direction. The rejected alternative in the old comment -- ship s6-rc-compile and compile at INSTALL time on the printer -- is a different thing and stays rejected. qa/static/test_s6rc_source.py is dropped. It was the only consumer of the native compiler, and it hard-failed on any checkout that had not run `make build`: 14 of the static suite's errors were it saying so. What goes with it is the timeout and edge assertions, which are now made nowhere; qa/replica/ test_s6rc.py still boots the real thing. bin/verify.sh keeps the half a host can still check, moved out of patch.sh: the oneshot runner's shebang, and that compiled/current resolves -- read as text straight out of the shipped payload, so it needs no mipsel binary. bin/patch.sh: 278 -> 207 lines. Static suite: 122 passed, errors 124 -> 110. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01La5MaDKqTW675k5fsXsP6S |
||
|
|
214a4e58b9 |
anvil-core owns firmwareExe and start.sh; kill the 3.8.2 myth
Three things, all downstream of klippy having moved to $MODDIR and our own
CPython 3.13.
SECTIONS 8 AND 9 ARE GONE. Both files install at $MODDIR/prog/ and
anvil-link-prog.sh points the stock paths at them -- from run-append.sh on a
flash and from anvil-core's postinst on `opkg upgrade anvil-core`. Nothing
execs either between run.sh distributing the component and link-prog running,
so the component copy bought no ordering. It cost, though: our firmwareExe
with no payload behind it logs "no anvil-env.sh", finds no s6-svscan and then
`while true; do sleep 3600; done` -- a printer that boots to a blank screen for
ever and never reaches app_startup.sh's own recovery. Absent, that recovery
restores a firmwareExe from a version directory. So patch.sh now REMOVES both
from the component: the stock component carries them, and deleting the two cp
lines alone would have shipped FlashForge's originals.
SECTION 5c IS GONE. It did no work -- two du prints and a comment whose first
line was the falsehood below.
THE FALSEHOOD. "klippy runs on the stock 3.8.2, started by
/usr/prog/klipper/start.sh" had spread well past patch.sh, and it had reached
things people follow:
- docs/toolchange.md told owners to scp the ff_*.py extras to
/usr/prog/klipper/klippy/extras/ -- the wrong partition -- and warned that
an OTA would delete them there. Same wrong path in three ff-*.cfg headers
and in ff_toolchange.py.
- versions.env labelled our CPython 3.13 and its eighteen packages
"SHIPPED BUT NOT USED".
- docs/how-it-works.md said Moonraker runs on python3.8, contradicting line
126 of the same file.
All corrected. Historical notes keep their history and get a [Superseded: ...]
marker instead -- including 80-s6-migration.md's "s6-rc-compile is not
shipped", which pkgs/3rdparty/s6-rc/build.sh contradicts deliberately.
Found while reviewing: every docs/ reference to the ff-*.cfg pointed at
pkgs/anvil-core/payload/config/, which does not exist -- they are
anvil-klipper-config's. 28 references across 9 files, several of them broken
markdown links on the published site. All 24 relative links under docs/ now
resolve.
Gates: verify.sh syntax-checks prog/start.sh and prog/firmwareExe out of the
payload (firmwareExe was never syntax-checked before) and fails if the
component carries either. A new replica test asserts both stock paths are
symlinks into $MODDIR/prog and do not dangle. One existing replica test stubbed
firmwareExe by redirecting into $FE, which now writes THROUGH the symlink into
the packaged wrapper; it moves the link aside instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La5MaDKqTW675k5fsXsP6S
|
||
|
|
b25a19683d |
bin/*.sh: 1,110 comment lines were half the code; 327 remain
The scripts under bin/ carried more prose than shell. Most of it was rationale for decisions already made, archaeology of flags since deleted (BUILD_KLIPPER=stock, the five MOD_* switches, KLIPPER_FORK), or a paragraph restating the line beneath it. What survives is what a reader cannot get from the code: the s6-rc --prefix trap that turns every oneshot into an ENOENT, chelper.tar being the stock installer's vehicle rather than a second build, PKG_EXCLUDE matching Mainsail's own .version at any depth, Perl's || making SOURCE_DATE_EPOCH=0 the one value that falls through, the herestrings that avoid a pipefail 141, unTar's -md md5. Each is now one or two lines instead of a paragraph, and the section dividers lost their rules. No code changed. qa/static is 122 passed / 124 errors before and after, the errors being the same missing vendor/opkg-utils and uncompiled s6-rc database in both runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0111WSZKjXNsvfWS2PR9wK7o |
||
|
|
53292b4d78 |
klippy runs from $MODDIR, so patch.sh stops staging a second copy
The klipper s6-rc service already execs klippy under $FF_PYTHON -- our own CPython 3.13 -- but it read the tree from /usr/prog/klipper/klippy: a copy bin/patch.sh staged into the software component out of the very anvil-klipper package that also installed it under $MODDIR. Two copies of one tree, and the one that ran was the one a stock flash could overwrite. One line moves it: KLIPPY=$MODDIR/klipper/klippy/klippy.py. That deletes sections 1 and 2 of bin/patch.sh -- the cp -a of the klippy tree, the chelper.tar wrapped around a .so the package already contained, the rm -rf of the stock extras/ and kinematics/, and the ff_*.py copy. The software component now carries firmwareExe, start.sh and run.sh and nothing else of ours, which is the phase 7 end state in docs/notes/80-s6-migration.md. It also lets pkgs/klipper declare the dependencies it has had since the interpreter moved. cffi and greenlet reached the printer only through a hardcoded list in patch.sh, so every build had them and no `opkg install anvil-klipper` did; pyserial and jinja2 came only as anvil-moonraker's Depends, so a BUILD_MOONRAKER=0 build shipped a klippy that dies the first time it opens an MCU. Both are real Depends now and MOD_ROOTS drops to ten. Also repointed: run-append.sh's __pycache__ sweep, which was clearing the tree nothing imports while klippy writes bytecode into the one it does -- and those runtime-created directories are in no .install-manifest, so the manifest delete walks past them. verify.sh reads the klippy tree and c_helper.so out of the payload (extracting ./klipper alone), and gains a check that the component does NOT carry a klippy tree. New replica tests assert the service execs the payload tree under $FF_PYTHON and that cffi/greenlet/serial/jinja2 import on it. Comments in anvil-env.sh, verify.sh and pkgs/klipper/pkg.conf that still said klippy runs on FlashForge's 3.8.2 are corrected; that stopped being true when FF_PYTHON moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01La5MaDKqTW675k5fsXsP6S |
||
|
|
c185ef3285 |
The replica is a build tool; test/ is 28 unit tests
Two changes that turned out to be one.
MOVED, NOT DELETED. `test/ffsim/` and `test/integration/printer/` are
`tools/replica/`. They were never tests: bin/patch.sh calls
bin/build-payload.py, which starts a replica and assembles the payload by
running the PRINTER'S OWN opkg against / inside it -- so the "test harness"
was on the build path, and deleting it as asked would have broken `make
build`. qa/lib/replica.py drives the same directory unmodified. No content
changed; what changed is qa/lib/replica.py's build directory, three launcher
sys.path lines, the Makefile targets and the CI shellcheck glob.
tools/replica/README.md says who drives it and why it is not under test/.
CUT TO 28. The five surviving modules were 136 tests; they are 28, chosen on
one question: would this failure be SILENT? What is kept is the mistakes that
run perfectly and are wrong --
test_startup 8 no stamp unless the values are verifiably saved (a
stamp written early is a printer that has lost its
factory calibration for good), and the handover order
test_tool_transform 7 every one is a move to the wrong place: a sign, a
frame left applied while probing, a stale gcode_move
cache, a config a calibrated printer cannot parse
test_ffscreen 5 the four ways it can do harm rather than look wrong
test_chamber 4 the chamber mistakes that cost a print before anyone
sees them, plus the parse gate they rest on
test_gcode 4 a renamed macro, and the safe file being cold
What went: the panel narration, the geometry probe and parse_geometry, the
z_adjust and per-job-Z bookkeeping, the config layout checks, the G-code
bounds checks. Each is either a wrong number shown to a person, or a failure
the printer reports loudly the first time -- and qa/replica/test_boot_screen.py
already renders the real frames on the real machine.
The rewrite is not a delete: assertions were folded so a kept test asks its
question in every direction it can be got wrong (both models, both rotations,
all four ways the migration can fail), and each file's header now says what it
dropped and why, where the next person will open it.
Also gone: conftest's `rootfs` fixture, orphaned two commits ago. Nothing under
test/ needs the firmware, docker, or a network any more.
28 host-side tests pass in 0.15s. qa/static unchanged at 218. `make rootfs`
re-verified end to end from its new path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzLebCrvc8ssWR5JuEsha4
|
||
|
|
465c9902f1 |
test/: drop the seven modules whose subject was the fake
Eight files, 46 tests and make-stock-fixture.sh. Each of the seven test modules built a stand-in -- a klippy stub, an argparse Namespace, a config tree -- elaborate enough that what they pinned down was the stand-in's behaviour rather than the printer's. What is left under test/ is the Klipper config gate and the three modules whose subject is our own Python: ff-startup, ffscreen, and the tool transform. make-stock-fixture.sh goes with them. docs/qa-migration.md argued for keeping it as the input a future build-path test would need, but it fed a gate that no longer exists, and a fixture generator with no consumer rots exactly as silently as a test that asserts nothing. What it had learnt is recorded in the doc so whoever ports the build path does not have to find it twice. The coverage this gives up is written down rather than glossed: a new section in docs/qa-migration.md names every dropped file, what it asserted, and what now asks the question instead -- for four of them, nothing. The one worth watching is the shebang-executable check, which qa/ makes only of qa/replica/actions/*.sh. Falling out of it: the rootfs fixture in test/conftest.py had no users left, and nothing under test/ needs the proprietary firmware any more. Comments in qa/static/test_probes.py, qa/replica/test_mcu_bringup.py, bin/patch.sh and ci.yml that pointed at the deleted files say what is true now; the tree listing in docs/building.md was stale from the run-tests.py removal and is rewritten. 136 host-side tests pass. qa/static is unchanged at 218. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzLebCrvc8ssWR5JuEsha4 |
||
|
|
abd4a01d73 |
Adopt the s6-rc boot graph -- DOES NOT BOOT YET, see below
Merge of qa-suite-bridgehead into the packaging line. The seven init.d/S*
scripts and the hand-written etc/s6 servicedirs are gone; anvil-core ships
etc/s6-rc/source/ and bin/patch.sh compiles it into etc/s6-rc/compiled/ after
the payload is installed. klipperDaemon joins firmwareExe and start.sh in
payload/prog/, all three linked into place by anvil-link-prog.sh.
WHAT IS KNOWN BROKEN. qa/replica/test_s6rc.py fails: the tree never comes up,
because s6-rc-init dies with
unable to supervise service directories in /run/s6-rc/servicedirs:
Value too large for defined data type
That is EOVERFLOW out of the readdir() loop in s6rc_servicedir_manage.c, which
checks errno after every entry. Measured, and these are ruled out:
* not the live directory's filesystem -- fails identically with -l on
/usr/data as with the default /run, and the inodes there are 3 and
1358496, nowhere near 32 bits
* not the database -- s6-rc-db lists all eleven services out of it, and a
database recompiled inside the replica by the TARGET s6-rc-compile fails
exactly like the one compiled natively on x86
* not a missing -D_FILE_OFFSET_BITS=64 -- both s6-svscan and s6-rc-init
reference readdir64 and never plain readdir
What is left is the toolchain. qa-suite-bridgehead built s6, s6-rc, execline
and skalibs with the Bootlin musl toolchain, where readdir has no EOVERFLOW
path at all; the packaging line builds them as recipes against the Ingenic
glibc, where it does. s6-svscan looked healthy only because it was started on
an EMPTY scandir, which never reaches the loop.
Everything else is green: 262 static tests, and bin/verify.sh passes on the
73MB package, including the graph's own "compiled s6-rc database" check.
Resolution notes: the five MOD_* switches stay deleted -- firmwareExe defaults
every gate to 1, so nothing changes -- but MOD_CAM is now worth having by hand
on a cameraless printer, because the camera is a longrun that waits out
timeout-up. anvil-service.sh went with init.d; nothing sourced it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpUmBb4xtmnmtTosonqeNi
|
||
|
|
39b5cb0d0b |
ipk-install was a stand-in for an opkg the printer now has
The script says so itself: it existed so phase 1 could ship without waiting for a cross-built opkg, and it would hand its database over when the real client arrived. pkgs/3rdparty/opkg builds that client, and anvil-opkg is in bin/patch.sh's payload roots, so the printer has opkg and the stand-in has nothing left to do. Nothing called it. bin/build-payload.py assembles the payload with the printer's own opkg inside the replica, and the test that existed only to keep ipk-install off the build path was a guard against a file that is now gone. Its five round-trip tests went with it. The shellcheck lane loses one file rather than a rule: _files() refuses a glob that matches nothing, so the two entries had to go with the script. Cherry-picked from fc98b04 on worktree-drop-ipk-install, so the two branches carry the same patch. Resolved against this branch's payload build: the host opkg that commit still described is deleted here, and the lane globs are payload/prog/ rather than prog/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VpUmBb4xtmnmtTosonqeNi |
||
|
|
5f36cd14e3 |
Say what the code does, not what it used to
Comment cleanup over the files this branch touched, plus the dead code the
last two commits left:
* pkgs/lib.sh's pkg_buildopkg, 89 lines building the x86-64 opkg nothing
calls any more, and the qa test that checked its configure flags
* patch.sh's "5d. what is no longer here" section, and its mkdir of the
prefix directories -- build-payload.py replaces $PAYLOAD_DIR wholesale, so
they were created and then thrown away
* the two emptied creator5*-config recipe directories
The rest is prose. Comments that narrated a previous design -- what the host
opkg did, which package used to own the chamber config, that prog/ was once a
sibling of payload/ -- are gone rather than shortened; the ones explaining
constraints that still hold are trimmed to the constraint.
docs/building.md and docs/toolchange.md describe the packages as they are.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpUmBb4xtmnmtTosonqeNi
|
||
|
|
ea1364ebca |
The payload is installed by the printer, not by a host opkg
bin/build-payload.py starts the replica, lets the machine's own opkg install the feed onto the machine's own filesystem under qemu-mipsel, and tars /usr/data/anvil back out. bin/patch.sh's section 0 is that call now. WHAT THE OLD WAY COULD NOT DO. A host opkg under --offline-root leaves every package Status: unpacked, because it cannot run maintainer scripts against a staging tree -- so patch.sh passed --force-postinstall to make the database claim work that had not happened. The payload shipped a postinst nothing had ever executed. It runs now, on the target userland, and its output is in the build log: anvil-core's postinst links the boot path and says so. THE REPLICA NEEDED A WAY TO HAND SOMETHING BACK. Every mount was :ro, which is right for a test harness and wrong for a build. run_case takes out_dir and mounts one writable /out -- but not at /printer/out, because assemble.sh binds /printer-src over /printer and a bind does not carry submounts, so the mount would be silently buried. entrypoint-out.sh makes the seam the stock entrypoint lacks: run it with a case that does nothing, then mount, then run the real case. It chowns /out back to the caller afterwards, because a privileged container writing into the repo leaves root-owned files the build user cannot delete. RUNBUILD IS A THIRD LANE for the same reason. The build has to reach the docker socket now AND still run as you, or work/ fills with root-owned files -- the two things the Makefile's existing split kept apart. --group-add with the socket's own gid, read rather than assumed, answers both. Left behind: pkgs/lib.sh's pkg_buildopkg is now defined and never called. It builds the host opkg this replaces. Deleting it is a separate change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VpUmBb4xtmnmtTosonqeNi |
||
|
|
26e3388895 |
The printer picks its own chamber config
anvil-klipper-config carries printer.base.cfg and BOTH chamber configs, under config/chamber/<Machine>.cfg, and anvil-link-prog.sh symlinks the right one into /usr/data/config. The model comes from FlashForge's own app_startup.sh, which carries MACHINE= at its top -- so the machine answers the question about itself instead of a build being told the answer and having to be right. THAT REMOVES A PACKAGE PAIR THAT COULD NOT COEXIST. anvil-klipper-creator5-config and -creator5pro-config each owned config/printer.chamber.cfg, so they Conflicted, opkg refused the pair with exit 255, and bin/patch.sh had to pick one from TARGET_MACHINE. The payload was model-specific for the sake of one file; it is not any more. TARGET_MACHINE still names the output, because runFirmwareExe.sh gates on it. THE SOFTWARE COMPONENT'S klipper/config IS NOW BYTE-IDENTICAL TO STOCK -- diffed, not assumed. printer.base.cfg was the last thing patch.sh wrote there. The seven configs we do NOT modify stay stock and unpackaged, and that works because Klipper resolves [include] against the directory of the file doing the including, by the path as opened rather than the resolved target (configfile.py: dirname = os.path.dirname(source_filename)). So a symlinked printer.base.cfg still finds printer.filament.cfg and friends beside it in /usr/data/config. run-append.sh no longer copies the files the linker links -- copying would only put a real file in the way of the symlink about to replace it. moonraker.conf and moonraker-custom.conf keep their branches: those are user-editable, and a symlink would lose the .mod-new preservation a tuned trusted_clients block depends on. anvil-klipper now Depends on anvil-klipper-config, which is where that dependency always belonged -- it used to arrive only because the per-model packages pulled it in, and naming it in MOD_ROOTS would answer a missing dependency in the one place a printer's own `opkg install` never reads. prog/ is gone as a recipe subtree. Its own test said an empty one was the good outcome; it emptied, so the rule went with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VpUmBb4xtmnmtTosonqeNi |
||
|
|
722804364a |
The boot path follows the package, not the last .tgz
firmwareExe and start.sh have to sit on /usr/prog because FlashForge's own scripts look for them by absolute path. They are package files now (anvil-core, under payload/prog/) and anvil-link-prog.sh replaces the copies on /usr/prog with symlinks into $MODDIR, so what the printer executes is what the package owns. WHAT THAT BUYS: `opkg upgrade anvil-core` on a running printer used to update $MODDIR and change nothing the boot path reads -- the new firmwareExe sat in the payload while the old copy kept being run, and only a full .tgz could land it. The postinst calls the same script, so an upgrade is now enough. THE LINKS ARE MADE AFTER EXTRACTION, and that is the whole ordering. Stock run.sh distributes the component at its lines 107-179 and run-append.sh does not extract anvil.tar.xz until line 180, so the component cannot carry the links itself: on a first install they would dangle, and on an upgrade they would resolve to the payload being replaced and silently install the previous release's files. The component still carries real copies. That is what keeps a printer bootable when the payload is not there, and patch.sh now takes them from the installed payload rather than from a directory beside the recipe -- the same move it already makes for the klippy fork. A postinst needed the recipe system to have maintainer scripts at all, so build-packages.sh copies pkgs/<name>/control/* into CONTROL/. Note what that exposes: patch.sh installs with a host opkg under --offline-root --force-postinstall, and that combination DOES run maintainer scripts, with IPKG_INSTROOT empty, so their absolute paths are taken as written. Inside the build image that is harmless -- there is no /usr/prog there -- and the script's guard is what makes it a quiet no-op rather than an error. pkg_payload_hash covers control/ too, or an edited postinst would read as "already current". It takes two find calls because `find a b` with b missing exits non-zero, which under the recipes' set -euo pipefail killed every build whose recipe has no control/ -- silently, the existing 2>/dev/null swallowing the one line that said so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VpUmBb4xtmnmtTosonqeNi |
||
|
|
5b463b889a |
Five switches that were only ever set to 1
MOD_WEB, MOD_CAM, MOD_UI, MOD_SSH and MOD_WIFI all defaulted to on, and what
each one really offered was a half-installed printer: Mainsail with no
moonraker behind it, a camera that would not stream, a screen dark on purpose,
a root password deliberately not set on a machine whose entire recovery story
is "ssh in". Nobody wants those states. What someone who sets one actually
wants is a stock printer, and that is one FlashForge package away.
What they cost was a second answer to every question. Each init script carried
a gate, each `down` file argued its existence from a flag rather than from the
ordering that was the real reason, and anvil.conf had five lines that told a
user they had a choice.
MOD_SSH was the clearest case: it never turned ssh off. dropbear is the
rootfs's own and listens either way, so all the flag did was decline to set a
password.
Each gate is replaced by the fact it was standing in front of, not by a
constant:
S60nginx, S62moonraker nothing -- the web stack is what this mod is
S65camera there is no video device, which the script already
finds out and reports
S50wifi there is no $WIFI_CONF, likewise
S80ui HelixScreen is not installed, which was already the
other half of the condition
To leave a piece out, leave it out: BUILD_MAINSAIL, BUILD_MOONRAKER and
BUILD_HELIX decide what goes into a build, and `opkg remove` decides what stays
on a printer. What survives in anvil.conf is what has a range rather than a
state -- MOD_CAM_RES/FPS/PORT and the NICE_* priorities -- plus MOD_SPLASH,
MOD_STARTUP, MOD_IMPORT and MOD_S6, which are untouched here.
The `down` files stay. They were argued from MOD_WEB and MOD_CAM, but the
argument never depended on them: s6-svscan starts a service the instant the
directory appears, which is before any init script has checked a prerequisite.
Their text now says so.
Measured: `make build` produces a payload with no live read of any of the five,
and qa-static is 265 passed with shellcheck over the edited init scripts. The
test suite still exercises the removed flags and has not been updated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKRUCqj18TVF1Vz1oMpmC
|
||
|
|
09e1e6521f |
patch.sh was asking the payload questions its recipes had already answered
Four sections went. Three of them were checks: _sqlite3 by name, which pkgs/3rdparty/python/build.sh already made; libsodium's symlink, which pkgs/3rdparty/libsodium/build.sh already made; and an ABI walk of the whole payload, which bin/build-packages.sh makes over every PKG_ROOT on the way into every .ipk -- from the packaging loop rather than a build.sh, so cached trees are re-read too. The fourth was not a check. config.env's BUSYBOX_BIN was copied into the payload here, which made it the one file no package owned: an allowance in the "every file is owned" test, an ABI gate of its own because the payload-wide one ran before the copy, and a file no `opkg remove` could take away. It is pkgs/3rdparty/busybox now, PKG_WHEN-gated on BUSYBOX_BIN, and it needed a third source verb -- pkg_prebuilt, beside pkg_unpack and pkg_intree -- because a recipe names its source exactly once and this one's source is a path someone typed. Once it was a package, nothing in the payload came from outside the feed, which is what made the payload-wide ABI gate redundant rather than merely duplicated. The "at least twelve extension modules in site-packages" count was the one check with nowhere to go and no duplicate. It is pkg_pynative in the seven python-* recipes that ship a .so: an aggregate could say that a native package had fallen back to a pure-python wheel but never which one, and each of those recipes can say it about itself. What that costs is written into the section it left: a gate inside build.sh does not run on a cache hit, and no python recipe sets PKG_STAMP_EXTRA. That is pkg_stamp's problem, not an argument for a second copy of every check here. Measured: `make packages` builds anvil-busybox_1.31.1-1_mipsel_xburst2.ipk, `make build` assembles a 32-package payload whose bin/busybox is listed in anvil-busybox.list, and qa-static is 265 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011gKRUCqj18TVF1Vz1oMpmC |
||
|
|
21cf19f2e2 |
BUILD_KLIPPER=stock kept a Klipper nothing else was written for
The flag chose between the Klipper4FlashForge fork and FlashForge's own v0.12 tree. Every other thing the mod ships had already stopped being neutral about that choice: the ff_*.py extras are the fork's package's own files, the ff-*.cfg declare sections only those extras implement, and c_helper.so is compiled from the tree it ships beside. A stock build was a printer with the mod's configuration and none of the code behind it -- and the previous commit turned that into a hard error, which is what showed the flag had nothing left to select. So it is gone rather than guarded. pkgs/klipper loses its PKG_WHEN, patch.sh section 1 loses the case and is straight-line, verify.sh section 7 stops being conditional -- a package with no klippy tree is now unrepresentable rather than merely gated -- and fetch-assets.sh pulls the fork tarball on every build, the way it already does s6 and CPython. Two fixture-lane repairs come with it. test/run-tests.py said BUILD_KLIPPER=stock to keep the lane off the network. That reason had already expired: since the payload became the feed installed, the lane runs build-packages first, and that needs the ~203MB Ingenic toolchain from vendor/ for CPython alone. The Klipper tarball is 27MB beside it, cached on versions.env like everything else. The lane now builds the fork, so verify.sh's ABI and symbol checks on c_helper.so run there for the first time. And the synthetic Moonraker tarball unpacked to moonraker-fixture/, while pkgs/moonraker/build.sh looks for moonraker-$MOONRAKER_VERSION/ -- the shape GitHub generates. That lane could therefore never build anvil-moonraker at all. It passed only while work/pkg/moonraker held a tree from a REAL build, because pkg_stamp keys on the version and not on the tarball; the first run against a cold cache died with "no moonraker/moonraker.py", a true statement about a tarball nobody could have made work. CI caches work/pkg on versions.env, so the first run after any pin bump would have hit it. Confirmed pre-existing by running the parent commit against the same cold cache: it fails identically. make qa-static 261 passed; make test 9 gates, 0 failed, with the fixture lane building Klipper and Moonraker from its own assets for the first time; CI's shellcheck gate clean. |
||
|
|
53a2bc010b |
Five extras were on the firmware partition because of a flag
pkgs/klipper/prog/klippy/extras/ff_*.py sat on /usr/prog for one reason, and it was not a technical one: anvil-klipper answers to BUILD_KLIPPER and the extras answered to BUILD_TOOLCHANGE, and a package cannot hold files gated differently from itself. So they stayed outside every package, copied to the firmware partition by bin/patch.sh. They are anvil-klipper's now, at payload/klipper/klippy/extras/, and BUILD_TOOLCHANGE no longer gates them. An extra is inert until a config section instantiates it, and those sections are anvil-klipper-config's, where the flag still applies -- so the gate bought nothing and cost the mismatch. Inside the tree is the only place they can go: klippy resolves an extra as dirname(klippy.py)/extras/<name>.py (klippy.py:90-103), with no search path and no environment variable. bin/patch.sh still copies them into the software component -- klippy runs from /usr/prog until the s6 service lands -- but reads them out of the payload now, the way section 1 already reads the klippy tree, so the recipe directory is not a second source for a file a package holds. It names the five rather than globbing ff_*.py: the fork ships an ff_eddy.py of its own and the glob could not tell them apart. Verified the component still gets exactly the same five files. The flags are coupled by this, and patch.sh says so rather than shipping a broken printer: BUILD_TOOLCHANGE=1 with BUILD_KLIPPER=stock now fails with the fix in the message. It has to -- the extras are written against the fork's v0.13 klippy, and dropping them onto FlashForge's v0.12 is the half-overwritten mixture that shipped as v20260824. The fixture lane in test/run-tests.py used exactly that pair (BUILD_KLIPPER=stock is a no-network convenience there) and now sets BUILD_TOOLCHANGE=0. pkgs/klipper/pkg.conf gains PKG_STAMP_EXTRA=$(pkg_payload_hash): KLIPPER_VERSION says nothing about payload/, so without it an edit to ff_toolchange.py would read as "already current". make qa-static 261 passed; make test 9 gates, 0 failed. The .ipk carries all five under klipper/klippy/extras/. |
||
|
|
d92b0778c5 |
The chamber-config check bin/verify.sh was already making
Nine lines in patch.sh asserting config/printer.chamber.cfg landed in the payload. bin/verify.sh:155 asserts the same thing under the same BUILD_TOOLCHANGE gate, and does it better: it greps the listing of the built anvil.tar.xz rather than the staging directory, so it checks what ships, and it checks ff-toolchange.cfg beside it. It runs on the release path and as a gate in make test. Confirmed both directions rather than assumed: removed printer.chamber.cfg from a built payload, repacked, and verify.sh reported FAIL with 'do not install'; rebuilt clean and both checks pass. make qa-static 261 passed; make test 9 gates, 0 failed. |
||
|
|
29a6266766 |
The cleanup that was tidying up after nobody
rm -rf of the opkg config, log, lists and cache. All four sit at $PAYLOAD_ROOT, beside $PAYLOAD_DIR rather than inside it, and bin/pack.sh tars -C "$PAYLOAD_DIR" . -- so they were never able to ship. Confirmed against the built tarball: zero entries. Line 34 already wipes the whole root at the start of every run, and the four come to 120K, most of it the fetched index. Deleting it means the opkg log survives a successful build, not only a failed one, which is where you look to ask what opkg actually did. The config comment now says why lists_dir and cache_dir are placed outside $MODDIR, since that placement is now the only thing keeping them out of the payload. Also trims the Installed-Time comment: the point is that bin/build-packages.sh already builds every .ipk with SOURCE_DATE_EPOCH=1 and this is the one clock opkg puts back, not four lines on upstream's position. Payload and tarball unchanged; qa-static 261 passed. |
||
|
|
1dd080324a |
Six lines of history for one flag
--force-postinstall needs to explain itself, not recount how we found out. The IPKG_INSTROOT finding lives in docs/notes/85-packaging.md, which is where somebody designing the firmwareExe postinst will look; it was not helping anyone reading this line. Moved onto the call it modifies, too -- it had drifted up onto the mod_opkg definition, which is not where --force-postinstall appears. |
||
|
|
dce1f62146 |
Naming the release and letting Depends work out the rest
bin/patch.sh enumerated the payload by hand: every recipe from pkg_recipes, minus the -dev packages, minus the chamber config for the other model. Forty lines to compute a closure opkg already computes, and computing it here meant the Depends fields were never read by anything. A printer running `opkg install anvil-moonraker` would have been the first to find out whether they were right. The feed is indexed, so point opkg at it -- `src anvil file:$PKG_FEED` and an `opkg update` -- and name the twelve packages the release IS. opkg pulls the other nineteen and marks them Auto-Installed. `file:` is answered before curl is reached at all (libopkg/opkg_download.c:134), which is why this works with the --disable-curl host opkg. The PKG_WHEN gates still reach here, by absence: a gated-off recipe leaves no .ipk in the feed, so BUILD_KLIPPER=stock has nothing to install and no BUILD_* flag is restated outside the pkg.conf that owns it. The cost is that a misspelled root drops out the same silent way, which test_the_payload_roots_name_real_packages now catches, and test_the_payload_roots_stay_a_short_list keeps a missing file from being answered by naming the package here instead of in the depending recipe's PKG_DEPENDS. Four of the twelve are Recommends, a field opkg does not have: pillow and preprocess-cancellation are Moonraker's thumbnail path, which pkgs/moonraker argues out of Depends; greenlet and cffi are klippy's, which pkgs/klipper cannot declare while klippy still runs under FlashForge's interpreter. anvil-core is the one version that cannot be left to opkg -- its PKG_VERSION is MOD_VER, today's date -- so its expected filename is still checked before installing. Payload verified against a build from the parent commit: 3298 paths, modes and symlinks identical, every file's sha256 identical except var/lib/opkg/status, which improves. Dependencies now say "Auto-Installed: yes" and the flag field is "ok" rather than "user" on packages nobody asked for by name. 31 packages, 154 ELF objects, as before. make qa-static 261 passed; make test 9 gates, 0 failed. |
||
|
|
8f54d87513 |
Comments that restated the code, and a few that had stopped being true
A pass over what this branch added, on the same rule as the branch merged before it: a comment earns its place by saying WHY, and the ones here had grown into essays. bin/patch.sh 781 -> 555 lines, its comments 523 -> 297. The install section keeps the three things a reader cannot get from the code -- the set comes from pkg_recipes so the BUILD_ flags are read once, -dev is filtered by package name because nine recipes ARE the dev package, --force-postinstall is what stops the database saying "unpacked" about files that are in place -- and loses the narration around them. Sections 3 and 4 were mostly an account of the sections that used to be there; they are four lines now. The CPython and libsodium headers keep the facts that took measuring (FF_PYTHON's callers, the missing _sqlite3 that pins Moonraker, libnacl's __file__ arithmetic and its dependence on "lib" appearing in the prefix) and drop the rest. Same for pkg_buildopkg, the payload-path block in bin/common.sh, and the four test docstrings. Also fixed, found while reading rather than looked for: bin/build-packages.sh still called itself "the proof-of-concept half" and said bin/patch.sh "still stages libsodium into the payload exactly as it did and `make build` is untouched". None of that survived the migration. The -maxdepth 1 comment had been wedged between `# shellcheck disable` and the command it applies to. Eleven "bin/patch.sh stages ..." references across pkgs/, test/ and qa/ described a script that stages nothing now. Payload bytes unchanged except S40s6, whose comment was one of the eleven. qa-static 259; make test 9 gates, 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
51f524b5cf |
Merge worktree-comment-cleanup
The branch tightens comments across the tree -- removing the ones that
explained the code's past rather than the code. It branched before both the
pkg/ -> pkgs/ rename and the migration of bin/patch.sh onto the feed, so most
of its conflicts were with text describing code that has since moved or gone.
Resolved by taking its brevity and correcting what it could not have known:
bin/patch.sh ours, entirely. Its only non-comment change there deletes a
trailing comment from a line this branch rewrote, and every
comment it edits annotates one of the eight staging sections
that no longer exist.
bin/fetch-assets.sh ours. Theirs reinstates a KLIPPER_FORK condition; that
knob was deleted with the move to pkgs/ -- a recipe names its
source exactly once.
pkgs/lib.sh theirs, except the two hunks where our side carries code it
never saw (pkg_payload_hash and pkg_dir; the PKG_CC_SHARED
branch of pkg_build). Its prose, our functions.
bin/common.sh theirs throughout, with pkg/ -> pkgs/ and $MOD_PAYLOAD ->
$PAYLOAD_DIR, and minus its claim that the toolchain is used
"by bin/patch.sh for Klipper's c_helper.so" -- patch.sh
compiles nothing now.
pkg/anvil-core/build.sh deleted. The file is pkgs/anvil-core/build.sh and
was rewritten as a loop; its comments are superseded.
Two of its deletions were right about things this branch had wrong:
bzip2 named a pkg_make verb that was retired into pkg_build.
zlib claimed a ./configure line of its own as "the one sanctioned
exception". It has not had one since PKG_CONFIGURE_AUTO=0 and CHOST; the
rule in qa/static/test_ipk.py is absolute, and its docstring says so. I had
kept that paragraph in an earlier pass of this merge and was wrong to.
Three stale hints in the Makefile went with them: work/pkg is filled by
`make packages`, not by bin/patch.sh, in the two gate targets and one error
message.
Verified: the payload rebuilds to the same 31 packages and 154 ELF objects,
and the six files whose bytes changed are five payload scripts this branch
comment-edited (zero non-comment lines between them) plus the opkg status
file, which records their sizes. qa-static 259; make test 9 gates, 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
9ea219444d |
A gate that stops before the last copy is a gate with a hole in it
BUSYBOX_BIN is the one file in the payload that no package puts there: an optional prebuilt binary named in config.env, for anyone who wants applets the printer's small busybox was built without. It was copied in nine lines below the last ABI check, so the single file that comes from outside this build entirely was the single file never checked -- the exact inversion of what the gate is for. A foreign-ABI busybox in $MODDIR/bin is ENOEXEC at the first call from an init script, on the printer, with nothing in the message to say where it came from. Moved above the payload sweep, and given its own mips_abi_gate call rather than being left to that sweep, so the error names BUSYBOX_BIN and config.env instead of "the payload". The plausible failure is a busybox lifted out of the printer's own rootfs, whose e_flags nobody here has measured, and that is a ten-second diagnosis only if the message says which file it means. Proven by pointing BUSYBOX_BIN at the container's x86-64 /bin/cat: the build stops and says so. Note it has to be pointed there in config.env and not in the environment -- config.env assigns BUSYBOX_BIN unconditionally, so an exported value is discarded, which is how the first attempt at this test passed while proving nothing. Also: the missing-.ipk error now recognises the case that actually happens. anvil-core and the config packages take PKG_VERSION from MOD_VER, which defaults to today's date, so a feed built yesterday resolves to filenames that do not exist today -- and "no .ipk for recipe 'anvil-core'" reads as a missing package rather than an expired feed. It now names both versions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e3d1bdcdf7 |
The payload is the feed installed, not the build trees copied
bin/patch.sh ran every recipe and then cp -a'd its build tree into
work/modpayload. Eight sections of it, each a second description of what the
.ipk that recipe produces already contains -- the same duplication the recipes
themselves removed one level down, one level further up.
It now installs the feed. pkg_buildopkg's x86-64 opkg gets one invocation with
the whole selected set and an --offline-root, and what lands there is the
payload. "What does this release install?" is answerable with
`opkg list-installed` against the payload itself, and the tarball and the
package cannot describe different bytes because the tarball is made of the
package.
THE SET COMES FROM pkg_recipes, which is already PKG_WHEN-gated -- so
BUILD_HELIX, BUILD_MAINSAIL, BUILD_MOONRAKER, BUILD_TOOLCHANGE and
BUILD_KLIPPER are read in the pkg.conf that owns each and nowhere else,
instead of being restated by an `if` here that could drift from it.
-dev IS FILTERED BY PACKAGE NAME, and the first attempt got this wrong in a
way worth recording. execline, s6 and CPython ship both halves and pkg_ipk
names the runtime one; zlib, openssl, sqlite, expat, libffi, xz, bzip2,
libarchive and skalibs ship ONLY a dev package and say so by setting
PKG_NAME=anvil-<x>-dev outright. Asking "does this recipe have a runtime half"
installed all nine: 40 packages and 2165 ELF objects where there should be 31
and 154.
--force-postinstall, because opkg skips configuration under an offline root
and would leave every package Status: unpacked. Nothing runs opkg on the
printer after run-append.sh extracts the tarball -- the extraction IS the
install -- so a database saying otherwise would make the first real `opkg
upgrade` argue with the filesystem. It runs nothing today: no recipe defines a
maintainer script, and opkg 0.7.0 would not run one here anyway (it sets no
IPKG_INSTROOT; see libopkg/pkg.c:1339).
Installed-Time is normalised afterwards. opkg takes it from the clock and
honours SOURCE_DATE_EPOCH only for a man-page date, so two builds of one
commit differed by one line per package -- measured, then fixed, then measured
again.
Gone with the staging: the hand-written PKG_DEV_FILES prune (the dev half is
not installed, so bin/verify.sh's "no headers" check now passes because they
were never there), three partial ABI gates in favour of one over the finished
payload, and the last pkg_out call -- section 1 reads klippy out of the
installed anvil-klipper.
The payload gains anvil-klipper's tree at $MODDIR/klipper and bin/opkg. The
first is inert until phase 7 of docs/notes/80-s6-migration.md and is 2.4MB of
duplicate until then; the second is route 1 of the bootstrap in
docs/notes/85-packaging.md -- the first payload that can install its own
successor.
VERIFIED by building the payload from the parent commit and from this one, on
the synthetic stock fixture with the real vendored assets:
files removed 0
content changes among the 2778 shared files 1 (.install-manifest,
which lists the payload)
mode changes 0
symlink changes 0
added klipper/**, var/lib/opkg/** (63), bin/opkg, and the
www/mainsail/.version the previous commit stopped deleting
two builds in a row byte-identical
bin/verify.sh passes every check including the ship boundary; qa-static 255;
make test 9 gates, 0 failed; test-py 181.
test/run-tests.py builds the feed as the first step of the fixture lane, into
a PKG_FEED of its own -- sharing work/packages let the MOD_VER=ci build rename
every dated package and the prune delete the rest on the way past, measured as
44 becoming 43.
qa/static/test_ipk.py's share-one-build test is inverted rather than deleted:
the property was "one build, two vehicles" and is now "one vehicle", so what
it asserts is that nobody reintroduces the second path -- no recipe run from
patch.sh, no pkg_out, no work/pkg, and opkg rather than pkgs/ipk-install,
which stays the printer's hand-repair tool.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ff5eb41cc6 |
PKG_EXCLUDE deleted a file upstream shipped, and only the packager could see it
PKG_EXCLUDE has exactly one value across all 41 recipes -- ".version", the stamp pkg_end writes at the ROOT of a recipe's output tree. It was applied with `find -name`, which matches a basename at ANY depth, and Mainsail's release zip ships a .version of its own at www/mainsail/.version. So anvil-mainsail has been shipping without upstream's version file. Nothing noticed because nothing looked: bin/patch.sh built the payload by copying work/pkg/mainsail, where the file was still there, so the tarball had it and only the .ipk did not. It surfaced as one line in the before/after diff of the payload while moving patch.sh onto the feed, which is the first arrangement in which a package's contents and the payload's contents are the same question. -maxdepth 1 is the fix: the stamp is always at the root, and nothing else this option is used for is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |