43e99d160c
gates / gates (push) Successful in 26s
An install over an app's kept drive folder (appdata/<app> non-empty) asks the household: "use my kept data" (a load from the newest copy of THIS drive's install, own unit or second-drive mirror, then the template's after_load) or "start fresh" (the folder is renamed into <drive>/kept/<app>/<date>/ with the removed app's unit; nothing deleted). The install API answers 409 kept_data_choice until one is chosen; DeployStack refuses too. New page Megorzott adatok / Kept data (/kept-data): Load / Look / Delete (typed confirmation, the only deletion of kept data). FileBrowser gets a read-only source. The drive-full warning names the kept folders. <drive>/kept is protected and outside every backup leg. R-690: the removed-app restore (R-487) never found a unit on a DATA drive — it asked GetStackComposePath (true for every catalog app) and restored nextcloud with no env. Now isStackDeployed; pinned with a production-shaped provider. Red-proofs: audits/night-2026-09-26/E/redproofs/. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
66 lines
2.3 KiB
Go
66 lines
2.3 KiB
Go
package backup
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func writeKeptUnit(t *testing.T, unitDir, hdd string, withData bool, at time.Time) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Join(unitDir, "compose"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
man := RecoveryManifest{SchemaVersion: 2, AppName: "nextcloud"}
|
|
if withData {
|
|
man.DBDumps = []string{"nextcloud-mariadb.sql"}
|
|
}
|
|
b, _ := json.Marshal(man)
|
|
if err := os.WriteFile(UnitManifestFile(unitDir), b, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = os.Chtimes(UnitManifestFile(unitDir), at, at)
|
|
if hdd != "" {
|
|
if err := os.WriteFile(filepath.Join(unitDir, "compose", "app.yaml"), []byte("env:\n HDD_PATH: "+hdd+"\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// `09` §3 decision 36 — „use my kept data" loads only a copy that (1) holds the app's data and (2) was
|
|
// taken of THIS drive's install; the newest such copy on any local tier wins.
|
|
// COMPANION RED-PROOF: drop the HDD_PATH comparison in KeptCopyAt → a unit taken of ANOTHER drive is
|
|
// offered and the first assertion fails.
|
|
func TestKept_DBCopyIsTheNewestUsableForThisDrive(t *testing.T) {
|
|
m, sett, drive := r690Manager(t)
|
|
_ = sett
|
|
unit := RecoveryUnitPath(m.namespaceRoot(drive), "nextcloud")
|
|
|
|
writeKeptUnit(t, unit, "/mnt/felhom-drives/other", true, time.Now().Add(-time.Hour))
|
|
if c, ok := m.KeptDBCopy("nextcloud", drive); ok {
|
|
t.Fatalf("a unit taken of another drive was offered: %+v", c)
|
|
}
|
|
writeKeptUnit(t, unit, drive, false, time.Now().Add(-time.Hour))
|
|
if c, ok := m.KeptDBCopy("nextcloud", drive); ok {
|
|
t.Fatalf("a unit holding no data was offered: %+v", c)
|
|
}
|
|
writeKeptUnit(t, unit, drive, true, time.Now().Add(-time.Hour))
|
|
c, ok := m.KeptDBCopy("nextcloud", drive)
|
|
if !ok || c.UnitDir != unit || c.Tier != 1 {
|
|
t.Fatalf("own unit not offered: %+v ok=%v", c, ok)
|
|
}
|
|
// An INSTALLED app's unit is its live backup, never a kept-data offer.
|
|
if c, ok := m.KeptDBCopy("still-here", drive); ok {
|
|
t.Fatalf("an installed app's unit was offered: %+v", c)
|
|
}
|
|
// The start-fresh hook names the unit only on the same drive.
|
|
if got := m.RemovedUnitOnDrive("nextcloud", drive); got != unit {
|
|
t.Fatalf("RemovedUnitOnDrive = %q, want %q", got, unit)
|
|
}
|
|
if got := m.RemovedUnitOnDrive("nextcloud", "/elsewhere"); got != "" {
|
|
t.Fatalf("RemovedUnitOnDrive named a unit on another drive: %q", got)
|
|
}
|
|
}
|