mirror of
https://github.com/Klipper4FlashForge/firmware.git
synced 2026-10-22 05:12:16 -05:00
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:
1 parent
2d92c681ec
commit
8da5c911ca
9 files changed
+173
-168
No files matched your search
@@ -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
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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."
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
Reference in new issue
Block a user