# Caddy config for serving the standalone Nosterm client SPA. # # In production, replace `:80` with your domain to get automatic HTTPS, e.g.: # nosterm.example.com { ... } # # The `/relay*` proxy is OPTIONAL. It lets the client reach a relay on the same # origin (ws:///relay), which sidesteps mixed-content and CORS concerns. # The upstream is configurable via the RELAY_UPSTREAM env var (default # `relay:3334`, the service name in this dir's docker-compose.yml). If you point # the client directly at an external relay via PUBLIC_DEFAULT_RELAYS, you can # delete the `handle /relay*` block. :80 { encode gzip zstd # Tor-overlay relay bridge: proxy to a clearnet→onion bridge sidecar that # forwards through Tor to an onion-only relay. Exposing it at a same-origin # wss:// path lets a plain browser reach the onion relay without Tor Browser # and without an HTTPS-page/insecure-ws mixed-content block. Only wired when # RELAY_TOR_UPSTREAM is set (the demo deploy sets it); otherwise it points at # a dead default and simply 502s, which is harmless if unused. MUST precede # the /relay* block below, which would otherwise swallow this prefix. handle /relay-tor* { reverse_proxy {$RELAY_TOR_UPSTREAM:tor-bridge:3335} } # Home relay: proxy WebSocket + NIP-11 requests to the configured relay. handle /relay* { reverse_proxy {$RELAY_UPSTREAM:relay:3334} } # Runtime client config: regenerated by the entrypoint from env on startup. # Never cache it so per-deployment relay changes take effect immediately. handle /config.json { root * /srv header Cache-Control "no-store" file_server } # Everything else: serve the static SPA with client-side-routing fallback. handle { root * /srv try_files {path} /index.html file_server } # Baseline security headers. No third-party analytics are ever included. header { X-Content-Type-Options nosniff X-Frame-Options DENY Referrer-Policy no-referrer # Content-Security-Policy for a client-only SPA: # - default-src 'self' only same-origin by default # - connect-src wss:/ws: reach ANY relay the user configures (core) # - connect-src https: NIP-05 verification fetches a domain's # /.well-known/nostr.json over HTTPS. Read-only # JSON GETs; no credentials are ever sent. # - script-src 'unsafe-inline' required for SvelteKit's hydration # bootstrap + the anti-FOUC theme script, # which are inlined into the static fallback # page (not prerendered, so hashes cannot be # pinned at build time). # - script-src 'unsafe-eval' required by NDK's event-emitter dependency # (tseep), which JIT-compiles listener # dispatch via `new Function`. Only triggers # once a relay subscription opens. No REMOTE # scripts are ever allowed, and remote Nostr # content is never rendered as HTML, so it # cannot inject code. # - object-src 'none' no plugins Content-Security-Policy "default-src 'self'; connect-src 'self' wss: ws: https:; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" -Server } }