feat: drive migration & Tier 2 restic deprecation (v0.18.0)

Phase 1: Deprecate restic as Tier 2 method (rsync only), auto-migrate on startup
Phase 2: Enhanced per-app migration with backup awareness, DB dump copy, auto-cleanup
Phase 3: Full drive migration with decommissioned state, rollback support, wizard UI
Phase 4: Hub report includes decommissioned drive state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 21:49:14 +01:00
parent bdbe170a54
commit 99bf3ca7a8
22 changed files with 1725 additions and 402 deletions
+12 -1
View File
@@ -18,6 +18,7 @@ import (
"gitea.dooplex.hu/admin/felhom-controller/internal/selfupdate"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
"gitea.dooplex.hu/admin/felhom-controller/internal/storage"
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
)
@@ -47,6 +48,9 @@ type Server struct {
// Active raw mount for the attach wizard (empty when not in use)
activeRawMount string
// Drive migration
driveMigrator *storage.DriveMigrator
// DR restore mode state
restoreMu sync.RWMutex
restorePlan *backup.RestorePlan
@@ -85,7 +89,7 @@ func NewServer(cfg *config.Config, stackMgr *stacks.Manager, cpuCollector *syste
}
// Sync FileBrowser config on startup to ensure mounts and sources are current
go s.syncFileBrowserMounts()
go s.SyncFileBrowserMounts()
return s
}
@@ -108,6 +112,11 @@ func (s *Server) SetStorageWatchdog(w *monitor.StorageWatchdog) {
s.storageWatchdog = w
}
// SetDriveMigrator sets the drive migration engine for full drive migration.
func (s *Server) SetDriveMigrator(dm *storage.DriveMigrator) {
s.driveMigrator = dm
}
// InRestoreMode returns true if the server is in DR restore mode.
func (s *Server) InRestoreMode() bool {
s.restoreMu.RLock()
@@ -179,6 +188,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.storageInitHandler(w, r)
case path == "/settings/storage/attach":
s.storageAttachHandler(w, r)
case path == "/settings/storage/migrate-drive":
s.migrateDrivePageHandler(w, r)
case strings.HasPrefix(path, "/stacks/") && strings.HasSuffix(path, "/migrate"):
name := strings.TrimPrefix(path, "/stacks/")
name = strings.TrimSuffix(name, "/migrate")