feat(host-install)+docs: D1 — install self-update artifacts on day-0 + architecture §11 self-update (implemented)

felhom-host-install.sh installs felhom-selfupdate-guarded (sh -n), the rollback
unit + the [Unit] start-limit drop-in (daemon-reload) so day-0 boxes get
operator-signed self-update from birth; non-fatal on pre-D1 agent repos.
03-host-agent.md §11 updated to the shipped mechanism (signed op, A/B wrapper,
OnFailure rollback, tuned start-limit backstop).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-05 15:36:15 +02:00
parent 582917d12d
commit b6bad953b5
2 changed files with 56 additions and 7 deletions
+39
View File
@@ -1264,6 +1264,19 @@ step_agent_install() {
log_success " installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)"
fi
# Self-update guarded wrapper (TASK D1) — the ONLY binary-swap path the sudoers permits. Install
# it BEFORE the sudoers (which allowlists it), 0755 root:root under /usr/local/sbin. sh -n first.
if $DRY_RUN; then
log_dry "fetch configs/felhom-selfupdate-guarded ; sh -n ; install 0755 -> /usr/local/sbin/felhom-selfupdate-guarded"
else
local sutmp; sutmp=$(mktemp -t felhom-selfupd.XXXXXX)
fetch_raw "configs/felhom-selfupdate-guarded" "$sutmp"
sh -n "$sutmp" || { rm -f "$sutmp"; die "fetched felhom-selfupdate-guarded failed sh -n — refusing to install"; }
install -m 0755 -o root -g root "$sutmp" /usr/local/sbin/felhom-selfupdate-guarded
rm -f "$sutmp"
log_success " installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)"
fi
# Sudoers — fetch the canonical file, validate with visudo -cf BEFORE installing (0440 root:root).
if $DRY_RUN; then
log_dry "fetch configs/felhom-agent.sudoers ; visudo -cf ; install 0440 -> $AGENT_SUDOERS"
@@ -1292,6 +1305,32 @@ step_agent_install() {
systemctl enable felhom-agent >/dev/null 2>&1 || true
log_success " installed $AGENT_UNIT + enabled (started in step 6 after config)"
fi
# Self-update rollback unit + start-limit drop-in (TASK D1). The drop-in gives felhom-agent the
# tuned start-limit (SPIKE-agent-selfupdate: [Unit] StartLimitIntervalSec=120 + Burst=4 → a
# crash-looping update reaches terminal `failed` in ~20s instead of looping forever) and the
# OnFailure= that auto-reverts. Both are idempotent installs (+ daemon-reload). Non-fatal on a
# box whose agent repo predates them (the raw fetch would 404) — self-update just stays manual.
if $DRY_RUN; then
log_dry "fetch configs/felhom-agent-rollback.service -> /etc/systemd/system/ ; fetch configs/felhom-agent-limits.conf -> $AGENT_UNIT.d/ ; daemon-reload"
else
local rbtmp; rbtmp=$(mktemp -t felhom-rollback.XXXXXX)
if fetch_raw "configs/felhom-agent-rollback.service" "$rbtmp" 2>/dev/null; then
install -m 0644 -o root -g root "$rbtmp" /etc/systemd/system/felhom-agent-rollback.service
local ddir="${AGENT_UNIT}.d"; mkdir -p "$ddir"
local lmtmp; lmtmp=$(mktemp -t felhom-limits.XXXXXX)
fetch_raw "configs/felhom-agent-limits.conf" "$lmtmp"
# [SF-3] the keys MUST be in [Unit] — refuse a drop-in that would half-apply in [Service].
grep -q '^\[Unit\]' "$lmtmp" || { rm -f "$lmtmp" "$rbtmp"; die "self-update limits drop-in missing [Unit] section — refusing"; }
install -m 0644 -o root -g root "$lmtmp" "$ddir/felhom-agent-limits.conf"
rm -f "$lmtmp"
systemctl daemon-reload
log_success " installed self-update rollback unit + start-limit drop-in (auto-rollback armed)"
else
log_skip " self-update rollback artifacts not in the agent repo yet — self-update stays manual"
fi
rm -f "$rbtmp"
fi
_state_mark agent_install
}