controller v0.67.4: startup recreate also recovers exited/restarting drive-backed apps

recreateBootStaleApps recreates a present drive-backed app when boot-stale OR
exited/restarting/unhealthy (the recency-only gate missed already-exited apps).
Still skips healthy long-running + cleanly user-stopped apps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 17:46:08 +02:00
parent 91a6dcfa75
commit be95a16246
2 changed files with 17 additions and 3 deletions
+7
View File
@@ -1,5 +1,12 @@
## Changelog ## Changelog
### v0.67.4 — gate: startup recreate also recovers exited/restarting drive-backed apps (2026-06-15)
Refines v0.67.3's `recreateBootStaleApps`: recreate a present drive-backed app when it is boot-stale
(recently started) OR currently `exited`/`restarting`/`unhealthy` (came up wrong on the empty bind and
bailed) — the recency-only gate missed apps that had already exited. Still skips healthy long-running
apps (no bounce on a controller-only restart) and cleanly user-stopped apps.
### v0.67.3 — gate: startup recreate of boot-stale drive-backed apps (2026-06-15) ### v0.67.3 — gate: startup recreate of boot-stale drive-backed apps (2026-06-15)
Completes guest-reboot convergence (caught in the live migration). On a guest reboot docker auto-starts Completes guest-reboot convergence (caught in the live migration). On a guest reboot docker auto-starts
+10 -3
View File
@@ -10,6 +10,7 @@ import (
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi" "gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings" "gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
) )
// Intermediary-mount model (controller side). Post-migration a drive is visible in the guest ONLY at its // Intermediary-mount model (controller side). Post-migration a drive is visible in the guest ONLY at its
@@ -249,10 +250,16 @@ func (s *Server) recreateBootStaleApps() {
if hdd == "" || !strings.HasPrefix(hdd, StableParentDir+"/") || !presentStable[hdd] { if hdd == "" || !strings.HasPrefix(hdd, StableParentDir+"/") || !presentStable[hdd] {
continue continue
} }
if !s.stackStartedRecently(st.Name, 5*time.Minute) { // Recreate when the app is boot-stale or not cleanly running: recently-started (it likely came up
continue // long-running across a controller-only restart — don't bounce it // on the empty bind before the drive was re-propagated) OR currently exited/restarting/unhealthy
// (came up wrong and bailed). SKIP a healthy long-running app (no bounce on a controller-only
// restart) and a cleanly user-Stopped app (respect the user's intent).
needs := s.stackStartedRecently(st.Name, 5*time.Minute) ||
st.State == stacks.StateExited || st.State == stacks.StateRestarting || st.State == stacks.StateUnhealthy
if !needs {
continue
} }
s.logger.Printf("[INFO] [gate] startup: recreating drive-backed app %s onto its (re-propagated) drive %s", st.Name, hdd) s.logger.Printf("[INFO] [gate] startup: recreating drive-backed app %s (state=%s) onto its drive %s", st.Name, st.State, hdd)
_ = s.stackMgr.StopStack(st.Name) _ = s.stackMgr.StopStack(st.Name)
if serr := s.stackMgr.StartStack(st.Name); serr != nil { if serr := s.stackMgr.StartStack(st.Name); serr != nil {
s.logger.Printf("[WARN] [gate] startup recreate %s: %v", st.Name, serr) s.logger.Printf("[WARN] [gate] startup recreate %s: %v", st.Name, serr)