Host report keeps the last backup across a restart; Secure Boot meta-package leaves the Proxmox lane
gates / gates (push) Successful in 1m7s
gates / gates (push) Successful in 1m7s
Found in the 2026-10-09 kernel-night read-back: - demo-felhom: the kernel step restarted the host 4 minutes after the night backup; the in-memory backup list was empty after the restart, so the hub alarmed "host tier: newest backup is 48h old". The report now adds R-894's saved newest success per tier (one shared instance). - demo-hp: proxmox-secure-boot-support pulled shim-signed-common into the ring-0 Proxmox plan; the step was refused R6 and the kernel step skipped. It joins HOST_SLOW_RE with shim and GRUB. Red-proved: TestCollectBackups_SavedSuccessSurvivesARestart, TestR894_LastKnownBackupsIsWiredIntoTheDaemon, test_ring0_pending_pve_leaves_the_secure_boot_meta_out. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1925,6 +1925,10 @@ func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.St
|
||||
return out, withheld, nil
|
||||
},
|
||||
}
|
||||
// R-894: ONE instance — the local API writes it, the host report reads it (2026-10-09: a restart right
|
||||
// after the night backup erased the hub's evidence of it).
|
||||
lastKnownBackups := backup.NewBackupSuccessState(filepath.Join(cfg.OOB.WithDefaults().StateDir, "backup-success-state.json"))
|
||||
collector.SetKnownBackupReporter(lastKnownBackups)
|
||||
srv, err := localapi.NewServer(localapi.Options{
|
||||
EscrowRecovery: escrowRecoverer,
|
||||
ListenAddr: cfg.LocalAPI.ListenAddr,
|
||||
@@ -1937,7 +1941,7 @@ func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.St
|
||||
Store: store,
|
||||
// R-894: the newest success per tier on disk — the due-check's fallback when the storage cannot be
|
||||
// read right after a restart. Same state dir as restore-test-state.json.
|
||||
LastKnownBackups: backup.NewBackupSuccessState(filepath.Join(cfg.OOB.WithDefaults().StateDir, "backup-success-state.json")),
|
||||
LastKnownBackups: lastKnownBackups,
|
||||
Storage: observer,
|
||||
DriveTargets: driveTargets, // Impl-2a: registry+units drives for the /disks view (union w/ Observe storages)
|
||||
Smart: storage.NewSmartReader(hostOps), // v0.95.0 Fix B: SMART for the union-path drives
|
||||
|
||||
@@ -12,12 +12,18 @@ import (
|
||||
//
|
||||
// COMPANION RED-PROOF (observed): delete the `LastKnownBackups:` line from buildLocalAPIServer → this
|
||||
// fails with "localapi.Options in buildLocalAPIServer has no LastKnownBackups field". Restored.
|
||||
//
|
||||
// 2026-10-09: the SAME instance also feeds the host report (collector.SetKnownBackupReporter), so a
|
||||
// restart right after a backup no longer erases the hub's evidence of it. The field may name a local
|
||||
// variable; the variable must be built by backup.NewBackupSuccessState and be the one handed to the
|
||||
// collector. RED-PROOF (observed): drop the SetKnownBackupReporter call → "not handed to the collector".
|
||||
func TestR894_LastKnownBackupsIsWiredIntoTheDaemon(t *testing.T) {
|
||||
_, f := parseMain(t)
|
||||
if !callsWithin(f, "main")["runDaemon"] || !callsWithin(f, "runDaemon")["buildLocalAPIServer"] {
|
||||
t.Fatal("main → runDaemon → buildLocalAPIServer is broken — the path this test asserts is not the live one")
|
||||
}
|
||||
var field, built bool
|
||||
var fieldVar, handed string
|
||||
for _, d := range f.Decls {
|
||||
fd, ok := d.(*ast.FuncDecl)
|
||||
if !ok || fd.Name == nil || fd.Name.Name != "buildLocalAPIServer" || fd.Body == nil {
|
||||
@@ -45,6 +51,28 @@ func TestR894_LastKnownBackupsIsWiredIntoTheDaemon(t *testing.T) {
|
||||
if callsIn(kv.Value)["backup.NewBackupSuccessState"] {
|
||||
built = true
|
||||
}
|
||||
if id, ok := kv.Value.(*ast.Ident); ok {
|
||||
fieldVar = id.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
// a local variable: built by NewBackupSuccessState, and handed to the collector
|
||||
ast.Inspect(fd.Body, func(n ast.Node) bool {
|
||||
switch x := n.(type) {
|
||||
case *ast.AssignStmt:
|
||||
for i, l := range x.Lhs {
|
||||
if id, ok := l.(*ast.Ident); ok && fieldVar != "" && id.Name == fieldVar && i < len(x.Rhs) &&
|
||||
callsIn(x.Rhs[i])["backup.NewBackupSuccessState"] {
|
||||
built = true
|
||||
}
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
if fn, ok := x.Fun.(*ast.SelectorExpr); ok && fn.Sel.Name == "SetKnownBackupReporter" && len(x.Args) == 1 {
|
||||
if id, ok := x.Args[0].(*ast.Ident); ok {
|
||||
handed = id.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -56,6 +84,9 @@ func TestR894_LastKnownBackupsIsWiredIntoTheDaemon(t *testing.T) {
|
||||
if !built {
|
||||
t.Fatal("LastKnownBackups is not built by backup.NewBackupSuccessState")
|
||||
}
|
||||
if handed == "" || handed != fieldVar {
|
||||
t.Fatalf("the saved backups (%q) are not handed to the collector (SetKnownBackupReporter got %q)", fieldVar, handed)
|
||||
}
|
||||
}
|
||||
|
||||
func callsIn(n ast.Node) map[string]bool {
|
||||
|
||||
Reference in New Issue
Block a user