controller v0.47.0: backups page — whole-guest backup visibility + manual trigger

Part 2 of the USB/backup spec. agentapi: StatusResponse.Backup record, DueResponse
age_seconds, RestoreTestStatus(). New "Rendszermentés (teljes mentés)" section
(read-only: last backup/target PBS-vs-local/next-due/restore-test) + "Mentés most"
manual trigger that goes through the quiesce loop (controller owns quiescing):
quiesce.Loop gains mutex + TriggerNow() (single-flight, async). New
/api/guest-backup/{trigger,status} (distinct from apiRouter's /api/backup/*).
App-data rows relabeled under an "Alkalmazás-mentések" divider. Config → slice 10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 11:15:25 +02:00
parent cd76afeca1
commit bbed5af662
10 changed files with 558 additions and 15 deletions
+57 -9
View File
@@ -105,11 +105,13 @@ func (c *Client) Storage(ctx context.Context) (StorageResponse, error) {
// ---- slice 8B: app-consistent backup (quiesce loop) -------------------------------------
// DueResponse mirrors the agent's GET /backup/due payload.
// DueResponse mirrors the agent's GET /backup/due payload. AgeSecs is the age of the newest
// successful backup (nil when none has run yet).
type DueResponse struct {
VMID int `json:"vmid"`
Due bool `json:"due"`
Reason string `json:"reason"`
VMID int `json:"vmid"`
Due bool `json:"due"`
Reason string `json:"reason"`
AgeSecs *int64 `json:"age_seconds"`
}
// BackupResponse mirrors the agent's POST /backup payload.
@@ -119,12 +121,42 @@ type BackupResponse struct {
Phase string `json:"phase"`
}
// StatusResponse mirrors the agent's GET /backup/status payload.
// StatusResponse mirrors the agent's GET /backup/status payload. Backup is the latest RECORDED
// whole-guest backup (nil until one has run), surfaced to the controller's backup page for visibility.
type StatusResponse struct {
VMID int `json:"vmid"`
Phase string `json:"phase"` // idle | running | done | failed
JobID string `json:"job_id"`
Error string `json:"error"`
VMID int `json:"vmid"`
Phase string `json:"phase"` // idle | running | snapshotted | done | failed
JobID string `json:"job_id"`
Error string `json:"error"`
Backup *BackupRecord `json:"backup,omitempty"`
}
// BackupRecord mirrors the agent's hub.Backup — one whole-guest vzdump/PBS backup result. The
// controller renders it read-only (it does NOT own whole-guest backup; the agent does).
type BackupRecord struct {
TargetID string `json:"target_id"` // backup storage name (e.g. "local", "felhom-pbs")
VMID int `json:"vmid"`
Archive string `json:"archive"` // produced vzdump volid (e.g. "local:backup/vzdump-lxc-…")
Mode string `json:"mode"` // snapshot | stop
CrashConsistent bool `json:"crash_consistent"`
SizeBytes int64 `json:"size_bytes"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
StartedAt string `json:"started_at"` // RFC3339
DurationSeconds float64 `json:"duration_seconds"`
}
// RestoreTestRecord mirrors the agent's hub.RestoreTest — the latest self-restore-test (the "backup
// verified restorable" trust signal). Nil when none has run yet.
type RestoreTestRecord struct {
SourceArchive string `json:"source_archive"`
SourceTier string `json:"source_tier"` // "local" (pbs = Phase B)
Pass bool `json:"pass"`
Verified string `json:"verified"` // "boot+running" this slice
Error string `json:"error,omitempty"`
TestedAt string `json:"tested_at"` // RFC3339
DurationSeconds float64 `json:"duration_seconds"`
Warnings []string `json:"warnings,omitempty"`
}
// Backup status phases (mirror the agent's vocabulary).
@@ -174,6 +206,22 @@ func (c *Client) BackupStatus(ctx context.Context) (StatusResponse, error) {
return out, nil
}
// RestoreTestStatus calls GET /restore-test/status and returns the latest self-restore-test result
// (nil when none has run yet — the agent payload is {"restore_test": {...}|null}).
func (c *Client) RestoreTestStatus(ctx context.Context) (*RestoreTestRecord, error) {
body, err := c.get(ctx, "/restore-test/status")
if err != nil {
return nil, err
}
var out struct {
RestoreTest *RestoreTestRecord `json:"restore_test"`
}
if err := json.Unmarshal(body, &out); err != nil {
return nil, fmt.Errorf("agentapi: decode /restore-test/status: %w", err)
}
return out.RestoreTest, nil
}
// ---- slice 8C: disk management (execution is the agent's) --------------------------------
// DiskInfo mirrors the agent's GET /disks entry.