ISO train v1.25.0 code: OOB belt default appliance leg (F9) + apt no-subscription (Part2) + R-63 Lat2 console font + R-71 golden>=floor build gate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
This commit is contained in:
@@ -182,7 +182,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_VERSION="1.17.0" # the SINGLE version source (F-1): -h, the run banners, and the hub
|
||||
SCRIPT_VERSION="1.18.0" # the SINGLE version source (F-1): -h, the run banners, and the hub
|
||||
# Setup-tab copy (hub internal/web/configs.go hostInstallVersion —
|
||||
# scripts/hostinstall_gates.py asserts the two stay equal) all follow it.
|
||||
# 1.16.0: the FELHOM_ESCROW sudoers alias (controller-driven escrow
|
||||
@@ -249,7 +249,10 @@ REMOVE_GOLDEN=false # --remove-golden: also delete the golden vzdump during
|
||||
ADOPT_POOL=false # --adopt-pool: retrofit an EXISTING Felhom guest into the felhom pool (non-destructive)
|
||||
RESCOPE_ACL=false # --rescope-acl: migrate an existing install from the broad-/ token to the scoped ACL
|
||||
ROTATE_RECOVERY=false # --rotate-recovery: regenerate + re-vault the break-glass root@pam password (TASK G1)
|
||||
ENABLE_OOB=false # --enable-oob: install the dedicated felhom-sshd OOB instance + belt (TASK H1)
|
||||
ENABLE_OOB=false # the OOB belt (TASK H1). DEFAULT-ON for appliance mode since v1.25.0 (F9 ruling
|
||||
# 2026-07-23: an operator-owned box must be reachable OOB) — see the appliance
|
||||
# default below; --enable-oob forces it, --no-oob opts an appliance box out.
|
||||
NO_OOB=false # --no-oob: opt an APPLIANCE install out of the default belt (byo is off already)
|
||||
|
||||
# --- Gitea (artifact source) + agent install model (BUNDLE slice) ---
|
||||
GITEA_BASE="https://gitea.dooplex.hu"
|
||||
@@ -1077,6 +1080,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--rescope-acl) RESCOPE_ACL=true; shift ;;
|
||||
--rotate-recovery) ROTATE_RECOVERY=true; shift ;;
|
||||
--enable-oob) ENABLE_OOB=true; shift ;;
|
||||
--no-oob) NO_OOB=true; shift ;;
|
||||
--acl-storages) read -ra PVE_STORAGES <<< "$2"; shift 2 ;;
|
||||
--preflight-only) PREFLIGHT_ONLY=true; shift ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
@@ -1224,6 +1228,16 @@ if [[ "$MODE" == "byo" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# H1 / F9 (ruling 2026-07-23 "install everywhere"): the OOB belt is now a DEFAULT appliance leg — a box
|
||||
# the operator owns must have an out-of-band way in (the 07-22 power-outage audit found the belt on
|
||||
# NEITHER fleet box because the universal ISO never passed --enable-oob). Appliance installs it unless
|
||||
# --no-oob; byo stays refuse-by-default above (owner-consented, never automatic). The belt is
|
||||
# lockout-SAFE if the fleet has no operator peer yet: empty sets + no felhom-sshd key = no access
|
||||
# granted and nothing on :22 touched, until the operator OOB peer is registered hub-side.
|
||||
if [[ "$MODE" == "appliance" ]] && ! $NO_OOB; then
|
||||
ENABLE_OOB=true
|
||||
fi
|
||||
|
||||
# GL-4: resolve the operator signing keys — script constants by default; --operator-pubkey-file
|
||||
# OVERRIDES them (C5). Validated HERE (before the passphrase prompt, before any step) so a bad key
|
||||
# file dies fast and the harness can exercise it on a non-PVE machine. File format: one key per
|
||||
@@ -1740,8 +1754,58 @@ step_break_glass() {
|
||||
# Gitea (git token from controller.yaml), VERIFIES its sha256 against the hub manifest, then installs
|
||||
# the non-root felhom-agent user + binary + sudoers + unit. The SERVICE is started in step 6 (after the
|
||||
# config is written) — here we only install + daemon-reload + enable.
|
||||
# Part 2 (v1.25.0): a fresh appliance install boots on the stock PVE ENTERPRISE apt repo with no
|
||||
# subscription key, so every `apt-get update` 401s and the box can never pull security updates. Switch
|
||||
# the Proxmox-managed repos to no-subscription — idempotent, APPLIANCE-ONLY, and scoped to the repos
|
||||
# Proxmox itself ships (pve-enterprise + ceph enterprise, deb822 `.sources` on trixie/PVE9, or the
|
||||
# legacy `.list`). The customer's own sources are never touched. No upgrades are run — repo alignment
|
||||
# only. Runs BEFORE the first apt operation (the sudo/age installs below) so they don't hit the 401.
|
||||
align_apt_repos() {
|
||||
[[ "$MODE" == "appliance" ]] || { log_skip " apt repo alignment skipped (byo — the owner manages repos)"; return 0; }
|
||||
if $DRY_RUN; then log_dry "disable pve/ceph enterprise .sources (Enabled: no) + legacy .list; add pve-no-subscription.sources; apt-get update"; return 0; fi
|
||||
local d=/etc/apt/sources.list.d changed=0 suite="" keyring="" f
|
||||
if [[ -f "$d/pve-enterprise.sources" ]]; then
|
||||
suite=$(awk -F': *' '/^Suites:/{print $2; exit}' "$d/pve-enterprise.sources")
|
||||
keyring=$(awk -F': *' '/^Signed-By:/{print $2; exit}' "$d/pve-enterprise.sources")
|
||||
fi
|
||||
[[ -n "$suite" ]] || suite="trixie"
|
||||
[[ -n "$keyring" ]] || keyring="/usr/share/keyrings/proxmox-archive-keyring.gpg"
|
||||
# 1a. disable enterprise deb822 .sources (append `Enabled: no`, idempotent)
|
||||
for f in "$d/pve-enterprise.sources" "$d/ceph.sources"; do
|
||||
[[ -f "$f" ]] || continue
|
||||
grep -qiE '^Enabled:[[:space:]]*no' "$f" && continue
|
||||
sed -i '/^[Ee]nabled:/d' "$f"; printf 'Enabled: no\n' >> "$f"; changed=1
|
||||
log_info " disabled $(basename "$f") (Enabled: no)"
|
||||
done
|
||||
# 1b. legacy .list enterprise line → commented
|
||||
for f in "$d/pve-enterprise.list"; do
|
||||
[[ -f "$f" ]] || continue
|
||||
grep -qE '^[[:space:]]*deb[[:space:]].*enterprise\.proxmox\.com' "$f" || continue
|
||||
sed -i 's|^[[:space:]]*\(deb[[:space:]].*enterprise\.proxmox\.com.*\)$|# \1 # felhom: no-subscription|' "$f"; changed=1
|
||||
log_info " commented enterprise line in $(basename "$f")"
|
||||
done
|
||||
# 2. add pve-no-subscription if it is not already present in any list/sources
|
||||
if ! grep -rqsE 'download\.proxmox\.com/debian/pve([[:space:]/]|$)' "$d"/*.sources "$d"/*.list 2>/dev/null; then
|
||||
cat > "$d/pve-no-subscription.sources" <<SRC
|
||||
Types: deb
|
||||
URIs: http://download.proxmox.com/debian/pve
|
||||
Suites: $suite
|
||||
Components: pve-no-subscription
|
||||
Signed-By: $keyring
|
||||
SRC
|
||||
changed=1; log_info " added pve-no-subscription.sources (suite=$suite)"
|
||||
fi
|
||||
# 3. refresh — must be clean now (a stale enterprise line would 401)
|
||||
if apt-get update -q >/dev/null 2>&1; then
|
||||
log_success " apt repos aligned to no-subscription ($([[ $changed == 1 ]] && echo changed || echo 'already aligned'); apt-get update OK)"
|
||||
else
|
||||
log_warn " apt-get update non-zero after repo alignment — inspect $d (non-fatal to the install)"
|
||||
fi
|
||||
}
|
||||
|
||||
step_agent_install() {
|
||||
log_step "5/8 agent install (fetch + verify + install)"
|
||||
align_apt_repos # Part 2: switch enterprise→no-subscription before any apt-get (appliance only)
|
||||
|
||||
# Manifest + git fetch credential (both passphrase / config-retrieve — NO new credential).
|
||||
resolve_artifacts
|
||||
@@ -1984,7 +2048,7 @@ step_agent_install() {
|
||||
# incident G1 closes (a second sshd's `RuntimeDirectory=sshd` removed the shared /run/sshd).
|
||||
install_mgmt_watchdog
|
||||
|
||||
# H1: dedicated felhom-sshd OOB instance + static belt (gated by --enable-oob).
|
||||
# H1: dedicated felhom-sshd OOB instance + static belt (appliance default since v1.25.0; --no-oob opts out).
|
||||
install_oob
|
||||
|
||||
_state_mark agent_install
|
||||
@@ -2038,7 +2102,7 @@ install_mgmt_watchdog() {
|
||||
# oob.enabled=true. Gated by --enable-oob. Non-fatal if the agent repo predates the artifacts.
|
||||
# The felhom-sshd unit is RuntimeDirectory-guarded (the G1 incident cause) before install.
|
||||
install_oob() {
|
||||
$ENABLE_OOB || { log_skip " OOB (felhom-sshd) not requested (--enable-oob) — skipping"; return 0; }
|
||||
$ENABLE_OOB || { log_skip " OOB (felhom-sshd) off (byo, or appliance --no-oob) — skipping"; return 0; }
|
||||
if $DRY_RUN; then
|
||||
log_dry "mkdir /etc/felhom-sshd{,/authorized_keys} ; ssh-keygen host key ; useradd felhom-op ; install felhom-op sudoers"
|
||||
log_dry "fetch felhom-sshd.service (RuntimeDirectory-guarded) + felhom-oob.nft + felhom-oob-nft.service ; enable"
|
||||
@@ -2125,7 +2189,7 @@ step_agent_config() {
|
||||
NODE="$NODE" FP="$fp" HUB_URL="$HUB_URL" HOST_ID="$HOST_ID" BRIDGE_ADDR="$BRIDGE_ADDR" \
|
||||
OP_KEY_ID="$RESOLVED_OP_ID" OP_KEY_LINE="$RESOLVED_OP_LINE" \
|
||||
REC_KEY_ID="$RESOLVED_REC_ID" REC_KEY_LINE="$RESOLVED_REC_LINE" \
|
||||
PRESERVE_FROM="$PRESERVE_FROM" INSTALL_MODE="$MODE" OUT="$AGENT_CONFIG" python3 <<'PY'
|
||||
PRESERVE_FROM="$PRESERVE_FROM" INSTALL_MODE="$MODE" ENABLE_OOB="$ENABLE_OOB" OUT="$AGENT_CONFIG" python3 <<'PY'
|
||||
import json, os, sys
|
||||
out = os.environ['OUT']
|
||||
base = {}
|
||||
@@ -2140,6 +2204,12 @@ base.setdefault('log_level','info')
|
||||
# else, including a stale preserved value) leaves it CHECK-only. Written explicitly so a byo box can
|
||||
# never inherit an appliance self-heal from a --preserve-from config.
|
||||
base['deployment_mode'] = 'appliance' if os.environ.get('INSTALL_MODE') == 'appliance' else 'byo'
|
||||
# H1 / F9 (v1.25.0): when the belt is installed (appliance default, or explicit --enable-oob), turn the
|
||||
# agent's OOB loop on so it claims a port, renders felhom-sshd's config, and fills the belt sets each
|
||||
# tick. Without this the static units sit inert (the pre-v1.25.0 state that left the belt unusable). byo
|
||||
# never reaches here with ENABLE_OOB true (refused above) and the byo asserts below re-check oob is off.
|
||||
if os.environ.get('ENABLE_OOB') == 'true':
|
||||
base.setdefault('oob', {})['enabled'] = True
|
||||
# privileged.mode = "sudo": the canonical unit runs the agent as the NON-root felhom-agent user, so
|
||||
# every host-root op goes through `sudo -n` against /etc/sudoers.d/felhom-agent. ("direct" was the old
|
||||
# dev/CI shortcut for a root agent.) Force the mode authoritative (a stale preserved "direct" config
|
||||
|
||||
Reference in New Issue
Block a user