fix(R-1220): an unreadable app.yaml counts as installed — its folders are never kept data

liveDriveBinds(drives): an app whose app.yaml exists but does not parse binds every folder its compose names on
every drive (fail closed); WARN per app. Red-proved (the live folder was listed). Commit only.
This commit is contained in:
2026-10-26 20:13:44 +01:00
parent e333477b11
commit 7e15a374b2
3 changed files with 73 additions and 2 deletions
+32 -2
View File
@@ -217,10 +217,31 @@ func readKeptMarker(dir string) *KeptMarker {
}
// liveDriveBinds is every drive folder an INSTALLED app binds — never kept data.
func (m *Manager) liveDriveBinds() []string {
//
// R-1220 — FAIL CLOSED ON AN UNREADABLE app.yaml. A parse error gives a nil AppConfig, so ScanStacks reads the app as
// „not installed"; before this, its live folder was then listed as kept data and offered for deletion. An app whose
// app.yaml EXISTS but cannot be read counts as installed: its HDD_PATH is unknown, so every folder its compose would bind
// on ANY of the given drives is live. One WARN per app per listing names it. Pinned by
// TestR1220_AnUnreadableAppYamlKeepsTheFolderLive.
func (m *Manager) liveDriveBinds(drives []string) []string {
var out []string
for _, st := range m.GetStacks() {
if st.ComposePath == "" {
continue
}
if !st.Deployed || st.AppConfig == nil {
if !st.Deployed && !appYAMLUnreadable(filepath.Dir(st.ComposePath)) {
continue // not installed: no app.yaml, or one that reads as not deployed
}
m.logger.Printf("[WARN] [stacks] kept: %s has an app.yaml that cannot be read — it counts as INSTALLED, so its drive folders are never listed as kept data (R-1220)", st.Name)
for _, d := range drives {
if d = strings.TrimSpace(d); d == "" {
continue
}
for _, p := range ParseComposeHDDMounts(st.ComposePath, filepath.Clean(d)) {
out = append(out, filepath.Clean(p))
}
}
continue
}
hdd := strings.TrimSpace(st.AppConfig.Env["HDD_PATH"])
@@ -234,6 +255,15 @@ func (m *Manager) liveDriveBinds() []string {
return out
}
// appYAMLUnreadable is true when the stack folder holds an app.yaml that LoadAppConfig cannot turn into a config — the
// R-1220 case. A missing app.yaml is false (a template that was never installed, or a removed app).
func appYAMLUnreadable(stackDir string) bool {
if _, err := os.Stat(filepath.Join(stackDir, "app.yaml")); err != nil {
return false
}
return LoadAppConfig(stackDir) == nil
}
func overlaps(a, b string) bool {
sep := string(filepath.Separator)
return a == b || strings.HasPrefix(a, b+sep) || strings.HasPrefix(b, a+sep)
@@ -284,7 +314,7 @@ var sizeFn = getDirSizeBytes
// ListKept lists every kept item on the given drive roots: the dated folders under <drive>/kept/<app>/,
// and each <drive>/appdata/<x> that holds something and that no installed app binds. Sorted newest first.
func (m *Manager) ListKept(drives []string) []KeptItem {
live := m.liveDriveBinds()
live := m.liveDriveBinds(drives)
var out []KeptItem
seen := map[string]bool{}
for _, d := range drives {