Go-live package: Day-0 install runbook + clean-room drill + host-install v1.9.1
- documentation/runbooks/day0-install.md (NEW): operator Day-0 guide, every command drill-executed verbatim (Parts A-F incl. the mandatory one-time controller update D.1b and the OQ-3 prereq checklist). - documentation/audits/DRILL-day0-cleanroom-2026-07-03.md (NEW): nested-PVE clean-room drill evidence D0-D6, residue tables, OQ-1/2/3 answers, findings ledger (B1-B5), go/no-go for Peti (GO). - scripts/felhom-host-install.sh v1.9.1: residue-free uninstall (agent config, shared-parent unit/script/mount, mkfs wrapper, hook snippet, dnsmasq snippets; zero-residue diff proven) + post-provision guest reboot (R6) + header/version sync. - CONTEXT.md, REPORT.md, scripts/CHANGELOG.md updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,5 +1,28 @@
|
||||
# Felhom scripts — Changelog
|
||||
|
||||
## felhom-host-install.sh v1.9.1 — clean-room drill fixes: residue-free uninstall + post-provision reboot (2026-07-03)
|
||||
|
||||
Companion to the Day-0 go-live package (`documentation/runbooks/day0-install.md` +
|
||||
`documentation/audits/DRILL-day0-cleanroom-2026-07-03.md`). Every fix was found by the clean-room
|
||||
drill (virgin nested PVE 9.2.2) and re-verified there (v1.9.1 uninstall → **zero-Felhom-residue
|
||||
diff vs the pre-install baseline**; v1.9.1 install → controller up with no manual intervention).
|
||||
|
||||
- **Header/version sync** (the header said v1.8.0 while `SCRIPT_VERSION` said 1.9.0); keep-in-sync
|
||||
note on `SCRIPT_VERSION`; usage sed range follows the header (2,95).
|
||||
- **Uninstall now removes the drill-found residue (R1–R5):** the agent **config**
|
||||
(resolved from the unit's `-config` BEFORE the unit is removed — it holds the per-host hub
|
||||
api_key), the `felhom-shared-parent` unit + wants links + `/usr/local/sbin/felhom-shared-parent.sh`
|
||||
+ the `/mnt/felhom-drives` self-bind/dir, `/usr/local/sbin/felhom-mkfs-guarded`,
|
||||
`/var/lib/vz/snippets/felhom-guest-hook.sh`, and `/etc/dnsmasq.d/felhom-*.conf`
|
||||
(+ dnsmasq restart when touched). All tolerate-absent; summary lines updated (`sudo` AND
|
||||
`dnsmasq` packages are the documented package remnants).
|
||||
- **Post-provision guest reboot (R6):** the golden's `felhom-controller-bootstrap.service`
|
||||
evaluates `ConditionPathExists=/etc/felhom-bootstrap/bootstrap.json` at BOOT, but the agent
|
||||
back-half hot-plugs the mount into the running guest — on slower hardware the first boot loses
|
||||
that race deterministically and the controller never deploys. `step_provision` now reboots the
|
||||
guest once (the agent's own output says "next: reboot the guest"); `step_verify` waits bounded
|
||||
(180 s) for the controller container instead of a momentary look.
|
||||
|
||||
## felhom-host-install.sh v1.9.0 — Pool.Audit for the stale-lock reaper (A1) (2026-07-03)
|
||||
|
||||
Companion to felhom-agent v0.62.0 (audit A1: pool-membership ownership check). `PVE_PRIVS_GUEST`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# felhom-host-install.sh v1.8.0
|
||||
# felhom-host-install.sh v1.9.1
|
||||
# Day-0 host-bootstrap for a Felhom Proxmox host (operator-deploy model).
|
||||
#
|
||||
# Run by the operator on a FRESHLY-PVE-INSTALLED box (after a manual PVE install
|
||||
@@ -71,8 +71,9 @@
|
||||
#
|
||||
# Uninstall (local host teardown — no hub contact, no passphrase):
|
||||
# --uninstall cleanly revert an install: destroy the Felhom guest, remove the agent
|
||||
# (unit/sudoers/binary/state/user), the pveum role/user/token/ACL, and the
|
||||
# install state file. Guarded: refuses a non-Felhom guest (no
|
||||
# (unit/sudoers/binary/state/config/user + runtime artifacts: shared-parent
|
||||
# unit, mkfs wrapper, hook snippet, dnsmasq snippets), the pveum
|
||||
# role/user/token/ACL, and the install state file. Refuses a non-Felhom guest (no
|
||||
# /etc/felhom-bootstrap mount) and skips host-level removal if OTHER Felhom
|
||||
# guests remain (both overridable with --force). Typed vmid confirmation
|
||||
# required. Reuses --vmid (else the recorded provisioned_vmid), --force,
|
||||
@@ -104,7 +105,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_VERSION="1.9.0"
|
||||
SCRIPT_VERSION="1.9.1" # keep in sync with the header line at the top of this file
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Logging (mirrors felhom-controller/scripts/docker-setup.sh)
|
||||
@@ -211,7 +212,7 @@ ART_GOLDEN_SHA=""
|
||||
#-------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
#-------------------------------------------------------------------------------
|
||||
usage() { sed -n '2,94p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
|
||||
usage() { sed -n '2,95p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
|
||||
|
||||
run() { # simple (no pipes/redirects) mutating command
|
||||
if $DRY_RUN; then log_dry "$*"; else "$@"; fi
|
||||
@@ -552,7 +553,15 @@ run_uninstall() {
|
||||
# ── host-level removal (reverse of install) ──────────────────────────────────────────────────
|
||||
log_step "host-level removal"
|
||||
|
||||
# 4. Agent removal — service, unit(+.bak), sudoers, binary(+.bak), state dir, user. NEVER `sudo`.
|
||||
# 4. Agent removal — service, unit(+.bak), sudoers, binary(+.bak), state dir, config, user.
|
||||
# NEVER `sudo`. Resolve the agent config path BEFORE the unit is removed (mirrors preflight —
|
||||
# the unit's -config arg is the truth, else the default); the config holds the per-host hub
|
||||
# api_key and must not survive an uninstall (drill finding R1).
|
||||
local agent_cfg=""
|
||||
if systemctl cat felhom-agent >/dev/null 2>&1; then
|
||||
agent_cfg=$(systemctl cat felhom-agent 2>/dev/null | grep -oP '(?<=-config )\S+' | head -1)
|
||||
fi
|
||||
[[ -n "$agent_cfg" ]] || agent_cfg="/etc/felhom-agent/agent.json"
|
||||
if systemctl list-unit-files felhom-agent.service >/dev/null 2>&1; then
|
||||
systemctl is-active --quiet felhom-agent 2>/dev/null && run systemctl stop felhom-agent
|
||||
systemctl is-enabled --quiet felhom-agent 2>/dev/null && run systemctl disable felhom-agent
|
||||
@@ -569,6 +578,36 @@ run_uninstall() {
|
||||
if [[ -d "$AGENT_STATE_DIR" ]]; then run rm -rf "$AGENT_STATE_DIR"; else log_skip " $AGENT_STATE_DIR already absent"; fi
|
||||
if id "$AGENT_USER" >/dev/null 2>&1; then run userdel "$AGENT_USER"; else log_skip " service user $AGENT_USER already absent"; fi
|
||||
|
||||
# 4b. Agent config (pve token + per-host hub api_key — secrets must not survive; drill R1).
|
||||
if [[ -f "$agent_cfg" ]]; then run rm -f "$agent_cfg"; else log_skip " $agent_cfg already absent"; fi
|
||||
run rmdir "$(dirname "$agent_cfg")" 2>/dev/null || true
|
||||
|
||||
# 4c. Shared-parent unit + wrapper + /mnt/felhom-drives (agent-installed at runtime; drill R2).
|
||||
# Stop/disable, remove unit + script, unbind + remove the (empty) parent dir. Tolerate-absent.
|
||||
if systemctl list-unit-files felhom-shared-parent.service 2>/dev/null | grep -q felhom-shared-parent; then
|
||||
systemctl is-active --quiet felhom-shared-parent 2>/dev/null && run systemctl stop felhom-shared-parent
|
||||
systemctl is-enabled --quiet felhom-shared-parent 2>/dev/null && run systemctl disable felhom-shared-parent
|
||||
else
|
||||
log_skip " felhom-shared-parent unit not loaded — skip stop/disable"
|
||||
fi
|
||||
if [[ -f /etc/systemd/system/felhom-shared-parent.service ]]; then run rm -f /etc/systemd/system/felhom-shared-parent.service; else log_skip " felhom-shared-parent.service already absent"; fi
|
||||
if [[ -f /usr/local/sbin/felhom-shared-parent.sh ]]; then run rm -f /usr/local/sbin/felhom-shared-parent.sh; fi
|
||||
run systemctl daemon-reload
|
||||
if mountpoint -q /mnt/felhom-drives 2>/dev/null; then run umount /mnt/felhom-drives; fi
|
||||
if [[ -d /mnt/felhom-drives ]]; then run rmdir /mnt/felhom-drives 2>/dev/null || true; fi
|
||||
|
||||
# 4d. Guarded-mkfs wrapper, guest-hook snippet, lan-resolver dnsmasq snippets (drill R3-R5).
|
||||
if [[ -f /usr/local/sbin/felhom-mkfs-guarded ]]; then run rm -f /usr/local/sbin/felhom-mkfs-guarded; else log_skip " felhom-mkfs-guarded already absent"; fi
|
||||
if [[ -f /var/lib/vz/snippets/felhom-guest-hook.sh ]]; then run rm -f /var/lib/vz/snippets/felhom-guest-hook.sh; fi
|
||||
local dconf _dnsmasq_touched=false
|
||||
for dconf in /etc/dnsmasq.d/felhom-*.conf; do
|
||||
[[ -e "$dconf" ]] || continue
|
||||
run rm -f "$dconf"; _dnsmasq_touched=true
|
||||
done
|
||||
if $_dnsmasq_touched && systemctl is-active --quiet dnsmasq 2>/dev/null; then
|
||||
run systemctl restart dnsmasq || true
|
||||
fi
|
||||
|
||||
# 5. pveum removal (presence-checked; tolerate-absent; roles deleted only after their grants).
|
||||
# Remove the 3-role scoped grants+roles (3b) AND the pre-3b single-role broad grant if present —
|
||||
# both tolerate-absent so --uninstall works on a box of either shape.
|
||||
@@ -624,9 +663,9 @@ run_uninstall() {
|
||||
|
||||
# 8. Summary.
|
||||
echo ""
|
||||
log_success "UNINSTALL complete — removed: guest $vmid, the felhom-agent (unit/sudoers/binary/state/user), the pveum role/user/token/ACL,$( $pool_removed && printf ' the %s pool,' "$PVE_POOL") and $STATE_FILE."
|
||||
log_success "UNINSTALL complete — removed: guest $vmid, the felhom-agent (unit/sudoers/binary/state/config/user + shared-parent/mkfs-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL,$( $pool_removed && printf ' the %s pool,' "$PVE_POOL") and $STATE_FILE."
|
||||
if $REMOVE_GOLDEN; then log_info " golden vzdump: removed."; else log_info " golden vzdump: left in place (--remove-golden to remove)."; fi
|
||||
log_info " NOTE: the 'sudo' package was left installed (system package); the host record still exists in the hub — remove it there if desired."
|
||||
log_info " NOTE: the 'sudo' and 'dnsmasq' packages were left installed (system packages); the host record still exists in the hub — remove it there if desired."
|
||||
$DRY_RUN && log_warn " DRY-RUN: nothing above was actually executed."
|
||||
return 0
|
||||
}
|
||||
@@ -1439,6 +1478,14 @@ step_provision() {
|
||||
# Record the provisioned vmid so a later --uninstall resolves the target automatically + safely.
|
||||
_state_put provisioned_vmid "$VMID"
|
||||
# (No pool_add_guest here — the agent's restore --pool already made the guest a member.)
|
||||
# Reboot the guest ONCE: the golden's controller-bootstrap unit evaluates its
|
||||
# ConditionPathExists=/etc/felhom-bootstrap/bootstrap.json at BOOT, and the back-half attaches
|
||||
# the bootstrap mount to the ALREADY-RUNNING guest — without a reboot the unit stays skipped and
|
||||
# the controller never deploys (drill finding R6; the agent's own provision output says
|
||||
# "next: reboot the guest"). On fast hosts the first boot sometimes wins the race — the reboot
|
||||
# is idempotent either way (the unit no-ops when the controller already runs).
|
||||
log_info " rebooting guest $VMID so the baked controller-bootstrap unit picks up the mount"
|
||||
run pct reboot "$VMID"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
@@ -1452,9 +1499,14 @@ step_verify() {
|
||||
[[ "$st" == "running" ]] && log_success " pct status: running" || { log_error " pct status: $st"; ok=false; }
|
||||
if pct config "$VMID" 2>/dev/null | grep -q '^onboot: 1'; then log_success " onboot: 1"; else log_error " onboot NOT 1"; ok=false; fi
|
||||
pct config "$VMID" 2>/dev/null | grep -E '^(rootfs|mp0|mp1|mp8):' | sed 's/^/ /'
|
||||
# controller container healthy in-guest
|
||||
local cstat; cstat=$(pct exec "$VMID" -- docker ps --filter name=felhom-controller --format '{{.Status}}' 2>/dev/null | head -1)
|
||||
if [[ -n "$cstat" ]]; then log_success " controller: $cstat"; else log_warn " controller container not visible yet (may still be starting)"; fi
|
||||
# controller container healthy in-guest — bounded wait (the post-provision reboot + docker start
|
||||
# take a while, especially on modest hardware; drill R6 re-verify)
|
||||
local cstat="" _waited=0
|
||||
while [[ -z "$cstat" && $_waited -lt 180 ]]; do
|
||||
cstat=$(pct exec "$VMID" -- docker ps --filter name=felhom-controller --format '{{.Status}}' 2>/dev/null | head -1)
|
||||
[[ -n "$cstat" ]] || { sleep 5; _waited=$((_waited+5)); }
|
||||
done
|
||||
if [[ -n "$cstat" ]]; then log_success " controller: $cstat (after ~${_waited}s)"; else log_warn " controller container not visible after ${_waited}s — check 'pct exec $VMID -- journalctl -u felhom-controller-bootstrap'"; fi
|
||||
local cver; cver=$(pct exec "$VMID" -- docker ps --filter name=felhom-controller --format '{{.Image}}' 2>/dev/null | head -1)
|
||||
[[ -n "$cver" ]] && log_info " controller image: $cver"
|
||||
# tunnel
|
||||
|
||||
Reference in New Issue
Block a user