v0.80.0: PBS DR tier slice 2 — the apply-bridge (pbs_dr consumer, felhom-pbs-apply set-only wrapper, verify-pin-before-consume, adoption-first, loud consumed-failed, escrow seed)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-10 21:50:36 +02:00
parent a6e8bcb475
commit e5e8f3920a
15 changed files with 1309 additions and 2 deletions
+23
View File
@@ -40,6 +40,7 @@ import (
applog "gitea.dooplex.hu/admin/felhom-agent/internal/log"
"gitea.dooplex.hu/admin/felhom-agent/internal/mgmtplane"
"gitea.dooplex.hu/admin/felhom-agent/internal/pbs"
"gitea.dooplex.hu/admin/felhom-agent/internal/pbsdr"
"gitea.dooplex.hu/admin/felhom-agent/internal/provision"
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
"gitea.dooplex.hu/admin/felhom-agent/internal/reconcile"
@@ -730,6 +731,27 @@ func runDaemon(cfg config.Config, logger *slog.Logger) int {
}
}
// PBS DR tier apply-bridge (slice 2): hub-driven — runs whenever the desired-state carries a
// pbs_dr descriptor (slice 1). No config gate: an absent/disabled descriptor is a no-op, so a
// v0.80.0 rollout changes nothing until the operator enables the tier on the hub. Adoption
// first (existing healthy entry → grant + mark, NO consume), verify-pin-before-consume,
// set-only re-apply, consumed-but-failed is loud (see internal/pbsdr).
var pbsdrLoop *pbsdr.Loop
{
pdMode := proxmox.RunnerMode(cfg.Privileged.Mode)
if pdMode == "" {
pdMode = proxmox.RunnerSudo
}
pdRunner := &proxmox.ExecRunner{Mode: pdMode, SudoPath: cfg.Privileged.SudoPath}
secretDir := filepath.Dir(cfg.Backup.PBSSecretPath("x"))
pdMgr := pbsdr.NewManager(pdRunner, px, client, cfg.WGTunnel.WithDefaults().StateDir,
secretDir, cfg.SourcePath, logger)
pbsdrLoop = pbsdr.NewLoop(pdMgr, 60*time.Second, logger)
desiredSyncer.AddConsumer(pbsdrLoop) // raw desired-state → the pbs_dr block
collector.SetPBSDRReporter(pbsdrLoop)
logger.Info("pbsdr: bridge enabled (hub-driven; no-op until a pbs_dr descriptor arrives)")
}
// Run reconcile, the hub loop, the storage watchdog, the restore-test scheduler, the PBS
// verify loop, (optionally) the local-API server, and (optionally) the LAN resolver loop
// concurrently; any one returning ends the daemon (ctx cancel tears down the rest).
@@ -739,6 +761,7 @@ func runDaemon(cfg config.Config, logger *slog.Logger) int {
go func() { errc <- watchdog.Run(ctx) }()
go func() { errc <- scheduler.Run(ctx) }()
go func() { errc <- pbsLoop.Run(ctx) }()
go func() { errc <- pbsdrLoop.Run(ctx) }()
if localSrv != nil {
localServers = 1
// Host-reboot remount fix: BEFORE binding into the guest, re-assert enrolled drive MOUNTS on the