v0.109.0: soft-quota gate + usage bar + offsite report status (SLICE 4)

QuotaGB rides the descriptor into OffboxTarget; RepoSizeBytes persisted
from restic stats. Pre-run gate: >=100% refuses NEW backups (Hungarian
notice + operator alert) but prune STILL runs (red-proofed) and restore is
never gated; >=80% warns. /backups usage bar (quota>0 only). The hub
report gains the non-secret offsite status object for the OffsiteChecker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-09 23:48:42 +02:00
parent 994e15896f
commit 8917014991
12 changed files with 326 additions and 23 deletions
@@ -81,11 +81,12 @@ type fakeEnabler struct {
gotHost string
gotKnownHost string
gotPriv string
gotQuotaGB int
}
func (f *fakeEnabler) ConfigureOffbox(_ context.Context, host, _ string, _ int, _, privPEM, knownHosts string) error {
func (f *fakeEnabler) ConfigureOffbox(_ context.Context, host, _ string, _ int, _, privPEM, knownHosts string, quotaGB int) error {
f.calls++
f.gotHost, f.gotKnownHost, f.gotPriv = host, knownHosts, privPEM
f.gotHost, f.gotKnownHost, f.gotPriv, f.gotQuotaGB = host, knownHosts, privPEM, quotaGB
return f.err
}
@@ -111,7 +112,7 @@ func newBridge(t *testing.T, o config.OffsiteConfig) (*Bridge, *fakeConsumer, *f
}
func goodOffsite() config.OffsiteConfig {
return config.OffsiteConfig{Enabled: true, Type: "shared", Host: "h", User: "u", Port: 23, RepoPath: "/home/felhom-repo", HostFingerprint: "SHA256:goodfp"}
return config.OffsiteConfig{Enabled: true, Type: "shared", Host: "h", User: "u", Port: 23, RepoPath: "/home/felhom-repo", QuotaGB: 50, HostFingerprint: "SHA256:goodfp"}
}
// Scenario A — full apply: consume → verify-pin → install → configure offbox → marker persisted; pw not logged.
@@ -132,6 +133,9 @@ func TestBridge_AppliesEndToEnd(t *testing.T) {
if en.calls != 1 || en.gotHost != "h" || en.gotKnownHost != "[h]:23 ssh-ed25519 AAAAKEY" || en.gotPriv != "PRIVPEM" {
t.Fatalf("enabler not called with the pinned known_hosts + key: %+v", en)
}
if en.gotQuotaGB != 50 {
t.Fatalf("the bridge must map the descriptor's quota_gb into the target (SLICE 4), got %d", en.gotQuotaGB)
}
if b.readMarker() != descriptorHash(b.Cfg.Offsite) {
t.Fatal("marker not persisted after a successful apply")
}