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.
This commit is contained in:
@@ -41,7 +41,15 @@
|
||||
#===============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
ISO_VERSION="1.25.0" # Felhom release the ISO is tagged to (aligns with felhom-host-install SCRIPT_VERSION).
|
||||
# R-128 RULING (2026-07-31): the previous comment here claimed this "aligns with felhom-host-install
|
||||
# SCRIPT_VERSION". That was a COMMENT, not an assertion — `SCRIPT_VERSION` appeared nowhere in
|
||||
# scripts/iso/ except inside that sentence, so nothing ever evaluated it, and the two had drifted
|
||||
# (1.25.0 vs 1.22.0). The fix is NOT to turn it into a real assertion, because the coupling it claimed
|
||||
# does not exist: the ISO is a frozen artifact, while felhom-host-install.sh is fetched at RUN TIME
|
||||
# from the website's git-sync of `main` (R-94/R-110), so whatever version an ISO carries, the script a
|
||||
# box runs is always current. Coupling them would invent a constraint. The claim is corrected instead.
|
||||
ISO_VERSION="1.26.0" # the ISO's own version. INDEPENDENT of felhom-host-install.sh's SCRIPT_VERSION,
|
||||
# which is fetched at run time from main and is not frozen into the image.
|
||||
IMAGE="${FELHOM_ISO_ASSISTANT_IMAGE:-felhom-iso-assistant:trixie}"
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
@@ -82,12 +90,15 @@ assert_golden_ge_floor() {
|
||||
PVE_ISO=""; ISO_SHA256=""; PROFILE=""; BOOTSTRAP_ENV=""; OUT_DIR="${FELHOM_ISO_OUT:-/mnt/5_hdd/felhom.eu/felhom-iso/out}"; PVE_VERSION=""; DRY_RUN=false
|
||||
LOADER_CLI="" # --loader override; empty = fall back to the profile, then the shim default.
|
||||
PAIRING=false # --pairing: build the GENERIC secret-free ISO (slice C); no --bootstrap-env.
|
||||
RELEASE=false # --release: the PUBLIC image. No answer.toml at all, an INTERACTIVE two-entry menu,
|
||||
# and day-0 delivered by a .deb rather than the answer file's [first-boot] hook.
|
||||
BRAND=true # R-38: Felhom boot screen + single-entry menu. --no-brand is a debugging escape only.
|
||||
BRAND_IMAGE="" # --brand-image override; empty = the website's og-image_2.png (the single source).
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage (direct): build-felhom-iso.sh --pve-iso PATH --iso-sha256 SHA --profile FILE --bootstrap-env FILE [options]
|
||||
Usage (generic): build-felhom-iso.sh --pve-iso PATH --iso-sha256 SHA --profile FILE --pairing [options]
|
||||
Usage (release): build-felhom-iso.sh --pve-iso PATH --iso-sha256 SHA --release [options]
|
||||
|
||||
Required:
|
||||
--pve-iso PATH pre-downloaded official PVE ISO (not fetched here)
|
||||
@@ -123,6 +134,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--profile) PROFILE="$2"; shift 2 ;;
|
||||
--bootstrap-env) BOOTSTRAP_ENV="$2"; shift 2 ;;
|
||||
--pairing) PAIRING=true; shift ;;
|
||||
--release) RELEASE=true; shift ;;
|
||||
--loader) LOADER_CLI="$2"; shift 2 ;;
|
||||
--no-brand) BRAND=false; shift ;;
|
||||
--brand-image) BRAND_IMAGE="$2"; shift 2 ;;
|
||||
@@ -138,9 +150,18 @@ done
|
||||
|
||||
[[ -n "$PVE_ISO" ]] || die "--pve-iso is required"
|
||||
[[ -n "$ISO_SHA256" ]] || die "--iso-sha256 is required"
|
||||
[[ -n "$PROFILE" ]] || die "--profile is required"
|
||||
[[ -f "$PVE_ISO" ]] || die "--pve-iso not found: $PVE_ISO"
|
||||
[[ -f "$PROFILE" ]] || die "--profile not found: $PROFILE"
|
||||
if $RELEASE; then
|
||||
# A release image has no answer file, so it has no fqdn and no [disk-setup] to take from a profile.
|
||||
# Accepting one would imply a disk selection the image deliberately does not make.
|
||||
[[ -z "$PROFILE" ]] || die "--release takes no --profile (a release image bakes no disk selection)"
|
||||
[[ -z "$BOOTSTRAP_ENV" ]] || die "--release and --bootstrap-env are mutually exclusive"
|
||||
$PAIRING && die "--release and --pairing are mutually exclusive"
|
||||
$BRAND || die "--release requires branding (the two-entry menu IS the release menu)"
|
||||
else
|
||||
[[ -n "$PROFILE" ]] || die "--profile is required"
|
||||
[[ -f "$PROFILE" ]] || die "--profile not found: $PROFILE"
|
||||
fi
|
||||
# Mode: exactly one of --bootstrap-env (direct, secret-bearing) or --pairing (generic, secret-free).
|
||||
if $PAIRING; then
|
||||
[[ -z "$BOOTSTRAP_ENV" ]] || die "--pairing and --bootstrap-env are mutually exclusive"
|
||||
@@ -165,6 +186,7 @@ fi
|
||||
PROFILE_NAME="$(basename "$PROFILE")"; PROFILE_NAME="${PROFILE_NAME%.profile}"
|
||||
|
||||
# --- load + validate profile ----------------------------------------------------------------------
|
||||
if ! $RELEASE; then
|
||||
log_step "loading profile: $PROFILE"
|
||||
FELHOM_FQDN=""; FELHOM_DISK_SETUP=""; FELHOM_ROOT_SSH_KEY=""; FELHOM_LOADER=""; FELHOM_HUB_URL=""; FELHOM_INSTALL_URL=""
|
||||
# shellcheck disable=SC1090
|
||||
@@ -174,6 +196,10 @@ source "$PROFILE"
|
||||
# Optional emergency/validation SSH key baked into the installed root account.
|
||||
ROOT_SSH_LINE=""
|
||||
[[ -n "$FELHOM_ROOT_SSH_KEY" ]] && ROOT_SSH_LINE="root-ssh-keys = [\"${FELHOM_ROOT_SSH_KEY}\"]"
|
||||
else
|
||||
log_info "release mode — no profile, no fqdn, no [disk-setup], no root key"
|
||||
FELHOM_FQDN="(none — interactive install)"
|
||||
fi
|
||||
|
||||
# --- resolve the loader mode: --loader wins over the profile's FELHOM_LOADER over the shim default ---
|
||||
LOADER="${LOADER_CLI:-${FELHOM_LOADER:-shim}}"
|
||||
@@ -205,7 +231,14 @@ else
|
||||
fi
|
||||
|
||||
# --- mode: DIRECT validates the secret-bearing env; PAIRING is secret-free (env generated below) -----
|
||||
if $PAIRING; then
|
||||
if $RELEASE; then
|
||||
SECRET_BEARING="no"
|
||||
echo -e "${YELLOW}==================================================================================${NC}"
|
||||
log_info "RELEASE MODE — the PUBLIC image. No answer.toml, no auto-installer-mode.toml, no root"
|
||||
log_info "password, no SSH key, no disk profile. Two INTERACTIVE menu entries; the person"
|
||||
log_info "installing chooses the disk. Day-0 rides felhom-bootstrap.deb in /proxmox/packages/."
|
||||
echo -e "${YELLOW}==================================================================================${NC}"
|
||||
elif $PAIRING; then
|
||||
SECRET_BEARING="no"
|
||||
PAIR_HUB_URL="${FELHOM_HUB_URL:-https://hub.felhom.eu}"
|
||||
PAIR_INSTALL_URL="${FELHOM_INSTALL_URL:-https://felhom.eu/scripts/felhom-host-install.sh}"
|
||||
@@ -265,9 +298,20 @@ fi
|
||||
# Secure-Boot-off prep. -----------------------------------------------------------------------
|
||||
LOADER_SUFFIX=""; [[ "$LOADER" != "shim" ]] && LOADER_SUFFIX="-${LOADER}"
|
||||
MODE_SUFFIX=""; $PAIRING && MODE_SUFFIX="-generic" # the secret-free universal ISO is unmistakable
|
||||
OUT_ISO="$OUT_DIR/felhom-pve-${PVE_VERSION}-v${ISO_VERSION}-${PROFILE_NAME}${MODE_SUFFIX}${LOADER_SUFFIX}.iso"
|
||||
if $RELEASE; then
|
||||
# Named so it cannot be mistaken for one of the lab artifacts in out/ (canary, probe, nested-vm,
|
||||
# demo, hp, n100). "felhom-installer" + the version is what a tester quotes in a bug report.
|
||||
OUT_ISO="$OUT_DIR/felhom-installer-${ISO_VERSION}-pve${PVE_VERSION}${LOADER_SUFFIX}.iso"
|
||||
else
|
||||
OUT_ISO="$OUT_DIR/felhom-pve-${PVE_VERSION}-v${ISO_VERSION}-${PROFILE_NAME}${MODE_SUFFIX}${LOADER_SUFFIX}.iso"
|
||||
fi
|
||||
|
||||
# --- mint fresh THROWAWAY root hash ---------------------------------------------------------------
|
||||
if $RELEASE; then
|
||||
log_info "release mode — NOT minting a root password (release gate G2: none is baked, and no"
|
||||
log_info " .rootpw.txt is emitted). The installer prompts the person doing the install."
|
||||
ROOT_HASH=""
|
||||
else
|
||||
log_step "minting fresh throwaway root password hash"
|
||||
ROOT_PLAIN="felhom-throwaway-$(head -c12 /dev/urandom | base64 | tr -dc 'A-Za-z0-9')"
|
||||
ROOT_HASH="$(openssl passwd -6 "$ROOT_PLAIN")"
|
||||
@@ -281,8 +325,10 @@ ROOT_HASH="$(openssl passwd -6 "$ROOT_PLAIN")"
|
||||
unset ROOT_PLAIN
|
||||
[[ -n "$ROOT_HASH" ]] || die "failed to mint root hash"
|
||||
log_info "throwaway root hash written to the answer file; plaintext in $(basename "$OUT_ISO").rootpw.txt (0600, operator-only — value not logged)"
|
||||
fi
|
||||
|
||||
# --- render answer.toml (pure bash param-expansion; no metachar hazards) ---------------------------
|
||||
if ! $RELEASE; then
|
||||
log_step "rendering answer.toml"
|
||||
ANSWER="$WORK/answer.toml"
|
||||
: > "$ANSWER"
|
||||
@@ -329,6 +375,17 @@ awk -v sh="$sh_b64" -v unit="$unit_b64" -v env="$env_b64" '
|
||||
' "$HERE/stub-first-boot.sh" > "$STUB"
|
||||
chmod 0755 "$STUB"
|
||||
grep -q '@@BOOTSTRAP_.*_B64@@' "$STUB" && die "stub still has unfilled markers — injection failed"
|
||||
fi # end: non-release answer/stub rendering
|
||||
|
||||
# --- RELEASE: build the felhom-bootstrap .deb from COMMITTED source (release gate G7/G9/G10) --------
|
||||
RELEASE_DEB=""
|
||||
if $RELEASE; then
|
||||
log_step "building the felhom-bootstrap package"
|
||||
[[ -x "$HERE/pkg/build-deb.sh" ]] || die "pkg/build-deb.sh not found next to build-felhom-iso.sh"
|
||||
RELEASE_DEB="$("$HERE/pkg/build-deb.sh" "$ISO_VERSION" "$WORK/pkg")" \
|
||||
|| die "package build FAILED (its own G8/G9 self-assertions gate it)"
|
||||
log_success "package: $(basename "$RELEASE_DEB") ($(stat -c%s "$RELEASE_DEB") bytes)"
|
||||
fi
|
||||
|
||||
# --- prepare-iso ----------------------------------------------------------------------------------
|
||||
# (OUT_ISO is resolved above the mint — the rootpw sibling file is named after it.)
|
||||
@@ -342,11 +399,19 @@ if $DRY_RUN; then
|
||||
log_info "DRY-RUN: no ISO produced"
|
||||
exit 0
|
||||
fi
|
||||
docker run --rm -v "$ISO_DIR":/iso:ro -v "$WORK":/work "$IMAGE" \
|
||||
proxmox-auto-install-assistant prepare-iso "/iso/$ISO_BASE" \
|
||||
--fetch-from iso --answer-file /work/answer.toml \
|
||||
--on-first-boot /work/stub-first-boot.sh \
|
||||
--tmp /work/tmp --output /work/out.iso
|
||||
if $RELEASE; then
|
||||
# NO prepare-iso. That step is what creates /answer.toml and /auto-installer-mode.toml, and their
|
||||
# absence is release-gate G1 — it also removes the Automated menu entry by construction, since the
|
||||
# stock grub.cfg emits it only inside `if [ -f auto-installer-mode.toml ]`.
|
||||
log_info "release mode — skipping prepare-iso entirely (no answer file is created)"
|
||||
cp "$PVE_ISO" "$WORK/out.iso"
|
||||
else
|
||||
docker run --rm -v "$ISO_DIR":/iso:ro -v "$WORK":/work "$IMAGE" \
|
||||
proxmox-auto-install-assistant prepare-iso "/iso/$ISO_BASE" \
|
||||
--fetch-from iso --answer-file /work/answer.toml \
|
||||
--on-first-boot /work/stub-first-boot.sh \
|
||||
--tmp /work/tmp --output /work/out.iso
|
||||
fi
|
||||
[[ -f "$WORK/out.iso" ]] || die "prepare-iso produced no output"
|
||||
|
||||
# --- REPACK: R-38 GRUB branding + single-entry menu, and (slice B) the mkimage loader surgery. Both
|
||||
@@ -389,6 +454,7 @@ BRAND_NOTE="FELHOM (single entry 'Felhom telepítés', default, 5s; stock instal
|
||||
$BRAND || BRAND_NOTE="STOCK PVE menu (--no-brand; DEBUG BUILD — manual installer reachable, do not ship)"
|
||||
MODE_NOTE="direct (env-baked customer-id + retrieval passphrase; secret-bearing)"
|
||||
$PAIRING && MODE_NOTE="pairing (GENERIC secret-free universal ISO — box self-registers, operator binds, hub delivers once)"
|
||||
$RELEASE && MODE_NOTE="release (PUBLIC image — NO answer.toml, NO baked credential, interactive disk selection)"
|
||||
cat > "$OUT_ISO.manifest.txt" <<EOF
|
||||
Felhom bare-metal ISO build manifest (R-21 slice A+B+C)
|
||||
built : $(date -Is)
|
||||
@@ -406,7 +472,10 @@ boot-menu : ${BRAND_NOTE}
|
||||
$( $BRAND && cat "$WORK/brand-report.txt" 2>/dev/null || true )
|
||||
host-install-url : $(grep -oE 'FELHOM_INSTALL_URL=[^ ]*' "$BOOTSTRAP_ENV" 2>/dev/null || echo 'https://felhom.eu/scripts/felhom-host-install.sh (default)')
|
||||
secret-bearing : ${SECRET_BEARING}$( $PAIRING && echo ' (GENERIC ISO — carries NO customer secret)' || echo ' (embeds the customer retrieval passphrase — supervised/single-use, delete after the run)')
|
||||
root-password : see $(basename "$OUT_ISO").rootpw.txt (operator-only; NEVER commit or paste into REPORTs)
|
||||
root-password : $( $RELEASE && echo 'NONE — not baked. The installer prompts the person installing (release gate G2).' || echo "see $(basename "$OUT_ISO").rootpw.txt (operator-only; NEVER commit or paste into REPORTs)" )
|
||||
answer-file : $( $RELEASE && echo 'NONE — no answer.toml, no auto-installer-mode.toml (release gate G1)' || echo 'baked (/answer.toml)' )
|
||||
felhom-package : $( [[ -n "$RELEASE_DEB" ]] && echo "$(basename "$RELEASE_DEB") sha256=$(sha256sum "$RELEASE_DEB" | cut -d\ -f1)" || echo 'n/a (day-0 rides the answer file first-boot hook)' )
|
||||
repo-commit : $(cd "$HERE" && git rev-parse HEAD 2>/dev/null || echo unknown)
|
||||
output : $(basename "$OUT_ISO")
|
||||
output-sha256 : ${OUT_SHA}
|
||||
output-size-bytes : ${OUT_SIZE}
|
||||
@@ -417,7 +486,10 @@ log_info "sha256 : $OUT_SHA"
|
||||
log_info "size : $OUT_SIZE bytes"
|
||||
log_info "manifest : $OUT_ISO.manifest.txt"
|
||||
log_info "root-pw : $OUT_ISO.rootpw.txt (0600, operator-only — the console credential for this build; never commit/paste)"
|
||||
if $PAIRING; then
|
||||
if $RELEASE; then
|
||||
log_success "PUBLIC RELEASE image — no answer file, no baked credential, interactive disk selection."
|
||||
log_info "Run the release gate before publishing: documentation/runbooks/iso-release-gate.md"
|
||||
elif $PAIRING; then
|
||||
log_success "GENERIC secret-free ISO — carries NO customer secret. Bind the box on the hub after it registers."
|
||||
else
|
||||
log_warn "SECRET-BEARING ISO (embeds the retrieval passphrase). Supervised/single-use; never distribute; delete after the run."
|
||||
|
||||
Reference in New Issue
Block a user