Files
felhom.eu/scripts/iso/pkg/build-deb.sh
T
admin a967da7d2c iso 1.26.1: ship /etc/felhom/ — the directory the bootstrap writes its state into
FIX for the Part-5 failure. felhom-bootstrap.sh writes the appliance token (:431), the pairing code
(:435) and .bootstrap-done into /etc/felhom/. The old stub-first-boot.sh created it explicitly
('install -d -m 0755 /etc/felhom /usr/local/sbin'); packaging dropped the env FILE correctly and the
DIRECTORY with it. Measured consequence on a real interactive install: the box registered at the hub,
could not persist its token, and polled 'HTTP 401 — still retrying' forever with no claim code.

- build-deb.sh now ships ./etc/felhom/ (0755, empty) and ASSERTS it, plus ./usr/local/sbin/ and
  ./lib/systemd/system/, as G13. RED-PROOFED: removing the install -d makes the build exit 3 with
  'is not in the package (G13)', and restoring it goes green.
- The gate gains G13 with the reasoning: G7/G8/G9 all passed on the broken package. G9 proves the
  payload is the right payload and says NOTHING about what the payload depends on.

ISO_VERSION -> 1.26.1.
2026-07-31 19:02:57 +02:00

93 lines
5.5 KiB
Bash
Executable File

