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
+26 -1
View File
@@ -134,14 +134,39 @@
</div>
{{with .Overrides}}{{with index . "offsite"}}{{if index . "host"}}
<p class="form-hint" style="margin-top:.5rem">Provisioned: {{index . "user"}}@{{index . "host"}}:{{index . "repo_path"}} — the transient password is delivered to the controller once (never shown here).</p>
<!-- F4: explicit operator recovery for a consumed-password dead-end (fresh-guest DR).
Rides the parent form (nested forms are invalid HTML) via formaction; the _csrf field
submits with it. Resets the box credential + stages a fresh one-time password. -->
<button type="submit" class="btn btn-outline" style="margin-top:.5rem"
formaction="/configs/{{$.Config.CustomerID}}/offsite-reissue" formmethod="POST"
onclick="return confirm('Re-issue the offsite credentials?\n\nThe box password is reset and a fresh one-time password is staged for the controller. Guests with a working installed key are unaffected (key-auth-first); a stuck fresh guest picks the new password up on its next config refresh.')">
Re-issue offsite credentials</button>
{{end}}{{end}}{{end}}
</details>
<div style="margin-top: 1.5rem; display: flex; gap: 1rem;">
<div style="margin-top: 1.5rem; display: flex; gap: 1rem; align-items: center;">
<button type="submit" class="btn">{{if .IsNew}}Create Configuration{{else}}Save Changes{{end}}</button>
<a href="{{if .IsNew}}/configs{{else}}/customers/{{.Config.CustomerID}}{{end}}" class="btn btn-outline">Cancel</a>
<span id="cfg-inflight" class="form-hint" style="display:none">Saving… offsite provisioning can take up to a minute — please do not click again.</span>
</div>
</form>
<script>
// F5: the offsite save takes ~2560s (create + action-wait + host-key scan). Without feedback
// operators re-click, which used to strand the provision (live finding F1). Disable all submit
// buttons + show the in-flight notice once a submit is underway (deferred a tick so the
// clicked button's formaction still applies).
(function () {
var form = document.querySelector('form.config-form');
if (!form) return;
form.addEventListener('submit', function () {
document.getElementById('cfg-inflight').style.display = 'inline';
var btns = form.querySelectorAll('button[type=submit]');
setTimeout(function () {
for (var i = 0; i < btns.length; i++) btns[i].disabled = true;
}, 0);
});
})();
</script>
<footer>
<p>Felhom Hub {{hubVersion}} — Configuration Management</p>