v0.51.0: offsite-backup UI (felhom-pbs DR) + Model-A double-nest fix
- Backups page: whole-guest backup shown as real DR — target label "Biztonsági szerver – külön hardver (PBS)"; app-data "Távoli mentés" card now reflects the PBS offsite tier (guestBackupView.Offsite) instead of "nincs beállítva". - Model-A double-nest fix: appbackup path helpers take a felhom-data NAMESPACE ROOT (no internal felhom-data join); backup.Manager.namespaceRoot/AppNamespaceRoot resolve HDD-vs-systemDataPath provenance so a drive-resident app's backups land single-nested (<drive>/backups/... on the guest = <drive>/felhom-data/backups/... on the host) instead of .../felhom-data/felhom-data/.... Writes, deletion (GetStackBackupData/RemoveStack/ ProtectedHDDPaths), wipe-warning scan, and export updated coherently; legacy double-nest dirs kept protected. New appbackup test asserts no doubled segment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -87,19 +87,26 @@ func humanizeBytes(b int64) string {
|
||||
}
|
||||
|
||||
// --- function forwarders (paths) ---
|
||||
//
|
||||
// NOTE: the path helpers below take a felhom-data NAMESPACE ROOT, not a bare drive path. Use
|
||||
// NamespaceRoot (or Manager.namespaceRoot / Manager.AppNamespaceRoot) to resolve the root first.
|
||||
|
||||
func PrimaryBackupPath(drivePath string) string {
|
||||
return appbackup.PrimaryBackupPath(drivePath)
|
||||
func NamespaceRoot(drivePath string, inGuestDrive bool) string {
|
||||
return appbackup.NamespaceRoot(drivePath, inGuestDrive)
|
||||
}
|
||||
|
||||
func AppDBDumpPath(drivePath, stackName string) string {
|
||||
return appbackup.AppDBDumpPath(drivePath, stackName)
|
||||
func PrimaryBackupPath(nsRoot string) string {
|
||||
return appbackup.PrimaryBackupPath(nsRoot)
|
||||
}
|
||||
|
||||
func AppVolumeDumpPath(drivePath, stackName string) string {
|
||||
return appbackup.AppVolumeDumpPath(drivePath, stackName)
|
||||
func AppDBDumpPath(nsRoot, stackName string) string {
|
||||
return appbackup.AppDBDumpPath(nsRoot, stackName)
|
||||
}
|
||||
|
||||
func AppDataDir(drivePath, stackName string) string {
|
||||
return appbackup.AppDataDir(drivePath, stackName)
|
||||
func AppVolumeDumpPath(nsRoot, stackName string) string {
|
||||
return appbackup.AppVolumeDumpPath(nsRoot, stackName)
|
||||
}
|
||||
|
||||
func AppDataDir(nsRoot, stackName string) string {
|
||||
return appbackup.AppDataDir(nsRoot, stackName)
|
||||
}
|
||||
|
||||
@@ -93,6 +93,26 @@ func (m *Manager) GetAppDrivePath(stackName string) string {
|
||||
return m.systemDataPath
|
||||
}
|
||||
|
||||
// namespaceRoot maps an app's drive path to its felhom-data namespace ROOT (the dir that directly
|
||||
// holds backups/ and appdata/). A drive-resident app's in-guest mount IS the namespace already
|
||||
// (Model A, slice 10 — the agent binds <drive>/felhom-data onto the guest mountpoint), so it is used
|
||||
// as-is; only the SSD-only system-data fallback gets the felhom-data subdir appended. This is what
|
||||
// keeps a drive-resident app's backups single-nested instead of .../felhom-data/felhom-data/... .
|
||||
func (m *Manager) namespaceRoot(drivePath string) string {
|
||||
return NamespaceRoot(drivePath, drivePath != m.systemDataPath)
|
||||
}
|
||||
|
||||
// AppNamespaceRoot returns the felhom-data namespace root for a stack's keep-side backups, resolving
|
||||
// HDD-vs-system provenance internally. For callers outside this package that only know the stack
|
||||
// name (e.g. the API router) so they don't double-nest the felhom-data segment.
|
||||
func (m *Manager) AppNamespaceRoot(stackName string) string {
|
||||
drivePath := m.GetAppDrivePath(stackName)
|
||||
if drivePath == "" {
|
||||
return ""
|
||||
}
|
||||
return m.namespaceRoot(drivePath)
|
||||
}
|
||||
|
||||
// groupStacksByDrive groups deployed stacks by their home drive path.
|
||||
func (m *Manager) groupStacksByDrive() map[string][]StackSummary {
|
||||
if m.stackProvider == nil {
|
||||
@@ -170,7 +190,7 @@ func (m *Manager) runDBDumpsInternal(ctx context.Context) error {
|
||||
continue
|
||||
}
|
||||
|
||||
dumpDir := AppDBDumpPath(drivePath, db.StackName)
|
||||
dumpDir := AppDBDumpPath(m.namespaceRoot(drivePath), db.StackName)
|
||||
|
||||
result := DumpOne(ctx, db, dumpDir, m.logger, m.isDebug())
|
||||
results = append(results, result)
|
||||
@@ -239,7 +259,7 @@ func (m *Manager) DumpAppVolumes(stackName string) error {
|
||||
return fmt.Errorf("cannot determine drive path for %s", stackName)
|
||||
}
|
||||
|
||||
dumpDir := AppVolumeDumpPath(drivePath, stackName)
|
||||
dumpDir := AppVolumeDumpPath(m.namespaceRoot(drivePath), stackName)
|
||||
if err := os.MkdirAll(dumpDir, 0755); err != nil {
|
||||
return fmt.Errorf("creating volume dump dir: %w", err)
|
||||
}
|
||||
@@ -395,7 +415,7 @@ func (m *Manager) DumpStackDB(ctx context.Context, stackName string) error {
|
||||
if drivePath == "" || !filepath.IsAbs(drivePath) {
|
||||
return fmt.Errorf("cannot determine absolute drive path for %s (systemDataPath not configured?)", stackName)
|
||||
}
|
||||
dumpDir := AppDBDumpPath(drivePath, stackName)
|
||||
dumpDir := AppDBDumpPath(m.namespaceRoot(drivePath), stackName)
|
||||
|
||||
m.logger.Printf("[INFO] [backup] Running pre-backup DB dump for %s (%d database(s)) → %s", stackName, len(stackDBs), dumpDir)
|
||||
|
||||
@@ -428,7 +448,7 @@ func (m *Manager) listAllDumpFiles() []DumpFileInfo {
|
||||
var allFiles []DumpFileInfo
|
||||
for drive, stacks := range m.groupStacksByDrive() {
|
||||
for _, stack := range stacks {
|
||||
dumpDir := AppDBDumpPath(drive, stack.Name)
|
||||
dumpDir := AppDBDumpPath(m.namespaceRoot(drive), stack.Name)
|
||||
if files, err := ListDumpFiles(dumpDir); err == nil {
|
||||
allFiles = append(allFiles, files...)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func (m *Manager) RestoreApp(stackName, snapshotID string) error {
|
||||
|
||||
// restoreDockerVolumes populates Docker volumes from tar files in the volume dump directory.
|
||||
func (m *Manager) restoreDockerVolumes(stackName, drivePath string) error {
|
||||
dumpDir := AppVolumeDumpPath(drivePath, stackName)
|
||||
dumpDir := AppVolumeDumpPath(m.namespaceRoot(drivePath), stackName)
|
||||
entries, err := os.ReadDir(dumpDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
||||
Reference in New Issue
Block a user