feat: Docker volume backup, Tier 2 restore, restore dropdown fixes (v0.33.0)

- Add Docker named volume backup to Tier 1 (dump to tar, include in restic)
  and Tier 2 (copy tars to rsync mirror _volumes/ dir)
- Fix volume name resolution: use project-prefixed names (mealie_mealie_data)
- Fix double Tier 1 in restore dropdown: filter snapshots by app's home drive
- Add Tier 2 restore: RestoreAppFromTier2() restores from rsync mirror
- Show Tier 2 entry in restore dropdown when cross-drive backup succeeded
- Add .fab import link in restore section
- Volume-aware restore type banners and backup content labels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 21:43:02 +01:00
parent 5bf13ca19d
commit c929948f27
12 changed files with 655 additions and 45 deletions
+38 -1
View File
@@ -795,7 +795,18 @@ func (r *Router) backupSnapshots(w http.ResponseWriter, req *http.Request) {
return
}
snapshots, err := r.backupMgr.ListAllSnapshots(50)
stackName := req.URL.Query().Get("stack")
var snapshots []backup.SnapshotInfo
var err error
if stackName != "" {
// Per-app: only snapshots from the app's home drive
snapshots, err = r.backupMgr.ListSnapshotsForApp(stackName, 20)
} else {
// Fallback: all snapshots (general use)
snapshots, err = r.backupMgr.ListAllSnapshots(50)
}
if err != nil {
r.logger.Printf("[ERROR] [api] Failed to list backup snapshots: %v", err)
writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
@@ -814,6 +825,32 @@ func (r *Router) backupSnapshots(w http.ResponseWriter, req *http.Request) {
}
}
}
// Append Tier 2 (cross-drive rsync) entry if available for this app
if stackName != "" {
cdCfg := r.sett.GetCrossDriveConfig(stackName)
if cdCfg != nil && cdCfg.Enabled && cdCfg.LastStatus == "ok" && cdCfg.LastRun != "" {
lastRun, _ := time.Parse(time.RFC3339, cdCfg.LastRun)
if !lastRun.IsZero() {
// Resolve drive label for destination
var destLabel string
for _, sp := range storagePaths {
if sp.Path == cdCfg.DestinationPath {
destLabel = sp.Label
break
}
}
tier2 := backup.SnapshotInfo{
ID: "tier2-rsync",
Time: lastRun,
Tier: 2,
Source: "rsync",
DriveLabel: destLabel,
}
snapshots = append(snapshots, tier2)
}
}
}
}
if snapshots == nil {