From 4fd907a09eeaff3b72255da3713a965c71e243b2 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 27 Feb 2026 10:48:00 +0100 Subject: [PATCH] fix: Tier2 backup status now detects drives removed from storage (not just disconnected) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, removing a storage drive from the controller only marked it as disconnected if the StoragePath entry still existed with Disconnected:true. Drives removed entirely from storage_paths were invisible to the check, causing Tier2 backup UI to show green "Sikeres" and scheduler to attempt backups to a no-longer-managed destination. New IsStoragePathKnown() method covers both cases. UI shows yellow "Cél meghajtó leválasztva" and scheduler skips silently. Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 4 +++- controller/internal/backup/crossdrive.go | 4 ++++ controller/internal/settings/settings.go | 14 ++++++++++++++ controller/internal/web/handlers.go | 7 +++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b44e4cd..f82eef3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,11 @@ #### Fixed - **system/mounts_linux.go**: `IsUSBDevice()` and `diskModel()` now strip findmnt bind-mount suffix (`[/subdir]`) before parsing device path — fixes USB badge and disk model not showing for drives mounted via the attach wizard - **backup/crossdrive.go**: Disconnected source/destination drives now silently skip with WARN log instead of returning error — prevents noisy error aggregation in `RunAllScheduled()` and false "failed" counts +- **web/handlers.go + backup/crossdrive.go**: Tier2 destination check now also covers drives **removed** from storage (not just marked disconnected) — `IsStoragePathKnown()` detects when destination path is no longer in any registered storage, UI shows yellow "Cél meghajtó leválasztva" and scheduler skips silently #### Added -- **web/handlers.go**: New `Tier2DestDisconnected` field on `AppBackupRow` — detects when a Tier2 backup destination drive is disconnected, sets yellow status dot ("2. mentés szünetel") instead of red +- **settings/settings.go**: New `IsStoragePathKnown(path)` method — returns whether a path belongs to any registered storage (connected, disconnected, or decommissioned); paths removed entirely return false +- **web/handlers.go**: New `Tier2DestDisconnected` field on `AppBackupRow` — detects when a Tier2 backup destination drive is disconnected or removed, sets yellow status dot ("2. mentés szünetel") instead of red - **web/templates/backups.html**: New template branch for disconnected Tier2 destinations — shows "Cél meghajtó leválasztva" warning badge with grayed-out info, hides "Futtatás most" button ### v0.32.4 — Controller telemetry: include controller in hub app telemetry (2026-02-27) diff --git a/controller/internal/backup/crossdrive.go b/controller/internal/backup/crossdrive.go index 901b122..c05f37a 100644 --- a/controller/internal/backup/crossdrive.go +++ b/controller/internal/backup/crossdrive.go @@ -100,6 +100,10 @@ func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) e r.logger.Printf("[WARN] [backup] Cross-drive backup skipped for %s: destination drive disconnected (%s)", stackName, cfg.DestinationPath) return nil } + if !r.sett.IsStoragePathKnown(cfg.DestinationPath) { + r.logger.Printf("[WARN] [backup] Cross-drive backup skipped for %s: destination not a registered storage (%s)", stackName, cfg.DestinationPath) + return nil + } // Mark as running in settings _ = r.sett.UpdateCrossDriveStatus(stackName, func(c *settings.CrossDriveBackup) { diff --git a/controller/internal/settings/settings.go b/controller/internal/settings/settings.go index fd6900e..76f438b 100644 --- a/controller/internal/settings/settings.go +++ b/controller/internal/settings/settings.go @@ -693,6 +693,20 @@ func (s *Settings) GetConnectedPaths() []StoragePath { return result } +// IsStoragePathKnown returns whether a path belongs to any registered storage path +// (connected, disconnected, or decommissioned). A path removed entirely from +// storage_paths is NOT known. +func (s *Settings) IsStoragePathKnown(path string) bool { + s.mu.RLock() + defer s.mu.RUnlock() + for _, sp := range s.StoragePaths { + if path == sp.Path || strings.HasPrefix(path, sp.Path+"/") { + return true + } + } + return false +} + // GetStoppedStacks returns the list of stacks that were auto-stopped for a storage path. func (s *Settings) GetStoppedStacks(path string) []string { s.mu.RLock() diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index a5846db..e9355b9 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -938,6 +938,13 @@ func (s *Server) buildAppBackupRows( } } + // Also treat as disconnected if dest was removed from storage entirely + if cfg.DestinationPath != "" && !row.Tier2DestDisconnected { + if !s.settings.IsStoragePathKnown(cfg.DestinationPath) { + row.Tier2DestDisconnected = true + } + } + if row.Tier2DestDisconnected { // Disconnected destination — treat as paused, not failed row.Status = "yellow"