diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb556d..80dc5ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - **Bind mount write**: `atomicWriteFile()` now falls back to direct write when rename fails (fixes "device or resource busy" on Docker bind-mounted `controller.yaml`) - **Drive mounting after restore**: Restore flow now calls `MountDrivesFromLayout()` to mount drives by UUID and add fstab entries — previously drives referenced in the infra backup were not mounted, causing "Adattároló nem elérhető" warnings - **Post-restore redirect**: UI now polls until the controller is actually up instead of using a fixed 5-second timeout (which was too short for container restart) +- **FileBrowser DB reset scoped to restore**: `SyncFileBrowserMounts()` no longer resets the FileBrowser database volume on source changes — only the post-restore startup path (`SyncFileBrowserMountsReset`) does, preserving user accounts, permissions, and share links during normal storage operations ### v0.31.6 — UI: Brand-consistent button & card styling (2026-02-25) diff --git a/controller/internal/setup/handlers.go b/controller/internal/setup/handlers.go index bc29bca..ccf828f 100644 --- a/controller/internal/setup/handlers.go +++ b/controller/internal/setup/handlers.go @@ -875,6 +875,11 @@ func (s *Server) restoreFromInfraBackup(ib *report.InfraBackup) { } } } + + // Signal that FileBrowser's database should be reset on next startup. + // After restore, the DB has stale source preferences from the initial install. + flagPath := filepath.Join(s.dataDir, ".fb-reset") + _ = os.WriteFile(flagPath, []byte("restore"), 0644) } // mountDrivesFromBackup mounts drives from the infra backup's disk layout. diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index 888f4ef..71ea4c9 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -1566,6 +1566,17 @@ func (s *Server) settingsStorageLabelHandler(w http.ResponseWriter, r *http.Requ // SyncFileBrowserMounts regenerates FileBrowser's docker-compose.yml and config.yaml // with volume mounts and sources for all registered storage paths, then recreates the container. func (s *Server) SyncFileBrowserMounts() { + s.syncFileBrowserMounts(false) +} + +// SyncFileBrowserMountsReset is like SyncFileBrowserMounts but resets the FileBrowser +// database when sources change. Use only after restore — normal operations should use +// SyncFileBrowserMounts to preserve user accounts, permissions, and share links. +func (s *Server) SyncFileBrowserMountsReset() { + s.syncFileBrowserMounts(true) +} + +func (s *Server) syncFileBrowserMounts(resetDBOnChange bool) { // Prevent concurrent syncs — multiple callers can race on the same files (H5 fix). s.fileBrowserMu.Lock() defer s.fileBrowserMu.Unlock() @@ -1625,13 +1636,13 @@ func (s *Server) SyncFileBrowserMounts() { return } - // If sources changed, reset database so FileBrowser re-reads config.yaml. - // The database caches user source preferences from the old config. - if sourcesChanged { - s.logger.Printf("[INFO] FileBrowser sources changed — resetting database") + // If sources changed and caller requested a DB reset (restore flow), + // nuke the data volume so FileBrowser re-reads config.yaml from scratch. + // Normal operations skip this to preserve user accounts, permissions, and share links. + if sourcesChanged && resetDBOnChange { + s.logger.Printf("[INFO] FileBrowser sources changed — resetting database (restore mode)") resetCtx, resetCancel := context.WithTimeout(context.Background(), 30*time.Second) defer resetCancel() - // Stop container first, then remove the data volume stop := exec.CommandContext(resetCtx, "docker", "compose", "down", "-v") stop.Dir = stackDir if out, err := stop.CombinedOutput(); err != nil { diff --git a/controller/internal/web/server.go b/controller/internal/web/server.go index 6f271c3..83bcf96 100644 --- a/controller/internal/web/server.go +++ b/controller/internal/web/server.go @@ -114,8 +114,16 @@ func NewServer(cfg *config.Config, stackMgr *stacks.Manager, cpuCollector *syste logger.Printf("[INFO] Auth: no password configured — dashboard is open") } - // Sync FileBrowser config on startup to ensure mounts and sources are current - go s.SyncFileBrowserMounts() + // Sync FileBrowser config on startup to ensure mounts and sources are current. + // After a restore, a flag file signals that the database should be reset + // (stale source prefs from initial install). Consume the flag and reset. + fbResetFlag := filepath.Join(cfg.Paths.DataDir, ".fb-reset") + if _, err := os.Stat(fbResetFlag); err == nil { + os.Remove(fbResetFlag) + go s.SyncFileBrowserMountsReset() + } else { + go s.SyncFileBrowserMounts() + } return s }