Merges gitea-ai/main (65 commits) into the phase-13 branch (419) so one
build carries both lines — the AIUI/assistant/container work and the
open-source readiness work (licensing, the marketplace DID signature layer,
the registry domain migration, the secrets and infrastructure scrub).
Every Rust file auto-merged. The container fixes from this branch and main's
registry-domain migration and node-name genericisation coexist without
manual intervention.
Conflict resolution — all of them were modify/delete, and all were resolved
in main's favour deliberately:
`.planning/**`, `scripts/deploy-to-target.sh` and `scripts/setup-aiui-server.sh`
were deleted by main's `6ba05996` ("security: remove all infrastructure and
internal process material from the repo") and added to .gitignore there.
Keeping this branch's copies would have re-committed internal process and
infrastructure material into a repo being prepared for publication, silently
undoing that cleanup. Resolved with `git rm --cached`, so every file remains
on disk locally and in this branch's history — it is untracked, not lost.
The remaining .planning files this branch added after the merge base were
untracked the same way, so the result is consistent rather than half-tracked.
Container suite 221/221 on the merged tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1437 lines
61 KiB
Plaintext
1437 lines
61 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;
|
|
# IPv6 listener is REQUIRED: companion phones reach this node over the
|
|
# FIPS mesh at its fips0 ULA (http://[fdxx:…]) — without [::]:80 that
|
|
# address can never connect (found live 2026-07-23, a test node).
|
|
listen [::]:80 default_server;
|
|
server_name _;
|
|
|
|
root /opt/archipelago/web-ui;
|
|
index index.html;
|
|
|
|
# This node's CA, for devices that have not trusted it yet. Deliberately
|
|
# unauthenticated and served over plain HTTP: a device fetches this BEFORE
|
|
# it can validate the node's own certificate, so requiring HTTPS or a login
|
|
# here would be a chicken-and-egg. It is a public certificate — never a key
|
|
# — and the dashboard shows its fingerprint so it can be checked on sight.
|
|
location = /ca.crt {
|
|
alias /etc/archipelago/ssl/ca-download.crt;
|
|
default_type application/x-x509-ca-cert;
|
|
add_header Content-Disposition 'attachment; filename="archipelago-node-ca.crt"';
|
|
}
|
|
|
|
# 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
|
|
#
|
|
# /aiui/-scoped CSP (AIUI-04, D-19 unaffected — this is a build-time/
|
|
# runtime property, not a repository-location one): this header governs
|
|
# ONLY the document served from this location (it replaces, not adds to,
|
|
# the site-wide policy above — nginx add_header does not inherit from
|
|
# the previous level once the current level declares its own, same as
|
|
# the Cache-Control line below already does at this location). Its
|
|
# connect-src is scoped to the AIUI path prefix, so AIUI's own
|
|
# JavaScript is browser-prevented from issuing a same-origin fetch to
|
|
# /rpc/v1 with the ambient session cookie. This makes AIUI-04's
|
|
# "sandboxed by construction" an enforced boundary rather than the
|
|
# code-discipline convention the old proxy comment further down
|
|
# mistakenly implied. It does NOT split AIUI onto a different origin —
|
|
# DOM, storage, and cookies are still shared with the rest of the site;
|
|
# only what this policy polices (script/style/connect/etc. sources) is
|
|
# restricted. The residual risk (a browser that ignores or partially
|
|
# enforces CSP) is named, not silently assumed away, in 13-AI-SPEC.md
|
|
# §6 and mitigated by G-B3's rate limit on assistant.chat (13-12).
|
|
location /aiui/ {
|
|
try_files $uri $uri/ /aiui/index.html;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; media-src 'self' blob: data:; connect-src $scheme://$host:*/aiui/ blob: data:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
|
|
}
|
|
|
|
# AIUI's own JS reaching a paid/relay path directly (openrouter was
|
|
# deleted outright in 13-02 — no proxy_pass to openrouter.ai survives
|
|
# anywhere in this config) must not silently 200 via the SPA catch-all
|
|
# below. Explicit here rather than bolted onto 13-02 after the fact
|
|
# (13-02's Task 3 checkpoint, operator-accepted 2026-08-03).
|
|
location /aiui/api/openrouter/ {
|
|
return 404;
|
|
}
|
|
|
|
# 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 — re-pointed to the Rust daemon (127.0.0.1:5678),
|
|
# which enforces the session cookie itself and reads the node's single
|
|
# key ledger (data_dir/secrets/claude-api-key). The old comment here said
|
|
# "API key managed by proxy, no session gate needed" — that confuses key
|
|
# *secrecy* with spend *authorization* and is the reasoning error that
|
|
# made this an unauthenticated door into a paid API (T-13-08/T-13-09).
|
|
# Do not point this at a standalone process again. No trailing path on
|
|
# proxy_pass: nginx forwards the request URI unmodified so the daemon's
|
|
# own prefix match sees the full /aiui/api/claude/... path.
|
|
location /aiui/api/claude/ {
|
|
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_buffering off;
|
|
proxy_cache off;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
|
|
# AIUI Ollama (local AI) proxy — same daemon, same session gate as above.
|
|
# The standalone AIUI OpenRouter relay that used to live here is deleted
|
|
# outright: the node holds no key for that backend, it is not in the
|
|
# model backend chain, and an unauthenticated proxy_pass to a paid
|
|
# third-party API from the node's IP was a plain open relay (T-13-10).
|
|
# AIUI's own standalone/dev mode keeps its own proxy and is unaffected.
|
|
location /aiui/api/ollama/ {
|
|
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_buffering off;
|
|
proxy_cache off;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
|
|
# AIUI web search — session-gated through the daemon's model proxy
|
|
# (S4; it re-derives auth from the session cookie and forces JSON
|
|
# upstream). Never proxy straight to SearXNG: that left an open search
|
|
# relay attributing arbitrary queries to this node's IP.
|
|
location /aiui/api/web-search {
|
|
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 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 @aiui_assets;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
}
|
|
# AIUI's built index.html emits ABSOLUTE /assets/<hashed> paths, so they
|
|
# land here, in the MAIN UI's assets dir, where they do not exist — the
|
|
# real files are in aiui/assets/. The nearby /aiui-assets/ alias names this
|
|
# exact problem in its comment but only catches a path AIUI never requests,
|
|
# so it never fixed it.
|
|
#
|
|
# A fallback rather than copying the files up one level: a frontend deploy
|
|
# replaces web-ui wholesale (update.rs preserves the aiui/ directory itself,
|
|
# not copies made into assets/), so a copy is erased by the very next deploy
|
|
# while this survives. Hash collision with a real main-UI asset is not a
|
|
# concern — Vite hashes are content-derived, and a main asset that exists is
|
|
# served by try_files before this is ever consulted.
|
|
location @aiui_assets {
|
|
rewrite ^/assets/(.*)$ /aiui/assets/$1 break;
|
|
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;
|
|
}
|
|
|
|
# Pine node status — live node facts for the Pine launcher page and the
|
|
# seeded Home Assistant sensors. Sensitive fields are token-gated at the
|
|
# backend; nginx only forwards.
|
|
location /api/pine/status {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Authorization $http_authorization;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 15s;
|
|
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/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;
|
|
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;
|
|
|
|
# Same CA download over HTTPS — see the note in the HTTP block above.
|
|
location = /ca.crt {
|
|
alias /etc/archipelago/ssl/ca-download.crt;
|
|
default_type application/x-x509-ca-cert;
|
|
add_header Content-Disposition 'attachment; filename="archipelago-node-ca.crt"';
|
|
}
|
|
|
|
# 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
|
|
#
|
|
# /aiui/-scoped CSP — see the HTTP server block above for the full
|
|
# rationale (AIUI-04, D-19 unaffected). Both server blocks must carry
|
|
# this header — a change applied to only one leaves AIUI's JS able to
|
|
# reach /rpc/v1 with the ambient session cookie on whichever block
|
|
# actually serves the request, same class of gap as T-13-15.
|
|
location /aiui/ {
|
|
try_files $uri $uri/ /aiui/index.html;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; media-src 'self' blob: data:; connect-src $scheme://$host:*/aiui/ blob: data:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
|
|
}
|
|
|
|
# AIUI's own JS reaching a paid/relay path directly must not silently
|
|
# 200 via the SPA catch-all below — see the HTTP server block above.
|
|
location /aiui/api/openrouter/ {
|
|
return 404;
|
|
}
|
|
|
|
# See the HTTP server block above for the full rationale: re-pointed to
|
|
# the session-gated Rust daemon (T-13-08/T-13-09), OpenRouter relay
|
|
# deleted outright (T-13-10). Both server blocks must carry this fix —
|
|
# a change applied to only one leaves the exposure live on whichever
|
|
# block actually serves the request (T-13-15).
|
|
location /aiui/api/claude/ {
|
|
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_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: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_buffering off;
|
|
proxy_cache off;
|
|
proxy_connect_timeout 120s;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 120s;
|
|
}
|
|
# Session-gated web search (S4) — same rationale and shape as the HTTP
|
|
# server block above; both blocks must carry it (T-13-15).
|
|
location /aiui/api/web-search {
|
|
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 30s;
|
|
proxy_read_timeout 30s;
|
|
error_page 502 503 =503 @searxng_unavailable;
|
|
}
|
|
|
|
# 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 @aiui_assets;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
}
|
|
# AIUI's built index.html emits ABSOLUTE /assets/<hashed> paths, so they
|
|
# land here, in the MAIN UI's assets dir, where they do not exist — the
|
|
# real files are in aiui/assets/. The nearby /aiui-assets/ alias names this
|
|
# exact problem in its comment but only catches a path AIUI never requests,
|
|
# so it never fixed it.
|
|
#
|
|
# A fallback rather than copying the files up one level: a frontend deploy
|
|
# replaces web-ui wholesale (update.rs preserves the aiui/ directory itself,
|
|
# not copies made into assets/), so a copy is erased by the very next deploy
|
|
# while this survives. Hash collision with a real main-UI asset is not a
|
|
# concern — Vite hashes are content-derived, and a main asset that exists is
|
|
# served by try_files before this is ever consulted.
|
|
location @aiui_assets {
|
|
rewrite ^/assets/(.*)$ /aiui/assets/$1 break;
|
|
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;
|
|
}
|
|
|
|
# Pine node status — live node facts for the Pine launcher page and the
|
|
# seeded Home Assistant sensors. Sensitive fields are token-gated at the
|
|
# backend; nginx only forwards.
|
|
location /api/pine/status {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Authorization $http_authorization;
|
|
proxy_set_header Cookie $http_cookie;
|
|
proxy_connect_timeout 10s;
|
|
proxy_read_timeout 15s;
|
|
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/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;
|
|
}
|
|
}
|
|
|