fbe113011d
Live PBS coord: new internal/pbs/live_reporter.go (LiveSnapshotReporter implements
hub.PBSReporter via the cheap Client.Snapshots() list with last-known-good fallback,
bounded by an 8s timeout, list-only — never triggers a verify). Closes the gap where
the recipe's pbs block was omitted whenever the verify-loop SnapshotStore was empty
(one-shot collect + the first ~6h after a daemon restart). SnapshotStore.Get added
(per-datastore LKG). Wired into the collector in both runDaemon and runSelftestHub;
the verify loop keeps Recording into the SAME shared store via one hoisted pbsTargets.
v1 host-half drive shape: dropped drives[].role (hub/operator-owned manifest concept,
not host-derivable) and drives[].restic_repo_coord (named a backup tier that doesn't
exist). Drive shape is now {durable_id, mount_path, intent, fs_type?, total_bytes}.
Hub reads drives as json.RawMessage → no hub struct change; goldens re-pinned
byte-identical (agent + hub copies).
Tests: live_reporter_test.go (T1 load-bearing coord-without-verify + T2..T6),
TestDRRecipeHostHalf_V1DriveShape; each companion demonstrated to fail pre-fix then
reverted. go build/vet/test green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
2.8 KiB
Go
100 lines
2.8 KiB
Go
package pbs
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/hub"
|
|
)
|
|
|
|
// verify-state constants for the reported PBSSnapshot.
|
|
const (
|
|
VerifyOK = "ok"
|
|
VerifyFailed = "failed"
|
|
VerifyNone = "none" // no verify has run yet (PBS omits the verification field)
|
|
)
|
|
|
|
// ToHub maps a PBS API Snapshot to the hub.PBSSnapshot wire record (slice 6 Phase B). The
|
|
// backup-time epoch becomes RFC3339; `encrypted` is derived from the data files'
|
|
// crypt-mode (any "encrypt" → encrypted); verify_state is "none" until a verify runs.
|
|
func (s Snapshot) ToHub() hub.PBSSnapshot {
|
|
ns := s.Namespace
|
|
if ns == "" {
|
|
ns = "root"
|
|
}
|
|
out := hub.PBSSnapshot{
|
|
Namespace: ns,
|
|
BackupType: s.BackupType,
|
|
BackupID: s.BackupID,
|
|
BackupTime: time.Unix(s.BackupTime, 0).UTC().Format(time.RFC3339),
|
|
SizeBytes: s.Size,
|
|
Owner: s.Owner,
|
|
Protected: s.Protected,
|
|
Encrypted: s.encrypted(),
|
|
VerifyState: VerifyNone,
|
|
}
|
|
if s.Verification != nil {
|
|
out.VerifyState = s.Verification.State
|
|
out.VerifyUPID = s.Verification.UPID
|
|
}
|
|
return out
|
|
}
|
|
|
|
// encrypted reports whether the snapshot's DATA is client-side encrypted (any data file with
|
|
// crypt-mode "encrypt"; index.json is "sign-only" and is ignored).
|
|
func (s Snapshot) encrypted() bool {
|
|
for _, f := range s.Files {
|
|
if f.CryptMode == "encrypt" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SnapshotStore holds the latest reported PBS snapshots per datastore — the point-in-time
|
|
// state the host-report surfaces. The verify loop writes it; the collector reads it via the
|
|
// hub PBSReporter seam. Mutex-guarded (concurrent collector vs loop).
|
|
type SnapshotStore struct {
|
|
mu sync.Mutex
|
|
byDatastore map[string][]hub.PBSSnapshot
|
|
}
|
|
|
|
// NewSnapshotStore builds an empty store.
|
|
func NewSnapshotStore() *SnapshotStore {
|
|
return &SnapshotStore{byDatastore: map[string][]hub.PBSSnapshot{}}
|
|
}
|
|
|
|
// Record replaces the snapshot set for a datastore.
|
|
func (s *SnapshotStore) Record(datastore string, snaps []hub.PBSSnapshot) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.byDatastore[datastore] = snaps
|
|
}
|
|
|
|
// Get returns a copy of the last-known-good snapshot set for ONE datastore (nil when absent or
|
|
// empty). It is the per-datastore fallback the live reporter reaches for when a live list fails —
|
|
// distinct from PBSSnapshots, which aggregates every datastore.
|
|
func (s *SnapshotStore) Get(datastore string) []hub.PBSSnapshot {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
src := s.byDatastore[datastore]
|
|
if len(src) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]hub.PBSSnapshot, len(src))
|
|
copy(out, src)
|
|
return out
|
|
}
|
|
|
|
// PBSSnapshots implements hub.PBSReporter — all known snapshots across datastores.
|
|
func (s *SnapshotStore) PBSSnapshots(context.Context) []hub.PBSSnapshot {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
out := []hub.PBSSnapshot{}
|
|
for _, snaps := range s.byDatastore {
|
|
out = append(out, snaps...)
|
|
}
|
|
return out
|
|
}
|