kept second-drive copies (D25: R-1175, R-1181, R-1179); .fab import keeps the family gate (R-1191); stale restore temp (R-1188 c); the overcommit sentence made true (R-1192); escrow before the floor (R-1176); unreleased
gates / gates (push) Successful in 1m20s

This commit is contained in:
2026-10-21 15:46:10 +02:00
parent 8f5bea0148
commit 5929b8e345
34 changed files with 2152 additions and 44 deletions
+88 -6
View File
@@ -128,6 +128,18 @@ func (m *Manager) RunSharesTier2() error {
}
g.shares = append(g.shares, sh)
}
// D25: every REGISTERED share per source drive, available or not — a registered share whose folder is missing
// keeps its copy (and is listed as kept); only a share the household deleted or renamed is pruned.
registeredByDrive := map[string][]settings.SMBShare{}
if m.settings != nil {
for _, sh := range m.settings.GetSMBShares() {
if d := m.shareSourceDrive(sh.Path); d != "" {
registeredByDrive[d] = append(registeredByDrive[d], sh)
}
}
}
touched := map[string]map[string]bool{} // destBase → the drive keys handled there this run
var groups []*sharesTier2Group
for _, g := range byDrive {
groups = append(groups, g)
@@ -202,7 +214,13 @@ func (m *Manager) RunSharesTier2() error {
legRels := make([]string, 0, len(legs)+1)
for _, sh := range legs {
rel := g.key + "/" + sh.Name
if err := mirror(sh.Path, filepath.Join(destBase, filepath.FromSlash(rel))); err != nil {
dst := filepath.Join(destBase, filepath.FromSlash(rel))
if m.emptiedSourceRefused("share "+sh.Name, sh.Path, dst) {
legRels = append(legRels, rel) // kept as it is
warns = append(warns, m.note("note.tier2.emptied_kept", sh.Name))
continue
}
if err := mirror(sh.Path, dst); err != nil {
m.recordTier2Failure(SharesPseudoStack, target, err)
if m.tier2Notify != nil {
// DISPLAY BOUNDARY: the notifier feeds Hungarian customer/operator copy, so the
@@ -215,6 +233,16 @@ func (m *Manager) RunSharesTier2() error {
legRels = append(legRels, rel)
totalSize += dirSizeBytes(sh.Path)
mirrored++
m.releaseKeptCopy(dst)
}
// D25: a registered share of this drive whose folder is missing keeps its copy (and is listed as kept).
for _, sh := range registeredByDrive[g.sourceDrive] {
if m.shareBackupAvailable(sh.Path) {
continue
}
rel := g.key + "/" + sh.Name
legRels = append(legRels, rel)
m.keepCopy("share", sh.Name, m.storageLabel(g.sourceDrive), "folder_missing", destBase, filepath.Join(destBase, filepath.FromSlash(rel)))
}
// The payload rides every target so each one is independently restorable.
if payloadDir != "" {
@@ -227,7 +255,15 @@ func (m *Manager) RunSharesTier2() error {
// Prune dest dirs no share covers any more (a share deleted or renamed since the last run
// stops occupying the secondary drive within one run), then the marker LAST — a half-written
// dest therefore has no marker and the restore path refuses it until the next good run.
m.tier2ReconcileRoots(destBase, sharesTier2DestRoots(destBase), legRels)
//
// R-1175: ONLY this group's own drive key. Walking every key on disk with only this group's legs removed
// every OTHER group's copies on a shared target (after each run only the last group had a copy) and, on the
// night a drive died, its only second copy. Keys no group handled this run are dealt with once, below.
m.tier2ReconcileRoots(destBase, []string{g.key}, legRels)
if touched[destBase] == nil {
touched[destBase] = map[string]bool{}
}
touched[destBase][g.key] = true
if err := m.writeTier2Marker(destBase); err != nil {
m.logger.Printf("[WARN] [shares] tier-2: layout marker write failed (restore will refuse until next run): %v", err)
}
@@ -236,6 +272,10 @@ func (m *Manager) RunSharesTier2() error {
len(legs), g.sourceDrive, destBase, humanizeBytes(totalSize))
}
for destBase, done := range touched {
m.sharesTier2OtherKeys(destBase, done, registeredByDrive)
}
dur := time.Since(start)
if lastTarget == nil {
reason := strings.Join(noTargetWhy, "; ")
@@ -260,9 +300,7 @@ func (m *Manager) RunSharesTier2() error {
return nil
}
// sharesTier2DestRoots lists the top-level dirs under a shares destBase that the reconcile pass may
// walk. It reads what is ON DISK rather than what this run produced, so a drive key from a drive that
// is no longer registered still gets visited (and pruned) instead of lingering forever.
// sharesTier2DestRoots lists the top-level drive-key dirs under a shares destBase (the payload dir is not one).
func sharesTier2DestRoots(destBase string) []string {
entries, err := os.ReadDir(destBase)
if err != nil {
@@ -270,7 +308,7 @@ func sharesTier2DestRoots(destBase string) []string {
}
var roots []string
for _, e := range entries {
if e.IsDir() {
if e.IsDir() && e.Name() != sharesPayloadDestRel {
roots = append(roots, e.Name())
}
}
@@ -278,6 +316,50 @@ func sharesTier2DestRoots(destBase string) []string {
return roots
}
// sharesTier2OtherKeys handles the drive keys on destBase that no group mirrored this run (D25, R-1175):
// - the key of a registered drive that is PRESENT: its shares were deleted, renamed or have missing folders — a
// registered share's copy is kept (listed), a deleted/renamed share's copy is pruned (the household's own act);
// - the key of a drive that is AWAY or DECOMMISSIONED, or of no registered drive at all: every copy under it is
// kept and listed. Before 2026-10-21 these were pruned on the first run without them.
func (m *Manager) sharesTier2OtherKeys(destBase string, done map[string]bool, registeredByDrive map[string][]settings.SMBShare) {
byKey := map[string]settings.StoragePath{}
if m.settings != nil {
for _, sp := range m.settings.GetStoragePaths() {
byKey[sharesDriveKey(sp.Path)] = sp
}
}
for _, key := range sharesTier2DestRoots(destBase) {
if done[key] {
continue
}
keyDir := filepath.Join(destBase, key)
sp, known := byKey[key]
if known && !sp.Disconnected && !sp.Decommissioned {
var keep []string
for _, sh := range registeredByDrive[sp.Path] {
rel := key + "/" + sh.Name
keep = append(keep, rel)
m.keepCopy("share", sh.Name, m.storageLabel(sp.Path), "folder_missing", destBase, filepath.Join(destBase, filepath.FromSlash(rel)))
}
m.tier2ReconcileRoots(destBase, []string{key}, keep)
continue
}
reason, drive := "drive_gone", key
if known {
drive = m.storageLabel(sp.Path)
if sp.Disconnected {
reason = "drive_away"
}
}
entries, _ := os.ReadDir(keyDir)
for _, e := range entries {
if e.IsDir() {
m.keepCopy("share", e.Name(), drive, reason, destBase, filepath.Join(keyDir, e.Name()))
}
}
}
}
// writeTier2Marker writes the shared layout marker (content "2") — the LAST write of a dest, so its
// presence means "every leg and the reconcile completed".
func (m *Manager) writeTier2Marker(destBase string) error {