Fixes from real fresh-install feedback (Framework node .81) + its log bundle:
Backend:
- websocket: subscribe before initial snapshot — broadcasts in the gap were
silently lost, stranding clients on stale state until a hard refresh
(the "everything needs ctrl-r" bug: My Apps stuck Loading, App Store
stuck Checking, containers-scanned never arriving)
- crash recovery: check the crash marker BEFORE writing our own PID —
recovery had never run on any node (always saw its own PID and skipped);
PID-reuse guard via /proc cmdline
- boot status: pending-boot-starts registry (recovery, stack recovery,
reconciler, adoption) — scanner overlays queued-but-down apps as
Restarting instead of Stopped after a reboot; scanner-authored
Restarting resolves immediately on a settled scan (no transitional wedge)
- install deps: bounded wait (36x5s) when a dependency is installed but
still starting ("Waiting for Bitcoin to start…") instead of instant
rejection; dependency-gate rejections remove the optimistic entry (no
phantom Stopped tile) and surface as a notification
- seed backup: auth.setup persists the onboarding mnemonic as the
encrypted seed backup (reveal previously failed on EVERY node — nothing
ever wrote master_seed.enc); seed.restore stashes too; error sanitizer
lets seed/2FA errors through instead of "Check server logs"
- lnd: bitcoind.rpchost resolved from the running Bitcoin variant
(hardcoded bitcoin-knots broke Core nodes); manifest uses derived_env
- bitcoin status: clean human message for connection-reset/startup; raw
URLs + os-error chains no longer reach the app card
- fedimint-clientd: chown /var/lib/archipelago/fmcd to 1000:1000 (root-
created dir crash-looped the rootless container, EACCES) — first-boot
script + pre-start self-heal
- log volume (>1GB/day on a day-old node): journald caps drop-in (ISO +
bootstrap self-heal), bitcoind -printtoconsole=0 everywhere (90% of the
journal was IBD UpdateTip spam), tracing default debug→info
Frontend:
- Login: Enter advances to confirm field then submits; submit always
clickable with inline errors (was silently disabled on mismatch);
Restart Onboarding needs a confirming second click (the mismatch →
"onboarding restarted" trap)
- sync store: 30s state reconciliation + refetch on re-entrant connect;
20s containers-scanned escape hatch so Checking can never show forever;
fresh empty node reaches the real "no apps yet" state
- intro video: CRF20 re-encode (SSIM 0.988) + faststart — moov was at EOF
so playback needed the full 15MB first (the intro lag)
- backgrounds: 10 heaviest JPEGs → WebP q90 (9.4MB→6.6MB); 7 stayed JPEG
(WebP larger on noisy sources)
- Web5ConnectedNodes: drop unused template ref that failed vue-tsc -b
ISO/kiosk:
- nginx: /assets/ 404s no longer cached immutable for a year; HTTPS block
gained the missing /assets/ location (served index.html as images)
- kiosk: launcher/service spliced from configs/ at ISO build (stale
heredoc force-disabled GPU); MemoryHigh/Max 1200/1500→2200/2800M (kiosk
rode the reclaim throttle = the lag); firmware-intel-graphics +
firmware-amd-graphics (trixie split DMC blobs out of misc-nonfree)
Verified: cargo test 898/898 green, npm run build green with dist
contents confirmed (webp refs, lnd.png, faststart video, new strings).
Handover for ISO build + deploy: docs/HANDOVER-2026-07-02-iso-feedback.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1424 lines
57 KiB
Plaintext
1424 lines
57 KiB
Plaintext
# Rate limit zones
|
|
limit_req_zone $binary_remote_addr zone=rpc:10m rate=20r/s;
|
|
limit_req_zone $binary_remote_addr zone=auth:10m rate=3r/s;
|
|
limit_req_zone $binary_remote_addr zone=peer:10m rate=10r/s;
|
|
|
|
# Resolve external domains at request time (not startup) to prevent boot failures
|
|
resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=off;
|
|
resolver_timeout 5s;
|
|
|
|
server {
|
|
listen 80 default_server;
|
|
server_name _;
|
|
|
|
root /opt/archipelago/web-ui;
|
|
index index.html;
|
|
|
|
# Security headers
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-DNS-Prefetch-Control "off" always;
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.basemaps.cartocdn.com https://tile.openstreetmap.org; font-src 'self' data:; connect-src 'self' ws: wss: http://$host:* https:; frame-src 'self' http://$host:* https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
|
|
|
|
# Serve Nginx Proxy Manager HTTP-01 challenge files before the SPA fallback.
|
|
location ^~ /.well-known/acme-challenge/ {
|
|
default_type text/plain;
|
|
root /var/lib/archipelago/nginx-proxy-manager/data/letsencrypt-acme-challenge;
|
|
try_files $uri =404;
|
|
}
|
|
|
|
# AIUI SPA (Chat mode iframe) — SPA fallback for client-side routing
|
|
location /aiui/ {
|
|
try_files $uri $uri/ /aiui/index.html;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
}
|
|
|
|
# AIUI assets fallback — AIUI may reference /assets/ without /aiui/ prefix
|
|
location /aiui-assets/ {
|
|
alias /opt/archipelago/web-ui/aiui/assets/;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
}
|
|
|
|
# AIUI Claude API proxy (API key managed by proxy, no session gate needed)
|
|
location /aiui/api/claude/ {
|
|
proxy_pass http://127.0.0.1:3142/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
|
|
# AIUI OpenRouter API proxy (API key managed by proxy, no session gate needed)
|
|
location /aiui/api/openrouter/ {
|
|
set $upstream_1 "https://openrouter.ai/api/";
|
|
|
|
proxy_pass $upstream_1;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host openrouter.ai;
|
|
proxy_ssl_server_name on;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 120s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
|
|
# AIUI Ollama (local AI) proxy — localhost:11434
|
|
location /aiui/api/ollama/ {
|
|
proxy_pass http://127.0.0.1:11434/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
|
|
# AIUI web search proxy — SearXNG on port 8888
|
|
location /aiui/api/web-search {
|
|
proxy_pass http://127.0.0.1:8888/search;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 30s;
|
|
error_page 502 503 =503 @searxng_unavailable;
|
|
}
|
|
location @searxng_unavailable {
|
|
default_type application/json;
|
|
return 503 '{"error":"SearXNG is not running"}';
|
|
}
|
|
|
|
# JSON error responses — prevents leaking HTML error pages to API clients
|
|
location @backend_unavailable {
|
|
default_type application/json;
|
|
return 502 '{"error":{"code":"BACKEND_UNAVAILABLE","message":"Service temporarily unavailable"}}';
|
|
}
|
|
location @backend_timeout {
|
|
default_type application/json;
|
|
return 504 '{"error":{"code":"BACKEND_TIMEOUT","message":"Service did not respond in time"}}';
|
|
}
|
|
|
|
# Icons, favicon, manifest — always revalidate (no heuristic caching)
|
|
location ~* ^/(favicon\.ico|manifest\.webmanifest|assets/icon/) {
|
|
add_header Cache-Control "no-cache, must-revalidate";
|
|
try_files $uri =404;
|
|
}
|
|
|
|
# Versioned Vite assets must never fall through to index.html. During OTA
|
|
# a browser can keep an old HTML shell that references now-removed hashed
|
|
# chunks; returning HTML for /assets/*.js triggers strict MIME failures.
|
|
# The immutable header must NOT use `always`: with `always` a transient
|
|
# 404 (e.g. mid web-ui swap on first boot) gets cached by the browser for
|
|
# a year and the asset stays "missing" until a hard cache clear. Without
|
|
# `always` the header applies only to 2xx/3xx; 404s are routed to a
|
|
# named location that marks them no-store so the browser retries.
|
|
location /assets/ {
|
|
try_files $uri =404;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
error_page 404 = @asset_missing;
|
|
}
|
|
location @asset_missing {
|
|
add_header Cache-Control "no-store" always;
|
|
return 404;
|
|
}
|
|
|
|
location ~* ^/(registerSW\.js|sw\.js|workbox-[^/]+\.js)$ {
|
|
try_files $uri =404;
|
|
add_header Cache-Control "no-cache, must-revalidate" always;
|
|
}
|
|
|
|
# Serve static files (Vue.js SPA)
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
add_header Cache-Control "no-cache, must-revalidate";
|
|
}
|
|
|
|
# Peer-to-peer node messaging (receives from other nodes over Tor)
|
|
location /archipelago/ {
|
|
limit_req zone=peer burst=20 nodelay;
|
|
client_max_body_size 256m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 60s;
|
|
proxy_send_timeout 30s;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# JSON-RPC endpoint. Browser GETs are navigational mistakes, so send them
|
|
# back to the dashboard while keeping RPC POSTs proxied to the backend.
|
|
location = /rpc/v1 {
|
|
if ($request_method = GET) {
|
|
return 303 /;
|
|
}
|
|
if ($request_method = HEAD) {
|
|
return 303 /;
|
|
}
|
|
|
|
limit_req zone=rpc burst=40 nodelay;
|
|
limit_req_status 429;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
# Limit request body to 1MB for RPC calls
|
|
client_max_body_size 1m;
|
|
|
|
# Increase timeout for long-running operations (e.g., Docker image pulls)
|
|
proxy_connect_timeout 600s;
|
|
proxy_send_timeout 600s;
|
|
proxy_read_timeout 600s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Proxy API requests to backend
|
|
location /rpc/ {
|
|
limit_req zone=rpc burst=40 nodelay;
|
|
limit_req_status 429;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
# Limit request body to 1MB for RPC calls
|
|
client_max_body_size 1m;
|
|
|
|
# Increase timeout for long-running operations (e.g., Docker image pulls)
|
|
proxy_connect_timeout 600s;
|
|
proxy_send_timeout 600s;
|
|
proxy_read_timeout 600s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Backend status endpoints (must be before the SPA catch-all)
|
|
location /health {
|
|
proxy_pass http://127.0.0.1:5678/health;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_connect_timeout 5s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
location /bitcoin-status {
|
|
proxy_pass http://127.0.0.1:5678/bitcoin-status;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
location /electrs-status {
|
|
proxy_pass http://127.0.0.1:5678/electrs-status;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
location /lnd-connect-info {
|
|
proxy_pass http://127.0.0.1:5678/lnd-connect-info;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
add_header Access-Control-Allow-Origin $http_origin always;
|
|
add_header Access-Control-Allow-Credentials "true" always;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# LND REST proxy — backend handles auth + CORS
|
|
location /proxy/lnd/ {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Content sharing — peer access over Tor (no auth)
|
|
location /content {
|
|
limit_req zone=peer burst=20 nodelay;
|
|
client_max_body_size 256m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 60s;
|
|
proxy_send_timeout 30s;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Blob store — peer-facing download (HMAC capability auth, no session)
|
|
location /blob/ {
|
|
limit_req zone=peer burst=20 nodelay;
|
|
client_max_body_size 64m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 120s;
|
|
proxy_send_timeout 60s;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Blob store — local upload (session-authenticated, raw body)
|
|
location /api/blob {
|
|
client_max_body_size 64m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 120s;
|
|
proxy_send_timeout 120s;
|
|
proxy_request_buffering off;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Cookie $http_cookie;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# App Store catalog proxy — backend fetches from configured registries
|
|
# so the browser doesn't hit CORS/CSP. Without this block nginx falls
|
|
# through to the SPA index.html and the frontend gets HTML back instead
|
|
# of JSON.
|
|
location /api/app-catalog {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_connect_timeout 15s;
|
|
proxy_read_timeout 30s;
|
|
proxy_send_timeout 15s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# DWN endpoints — peer access over Tor (no auth)
|
|
location /dwn {
|
|
limit_req zone=peer burst=20 nodelay;
|
|
client_max_body_size 256m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 60s;
|
|
proxy_send_timeout 30s;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Proxy apps that set X-Frame-Options - strip header so iframe works
|
|
location /app/nextcloud/ {
|
|
proxy_pass http://127.0.0.1:8085/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/vaultwarden/ {
|
|
proxy_pass http://127.0.0.1:8082/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/immich/ {
|
|
proxy_pass http://127.0.0.1:2283/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/penpot/ {
|
|
proxy_pass http://127.0.0.1:9001/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
# Block path traversal attempts before they reach FileBrowser
|
|
location ~* /app/filebrowser/api/resources/.*/\.\. {
|
|
return 403;
|
|
}
|
|
location ~* /app/filebrowser/api/raw/.*/\.\. {
|
|
return 403;
|
|
}
|
|
location /app/filebrowser/ {
|
|
client_max_body_size 10G;
|
|
proxy_pass http://127.0.0.1:8083/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_request_buffering off;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/grafana/ {
|
|
proxy_pass http://127.0.0.1:3000/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/jellyfin/ {
|
|
proxy_pass http://127.0.0.1:8096/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location = /app/uptime-kuma/ {
|
|
return 302 /app/uptime-kuma/dashboard;
|
|
}
|
|
location /app/uptime-kuma/ {
|
|
proxy_pass http://127.0.0.1:3002/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Prefix /app/uptime-kuma;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_redirect / /app/uptime-kuma/;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/portainer/ {
|
|
proxy_pass http://127.0.0.1:9000/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
# Remaining apps (also available on HTTPS via snippet include)
|
|
location /app/searxng/ {
|
|
proxy_pass http://127.0.0.1:8888/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/indeedhub/_next/ {
|
|
proxy_pass http://127.0.0.1:7778/_next/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_cache_valid 200 30d;
|
|
add_header Cache-Control "public, max-age=2592000, immutable";
|
|
}
|
|
# IndeeHub WebSocket proxy
|
|
location /app/indeedhub/ws/ {
|
|
proxy_pass http://127.0.0.1:7778/ws/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_read_timeout 86400s;
|
|
}
|
|
location /app/indeedhub/ {
|
|
proxy_pass http://127.0.0.1:7778/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_types text/css application/javascript application/json;
|
|
sub_filter_once off;
|
|
sub_filter 'href="/' 'href="/app/indeedhub/';
|
|
sub_filter 'src="/' 'src="/app/indeedhub/';
|
|
sub_filter "href='/" "href='/app/indeedhub/";
|
|
sub_filter "src='/" "src='/app/indeedhub/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/botfights/api/ {
|
|
proxy_pass http://127.0.0.1:9100/api/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
}
|
|
location /app/botfights/ {
|
|
proxy_pass http://127.0.0.1:9100/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
proxy_hide_header Cross-Origin-Embedder-Policy;
|
|
proxy_hide_header Cross-Origin-Opener-Policy;
|
|
proxy_hide_header Cross-Origin-Resource-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_types text/css application/javascript application/json;
|
|
sub_filter_once off;
|
|
sub_filter 'href="/' 'href="/app/botfights/';
|
|
sub_filter 'src="/' 'src="/app/botfights/';
|
|
sub_filter "href='/" "href='/app/botfights/";
|
|
sub_filter "src='/" "src='/app/botfights/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script><script>window.addEventListener("message",function(e){var d=e.data;if(d&&d.type==="arcade-input"&&d.key){var t=d.action==="up"?"keyup":"keydown";document.dispatchEvent(new KeyboardEvent(t,{key:d.key,bubbles:true}))}})</script></head>';
|
|
}
|
|
location /app/gitea/ {
|
|
proxy_pass http://127.0.0.1:3001/;
|
|
proxy_set_header Host $http_host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
client_max_body_size 1G;
|
|
proxy_hide_header X-Frame-Options;
|
|
proxy_hide_header Content-Security-Policy;
|
|
# Override parent add_header to allow iframe embedding
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
|
}
|
|
location /app/lnd/ {
|
|
proxy_pass http://127.0.0.1:18083/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/mempool/ {
|
|
proxy_pass http://127.0.0.1:4080/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/photoprism/ {
|
|
proxy_pass http://127.0.0.1:2342/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/fedimint/ {
|
|
proxy_pass http://127.0.0.1:8175/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_types text/css application/javascript application/json;
|
|
sub_filter_once off;
|
|
sub_filter 'href="/' 'href="/app/fedimint/';
|
|
sub_filter 'src="/' 'src="/app/fedimint/';
|
|
sub_filter "href='/" "href='/app/fedimint/";
|
|
sub_filter "src='/" "src='/app/fedimint/";
|
|
sub_filter 'url("/' 'url("/app/fedimint/';
|
|
sub_filter "url('/" "url('/app/fedimint/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/fedimint-gateway/ {
|
|
proxy_pass http://127.0.0.1:8176/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/tailscale/ {
|
|
# Tailscale has no web UI — managed via CLI/Tailscale app
|
|
default_type application/json;
|
|
return 503 '{"error":{"code":"NO_WEB_UI","message":"Tailscale is managed via CLI"}}';
|
|
}
|
|
location /app/routstr/ {
|
|
proxy_pass http://127.0.0.1:8200/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/nostr-vpn/ {
|
|
proxy_pass http://127.0.0.1:8201/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
}
|
|
location /app/fips/ {
|
|
proxy_pass http://127.0.0.1:8202/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
}
|
|
location /app/ollama/ {
|
|
proxy_pass http://127.0.0.1:11434/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/bitcoin-ui/ {
|
|
proxy_pass http://127.0.0.1:8334/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/electrumx/ {
|
|
proxy_pass http://127.0.0.1:50002/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/endurain/ {
|
|
proxy_pass http://127.0.0.1:8080/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/nginx-proxy-manager/ {
|
|
proxy_pass http://127.0.0.1:8081/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/btcpay/ {
|
|
proxy_pass http://127.0.0.1:23000/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Prefix /app/btcpay;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_redirect http://127.0.0.1:23000/ /app/btcpay/;
|
|
proxy_redirect / /app/btcpay/;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/homeassistant/ {
|
|
proxy_pass http://127.0.0.1:8123/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 86400s;
|
|
proxy_send_timeout 86400s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
|
|
# External site proxies — strip X-Frame-Options so iframe embedding works.
|
|
# add_header here prevents inheritance of server-level X-Frame-Options.
|
|
location /ext/484-kitchen/ {
|
|
set $upstream_3 "https://484.kitchen/";
|
|
|
|
proxy_pass $upstream_3;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host 484.kitchen;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_ssl_server_name on;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
sub_filter_once off;
|
|
sub_filter_types text/css application/javascript;
|
|
sub_filter 'href="/' 'href="/ext/484-kitchen/';
|
|
sub_filter 'src="/' 'src="/ext/484-kitchen/';
|
|
sub_filter 'action="/' 'action="/ext/484-kitchen/';
|
|
sub_filter "href='/" "href='/ext/484-kitchen/";
|
|
sub_filter "src='/" "src='/ext/484-kitchen/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /ext/arch-presentation/ {
|
|
set $upstream_4 "https://present.l484.com/";
|
|
|
|
proxy_pass $upstream_4;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host present.l484.com;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_ssl_server_name on;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
sub_filter_once off;
|
|
sub_filter_types text/css application/javascript;
|
|
sub_filter 'href="/' 'href="/ext/arch-presentation/';
|
|
sub_filter 'src="/' 'src="/ext/arch-presentation/';
|
|
sub_filter 'action="/' 'action="/ext/arch-presentation/';
|
|
sub_filter "href='/" "href='/ext/arch-presentation/";
|
|
sub_filter "src='/" "src='/ext/arch-presentation/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /ext/nostrudel/ {
|
|
set $upstream_5 "https://nostrudel.ninja/";
|
|
|
|
proxy_pass $upstream_5;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host nostrudel.ninja;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_ssl_server_name on;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
proxy_hide_header Cross-Origin-Embedder-Policy;
|
|
proxy_hide_header Cross-Origin-Opener-Policy;
|
|
proxy_hide_header Cross-Origin-Resource-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
sub_filter_once off;
|
|
sub_filter_types text/css application/javascript;
|
|
sub_filter 'href="/' 'href="/ext/nostrudel/';
|
|
sub_filter 'src="/' 'src="/ext/nostrudel/';
|
|
sub_filter 'action="/' 'action="/ext/nostrudel/';
|
|
sub_filter "href='/" "href='/ext/nostrudel/";
|
|
sub_filter "src='/" "src='/ext/nostrudel/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
|
|
# Proxy WebSocket
|
|
location /ws {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_read_timeout 86400s;
|
|
}
|
|
}
|
|
|
|
# HTTPS - required for PWA install (Add to Home Screen) from dev servers
|
|
server {
|
|
listen 443 ssl default_server;
|
|
server_name _;
|
|
|
|
ssl_certificate /etc/archipelago/ssl/archipelago.crt;
|
|
ssl_certificate_key /etc/archipelago/ssl/archipelago.key;
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
|
|
|
root /opt/archipelago/web-ui;
|
|
index index.html;
|
|
include snippets/archipelago-pwa.conf;
|
|
|
|
# Security headers
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-DNS-Prefetch-Control "off" always;
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.basemaps.cartocdn.com https://tile.openstreetmap.org; font-src 'self' data:; connect-src 'self' ws: wss: http://$host:* https:; frame-src 'self' http://$host:* https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
|
|
|
|
# JSON error responses — prevents leaking HTML error pages to API clients
|
|
location @backend_unavailable {
|
|
default_type application/json;
|
|
return 502 '{"error":{"code":"BACKEND_UNAVAILABLE","message":"Service temporarily unavailable"}}';
|
|
}
|
|
location @backend_timeout {
|
|
default_type application/json;
|
|
return 504 '{"error":{"code":"BACKEND_TIMEOUT","message":"Service did not respond in time"}}';
|
|
}
|
|
|
|
# AIUI SPA (Chat mode iframe) — SPA fallback for client-side routing
|
|
location /aiui/ {
|
|
try_files $uri $uri/ /aiui/index.html;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
}
|
|
location /aiui/api/claude/ {
|
|
proxy_pass http://127.0.0.1:3142/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
location /aiui/api/ollama/ {
|
|
proxy_pass http://127.0.0.1:11434/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
location /aiui/api/openrouter/ {
|
|
set $upstream_6 "https://openrouter.ai/api/";
|
|
|
|
proxy_pass $upstream_6;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host openrouter.ai;
|
|
proxy_ssl_server_name on;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 120s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
|
|
# Icons, favicon, manifest — always revalidate (no heuristic caching)
|
|
location ~* ^/(favicon\.ico|manifest\.webmanifest|assets/icon/) {
|
|
add_header Cache-Control "no-cache, must-revalidate";
|
|
try_files $uri =404;
|
|
}
|
|
|
|
# Versioned Vite assets must never fall through to index.html (mirrors the
|
|
# HTTP block). No `always` on the immutable header: a transient 404 must
|
|
# not be cached for a year — 404s go to @asset_missing (no-store) instead.
|
|
location /assets/ {
|
|
try_files $uri =404;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
error_page 404 = @asset_missing;
|
|
}
|
|
location @asset_missing {
|
|
add_header Cache-Control "no-store" always;
|
|
return 404;
|
|
}
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
location /archipelago/ {
|
|
limit_req zone=peer burst=20 nodelay;
|
|
client_max_body_size 256m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 60s;
|
|
proxy_send_timeout 30s;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
location /health {
|
|
proxy_pass http://127.0.0.1:5678/health;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_connect_timeout 5s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
location /bitcoin-status {
|
|
proxy_pass http://127.0.0.1:5678/bitcoin-status;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
location /electrs-status {
|
|
proxy_pass http://127.0.0.1:5678/electrs-status;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
location /lnd-connect-info {
|
|
proxy_pass http://127.0.0.1:5678/lnd-connect-info;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
add_header Access-Control-Allow-Origin $http_origin always;
|
|
add_header Access-Control-Allow-Credentials "true" always;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# LND REST proxy — backend handles auth + CORS
|
|
location /proxy/lnd/ {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
proxy_send_timeout 5s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Content sharing — peer access over Tor (no auth)
|
|
location /content {
|
|
limit_req zone=peer burst=20 nodelay;
|
|
client_max_body_size 256m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 60s;
|
|
proxy_send_timeout 30s;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Blob store — peer-facing download (HMAC capability auth, no session)
|
|
location /blob/ {
|
|
limit_req zone=peer burst=20 nodelay;
|
|
client_max_body_size 64m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 120s;
|
|
proxy_send_timeout 60s;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# Blob store — local upload (session-authenticated, raw body)
|
|
location /api/blob {
|
|
client_max_body_size 64m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 120s;
|
|
proxy_send_timeout 120s;
|
|
proxy_request_buffering off;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Cookie $http_cookie;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# App Store catalog proxy — backend fetches from configured registries
|
|
# so the browser doesn't hit CORS/CSP. Without this block nginx falls
|
|
# through to the SPA index.html and the frontend gets HTML back instead
|
|
# of JSON.
|
|
location /api/app-catalog {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_connect_timeout 15s;
|
|
proxy_read_timeout 30s;
|
|
proxy_send_timeout 15s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
# DWN endpoints — peer access over Tor (no auth)
|
|
location /dwn {
|
|
limit_req zone=peer burst=20 nodelay;
|
|
client_max_body_size 256m;
|
|
proxy_connect_timeout 30s;
|
|
proxy_read_timeout 60s;
|
|
proxy_send_timeout 30s;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
location /rpc/ {
|
|
limit_req zone=rpc burst=40 nodelay;
|
|
limit_req_status 429;
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
# Limit request body to 1MB for RPC calls
|
|
client_max_body_size 1m;
|
|
|
|
proxy_connect_timeout 600s;
|
|
proxy_send_timeout 600s;
|
|
proxy_read_timeout 600s;
|
|
error_page 502 503 = @backend_unavailable;
|
|
error_page 504 = @backend_timeout;
|
|
}
|
|
|
|
location /app/nextcloud/ {
|
|
proxy_pass http://127.0.0.1:8085/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/vaultwarden/ {
|
|
proxy_pass http://127.0.0.1:8082/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/immich/ {
|
|
proxy_pass http://127.0.0.1:2283/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/penpot/ {
|
|
proxy_pass http://127.0.0.1:9001/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/btcpay/ {
|
|
proxy_pass http://127.0.0.1:23000/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Prefix /app/btcpay;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_redirect http://127.0.0.1:23000/ /app/btcpay/;
|
|
proxy_redirect / /app/btcpay/;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /app/homeassistant/ {
|
|
proxy_pass http://127.0.0.1:8123/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
proxy_read_timeout 86400s;
|
|
proxy_send_timeout 86400s;
|
|
proxy_set_header Accept-Encoding "";
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
# All remaining app proxies (mempool, fedimint, lnd, bitcoin-ui, etc.)
|
|
include snippets/archipelago-https-app-proxies.conf;
|
|
|
|
# External site proxies — strip X-Frame-Options so iframe embedding works.
|
|
# add_header here prevents inheritance of server-level X-Frame-Options.
|
|
location /ext/484-kitchen/ {
|
|
set $upstream_8 "https://484.kitchen/";
|
|
|
|
proxy_pass $upstream_8;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host 484.kitchen;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_ssl_server_name on;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
sub_filter_once off;
|
|
sub_filter_types text/css application/javascript;
|
|
sub_filter 'href="/' 'href="/ext/484-kitchen/';
|
|
sub_filter 'src="/' 'src="/ext/484-kitchen/';
|
|
sub_filter 'action="/' 'action="/ext/484-kitchen/';
|
|
sub_filter "href='/" "href='/ext/484-kitchen/";
|
|
sub_filter "src='/" "src='/ext/484-kitchen/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /ext/arch-presentation/ {
|
|
set $upstream_9 "https://present.l484.com/";
|
|
|
|
proxy_pass $upstream_9;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host present.l484.com;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_ssl_server_name on;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
sub_filter_once off;
|
|
sub_filter_types text/css application/javascript;
|
|
sub_filter 'href="/' 'href="/ext/arch-presentation/';
|
|
sub_filter 'src="/' 'src="/ext/arch-presentation/';
|
|
sub_filter 'action="/' 'action="/ext/arch-presentation/';
|
|
sub_filter "href='/" "href='/ext/arch-presentation/";
|
|
sub_filter "src='/" "src='/ext/arch-presentation/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
location /ext/nostrudel/ {
|
|
set $upstream_10 "https://nostrudel.ninja/";
|
|
|
|
proxy_pass $upstream_10;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host nostrudel.ninja;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_ssl_server_name on;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
proxy_hide_header Cross-Origin-Embedder-Policy;
|
|
proxy_hide_header Cross-Origin-Opener-Policy;
|
|
proxy_hide_header Cross-Origin-Resource-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
sub_filter_once off;
|
|
sub_filter_types text/css application/javascript;
|
|
sub_filter 'href="/' 'href="/ext/nostrudel/';
|
|
sub_filter 'src="/' 'src="/ext/nostrudel/';
|
|
sub_filter 'action="/' 'action="/ext/nostrudel/';
|
|
sub_filter "href='/" "href='/ext/nostrudel/";
|
|
sub_filter "src='/" "src='/ext/nostrudel/";
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
}
|
|
|
|
location /ws {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_read_timeout 86400s;
|
|
}
|
|
}
|
|
|
|
# External site reverse proxies — each on its own port so SPAs work at root.
|
|
# Strips X-Frame-Options to allow iframe embedding from Archipelago UI.
|
|
# Injects NIP-07 nostr-provider.js for Nostr login integration.
|
|
server {
|
|
listen 8902;
|
|
server_name _;
|
|
location / {
|
|
set $upstream_12 "https://484.kitchen";
|
|
|
|
proxy_pass $upstream_12;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host 484.kitchen;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_ssl_server_name on;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
sub_filter_once on;
|
|
}
|
|
location = /nostr-provider.js {
|
|
alias /opt/archipelago/web-ui/nostr-provider.js;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 8903;
|
|
server_name _;
|
|
location / {
|
|
set $upstream_13 "https://present.l484.com";
|
|
|
|
proxy_pass $upstream_13;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host present.l484.com;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_ssl_server_name on;
|
|
proxy_hide_header X-Frame-Options;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
|
sub_filter_once on;
|
|
}
|
|
location = /nostr-provider.js {
|
|
alias /opt/archipelago/web-ui/nostr-provider.js;
|
|
}
|
|
}
|