diff --git a/CHANGELOG.md b/CHANGELOG.md index 192150f..e2edbbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ ## Changelog +### What was just completed (2026-02-17 session 38) +- **v0.12.2 — Restore Section Simplification (Bug 4 from v0.12.1 TASK.md):** + - **Feature: Snapshot filtering by app** — `GET /api/backup/snapshots?stack={name}` now filters snapshots to those whose `Paths` overlap with the app's HDD mount paths. Uses prefix matching (snapshot path is prefix of required, or vice versa). New `filterSnapshotsByPaths()` helper in `internal/api/router.go`. Manager gains `GetStackHDDMounts()` method to expose stackProvider's mount resolution. + - **Feature: Auto-stop/restart on restore** — `RestoreApp()` now stops the app's containers before running `restic restore` and restarts them after (even on failure). Avoids data corruption from live writes during restore. Eliminates the "Javasoljuk az alkalmazás leállítását" advisory from the UI. + - **Interface extension: StackDataProvider** — Added `StopStack(name string) error` and `StartStack(name string) error` to the `backup.StackDataProvider` interface in `internal/backup/appdata.go`. `stackAdapter` in `cmd/controller/main.go` wires these through to `stacks.Manager`. + - **UI simplification: Restore section** — Removed confusing "Visszaállítandó útvonalak" path list (technical detail not needed by customer). Snapshot dropdown now populated per-app (filtered) with human-friendly format: `2026-02-17 hétfő 03:00 (a3f2b1)`. Single calm warning replacing the triple-exclamation block. Empty filtered result shows inline message instead of empty dropdown. `data-paths` attribute removed from app dropdown options. + - **Files modified (6):** `internal/backup/appdata.go`, `internal/backup/backup.go`, `internal/backup/restore.go`, `internal/api/router.go`, `internal/web/templates/backups.html`, `cmd/controller/main.go` + ### What was just completed (2026-02-17 session 37) - **v0.12.0 — Backup Page Overhaul — Unified App Backup Status & Bug Fixes:** - **Bug Fix 1: Duplicate unconfigured apps** — `GetFullStatus()` now returns a deep copy of the cached status. `CrossDriveSummary`, `UnconfiguredApps`, and `CrossDriveWarnings` slices are always nil in the returned copy so the handler builds them fresh on every page load. Previously the handler appended to the cached slices, causing 3× duplication on 3 page loads. diff --git a/controller/README.md b/controller/README.md index c9741fb..86a5c32 100644 --- a/controller/README.md +++ b/controller/README.md @@ -24,7 +24,7 @@ controller generates secrets, saves app.yaml, runs `docker compose up -d`, and t with Traefik routing and health checks. The dashboard correctly shows real-time container states including health substatus (starting → healthy → running). -Current version: **v0.10.0** +Current version: **v0.12.2** ### What works - Dashboard with live container state (green/orange/yellow/red) diff --git a/controller/cmd/controller/main.go b/controller/cmd/controller/main.go index 430c6ba..5ac3674 100644 --- a/controller/cmd/controller/main.go +++ b/controller/cmd/controller/main.go @@ -407,6 +407,14 @@ func (a *stackAdapter) ListDeployedStacks() []backup.StackSummary { return result } +func (a *stackAdapter) StopStack(name string) error { + return a.mgr.StopStack(name) +} + +func (a *stackAdapter) StartStack(name string) error { + return a.mgr.StartStack(name) +} + func (a *stackAdapter) GetStackHDDMounts(name string) []string { s, ok := a.mgr.GetStack(name) if !ok { diff --git a/controller/internal/api/router.go b/controller/internal/api/router.go index 0397153..d680e16 100644 --- a/controller/internal/api/router.go +++ b/controller/internal/api/router.go @@ -445,7 +445,7 @@ func (r *Router) triggerBackup(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Mentés elindítva"}) } -func (r *Router) backupSnapshots(w http.ResponseWriter, _ *http.Request) { +func (r *Router) backupSnapshots(w http.ResponseWriter, req *http.Request) { if r.backupMgr == nil { writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: []interface{}{}}) return @@ -456,12 +456,39 @@ func (r *Router) backupSnapshots(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) return } + + // Filter by stack if requested — only return snapshots that include the app's HDD paths. + if stackName := req.URL.Query().Get("stack"); stackName != "" { + mounts := r.backupMgr.GetStackHDDMounts(stackName) + if len(mounts) > 0 { + snapshots = filterSnapshotsByPaths(snapshots, mounts) + } + } + if snapshots == nil { snapshots = []backup.SnapshotInfo{} } writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: snapshots}) } +// filterSnapshotsByPaths returns only snapshots whose Paths overlap with requiredPaths. +// A snapshot matches if any of its paths is a prefix of (or prefixed by) any required path. +func filterSnapshotsByPaths(snapshots []backup.SnapshotInfo, requiredPaths []string) []backup.SnapshotInfo { + var filtered []backup.SnapshotInfo +outer: + for _, snap := range snapshots { + for _, required := range requiredPaths { + for _, sp := range snap.Paths { + if strings.HasPrefix(required, sp) || strings.HasPrefix(sp, required) { + filtered = append(filtered, snap) + continue outer + } + } + } + } + return filtered +} + // --- Metrics handlers --- func (r *Router) metricsSystem(w http.ResponseWriter, req *http.Request) { diff --git a/controller/internal/backup/appdata.go b/controller/internal/backup/appdata.go index 7a141dc..6b9e6fd 100644 --- a/controller/internal/backup/appdata.go +++ b/controller/internal/backup/appdata.go @@ -14,6 +14,8 @@ type StackDataProvider interface { GetStackComposePath(name string) (composePath string, ok bool) ListDeployedStacks() []StackSummary GetStackHDDMounts(name string) []string + StopStack(name string) error + StartStack(name string) error } // StackSummary holds minimal stack info needed for app data discovery. diff --git a/controller/internal/backup/backup.go b/controller/internal/backup/backup.go index 1191b73..096e09d 100644 --- a/controller/internal/backup/backup.go +++ b/controller/internal/backup/backup.go @@ -414,6 +414,14 @@ func (m *Manager) SetStackProvider(provider StackDataProvider) { m.stackProvider = provider } +// GetStackHDDMounts returns HDD mount paths for the named stack via the stack provider. +func (m *Manager) GetStackHDDMounts(name string) []string { + if m.stackProvider == nil { + return nil + } + return m.stackProvider.GetStackHDDMounts(name) +} + // resolveAppBackupPaths returns HDD paths for all enabled app backups. func (m *Manager) resolveAppBackupPaths() []string { if m.stackProvider == nil || m.settings == nil { diff --git a/controller/internal/backup/restore.go b/controller/internal/backup/restore.go index f826e3b..0c00cd2 100644 --- a/controller/internal/backup/restore.go +++ b/controller/internal/backup/restore.go @@ -51,12 +51,26 @@ func (m *Manager) RestoreApp(stackName, snapshotID string) error { m.logger.Printf("[WARN] RESTORE starting: stack=%s, snapshot=%s, paths=%v", stackName, snapshotID, hddMounts) + // Stop the app before restore to avoid data corruption + if err := m.stackProvider.StopStack(stackName); err != nil { + m.logger.Printf("[WARN] RESTORE could not stop %s before restore: %v (proceeding anyway)", stackName, err) + } + // Execute restore if err := m.restic.RestoreAppData(snapshotID, hddMounts); err != nil { m.logger.Printf("[ERROR] RESTORE failed for %s: %v", stackName, err) + // Try to restart the app even on failure + if startErr := m.stackProvider.StartStack(stackName); startErr != nil { + m.logger.Printf("[WARN] RESTORE could not restart %s after failed restore: %v", stackName, startErr) + } return err } + // Restart the app after successful restore + if err := m.stackProvider.StartStack(stackName); err != nil { + m.logger.Printf("[WARN] RESTORE could not restart %s after restore: %v", stackName, err) + } + m.logger.Printf("[INFO] RESTORE completed: stack=%s, snapshot=%s", stackName, snapshotID) return nil } diff --git a/controller/internal/web/templates/backups.html b/controller/internal/web/templates/backups.html index d44f0cb..1c4eb00 100644 --- a/controller/internal/web/templates/backups.html +++ b/controller/internal/web/templates/backups.html @@ -451,31 +451,28 @@ {{range .Backup.AppDataInfo}} {{if and .HasHDDData .BackupEnabled}} - + {{end}} {{end}}