From 98bf5a434ff435a5da4155844d6f54f6d61f1dc8 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Wed, 8 Jul 2026 13:01:33 +0200 Subject: [PATCH] =?UTF-8?q?GL-6=20F4=20fix:=20v1.11.3=20=E2=80=94=20--resu?= =?UTF-8?q?me=20repopulates=20producer-step=20outputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live-found in the GL-6 drill: `should_skip X || step_X` fully skips a completed step on --resume, but token/enroll/grows produce IN-MEMORY outputs (pve token; hub host_id/api_key; volume grows) that later steps consume — agent_config writes them into the config, provision passes the grows as flags. A resume that had completed token/enroll but not agent_config wrote a config missing hub.host_id/proxmox.token (daemon crash-loop, "hub.host_id is required"); a resume past grows passed `-rootfs-grow ""` (flag parse error). step_token even had an internal resume-guard the `|| step_token` dispatch defeated. Fix: token/enroll/grows now run every pass (all idempotent — token reuses-or-rotates from the on-disk config, enroll is mint-once-reuse, grows is a pure recompute); the guard uses _state_has (no misleading SKIP log). golden's GOLDEN_VOLID is re-derived from the local archive in the resume block so provision never gets an empty -archive. Harness +GL6-F4 invariant; 27/27; shellcheck clean. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6 --- scripts/felhom-host-install.sh | 36 +++++++++++++++++++++++++---- scripts/hostinstall-mode-harness.sh | 11 +++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/scripts/felhom-host-install.sh b/scripts/felhom-host-install.sh index afddb18..7965742 100644 --- a/scripts/felhom-host-install.sh +++ b/scripts/felhom-host-install.sh @@ -42,6 +42,12 @@ # artifacts are world-readable by ruling; the hub-vouched sha256 stays the integrity root). # A configured credential is still used when present. # +# v1.11.3 (GL-6 finding F4): --resume correctness — the producer steps (token/enroll/grows) now +# run every pass so their in-memory outputs (pve token, hub host_id/api_key, volume grows) are +# repopulated for the later steps that consume them; a resumed install no longer writes a config +# missing hub.host_id/proxmox.token (daemon crash-loop) or passes an empty -rootfs-grow. golden's +# GOLDEN_VOLID is re-derived from the local archive on resume. +# # Grounding: documentation/audits/SPIKE-day0-firstboot-handshake-2026-06-26.md # # Usage: @@ -149,7 +155,7 @@ set -euo pipefail -SCRIPT_VERSION="1.11.2" # keep in sync with the header line at the top of this file +SCRIPT_VERSION="1.11.3" # keep in sync with the header line at the top of this file # Operator signing keys pinned at day-0 (GL-4; doc 04 §3 two-key model). EMPTY by default — the pin # CEREMONY is an operator step: generate the real keypairs OFFLINE, then fill these four constants @@ -1448,7 +1454,10 @@ step_preflight() { #------------------------------------------------------------------------------- step_token() { log_step "2/8 Proxmox API token" - if should_skip token && [[ -n "$PVE_TOKEN" ]]; then return 0; fi + # Fast-path only when the token is ALREADY in memory this process (avoids a double reuse-check if + # called twice). On --resume PVE_TOKEN starts empty, so this correctly re-derives it from the + # on-disk config (reuse) or a rotation — GL6-F4. Uses _state_has (no misleading SKIP log here). + if _state_has token && [[ -n "$PVE_TOKEN" ]]; then return 0; fi # Pool BEFORE the ACL: /pool/felhom must exist before apply_scoped_acl grants on it (3b). Always — # even under --skip-provision (the token exists now; a later provision-into-pool needs pool + grant). @@ -2265,14 +2274,31 @@ if $RESUME && _state_has preflight; then [[ -n "$AGENT_CONFIG" ]] || AGENT_CONFIG="/etc/felhom-agent/agent.json" # Backfill display values from the already-written config so the summary is complete. [[ -f "$AGENT_CONFIG" ]] && HOST_ID=$(python3 -c "import json;print(json.load(open('$AGENT_CONFIG')).get('hub',{}).get('host_id',''))" 2>/dev/null || true) + # GL6-F4: golden's GOLDEN_VOLID feeds provision, but the resume path skips preflight (where local + # auto-discovery sets it). If the golden step already completed, the archive is on the local + # storage from run 1 — re-derive the volid so provision doesn't get an empty -archive. (When + # golden hasn't completed yet, step_golden runs and sets it.) + if [[ -z "$GOLDEN_VOLID" ]] && _state_has golden; then + GOLDEN_VOLID=$(pvesm list "$ARCHIVE_STORAGE" --content backup 2>/dev/null | awk -v v="$GOLDEN_VMID" '$0 ~ ("vzdump-lxc-" v "-"){print $1}' | sort | tail -1) + [[ -n "$GOLDEN_VOLID" ]] && log_info " golden (resumed from local): $GOLDEN_VOLID" + fi log_skip "pre-flight (resumed)" else step_preflight fi -should_skip token || step_token -should_skip grows || step_grows -should_skip enroll || step_enroll +# GL6-F4: token/enroll/grows are PRODUCERS whose IN-MEMORY outputs (PVE_TOKEN; HOST_ID/HOST_API_KEY; +# ROOTFS_GROW/DATAVOL_GROW/SYSDATA_GROW) are consumed by later steps (agent_config writes the token + +# hub.host_id into the config; provision passes the grows as flags). They MUST run every pass — even +# on --resume — or a resumed install writes a config missing hub.host_id/proxmox.token (daemon +# crash-loops) and provision gets `-rootfs-grow ""` (flag parse error). All three are idempotent: +# token reuses the on-disk token if it still authenticates (else rotates), enroll is a mint-once- +# reuse POST (200 REUSED), grows is a pure recompute — so unconditional re-run is cheap + correct. +# (This is why they are NOT behind `should_skip … ||`; step_token's own `_state_has` fast-path still +# short-circuits the reuse check when the token is already in memory within one process.) +step_token +step_grows +step_enroll # GL-2: break-glass is gated at the CALL SITE (not inside the step) so the byo skip is auditable in # one place. byo = a host the operator does not own: root@pam is the OWNER's credential — never # reset, never vaulted. diff --git a/scripts/hostinstall-mode-harness.sh b/scripts/hostinstall-mode-harness.sh index 187ebbd..e3b457b 100644 --- a/scripts/hostinstall-mode-harness.sh +++ b/scripts/hostinstall-mode-harness.sh @@ -281,6 +281,17 @@ else verdict FAIL "GL6-ANON empty-cred anonymous-fetch fallback (warn-not-die + conditional auth)" fi +# GL6-F4 (v1.11.3): the producer steps token/enroll/grows must run UNCONDITIONALLY (not behind +# `should_skip … ||`) so --resume repopulates the in-memory outputs later steps consume; and the +# resume block must re-derive GOLDEN_VOLID from the local archive when golden already completed. +if grep -qE '^step_token$' "$SCRIPT" && grep -qE '^step_grows$' "$SCRIPT" && grep -qE '^step_enroll$' "$SCRIPT" \ + && ! grep -qE 'should_skip (token|grows|enroll) +\|\| +step_' "$SCRIPT" \ + && grep -q 'golden (resumed from local)' "$SCRIPT"; then + verdict PASS "GL6-F4 resume repopulates producer outputs (token/enroll/grows unconditional + golden re-derive)" +else + verdict FAIL "GL6-F4 resume repopulates producer outputs (token/enroll/grows unconditional + golden re-derive)" +fi + # GL4-INV: no forced/lazy unmount and no format op on the drives root — REAL invocations only # (comment lines and log_* guidance strings legitimately SAY "never umount -l/-f"). if ! grep -vE '^[[:space:]]*#|log_(warn|info|dry|error|success|skip)' "$SCRIPT" | grep -E 'umount +-(l|f)' >/dev/null \