agent v0.49.0: reboot-during-backup stale-lock recovery (F2-b) + shared-parent script redeploy fix (F2-a)

F2-b: at startup, recover a guest left with a stale vzdump lock by a
reboot-during-backup — pct unlock -> delete dangling vzdump snapshot ->
start iff onboot, guarded by a no-vzdump-running invariant (fail-safe).
New internal/localapi/stalelock.go; proxmox GuestConfig.Lock()/OnBoot(),
ListSnapshots, ListRunningTasks, Snapshot type. New narrow sudoers grant
FELHOM_STALELOCK (pct unlock) + Critical capability stalelock-unlock.

F2-a: EnsureSharedParent only redeployed the boot script when the UNIT
differed, so the v0.36.6 make-private fix never reached hosts whose unit
was current -> /mnt/felhom-drives stayed in root's shared:1 and doubled
every drive bind. New sharedParentInstallStale compares BOTH script and
unit. Boot-time-only; never churns the live mount.

Both root causes confirmed live on felhom-pve before fixing. Green gate
(build/vet/test) all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162BnMpUXscPsUB1cU8Tr6K
This commit is contained in:
2026-06-30 09:00:32 +02:00
parent 81954c3d2a
commit 6e38e2f921
12 changed files with 743 additions and 32 deletions
+5
View File
@@ -87,6 +87,9 @@ type Options struct {
// ControllerSwap runs guest commands (pct exec) for the agentic controller-update swap (Phase 1).
// OPTIONAL — when nil, POST /controller/swap reports "not configured". Satisfied by *GuestBinder.
ControllerSwap GuestExecutor
// StaleLock recovers a guest left with a stale vzdump lock by a reboot-during-backup (F2-b), run at
// startup by RecoverStaleLockedGuests. OPTIONAL — when nil, the recovery is a no-op.
StaleLock StaleLockController
// ControllerSwapStateDir holds the per-guest swap state file (crash-safety). "" → /var/lib/felhom-agent.
ControllerSwapStateDir string
// Intent records drive enroll/eject intent for the self-heal watchdog (slice 10 P3). OPTIONAL —
@@ -158,6 +161,7 @@ type Server struct {
intent IntentRecorder // slice 10 P3 (optional)
guestBinds *GuestBindStore // F9 startup bind re-assert record (optional)
formatJobs *FormatJobStore // F20-BUG3 detached-format job record (optional)
staleLock StaleLockController // F2-b startup stale-lock recovery (optional)
host storage.HostReader // role classification source (optional; defaults to ProcHostReader)
hostMetrics HostMetricsProvider // slice 9 (optional)
@@ -224,6 +228,7 @@ func NewServer(o Options) (*Server, error) {
intent: o.Intent,
guestBinds: o.GuestBinds,
formatJobs: o.FormatJobs,
staleLock: o.StaleLock,
host: o.HostReader,
hostMetrics: o.HostMetrics,
hostID: o.HostID,