mirror of
https://github.com/Klipper4FlashForge/firmware.git
synced 2026-10-22 13:22:16 -05:00
`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 a1b4ea6, 45 commits after ci.yml was last touched.
Both ran unpack/patch/pack straight into that guard.
* `make release` ran bin/build.sh on $(RUN), which has no docker socket,
while patch.sh assembles the payload inside the replica. `build` has
used RUNBUILD all along; `release` was left behind. It also built no
feed. It now builds one, once, outside the per-model loop -- nothing in
it is model-specific.
* ci.yml's shellcheck gate named pkgs/*/payload/init.d/S* and
pkgs/*/prog/*.sh, neither of which exists any more; shellcheck exits 2
on a path it cannot open. It is a `find` with an asserted count now,
because the opposite failure is worse: a glob that quietly matches
nothing checks nothing and still reports green.
Both workflows now go through `make` instead of hand-rolling `docker run`.
Hand-rolling meant CI exercised entry points no human uses, which is exactly
why the `release` runner bug could sit there unseen; it also ran as root and
left a work/ that the later `make release` could not write to.
ci.yml's test job builds the feed before the static lane. A good part of that
lane asks what is inside an .ipk and those questions do not fail without one
-- they return, and the lane reports a pass having never opened a package.
build-packages.sh needs no stock firmware, which is what makes that possible.
Docs, and the packages skill, which still documented pkg_buildopkg and
work/.opkg-host as how the payload is assembled -- the host opkg went in
dcd037c and the printer's own installs the feed now -- and still claimed
qa/static holds an ABI gate over every ELF in every package. It does not:
qa/replica/test_abi.py is the only one, and qa/static asserts a seventh does
not grow back.
Verified: qa/static is 122 passed / 110 errors here, identical to master --
every error is `vendor/opkg-utils is missing` in a checkout that has not run
`make vendor`. mkdocs build --strict passes. The new shellcheck gate was run
as written. The workflows themselves were not executed.
tools/replica/printer/*.sh live inside the published image, so those edits
take effect on the next `make printer-image`; behaviour is unchanged either
way, because nothing sets PROG_DUMP any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TrhjKabxDihvVjNQGGT4MK
151 lines
6.5 KiB
Bash
Executable File
151 lines
6.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# Run a test case inside a replica of the printer, on the printer's own binaries.
|
|
#
|
|
# ROOTFS dir with the extracted rootfs.squashfs (default /rootfs)
|
|
# PROG_DUMP a real /usr/prog taken off a printer (tar or dir), used
|
|
# verbatim -- this is what removes the stubs entirely
|
|
# BASE_PKG stock .tgz to install first, to get an authentic baseline
|
|
# PKGS "name=/path/to.tgz ..." -- copied onto the simulated USB stick
|
|
# FF_KEY package encryption key
|
|
# $1 script to execute inside the chroot
|
|
#
|
|
# Everything after assemble.sh runs under the printer's MIPS busybox via
|
|
# qemu-user + binfmt. There is no host shell inside the chroot.
|
|
set -e
|
|
|
|
CASE="${1:?usage: entrypoint.sh <script-to-run-inside-chroot>}"
|
|
R=/printer
|
|
FF_KEY="${FF_KEY:-FFP0331&*%root}"
|
|
|
|
/opt/printer/binfmt.sh
|
|
|
|
# USB_STICK=1 -- build a real FAT filesystem out of the packages before the
|
|
# machine is assembled, so app_startup.sh can find and mount it itself. See
|
|
# assemble.sh for the loop device, and case-install.sh for what uses it.
|
|
if [ "${USB_STICK:-0}" = 1 ]; then
|
|
SZ=64
|
|
for spec in ${PKGS:-}; do
|
|
f="${spec#*=}"
|
|
[ -f "$f" ] || { echo "entrypoint: $f is not there -- nothing to put on the stick" >&2; exit 1; }
|
|
SZ=$((SZ + $(du -m "$f" | cut -f1)))
|
|
done
|
|
dd if=/dev/zero of=/stick.img bs=1M count=$SZ status=none
|
|
mkfs.vfat -n CREATOR5 /stick.img >/dev/null
|
|
mkdir -p /stick
|
|
mount -o loop /stick.img /stick
|
|
for spec in ${PKGS:-}; do
|
|
name="${spec%%=*}"; path="${spec#*=}"
|
|
cp "$path" "/stick/$name"
|
|
done
|
|
ls -l /stick | sed 's/^/printer-sim: stick: /'
|
|
umount /stick
|
|
fi
|
|
|
|
/opt/printer/assemble.sh
|
|
|
|
# /usr/prog and /usr/data are the image's own /parts, bind-mounted by
|
|
# assemble.sh. A PROG_DUMP branch used to sit here, unpacking a factory image
|
|
# or a tar off a real printer for the replica that was built locally; that
|
|
# replica is gone and the image always has the genuine partitions baked in.
|
|
|
|
ROOTFS="$ROOTFS" /opt/printer/seed-prog.sh
|
|
|
|
# Prove we really are on the printer's userland before running anything.
|
|
ID="$(chroot $R /bin/busybox sh -c 'echo "$(uname -m) $(busybox 2>&1 | head -1 | cut -d" " -f2)"')"
|
|
case "$ID" in
|
|
mips\ v*) ;;
|
|
*) echo "printer-sim: chroot is not the printer userland (got '$ID')" >&2; exit 1 ;;
|
|
esac
|
|
echo "printer-sim: $ID (real rootfs, qemu-mipsel)"
|
|
|
|
# The stock package goes on first: that is the only authentic source for
|
|
# /usr/prog/klipper, firmwareExe, unTar, app_startup.sh and friends.
|
|
# Already baked into the image? tools/replica/build-printer-image.sh installs
|
|
# the stock
|
|
# package once at build time and records its md5 here, because doing it per run
|
|
# costs 37 seconds of qemu and real `sleep` calls for a result that is
|
|
# identical every time.
|
|
BASELINE_SKIP=0
|
|
if [ -n "${BASE_PKG:-}" ] && [ -f "$BASE_PKG" ] && [ "${FORCE_BASELINE:-0}" != 1 ]; then
|
|
HAVE="$(cat $R/usr/prog/.BASELINE 2>/dev/null || true)"
|
|
if [ -n "$HAVE" ] && [ "$HAVE" = "$(md5sum "$BASE_PKG" | cut -d' ' -f1)" ]; then
|
|
echo "printer-sim: stock baseline already in the image ($HAVE)"
|
|
BASELINE_SKIP=1
|
|
elif [ -n "$HAVE" ]; then
|
|
echo "printer-sim: image was baked with a different package ($HAVE) -- reinstalling"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "${BASE_PKG:-}" ] && [ -f "$BASE_PKG" ] && [ "$BASELINE_SKIP" = 0 ]; then
|
|
mkdir -p $R/mnt/base
|
|
openssl des3 -d -k "$FF_KEY" -salt -md md5 -in "$BASE_PKG" | tar -xf - -C $R/mnt/base
|
|
# kernel-* rewrites eMMC partitions and control-* flashes MCUs over
|
|
# /dev/ttyS*. Neither can do anything useful in a container and both are
|
|
# destructive if they ever found real hardware, so the baseline install is
|
|
# software+library only. /dev here has no block devices and /sys is
|
|
# read-only, so even a stray attempt cannot reach a disk.
|
|
rm -f $R/mnt/base/kernel-*.tar.xz $R/mnt/base/control-*.tar.xz
|
|
chmod +x $R/mnt/base/runFirmwareExe.sh
|
|
MACH="$(sed -n 's/^MACHINE=//p' $R/mnt/base/runFirmwareExe.sh | head -1)"
|
|
PID="$(sed -n 's/^PID=//p' $R/mnt/base/runFirmwareExe.sh | head -1)"
|
|
echo "printer-sim: installing stock baseline ($MACH/$PID)"
|
|
chroot $R /bin/sh /mnt/base/runFirmwareExe.sh "$MACH" "$PID" > $R/tmp/baseline.log 2>&1 || {
|
|
echo "printer-sim: BASELINE stock install failed -- the harness is wrong, not the mod" >&2
|
|
tail -30 $R/tmp/baseline.log >&2; exit 1; }
|
|
rm -rf $R/mnt/base
|
|
echo "printer-sim: baseline installed"
|
|
fi
|
|
|
|
# The mod payload, for cases that exercise it directly rather than through an
|
|
# installed package.
|
|
#
|
|
# ASSEMBLED FROM TWO MOUNTS, because the repository files it draws on live
|
|
# with the recipes that own them: anvil-core's $MODDIR overlay, and Klipper's
|
|
# start.sh, which is a /usr/prog file and so lives in prog/. There was a third
|
|
# for anvil-core's anvil.conf template, and it went when anvil.conf did.
|
|
# The assembled tree is what /tmp/payload has always been; see the comment on
|
|
# the mounts in qa/lib/replica.py.
|
|
if [ -d /payload ]; then
|
|
mkdir -p $R/tmp/payload
|
|
cp -a /payload/. $R/tmp/payload/
|
|
[ -f /payload-klipper/start.sh ] \
|
|
&& cp -f /payload-klipper/start.sh $R/tmp/payload/start.sh
|
|
fi
|
|
|
|
# The USB stick. In USB_STICK mode it is already a FAT filesystem on
|
|
# /dev/sda1 and the boot script mounts it itself; here we would only be
|
|
# handing the case script a shortcut it must not have.
|
|
if [ "${USB_STICK:-0}" != 1 ]; then
|
|
for spec in ${PKGS:-}; do
|
|
name="${spec%%=*}"; path="${spec#*=}"
|
|
cp "$path" "$R/mnt/$name"
|
|
done
|
|
fi
|
|
|
|
# Say out loud what is not authentic, every run. A simulation that hides its
|
|
# own substitutions is how a test ends up proving nothing.
|
|
N=$(wc -l < $R/usr/prog/.SIMULATED 2>/dev/null || echo 0)
|
|
echo "printer-sim: $N simulated pieces (/usr/prog/.SIMULATED)"
|
|
[ "${SIM_VERBOSE:-0}" = 1 ] && sed 's/^/ /' $R/usr/prog/.SIMULATED || true
|
|
|
|
cp "$CASE" $R/tmp/case.sh
|
|
chmod +x $R/tmp/case.sh
|
|
set +e
|
|
chroot $R /bin/sh /tmp/case.sh
|
|
RC=$?
|
|
set -e
|
|
|
|
# DETACH THE STICK'S LOOP DEVICE. assemble.sh attaches /stick.img with
|
|
# `losetup -f --show` and records the device in /stick.loop, and until this
|
|
# existed nothing ever read that file. Loop devices are the HOST's -- they are
|
|
# not namespaced -- so every USB_STICK=1 run leaked one permanently, and a
|
|
# WSL2 kernel has thirteen. Once they were gone every replica run failed with
|
|
# "failed to setup loop device", which looks like a docker problem and is not.
|
|
# `mount -o loop` would autoclear; `losetup` does not, so it has to be here.
|
|
if [ -f /stick.loop ]; then
|
|
umount "$R/mnt" 2>/dev/null || true
|
|
losetup -d "$(cat /stick.loop)" 2>/dev/null || true
|
|
fi
|
|
|
|
exit $RC
|