fix(nginx): route /api/peer-content/* to the backend for B3 streaming

The B3 streaming proxy endpoint existed in the backend but nginx had no
location for /api/peer-content/*, so the browser's requests fell through to
the SPA (200 text/html) and media still wouldn't play. Add an
NGINX_PEER_CONTENT_BLOCK that bootstrap patches into every server block
(forwards Cookie for session auth + Range, proxy_buffering off). Idempotent;
covers fresh-ISO nodes too since bootstrap runs on every startup.

Verified on .198: after restart the async nginx patch lands and
/api/peer-content/<onion>/<id> returns 401 (reaches backend, auth-gated)
instead of the SPA; nginx block present in both server blocks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-15 14:07:39 -04:00
co-authored by Claude Opus 4.8
parent 5c8707432b
commit 602b9cd3df
2 changed files with 34 additions and 1 deletions
+31 -1
View File
@@ -48,6 +48,14 @@ const NGINX_BITCOIN_STATUS_BLOCK: &str = "\n location /bitcoin-status {\n
/// sync with the canonical block in image-recipe/configs/nginx-archipelago.conf.
const NGINX_LND_PROXY_BLOCK: &str = "\n # LND REST proxy — backend handles auth + CORS\n location /proxy/lnd/ {\n proxy_pass http://127.0.0.1:5678;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header Cookie $http_cookie;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_connect_timeout 10s;\n proxy_read_timeout 10s;\n proxy_send_timeout 5s;\n error_page 502 503 = @backend_unavailable;\n error_page 504 = @backend_timeout;\n }\n";
/// Inserted into every server block lacking the peer-content streaming proxy.
/// Without it, the browser's `<video>`/`<audio>` Range requests to
/// `/api/peer-content/*` fall through to the SPA index.html (HTML, no Range)
/// and peer media won't play (B3). Forwards Cookie (session auth) + Range and
/// disables buffering so streaming works. Kept in sync with the canonical
/// block in image-recipe/configs/nginx-archipelago.conf.
const NGINX_PEER_CONTENT_BLOCK: &str = "\n # Peer content streaming proxy (B3) — Range-streams a peer's media file\n location /api/peer-content/ {\n proxy_pass http://127.0.0.1:5678;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header Cookie $http_cookie;\n proxy_set_header Range $http_range;\n proxy_buffering off;\n proxy_connect_timeout 10s;\n proxy_read_timeout 120s;\n error_page 502 503 = @backend_unavailable;\n error_page 504 = @backend_timeout;\n }\n";
/// Entry point called from main startup. Never returns an error to the caller —
/// failing to bootstrap host artifacts must not prevent the backend from serving.
pub async fn ensure_doctor_installed() {
@@ -536,8 +544,14 @@ async fn patch_nginx_conf(path: &str) -> Result<bool> {
let missing_app_catalog = !content.contains("location /api/app-catalog");
let missing_bitcoin_status = !content.contains("location /bitcoin-status");
let missing_lnd_proxy = !content.contains("location /proxy/lnd/");
let missing_peer_content = !content.contains("location /api/peer-content");
let has_lnd_dup_cors = content.contains(NGINX_LND_DUP_CORS);
if !missing_app_catalog && !missing_bitcoin_status && !missing_lnd_proxy && !has_lnd_dup_cors {
if !missing_app_catalog
&& !missing_bitcoin_status
&& !missing_lnd_proxy
&& !missing_peer_content
&& !has_lnd_dup_cors
{
return Ok(false);
}
@@ -567,6 +581,22 @@ async fn patch_nginx_conf(path: &str) -> Result<bool> {
}
}
if missing_peer_content {
// Same anchoring as the LND proxy: prepend the block to every server
// block so /api/peer-content/* reaches the backend instead of the SPA.
let anchor = if patched.contains(" location /lnd-connect-info {") {
" location /lnd-connect-info {"
} else {
" location /electrs-status {"
};
if patched.contains(anchor) {
let replacement = format!("{}{}", NGINX_PEER_CONTENT_BLOCK, anchor);
patched = patched.replace(anchor, &replacement);
} else {
warn!("nginx conf missing anchor — skipping /api/peer-content patch");
}
}
if missing_bitcoin_status {
let anchor = " location /electrs-status {";
if !patched.contains(anchor) {