From 8da5c911ca7554c16a3f89dd08086e23dfd90d04 Mon Sep 17 00:00:00 2001 From: Oleksandr Shyshatskyi Date: Sat, 22 Aug 2026 22:11:53 +0300 Subject: [PATCH] Recovery is the stock package; add the model gate check Drop bin/make-uninstall.sh. Diffing its output against FlashForge's own software component showed it differed in exactly two files -- run.sh (22 appended lines) and the md5sum.list that changed because of it. Everything doing the actual reverting was FlashForge's. So recovery is simply "flash the stock package you already have"; the leftover /usr/data/mod payload is inert once firmwareExe is the genuine binary again. test/sim-roundtrip.sh now proves that path instead: install the mod, flash the stock package, assert firmwareExe matches the stock binary byte-for-byte, the boot scripts are restored, and no stock script references the payload. That test immediately surfaced a real problem. Packages carry a model gate -- runFirmwareExe.sh has MACHINE=/PID= baked in and refuses to install when they do not match what app_startup.sh passes from the installed firmware: Creator5 / 0028 Creator5Pro / 0029 The stock package in use here is the non-Pro build (MACHINE=Creator5), while the target printer reports "Creator 5 Pro" in general.json. Anything built from it would be refused by the printer, and it is not a valid recovery image for a Pro either. unpack.sh now reports the gate, verify.sh fails on a mismatch against TARGET_MACHINE in config.env, and the ladder doc leads with checking it. --- Makefile | 32 +++++----- README.md | 11 +++- bin/make-uninstall.sh | 88 ---------------------------- bin/unpack.sh | 10 ++++ bin/verify.sh | 23 ++++++++ config.env.example | 11 ++++ docs/hardware-testing.md | 35 +++++++++-- test/run-tests.sh | 9 +-- test/sim-roundtrip.sh | 122 +++++++++++++++++++++------------------ 9 files changed, 173 insertions(+), 168 deletions(-) delete mode 100644 bin/make-uninstall.sh diff --git a/Makefile b/Makefile index 8ac6214..cbf0b0e 100644 --- a/Makefile +++ b/Makefile @@ -23,9 +23,15 @@ DOCKER_SOCK := /var/run/docker.sock # build container saw the repo as /src it would ask the daemon to mount a # /src that does not exist there, and the sibling would get empty # directories. Keeping the path identical on both sides avoids that entirely. +# config.env usually points at a stock package OUTSIDE this repo, so the +# directory holding it must be mounted too. Override when yours lives +# elsewhere: make ASSET_ROOT=/path/to/parent probe +ASSET_ROOT ?= $(firstword $(wildcard /mnt/c /Users /home)) + ifeq ($(LOCAL),) RUN = $(DOCKER) run --rm -i \ -v "$(CURDIR)":"$(CURDIR)" -w "$(CURDIR)" \ + $(if $(ASSET_ROOT),-v "$(ASSET_ROOT)":"$(ASSET_ROOT)",) \ -v $(DOCKER_SOCK):$(DOCKER_SOCK) \ -e PROFILE -e REAL_PKG -e SIM_IMAGE \ $(IMAGE) @@ -37,8 +43,8 @@ endif .DEFAULT_GOAL := help .PHONY: help image shell build probe ssh web full helix all-profiles \ - rootfs uninstall-pkg verify test test-lint test-install \ - test-roundtrip test-ui test-ash test-abi clean distclean + rootfs verify test test-lint test-install \ + test-recovery test-ui test-ash test-abi clean distclean help: @echo 'creator5-custom-firmware -- everything runs in Docker' @@ -50,13 +56,15 @@ help: @echo ' make full stage 3 + forked Klipper, toolchanger' @echo ' make helix stage 4 + HelixScreen as the UI' @echo ' make all-profiles' - @echo ' make uninstall-pkg build this FIRST, keep it on a spare stick' + @echo + @echo 'Recovery: keep a copy of the STOCK FlashForge .tgz on a spare stick.' + @echo 'Flashing it restores every file the mod touches (see make test-recovery).' @echo @echo 'Test:' @echo ' make test everything below' @echo ' make test-lint brick-risk lint' @echo ' make test-install run the installer against a fake printer' - @echo ' make test-roundtrip install -> uninstall -> back to stock' + @echo ' make test-recovery install mod -> flash stock -> back to stock' @echo ' make test-ui UI selection, crash fallback, SAFE-MODE' @echo ' make test-ash parse the payload with the printer own busybox' @echo ' make test-abi MIPS ELF ABI checks' @@ -93,10 +101,6 @@ rootfs: image config.env @$(RUN) ./bin/unpack.sh >/dev/null @$(RUN) ./bin/extract-rootfs.sh -uninstall-pkg: image config.env - @$(RUN) ./bin/unpack.sh >/dev/null - @$(RUN) ./bin/make-uninstall.sh - verify: image @$(RUN) ./bin/verify.sh @@ -120,11 +124,13 @@ test-install: image [ -n "$$pkg" ] || { echo "build a package first: make probe"; exit 1; }; \ ./test/sim-install.sh "$$pkg"' -test-roundtrip: image - @$(RUN) sh -c 'm=$$(ls -1 work/out/Creator5Pro-*.tgz 2>/dev/null | grep -v uninstall | head -1); \ - u=work/out/Creator5Pro-uninstall.tgz; \ - [ -n "$$m" ] && [ -f "$$u" ] || { echo "need: make build && make uninstall-pkg"; exit 1; }; \ - ./test/sim-roundtrip.sh "$$m" "$$u"' +# Recovery = flash the stock package you already have. This proves it works. +test-recovery: image config.env + @$(RUN) sh -c '. ./config.env; \ + m=$$(ls -1 work/out/Creator5Pro-*.tgz 2>/dev/null | head -1); \ + [ -n "$$m" ] || { echo "build a package first: make full"; exit 1; }; \ + [ -f "$$STOCK_TGZ" ] || { echo "STOCK_TGZ not found: $$STOCK_TGZ"; exit 1; }; \ + ./test/sim-roundtrip.sh "$$m" "$$STOCK_TGZ"' clean: @rm -rf work/stage work/out work/uninst work/uninst-sw work/modpayload diff --git a/README.md b/README.md index 2ee5292..dfaa64a 100644 --- a/README.md +++ b/README.md @@ -95,9 +95,16 @@ recoverable. Full procedure with go/no-go checks: ```sh make probe # or: make build PROFILE=probe make all-profiles -make uninstall-pkg # build this FIRST and keep it on a spare stick ``` +**Recovery** is the stock FlashForge package for your model — keep a copy on a +spare stick. Flashing it restores every file the mod touches; the payload +under `/usr/data/mod` survives but is inert. `make test-recovery` proves it. + +**Model gate:** every package refuses to install on the other model +(`Creator5`/`0028` vs `Creator5Pro`/`0029`). Set `TARGET_MACHINE` in +`config.env` and `make verify` will catch a mismatch before you flash. + --- ## Testing: how we know it does not brick @@ -110,7 +117,7 @@ the whole pipeline runs in CI on a clean machine. |---|---| | `test-lint` | Scans on-printer scripts for brick patterns: raw block-device writes, `rm -rf` of top-level paths, unguarded `rm -rf $VAR/`, missing backups | | `test-install` | **Runs the installer for real** in a container with a fake printer rootfs, then asserts the printer would still boot: UI present and executable, touchscreen driver intact, boot scripts unmodified, user `printer.cfg` preserved, re-install idempotent | -| `test-roundtrip` | Installs the mod, then the uninstall package, and asserts the machine is back to stock | +| `test-recovery` | Installs the mod, then flashes the **stock** package, and asserts the machine is genuinely back to stock and the leftover payload is inert | | `test-ui` | Drives the UI selection logic: helix missing, crash-loop, SAFE-MODE latch and release, **and the no-UI-at-all case** | | `test-ash` | Parses every on-printer script with the **printer's own busybox 1.31.1 ash**, extracted from the stock `rootfs.squashfs` and run under `qemu-mipsel` — a parse error in `firmwareExe` means a blank screen | | `test-abi` | Every shipped MIPS binary is `nan2008`/`mips32r2`/`o32`, and executes under `qemu-mipsel` | diff --git a/bin/make-uninstall.sh b/bin/make-uninstall.sh deleted file mode 100644 index d91e8f4..0000000 --- a/bin/make-uninstall.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash -# Build a USB package that reverts the mod and returns the printer to stock. -# -# Design note, learned from test/sim-roundtrip.sh: the stock installer runs -# cd /usr/prog/PROGRAM/software && ls | grep -v temp | xargs rm -rf -# which deletes the top-level firmwareExe as well as the old version dirs. -# It only survives because the stock run.sh copies it back out of the payload. -# So an uninstall package that ships only a revert script leaves the printer -# with no UI binary at all -- a blank screen. -# -# Therefore the uninstall package is the PRISTINE stock software component -# (which restores the stock app_startup.sh, start.sh, passwd, shadow and -# firmwareExe by itself, simply by being installed) plus a small cleanup -# block that removes the mod payload. -set -euo pipefail -. "$(dirname "$0")/common.sh" - -[ -d work/outer ] || { echo "run bin/unpack.sh first" >&2; exit 1; } -SW_TARBALL=$(ls -1 work/outer/software-*.tar.xz 2>/dev/null | head -n1) -[ -n "$SW_TARBALL" ] || { echo "no stock software component in work/outer" >&2; exit 1; } - -rm -rf work/uninst work/uninst-sw -mkdir -p work/uninst work/uninst-sw work/out - -echo ">> extracting PRISTINE stock component (not the patched tree)" -tar -xf "$SW_TARBALL" -C work/uninst-sw -echo " $(find work/uninst-sw -type f | wc -l) files" - -echo ">> appending cleanup block" -cat > work/.uninst-block.sh <<'EOF' -# Remove the mod payload. The stock files (app_startup.sh, start.sh, passwd, -# shadow, firmwareExe) have already been restored by the stock run.sh above, -# simply because this package carries the pristine originals. -MODDIR=/usr/data/mod -case "$MODDIR" in - /usr/data/?*) ;; - *) exit 0 ;; -esac -{ - echo "=== mod uninstall `date 2>/dev/null` ===" - [ -f $MODDIR/nginx/logs/nginx.pid ] && kill `cat $MODDIR/nginx/logs/nginx.pid` 2>/dev/null - killall dropbear helix-screen helix-watchdog helix-splash 2>/dev/null - # Keep backup/ and the logs; drop everything we installed. - rm -rf $MODDIR/bin $MODDIR/www $MODDIR/nginx $MODDIR/helixscreen \ - $MODDIR/config $MODDIR/boot.sh $MODDIR/mod.conf $MODDIR/VERSION - echo "mod payload removed; /usr/data/config and $MODDIR/backup kept" - echo "=== uninstall done ===" -} >> /usr/data/mod-uninstall.log 2>&1 -sync -EOF -python3 - work/uninst-sw/run.sh work/.uninst-block.sh <<'PY' -import sys, re -run, extra = sys.argv[1], sys.argv[2] -B, E = "# >>> c5mod uninstall >>>", "# <<< c5mod uninstall <<<" -s = open(run, encoding='utf-8', errors='surrogateescape').read() -s = re.sub(re.escape(B) + r".*?" + re.escape(E) + r"\n?", "", s, flags=re.S) -block = open(extra, encoding='utf-8').read() -m = list(re.finditer(r"^exit 0\s*$", s, flags=re.M)) -i = m[-1].start() if m else len(s) -s = s[:i] + B + "\n" + block + E + "\n\n" + s[i:] -open(run, 'w', encoding='utf-8', errors='surrogateescape').write(s) -print(" cleanup block appended to the stock run.sh") -PY -rm -f work/.uninst-block.sh -chmod +x work/uninst-sw/run.sh - -echo ">> regenerating md5sum.list" -( cd work/uninst-sw && rm -f md5sum.list && \ - find . -type f ! -name md5sum.list -print0 | sort -z | xargs -0 md5sum > md5sum.list ) - -STOCK_SW_VER=$(cat work/.stock_sw_ver 2>/dev/null || echo 1.9.7) -# Plain tar -- the stock installer runs a bare `tar -xvf` (see bin/pack.sh). -tar -cf "work/uninst/software-${STOCK_SW_VER}.tar.xz" -C work/uninst-sw . -cp -f work/outer/runFirmwareExe.sh work/uninst/ -for f in start.img end.img play; do - [ -f "work/outer/$f" ] && cp -f "work/outer/$f" work/uninst/ -done - -tar -cf - -C work/uninst . | openssl des3 -salt -md md5 -k "$FF_KEY" \ - > work/out/Creator5Pro-uninstall.tgz 2>/dev/null -cp -f work/out/Creator5Pro-uninstall.tgz work/out/Creator5-uninstall.tgz - -echo -echo "Uninstall packages:" -ls -lh work/out/*uninstall*.tgz | awk '{print " "$9" "$5}' -echo -echo "Put these on a spare USB stick BEFORE you flash anything else." -echo "They reinstall the stock component and delete the mod payload." diff --git a/bin/unpack.sh b/bin/unpack.sh index bf86279..f1b6230 100644 --- a/bin/unpack.sh +++ b/bin/unpack.sh @@ -32,6 +32,16 @@ STOCK_SW_VER=$(basename "$SW_TARBALL" | sed 's/^software-//; s/\.tar\.xz$//') echo ">> extracting software component $STOCK_SW_VER" tar -xf "$SW_TARBALL" -C work/software +# Every package carries a model gate. runFirmwareExe.sh compares the MACHINE +# and PID it was built for against the ones app_startup.sh passes in (which +# come from the firmware already on the printer) and REFUSES to install on a +# mismatch. Record it so verify.sh can check it against your printer. +PKG_MACHINE=$(sed -n 's/^MACHINE=//p' work/outer/runFirmwareExe.sh | head -n1) +PKG_PID=$(sed -n 's/^PID=//p' work/outer/runFirmwareExe.sh | head -n1) +echo "${PKG_MACHINE:-unknown}" > work/.pkg_machine +echo "${PKG_PID:-unknown}" > work/.pkg_pid +echo ">> package installs on: ${PKG_MACHINE:-unknown} (PID ${PKG_PID:-unknown})" + echo "$STOCK_SW_VER" > work/.stock_sw_ver echo "$SRC" > work/.source_pkg diff --git a/bin/verify.sh b/bin/verify.sh index 7ec216f..fe5af28 100644 --- a/bin/verify.sh +++ b/bin/verify.sh @@ -114,6 +114,29 @@ else warn "no mod.tar.xz -- scripts only" fi +# 8b ------------------------------------------------------- MODEL GATE +# The most easily missed failure: a package built from the wrong model's +# firmware refuses to install, and a package built from the RIGHT model but +# flashed to the wrong one would install the wrong binaries. +PKG_MACHINE=$(sed -n 's/^MACHINE=//p' "$T/runFirmwareExe.sh" 2>/dev/null | head -n1) +PKG_PID=$(sed -n 's/^PID=//p' "$T/runFirmwareExe.sh" 2>/dev/null | head -n1) +if [ -n "$PKG_MACHINE" ]; then + echo " package installs on: $PKG_MACHINE (PID $PKG_PID)" + if [ -n "${TARGET_MACHINE:-}" ]; then + if [ "$PKG_MACHINE" = "$TARGET_MACHINE" ]; then + ok "model gate matches TARGET_MACHINE=$TARGET_MACHINE" + else + bad "MODEL MISMATCH: package is for '$PKG_MACHINE' but TARGET_MACHINE='$TARGET_MACHINE'" + bad " this package will be REFUSED by your printer (\"Firmware does not match machine type\")" + bad " you need a stock package built for $TARGET_MACHINE to start from" + fi + else + warn "TARGET_MACHINE not set in config.env -- cannot check this against your printer" + fi +else + warn "no MACHINE= gate found in runFirmwareExe.sh" +fi + # 9 --------------------------------------------------------------- USB names BN=$(basename "$PKG") case "$BN" in diff --git a/config.env.example b/config.env.example index 25de395..098dde1 100644 --- a/config.env.example +++ b/config.env.example @@ -11,6 +11,17 @@ MOD_VER=1.0.0 # your mod version # only useful if you want to see at a glance that a machine is modded. SW_VER="" +# ---- which printer is this for? -------------------------------------------- +# Creator5Pro / 0029 or Creator5 / 0028 +# Find it on the printer: Settings -> About, or in +# /usr/data/firmwareRes/config/general.json ("machineName"). +# +# This MUST match the model the stock package was built for. A package's +# runFirmwareExe.sh carries its own MACHINE=/PID= gate and refuses to install +# on the other model, so a Pro cannot be recovered or modded with a non-Pro +# package (and vice versa). +TARGET_MACHINE=Creator5Pro + # ---- inputs (sources you already have on this machine) ---------------------- DESKTOP="$HOME/Desktop" diff --git a/docs/hardware-testing.md b/docs/hardware-testing.md index be54f3e..a2d0012 100644 --- a/docs/hardware-testing.md +++ b/docs/hardware-testing.md @@ -10,10 +10,37 @@ recoverable.** --- +## Step zero: check the model gate + +Every package carries a gate. Its `runFirmwareExe.sh` has `MACHINE=` / `PID=` +baked in, and compares them against the values `app_startup.sh` passes from +the firmware already on the printer. A mismatch is **refused** with +"Firmware does not match machine type" — it will not install, and it is not a +valid recovery image either. + +| Model | MACHINE | PID | +|---|---|---| +| Creator 5 | `Creator5` | `0028` | +| Creator 5 Pro | `Creator5Pro` | `0029` | + +Find yours in Settings → About, or in +`/usr/data/firmwareRes/config/general.json` (`machineName`), and set +`TARGET_MACHINE` in `config.env`. Then: + +```sh +make full && make verify +``` + +`verify.sh` fails loudly on a mismatch. **You must start from a stock package +built for your own model** — the mod inherits the gate from whatever package +you unpack. + ## Before the first flash -- [ ] Build the uninstall stick and keep it physically separate: - `make uninstall-pkg` → put `Creator5*-uninstall.tgz` on a spare USB stick. +- [ ] Put a copy of the **stock FlashForge package for your model** on a spare + USB stick and keep it physically separate. That is your recovery image: + flashing it restores every file the mod touches (proven by + `make test-recovery`). Check it is the right model first — see above. - [ ] Note your printer's serial number (Settings → About). The factory image restores a placeholder serial and you may need to put yours back. - [ ] Confirm you can reach the printer's IP. @@ -155,8 +182,8 @@ If ssh is gone too, flash the uninstall stick. | Symptom | Do this | |---|---| | Printer boots, screen blank | ssh in; `touch /usr/data/mod/SAFE-MODE`; reboot | -| No ssh, no screen | flash the uninstall stick | -| Uninstall stick does not help | flash a stock FlashForge package | +| No ssh, no screen | flash the stock package for your model | +| Recovery stick does not help | try a newer stock FlashForge package for your model | | Still broken | factory package (`Creator5Pro-factory-*.tgz` **plus** the separate `Creator5Pro-factory.tar.xz` on the same stick; needs 800 MB free) | The mod never creates a mount point named like a mod, specifically so that diff --git a/test/run-tests.sh b/test/run-tests.sh index f32cfea..281a37c 100644 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -86,16 +86,13 @@ DIFF=$(diff -rq "$TMP/sw-before" work/software 2>&1 | grep -vE 'run\.sh|md5sum\. if [ -z "$DIFF" ]; then pass "probe touches only run.sh (adds the report step)" else fail "probe modified more than run.sh"; echo "$DIFF" | sed 's/^/ /'; fi -hdr "uninstall round trip" +hdr "recovery: a stock package reverts the mod" export PROFILE=full run ./bin/unpack.sh run ./bin/patch.sh run ./bin/pack.sh -MODPKG=$(ls -1 work/out/Creator5Pro-*.tgz | grep -v uninstall | head -n1) -run ./bin/make-uninstall.sh -UNI=work/out/Creator5Pro-uninstall.tgz -run ./bin/verify.sh "$UNI" -sub "install -> uninstall -> stock" ./test/sim-roundtrip.sh "$MODPKG" "$UNI" +MODPKG=$(ls -1 work/out/Creator5Pro-*.tgz | head -n1) +sub "install mod -> flash stock -> back to stock" ./test/sim-roundtrip.sh "$MODPKG" "$FIXTURE" hdr "UI selection and fallback" sub "ui fallback" ./test/sim-ui-fallback.sh diff --git a/test/sim-roundtrip.sh b/test/sim-roundtrip.sh index 95fffcc..b803c47 100644 --- a/test/sim-roundtrip.sh +++ b/test/sim-roundtrip.sh @@ -1,12 +1,20 @@ #!/usr/bin/env bash -# Install the mod, then install the uninstall package, and assert the printer -# is back to stock. This is what makes the "you can always get back" claim -# testable rather than aspirational. +# Recovery test: install the mod, then flash the STOCK FlashForge package, +# and assert the printer is genuinely back to stock. # -# ./test/sim-roundtrip.sh +# This is the real recovery path, so it is the one worth testing. A stock +# package carries the pristine app_startup.sh, start.sh, passwd, shadow and +# firmwareExe, and its run.sh copies all of them into place -- which undoes +# every file the mod touches. Nothing bespoke is required. +# +# The mod's payload under /usr/data/mod survives, deliberately: once +# firmwareExe is the genuine binary again nothing reads it, so it is inert. +# The assertions below check exactly that -- inert, not absent. +# +# ./test/sim-roundtrip.sh set -uo pipefail -MOD="${1:?usage: sim-roundtrip.sh }" -UNI="${2:?usage: sim-roundtrip.sh }" +MOD="${1:?usage: sim-roundtrip.sh }" +STOCK="${2:?usage: sim-roundtrip.sh }" KEY="${FF_KEY:-FFP0331&*%root}" IMAGE="${SIM_IMAGE:-debian:bookworm-slim}" @@ -16,9 +24,9 @@ command -v $DOCKER >/dev/null 2>&1 || { echo " SKIP: docker not available"; exi $DOCKER info >/dev/null 2>&1 || { echo " SKIP: docker daemon not running"; exit 0; } A="$(cd "$(dirname "$MOD")" && pwd)/$(basename "$MOD")" -B="$(cd "$(dirname "$UNI")" && pwd)/$(basename "$UNI")" +B="$(cd "$(dirname "$STOCK")" && pwd)/$(basename "$STOCK")" -$DOCKER run --rm -i -v "$A:/mod.tgz:ro" -v "$B:/uninstall.tgz:ro" -e "FF_KEY=$KEY" \ +$DOCKER run --rm -i -v "$A:/mod.tgz:ro" -v "$B:/stock.tgz:ro" -e "FF_KEY=$KEY" \ "$IMAGE" sh -s <<'INNER' set -u FAIL=0 @@ -29,36 +37,19 @@ apt-get update -qq >/dev/null 2>&1; apt-get install -y -qq openssl xz-utils >/de mkdir -p /usr/prog/bin /usr/prog/etc /usr/prog/modules /usr/prog/klipper/klippy \ /usr/prog/nginx/conf /usr/prog/PROGRAM/software /usr/data/config /usr/data/update /mnt -cat > /usr/prog/app_startup.sh <<'A' -#!/bin/sh -# STOCK app_startup marker -insmod /usr/prog/modules/gt9xx_touch.ko -/usr/prog/PROGRAM/software/firmwareExe & -A -cat > /usr/prog/klipper/start.sh <<'A' -#!/bin/sh -# STOCK start.sh marker -#/usr/prog/klipper/moonrakerDaemon start -A printf 'root:x:0:0:root:/root:/bin/sh\n' > /usr/prog/etc/passwd -printf 'root:$1$stockstock$aaaa:20603::::::\n' > /usr/prog/etc/shadow -printf 'STOCK-FIRMWAREEXE\n' > /usr/prog/PROGRAM/software/firmwareExe -printf '# stock klippy\n' > /usr/prog/klipper/klippy/klippy.py +printf 'root:$1$stock$aaaa:20603::::::\n' > /usr/prog/etc/shadow +printf 'PLACEHOLDER\n' > /usr/prog/PROGRAM/software/firmwareExe +chmod +x /usr/prog/PROGRAM/software/firmwareExe +printf 'USER-CONFIG-MUST-SURVIVE\n' > /usr/data/config/printer.cfg : > /usr/prog/nginx/conf/mime.types -chmod +x /usr/prog/app_startup.sh /usr/prog/klipper/start.sh mkdir -p /usr/local/bin for c in insmod reboot chrt start-stop-daemon killall pgrep cmd_mcu dropbear dropbearkey; do printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/$c; chmod +x /usr/local/bin/$c done -# The reference for "stock" is what the PACKAGE ships, not the file this test -# hand-wrote: the uninstall package reinstalls FlashForge's own component, so -# that is the correct end state. -mkdir -p /tmp/ref && cd /tmp/ref -openssl des3 -d -k "$FF_KEY" -salt -md md5 -in /uninstall.tgz 2>/dev/null | tar -xf - 2>/dev/null -mkdir -p /tmp/ref/sw && tar -xf /tmp/ref/software-*.tar.xz -C /tmp/ref/sw 2>/dev/null -cp /tmp/ref/sw/app_startup.sh /tmp/stock_app 2>/dev/null -cp /tmp/ref/sw/start.sh /tmp/stock_start 2>/dev/null -cd / +printf '#!/bin/sh\n[ "$1" = "-m" ] && { echo mips; exit 0; }\nexec /bin/uname "$@"\n' > /usr/local/bin/uname +chmod +x /usr/local/bin/uname +export PATH=/usr/local/bin:$PATH install_pkg() { rm -rf /usr/data/update; mkdir -p /usr/data/update @@ -67,44 +58,65 @@ install_pkg() { /usr/data/update/runFirmwareExe.sh Creator5Pro 0029 >>/tmp/inst.log 2>&1 } +# Reference copies of the genuine stock files, taken from the stock package. +mkdir -p /tmp/ref && cd /tmp/ref +openssl des3 -d -k "$FF_KEY" -salt -md md5 -in /stock.tgz 2>/dev/null | tar -xf - 2>/dev/null +mkdir -p /tmp/ref/sw && tar -xf /tmp/ref/software-*.tar.xz -C /tmp/ref/sw 2>/dev/null +cd / + install_pkg /mod.tgz && ok "mod installed" || bad "mod install failed" FE=/usr/prog/PROGRAM/software/firmwareExe head -c 2 "$FE" 2>/dev/null | grep -q '#!' \ && ok "firmwareExe is the mod wrapper after install" \ - || bad "firmwareExe was not replaced -- nothing to uninstall" + || bad "firmwareExe was not replaced -- nothing to recover from" +[ -d /usr/data/mod ] && ok "mod payload present after install" || bad "no mod payload" -install_pkg /uninstall.tgz && ok "uninstall package ran" || bad "uninstall package failed" +echo +echo " -- now flash the STOCK FlashForge package --" +install_pkg /stock.tgz && ok "stock package installed" || bad "stock package failed" echo echo " -- back to stock? --" -if diff -q /tmp/stock_app /usr/prog/app_startup.sh >/dev/null 2>&1; then - ok "app_startup.sh matches the stock file the package ships" -else - bad "app_startup.sh does not match the shipped stock file" - diff /tmp/stock_app /usr/prog/app_startup.sh | head -10 | sed 's/^/ /' -fi -grep -qE 'firmwareExe' /usr/prog/app_startup.sh \ - && ok "restored boot chain launches the stock UI" \ - || bad "BRICK: restored app_startup.sh launches no UI" if head -c 2 "$FE" 2>/dev/null | grep -q '#!'; then - bad "firmwareExe is still the mod wrapper -- uninstall did not restore the binary" + bad "firmwareExe is still the mod wrapper -- stock reflash did not restore it" else ok "firmwareExe is the genuine binary again" fi -diff -q /tmp/stock_start /usr/prog/klipper/start.sh >/dev/null 2>&1 \ - && ok "start.sh matches the shipped stock file" || bad "start.sh not restored" -grep -q '^/usr/prog/klipper/moonrakerDaemon start' /usr/prog/klipper/start.sh \ +if [ -f /tmp/ref/sw/firmwareExe ] && cmp -s /tmp/ref/sw/firmwareExe "$FE"; then + ok "firmwareExe matches the stock package byte-for-byte" +else + bad "firmwareExe does not match the stock binary" +fi +for f in app_startup.sh; do + if [ -f /tmp/ref/sw/$f ] && cmp -s /tmp/ref/sw/$f /usr/prog/$f; then + ok "$f restored byte-for-byte" + else + bad "$f not restored" + fi +done +if [ -f /tmp/ref/sw/start.sh ] && cmp -s /tmp/ref/sw/start.sh /usr/prog/klipper/start.sh; then + ok "klipper/start.sh restored byte-for-byte" +else + bad "klipper/start.sh not restored" +fi +grep -q '^/usr/prog/klipper/moonrakerDaemon start' /usr/prog/klipper/start.sh 2>/dev/null \ && bad "start.sh still has the mod's uncommented moonraker line" \ || ok "start.sh web-stack lines are commented out again (stock state)" -sh -n /usr/prog/app_startup.sh 2>/dev/null && ok "restored app_startup.sh is valid shell" \ - || bad "restored app_startup.sh is broken" -[ -s /usr/prog/PROGRAM/software/firmwareExe ] \ - && ok "firmwareExe restored (the installer wipes this dir -- see make-uninstall.sh)" \ - || bad "BRICK: firmwareExe lost, printer would boot to a blank screen" -[ -d /usr/data/mod/backup ] && ok "backups kept for forensics" || ok "no backup dir (nothing was installed)" -[ -e /usr/data/mod/boot.sh ] && bad "mod boot.sh still present" || ok "mod payload removed" + +# The payload is intentionally left behind. Prove it cannot do anything. +if [ -d /usr/data/mod ]; then + ok "mod payload left on the data partition (expected)" + grep -rq 'c5mod\|/usr/data/mod' /usr/prog/app_startup.sh /usr/prog/klipper/start.sh 2>/dev/null \ + && bad "a stock boot script still references the mod -- payload is NOT inert" \ + || ok "no stock boot script references it -- payload is inert" +fi + +grep -q 'USER-CONFIG-MUST-SURVIVE' /usr/data/config/printer.cfg 2>/dev/null \ + && ok "user printer.cfg preserved through both flashes" \ + || bad "user printer.cfg was clobbered" echo -[ "$FAIL" = 0 ] && echo " round-trip clean: mod installs and fully reverts" || echo " ROUND-TRIP FAILED" +[ "$FAIL" = 0 ] && echo " recovery clean: a stock package fully reverts the mod" \ + || echo " RECOVERY TEST FAILED" exit $FAIL INNER