73efb091d9
gates / gates (push) Successful in 9s
appbackup's path helpers take a NAMESPACE ROOT. Five call sites passed a bare DRIVE path.
On an enrolled drive the two coincide, so nothing showed; on the system-data fallback they
differ by exactly the felhom-data segment, and the app then bound a directory the off-site
capture set never looked at -- while the run reported ok. Measured live on demo-hp: the app
wrote to /mnt/sys_drive/userdata/media/books, the capture set looked for
/mnt/sys_drive/felhom-data/userdata/media/books.
THE RULE NOW HAS ONE EXPRESSION. appbackup.NamespaceRootFor / IsEnrolledDrive encode the
drive-kind comparison; backup.Manager.namespaceRoot and stacks.Manager.inGuest delegate to
it. There were already TWO copies and they differed -- the backup package's compared without
filepath.Clean, the stacks package's with it, so a trailing slash from config would have
flipped the mode in one and not the other.
Sites routed through it:
- stacks/deploy.go withPathVars -> ${USERDATA_PATH} (the live defect)
- appexport/fabplan.go + export.go (via a new provider method)
- web/handlers.go FileBrowser mounts (latent: the system drive is
deliberately never a registered StoragePath, so this is the identity today)
ComputeFabBuckets now receives the namespace root, which is what ComputeCaptureSet has always
received -- so the export's classified paths and the backup's capture set describe the same
directories by construction instead of by coincidence.
Tests are table-driven over BOTH drive kinds, because this survived by being invisible on the
kind that already worked. Red-proofs observed: restoring the bare-path call fails the
system-drive row with the two paths differing by /felhom-data; inverting the drive-kind
comparison fails every enrolled row.
67 lines
3.5 KiB
Go
67 lines
3.5 KiB
Go
// Package appexport provides per-app export/import via .fab bundles.
|
|
// A .fab file is a tar.gz (optionally password-encrypted) containing an app's
|
|
// config, database dump, and all user data — everything needed to restore
|
|
// the app to its current state.
|
|
package appexport
|
|
|
|
import "gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
|
|
|
// ExportStackProvider provides stack data without circular imports.
|
|
// Implemented by exportAdapter in main.go (same pattern as backup.StackDataProvider).
|
|
type ExportStackProvider interface {
|
|
// GetStackDir returns the stack's directory path (e.g., /opt/docker/stacks/nextcloud).
|
|
GetStackDir(name string) (string, bool)
|
|
// GetStackComposePath returns the compose file path.
|
|
GetStackComposePath(name string) (string, bool)
|
|
// GetStackHDDMounts returns resolved HDD bind mount host paths for the stack.
|
|
GetStackHDDMounts(name string) []string
|
|
// GetStackHDDPath returns the raw HDD_PATH env var from app.yaml.
|
|
GetStackHDDPath(name string) string
|
|
// GetImportRoot returns the CANONICAL drop-zone root (R-75), on the SYSTEM drive. ${IMPORT_PATH}
|
|
// binds resolve against THIS, never against GetStackHDDPath. Empty when unresolvable.
|
|
GetImportRoot() string
|
|
// GetStackNamespaceRoot returns the app's felhom-data NAMESPACE ROOT — the directory that directly
|
|
// contains backups/ and userdata/. It is NOT GetStackHDDPath: on an enrolled drive the two are the
|
|
// same, and on the system-data fallback the namespace root has one more segment (R-203). Every
|
|
// appbackup path helper takes THIS, never the drive path. Empty when the app has no HDD_PATH.
|
|
GetStackNamespaceRoot(name string) string
|
|
// GetStackClassifiedBinds returns the app's backup-classified compose binds + whether it carries a
|
|
// (valid) backup block (Task 2). Drives the `.fab` class-scoped export plan (Task 4); a legacy app
|
|
// (false) exports the v0.130.0 full-root capture unchanged.
|
|
GetStackClassifiedBinds(name string) ([]appbackup.ClassifiedBind, bool)
|
|
// IsStackRunning returns true if the stack has running containers.
|
|
IsStackRunning(name string) bool
|
|
// StopStack stops the stack via docker compose down.
|
|
StopStack(name string) error
|
|
// StartStack starts the stack via docker compose up -d.
|
|
StartStack(name string) error
|
|
// GetStackDisplayName returns the human-readable name from .felhom.yml.
|
|
GetStackDisplayName(name string) string
|
|
// GetStackNeedsHDD returns true if the app requires HDD storage.
|
|
GetStackNeedsHDD(name string) bool
|
|
// GetDockerVolumes returns named Docker volume names from the compose file.
|
|
GetDockerVolumes(name string) []string
|
|
// IsStackDeployed returns true if the stack has a saved app.yaml config.
|
|
IsStackDeployed(name string) bool
|
|
// GetDecryptedEnv returns the decrypted env var map from app.yaml.
|
|
GetDecryptedEnv(name string) map[string]string
|
|
|
|
// --- Import-specific methods ---
|
|
|
|
// GetStacksBaseDir returns the base directory where stacks live (e.g., /opt/docker/stacks).
|
|
GetStacksBaseDir() string
|
|
// SaveEncryptedAppConfig saves app.yaml with re-encrypted sensitive fields.
|
|
// env is the plaintext env map from the bundle; encryption uses the current server key.
|
|
SaveEncryptedAppConfig(stackDir string, env map[string]string) error
|
|
// RefreshStacks rescans all stacks and refreshes container state.
|
|
RefreshStacks() error
|
|
// RemoveStackVolumes stops the stack and removes its named Docker volumes.
|
|
RemoveStackVolumes(name string) error
|
|
}
|
|
|
|
// DrivePathInfo holds a registered storage path and its label.
|
|
type DrivePathInfo struct {
|
|
Path string
|
|
Label string
|
|
}
|