bcdb04222a
Waiting to be bound is the NORMAL state of a freshly installed box, and it must
not be reported as failure. The PAIRING poll loop used to BE systemd's
Restart=on-failure/RestartSec=30 — one poll per invocation, exiting non-zero
until the bind landed — so every 30s systemd printed "Failed to start Felhom
host bootstrap" on the physical console the CUSTOMER is watching. The
2026-07-18 N100 rehearsal measured 52 FAILED lines in ~11 minutes while nothing
was wrong (VALIDATION-n100-rehearsal-2026-07-18.md F6).
felhom-bootstrap.sh: run_pairing() is now a while-loop that sleeps
POLL_INTERVAL (30s — the hub-side rate is unchanged) between polls, so the unit
sits in `activating`. Registration split into register_appliance(), which
returns non-zero for a transient problem (no network yet, no identity, no
token) and is retried by the loop instead of taking the unit down. Cadence
constants: POLL_INTERVAL=30, BANNER_EVERY=10 (5 min), HEARTBEAT_EVERY=20
(10 min).
Quiet without going dark: a 204 is logged once on entry (worded so nobody reads
it as an error) and then only on the 10-minute heartbeat with elapsed minutes;
404 and unexpected codes degrade the same way. 410 STILL exits non-zero on
purpose — delivery consumed but no local env is a real crash window, and a
clean systemd restart is the right response.
Console banner: every 5 min instead of every cycle, single accented spelling
instead of the parositasra/párosításra double, and the reassurance the
rehearsal showed was missing ("Ez a képernyő magától frissül — nincs teendő a
doboznál").
felhom-bootstrap.service: TimeoutStartSec=infinity. This is load-bearing, not
cosmetic — a Type=oneshot ExecStart is killed at DefaultTimeoutStartSec (90s),
so without it systemd would kill the new in-script wait after 90 seconds and
Restart=on-failure would silently reinstate the exact spam this removes, after
appearing to work for the first three polls. Restart=/RestartSec= are kept
deliberately: they still cover the DIRECT path, a failed host-install, and 410.
Verified behaviourally, not assumed: driven in a throwaway Debian container
against a stub hub answering 204 five times then delivering — logged the wait
once plus one heartbeat, never exited between polls, then consumed the
delivery, wrote the 0600 env, fell through to the direct install in the same
invocation and exited 0. The old design produced five unit invocations and five
"Failed to start" console lines for that same sequence.
Hub endpoints, payloads, polling rate and one-shot delivery semantics are all
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
331 lines
20 KiB
Bash
331 lines
20 KiB
Bash
#!/bin/bash
|
|
#===============================================================================
|
|
# build-felhom-iso.sh — R-21 slice A+B+C: turn the official PVE ISO into a Felhom auto-install ISO.
|
|
#
|
|
# SLICE C — --pairing builds the GENERIC, SECRET-FREE universal ISO: no customer-id / passphrase is
|
|
# baked in. The box registers itself at the hub as an UNCLAIMED APPLIANCE, the operator binds it to a
|
|
# customer, and the hub delivers the credentials ONCE — then the box completes day-0 exactly like a
|
|
# direct-mode box. Direct mode (--bootstrap-env, secret-bearing, operator-prepped) is unchanged.
|
|
#
|
|
# Renders answer.toml (from answer.toml.tmpl + a profile), mints a fresh THROWAWAY root hash,
|
|
# gates the answer through validate-answer by PARSING ITS OUTPUT (never $? — validate-answer returns
|
|
# exit 0 even on failure, spike S1 trap), renders the first-boot stub (injecting the bootstrap
|
|
# script/unit/env), and runs prepare-iso --fetch-from iso --on-first-boot. Emits the ISO + sha256 +
|
|
# a build manifest.
|
|
#
|
|
# SLICE B — --loader shim|mkimage (default shim). shim = the stock output (MS-signed shim→GRUB chain,
|
|
# keeps Secure Boot working on compliant firmware, spike S2b). mkimage = replace the ISO's UEFI boot
|
|
# path with a monolithic grub-mkimage-built BOOTX64.EFI built from the ISO's OWN GRUB modules — the
|
|
# workaround PROVEN LIVE during the N100 run (VALIDATION-n100-baremetal F1: cheap AMI AN3PLUS-class
|
|
# firmware can't relocate the ISO's signed GRUB from USB, `relocation 0x0`). The mkimage loader is
|
|
# UNSIGNED → the target board MUST have Secure Boot OFF (documented in the n100 profile's prep). The
|
|
# loader surgery runs AFTER prepare-iso (the assistant's answer/first-boot payload is provably
|
|
# untouched except the loader path).
|
|
#
|
|
# SECRET-BEARING: if the bootstrap-env carries a retrieval passphrase (it must, for an unattended
|
|
# install — see README "secret-bearing"), the produced ISO embeds it. Supervised/single-use only;
|
|
# never distributed; delete after the run. The build log says so loudly.
|
|
#
|
|
# Runs on DooPlex; delegates validate-answer + prepare-iso + the mkimage surgery to the
|
|
# felhom-iso-assistant container (which carries proxmox-auto-install-assistant, xorriso, grub-mkimage,
|
|
# and mtools).
|
|
#===============================================================================
|
|
set -euo pipefail
|
|
|
|
ISO_VERSION="1.21.0" # Felhom release the ISO is tagged to (aligns with felhom-host-install SCRIPT_VERSION).
|
|
IMAGE="${FELHOM_ISO_ASSISTANT_IMAGE:-felhom-iso-assistant:trixie}"
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# --- logging (host-install idiom) -----------------------------------------------------------------
|
|
if [[ -t 1 ]]; then RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m'; BLUE=$'\033[0;34m'; CYAN=$'\033[0;36m'; NC=$'\033[0m'
|
|
else RED=""; GREEN=""; YELLOW=""; BLUE=""; CYAN=""; NC=""; fi
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
|
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
|
|
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
log_dry() { echo -e "${CYAN}[DRY-RUN]${NC} $1"; }
|
|
die() { log_error "$1"; exit 1; }
|
|
|
|
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.
|
|
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]
|
|
|
|
Required:
|
|
--pve-iso PATH pre-downloaded official PVE ISO (not fetched here)
|
|
--iso-sha256 SHA expected sha256 of --pve-iso (verified before build; abort on mismatch)
|
|
--profile FILE build profile (fqdn + [disk-setup]); see profiles/ and README
|
|
|
|
Mode (exactly one):
|
|
--bootstrap-env FILE DIRECT mode: the in-ISO /etc/felhom/bootstrap.env (SECRET-BEARING: retrieval
|
|
passphrase). Must define FELHOM_CUSTOMER_ID, FELHOM_MODE, FELHOM_RETRIEVAL_PASSPHRASE.
|
|
--pairing PAIRING mode (slice C): the GENERIC, SECRET-FREE universal ISO. The box registers
|
|
itself as an unclaimed appliance at the hub; the operator binds it; the hub
|
|
delivers the customer-id + passphrase ONCE. No customer secret is baked in. The
|
|
hub URL comes from the profile (FELHOM_HUB_URL) or the default.
|
|
Options:
|
|
--loader shim|mkimage UEFI boot loader (default: shim, or the profile's FELHOM_LOADER; --loader wins).
|
|
shim = stock MS-signed chain (Secure Boot OK on compliant firmware).
|
|
mkimage = monolithic grub-mkimage loader for cheap AMI boards that can't boot
|
|
the ISO's GRUB from USB (F1). UNSIGNED -> the target board needs Secure Boot OFF.
|
|
--out DIR output directory (default: the DooPlex build root
|
|
/mnt/5_hdd/felhom.eu/felhom-iso/out; override via \$FELHOM_ISO_OUT for other hosts)
|
|
--pve-version VER override PVE version tag (default: parsed from the ISO filename)
|
|
--dry-run print the steps without producing an ISO
|
|
-h, --help this help
|
|
EOF
|
|
}
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--pve-iso) PVE_ISO="$2"; shift 2 ;;
|
|
--iso-sha256) ISO_SHA256="$2"; shift 2 ;;
|
|
--profile) PROFILE="$2"; shift 2 ;;
|
|
--bootstrap-env) BOOTSTRAP_ENV="$2"; shift 2 ;;
|
|
--pairing) PAIRING=true; shift ;;
|
|
--loader) LOADER_CLI="$2"; shift 2 ;;
|
|
--out) OUT_DIR="$2"; shift 2 ;;
|
|
--pve-version) PVE_VERSION="$2"; shift 2 ;;
|
|
--dry-run) DRY_RUN=true; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) die "unknown argument: $1 (see --help)" ;;
|
|
esac
|
|
done
|
|
[[ -z "$LOADER_CLI" || "$LOADER_CLI" == "shim" || "$LOADER_CLI" == "mkimage" ]] \
|
|
|| die "--loader must be 'shim' or 'mkimage' (got '$LOADER_CLI')"
|
|
|
|
[[ -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"
|
|
# 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"
|
|
else
|
|
[[ -n "$BOOTSTRAP_ENV" ]] || die "one of --bootstrap-env (direct) or --pairing (generic) is required"
|
|
[[ -f "$BOOTSTRAP_ENV" ]] || die "--bootstrap-env not found: $BOOTSTRAP_ENV"
|
|
fi
|
|
|
|
command -v docker >/dev/null || die "docker not found (needed for the assistant container)"
|
|
docker image inspect "$IMAGE" >/dev/null 2>&1 || die "assistant image '$IMAGE' not found — build it: docker build -f $HERE/Dockerfile.assistant -t $IMAGE $HERE"
|
|
|
|
# --- verify source ISO ----------------------------------------------------------------------------
|
|
log_step "verifying source ISO sha256"
|
|
actual_sha=$(sha256sum "$PVE_ISO" | awk '{print $1}')
|
|
[[ "$actual_sha" == "$ISO_SHA256" ]] || die "ISO sha256 MISMATCH: expected $ISO_SHA256, got $actual_sha"
|
|
log_success "source ISO sha256 OK ($actual_sha)"
|
|
|
|
if [[ -z "$PVE_VERSION" ]]; then
|
|
PVE_VERSION=$(basename "$PVE_ISO" | sed -E 's/^proxmox-ve_(.+)\.iso$/\1/')
|
|
[[ "$PVE_VERSION" != "$(basename "$PVE_ISO")" ]] || die "cannot parse PVE version from '$(basename "$PVE_ISO")' — pass --pve-version"
|
|
fi
|
|
PROFILE_NAME="$(basename "$PROFILE")"; PROFILE_NAME="${PROFILE_NAME%.profile}"
|
|
|
|
# --- load + validate profile ----------------------------------------------------------------------
|
|
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
|
|
source "$PROFILE"
|
|
[[ -n "$FELHOM_FQDN" ]] || die "profile missing FELHOM_FQDN"
|
|
[[ -n "$FELHOM_DISK_SETUP" ]] || die "profile missing FELHOM_DISK_SETUP"
|
|
# 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}\"]"
|
|
|
|
# --- resolve the loader mode: --loader wins over the profile's FELHOM_LOADER over the shim default ---
|
|
LOADER="${LOADER_CLI:-${FELHOM_LOADER:-shim}}"
|
|
[[ "$LOADER" == "shim" || "$LOADER" == "mkimage" ]] \
|
|
|| die "profile FELHOM_LOADER must be 'shim' or 'mkimage' (got '$FELHOM_LOADER')"
|
|
if [[ "$LOADER" == "mkimage" ]]; then
|
|
echo -e "${YELLOW}==================================================================================${NC}"
|
|
log_warn "LOADER MODE = mkimage — the UEFI boot path is a monolithic grub-mkimage loader (F1 fix)."
|
|
log_warn "This loader is UNSIGNED: the target board MUST have Secure Boot OFF. shim/SB is bypassed."
|
|
log_warn "The ISO filename gains '-mkimage'; never confuse it with a shim (SB-capable) build."
|
|
echo -e "${YELLOW}==================================================================================${NC}"
|
|
else
|
|
log_info "loader mode = shim (stock MS-signed chain; Secure Boot works on compliant firmware)"
|
|
fi
|
|
|
|
# --- mode: DIRECT validates the secret-bearing env; PAIRING is secret-free (env generated below) -----
|
|
if $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}"
|
|
echo -e "${YELLOW}==================================================================================${NC}"
|
|
log_info "PAIRING MODE — building the GENERIC, SECRET-FREE universal ISO (slice C)."
|
|
log_info "The box registers as an unclaimed appliance; the operator binds it; the hub delivers the"
|
|
log_info "customer-id + passphrase ONCE. Baked env carries only the hub URL ($PAIR_HUB_URL) — no secret."
|
|
echo -e "${YELLOW}==================================================================================${NC}"
|
|
else
|
|
log_step "checking bootstrap-env (secret-bearing detection)"
|
|
( set +e
|
|
FELHOM_CUSTOMER_ID=""; FELHOM_MODE=""; FELHOM_RETRIEVAL_PASSPHRASE=""
|
|
# shellcheck disable=SC1090
|
|
source "$BOOTSTRAP_ENV"
|
|
[[ -n "$FELHOM_CUSTOMER_ID" ]] || { echo "MISSING FELHOM_CUSTOMER_ID"; exit 3; }
|
|
[[ -n "$FELHOM_MODE" ]] || { echo "MISSING FELHOM_MODE"; exit 3; }
|
|
[[ -n "$FELHOM_RETRIEVAL_PASSPHRASE" ]] || { echo "MISSING FELHOM_RETRIEVAL_PASSPHRASE"; exit 3; }
|
|
) || die "bootstrap-env invalid ($BOOTSTRAP_ENV) — must define FELHOM_CUSTOMER_ID, FELHOM_MODE, FELHOM_RETRIEVAL_PASSPHRASE"
|
|
SECRET_BEARING="yes" # a valid bootstrap-env always carries the retrieval passphrase
|
|
log_warn "this ISO will be SECRET-BEARING (embeds the customer retrieval passphrase) — supervised/single-use only"
|
|
fi
|
|
|
|
# --- workspace ------------------------------------------------------------------------------------
|
|
WORK="$(mktemp -d "${TMPDIR:-/tmp}/felhom-iso.XXXXXX")"
|
|
# chmod first: the mkimage surgery's osirrox extract leaves read-only file modes that rm can't clear.
|
|
cleanup() { chmod -R u+w "$WORK" 2>/dev/null || true; rm -rf "$WORK"; }
|
|
trap cleanup EXIT
|
|
mkdir -p "$OUT_DIR" "$WORK/tmp"
|
|
ISO_DIR="$(cd "$(dirname "$PVE_ISO")" && pwd)"; ISO_BASE="$(basename "$PVE_ISO")"
|
|
|
|
# PAIRING: generate the SECRET-FREE env the stub bakes — only the hub URL, no customer/passphrase.
|
|
# (The bootstrap detects the absent customer-id/passphrase and enters pairing mode.)
|
|
if $PAIRING; then
|
|
BOOTSTRAP_ENV="$WORK/pairing.env"
|
|
cat > "$BOOTSTRAP_ENV" <<EOF
|
|
# GENERIC secret-free pairing env (R-21 slice C). NO customer-id, NO passphrase — the box registers
|
|
# as an unclaimed appliance and the hub delivers the credentials once, after the operator binds it.
|
|
FELHOM_HUB_URL=$PAIR_HUB_URL
|
|
FELHOM_INSTALL_URL=$PAIR_INSTALL_URL
|
|
EOF
|
|
log_info "generated secret-free pairing env (hub=$PAIR_HUB_URL)"
|
|
fi
|
|
|
|
# --- mint fresh THROWAWAY root hash ---------------------------------------------------------------
|
|
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")"
|
|
unset ROOT_PLAIN
|
|
[[ -n "$ROOT_HASH" ]] || die "failed to mint root hash"
|
|
log_info "throwaway root hash written to the answer file (value stored out-of-band, not logged)"
|
|
|
|
# --- render answer.toml (pure bash param-expansion; no metachar hazards) ---------------------------
|
|
log_step "rendering answer.toml"
|
|
ANSWER="$WORK/answer.toml"
|
|
: > "$ANSWER"
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
if [[ "$line" == "__DISK_SETUP__" ]]; then
|
|
printf '%s\n' "$FELHOM_DISK_SETUP" >> "$ANSWER"
|
|
elif [[ "$line" == "__ROOT_SSH_KEYS__" ]]; then
|
|
[[ -n "$ROOT_SSH_LINE" ]] && printf '%s\n' "$ROOT_SSH_LINE" >> "$ANSWER" # blank -> omit line
|
|
else
|
|
line="${line//__FQDN__/$FELHOM_FQDN}"
|
|
line="${line//__ROOT_HASH__/$ROOT_HASH}"
|
|
printf '%s\n' "$line" >> "$ANSWER"
|
|
fi
|
|
done < "$HERE/answer.toml.tmpl"
|
|
|
|
# --- validate-answer OUTPUT-PARSE gate (never $? — spike S1) --------------------------------------
|
|
gate_validate_answer() {
|
|
local out
|
|
out=$(docker run --rm -v "$WORK":/work "$IMAGE" \
|
|
proxmox-auto-install-assistant validate-answer /work/answer.toml 2>&1) || true
|
|
echo "----- validate-answer output -----"; echo "$out"; echo "----------------------------------"
|
|
# LOAD-BEARING: validate-answer exits 0 even on failure; decide on the MESSAGE TEXT, not $?.
|
|
if echo "$out" | grep -q "parsed successfully" && ! echo "$out" | grep -qi "Found issues"; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
log_step "validating rendered answer (output-parse gate)"
|
|
if $DRY_RUN; then
|
|
log_dry "docker run … validate-answer /work/answer.toml (output-parse gate)"
|
|
else
|
|
gate_validate_answer || die "answer validation FAILED — NO ISO produced (fix the answer/profile)"
|
|
log_success "answer validated"
|
|
fi
|
|
|
|
# --- render the first-boot stub (inject bootstrap script/unit/env as base64) ----------------------
|
|
log_step "rendering first-boot stub"
|
|
STUB="$WORK/stub-first-boot.sh"
|
|
sh_b64="$(base64 -w0 < "$HERE/felhom-bootstrap.sh")"
|
|
unit_b64="$(base64 -w0 < "$HERE/felhom-bootstrap.service")"
|
|
env_b64="$(base64 -w0 < "$BOOTSTRAP_ENV")"
|
|
awk -v sh="$sh_b64" -v unit="$unit_b64" -v env="$env_b64" '
|
|
{ gsub(/@@BOOTSTRAP_SH_B64@@/, sh); gsub(/@@BOOTSTRAP_UNIT_B64@@/, unit); gsub(/@@BOOTSTRAP_ENV_B64@@/, env); print }
|
|
' "$HERE/stub-first-boot.sh" > "$STUB"
|
|
chmod 0755 "$STUB"
|
|
grep -q '@@BOOTSTRAP_.*_B64@@' "$STUB" && die "stub still has unfilled markers — injection failed"
|
|
|
|
# --- prepare-iso ----------------------------------------------------------------------------------
|
|
# Rule 4: the loader mode is loud in the filename — a '-mkimage' ISO implies 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"
|
|
GRUB_VERSION="" # populated by the mkimage surgery (the grub-mkimage build used)
|
|
log_step "building ISO: $(basename "$OUT_ISO")"
|
|
if $DRY_RUN; then
|
|
log_dry "docker run … prepare-iso /iso/$ISO_BASE --fetch-from iso --answer-file /work/answer.toml --on-first-boot /work/stub-first-boot.sh --output /work/out.iso"
|
|
[[ "$LOADER" == "mkimage" ]] && log_dry "docker run … (mkimage surgery) grub-mkimage from the ISO's own modules → swap BOOTX64.EFI in the EFI tree + efi.img → xorriso re-master → /work/final.iso"
|
|
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
|
|
[[ -f "$WORK/out.iso" ]] || die "prepare-iso produced no output"
|
|
|
|
# --- SLICE B: mkimage loader surgery (post-prepare; the assistant payload is untouched but the EFI
|
|
# boot path). The recipe is the N100 run's proven workaround (VALIDATION F1) — do NOT re-derive it:
|
|
# grub-mkimage from the ISO's OWN x86_64-efi modules (2.12-9+pmx2), embedding the module set the
|
|
# ISO's grub.cfg needs + an embedded config that `search --fs-uuid`es the ISO and `configfile`s its
|
|
# real menu; then swap BOOTX64.EFI in the ISO9660 EFI/BOOT tree AND inside efi.img, and re-master
|
|
# with xorriso preserving BOTH the hybrid BIOS boot and the injected answer/first-boot payload. ---
|
|
if [[ "$LOADER" == "mkimage" ]]; then
|
|
log_step "applying mkimage UEFI loader (F1 firmware fix; recipe from the N100 run evidence)"
|
|
[[ -f "$HERE/mkimage-surgery.sh" ]] || die "mkimage-surgery.sh not found next to build-felhom-iso.sh"
|
|
cp "$HERE/mkimage-surgery.sh" "$WORK/mkimage-surgery.sh"
|
|
docker run --rm -v "$WORK":/work "$IMAGE" bash /work/mkimage-surgery.sh 2>&1 | sed 's/^/ [surgery] /'
|
|
[[ -f "$WORK/final.iso" ]] || die "mkimage surgery produced no output (see [surgery] log above)"
|
|
GRUB_VERSION="$(cat "$WORK/grub-version.txt" 2>/dev/null || echo unknown)"
|
|
cp "$WORK/final.iso" "$OUT_ISO"
|
|
log_success "mkimage loader applied (grub-mkimage: ${GRUB_VERSION})"
|
|
else
|
|
cp "$WORK/out.iso" "$OUT_ISO"
|
|
fi
|
|
|
|
# --- sha256 + manifest ----------------------------------------------------------------------------
|
|
OUT_SHA="$(sha256sum "$OUT_ISO" | awk '{print $1}')"
|
|
OUT_SIZE="$(stat -c '%s' "$OUT_ISO")"
|
|
ASSISTANT_VER="$(docker run --rm "$IMAGE" proxmox-auto-install-assistant --version 2>&1 | head -1)"
|
|
echo "$OUT_SHA $(basename "$OUT_ISO")" > "$OUT_ISO.sha256"
|
|
LOADER_NOTE="shim (stock MS-signed chain; Secure Boot OK on compliant firmware)"
|
|
[[ "$LOADER" == "mkimage" ]] && LOADER_NOTE="mkimage (monolithic grub-mkimage UEFI loader, F1 fix — UNSIGNED; target board MUST have Secure Boot OFF)"
|
|
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)"
|
|
cat > "$OUT_ISO.manifest.txt" <<EOF
|
|
Felhom bare-metal ISO build manifest (R-21 slice A+B+C)
|
|
built : $(date -Is)
|
|
iso-version-tag : v${ISO_VERSION}
|
|
pve-version : ${PVE_VERSION}
|
|
source-iso : ${ISO_BASE}
|
|
source-iso-sha256 : ${ISO_SHA256}
|
|
assistant-version : ${ASSISTANT_VER}
|
|
profile : ${PROFILE_NAME}
|
|
fqdn : ${FELHOM_FQDN}
|
|
mode : ${MODE_NOTE}
|
|
loader : ${LOADER_NOTE}
|
|
grub-mkimage : ${GRUB_VERSION:-n/a (shim mode; loader unchanged)}
|
|
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)')
|
|
output : $(basename "$OUT_ISO")
|
|
output-sha256 : ${OUT_SHA}
|
|
output-size-bytes : ${OUT_SIZE}
|
|
EOF
|
|
|
|
log_success "ISO built: $OUT_ISO"
|
|
log_info "sha256 : $OUT_SHA"
|
|
log_info "size : $OUT_SIZE bytes"
|
|
log_info "manifest : $OUT_ISO.manifest.txt"
|
|
if $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."
|
|
fi
|