agent v0.51.0: local vzdump retention default (--prune-backups keep-last=3)

The preventive counterpart to host_disk + storage_fill detectors: the periodic local
whole-guest vzdump now prunes its own old archives (keep-last=3, clamped >=1) so a box
can't refill its own root via its own backups. Local target only — PBS never pruned
(resolved via ListStorage; fail-safe skip on unknown). Seeded in host-install.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
2026-06-30 19:44:07 +02:00
parent 79eb0a8486
commit 06e0bc9c25
8 changed files with 212 additions and 16 deletions
+9 -4
View File
@@ -24,6 +24,8 @@ type fakeBackupAPI struct {
cfgErr error
content []proxmox.StorageContent
contentErr error
storages []proxmox.Storage // returned by ListStorage (the local-prune scope gate)
storageErr error
vzdumps []proxmox.VzdumpOptions
logLines []string // returned by TaskLogTail (e.g. "INFO: backup mode: stop")
waitGate chan struct{} // if non-nil, WaitTask blocks until closed (8B.2 watcher timing)
@@ -45,6 +47,9 @@ func (f *fakeBackupAPI) GuestConfig(_ context.Context, _ int) (proxmox.GuestConf
func (f *fakeBackupAPI) StorageContent(_ context.Context, _ string) ([]proxmox.StorageContent, error) {
return f.content, f.contentErr
}
func (f *fakeBackupAPI) ListStorage(_ context.Context) ([]proxmox.Storage, error) {
return f.storages, f.storageErr
}
func (f *fakeBackupAPI) TaskLogTail(_ context.Context, _ string, _ int) ([]string, error) {
return f.logLines, nil
}
@@ -73,7 +78,7 @@ func TestBackup_SuccessResolvesArchiveAndBulkGap(t *testing.T) {
{VolID: "local:backup/other-9002.tar.zst", Content: "backup", VMID: 9002, Size: 7, CTime: 999},
},
}
r := NewBackupRunner(api, "local", "", "felhom test", quiet())
r := NewBackupRunner(api, "local", "", "felhom test", "", quiet())
rec, err := r.Backup(context.Background(), 9001)
if err != nil {
t.Fatalf("Backup: %v", err)
@@ -107,7 +112,7 @@ func TestBackup_ReportsActualModeFromTaskLog(t *testing.T) {
content: []proxmox.StorageContent{{VolID: "v", Content: "backup", VMID: 9001, Size: 10, CTime: 1}},
logLines: []string{"INFO: CT Name: spike", "INFO: backup mode: stop", "INFO: Finished"},
}
r := NewBackupRunner(api, "local", proxmox.ModeSnapshot, "", quiet())
r := NewBackupRunner(api, "local", proxmox.ModeSnapshot, "", "", quiet())
rec, err := r.Backup(context.Background(), 9001)
if err != nil {
t.Fatal(err)
@@ -119,7 +124,7 @@ func TestBackup_ReportsActualModeFromTaskLog(t *testing.T) {
func TestBackup_VzdumpFailureReturnsFailedRecord(t *testing.T) {
api := &fakeBackupAPI{vzdumpErr: errors.New("vzdump boom")}
r := NewBackupRunner(api, "local", "", "", quiet())
r := NewBackupRunner(api, "local", "", "", "", quiet())
rec, err := r.Backup(context.Background(), 9001)
if err == nil {
t.Fatal("expected error")
@@ -135,7 +140,7 @@ func TestPickRestoreCandidate_NewestOrEmpty(t *testing.T) {
{VolID: "b", Content: "backup", CTime: 99},
{VolID: "iso", Content: "iso", CTime: 999}, // not a backup → ignored
}}
r := NewBackupRunner(api, "local", "", "", quiet())
r := NewBackupRunner(api, "local", "", "", "", quiet())
vol, err := r.PickRestoreCandidate(context.Background())
if err != nil || vol != "b" {
t.Fatalf("pick = %q,%v want newest 'b'", vol, err)