fix: scope FileBrowser DB reset to restore-only path

Normal storage add/remove no longer nukes the FileBrowser database volume.
A .fb-reset flag file is written during restore and consumed on next startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 15:35:22 +01:00
parent 1e9300e5a0
commit f6caea8067
4 changed files with 32 additions and 7 deletions
+1
View File
@@ -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`) - **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 - **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) - **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) ### v0.31.6 — UI: Brand-consistent button & card styling (2026-02-25)
+5
View File
@@ -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. // mountDrivesFromBackup mounts drives from the infra backup's disk layout.
+16 -5
View File
@@ -1566,6 +1566,17 @@ func (s *Server) settingsStorageLabelHandler(w http.ResponseWriter, r *http.Requ
// SyncFileBrowserMounts regenerates FileBrowser's docker-compose.yml and config.yaml // SyncFileBrowserMounts regenerates FileBrowser's docker-compose.yml and config.yaml
// with volume mounts and sources for all registered storage paths, then recreates the container. // with volume mounts and sources for all registered storage paths, then recreates the container.
func (s *Server) SyncFileBrowserMounts() { 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). // Prevent concurrent syncs — multiple callers can race on the same files (H5 fix).
s.fileBrowserMu.Lock() s.fileBrowserMu.Lock()
defer s.fileBrowserMu.Unlock() defer s.fileBrowserMu.Unlock()
@@ -1625,13 +1636,13 @@ func (s *Server) SyncFileBrowserMounts() {
return return
} }
// If sources changed, reset database so FileBrowser re-reads config.yaml. // If sources changed and caller requested a DB reset (restore flow),
// The database caches user source preferences from the old config. // nuke the data volume so FileBrowser re-reads config.yaml from scratch.
if sourcesChanged { // Normal operations skip this to preserve user accounts, permissions, and share links.
s.logger.Printf("[INFO] FileBrowser sources changed — resetting database") if sourcesChanged && resetDBOnChange {
s.logger.Printf("[INFO] FileBrowser sources changed — resetting database (restore mode)")
resetCtx, resetCancel := context.WithTimeout(context.Background(), 30*time.Second) resetCtx, resetCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer resetCancel() defer resetCancel()
// Stop container first, then remove the data volume
stop := exec.CommandContext(resetCtx, "docker", "compose", "down", "-v") stop := exec.CommandContext(resetCtx, "docker", "compose", "down", "-v")
stop.Dir = stackDir stop.Dir = stackDir
if out, err := stop.CombinedOutput(); err != nil { if out, err := stop.CombinedOutput(); err != nil {
+10 -2
View File
@@ -114,8 +114,16 @@ func NewServer(cfg *config.Config, stackMgr *stacks.Manager, cpuCollector *syste
logger.Printf("[INFO] Auth: no password configured — dashboard is open") logger.Printf("[INFO] Auth: no password configured — dashboard is open")
} }
// Sync FileBrowser config on startup to ensure mounts and sources are current // Sync FileBrowser config on startup to ensure mounts and sources are current.
go s.SyncFileBrowserMounts() // 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 return s
} }