diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b49836..41b9249 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## 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) Completes guest-reboot convergence (caught in the live migration). On a guest reboot docker auto-starts diff --git a/controller/internal/web/intermediary.go b/controller/internal/web/intermediary.go index c93ec84..ae723b5 100644 --- a/controller/internal/web/intermediary.go +++ b/controller/internal/web/intermediary.go @@ -10,6 +10,7 @@ import ( "gitea.dooplex.hu/admin/felhom-controller/internal/agentapi" "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 @@ -249,10 +250,16 @@ func (s *Server) recreateBootStaleApps() { if hdd == "" || !strings.HasPrefix(hdd, StableParentDir+"/") || !presentStable[hdd] { continue } - if !s.stackStartedRecently(st.Name, 5*time.Minute) { - continue // long-running across a controller-only restart — don't bounce it + // Recreate when the app is boot-stale or not cleanly running: recently-started (it likely came up + // 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) if serr := s.stackMgr.StartStack(st.Name); serr != nil { s.logger.Printf("[WARN] [gate] startup recreate %s: %v", st.Name, serr)