From 75919a2071d7c0d7914896763ccfed8327854b64 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 7 Aug 2026 03:52:57 -0400 Subject: [PATCH] fix: cap peer browse, containerise the cert section, serve LAN HTTPS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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://.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) --- core/archipelago/src/api/rpc/content.rs | 27 ++++++++-- .../views/settings/NodeCertificateSection.vue | 9 ++-- scripts/setup-node-ca.sh | 53 +++++++++++++++++++ 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/core/archipelago/src/api/rpc/content.rs b/core/archipelago/src/api/rpc/content.rs index 18d11a3b..0b0d0762 100644 --- a/core/archipelago/src/api/rpc/content.rs +++ b/core/archipelago/src/api/rpc/content.rs @@ -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, })) } diff --git a/neode-ui/src/views/settings/NodeCertificateSection.vue b/neode-ui/src/views/settings/NodeCertificateSection.vue index 71554dd0..87a18359 100644 --- a/neode-ui/src/views/settings/NodeCertificateSection.vue +++ b/neode-ui/src/views/settings/NodeCertificateSection.vue @@ -52,9 +52,12 @@ onMounted(async () => {