v0.110.0: offbox stale-lock self-heal (campaign C2) + crash-truthful status (C1)

resticStep escalates a restic lock error to `unlock --remove-all` + one
retry (safe: single-writer repo — sub-account isolation + single-flight
mutex); plain `unlock` is stale-only and can't clear a crash lock across a
container-hostname change. Pre-run stale unlock hygiene on run+restore.
C1: NewManager flips a persisted LastStatus=running to a truthful error.
Both red-proofed (A reproduces the exact campaign backup failure).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-10 07:02:23 +02:00
parent 0bd4cd02be
commit cb9e992c7a
5 changed files with 224 additions and 7 deletions
+23 -1
View File
@@ -110,12 +110,34 @@ func NewManager(cfg *config.Config, sett *settings.Settings, logger *log.Logger)
if cfg.Paths.SystemDataPath == "" {
logger.Printf("[WARN] [backup] SystemDataPath is empty in config — SSD-only apps will not have correct backup paths")
}
return &Manager{
m := &Manager{
cfg: cfg,
logger: logger,
settings: sett,
systemDataPath: cfg.Paths.SystemDataPath,
}
m.reconcileCrashedRun()
return m
}
// reconcileCrashedRun makes the persisted offbox status truthful after a crash (campaign C1): a controller
// that died mid-run left LastStatus="running" on disk (the in-memory single-flight mutex is gone with the
// process, but the persisted status keeps lying "running" forever). Flip it to error with a Hungarian
// "interrupted run" message; the next successful run overwrites it. No-op unless a run was actually in
// flight at the crash.
func (m *Manager) reconcileCrashedRun() {
if m.settings == nil {
return
}
t := m.settings.GetOffboxTarget()
if t == nil || t.LastStatus != "running" {
return
}
_ = m.settings.UpdateOffboxStatus(func(o *settings.OffboxTarget) {
o.LastStatus = "error"
o.LastError = "megszakadt futás (a vezérlő újraindult futás közben)"
})
m.logger.Printf("[WARN] [offbox] previous run was interrupted by a controller restart — marking the last status as failed (self-corrects on the next run)")
}
// GetAppDrivePath returns the drive path for an app.