From ccb378060dd460c3c829ba5bbb0e901ffe76bff4 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 5 Jul 2026 22:34:06 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6 --- scripts/felhom-host-install.sh | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/scripts/felhom-host-install.sh b/scripts/felhom-host-install.sh index 00ee61e..11dda33 100644 --- a/scripts/felhom-host-install.sh +++ b/scripts/felhom-host-install.sh @@ -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 #-------------------------------------------------------------------------------