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) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 17:58:52 +02:00
parent 26c6d1e4d1
commit a2374ca679
3 changed files with 19 additions and 6 deletions
+8
View File
@@ -3,6 +3,14 @@
All notable changes to **felhom-agent** are recorded here. Update on every code All notable changes to **felhom-agent** are recorded here. Update on every code
change that gets pushed. 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) ## 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 Fix for the guest-reboot gap (caught in the live demo migration). A guest's parent bind is
+1 -1
View File
@@ -44,7 +44,7 @@ import (
// version is the agent version. Overridable at build time with // version is the agent version. Overridable at build time with
// -ldflags "-X main.version=<v>"; defaults to the in-repo CHANGELOG version. // -ldflags "-X main.version=<v>"; 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 <vmid> <phase>`). On the // 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 // pre-start phase it creates placeholder dirs for any absent bind-mount source so the guest always boots
+10 -5
View File
@@ -37,9 +37,13 @@ mount --make-shared ` + StableParentDir + `
` `
const sharedParentUnitPath = "/etc/systemd/system/felhom-shared-parent.service" 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] const sharedParentUnit = `[Unit]
Description=Felhom stable drive parent (shared bind for live drive hot-swap) Description=Felhom stable drive parent (shared bind for live drive hot-swap)
DefaultDependencies=no
After=local-fs.target After=local-fs.target
Before=pve-guests.service Before=pve-guests.service
ConditionPathExists=` + sharedParentScriptPath + ` ConditionPathExists=` + sharedParentScriptPath + `
@@ -50,7 +54,7 @@ RemainAfterExit=yes
ExecStart=` + sharedParentScriptPath + ` ExecStart=` + sharedParentScriptPath + `
[Install] [Install]
WantedBy=multi-user.target WantedBy=pve-guests.service multi-user.target
` `
// StablePathForRaw maps a drive's RAW host mount (/mnt/<name>) to its stable in-guest path // StablePathForRaw maps a drive's RAW host mount (/mnt/<name>) 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 { if err := b.run(ctx, "mount", "--make-shared", StableParentDir); err != nil {
return fmt.Errorf("shared-parent: make-shared: %w", err) 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 // Install the boot-persistence unit only when missing OR its content differs from what we ship (so a
// periodic reconcile, and re-writing files + daemon-reload every tick would be wasteful. // unit-template fix deploys) — EnsureSharedParent runs on a periodic reconcile, and re-writing files +
if _, err := os.Stat(sharedParentUnitPath); err != nil { // 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 { 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) b.logger.Warn("shared-parent: boot-persistence unit install failed (live setup OK; survives until host reboot)", "err", ierr)
} }