First Addon migrated: menus/addon_cups.sh (install, reconfigure for
network access, complete uninstall/purge). Different risk profile from
everything migrated so far - it genuinely mutates real system state
(apt install/remove --purge, /etc/cups, ufw) at fixed paths CUPS itself
doesn't let us relocate, unlike the systemd/cron/bin paths this project
already controls via $SYSTEMD_DIR etc. Only the polkit rule's directory
is parameterized ($POLKIT_DIR, lib/config.sh, since that one is ours to
place); everything else gets full command-level `sudo` stubbing in
every test - there is no scratch equivalent for a real apt-managed
subsystem's own file layout. Also added $BUILD_USER (the admin account
actually running the tool, as opposed to $KIOSK_USER) since CUPS needs
to grant it lpadmin group membership.
Restructured install.sh's top-level menu into Core Settings / Addons /
Advanced (matching the legacy tool) instead of one flat list, now that
Addons exists as its own category - cheap to do with one item in it,
much more annoying to retrofit once the flat list has fifteen.
Two bugs caught and fixed before they shipped:
- A "wait for CUPS to start" retry loop used a bare `cmd1 && cmd2 &&
break` as its body while "simplifying" the legacy script's `if cmd1
&& cmd2; then break; fi`. Being inside a loop doesn't protect a bare
&&/|| list from set -e - only if/while/until conditions and the
protected side of &&/|| do that - so the first command failing on an
early iteration (near-certain right after a fresh install, before
CUPS has actually started) would have crashed the entire session.
Restored the `if` form; noted the lesson in the file's own header
comment since it's a general trap, not CUPS-specific.
- Resolved real uncertainty, rather than assuming: how far does
run_menu's `handler || true` guard (v2.1.0) actually protect? Wrote a
minimal isolated test (a bare `false` three function calls deep,
called via `outer || true` at the top) and confirmed bash's errexit
exemption for the left side of `||` covers the *entire* evaluation,
arbitrarily deep through function calls - not just the immediately
invoked function. So the session-crash risk this project has been
chasing since v2.1.0 is already covered end-to-end by that one fix.
Per-statement guards (`|| true`, explicit `if`) still earn their keep
for a different reason: without them a deep failure bubbles silently
past the menu actually responsible for it to wherever the nearest
`|| true` happens to sit, which can be several menu levels above
where the user actually was - not a crash, but a confusing jump.
Verified:
- Full regression: re-ran every existing scratch-config/stub test suite
after the lib/config.sh change (new $BUILD_USER/$POLKIT_DIR) and
after the install.sh restructuring - all still clean.
- New scratch/stub test for addon_cups.sh: full state-machine coverage
(not installed -> decline -> install -> running -> reconfigure ->
stopped -> start -> uninstall decline -> uninstall confirm -> not
installed again) with every `sudo` call intercepted and only `rm`
targeting the scratch $POLKIT_DIR ever actually executed; confirmed
the polkit rule's content and that declining install makes zero sudo
calls. Added both apt-failure paths (update fails, install fails)
and confirmed the tool reports clearly and returns to the menu
instead of dying, exercising the exact bug class just fixed.
- End-to-end: ran the real install.sh as a genuine non-root, non-
"kiosk" user through the full new three-level structure - Core
Settings -> Sites -> back -> back, Addons -> CUPS -> declined install
(using this container's real, unstubbed dpkg check, correctly
reporting "not installed" and making no apt/systemctl calls) -> back
-> back, Advanced -> Diagnostics -> System status -> back -> back ->
Exit. Zero invalid-choice errors, clean exit code 0 throughout.