Skip to content

Commit 2d6fba5

Browse files
ralyodioclaude
andcommitted
fix(pods): self-heal pre-existing pods to pick up the public_html bind
The box auto-deploys (self-update timer pulls origin/main, rebuilds, restarts agentbbs), but `systemctl restart agentbbs` only restarts the daemon — it never touches the long-lived per-user pod containers. ensure() also short-circuits on any container that already exists, so pods created before the homepage bind landed would never gain the /home/dev/public_html mount without a manual `podman rm`. That defeats the "everything happens automatically on push" goal. Make ensure() self-healing: when a pod exists but lacks the public_html mount, recreate it so the bind is applied. The named home volume survives `rm`, so the member's files are kept. Only heal when the pod is idle (attached count 0) to avoid pulling a running pod out from under an active session — an unbound pod heals on its next idle attach. New hasMount() inspects the container's mounts. Net effect: push to main -> self-update redeploys within the timer interval -> the next `ssh pod@` recreates the pod with the bind. No manual step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cbe7a44 commit 2d6fba5

1 file changed

Lines changed: 32 additions & 3 deletions

File tree

internal/pods/pods.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@ func (m *Manager) publicHTMLMount(user string) (host, spec string) {
7070
return host, host + ":/home/dev/public_html"
7171
}
7272

73+
// hasMount reports whether the named container already has a mount at the given
74+
// destination path. Used to detect pods created before a mount was introduced so
75+
// ensure can recreate them. A failed inspect reports false (treat as missing).
76+
func (m *Manager) hasMount(name, dest string) bool {
77+
out, err := exec.Command(m.engine, "container", "inspect",
78+
"-f", "{{range .Mounts}}{{println .Destination}}{{end}}", name).Output()
79+
if err != nil {
80+
return false
81+
}
82+
for _, line := range strings.Split(string(out), "\n") {
83+
if strings.TrimSpace(line) == dest {
84+
return true
85+
}
86+
}
87+
return false
88+
}
89+
7390
// Engine reports the active container engine.
7491
func (m *Manager) Engine() string { return m.engine }
7592

@@ -106,9 +123,21 @@ func (m *Manager) ensure(user string) (string, error) {
106123
}
107124
// Already exists?
108125
if err := exec.Command(m.engine, "container", "inspect", name).Run(); err == nil {
109-
_ = exec.Command(m.engine, "start", name).Run() // no-op if running
110-
m.tuneApt(name)
111-
return name, nil
126+
// Self-heal pods created before the homepage bind existed: recreate so
127+
// ~/public_html maps to the served dir. The named home volume persists
128+
// across rm, so the member's files are kept. Only heal when the pod is
129+
// idle (no live session) — never pull a running pod out from under an
130+
// active session; a still-unbound pod heals on its next idle attach.
131+
m.mu.Lock()
132+
idle := m.attached[name] == 0
133+
m.mu.Unlock()
134+
if pubSpec != "" && idle && !m.hasMount(name, "/home/dev/public_html") {
135+
_ = exec.Command(m.engine, "rm", "-f", name).Run() // fall through to recreate with the bind
136+
} else {
137+
_ = exec.Command(m.engine, "start", name).Run() // no-op if running
138+
m.tuneApt(name)
139+
return name, nil
140+
}
112141
}
113142
args := []string{
114143
"run", "-d",

0 commit comments

Comments
 (0)