D22: every restore puts the data back exactly on failure and after a power cut (R-1177, R-1195); whole restore asks the unit first (R-1178); no mirror during a restore (R-1180); W22 restore refusals in Hungarian; unreleased
gates / gates (push) Successful in 1m40s

This commit is contained in:
2026-10-21 21:19:46 +02:00
parent 7848d4aaac
commit d582feb245
25 changed files with 1065 additions and 71 deletions
+24
View File
@@ -528,6 +528,12 @@ func (m *Manager) RunAllTier2() {
m.logger.Printf("[WARN] [backup] Tier 2 skipped for %s — the app is HELD; its copy is the restore point and is preserved", stack.Name)
continue
}
// R-1180: never mirror an app while a restore of it runs — the mirror (rsync --delete) would remove files the
// restore is still bringing back from that same copy. Skipped and said; the next night mirrors it.
if m.restoreRunningFor(stack.Name) {
m.logger.Printf("[WARN] [backup] Tier 2 skipped for %s — a restore of it is running; it is mirrored at the next run (R-1180)", stack.Name)
continue
}
// R-645: the mirror of an app not running its pinned version stays as it is.
if why, skip := m.versionSkip(stack.Name); skip {
m.logger.Printf("[WARN] [backup] Tier 2 skipped for %s — it is not running its pinned version (%s); the last good copy is kept (R-645)", stack.Name, why)
@@ -546,6 +552,10 @@ func (m *Manager) RunAllTier2() {
// R-7b: the SHARES source runs after the per-app loop, in the SAME orchestrator run. It is a
// sibling job — nothing above it changed — and its failure never fails the app tier.
if m.restoreRunningFor(SharesPseudoStack) {
m.logger.Printf("[WARN] [backup] Tier 2 shares job skipped — a restore of the shares is running; mirrored at the next run (R-1180)")
return
}
if err := m.RunSharesTier2(); err != nil {
m.logger.Printf("[WARN] [backup] Tier 2 shares job failed: %v", err)
}
@@ -811,3 +821,17 @@ func dirSizeBytes(dir string) int64 {
}
return size
}
// restoreRunningFor reports whether a restore of stack is in flight (the restore record the pages follow, R-550).
// The shares restores are recorded under their display name.
func (m *Manager) restoreRunningFor(stack string) bool {
m.mu.Lock()
defer m.mu.Unlock()
if !m.opRunning {
return false
}
if stack == SharesPseudoStack {
return m.opStack == SharesDisplayName || m.opStack == SharesPseudoStack
}
return m.opStack == stack
}