From 2b17419e608c1a3fcc8cba8c9373b7f5adeaac79 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 15 Jun 2026 20:28:57 +0200 Subject: [PATCH] agent v0.36.6: shared parent make-private before make-shared (ROOT CAUSE of double-bind) The self-bind inherited /'s shared peer group, so binds under /mnt/felhom-drives propagated back and doubled. make-private (own group) before make-shared fixes it. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 8 ++++++++ cmd/felhom-agent/main.go | 2 +- internal/localapi/intermediary.go | 10 ++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab3131a..218074d 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.36.6 — shared parent gets its OWN peer group (make-private first) — ROOT CAUSE of double-bind (2026-06-15) + +The shared-parent self-bind INHERITED the root mount`s shared peer group (`/mnt/felhom-drives` was +`shared:1` same as `/`), so every drive bind under it propagated back via the root peer and DOUBLED +(2 stacked binds per drive — the real cause behind v0.36.3-.5). EnsureSharedParent + the boot script now +`make-private` (detach from the root group) BEFORE `make-shared` (own group whose only slave is the +guest), so a drive bind propagates to the guest exactly once. + ## v0.36.5 — AttachDrive normalizes to exactly one bind (2026-06-15) AttachDrive now COUNTS the binds at a stable path (countHostMounts) and normalizes to exactly one: it is diff --git a/cmd/felhom-agent/main.go b/cmd/felhom-agent/main.go index 234ff6d..583b274 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.36.5" +var version = "0.36.6" // 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 3a9fd56..5c6d96a 100644 --- a/internal/localapi/intermediary.go +++ b/internal/localapi/intermediary.go @@ -33,6 +33,10 @@ const sharedParentScript = `#!/bin/sh set -e mkdir -p ` + StableParentDir + ` mountpoint -q ` + StableParentDir + ` || mount --bind ` + StableParentDir + ` ` + StableParentDir + ` +# make-PRIVATE first: the self-bind inherits the root mount's shared peer group, so without detaching it +# every drive bind under the parent would propagate back (and DOUBLE). Then make-shared = its OWN group, +# whose only slave is the guest's parent bind → binds propagate to the guest exactly once. +mount --make-private ` + StableParentDir + ` mount --make-shared ` + StableParentDir + ` ` @@ -95,6 +99,12 @@ func (b *GuestBinder) EnsureSharedParent(ctx context.Context) error { return fmt.Errorf("shared-parent: self-bind: %w", err) } } + // make-PRIVATE first to detach from the root mount's shared peer group (the self-bind inherits it), + // THEN make-shared so the parent owns its OWN group — otherwise binds under it propagate back via the + // root peer and DOUBLE (observed live as 2 stacked binds per drive). + if err := b.run(ctx, "mount", "--make-private", StableParentDir); err != nil { + return fmt.Errorf("shared-parent: make-private: %w", err) + } if err := b.run(ctx, "mount", "--make-shared", StableParentDir); err != nil { return fmt.Errorf("shared-parent: make-shared: %w", err) }