server { # Loopback ONLY — same rule as docker/bitcoin-ui and docker/electrs-ui. # This container is host-networked, so nginx binds the HOST's address # directly; a bare `listen` would expose the page on LAN, Tailscale and # the mesh with the app gate nowhere in front of it. Binding loopback lets # the daemon claim the external addresses and authenticate them; # see appgate::listener and apps/cuprate-ui/manifest.yml (auth: gated). listen 127.0.0.1:18091; server_name _; root /usr/share/nginx/html; index index.html; # Session gate for the RPC proxy below. Internal: reachable only by # nginx's own auth_request subrequest, never by a client. location = /_session_check { internal; proxy_pass http://127.0.0.1:5678/auth/session-check; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header Host $host; proxy_set_header Cookie $http_cookie; proxy_set_header X-CSRF-Token $http_x_csrf_token; } # Cuprate's restricted RPC (host-published on 127.0.0.1:18090, auth: open # — Monero's own safe-for-public subset, what remote-node wallets use). # It injects no credentials the caller lacks, but it is still session- # gated here so the whole companion behaves as one authenticated surface # (same defence-in-depth bitcoin-ui applies to its credential-injecting # proxy: loopback reaches it without the gate's challenge). location /cuprate-rpc/ { # Preflight carries no cookies by design — answer it before the gate, # otherwise the browser reports an opaque CORS failure instead of a 401. if ($request_method = OPTIONS) { return 204; } auth_request /_session_check; proxy_pass http://127.0.0.1:18090/; 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; add_header Access-Control-Allow-Origin $scheme://$http_host always; add_header Access-Control-Allow-Credentials "true" always; add_header Vary "Origin" always; add_header Access-Control-Allow-Methods "POST, GET, OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; } # no-cache (revalidate), not no-store — same reasoning as docker/bitcoin-ui: # a rebuilt companion image must actually be seen by the browser, while the # ETag still saves the transfer when nothing changed. location / { add_header Cache-Control "no-cache"; try_files $uri $uri/ /index.html; } }