diff --git a/controller/internal/integrations/manager.go b/controller/internal/integrations/manager.go index 348a7bd..8cf8c01 100644 --- a/controller/internal/integrations/manager.go +++ b/controller/internal/integrations/manager.go @@ -184,6 +184,53 @@ func (m *Manager) buildApplyContext(provider, target string) (*ApplyContext, err }, nil } +// ReapplyConfigForTarget applies all active integrations targeting the given stack, +// writing config changes only (no container restart). Use this when the caller +// handles container restart separately (e.g. SyncFileBrowserMounts). +func (m *Manager) ReapplyConfigForTarget(targetName string) { + m.mu.Lock() + defer m.mu.Unlock() + + all := m.sett.GetIntegrationsForTarget(targetName) + for key, state := range all { + if !state.Enabled || state.Status == "disabled" { + continue + } + + provider, target, ok := ParseIntegrationKey(key) + if !ok { + continue + } + + handler, hOk := m.handlers[key] + if !hOk { + continue + } + + ac, err := m.buildApplyContext(provider, target) + if err != nil { + m.logger.Printf("[WARN] Cannot build context for integration %s reapply: %v", key, err) + continue + } + + // Override RestartStack to no-op — caller handles restart + ac.RestartStack = func(string) error { return nil } + + if err := handler.Apply(ac); err != nil { + m.logger.Printf("[WARN] Integration config reapply failed for %s: %v", key, err) + state.Status = "error" + state.LastError = err.Error() + _ = m.sett.SetIntegrationState(key, state) + continue + } + + state.Status = "active" + state.LastError = "" + _ = m.sett.SetIntegrationState(key, state) + m.logger.Printf("[INFO] Integration %s config reapplied for %s", key, targetName) + } +} + // loadDecryptedEnv reads app.yaml for a stack and decrypts sensitive values. func (m *Manager) loadDecryptedEnv(s *stacks.Stack) map[string]string { stackDir := filepath.Dir(s.ComposePath) diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index b0ca9a0..701519d 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -1605,6 +1605,11 @@ func (s *Server) SyncFileBrowserMounts() { return } + // Re-apply active integrations into config.yaml (before container restart) + if s.integrationMgr != nil { + s.integrationMgr.ReapplyConfigForTarget("filebrowser") + } + // Generate and write compose (includes config.yaml mount) compose := generateFileBrowserCompose(domain, storageMounts) if err := os.WriteFile(composePath, []byte(compose), 0644); err != nil { @@ -1621,10 +1626,6 @@ func (s *Server) SyncFileBrowserMounts() { s.logger.Printf("[ERROR] Failed to recreate FileBrowser: %s — %v", string(out), err) } else { s.logger.Printf("[INFO] FileBrowser mounts synced — %d storage path(s), config updated", len(paths)) - // Re-apply active integrations (config regeneration overwrites config.yaml) - if s.integrationMgr != nil { - go s.integrationMgr.OnStackStart(context.Background(), "filebrowser") - } } }