controller v0.72.0: FileBrowser converges on boot-recreate
processGuestBootChange recreated the drive-backed app stacks but never re-synced FileBrowser (base-infra, no HDD_PATH), so its drive mounts went stale after a reboot. Now, AFTER pollLiveBinds confirms the live binds and the apps are recreated, trigger go s.SyncFileBrowserMounts() so FileBrowser converges against the now-live drives. Refactored into pure recreateDriveBackedApps(stacks, present, recreate, syncFB). Tests: FB sync runs once after recreate (red-proofed companion); runs even when nothing recreated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -370,24 +370,26 @@ func (s *Server) processGuestBootChange() {
|
||||
s.logger.Printf("[INFO] [gate] boot %s: waiting (≤%s) for live drive bind(s) %v before recreating drive-backed apps", resp.GuestBootID, bootBindWait, paths)
|
||||
presentStable := pollLiveBinds(paths, driveBindLive, time.Sleep, time.Now, bootBindWait, bootBindPoll)
|
||||
|
||||
skipped := 0
|
||||
var bootStacks []bootStack
|
||||
for _, st := range s.stackMgr.GetStacks() {
|
||||
cfg := s.stackMgr.LoadAppConfigByName(st.Name)
|
||||
if cfg == nil {
|
||||
continue
|
||||
}
|
||||
if !shouldRecreateOnBoot(cfg.Deployed, cfg.Env["HDD_PATH"], presentStable) {
|
||||
if cfg.Deployed && strings.HasPrefix(cfg.Env["HDD_PATH"], StableParentDir+"/") {
|
||||
skipped++ // a deployed drive-backed app whose bind never went live → gate's job
|
||||
}
|
||||
continue
|
||||
}
|
||||
s.logger.Printf("[INFO] [gate] boot %s: live bind confirmed — recreating drive-backed app %s (state=%s) onto %s", resp.GuestBootID, st.Name, st.State, cfg.Env["HDD_PATH"])
|
||||
_ = s.stackMgr.StopStack(st.Name)
|
||||
if serr := s.stackMgr.StartStack(st.Name); serr != nil {
|
||||
s.logger.Printf("[WARN] [gate] boot recreate %s: %v", st.Name, serr)
|
||||
bootStacks = append(bootStacks, bootStack{name: st.Name, deployed: cfg.Deployed, hdd: cfg.Env["HDD_PATH"], state: string(st.State)})
|
||||
}
|
||||
recreate := func(bs bootStack) {
|
||||
s.logger.Printf("[INFO] [gate] boot %s: live bind confirmed — recreating drive-backed app %s (state=%s) onto %s", resp.GuestBootID, bs.name, bs.state, bs.hdd)
|
||||
_ = s.stackMgr.StopStack(bs.name)
|
||||
if serr := s.stackMgr.StartStack(bs.name); serr != nil {
|
||||
s.logger.Printf("[WARN] [gate] boot recreate %s: %v", bs.name, serr)
|
||||
}
|
||||
}
|
||||
syncFB := func() {
|
||||
s.logger.Printf("[INFO] [gate] boot %s: re-syncing FileBrowser mounts against the live binds", resp.GuestBootID)
|
||||
go s.SyncFileBrowserMounts()
|
||||
}
|
||||
_, skipped := recreateDriveBackedApps(bootStacks, presentStable, recreate, syncFB)
|
||||
if skipped > 0 {
|
||||
s.logger.Printf("[WARN] [gate] boot %s: %d drive-backed app(s) had no live bind within %s — leaving to the drive gate", resp.GuestBootID, skipped, bootBindWait)
|
||||
}
|
||||
@@ -396,6 +398,35 @@ func (s *Server) processGuestBootChange() {
|
||||
}
|
||||
}
|
||||
|
||||
// bootStack is one deployed stack's boot-recreate inputs (decoupled from stacks.Manager for testing).
|
||||
type bootStack struct {
|
||||
name string
|
||||
deployed bool
|
||||
hdd string
|
||||
state string
|
||||
}
|
||||
|
||||
// recreateDriveBackedApps recreates every deployed drive-backed app whose drive bind is live, then
|
||||
// triggers the FileBrowser sync. FileBrowser binds the drives' userdata but is base-infra (no HDD_PATH),
|
||||
// so it is NOT in the recreate set — it must be converged HERE, AFTER the recreate (which itself only
|
||||
// ran once pollLiveBinds confirmed the live binds), so FileBrowser's mounts reflect the now-live drives
|
||||
// instead of going stale (the gap a host/guest reboot left before this fix). syncFB is always called so
|
||||
// FileBrowser reflects the current bind state even if no app needed recreating. Pure (ops injected).
|
||||
func recreateDriveBackedApps(stacks []bootStack, presentStable map[string]bool, recreate func(bootStack), syncFB func()) (recreated, skipped int) {
|
||||
for _, bs := range stacks {
|
||||
if !shouldRecreateOnBoot(bs.deployed, bs.hdd, presentStable) {
|
||||
if bs.deployed && strings.HasPrefix(bs.hdd, StableParentDir+"/") {
|
||||
skipped++ // a deployed drive-backed app whose bind never went live → gate's job
|
||||
}
|
||||
continue
|
||||
}
|
||||
recreate(bs)
|
||||
recreated++
|
||||
}
|
||||
syncFB()
|
||||
return
|
||||
}
|
||||
|
||||
// ---- H1 endpoints (the UI's settings.js calls these; previously 404/unrouted) -----------------
|
||||
|
||||
// handleStorageDisconnect EJECTS a drive without restart: stop its apps (gate-stopped), agent-detach the
|
||||
|
||||
Reference in New Issue
Block a user