Compare commits

...
4 Commits
Author SHA1 Message Date
CyberMind 7d971bbb88 Merge pull request #438 from CyberMind-FR/fix/436-build-scripts-stub-systemctl-handle-rasp
build scripts: stub systemctl + handle raspi-firmware /boot/firmware in qemu chroot
2026-06-01 07:29:06 +02:00
gandalf ef9e9dc133 fix(rpi-usb): bump tmpfs /boot/firmware 64M→512M + bind /proc,/sys (ref #436)
Follow-up to PR #437. v2.13.6 build hit two new failures inside the
safety net :

1. **ENOSPC on tmpfs /boot/firmware** — 64M was too small. The
   raspi-firmware post-update.d hook copies kernel (~25M) + initrd
   (~30M arm64) + dtbs + overlays. Bumped to 512M to cover all
   firmware artefacts comfortably.

2. **/proc + /sys not bind-mounted into the chroot** — raspi-firmware's
   hook uses `findmnt /boot/firmware` which reads /proc/mounts. Without
   /proc mounted in the chroot it errored
   `findmnt: can't read /proc/mounts`. Now bound at safety-net install
   (matches the pattern build-live-usb.sh uses at lines 277-278) and
   unmounted at teardown.

Both errors surface during the apt --fix-broken install OR the apt-get
install kiosk stack, where postinsts trigger update-initramfs which
runs the raspi-firmware hook. The systemctl wrapper + policy-rc.d
parts of the safety net (PR #437) were already working — these two
extras finish the job.

Expected v2.13.7: kiosk install completes cleanly through to the
build-time assertion, image rsyncs successfully.

Refs: #423#433#436 (this and PR #437 close together).
2026-06-01 07:28:50 +02:00
CyberMind a96ce3bf5b Merge pull request #437 from CyberMind-FR/fix/436-build-scripts-stub-systemctl-handle-rasp
build scripts: stub systemctl + handle raspi-firmware /boot/firmware in qemu chroot
2026-05-31 10:51:48 +02:00
gandalf 61f773c5e4 fix(rpi-usb): chroot safety net for qemu-arm64 postinsts (closes #436)
v2.13.5 rpi400 build aborted on apt --fix-broken install. Root cause:
in a qemu-arm64 chroot, /run/systemd/system is bind-mounted from the
host so systemctl thinks systemd is running, but it can't reach dbus.
Every postinst calling `systemctl enable/start` fails with
"Failed to connect to bus: Host is down", leaving secubox-core (and
the cascade of secubox-* depending on it) installed-but-not-configured.

Three-part safety net installed right after debootstrap --second-stage
(Step 1.5) and torn down before Step 7's image creation (Step 6.5):

1. **systemctl wrapper** — dpkg-divert /bin/systemctl to
   /bin/systemctl.distrib, install a thin shim that exports
   SYSTEMD_OFFLINE=1 before exec'ing the real binary. Postinsts
   that call `systemctl enable X.service` now succeed (filesystem-only
   operation) instead of failing on dbus.

2. **policy-rc.d** — /usr/sbin/policy-rc.d returns 101 to block
   invoke-rc.d from starting daemons during apt operations.

3. **tmpfs at /boot/firmware** — raspi-firmware's postinst checks
   `mountpoint -q /boot/firmware`. The real BOOT partition is only
   loop-mounted in Step 7, so we bind a 64M tmpfs at that path during
   build; raspi-firmware writes its files there (discarded on umount,
   regenerated by Step 7's actual firmware copy). Without this,
   apt --fix-broken install fails on raspi-firmware before reaching
   the kiosk packages.

Also softens the apt --fix-broken to best-effort (warn instead of
err) — the kiosk install below is now the gate, with `-f` to satisfy
deps. The pre-rsync assertion from #433 stays as the safety net.

Tested locally on v2.13.5 sources : apt --fix-broken now succeeds
(secubox-core configures, cascades complete), kiosk packages install
cleanly, all artefacts present in ROOTFS before rsync.

Expected v2.13.6 outcome: rpi400 image actually ships with kiosk
working out of the box, AND the dpkg state is clean so apt operations
on the running device don't hit broken-deps surprises.

Refs: #423 (kiosk install original) → #433 (silent fail, fail-loud) →
#436 (this, root cause fix).
2026-05-31 09:56:49 +02:00
+90 -10
View File
@@ -152,6 +152,62 @@ chroot "${ROOTFS}" /debootstrap/debootstrap --second-stage
ok "Debootstrap complete"
# ══════════════════════════════════════════════════════════════════
# Step 1.5: Chroot safety net for qemu-arm64 (closes #436)
# ══════════════════════════════════════════════════════════════════
# In a qemu-arm64 chroot, /run/systemd/system is bind-mounted from host
# (so systemctl thinks systemd is running), but dbus can't be reached.
# Result: every postinst that calls `systemctl enable/start` fails with
# "Failed to connect to bus: Host is down", leaving packages
# installed-but-not-configured (including secubox-core, cascading to
# every secubox-* that depends on it).
#
# Three-part safety net :
# (1) divert /bin/systemctl → /bin/systemctl.distrib, install a wrapper
# that exports SYSTEMD_OFFLINE=1 before exec'ing the real binary;
# (2) install policy-rc.d returning 101 (blocks invoke-rc.d service
# starts during apt operations);
# (3) tmpfs-bind /boot/firmware so raspi-firmware's postinst
# (mountpoint -q /boot/firmware) doesn't fail before Step 7
# loop-mounts the real BOOT partition.
#
# Torn down right before Step 7 so the image ships clean.
log "Installing chroot safety net (systemctl wrapper, policy-rc.d, /boot/firmware tmpfs)"
# (1) systemctl wrapper
chroot "${ROOTFS}" dpkg-divert --add --rename --quiet /bin/systemctl
cat > "${ROOTFS}/bin/systemctl" <<'SYSTEMCTL_WRAPPER'
#!/bin/sh
# Build-time wrapper (#436): force offline mode so postinsts in a qemu
# chroot can enable/disable units via filesystem only, no dbus needed.
export SYSTEMD_OFFLINE=1
exec /bin/systemctl.distrib "$@"
SYSTEMCTL_WRAPPER
chmod 0755 "${ROOTFS}/bin/systemctl"
# (2) policy-rc.d
cat > "${ROOTFS}/usr/sbin/policy-rc.d" <<'POLICY_RC_D'
#!/bin/sh
# Build-time policy (#436): never start daemons during chroot apt ops.
exit 101
POLICY_RC_D
chmod 0755 "${ROOTFS}/usr/sbin/policy-rc.d"
# (3) tmpfs at /boot/firmware (raspi-firmware postinst needs mountpoint).
# 512M: must fit kernel + initrd (~50-100M arm64) + dtbs + overlays + bootloader.
# 64M was too small — v2.13.6 hit ENOSPC during initramfs-tools postinst.
mkdir -p "${ROOTFS}/boot/firmware"
mount -t tmpfs -o size=512M,mode=0755 tmpfs "${ROOTFS}/boot/firmware"
# (4) /proc + /sys inside the chroot — raspi-firmware's update-initramfs hook
# uses findmnt which reads /proc/mounts. Without these, postinst fails
# silently with "findmnt: can't read /proc/mounts" before chmod/chown errors
# show. Matches build-live-usb.sh's pattern (lines 277-278).
mountpoint -q "${ROOTFS}/proc" || mount -t proc proc "${ROOTFS}/proc"
mountpoint -q "${ROOTFS}/sys" || mount -t sysfs sysfs "${ROOTFS}/sys"
ok "Chroot safety net installed"
# ══════════════════════════════════════════════════════════════════
# Step 2: Base configuration
# ══════════════════════════════════════════════════════════════════
@@ -788,23 +844,22 @@ ok "SecuBox packages installed"
if [[ "${INCLUDE_KIOSK}" -eq 1 ]]; then
log "Installing kiosk mode (chromium fullscreen → http://127.0.0.1/) ..."
# The secubox-* .debs installed via dpkg -i in Step 5 (no dep resolution)
# leave the rootfs in a broken-deps state. apt refuses any new install
# while that state exists, so --fix-broken must run FIRST. This pulls in
# lxc / debootstrap / python3-cryptography / netdata / certbot / ...
# (~500 MB extra). Required for #433 — without it the chromium install
# below silently fails with "Some packages were not installed".
log " apt --fix-broken install (clear pre-existing broken-deps state)"
# Best-effort fix-broken (postinsts may still fail individually in qemu
# chroot even with the #436 safety net, but we want the kiosk install
# to proceed regardless). The fail-loud is on the kiosk install below,
# not on fix-broken.
log " apt --fix-broken install (best-effort, pre-existing state cleanup)"
chroot "${ROOTFS}" /bin/bash -c \
'DEBIAN_FRONTEND=noninteractive apt-get --fix-broken install -y -q' \
|| err "apt --fix-broken install failed inside chroot — kiosk install aborted"
|| warn "apt --fix-broken install reported errors (continuing — kiosk install will report its own)"
# Fail-loud on apt errors (was silently masked with || warn, #433 root cause).
# If chromium/X can't install, abort the whole image build — silently
# shipping a no-kiosk image is worse than no image.
# shipping a no-kiosk image is worse than no image. The `-f` flag tells
# apt to satisfy deps even with some pre-existing broken state.
log " apt-get install kiosk stack"
chroot "${ROOTFS}" /bin/bash -c \
'DEBIAN_FRONTEND=noninteractive apt-get install -y -q --no-install-recommends \
'DEBIAN_FRONTEND=noninteractive apt-get install -y -q -f --no-install-recommends \
xserver-xorg xinit openbox chromium x11-xserver-utils \
ca-certificates dbus-x11' \
|| err "apt-get install failed inside chroot — kiosk packages required when --kiosk is passed"
@@ -1127,6 +1182,31 @@ fi
ok "Pi bootloader configured"
# ══════════════════════════════════════════════════════════════════
# Step 6.5: Tear down chroot safety net (closes #436)
# ══════════════════════════════════════════════════════════════════
# Undo Step 1.5's scaffolding so the .img doesn't ship with build-time
# wrappers. Order matters : umount tmpfs BEFORE the rsync to /boot/firmware
# in Step 7, restore systemctl AFTER the last `chroot systemctl` call
# (which was Step 5.4's enable secubox-kiosk.service, already done by here).
log "Removing chroot safety net (systemctl wrapper, policy-rc.d, /boot/firmware tmpfs)"
# (4) umount /proc + /sys (chroot no longer needs them)
umount -lf "${ROOTFS}/proc" 2>/dev/null || true
umount -lf "${ROOTFS}/sys" 2>/dev/null || true
# (3) umount tmpfs /boot/firmware (Step 7 mounts the real BOOT partition there)
umount "${ROOTFS}/boot/firmware" 2>/dev/null || warn "tmpfs /boot/firmware was not mounted"
# (2) remove policy-rc.d (first boot must be able to start daemons)
rm -f "${ROOTFS}/usr/sbin/policy-rc.d"
# (1) restore real systemctl
rm -f "${ROOTFS}/bin/systemctl"
chroot "${ROOTFS}" dpkg-divert --remove --rename --quiet /bin/systemctl
ok "Chroot safety net removed"
# ══════════════════════════════════════════════════════════════════
# Step 7: Create image
# ══════════════════════════════════════════════════════════════════