restic: raise a night that never happens, and have the mesh write the retention readable (novox/hq issue 454)
mesh/merge-gate pass: builds restic → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered

A hung night, a lock held for hours or a lock that cannot be taken left no
failed night behind, so it stayed healthy. The night's process now counts
how long the machine was up since its newest good night, by the monotonic
clock so a sleeping laptop is not blamed, and past 26 hours it is raised.

The retention file is written 0644 and root's instead of read through
sudo: the account reads it and still cannot change it. An unreadable file
names its cause, and the healthy answer says only what was seen.
This commit is contained in:
2026-10-11 12:14:54 +02:00
parent d649309895
commit cbe9fa7a2c
8 changed files with 310 additions and 84 deletions
+7 -13
View File
@@ -184,7 +184,6 @@ type Backups struct {
Run Runner
Now func() time.Time
Say func(format string, args ...any)
read retentionRead
}
// The locks, files in this module's state. The night runs in a process of its own and `now`, `restore` and a
@@ -333,11 +332,6 @@ func snapshotOf(out string) string {
// BackUp is a night: every module, or one, then the rotation. It answers each module's outcome, and
// keeps how the night as a whole and its thinning went for the health (novox/hq issue 454).
func (b *Backups) BackUp(ctx context.Context, only string) (map[string]Night, error) {
release, err := b.hold(nightLock)
if err != nil {
return nil, err
}
defer release()
run := b.LastRun()
run.At = b.Now().UTC()
failed := func(err error) (map[string]Night, error) {
@@ -345,6 +339,11 @@ func (b *Backups) BackUp(ctx context.Context, only string) (map[string]Night, er
b.keepRun(run)
return nil, err
}
release, err := b.hold(nightLock)
if err != nil {
return failed(err)
}
defer release()
if err := b.ensureRepository(ctx); err != nil {
return failed(err)
}
@@ -701,11 +700,6 @@ func nextNight(now time.Time, hour int) time.Time {
// missedANight is whether a night was missed: the newest good night of any module is older than a
// day and a bit — the machine was off, or this module was not running, at the hour.
func missedANight(nights map[string]Night, now time.Time) bool {
var newest time.Time
for _, n := range nights {
if n.OK && n.At.After(newest) {
newest = n.At
}
}
return newest.IsZero() || now.Sub(newest) > 26*time.Hour
newest := newestGood(nights)
return newest.IsZero() || now.Sub(newest) > Overdue
}