766500dfc3
Spike-proven that backup/restore-to-PBS reuse Phase A unchanged; the only new code is the verify capability, a small PBS-API client, and PBSSnapshot reporting. - internal/pbs: fingerprint-pinned, token-authed PBS-API client (Verify/Snapshots/ TaskStatus, node-from-UPID; secret read from /etc/pve/priv/storage/<id>.pw at runtime, never logged) + the verify maintenance loop (own cadence, default 6h, NOT gated/journaled, like the watchdog) + SnapshotStore. - hub: PBSSnapshot filled (namespace/type/id/time/size/owner/protected/encrypted/ verify_state/verify_upid); PBSReporter collector seam; cross-repo golden + bidirectional key-set tests; hub handler parses pbs_snapshots + logs a failed-verify WARN. - backup: report the ACTUAL vzdump mode (parsed from the task log; PVE may downgrade snapshot->stop). proxmox.Storage.Username. config PBSVerifyCadence/secret-dir. --selftest=pbs-verify. Backup/restore-to-PBS unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
85 lines
2.3 KiB
Go
85 lines
2.3 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
|
|
}
|
|
|
|
// 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
|
|
}
|