From a2374ca67916942b82ed4223fd98d7010e0871ac Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 15 Jun 2026 17:58:52 +0200 Subject: [PATCH] agent v0.35.1: shared-parent unit runs before pve-guests on host boot The shared-parent oneshot never ran before pve-guests (WantedBy=multi-user wasn't pulled into the boot transaction), so on a host reboot the guest bound a not-yet-shared parent -> private -> propagation broken. Now WantedBy=pve-guests (pve-guests pulls it in + Before= orders it first); EnsureSharedParent reinstalls the unit when content differs so the fix deploys. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 8 ++++++++ cmd/felhom-agent/main.go | 2 +- internal/localapi/intermediary.go | 15 ++++++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2229d43..b5fae69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to **felhom-agent** are recorded here. Update on every code change that gets pushed. +## v0.35.1 — shared-parent unit: run before pve-guests on host boot (2026-06-15) + +Fix for the host-reboot ordering (the shared-parent oneshot never ran before pve-guests on the live +host, so the guest bound a not-yet-shared parent → private bind → propagation broken). The unit now uses +`WantedBy=pve-guests.service` (pve-guests PULLS IT IN + Before= orders it first) instead of the +unreliable `WantedBy=multi-user.target`, and drops `DefaultDependencies=no`. `EnsureSharedParent` +reinstalls the unit when its content differs (so the fix deploys on the next agent start/reconcile). + ## v0.35.0 — intermediary mount: guest-reboot re-propagation (load-bearing) (2026-06-15) Fix for the guest-reboot gap (caught in the live demo migration). A guest's parent bind is diff --git a/cmd/felhom-agent/main.go b/cmd/felhom-agent/main.go index 29c3aa4..4e4b3e0 100644 --- a/cmd/felhom-agent/main.go +++ b/cmd/felhom-agent/main.go @@ -44,7 +44,7 @@ import ( // version is the agent version. Overridable at build time with // -ldflags "-X main.version="; defaults to the in-repo CHANGELOG version. -var version = "0.35.0" +var version = "0.35.1" // runGuestHook is the PVE pre-start hook body (`felhom-agent guest-hook `). On the // pre-start phase it creates placeholder dirs for any absent bind-mount source so the guest always boots diff --git a/internal/localapi/intermediary.go b/internal/localapi/intermediary.go index b623a4a..e69c022 100644 --- a/internal/localapi/intermediary.go +++ b/internal/localapi/intermediary.go @@ -37,9 +37,13 @@ mount --make-shared ` + StableParentDir + ` ` const sharedParentUnitPath = "/etc/systemd/system/felhom-shared-parent.service" + +// sharedParentUnit MUST run before pve-guests so the guest's parent bind inherits the shared peer group. +// WantedBy=pve-guests.service makes pve-guests itself PULL IT IN (and Before= orders it first) — a plain +// WantedBy=multi-user.target proved unreliable (the unit wasn't pulled into the boot transaction; it +// never ran before pve-guests). local-fs.target ordering ensures /mnt is available. const sharedParentUnit = `[Unit] Description=Felhom stable drive parent (shared bind for live drive hot-swap) -DefaultDependencies=no After=local-fs.target Before=pve-guests.service ConditionPathExists=` + sharedParentScriptPath + ` @@ -50,7 +54,7 @@ RemainAfterExit=yes ExecStart=` + sharedParentScriptPath + ` [Install] -WantedBy=multi-user.target +WantedBy=pve-guests.service multi-user.target ` // StablePathForRaw maps a drive's RAW host mount (/mnt/) to its stable in-guest path @@ -94,9 +98,10 @@ func (b *GuestBinder) EnsureSharedParent(ctx context.Context) error { if err := b.run(ctx, "mount", "--make-shared", StableParentDir); err != nil { return fmt.Errorf("shared-parent: make-shared: %w", err) } - // Install the boot-persistence unit only if it's not already there — EnsureSharedParent runs on a - // periodic reconcile, and re-writing files + daemon-reload every tick would be wasteful. - if _, err := os.Stat(sharedParentUnitPath); err != nil { + // Install the boot-persistence unit only when missing OR its content differs from what we ship (so a + // unit-template fix deploys) — EnsureSharedParent runs on a periodic reconcile, and re-writing files + + // daemon-reload every tick would be wasteful, so the common case (unchanged) is a cheap read. + if cur, err := os.ReadFile(sharedParentUnitPath); err != nil || string(cur) != sharedParentUnit { if ierr := b.installSharedParentUnit(ctx); ierr != nil { b.logger.Warn("shared-parent: boot-persistence unit install failed (live setup OK; survives until host reboot)", "err", ierr) }