feat(install): --enable-oob installs felhom-sshd + static belt (H1 Part 5)

install_oob: /etc/felhom-sshd tree + dedicated host key + felhom-op user + scoped
sudoers + RuntimeDirectory-guarded felhom-sshd.service (enable-not-start; agent
renders config) + static felhom_oob nft table + boot loader. Uninstall removes all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-05 22:34:06 +02:00
parent f8fc09e5cc
commit ccb378060d
+78
View File
@@ -154,6 +154,7 @@ 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)
# --- Gitea (artifact source) + agent install model (BUNDLE slice) ---
GITEA_BASE="https://gitea.dooplex.hu"
@@ -600,6 +601,27 @@ run_uninstall() {
if [[ -e "$wda" ]]; then run rm -f "$wda"; fi
done
# 4b3. OOB felhom-sshd instance + belt (TASK H1). Stop/disable felhom-sshd + the belt loader,
# remove units + config tree + belt table + felhom-op user/sudoers. NEVER touch the stock
# sshd/:22/etc/ssh. Tolerate-absent throughout.
if systemctl list-unit-files felhom-sshd.service >/dev/null 2>&1; then
systemctl is-active --quiet felhom-sshd 2>/dev/null && run systemctl stop felhom-sshd
systemctl is-enabled --quiet felhom-sshd 2>/dev/null && run systemctl disable felhom-sshd
fi
if systemctl list-unit-files felhom-oob-nft.service >/dev/null 2>&1; then
systemctl is-active --quiet felhom-oob-nft 2>/dev/null && run systemctl stop felhom-oob-nft
systemctl is-enabled --quiet felhom-oob-nft 2>/dev/null && run systemctl disable felhom-oob-nft
fi
run systemctl reset-failed felhom-sshd felhom-oob-nft 2>/dev/null || true
nft list table inet felhom_oob >/dev/null 2>&1 && run nft delete table inet felhom_oob
local oa
for oa in /etc/systemd/system/felhom-sshd.service /etc/systemd/system/felhom-oob-nft.service \
/etc/felhom-oob.nft /etc/sudoers.d/felhom-op /run/felhom-sshd.pid /run/felhom-sshd.healed; do
if [[ -e "$oa" ]]; then run rm -f "$oa"; fi
done
if [[ -d /etc/felhom-sshd ]]; then run rm -rf /etc/felhom-sshd; fi
if id felhom-op >/dev/null 2>&1; then run userdel -r felhom-op 2>/dev/null || run userdel felhom-op; fi
# 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
@@ -801,6 +823,7 @@ while [[ $# -gt 0 ]]; do
--adopt-pool) ADOPT_POOL=true; shift ;;
--rescope-acl) RESCOPE_ACL=true; shift ;;
--rotate-recovery) ROTATE_RECOVERY=true; shift ;;
--enable-oob) ENABLE_OOB=true; shift ;;
--acl-storages) read -ra PVE_STORAGES <<< "$2"; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
--resume) RESUME=true; shift ;;
@@ -1410,6 +1433,9 @@ 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).
install_oob
_state_mark agent_install
}
@@ -1455,6 +1481,58 @@ install_mgmt_watchdog() {
log_success " installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)"
}
# install_oob installs the dedicated felhom-sshd OOB instance + the static nft belt (TASK H1). The
# STATIC parts land here (unit, host key, felhom-op user + sudoers, belt table + loader); the agent
# reconciles the DYNAMIC parts (port claim, config render, authorized_keys, belt set elements) once
# 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; }
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"
return 0
fi
install -d -o root -g root -m 0755 /etc/felhom-sshd /etc/felhom-sshd/authorized_keys
# dedicated host key (stable across reloads) — generate once.
if [[ ! -f /etc/felhom-sshd/ssh_host_ed25519_key ]]; then
ssh-keygen -t ed25519 -N "" -f /etc/felhom-sshd/ssh_host_ed25519_key -C felhom-sshd-hostkey -q
chmod 600 /etc/felhom-sshd/ssh_host_ed25519_key
fi
# operator login user (no ambient privilege; escalates only via the felhom-op sudoers verbs).
id felhom-op >/dev/null 2>&1 || useradd --create-home --shell /bin/bash felhom-op
# felhom-op scoped sudoers (visudo-validated before install).
local optmp; optmp=$(mktemp -t felhom-op-sudoers.XXXXXX)
if fetch_raw "configs/felhom-op.sudoers" "$optmp" 2>/dev/null; then
"$VISUDO" -cf "$optmp" >/dev/null || { rm -f "$optmp"; die "fetched felhom-op sudoers failed visudo -cf"; }
install -m 0440 -o root -g root "$optmp" /etc/sudoers.d/felhom-op
fi
rm -f "$optmp"
# felhom-sshd unit — RuntimeDirectory guard (the G1 incident cause) BEFORE install.
local sutmp; sutmp=$(mktemp -t felhom-sshd-unit.XXXXXX)
if ! fetch_raw "configs/felhom-sshd.service" "$sutmp" 2>/dev/null; then
log_skip " felhom-sshd.service not in the agent repo yet — OOB stays unconfigured"
rm -f "$sutmp"; return 0
fi
if grep -qiE '^[[:space:]]*RuntimeDirectory[[:space:]]*=' "$sutmp"; then
rm -f "$sutmp"; die "felhom-sshd unit declares RuntimeDirectory= — the SPIKE-felhom-sshd §8 incident cause; refusing"
fi
install -m 0644 -o root -g root "$sutmp" /etc/systemd/system/felhom-sshd.service
rm -f "$sutmp"
# static belt table + boot loader.
local ntmp utmp; ntmp=$(mktemp -t felhom-oob-nft.XXXXXX); utmp=$(mktemp -t felhom-oob-unit.XXXXXX)
fetch_raw "configs/felhom-oob.nft" "$ntmp"
fetch_raw "configs/felhom-oob-nft.service" "$utmp"
nft -c -f "$ntmp" >/dev/null 2>&1 || { rm -f "$ntmp" "$utmp"; die "felhom-oob.nft failed nft -c — refusing"; }
install -m 0644 -o root -g root "$ntmp" /etc/felhom-oob.nft
install -m 0644 -o root -g root "$utmp" /etc/systemd/system/felhom-oob-nft.service
rm -f "$ntmp" "$utmp"
systemctl daemon-reload
systemctl enable --now felhom-oob-nft.service >/dev/null 2>&1 || true # load the static belt now
systemctl enable felhom-sshd >/dev/null 2>&1 || true # NOT start — the agent renders the config first
log_success " installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)"
}
#-------------------------------------------------------------------------------
# STEP 6 — write agent config + ensure service healthy
#-------------------------------------------------------------------------------