Files
felhom.eu/scripts/iso/pkg/build-deb.sh
T
admin 01a8155c5a iso v1.26.0: the PUBLIC release image — no answer file, interactive install, day-0 by .deb
Design inputs: SPIKE-universal-iso-{1,2,3,4}-2026-07-31.md. Every choice below is a measurement.

NEW: scripts/iso/pkg/ — the felhom-bootstrap .deb, built from committed source.
  Two files only (script + unit), NOT three: felhom-bootstrap.sh:91 reads /etc/felhom/bootstrap.env
  only 'if [[ -r ]]', and its defaults at :95-96 are EXACTLY what the pairing env set
  (build-felhom-iso.sh:257-258) — so shipping it would add a 0600 file to a public package to express
  values the script already defaults to. NO dependencies: the binaries it calls run at FIRST BOOT,
  not at postinst time, so SPIKE 4's open 'dpkg --configure -a' ordering question does not arise.
  The postinst is structurally incapable of failing (no 'set -e', every statement guarded, ends
  'exit 0'); build-deb.sh self-asserts G8/G9 and REFUSES to emit a package that violates them.

iso-repack.sh — two changes, both narrowing rather than deleting:
  - R-155 guard: now applies to FELHOM_MENU=single ONLY. It protected the single-entry mode's promise
    (one button labelled 'install' must not drop into a disk-picker); a release image carries no
    auto-installer-mode.toml BY DESIGN (gate G1), so refusing it would be the guard firing on the
    shape it describes rather than the one it prevents.
  - the menu collapse now has a release mode: two INTERACTIVE entries, Graphical default, timeout 15.
    Entry-count and banned-token gates are per-mode; the six-token list is UNCHANGED for single mode.
  - .deb injection into /proxmox/packages/, with a skip-list collision check (a colliding name would
    be dropped silently — the inert-payload class) and a post-remaster assertion that it landed in
    final.iso, not merely in the extract tree.

build-felhom-iso.sh — --release: no profile, no root hash, no answer.toml, no prepare-iso at all.
  Skipping prepare-iso is what removes the Automated entry by construction, since the stock grub.cfg
  emits it only inside 'if [ -f auto-installer-mode.toml ]'.

R-128 RULING — FIXED, by correcting the claim rather than inventing an assertion for it. The comment
  said ISO_VERSION 'aligns with SCRIPT_VERSION'; nothing evaluated it and the two had drifted. The
  coupling does not exist: the ISO is frozen, felhom-host-install.sh is fetched at run time from main
  (R-94/R-110), so an assertion would invent a constraint. Comment corrected, ISO_VERSION -> 1.26.0.

Release gate G6 AMENDED before the build, with its reasoning recorded in the runbook: the six-token
  ban existed to keep users away from the manual installer, which the ruling makes the product.
  'proxtui' (the TUI installer we ship) and 'nomodeset' (its graphics fallback) are dropped for
  release images; proxdebug/Rescue Boot/memtest/fwsetup stay banned in both modes.
2026-07-31 16:39:47 +02:00

77 lines
4.3 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
# 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"
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
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"