agent v0.35.0: intermediary mount — guest-reboot re-propagation (load-bearing)

A guest's parent bind is non-recursive, so a guest reboot leaves enrolled drives
bound on the HOST but invisible in the fresh guest ns (propagation only delivers
new events). AttachDrive(vmid) now checks GuestSeesMount (/proc/<pid>/mountinfo)
and force re-binds (umount+mount) to re-propagate; a 20s periodic reconcile
self-heals guest reboots without an agent restart; BoundUnderParent reflects guest
visibility (the controller gate's signal). Caught + fixed in the live migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 17:26:32 +02:00
parent 3a9be73875
commit 26c6d1e4d1
5 changed files with 121 additions and 22 deletions
+18 -1
View File
@@ -44,7 +44,7 @@ import (
// version is the agent version. Overridable at build time with
// -ldflags "-X main.version=<v>"; defaults to the in-repo CHANGELOG version.
var version = "0.34.0"
var version = "0.35.0"
// runGuestHook is the PVE pre-start hook body (`felhom-agent guest-hook <vmid> <phase>`). On the
// pre-start phase it creates placeholder dirs for any absent bind-mount source so the guest always boots
@@ -510,6 +510,23 @@ func runDaemon(cfg config.Config, logger *slog.Logger) int {
// bind that a re-provision dropped — before serving, so the drive is back in the guest config
// (activates on the guest's next reboot). On-durable-id-match; absent/swapped drives are skipped.
localSrv.ReassertGuestBinds(ctx)
// Intermediary-mount GUEST-REBOOT self-heal: re-run the reconcile periodically. A guest reboot
// (without an agent restart) leaves enrolled drives bound on the HOST but INVISIBLE in the fresh
// guest namespace (a non-recursive parent bind doesn't carry pre-existing submounts); the periodic
// reconcile detects the guest can't see the bind and re-fires propagation (force re-bind). Cheap
// when nothing changed (guest-visibility checks only). 20s ≪ the controller gate's 30s tick.
go func() {
t := time.NewTicker(20 * time.Second)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
localSrv.ReassertGuestBinds(ctx)
}
}
}()
// F20-BUG3: complete a disk format that an agent restart interrupted (durable-id-bound, re-resolved).
localSrv.RecoverFormatJob(ctx)
go func() { errc <- localSrv.Run(ctx) }()