C6B-F1 cause 1 + §8: additive .fab export (HDD binds AND named volumes)
executeExport no longer either/or gates user data on needs_hdd — a needs_hdd app bundles BOTH its HDD mounts and its named volumes (sonarr_config = the whole app DB was silently dropped pre-fix). exportHDDData returns error and fails LOUDLY on a basename collision between mounts (the manifest keys tars by basename; the old code silently overwrote the first tar). EstimateExport made additive to match, so the fits-on-dest gate counts both. Round-trip placement test proves a userdata tar restores to <HDD_PATH>/userdata through the untouched import mapping. Red-proofs recorded: either/or revert fails scenario A; collision-check removal fails the collision test.
This commit is contained in:
@@ -324,18 +324,24 @@ func (e *Exporter) executeExport(req ExportRequest, job *Job) {
|
||||
dataDir := filepath.Join(tmpDir, "data")
|
||||
os.MkdirAll(dataDir, 0755)
|
||||
|
||||
// C6B-F1 cause 1 (v0.130.0): user-data capture is ADDITIVE, not either/or. A needs_hdd app
|
||||
// can hold state in BOTH its HDD binds and its named volumes (sonarr: ${USERDATA_PATH} media
|
||||
// binds + the sonarr_config volume with the entire app DB) — the old else-branch silently
|
||||
// dropped every named volume of every needs_hdd app.
|
||||
if e.provider.GetStackNeedsHDD(req.StackName) {
|
||||
e.debugf("exporting HDD data for %s", req.StackName)
|
||||
e.exportHDDData(req.StackName, dataDir, manifest)
|
||||
e.debugf("HDD data exported: subdirs=%v hasData=%v", manifest.HDDSubdirs, manifest.HasHDDData)
|
||||
} else {
|
||||
e.debugf("exporting Docker volumes for %s", req.StackName)
|
||||
if err := e.exportVolumeData(req.StackName, dataDir, manifest); err != nil {
|
||||
e.failJob(job, step, fmt.Sprintf("Kötet mentése sikertelen: %v", err))
|
||||
if err := e.exportHDDData(req.StackName, dataDir, manifest); err != nil {
|
||||
e.failJob(job, step, fmt.Sprintf("Felhasználói adatok mentése sikertelen: %v", err))
|
||||
return
|
||||
}
|
||||
e.debugf("volume data exported: volumes=%v hasData=%v", manifest.VolumeNames, manifest.HasVolumeData)
|
||||
e.debugf("HDD data exported: subdirs=%v hasData=%v", manifest.HDDSubdirs, manifest.HasHDDData)
|
||||
}
|
||||
e.debugf("exporting Docker volumes for %s", req.StackName)
|
||||
if err := e.exportVolumeData(req.StackName, dataDir, manifest); err != nil {
|
||||
e.failJob(job, step, fmt.Sprintf("Kötet mentése sikertelen: %v", err))
|
||||
return
|
||||
}
|
||||
e.debugf("volume data exported: volumes=%v hasData=%v", manifest.VolumeNames, manifest.HasVolumeData)
|
||||
e.debugf("step 3 (user data) done in %v", time.Since(stepStart))
|
||||
|
||||
job.setStep(step, "done", "")
|
||||
@@ -581,8 +587,11 @@ func (e *Exporter) dumpDatabase(stackName, dbDir string, manifest *Manifest) boo
|
||||
return true
|
||||
}
|
||||
|
||||
// exportHDDData copies HDD bind mount data for the export.
|
||||
func (e *Exporter) exportHDDData(stackName, dataDir string, manifest *Manifest) {
|
||||
// exportHDDData copies HDD bind mount data for the export. v0.130.0 (C6B-F1 §8): a basename
|
||||
// collision between two mounts is a FATAL error (the manifest keys tars by basename; the old
|
||||
// code silently overwrote the first tar). A non-existent mount is still soft-skipped (honestly
|
||||
// absent from the manifest — the anti-hollow guard catches total emptiness).
|
||||
func (e *Exporter) exportHDDData(stackName, dataDir string, manifest *Manifest) error {
|
||||
hddDir := filepath.Join(dataDir, "hdd")
|
||||
os.MkdirAll(hddDir, 0755)
|
||||
|
||||
@@ -590,15 +599,25 @@ func (e *Exporter) exportHDDData(stackName, dataDir string, manifest *Manifest)
|
||||
e.debugf("HDD mounts for %s: %v (%d total)", stackName, mounts, len(mounts))
|
||||
if len(mounts) == 0 {
|
||||
e.debugf("no HDD mounts — skipping HDD data export")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
claimed := make(map[string]string) // subdir → mount that claimed it
|
||||
for _, mount := range mounts {
|
||||
if _, err := os.Stat(mount); os.IsNotExist(err) {
|
||||
e.debugf("HDD mount %s does not exist — skipping", mount)
|
||||
continue
|
||||
}
|
||||
subdir := filepath.Base(mount)
|
||||
// C6B-F1 §8 (v0.130.0): the manifest keys HDD tars by BASENAME (the import side maps a
|
||||
// basename back to a path), so two mounts sharing a basename cannot round-trip — the old
|
||||
// code silently overwrote the first tar with the second (silent partial data loss).
|
||||
// Renaming can't help either (the import couldn't map the new name), so the only honest
|
||||
// outcome is a loud failure.
|
||||
if prev, dup := claimed[subdir]; dup {
|
||||
return fmt.Errorf("két adatkönyvtár azonos névvel végződik (%q: %s és %s) — a csomag nem tudná megkülönböztetni őket", subdir, prev, mount)
|
||||
}
|
||||
claimed[subdir] = mount
|
||||
|
||||
tarPath := filepath.Join(hddDir, subdir+".tar")
|
||||
e.debugf("tarring HDD mount: %s → %s", mount, tarPath)
|
||||
@@ -616,6 +635,7 @@ func (e *Exporter) exportHDDData(stackName, dataDir string, manifest *Manifest)
|
||||
}
|
||||
}
|
||||
manifest.HasHDDData = len(manifest.HDDSubdirs) > 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// dockerExec is the docker-CLI seam (v0.125.0): runs `docker args...` with optional
|
||||
@@ -684,9 +704,11 @@ func (e *Exporter) exportVolumeTar(volName, tarPath string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// exportVolumeData exports Docker named volumes for apps without HDD storage. v0.125.0: a
|
||||
// failed volume export is FATAL (export must never report success on a hollow bundle — the
|
||||
// pre-fix WARN+continue is exactly how the data-loss bundles were born).
|
||||
// exportVolumeData exports the app's Docker named volumes. v0.130.0 (C6B-F1): runs for EVERY
|
||||
// app — needs_hdd apps hold state in named volumes too (sonarr_config = the whole app DB); the
|
||||
// pre-fix else-branch silently dropped them. v0.125.0: a failed volume export is FATAL (export
|
||||
// must never report success on a hollow bundle — the pre-fix WARN+continue is exactly how the
|
||||
// data-loss bundles were born).
|
||||
func (e *Exporter) exportVolumeData(stackName, dataDir string, manifest *Manifest) error {
|
||||
volDir := filepath.Join(dataDir, "volumes")
|
||||
os.MkdirAll(volDir, 0755)
|
||||
|
||||
Reference in New Issue
Block a user