Files
felhom.eu/documentation/audits/d53-held-apps-2026-10-26/live/2-cleared_hosts_d53-test-9202.html
T
admin e33ec8210a
gates / gates (push) Successful in 3m25s
D53 built + live-proven (08 §10 [DESIGN — built]); R-1297 decoys: register-shape decoys assert the script's exit
- 08 §10: built (controller 5bd03ad, hub 7e57e824, commit only); putback_pending as on the wire; as-built notes
- audits/d53-held-apps-2026-10-26: live proof on 9202 against a test hub (3-min grace), red-proofs, teardown
- R-1291 BUILT (not released); R-1298 filed (9202's orphan holds + PrivateBin leftover)
- test_gate_decoys.py: the register-shape cases also run the script and assert its exit code — R-1297's decoy 2
  showed an inert main() passed every case (9 cases red with it now)
- audits/r1297-ci-trim-2026-10-26: runners, times, decoys
2026-10-26 19:03:07 +01:00

392 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>d53-test-9202 — Felhom Hub</title>
<link rel="stylesheet" href="/style.css?v=0.173.0-d53test">
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-clock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 6v6l4 2" /></symbol>
<symbol id="i-boxes" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" /> <path d="m7 16.5-4.74-2.85" /> <path d="m7 16.5 5-3" /> <path d="M7 16.5v5.17" /> <path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" /> <path d="m17 16.5-5-3" /> <path d="m17 16.5 4.74-2.85" /> <path d="M17 16.5v5.17" /> <path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z" /> <path d="M12 8 7.26 5.15" /> <path d="m12 8 4.74-2.85" /> <path d="M12 13.5V8" /></symbol>
<symbol id="i-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /> <path d="M16 3.128a4 4 0 0 1 0 7.744" /> <path d="M22 21v-2a4 4 0 0 0-3-3.87" /> <circle cx="9" cy="7" r="4" /></symbol>
</svg>
<div class="container">
<header>
<h1>Felhom <span>Hub</span></h1>
<nav class="nav-links">
<a href="/" class="nav-link">Dashboard</a>
<a href="/configs" class="nav-link">Customers</a>
<a href="/apps" class="nav-link">Apps</a>
<a href="/hosts" class="nav-link active">Hosts</a>
<a href="/system" class="nav-link">System</a>
<a href="/offsite" class="nav-link">Offsite</a>
<a href="/configuration" class="nav-link">Configuration</a>
</nav>
</header>
<a href="/hosts" class="back-link">&larr; Hosts</a>
<section class="card">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;">
<h2 style="margin: 0;">d53-test-9202</h2>
<span class="status-badge status-badge-pending">NO REPORT</span>
</div>
<div class="info-grid" style="margin-top: 1rem;">
<div class="info-item">
<span class="label">Host ID</span>
<span class="value" style="font-family: var(--font-mono)">d53-test-9202</span>
</div>
<div class="info-item">
<span class="label">Customer</span>
<span class="value"><a href="/customers/demo-hp">Demo HP (scratch guest 9202)</a></span>
</div>
<div class="info-item">
<span class="label">Agent Version</span>
<span class="value">—</span>
</div>
<div class="info-item">
<span class="label">Enrolled</span>
<span class="value">22 min ago</span>
</div>
<div class="info-item">
<span class="label">Last Report</span>
<span class="value">waiting for first report</span>
</div>
<div class="info-item">
<span class="label">Box connection</span>
<span class="value" data-box-connection="not_connected" style="color: var(--warn)">not connected since the hub started (2026-10-26 17:28 UTC, 23 min ago)</span>
</div>
<div class="info-item">
<span class="label">Desired Generation</span>
<span class="value">0</span>
</div>
</div>
</section>
<section class="card">
<div class="empty-state">
<p>Waiting for first report.</p>
<p class="hint">Vitals, guests and storage appear once this host's agent sends a host-report.</p>
</div>
</section>
<section class="card" style="padding: 0; overflow: hidden;">
<h2 style="padding: 1.25rem 1.25rem 0.5rem;">Guests</h2>
<div class="empty-state" style="border: none;">
<p>No guests reported on this host.</p>
</div>
</section>
<section class="card" id="held-apps" style="padding: 0; overflow: hidden;">
<h2 style="padding: 1.25rem 1.25rem 0.5rem;">Held apps</h2>
<table class="data-table">
<thead>
<tr>
<th>App</th>
<th>Reason</th>
<th>Since</th>
<th>Why</th>
<th>Who may clear it</th>
</tr>
</thead>
<tbody>
<tr data-held-app="gokapi">
<td><code>gokapi</code></td>
<td><span class="severity-badge severity-warning">unhealthy_stop</span></td>
<td>32d ago</td>
<td>stopped by the box for a crash loop (2026-09-24T10:37:48Z) — the household&#39;s Start gives it one more try</td>
<td>the household&#39;s Start (one more try) <span class="text-muted">(operator reminded)</span></td>
</tr>
<tr data-held-app="chaosoom">
<td><code>chaosoom</code></td>
<td><span class="severity-badge severity-warning">unhealthy_stop</span></td>
<td>32d ago</td>
<td>stopped by the box for a oom storm (2026-09-24T11:48:01Z) — the household&#39;s Start gives it one more try</td>
<td>the household&#39;s Start (one more try) <span class="text-muted">(operator reminded)</span></td>
</tr>
<tr data-held-app="chaoscrash">
<td><code>chaoscrash</code></td>
<td><span class="severity-badge severity-warning">unhealthy_stop</span></td>
<td>32d ago</td>
<td>stopped by the box for a crash loop (2026-09-24T12:16:24Z) — the household&#39;s Start gives it one more try</td>
<td>the household&#39;s Start (one more try) <span class="text-muted">(operator reminded)</span></td>
</tr>
<tr data-held-app="chaosoomb">
<td><code>chaosoomb</code></td>
<td><span class="severity-badge severity-warning">unhealthy_stop</span></td>
<td>32d ago</td>
<td>stopped by the box for a oom storm (2026-09-24T12:39:51Z) — the household&#39;s Start gives it one more try</td>
<td>the household&#39;s Start (one more try) <span class="text-muted">(operator reminded)</span></td>
</tr>
</tbody>
</table>
</section>
<section class="card" style="padding: 0; overflow: hidden;">
<h2 style="padding: 1.25rem 1.25rem 0.5rem;">Storage Targets</h2>
<div class="empty-state" style="border: none;">
<p>No storage targets reported.</p>
</div>
</section>
<section class="card" style="padding: 0; overflow: hidden;">
<h2 style="padding: 1.25rem 1.25rem 0.5rem;">Capabilities</h2>
<div class="empty-state" style="border: none;">
<p>No capability report yet (waiting for the first host report).</p>
</div>
</section>
<section class="card">
<h2>Diagnostics — Log Bundles</h2>
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem;">
Pull-based: the box ships its debug ring on its own next cycle — controller &le; one report interval (~15 min),
agent &asymp; one heartbeat. The pull is recorded in the box's own log (customer-visible). Bundles expire after 72 h.
</p>
<div style="display: flex; gap: 0.5rem; margin: 0.75rem 0;">
<form method="POST" action="/hosts/d53-test-9202/request-logs" style="display: inline;">
<input type="hidden" name="_csrf" value="<redacted-test-csrf>">
<input type="hidden" name="component" value="controller">
<button type="submit" class="btn btn-sm">Request controller logs</button>
</form>
<form method="POST" action="/hosts/d53-test-9202/request-logs" style="display: inline;">
<input type="hidden" name="_csrf" value="<redacted-test-csrf>">
<input type="hidden" name="component" value="agent">
<button type="submit" class="btn btn-sm">Request agent logs</button>
</form>
</div>
<div class="empty-state" style="border: none;">
<p>No log bundles. Use the request buttons above — the box delivers on its next cycle.</p>
</div>
</section>
<section class="card" id="operator-actions">
<h2>Operator Actions</h2>
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem;">
The box acts on its next report reply (usually seconds, at most one report interval) and answers on the
report after that. Each press runs once. Recorded in the box's own log and in Events. Unanswered after a day: expired.
</p>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.75rem 0;">
<form method="POST" action="/hosts/d53-test-9202/operator-action" style="display: inline;">
<input type="hidden" name="_csrf" value="<redacted-test-csrf>">
<input type="hidden" name="action" value="offsite_backup_now">
<button type="submit" class="btn btn-sm">Run off-site backup now</button>
</form>
<form method="POST" action="/hosts/d53-test-9202/operator-action" style="display: inline;">
<input type="hidden" name="_csrf" value="<redacted-test-csrf>">
<input type="hidden" name="action" value="run_job">
<select name="arg"><option value="disk-health-check">disk-health-check</option><option value="fill-watch">fill-watch</option><option value="offsite-integrity">offsite-integrity</option><option value="offsite-proof">offsite-proof</option></select>
<button type="submit" class="btn btn-sm">Run check now</button>
</form>
<form method="POST" action="/hosts/d53-test-9202/operator-action" style="display: inline;">
<input type="hidden" name="_csrf" value="<redacted-test-csrf>">
<input type="hidden" name="action" value="abandon_stop">
<button type="submit" class="btn btn-sm btn-outline">Stop deletion countdown</button>
</form>
<form method="POST" action="/hosts/d53-test-9202/operator-action" style="display: inline;">
<input type="hidden" name="_csrf" value="<redacted-test-csrf>">
<input type="hidden" name="action" value="abandon_extend">
<input type="number" name="arg" min="1" max="30" value="7" style="width: 4.5em; padding: 0.3em 0.5em;" aria-label="days">
<button type="submit" class="btn btn-sm btn-outline">Extend countdown (days from now)</button>
</form>
</div>
<div class="empty-state" style="border: none;">
<p>No operator actions for this box yet.</p>
</div>
</section>
<section class="card">
<h2>Network</h2>
<div class="info-grid">
<div class="info-item">
<span class="label">WireGuard</span>
<span class="value">
<span class="text-muted">no peer allocated</span>
</span>
</div>
</div>
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.75rem;">
Waiting for the first host report.
</p>
</section>
<section class="card">
<h2>Guest network
<span class="badge badge-neutral" title="A capable agent sent no guest-network stanza">unknown</span>
</h2>
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem;">
This host reported no guest-network state, so it is <strong>unknown</strong> &mdash; not healthy.
The watchdog is default-on; a silent capable agent means it was switched off
(<code>guest_net.disable</code>), or this report predates it on this box.
</p>
</section>
<section class="card">
<h2>DR / Backup</h2>
<div class="info-grid">
<div class="info-item">
<span class="label">DR Recipe</span>
<span class="value"><span class="text-muted">none</span></span>
</div>
<div class="info-item">
<span class="label">Key Escrow</span>
<span class="value"><span class="text-muted">none</span></span>
</div>
<div class="info-item" id="offsite-integrity">
<span class="label">Last full check (off-site)</span>
<span class="value">not reported (no off-site status in the box&#39;s report)</span>
</div>
</div>
</section>
<section class="card">
<h2>Console access</h2>
<p><span class="badge badge-neutral">not vaulted</span></p>
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem;">
No console credential is vaulted for this host. Expected for a byo host — the owner manages root@pam. Otherwise the installer's step 4b did not run; re-run felhom-host-install.sh, or set and vault one per the break-glass runbook &sect;5.
</p>
</section>
<section class="card" style="border-color: var(--crit);">
<h2>Danger zone</h2>
<p class="text-muted" style="font-size: 0.85rem;">
Removing this host deletes its reports, guests, log bundles and WireGuard peer, and
permanently invalidates its API key — a still-running agent would receive 401s.
Re-enrollment requires the Day-0 passphrase flow.
</p>
<button type="button" class="btn btn-danger btn-sm" onclick="hostDeleteConfirm('d53-test-9202')">Remove host&hellip;</button>
<div id="host-delete-confirm-d53-test-9202" style="display: none; margin-top: 0.75rem; padding: 0.75rem; border: 1px solid var(--crit); background: var(--crit-dim); border-radius: var(--radius); max-width: 44em;">
<p id="host-delete-impact-d53-test-9202" style="margin: 0 0 0.5rem; font-size: 0.9em;">&hellip;</p>
<label id="host-delete-escrow-row-d53-test-9202" style="display: none; margin: 0 0 0.5rem; font-size: 0.85em;">
<input type="checkbox" id="host-delete-escrow-d53-test-9202">
Move key escrow to retained custody (required when escrow present) + remove DR bundle for this host
</label>
<p style="margin: 0 0 0.5rem; font-size: 0.85em; color: var(--text-2);">Type the host id to confirm:</p>
<form method="POST" action="/hosts/d53-test-9202/delete" id="host-delete-form-d53-test-9202" style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<input type="hidden" name="_csrf" value="<redacted-test-csrf>">
<input type="hidden" name="confirm_host_id" id="host-delete-confirm-hidden-d53-test-9202" value="">
<input type="hidden" name="delete_escrow" id="host-delete-escrow-hidden-d53-test-9202" value="">
<input type="text" id="host-delete-confirm-input-d53-test-9202" placeholder="retype the host id&hellip;" style="padding: 0.3em 0.5em; width: 16em;">
<button type="button" class="btn btn-danger btn-sm" onclick="hostDeleteSubmit('d53-test-9202')">Confirm &amp; remove</button>
<button type="button" class="btn btn-sm btn-outline" onclick="document.getElementById('host-delete-confirm-d53-test-9202').style.display='none';">Cancel</button>
</form>
<p id="host-delete-err-d53-test-9202" style="margin: 0.4em 0 0; font-size: 0.8em; color: var(--crit);"></p>
</div>
</section>
<script>
function hostDeleteConfirm(hostID) {
var box = document.getElementById('host-delete-confirm-' + hostID);
var impact = document.getElementById('host-delete-impact-' + hostID);
document.getElementById('host-delete-confirm-input-' + hostID).value = '';
document.getElementById('host-delete-err-' + hostID).textContent = '';
box.style.display = 'block';
impact.textContent = 'Checking impact…';
fetch('/hosts/' + encodeURIComponent(hostID) + '/delete-impact')
.then(function(r){ return r.json(); })
.then(function(d){
var parts = ['Deleting ' + hostID + ' removes ' + d.guests + ' guest row(s), ' +
d.reports + ' host report(s), ' + d.log_bundles + ' agent log bundle(s)'];
if (d.wg_peer_bound) parts.push('the bound WireGuard peer');
if (d.pbs_secret_present) parts.push('the staged PBS secret');
if (d.recovery_present) parts.push('the break-glass recovery credential');
impact.textContent = parts.join(', ') + '. Host status: ' + d.status + '.' +
(d.deletable ? (d.off_tick_required ? ' No box connection for 6+ minutes — the "box is off" tick is required.' : '') : ' Host is ONLINE — deletion will be refused.');
document.getElementById('host-delete-escrow-row-' + hostID).style.display =
d.escrow_present ? 'block' : 'none';
})
.catch(function(){ impact.textContent = 'Could not compute the impact — the server will still enforce every gate.'; });
}
function hostDeleteSubmit(hostID) {
var typed = document.getElementById('host-delete-confirm-input-' + hostID).value.trim();
var err = document.getElementById('host-delete-err-' + hostID);
if (typed !== hostID) { err.textContent = 'Confirmation does not match the host id.'; return; }
var escrowRow = document.getElementById('host-delete-escrow-row-' + hostID);
var escrowCb = document.getElementById('host-delete-escrow-' + hostID);
if (escrowRow.style.display !== 'none' && !escrowCb.checked) {
err.textContent = 'This host has a key escrow — tick the acknowledgement to delete it too.';
return;
}
var offCb = document.getElementById('host-delete-off-' + hostID);
if (offCb && !offCb.checked) {
err.textContent = 'Tick "I checked: the box is off" first.';
return;
}
if (offCb) { document.getElementById('host-delete-off-hidden-' + hostID).value = '1'; }
document.getElementById('host-delete-confirm-hidden-' + hostID).value = typed;
document.getElementById('host-delete-escrow-hidden-' + hostID).value = escrowCb.checked ? '1' : '';
document.getElementById('host-delete-form-' + hostID).submit();
}
</script>
<footer style="margin-top: 2rem; color: var(--text-muted); font-size: 0.8rem; text-align: center;">
Felhom Hub <span style="font-family: var(--font-mono)">0.173.0-d53test</span>
</footer>
</div>
</body>
</html>