Files
felhom-controller/controller/internal/backup/appbackup_bridge.go
T
admin c732006d26 R-102 phase 1.1: split the recovery-unit path helpers, zero behaviour change
Every unit path helper took (nsRoot, stackName) and joined backups/primary/<stack>/... . That
hard-coded 'primary' is the mechanism of R-102: Tier-2 mirrors the whole unit directory to
<dest>/backups/secondary/<stack>/recovery-unit/ every night, and because no reader could NAME a
unit outside backups/primary/, that mirror has been captured for months and read by nothing.

Adds four unit-directory-relative primitives - UnitComposeDir, UnitManifestFile, UnitDBDumpDir,
UnitVolumeDumpDir - each taking the recovery-unit DIRECTORY itself. The four existing
(nsRoot, stackName) helpers become thin wrappers over them and keep their exact signatures and
their exact return values; every current caller compiles untouched.

ONE implementation, two callers - the rule restoreDockerVolumesFrom already follows in this repo.

TestR102_PathWrappersAreByteIdenticalToToday pins the wrappers against hand-written literals (not
re-derived from the helpers under test). Red-proof: UnitComposeDir join changed to 'compose2' ->
the test fails on all three fixtures.
2026-08-31 11:16:02 +02:00

182 lines
6.6 KiB
Go

