C6B-F1 cause 2: .fab export mount discovery resolves the userdata convention
exportAdapter.GetStackHDDMounts now returns stacks.ExportDataMounts — the
${HDD_PATH} binds UNIONed with the ${USERDATA_PATH} ROOT (captured at the
root, not per-bind, so the manifest's basename keying round-trips through the
existing import mapping without touching restore). Containment-aware dedupe
both directions. The backup-side stackAdapter is intentionally unchanged.
Red-proof: pre-fix behavior fails TestExportDataMounts_UserdataConvention/
MixedBindsUnion/LiteralUserdataBindDeduped (run->fail->revert recorded).
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
||||
)
|
||||
|
||||
// felhomDataDir matches backup.FelhomDataDir — duplicated to avoid circular import via StackDataProvider.
|
||||
@@ -552,6 +554,49 @@ func ParseComposeUserdataMounts(composePath, userdataPath string) []string {
|
||||
return mounts
|
||||
}
|
||||
|
||||
// ExportDataMounts returns the host directories a .fab export must capture for an app: the
|
||||
// ${HDD_PATH}-referencing bind mounts PLUS — when the compose binds ${USERDATA_PATH} (the standard
|
||||
// felhom convention; USERDATA_PATH = <HDD_PATH>/userdata, injected at deploy by withUserdataPath) —
|
||||
// the userdata ROOT as a single entry. C6B-F1 (v0.130.0): ParseComposeHDDMounts alone never
|
||||
// resolves ${USERDATA_PATH}, so 12/13 needs_hdd catalog apps exported ZERO userdata (a silent
|
||||
// hollow bundle that passed the v0.125.0 guard).
|
||||
//
|
||||
// The userdata subtree is deliberately captured at its ROOT, not per-bind: the .fab manifest keys
|
||||
// HDD tars by basename, and the import side maps a basename either to a resolved ${HDD_PATH} mount
|
||||
// or to <HDD_PATH>/<basename> — "userdata" round-trips through that mapping exactly, while a
|
||||
// nested bind like ${USERDATA_PATH}/media/tv would base to "tv" and restore to the wrong place.
|
||||
// The root also covers sibling dirs the app created beyond its declared binds (same philosophy as
|
||||
// the tier-2 namespace-wholesale copy).
|
||||
//
|
||||
// Dedupe is containment-aware in both directions: the userdata root is skipped when an HDD mount
|
||||
// already covers it (an app binding ${HDD_PATH} itself), and HDD mounts inside the userdata root
|
||||
// are dropped when the root is added (a literal ${HDD_PATH}/userdata/x bind would otherwise
|
||||
// double-tar and basename-collide with the root).
|
||||
func ExportDataMounts(composePath, hddPath string) []string {
|
||||
if hddPath == "" {
|
||||
return nil
|
||||
}
|
||||
hddMounts := ParseComposeHDDMounts(composePath, hddPath)
|
||||
ud := appbackup.UserdataDir(filepath.Clean(hddPath))
|
||||
if len(ParseComposeUserdataMounts(composePath, ud)) == 0 {
|
||||
return hddMounts
|
||||
}
|
||||
for _, m := range hddMounts {
|
||||
if m == ud || strings.HasPrefix(ud, m+string(filepath.Separator)) {
|
||||
// an HDD mount already covers the userdata root — nothing to add
|
||||
return hddMounts
|
||||
}
|
||||
}
|
||||
mounts := make([]string, 0, len(hddMounts)+1)
|
||||
for _, m := range hddMounts {
|
||||
if strings.HasPrefix(m, ud+string(filepath.Separator)) {
|
||||
continue // inside the userdata root — the root tar covers it
|
||||
}
|
||||
mounts = append(mounts, m)
|
||||
}
|
||||
return append(mounts, ud)
|
||||
}
|
||||
|
||||
// ParseComposeHDDMounts reads a docker-compose.yml and extracts host paths
|
||||
// that reference the HDD path from volume bind mounts.
|
||||
func ParseComposeHDDMounts(composePath, hddPath string) []string {
|
||||
|
||||
Reference in New Issue
Block a user