fix: cap peer browse, containerise the cert section, serve LAN HTTPS

content.browse-all-peers had a per-peer timeout but no OVERALL budget. On this
node that meant >45s with no answer, which the assistant reported to the
operator as "having trouble accessing the peer content list". Measured cause:
16 federated peers, 1 reachable. Now bounded to 20s total, returning partial
results with peers_reached / peers_total / peers_unreachable / partial, so the
assistant can say "1 of 16 peers answered" instead of implying the rest have
nothing. Verified on the node: 20.015s, was >45s.

NodeCertificateSection had no container — I copied a section that sits INSIDE a
card rather than one that provides its own. Now uses the same
`glass-card px-6 py-6 mb-6` shell and heading level as every other settings
section, so it matches on desktop and mobile.

setup-node-ca.sh now also ensures the nginx HTTPS listener, because a CA is
useless if nothing serves TLS. It binds LAN addresses ONLY: tailscaled already
owns :443 on the tailnet addresses with its own Let's Encrypt cert, so a plain
`listen 443 default_server` binds 0.0.0.0 and fails EADDRINUSE — and nginx then
keeps running the OLD config while the reload reports success. Hit exactly that
on archi-dev-box. Port 80 keeps serving: nodes are reached by IP on LANs where
forcing a redirect would strand anyone who has not installed the CA.

Live now: https://192.168.63.240/ and https://<host>.local/ both 200 with
verify=0 against the node CA, http still 200, tailscaled's 443 untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-07 03:52:57 -04:00
co-authored by Claude Opus 5
parent 0a23c99463
commit 75919a2071
3 changed files with 82 additions and 7 deletions
+23 -4
View File
@@ -1261,16 +1261,31 @@ impl RpcHandler {
let mut reached = 0usize;
let mut unreachable = 0usize;
// OVERALL budget, not just per-peer. With a dozen federated peers an
// 8s per-peer timeout still adds up past any usable answer — measured
// on the node: this call returned nothing after 45 seconds, which the
// assistant reports to the user as "having trouble accessing the peer
// content list". Partial results beat a timeout: whatever answered
// inside the budget is returned, and the counts say what was missed.
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(20);
// Sequential with a per-peer timeout rather than an unbounded fan-out:
// 02-08 traced a real UI stall to content.browse-peer starving the
// connection pool, and the assistant is not latency-critical.
for onion in &onions {
if tokio::time::Instant::now() >= deadline {
// Everything not yet tried counts as unreachable for this call
// rather than being silently omitted.
unreachable += onions.len() - reached - unreachable;
break;
}
let params = Some(serde_json::json!({ "onion": onion }));
match tokio::time::timeout(
// Never wait past the overall deadline for a single peer.
let per_peer = std::cmp::min(
std::time::Duration::from_secs(8),
self.handle_content_browse_peer(params),
)
.await
deadline.saturating_duration_since(tokio::time::Instant::now()),
);
match tokio::time::timeout(per_peer, self.handle_content_browse_peer(params)).await
{
Ok(Ok(v)) => {
reached += 1;
@@ -1292,6 +1307,10 @@ impl RpcHandler {
"items": items,
"peers_reached": reached,
"peers_unreachable": unreachable,
"peers_total": onions.len(),
// Explicit so the assistant can say "3 of 12 peers answered"
// instead of implying the empty ones have nothing to share.
"partial": unreachable > 0,
}))
}