hub v0.62.0 + scripts v1.19.0 — R-21 slice C: the universal secret-free ISO
A generic ISO carries NO customer secret. The box registers itself at the hub as an unclaimed appliance; the operator binds it to a customer; the hub delivers the customer-id + retrieval passphrase ONCE; day-0 completes via the slice-A path. Hub (v0.62.0): - store/appliance.go: appliance_registrations keyed by (uuid, mac_set) — MAC set is the tiebreaker (duplicate SMBIOS UUIDs); token stored as sha256 only. Idempotent register (sticky-discard), atomic one-shot delivery, bind/discard. - api/appliance.go: POST /appliance/register (the one unauth endpoint, per-IP rate-limited, 256-bit token); GET /appliance/poll (404 no-oracle / 204 unbound / 200 deliver-once / 410 delivered). Passphrase read live, never logged. - web/appliances.go: Hosts-page "Unclaimed appliances" section + BIND (customer picker, host count display-only) + DISCARD; SSH host-key fingerprints; events. - Red-proofs: one-shot delivery + register idempotency (both proven red); 404-no-oracle, sticky-discard, bind staging, render. Green + confirm gate. Scripts (v1.19.0): - felhom-bootstrap.sh: ONE unit, TWO modes. Direct (env has customer/passphrase) = slice-A path, byte-identical, only branched around. Pairing (generic) = register + poll (RestartSec=30 is the poll timer); on delivery write the env 0600 and fall through to direct. Secrets + token shredded on success. - build-felhom-iso.sh --pairing: generic secret-free ISO, -generic filename, manifest mode=pairing. profiles/generic.profile (new). - test/bootstrap-modes.sh: Scenario D (direct = zero appliance calls) + pairing register/poll + delivery handoff — all green in a debian container.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# build-felhom-iso.sh — R-21 slice A+B: turn the official PVE ISO into a Felhom auto-install ISO.
|
||||
# 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
|
||||
@@ -27,7 +32,7 @@
|
||||
#===============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
ISO_VERSION="1.18.0" # Felhom release the ISO is tagged to (aligns with felhom-host-install SCRIPT_VERSION).
|
||||
ISO_VERSION="1.19.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)"
|
||||
|
||||
@@ -44,16 +49,24 @@ die() { log_error "$1"; exit 1; }
|
||||
|
||||
PVE_ISO=""; ISO_SHA256=""; PROFILE=""; BOOTSTRAP_ENV=""; OUT_DIR="${HOME}/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: build-felhom-iso.sh --pve-iso PATH --iso-sha256 SHA --profile FILE --bootstrap-env FILE [options]
|
||||
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
|
||||
--bootstrap-env FILE the in-ISO /etc/felhom/bootstrap.env (SECRET-BEARING: retrieval passphrase).
|
||||
Must define FELHOM_CUSTOMER_ID, FELHOM_MODE, FELHOM_RETRIEVAL_PASSPHRASE.
|
||||
|
||||
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).
|
||||
@@ -71,6 +84,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--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 ;;
|
||||
@@ -85,10 +99,15 @@ done
|
||||
[[ -n "$PVE_ISO" ]] || die "--pve-iso is required"
|
||||
[[ -n "$ISO_SHA256" ]] || die "--iso-sha256 is required"
|
||||
[[ -n "$PROFILE" ]] || die "--profile is required"
|
||||
[[ -n "$BOOTSTRAP_ENV" ]] || die "--bootstrap-env is required"
|
||||
[[ -f "$PVE_ISO" ]] || die "--pve-iso not found: $PVE_ISO"
|
||||
[[ -f "$PROFILE" ]] || die "--profile not found: $PROFILE"
|
||||
[[ -f "$BOOTSTRAP_ENV" ]] || die "--bootstrap-env not found: $BOOTSTRAP_ENV"
|
||||
# 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"
|
||||
@@ -107,7 +126,7 @@ 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_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"
|
||||
@@ -130,18 +149,29 @@ else
|
||||
log_info "loader mode = shim (stock MS-signed chain; Secure Boot works on compliant firmware)"
|
||||
fi
|
||||
|
||||
# --- validate bootstrap-env (secret-bearing detection) --------------------------------------------
|
||||
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"
|
||||
# --- 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")"
|
||||
@@ -151,6 +181,19 @@ 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')"
|
||||
@@ -210,7 +253,8 @@ grep -q '@@BOOTSTRAP_.*_B64@@' "$STUB" && die "stub still has unfilled markers
|
||||
# --- 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}"
|
||||
OUT_ISO="$OUT_DIR/felhom-pve-${PVE_VERSION}-v${ISO_VERSION}-${PROFILE_NAME}${LOADER_SUFFIX}.iso"
|
||||
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
|
||||
@@ -252,8 +296,10 @@ ASSISTANT_VER="$(docker run --rm "$IMAGE" proxmox-auto-install-assistant --versi
|
||||
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)
|
||||
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}
|
||||
@@ -262,10 +308,11 @@ 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} (embeds the customer retrieval passphrase — supervised/single-use, delete after the run)
|
||||
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}
|
||||
@@ -275,4 +322,8 @@ log_success "ISO built: $OUT_ISO"
|
||||
log_info "sha256 : $OUT_SHA"
|
||||
log_info "size : $OUT_SIZE bytes"
|
||||
log_info "manifest : $OUT_ISO.manifest.txt"
|
||||
log_warn "SECRET-BEARING ISO (embeds the retrieval passphrase). Supervised/single-use; never distribute; delete after the run."
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user