From 121d8eee24450ca4180b2a3bf3cd72e22146f029 Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Thu, 9 Apr 2026 19:12:10 +0100 Subject: [PATCH 1/2] fix: use podman unshare to clean up rootless home directories In podman rootless mode, files created as root inside the container are owned by a mapped subuid on the host. When an agent is deleted, RemoveAllSafe() fails silently because the host user can't remove these files, leaving stale directories in ~/.scion/grove-configs/*/home/. Fall back to `podman unshare rm -rf` when standard removal fails. This enters the user namespace where the mapped UIDs are accessible, allowing cleanup without requiring sudo. --- pkg/agent/provision.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/agent/provision.go b/pkg/agent/provision.go index e809b411..23280d95 100644 --- a/pkg/agent/provision.go +++ b/pkg/agent/provision.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "os" + "os/exec" "path/filepath" "strings" "time" @@ -117,11 +118,18 @@ func DeleteAgentFiles(agentName string, grovePath string, removeBranch bool) (bo } // Phase 3: remove external agent home (git grove split storage). + // In podman rootless mode, files created as root inside the container are + // owned by a mapped subuid on the host, making them inaccessible to the + // normal user. If standard removal fails, try `podman unshare rm -rf` + // which enters the user namespace where the mapped UIDs are accessible. if externalAgentDir != "" { if _, err := os.Stat(externalAgentDir); err == nil { util.Debugf("delete: removing external agent home: %s", externalAgentDir) if err := util.RemoveAllSafe(externalAgentDir); err != nil { - util.Debugf("delete: external home removal failed: %v", err) + util.Debugf("delete: standard removal failed, trying podman unshare: %v", err) + if unshareErr := exec.Command("podman", "unshare", "rm", "-rf", externalAgentDir).Run(); unshareErr != nil { + util.Debugf("delete: podman unshare removal also failed: %v", unshareErr) + } } } } From ac056d25d4c082fa5f0ff70d451f5a7e96846948 Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Sun, 12 Apr 2026 12:34:09 +0100 Subject: [PATCH 2/2] fix: add timeout to podman unshare cleanup exec Addresses review feedback on #107: exec.Command(...).Run() blocks indefinitely if podman unshare hangs (e.g. waiting on a user namespace lock). Use exec.CommandContext with a 30s timeout so DeleteAgentFiles always returns. --- pkg/agent/provision.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/agent/provision.go b/pkg/agent/provision.go index 23280d95..5d940f2b 100644 --- a/pkg/agent/provision.go +++ b/pkg/agent/provision.go @@ -127,9 +127,11 @@ func DeleteAgentFiles(agentName string, grovePath string, removeBranch bool) (bo util.Debugf("delete: removing external agent home: %s", externalAgentDir) if err := util.RemoveAllSafe(externalAgentDir); err != nil { util.Debugf("delete: standard removal failed, trying podman unshare: %v", err) - if unshareErr := exec.Command("podman", "unshare", "rm", "-rf", externalAgentDir).Run(); unshareErr != nil { + unshareCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + if unshareErr := exec.CommandContext(unshareCtx, "podman", "unshare", "rm", "-rf", externalAgentDir).Run(); unshareErr != nil { util.Debugf("delete: podman unshare removal also failed: %v", unshareErr) } + cancel() } } }