fix: mount drives after restore + poll-based redirect
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -33,6 +33,19 @@
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function waitForRestart() {
|
||||
fetch('/', {redirect: 'follow'})
|
||||
.then(function(r) {
|
||||
if (r.ok) {
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
setTimeout(waitForRestart, 2000);
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
setTimeout(waitForRestart, 2000);
|
||||
});
|
||||
}
|
||||
function poll() {
|
||||
fetch('/setup/restore/status')
|
||||
.then(function(r) { return r.json(); })
|
||||
@@ -59,15 +72,15 @@
|
||||
}
|
||||
if (data.done) {
|
||||
document.getElementById('done-msg').style.display = 'block';
|
||||
setTimeout(function() { window.location.href = '/'; }, 5000);
|
||||
setTimeout(waitForRestart, 3000);
|
||||
return;
|
||||
}
|
||||
setTimeout(poll, 1500);
|
||||
})
|
||||
.catch(function() {
|
||||
// Connection lost — controller may be restarting
|
||||
// Connection lost — controller is restarting
|
||||
document.getElementById('done-msg').style.display = 'block';
|
||||
setTimeout(function() { window.location.href = '/'; }, 5000);
|
||||
setTimeout(waitForRestart, 3000);
|
||||
});
|
||||
}
|
||||
poll();
|
||||
|
||||
Reference in New Issue
Block a user