v0.142.0: offsite repo continuity — orphaned-repo guard (A) + run-status auto-refresh (C)

- Part A: classify restic cat-config failure (wrong-password=orphaned vs no-repo vs other); ORPHANED state + Hungarian card + offbox_repo_orphaned/reset events (once, not nightly); reset = move-aside (never delete) + init, unclaimed auto / claimed confirm. Red-proofs TestOffbox_OrphanDetection_* + ConfirmedReset.
- Part C: GET /backup/offbox/status + poll on backups_remote → flips Fut→Rendben/Hiba without manual reload.
This commit is contained in:
2026-07-17 10:47:30 +02:00
parent 1452dd2b17
commit 596505ed64
11 changed files with 587 additions and 18 deletions
@@ -3,6 +3,7 @@ package web
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"log"
@@ -70,6 +71,54 @@ func TestOffboxWeb_RunGatedUntilConfirm(t *testing.T) {
}
}
// Part C — the status endpoint (poll source) reports the current run status/snapshots as JSON.
func TestOffboxStatusHandler(t *testing.T) {
s, sett, _ := newOffboxWebServer(t)
if err := sett.SetOffboxTarget(&settings.OffboxTarget{
Enabled: true, Host: "nas.local", User: "felhom", RepoPath: "/srv/repo",
LastStatus: "running", SnapshotCount: 7,
}); err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
s.offboxStatusHandler(w, httptest.NewRequest("GET", "/backup/offbox/status", nil))
var d map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &d); err != nil {
t.Fatalf("decode: %v (%s)", err, w.Body.String())
}
if d["status"] != "running" {
t.Fatalf("status = %v, want running", d["status"])
}
if d["snapshots"].(float64) != 7 {
t.Fatalf("snapshots = %v, want 7", d["snapshots"])
}
if d["orphaned"] != false {
t.Fatalf("orphaned = %v, want false", d["orphaned"])
}
}
// Part A edge — an ORPHANED repo routes "Távoli mentés most" to the card, never attempting the write.
func TestOffboxRun_RefusedWhenOrphaned(t *testing.T) {
s, sett, m := newOffboxWebServer(t)
if err := m.WriteOffboxSecrets("KEYMATERIAL", "nas.local ssh-ed25519 HOSTKEY"); err != nil {
t.Fatal(err)
}
if err := sett.SetOffboxTarget(&settings.OffboxTarget{
Enabled: true, Host: "nas.local", Port: 22, User: "felhom", RepoPath: "/srv/repo", Schedule: "daily",
EscrowState: "escrowed", RepoState: "orphaned",
}); err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
s.offboxRunHandler(w, httptest.NewRequest("POST", "/backup/offbox/run", nil))
if w.Code != 302 {
t.Fatalf("orphaned run must redirect, got %d", w.Code)
}
if loc := w.Header().Get("Location"); !strings.Contains(loc, "el%C3%A1rvult") {
t.Fatalf("orphaned run must redirect to the orphan-card flash, got %q", loc)
}
}
// Scenario E — the confirm flip wipes the agent-staged secret; a wipe failure is logged loudly but does
// NOT fail the confirm (the state flip is the primary effect).
func TestOffboxWeb_ConfirmWipesStagedSecret(t *testing.T) {