v0.311.0: after a power cut, an app repairs the files the cut left empty; the household sees lost photos (R-1007)
gates / gates (push) Successful in 52s
gates / gates (push) Successful in 52s
New template block after_unclean_boot (decision 236): once per controller start that followed an UNCLEAN host boot (crashboot's own verdict, after the 15-minute crash grace; a normal or unknown boot never runs it), each app with the block rebuilds ONLY its zero-byte files (scan: regular files, no symlinks, its own filesystem; ids appended to the app's own command in batches; one retry, then reported) and its own lost-file check is read. The household sees lost files (name, date taken) on the app page until dismissed; the operator gets app_repaired_after_crash with counts only. Skips, with the reason, an unproven image, an unknown key, an app not running. MinAgent 0.155.0 unchanged. Tests red-proofed (14 guards). One new parity fixture (the new card only). Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/aftercrash"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/api"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
||||
@@ -963,6 +964,32 @@ func main() {
|
||||
}
|
||||
}
|
||||
bootGrace := crashboot.New(startTime, crashProbe, logger)
|
||||
// R-1007 (`09` §3 decision 236): ONCE per start that followed an unclean host boot — the same verdict, after the same
|
||||
// CrashGrace — each app with an after_unclean_boot block rebuilds the files the power cut left empty and its own
|
||||
// lost-file check is read. A normal or unknown boot never runs it. The operator gets counts only
|
||||
// (app_repaired_after_crash); the household's note lives on the app page. Pinned by internal/aftercrash.
|
||||
aubRunner := aftercrash.New(bootGrace, stackMgr.AfterUncleanBootApps, stackMgr.RunAfterUncleanBoot,
|
||||
func(name string, bootAt time.Time, res stacks.AfterUncleanBootResult) {
|
||||
disp := name
|
||||
if st, ok := stackMgr.GetStack(name); ok && st.Meta.DisplayName != "" {
|
||||
disp = st.Meta.DisplayName
|
||||
}
|
||||
d := notify.AppRepairDetails{App: disp, StackName: name, Found: res.Found, Repaired: res.Repaired,
|
||||
StillBroken: res.StillBroken, Lost: res.Lost, LostChecked: res.LostChecked,
|
||||
DurationS: int64(res.Duration.Round(time.Second) / time.Second), Skipped: res.Skipped, Problem: res.Problem}
|
||||
if !bootAt.IsZero() {
|
||||
d.BootAt = bootAt.UTC().Format(time.RFC3339)
|
||||
}
|
||||
notifier.NotifyAppRepairedAfterCrash(d)
|
||||
}, logger)
|
||||
var aubBusy sync.Mutex
|
||||
sched.Every("after-unclean-boot", 30*time.Second, func(ctx context.Context) error {
|
||||
if aubRunner.Done() || !aubBusy.TryLock() {
|
||||
return nil
|
||||
}
|
||||
go func() { defer aubBusy.Unlock(); aubRunner.Tick(time.Now()) }() // a run can take minutes; never block the scheduler
|
||||
return nil
|
||||
})
|
||||
sched.Every("deadapp-check", 30*time.Second, func(ctx context.Context) error {
|
||||
if bootGrace.Within(time.Now()) {
|
||||
return nil // still inside the startup settle window (90 s, or ~15 min after a crash boot)
|
||||
|
||||
Reference in New Issue
Block a user