#!/bin/bash
#===============================================================================
# build-deb.sh — build the felhom-bootstrap .deb that the PUBLIC ISO carries.
#
# WHY A PACKAGE AND NOT THE ANSWER FILE'S [first-boot] HOOK:
# SPIKE-universal-iso-3 measured, with a same-image control, that an INTERACTIVE install never places
# the first-boot hook on the system at all — the proxmox-first-boot PACKAGE is not even installed
# (Config.pm:118 defaults first_boot.enabled=0, Install.pm:746 returns early, :1360 skips the package,
# and proxinstall contains zero occurrences of "first-boot"). SPIKE-universal-iso-4 then measured that
# a .deb in the ISO's /proxmox/packages/ IS delivered on that same interactive path — installed,
# postinst run, unit enabled, unit fired at 7.98 s uptime — because Install.pm:1343-1372 unpacks every
# .deb on the medium and :1378 configures them.
#
# CONTENTS — exactly two files, and deliberately not three:
# /usr/local/sbin/felhom-bootstrap.sh 0755 (byte-identical to scripts/iso/felhom-bootstrap.sh)
# /lib/systemd/system/felhom-bootstrap.service 0644
# /etc/felhom/ 0755 (empty — the bootstrap's runtime state dir)
# The old stub also wrote /etc/felhom/bootstrap.env (0600). This package does NOT, because
# felhom-bootstrap.sh:91 reads it only `if [[ -r ... ]]` and its defaults at :95-96 are EXACTLY what
# the generic pairing env set (build-felhom-iso.sh:257-258). Shipping it would add a 0600 file to a
# public package to express values the script already defaults to.
#
# DEPENDENCIES: none, and that is a finding rather than an omission. The payload is a shell script and
# a unit file. The binaries the script calls (curl, ip, dhclient, python3, systemctl) run at FIRST
# BOOT, not at postinst time, and are all in a PVE base install — so there is nothing for
# `dpkg --configure -a` to order against, and SPIKE 4's open ordering question does not arise.
#===============================================================================
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ISO_DIR="$(cd "$HERE/.." && pwd)"
VERSION="${1:?usage: build-deb.sh <version> <outdir>}"
OUTDIR="${2:?usage: build-deb.sh <version> <outdir>}"
WORK="$(mktemp -d "${TMPDIR:-/tmp}/felhom-deb.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT
ROOT="$WORK/pkg"
install -d -m 0755 "$ROOT/DEBIAN" "$ROOT/usr/local/sbin" "$ROOT/lib/systemd/system"
# /etc/felhom is the bootstrap's RUNTIME STATE directory, and shipping it is not optional:
# felhom-bootstrap.sh writes the appliance token there (:431), the pairing code (:435) and
# .bootstrap-done (the unit's ConditionPathExists). The old stub created it explicitly
# (stub-first-boot.sh: `install -d -m 0755 /etc/felhom /usr/local/sbin`). Dropping the env FILE from
# this package was correct; dropping the DIRECTORY with it was not — a box installed without it
# registers at the hub, fails to persist its token, and then 401s forever without ever showing a
# claim code. Measured on a real interactive install, 2026-07-31.
install -d -m 0755 "$ROOT/etc/felhom"
sed "s/^Package: /Version: $VERSION\nPackage: /" /dev/null >/dev/null 2>&1 || true
{ head -1 "$HERE/debian/control"; echo "Version: $VERSION"; tail -n +2 "$HERE/debian/control"; } \
> "$ROOT/DEBIAN/control"
install -m 0755 "$HERE/debian/postinst" "$ROOT/DEBIAN/postinst"
# The two payload files, copied VERBATIM from the same sources the old stub embedded, so the ISO's
# frozen script is provably the repo's (release gate G9).
install -m 0755 "$ISO_DIR/felhom-bootstrap.sh" "$ROOT/usr/local/sbin/felhom-bootstrap.sh"
install -m 0644 "$ISO_DIR/felhom-bootstrap.service" "$ROOT/lib/systemd/system/felhom-bootstrap.service"
mkdir -p "$OUTDIR"
DEB="$OUTDIR/felhom-bootstrap_${VERSION}_all.deb"
dpkg-deb --build --root-owner-group "$ROOT" "$DEB" >/dev/null
# Self-assertions: the package must satisfy the release gate's G8/G9 before it ever reaches an ISO.
# Strip comments first — the postinst's header NAMES the forbidden verbs in order to explain why they
# are banned, and naming them there must not trip the gate. Same reasoning, and same fix, as
# iso-repack.sh:157-159 applies to the GRUB banned-token gate.
POST_RAW="$(dpkg-deb --ctrl-tarfile "$DEB" | tar -xO ./postinst)"
POST_LIVE="$(grep -v '^[[:space:]]*#' <<<"$POST_RAW")"
if grep -qE 'systemctl (start|daemon-reload|restart)' <<<"$POST_LIVE"; then
echo "build-deb: postinst has a LIVE forbidden systemctl verb (G8)" >&2; exit 3
fi
if grep -qE '^[[:space:]]*set -e' <<<"$POST_LIVE"; then
echo "build-deb: postinst uses 'set -e' (G8)" >&2; exit 3
fi
if grep -qE '\b(curl|wget|apt-get|nc|ping)\b' <<<"$POST_LIVE"; then
echo "build-deb: postinst uses the network (G8)" >&2; exit 3
fi
if [[ "$(tail -1 <<<"$POST_RAW")" != "exit 0" ]]; then
echo "build-deb: postinst does not end 'exit 0' (G8)" >&2; exit 3
fi
# G13 — every directory the payload writes into must be IN the package. G9 proves the script is the
# right script; it says nothing about the directories that script needs.
for d in ./etc/felhom/ ./usr/local/sbin/ ./lib/systemd/system/; do
if ! dpkg-deb -c "$DEB" | awk '{print $6}' | grep -qx "$d"; then
echo "build-deb: $d is not in the package (G13) — the payload would fail at run time" >&2; exit 3
fi
done
A="$(dpkg-deb --fsys-tarfile "$DEB" | tar -xO ./usr/local/sbin/felhom-bootstrap.sh | sha256sum | cut -d' ' -f1)"
B="$(sha256sum "$ISO_DIR/felhom-bootstrap.sh" | cut -d' ' -f1)"
if [[ "$A" != "$B" ]]; then
echo "build-deb: packaged felhom-bootstrap.sh != repo HEAD (G9)" >&2; exit 3
fi
echo "$DEB"