v0.6.0: slice 6 Phase B — PBS offsite tier (verify + PBS-API client + reporting)
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>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user