package backup
// This file bridges the backup package to internal/appbackup, where the
// self-contained app-data backup primitives (DB dump, Docker-volume archive
// discovery, keep-side path helpers) now live. The backup package keeps these
// names available — via type/const aliases and thin function forwarders — so
// the (still present) delete-side code and the both-side consumers (web, api,
// report) compile unchanged. Behaviour is identical: the forwarders call
// straight through to appbackup.
//
// Go has no function aliasing, so the functions are one-line forwarders while
// the types/consts use real aliases.
import (
"context"
"log"
"time"
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
)
// --- type aliases (appdata) ---
type StackDataProvider = appbackup.StackDataProvider
type StackSummary = appbackup.StackSummary
type AppBackupInfo = appbackup.AppBackupInfo
type AppDataPath = appbackup.AppDataPath
type AppDockerVolume = appbackup.AppDockerVolume
type RecoveryInfo = appbackup.RecoveryInfo
type ClassifiedBind = appbackup.ClassifiedBind
// --- type aliases (dbdump) ---
type DBType = appbackup.DBType
type DiscoveredDB = appbackup.DiscoveredDB
type DumpResult = appbackup.DumpResult
type DumpValidation = appbackup.DumpValidation
type DumpFileInfo = appbackup.DumpFileInfo
// --- const aliases ---
const (
DBTypePostgres = appbackup.DBTypePostgres
DBTypeMariaDB = appbackup.DBTypeMariaDB
)
// Backup-classification class constants (Task 3-core) — aliased so the tier engines can switch on
// class without importing appbackup directly.
const (
ClassMandatory = appbackup.ClassMandatory
ClassOptional = appbackup.ClassOptional
ClassExcluded = appbackup.ClassExcluded
)
// FelhomDataDir is the namespace directory on storage drives for all felhom-managed data.
const FelhomDataDir = appbackup.FelhomDataDir
// --- function forwarders (dbdump) ---
func DiscoverDatabases(ctx context.Context, logger *log.Logger, debug bool, knownStacks []string) ([]DiscoveredDB, error) {
return appbackup.DiscoverDatabases(ctx, logger, debug, knownStacks)
}
func DumpAll(ctx context.Context, dbs []DiscoveredDB, dumpDir string, logger *log.Logger, debug bool) []DumpResult {
return appbackup.DumpAll(ctx, dbs, dumpDir, logger, debug)
}
func DumpOne(ctx context.Context, db DiscoveredDB, dumpDir string, logger *log.Logger, debug bool) DumpResult {
return appbackup.DumpOne(ctx, db, dumpDir, logger, debug)
}
// DumpOneTo dumps to an EXPLICIT final path (R-361). The safety dump uses it so it never names — and
// therefore never destroys — the app's own `<stack>-<dbtype>.sql`.
func DumpOneTo(ctx context.Context, db DiscoveredDB, finalPath string, logger *log.Logger, debug bool) DumpResult {
return appbackup.DumpOneTo(ctx, db, finalPath, logger, debug)
}
// ImportDump replays a captured .sql dump back into a running DB container (F17 restore path).
func ImportDump(ctx context.Context, db DiscoveredDB, dumpPath string, logger *log.Logger, debug bool) error {
return appbackup.ImportDump(ctx, db, dumpPath, logger, debug)
}
func ValidateDump(filePath string, dbType DBType) DumpValidation {
return appbackup.ValidateDump(filePath, dbType)
}
func ListDumpFiles(dumpDir string, cached func(name string, size int64, mod time.Time) (DumpValidation, bool)) ([]DumpFileInfo, error) {
return appbackup.ListDumpFiles(dumpDir, cached)
}
// --- function forwarders (appdata) ---
func DiscoverAppData(provider StackDataProvider, discoveredDBs []DiscoveredDB) []AppBackupInfo {
return appbackup.DiscoverAppData(provider, discoveredDBs)
}
func ParseComposeNamedVolumes(composePath string) []AppDockerVolume {
return appbackup.ParseComposeNamedVolumes(composePath)
}
func ResolveDockerVolumeNames(composePath string) []string {
return appbackup.ResolveDockerVolumeNames(composePath)
}
func ParseComposeImages(composePath string) []string {
return appbackup.ParseComposeImages(composePath)
}
// DBServiceNames forwards to appbackup.DBServiceNames — the compose SERVICE names holding a database,
// i.e. the argument list for the DB-only bring-up both restore paths use before a dump replay (R-47).
func DBServiceNames(composePath string) ([]string, error) {
return appbackup.DBServiceNames(composePath)
}
// humanizeBytes forwards to appbackup.HumanizeBytes; kept unexported so the
// many in-package call sites (backup.go, crossdrive.go, restore code) need no edit.
func humanizeBytes(b int64) string {
return appbackup.HumanizeBytes(b)
}
// --- 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 NamespaceRoot(drivePath string, inGuestDrive bool) string {
return appbackup.NamespaceRoot(drivePath, inGuestDrive)
}
// NamespaceRootFor re-exports the ONE drive-kind-aware resolver (R-203).
func NamespaceRootFor(drivePath, systemDataPath string) string {
return appbackup.NamespaceRootFor(drivePath, systemDataPath)
}
func PrimaryBackupPath(nsRoot string) string {
return appbackup.PrimaryBackupPath(nsRoot)
}
func AppDBDumpPath(nsRoot, stackName string) string {
return appbackup.AppDBDumpPath(nsRoot, stackName)
}
func AppVolumeDumpPath(nsRoot, stackName string) string {
return appbackup.AppVolumeDumpPath(nsRoot, stackName)
}
func RecoveryUnitPath(nsRoot, stackName string) string {
return appbackup.RecoveryUnitPath(nsRoot, stackName)
}
func RecoveryUnitComposePath(nsRoot, stackName string) string {
return appbackup.RecoveryUnitComposePath(nsRoot, stackName)
}
func RecoveryUnitManifestPath(nsRoot, stackName string) string {
return appbackup.RecoveryUnitManifestPath(nsRoot, stackName)
}
// The four UNIT-DIRECTORY-RELATIVE helpers (R-102). They take the recovery-unit DIRECTORY itself, so
// a reader can name a unit that is NOT under backups/primary/ — the Tier-2 mirror at
// <dest>/backups/secondary/<stack>/recovery-unit/ being the whole point.
func UnitComposeDir(unitDir string) string { return appbackup.UnitComposeDir(unitDir) }
func UnitManifestFile(unitDir string) string { return appbackup.UnitManifestFile(unitDir) }
func UnitDBDumpDir(unitDir string) string { return appbackup.UnitDBDumpDir(unitDir) }
func UnitVolumeDumpDir(unitDir string) string { return appbackup.UnitVolumeDumpDir(unitDir) }
func AppDataDir(nsRoot, stackName string) string {
return appbackup.AppDataDir(nsRoot, stackName)
}
func AppDataDirNames(hddPath, stackName string, hddMounts []string) []string {
return appbackup.AppDataDirNames(hddPath, stackName, hddMounts)
}
func AppDataBindsPresent(hddPath string, hddMounts []string) bool {
return appbackup.AppDataBindsPresent(hddPath, hddMounts)
}