1fa3250aa3
scripts/iso/: a DooPlex pipeline (build-felhom-iso.sh + Dockerfile.assistant) that turns the official PVE ISO into a Felhom auto-install ISO whose first-boot stub installs a retry-forever felhom-bootstrap unit which unattended-fetches felhom-host-install.sh from the public felhom.eu/scripts channel and runs it until the host is enrolled + a guest provisioned. host-install is UNMODIFIED (invoked only). - build gates the answer on validate-answer OUTPUT text, never $? (spike S1 exit-0 trap) - stub is from-iso, fully-up, exactly-once; retry unit owns all network work (S8a) - retry-vs-resume encoded once: plain first, --resume when install state exists (v1.11.3) - secret-bearing (embeds the retrieval passphrase): supervised/single-use; env shredded on success Validated on VM 310: build gate + red-proof, disk-filter fail-safe, chain + retry, resume-decision, exactly-once, no-net retry+recovery. Terminal host-install rc-0 success operator-gated (drill customer needs the password-gated create-UI). scripts v1.16.0; ROADMAP R-21 -> in-progress. Detail in REPORT.md.
99 lines
4.5 KiB
Bash
99 lines
4.5 KiB
Bash
#!/bin/bash
|
|
#===============================================================================
|
|
# felhom-bootstrap.sh — invoked by felhom-bootstrap.service, retried until host-install succeeds.
|
|
#
|
|
# One attempt: read /etc/felhom/bootstrap.env -> fetch felhom-host-install.sh from the PUBLIC
|
|
# distribution channel (hub install-command Option-1 URL) -> run it unattended with the customer's
|
|
# retrieval passphrase -> on rc 0 write the done-flag + disable the unit; else exit non-zero so the
|
|
# unit retries. Journal-only logging; the passphrase is never echoed and lives only in a 0600 tmpfs
|
|
# file for the duration of one host-install invocation.
|
|
#
|
|
# Retry-vs-resume (source-verified, encoded ONCE): felhom-host-install.sh v1.11.3 makes --resume
|
|
# safe — its producer steps (token/enroll/grows) re-run every pass, so a resumed install repopulates
|
|
# hub.host_id/proxmox.token and never writes a crash-loop config. A plain re-invoke over an existing
|
|
# install state, by contrast, would re-hit the populated-host leaf guard / existing-vmid refusal.
|
|
# Therefore: FIRST attempt is plain; any later attempt that finds the install state file adds
|
|
# --resume. (--mode is required in both forms.) State file: /var/lib/felhom-install/state.json.
|
|
#
|
|
# NOT production-generic: this is the R-21 bare-metal first-boot bootstrap. It does NOT modify
|
|
# felhom-host-install.sh; it only invokes it.
|
|
#===============================================================================
|
|
# Deliberately NOT `set -e`: we must capture host-install's exit code and exit on our own terms.
|
|
set -uo pipefail
|
|
|
|
ENV_FILE=/etc/felhom/bootstrap.env
|
|
DONE_FLAG=/etc/felhom/.bootstrap-done
|
|
STATE_FILE=/var/lib/felhom-install/state.json
|
|
PASS_FILE=/run/felhom-bootstrap-pass
|
|
SCRIPT_TMP=/run/felhom-host-install.sh
|
|
|
|
log() { echo "felhom-bootstrap: $*"; }
|
|
|
|
cleanup_pass() { [[ -e "$PASS_FILE" ]] && { shred -u "$PASS_FILE" 2>/dev/null || rm -f "$PASS_FILE"; }; return 0; }
|
|
trap cleanup_pass EXIT
|
|
|
|
# Belt-and-suspenders: the unit already has ConditionPathExists=!done, but guard here too.
|
|
if [[ -e "$DONE_FLAG" ]]; then
|
|
log "done-flag present ($DONE_FLAG) — nothing to do"
|
|
exit 0
|
|
fi
|
|
|
|
# --- env ------------------------------------------------------------------------------------------
|
|
if [[ ! -r "$ENV_FILE" ]]; then
|
|
log "ERROR: $ENV_FILE missing or unreadable — cannot bootstrap (no guessed defaults)"
|
|
exit 1
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
|
|
for var in FELHOM_CUSTOMER_ID FELHOM_MODE FELHOM_RETRIEVAL_PASSPHRASE; do
|
|
if [[ -z "${!var:-}" ]]; then
|
|
log "ERROR: $var is unset/empty in $ENV_FILE — refusing to guess"
|
|
exit 1
|
|
fi
|
|
done
|
|
HUB_URL="${FELHOM_HUB_URL:-https://hub.felhom.eu}"
|
|
INSTALL_URL="${FELHOM_INSTALL_URL:-https://felhom.eu/scripts/felhom-host-install.sh}"
|
|
EXTRA_ARGS="${FELHOM_EXTRA_ARGS:-}"
|
|
|
|
# --- fetch host-install (public channel) ----------------------------------------------------------
|
|
log "fetching host-install: $INSTALL_URL"
|
|
if ! curl -fsSL --max-time 60 "$INSTALL_URL" -o "$SCRIPT_TMP"; then
|
|
log "ERROR: host-install fetch failed (no network yet?) — unit will retry"
|
|
exit 1
|
|
fi
|
|
if [[ ! -s "$SCRIPT_TMP" ]]; then
|
|
log "ERROR: fetched host-install is empty — unit will retry"
|
|
exit 1
|
|
fi
|
|
|
|
# --- retrieval passphrase -> 0600 tmpfs file ------------------------------------------------------
|
|
( umask 077; printf '%s' "$FELHOM_RETRIEVAL_PASSPHRASE" > "$PASS_FILE" )
|
|
|
|
# --- retry-vs-resume ruling -----------------------------------------------------------------------
|
|
args=(--customer-id "$FELHOM_CUSTOMER_ID" --mode "$FELHOM_MODE" --hub-url "$HUB_URL" --passphrase-file "$PASS_FILE")
|
|
if [[ -f "$STATE_FILE" ]]; then
|
|
log "prior install state present ($STATE_FILE) -> adding --resume (host-install v1.11.3: producers re-run, safe)"
|
|
args+=(--resume)
|
|
fi
|
|
# EXTRA_ARGS are profile-only flags (never secrets); intentional word-split.
|
|
read -ra extra <<< "$EXTRA_ARGS"
|
|
|
|
log "running host-install (customer=${FELHOM_CUSTOMER_ID} mode=${FELHOM_MODE} hub=${HUB_URL})"
|
|
bash "$SCRIPT_TMP" "${args[@]}" "${extra[@]}"
|
|
rc=$?
|
|
cleanup_pass
|
|
|
|
if [[ $rc -eq 0 ]]; then
|
|
log "host-install SUCCESS — writing done-flag, disabling unit, scrubbing env"
|
|
install -d -m 0755 "$(dirname "$DONE_FLAG")"
|
|
: > "$DONE_FLAG"; chmod 0644 "$DONE_FLAG"
|
|
systemctl disable felhom-bootstrap.service 2>/dev/null || true
|
|
# Reduce secret-at-rest: the box is enrolled; the passphrase is no longer needed.
|
|
shred -u "$ENV_FILE" 2>/dev/null || rm -f "$ENV_FILE"
|
|
exit 0
|
|
fi
|
|
|
|
log "host-install FAILED rc=${rc} — unit will retry in 30s"
|
|
exit "$rc"
|