v0.325.0: share + escrow limiters reserve first; family sessions as fingerprints; samba renames apply; path guard widened; gates in a worktree
gates / gates (push) Successful in 45s
gates / gates (push) Successful in 45s
R-1111 reserveAttempt is the one limiter body (share password, escrow re-auth, dashboard sign-in). R-1112 family.json holds fingerprints; old files are converted at load. R-1113 the household's pass reuses its family session. R-1109 the samba compose carries the smb.conf hash. R-1110 TestNoBareDrivePathToPathHelpers. R-1114 shared_scripts.py resolves the felhom.eu sibling from a worktree. Each red-proved. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -884,14 +884,14 @@ func (m *Manager) RemoveStack(name string, removeHDDData bool, backupPathsToRemo
|
||||
}
|
||||
|
||||
// GetStackBackupData returns information about backup data for a stack — what the remove dialog
|
||||
// sizes "delete backups" from. drivePath is the app's namespace root; mirrorDirs are the app's
|
||||
// sizes "delete backups" from. nsRoot is the app's namespace root; mirrorDirs are the app's
|
||||
// Tier-2 mirror directories (backup.Manager.Tier2MirrorDirsForApp — stacks cannot import backup).
|
||||
//
|
||||
// R-485 (v0.240.0): until v0.239.0 this read `<ns>/backups/primary/<app>/db-dumps` and a pre-v2
|
||||
// `<ns>/backups/secondary/<app>/rsync` path — both dead for an app whose backups are the recovery
|
||||
// unit and a v2 mirror — and answered `has_backups:false` over 484 MB (measured on demo-hp
|
||||
// 2026-09-13). It now sizes the whole recovery unit and every mirror.
|
||||
func (m *Manager) GetStackBackupData(name string, drivePath string, mirrorDirs []string) (*BackupDataResponse, error) {
|
||||
func (m *Manager) GetStackBackupData(name string, nsRoot string, mirrorDirs []string) (*BackupDataResponse, error) {
|
||||
_, ok := m.GetStack(name)
|
||||
if !ok {
|
||||
return nil, util.KindErrorf(ErrStackNotFound, "stack %q not found", name)
|
||||
@@ -901,16 +901,17 @@ func (m *Manager) GetStackBackupData(name string, drivePath string, mirrorDirs [
|
||||
Stack: name,
|
||||
}
|
||||
|
||||
if drivePath == "" {
|
||||
if nsRoot == "" {
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackBackupData %s: no drive path provided, returning empty", name)
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackBackupData %s: no namespace root provided, returning empty", name)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// The recovery unit — definition, db-dumps and volume-dumps together. drivePath is the felhom-data
|
||||
// namespace ROOT (Model A: the in-guest drive mount itself), so backups/ sits directly under it.
|
||||
resp.BackupPaths = append(resp.BackupPaths, buildPathInfo(appbackup.RecoveryUnitPath(drivePath, name)))
|
||||
// The recovery unit — definition, db-dumps and volume-dumps together. nsRoot is the felhom-data
|
||||
// namespace ROOT (its one caller passes backup.AppNamespaceRoot), so backups/ sits directly under it.
|
||||
// Named nsRoot (was drivePath) so TestNoBareDrivePathToPathHelpers reads it (R-1110).
|
||||
resp.BackupPaths = append(resp.BackupPaths, buildPathInfo(appbackup.RecoveryUnitPath(nsRoot, name)))
|
||||
for _, d := range mirrorDirs {
|
||||
resp.BackupPaths = append(resp.BackupPaths, buildPathInfo(d))
|
||||
}
|
||||
|
||||
@@ -167,6 +167,53 @@ func TestSambaReconcile_IdempotentNoComposeCall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// R-1109: the household renames a share at the same folder (remove + add, the UI's way). The rendered compose on
|
||||
// disk must change, so the compose up the reconcile runs RECREATES the container and the new name is served; a
|
||||
// second pass with nothing new performs no compose call.
|
||||
func TestSambaReconcile_SamePathRenameChangesCompose(t *testing.T) {
|
||||
m, sett, root, upCalls := newSambaManager(t)
|
||||
shareDir := seedShare(t, sett, root, "dokumentumok")
|
||||
enableSMB(t, sett)
|
||||
if err := sett.AddSMBShare(settings.SMBShare{Name: "dokumentumok", Path: shareDir}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := m.ReconcileSamba(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
composePath := filepath.Join(m.sambaDir(), "docker-compose.yml")
|
||||
c1, err := os.ReadFile(composePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.sambaRunFn = func() bool { return true }
|
||||
if err := sett.RemoveSMBShare("dokumentumok"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sett.AddSMBShare(settings.SMBShare{Name: "iratok", Path: shareDir}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before := *upCalls
|
||||
if err := m.ReconcileSamba(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c2, err := os.ReadFile(composePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(c1) == string(c2) {
|
||||
t.Fatalf("a same-path rename left docker-compose.yml unchanged — the running container is never recreated (R-1109)")
|
||||
}
|
||||
if *upCalls != before+1 {
|
||||
t.Errorf("the rename must run exactly one compose up, got %d", *upCalls-before)
|
||||
}
|
||||
if err := m.ReconcileSamba(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if *upCalls != before+1 {
|
||||
t.Errorf("a pass with nothing new must perform no compose call, got %d more", *upCalls-before-1)
|
||||
}
|
||||
}
|
||||
|
||||
// Edge case: a share on a DISCONNECTED storage path is never exported (config retained).
|
||||
func TestSambaReconcile_DeadMountOmitted(t *testing.T) {
|
||||
m, sett, root, _ := newSambaManager(t)
|
||||
|
||||
Reference in New Issue
Block a user