diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ee3b4..2fb556d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ #### Fixed - **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) ### 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 1f5de37..bc29bca 100644 --- a/controller/internal/setup/handlers.go +++ b/controller/internal/setup/handlers.go @@ -1,6 +1,7 @@ package setup import ( + "context" crand "crypto/rand" "crypto/sha256" "embed" @@ -683,6 +684,7 @@ func (s *Server) executeLocalRestore(drivePath, historyFile string) { s.restoreSteps = []RestoreStep{ {Label: "Mentés beolvasása...", Status: "running"}, {Label: "Konfiguráció visszaállítása...", Status: "pending"}, + {Label: "Meghajtók csatolása...", Status: "pending"}, {Label: "Beállítás befejezése...", Status: "pending"}, } s.restoreMu.Unlock() @@ -715,8 +717,13 @@ func (s *Server) executeLocalRestore(drivePath, historyFile string) { } s.setRestoreStepDone(1) - // Step 3: Finalize + // Step 3: Mount drives from disk layout s.setRestoreStepRunning(2) + s.mountDrivesFromBackup(&ib) + s.setRestoreStepDone(2) + + // Step 4: Finalize + s.setRestoreStepRunning(3) // Save retrieval password from state if available retrievalPw := s.state.GetFormField("retrieval_password") @@ -730,7 +737,7 @@ func (s *Server) executeLocalRestore(drivePath, historyFile string) { // Queue DR event s.queueDREvent("local", ib.Timestamp, len(ib.DeployedStacks)) - s.setRestoreStepDone(2) + s.setRestoreStepDone(3) s.restoreMu.Lock() s.restoreRunning = false @@ -751,6 +758,7 @@ func (s *Server) executeHubRestore() { s.restoreError = "" s.restoreSteps = []RestoreStep{ {Label: "Konfiguráció visszaállítása...", Status: "running"}, + {Label: "Meghajtók csatolása...", Status: "pending"}, {Label: "Beállítás befejezése...", Status: "pending"}, } s.restoreMu.Unlock() @@ -767,16 +775,25 @@ func (s *Server) executeHubRestore() { } // Restore settings from infra backup if available + var restoredIB *report.InfraBackup if ibJSON != "" { var ib report.InfraBackup if err := json.Unmarshal([]byte(ibJSON), &ib); err == nil { s.restoreFromInfraBackup(&ib) + restoredIB = &ib } } s.setRestoreStepDone(0) - // Step 2: Finalize + // Step 2: Mount drives from disk layout s.setRestoreStepRunning(1) + if restoredIB != nil { + s.mountDrivesFromBackup(restoredIB) + } + s.setRestoreStepDone(1) + + // Step 3: Finalize + s.setRestoreStepRunning(2) // Save retrieval password retrievalPw := s.state.GetFormField("retrieval_password") @@ -790,16 +807,13 @@ func (s *Server) executeHubRestore() { // Queue DR event stackCount := 0 timestamp := "" - if ibJSON != "" { - var ib report.InfraBackup - if json.Unmarshal([]byte(ibJSON), &ib) == nil { - stackCount = len(ib.DeployedStacks) - timestamp = ib.Timestamp - } + if restoredIB != nil { + stackCount = len(restoredIB.DeployedStacks) + timestamp = restoredIB.Timestamp } s.queueDREvent("hub", timestamp, stackCount) - s.setRestoreStepDone(1) + s.setRestoreStepDone(2) s.restoreMu.Lock() s.restoreRunning = false @@ -863,6 +877,26 @@ func (s *Server) restoreFromInfraBackup(ib *report.InfraBackup) { } } +// mountDrivesFromBackup mounts drives from the infra backup's disk layout. +// Best-effort: logs warnings on failure but does not block restore. +func (s *Server) mountDrivesFromBackup(ib *report.InfraBackup) { + if len(ib.DiskLayout.Mounts) == 0 { + s.logger.Printf("[INFO] Setup: no drives in disk layout to mount") + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + mounted, err := backup.MountDrivesFromLayout(ctx, ib.DiskLayout, s.logger) + if err != nil { + s.logger.Printf("[WARN] Setup: drive mounting error: %v", err) + } + if len(mounted) > 0 { + s.logger.Printf("[INFO] Setup: mounted %d drive(s): %v", len(mounted), mounted) + } +} + func (s *Server) writeFreshConfig(configYAML, retrievalPassword string) error { configPath := "/opt/docker/felhom-controller/controller.yaml" if err := atomicWriteFile(configPath, []byte(configYAML), 0600); err != nil { diff --git a/controller/internal/setup/templates/setup_restore_exec.html b/controller/internal/setup/templates/setup_restore_exec.html index 92789ee..b927d96 100644 --- a/controller/internal/setup/templates/setup_restore_exec.html +++ b/controller/internal/setup/templates/setup_restore_exec.html @@ -33,6 +33,19 @@