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.
This commit is contained in:
Oleksandr Shyshatskyi committed 2026-08-22 22:11:53 +03:00
1 parent 2d92c681ec
commit 8da5c911ca
9 files changed
+173 -168

No files matched your search

+19 -13
View File
@@ -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
+9 -2
View File
@@ -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` |
-88
View File
@@ -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."
+10
View File
@@ -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
+23
View File
@@ -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
+11
View File
@@ -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"
+31 -4
View File
@@ -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
+3 -6
View File
@@ -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
+67 -55
View File
@@ -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 <mod.tgz> <uninstall.tgz>
# 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 <mod.tgz> <stock.tgz>
set -uo pipefail
MOD="${1:?usage: sim-roundtrip.sh <mod.tgz> <uninstall.tgz>}"
UNI="${2:?usage: sim-roundtrip.sh <mod.tgz> <uninstall.tgz>}"
MOD="${1:?usage: sim-roundtrip.sh <mod.tgz> <stock.tgz>}"
STOCK="${2:?usage: sim-roundtrip.sh <mod.tgz> <stock.tgz>}"
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