hub v0.39.0: offsite hardening — F4 credential re-issue, F2 scan retry, F5 save UX

F4: ReissueCredentials — explicit operator recovery for consumed-password
dead-ends; resets the labelled resource's password (exactly-1 guard,
red-proofed), stores a fresh one-time secret, bumps ConfigVersion.
New hetznerapi.ResetBoxPassword for the dedicated path.
F2: host-key scan retry-with-backoff (~60s ladder, red-proofed) — first
save survives fresh-subaccount DNS lag.
F5: config form disables submits + shows an in-flight notice (the re-click
bait that caused live F1).

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 22:39:08 +02:00
parent ecf9185605
commit 17cc67f7cd
9 changed files with 321 additions and 3 deletions
+8
View File
@@ -20,6 +20,7 @@ type Fake struct {
CreatedSubaccounts int
CreatedBoxes int
ResetCalls int
BoxResetCalls int
DeletedSubaccounts int
DeletedBoxes int
@@ -175,6 +176,13 @@ func (f *Fake) ChangeType(_ context.Context, _ int64, _ string) (Action, error)
return f.newAction("change_type"), nil
}
func (f *Fake) ResetBoxPassword(_ context.Context, _ int64, _ string) (Action, error) {
f.mu.Lock()
defer f.mu.Unlock()
f.BoxResetCalls++
return f.newAction("reset_password"), nil
}
func (f *Fake) DeleteStorageBox(_ context.Context, boxID int64) (Action, error) {
f.mu.Lock()
defer f.mu.Unlock()