From 80b756f0e43a6d653bdeafbcbaf6b031ac455e1f Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 26 Feb 2026 15:07:38 +0100 Subject: [PATCH] fix: mount drives after restore + poll-based redirect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore flow now calls MountDrivesFromLayout() after writing config, which mounts drives by UUID and adds fstab entries. Previously drives from the infra backup were never mounted, causing "Adattároló nem elérhető" warnings. Post-restore redirect now polls until the controller responds instead of using a fixed 5-second timeout that was too short for container restart. Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 2 + controller/internal/setup/handlers.go | 54 +++++++++++++++---- .../setup/templates/setup_restore_exec.html | 19 +++++-- 3 files changed, 62 insertions(+), 13 deletions(-) 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 @@