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:
@@ -38,10 +38,10 @@ type (
|
||||
KeyInstaller interface {
|
||||
Install(ctx context.Context, host, user string, port int, password, privPEM, pubAuthorized, knownHosts string) error
|
||||
}
|
||||
// OffboxEnabler configures the offbox target (key + known_hosts + target) and goes EscrowState="pending"
|
||||
// (the fork-4 enable path).
|
||||
// OffboxEnabler configures the offbox target (key + known_hosts + target + soft quota) and goes
|
||||
// EscrowState="pending" (the fork-4 enable path). quotaGB=0 = no soft limit (dedicated boxes).
|
||||
OffboxEnabler interface {
|
||||
ConfigureOffbox(ctx context.Context, host, user string, port int, repoPath, privPEM, knownHosts string) error
|
||||
ConfigureOffbox(ctx context.Context, host, user string, port int, repoPath, privPEM, knownHosts string, quotaGB int) error
|
||||
}
|
||||
// KeyAuthProber checks whether an ALREADY-INSTALLED key authenticates to the target (pinned to the
|
||||
// freshly-scanned knownHosts). ok=true returns that key's PEM so the descriptor change is applied by
|
||||
@@ -136,7 +136,7 @@ func (b *Bridge) Reconcile(ctx context.Context) error {
|
||||
// on an already-provisioned guest no longer loops on consume-404.
|
||||
if b.Prober != nil {
|
||||
if privPEM, ok := b.Prober.Probe(ctx, o.Host, o.User, port, knownHostsLine); ok {
|
||||
if err := b.Enabler.ConfigureOffbox(ctx, o.Host, o.User, port, o.RepoPath, privPEM, knownHostsLine); err != nil {
|
||||
if err := b.Enabler.ConfigureOffbox(ctx, o.Host, o.User, port, o.RepoPath, privPEM, knownHostsLine, o.QuotaGB); err != nil {
|
||||
return fmt.Errorf("offsite-apply: reconfigure (key-auth-first): %w", err)
|
||||
}
|
||||
if err := b.writeMarker(h); err != nil {
|
||||
@@ -171,7 +171,7 @@ func (b *Bridge) Reconcile(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// 5) Configure the offbox target + go EscrowState="pending" (fork-4 enable path).
|
||||
if err := b.Enabler.ConfigureOffbox(ctx, o.Host, o.User, port, o.RepoPath, privPEM, knownHostsLine); err != nil {
|
||||
if err := b.Enabler.ConfigureOffbox(ctx, o.Host, o.User, port, o.RepoPath, privPEM, knownHostsLine, o.QuotaGB); err != nil {
|
||||
return fmt.Errorf("offsite-apply: configure offbox: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ type ConsumerFunc func(ctx context.Context) (string, error)
|
||||
|
||||
func (f ConsumerFunc) Consume(ctx context.Context) (string, error) { return f(ctx) }
|
||||
|
||||
type EnablerFunc func(ctx context.Context, host, user string, port int, repoPath, privPEM, knownHosts string) error
|
||||
type EnablerFunc func(ctx context.Context, host, user string, port int, repoPath, privPEM, knownHosts string, quotaGB int) error
|
||||
|
||||
func (f EnablerFunc) ConfigureOffbox(ctx context.Context, host, user string, port int, repoPath, privPEM, knownHosts string) error {
|
||||
return f(ctx, host, user, port, repoPath, privPEM, knownHosts)
|
||||
func (f EnablerFunc) ConfigureOffbox(ctx context.Context, host, user string, port int, repoPath, privPEM, knownHosts string, quotaGB int) error {
|
||||
return f(ctx, host, user, port, repoPath, privPEM, knownHosts, quotaGB)
|
||||
}
|
||||
|
||||
// --- HTTPConsumer: POST the hub consume-password endpoint with the per-customer API key ---
|
||||
|
||||
Reference in New Issue
Block a user