configs: build-golden.sh v2.0.0 — mandatory controller tag (B5) + bootstrap .path unit (B1)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-03 18:21:21 +02:00
parent c9f963d9f4
commit ceca355493
2 changed files with 68 additions and 5 deletions
+28
View File
@@ -1,3 +1,31 @@
## configs: build-golden.sh v2.0.0 — mandatory controller tag + bootstrap .path unit (B5 + B1) (2026-07-03)
Golden-bake script only — **no agent code, no binary, no agent version bump** (docs/config
precedent). Resolves drill findings B5 + B1
(`felhom.eu/documentation/audits/DRILL-day0-cleanroom-2026-07-03.md` §9) and closes the stale
backlog note `FOLLOWUP-golden-default-controller-tag.md`.
- **B5 — `CONTROLLER_IMAGE` (arg 6) is now MANDATORY** — the hand-bumped default rotted twice
(0.43.0 → 0.85.1 → stale again; 0.85.1 predates the v0.86.0 floor-honoring code, which is why
every fresh install needed the manual D.1b update). No 6th arg → die with usage (red-proofed:
exits 1 before any `pct` op). Auto-resolving "latest" was rejected — it could bake an unvouched
tag.
- **B1 — baked `felhom-controller-bootstrap.path` unit** (`PathExists=/etc/felhom-bootstrap/
bootstrap.json`, enabled next to the service): the service's `ConditionPathExists` is evaluated
only at boot, but the agent back-half hot-plugs the bootstrap mount into the already-running
guest — the path unit starts the service when the file APPEARS, so the controller deploys with
NO reboot (isolated systemd proof + full Day-0 proof on the clean-room drill VM; the installer's
v1.9.1 post-provision reboot is now a redundant belt, kept). `RemainAfterExit=yes` on the service
prevents re-trigger loops; the service itself stays unchanged.
- `GOLDEN_SCRIPT_VERSION` (2.0.0) + a `[golden]` provenance line (script version + baked controller
tag) now open every bake transcript.
- Baked + published with controller **0.98.3**: golden `0.98.3` /
sha256 `b9a02ef1b6f02b9b58babc4c6aad9cf6c053ebdfba116c78c8e7830de757fd01` (Gitea generic package,
201 + sha round-trip). Clean-room validation: bake integrity (mp0+mp1 included), isolated
hot-plug proof, full local-golden Day-0 install (first boot = 0.98.3, self-update reports
up-to-date, app deploy OK) — evidence:
`felhom.eu/documentation/audits/DRILL-golden-098-2026-07-03.md`.
## v0.63.0 — B3 + B2: fresh-install fixes — token reload-on-miss + guesthook snippets dir (2026-07-03)
The two agent-side gaps the Day-0 clean-room drill surfaced
+40 -5
View File
@@ -20,7 +20,9 @@
# config mount (/etc/felhom-bootstrap/bootstrap.json) — no docker login/pull at deploy. Refreshing
# the golden bumps the controller baseline; controller self-update covers in-between drift.
#
# Usage: build-golden.sh [VMID] [TEMPLATE_VOLID] [ROOTFS_STORAGE] [ARCHIVE_STORAGE] [BRIDGE] [CONTROLLER_IMAGE]
# Usage: build-golden.sh [VMID] [TEMPLATE_VOLID] [ROOTFS_STORAGE] [ARCHIVE_STORAGE] [BRIDGE] CONTROLLER_IMAGE
# CONTROLLER_IMAGE is REQUIRED (no default) — pass the released controller tag explicitly,
# e.g. gitea.dooplex.hu/admin/felhom-controller:0.98.3.
# Build-time registry login for the controller pull (used ONCE inside the build guest, then logged
# out — never baked): set REGISTRY_USER + REGISTRY_TOKEN in the environment.
#
@@ -35,15 +37,27 @@
# so the archive would carry NO images and provisioned guests would boot imageless.
set -euo pipefail
# Script provenance — logged into every bake transcript next to the baked controller tag, so an
# archive can always be traced to the script that produced it. Bump on any behavior change.
GOLDEN_SCRIPT_VERSION="2.0.0"
VMID="${1:-9100}"
TEMPLATE="${2:-local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst}"
ROOTFS_STORAGE="${3:-local-lvm}"
ARCHIVE_STORAGE="${4:-local}"
BRIDGE="${5:-vmbr0}"
# Default to the CURRENT controller so an argument-less build is never wildly stale. ALWAYS pass the
# controller version explicitly at each rebuild (this default only bounds the worst case); a future
# `make golden` that resolves the latest pullable tag would remove the need for a hand-bumped default.
CONTROLLER_IMAGE="${6:-gitea.dooplex.hu/admin/felhom-controller:0.85.1}"
# CONTROLLER_IMAGE is MANDATORY — no default. The hand-bumped default rotted twice (0.43.0 →
# 0.85.1 → stale again): each time, a fresh provision would have booted a pre-floor controller
# needing a manual install-day update (drill finding B5). A required argument cannot rot, and
# auto-resolving "latest" could bake a tag the hub manifest never vouched — so the caller states
# the released controller tag explicitly at every rebuild.
CONTROLLER_IMAGE="${6:-}"
if [ -z "$CONTROLLER_IMAGE" ]; then
echo "[golden] FATAL: CONTROLLER_IMAGE (argument 6) is required — pass the released controller tag explicitly." >&2
echo "Usage: build-golden.sh [VMID] [TEMPLATE_VOLID] [ROOTFS_STORAGE] [ARCHIVE_STORAGE] [BRIDGE] CONTROLLER_IMAGE" >&2
echo " e.g.: build-golden.sh 9100 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst local-lvm local vmbr0 gitea.dooplex.hu/admin/felhom-controller:0.98.3" >&2
exit 1
fi
REGISTRY_HOST="${CONTROLLER_IMAGE%%/*}"
# OS rootfs size (GiB) and the golden's Docker-data volume size (GiB). Keep GOLDEN_DOCKER_GB just
# large enough for the baked images + headroom; provision grows it to the per-customer target.
@@ -56,6 +70,7 @@ GOLDEN_DOCKER_GB="${GOLDEN_DOCKER_GB:-16}"
# storage-split B3) and the user-data area would silently fall out of PBS coverage.
GOLDEN_SYSDATA_GB="${GOLDEN_SYSDATA_GB:-8}"
echo "[golden] build-golden.sh v${GOLDEN_SCRIPT_VERSION} — baking controller ${CONTROLLER_IMAGE}"
echo "[golden] creating build LXC $VMID (nesting=1,keyctl=1, unprivileged; rootfs ${OS_SIZE_GB}G + Docker-data ${GOLDEN_DOCKER_GB}G @ /var/lib/docker + user-data ${GOLDEN_SYSDATA_GB}G @ /mnt/sys_drive, both backup=1) …"
pct create "$VMID" "$TEMPLATE" \
--hostname felhom-golden --unprivileged 1 \
@@ -217,6 +232,26 @@ WantedBy=multi-user.target
UNIT
systemctl enable felhom-controller-bootstrap.service'
# B1 (DRILL-day0-cleanroom-2026-07-03 R6/B1): the service's ConditionPathExists is evaluated only
# when the service is STARTED — i.e. at boot via multi-user.target — but the agent back-half
# hot-plugs the bootstrap mount into the ALREADY-RUNNING guest, so on a provision the boot-time
# start races the mount and loses on slow hardware. This path unit watches for bootstrap.json and
# starts the service when it APPEARS — covering the provision hot-plug without a reboot. The boot
# case is still served by the enabled service itself; RemainAfterExit=yes on the service means the
# path unit cannot re-trigger it in a loop once it has run.
echo "[golden] baking the controller-bootstrap PATH unit (starts the service on bootstrap-mount hot-plug — B1) …"
pct exec "$VMID" -- bash -c 'cat > /etc/systemd/system/felhom-controller-bootstrap.path <<UNIT
[Unit]
Description=Watch for the agent-populated bootstrap config; start the controller bootstrap when it appears
[Path]
PathExists=/etc/felhom-bootstrap/bootstrap.json
[Install]
WantedBy=multi-user.target
UNIT
systemctl enable felhom-controller-bootstrap.path'
echo "[golden] baking the first-boot SSH host-key regeneration unit (F3) …"
pct exec "$VMID" -- bash -c 'cat > /etc/systemd/system/felhom-regen-hostkeys.service <<UNIT
[Unit]