# Self-contained nginx.conf for the CurrenciCombo Vite SPA. # Invoked by the `currencicombo-webapp.service` systemd unit and installed # to /etc/currencicombo/webapp-nginx.conf by scripts/deployment/install.sh. # # Listens on :3000 (NPMplus upstream). NPMplus path-routes /api/* to the # orchestrator on :8080 (with SSE-friendly settings — see README.md); # everything else lands here. # This config does NOT proxy /api itself — that's intentional so a wrong # NPMplus rule fails loudly instead of silently bypassing the orchestrator. worker_processes auto; error_log /var/log/currencicombo/webapp-nginx.error.log warn; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/currencicombo/webapp-nginx.access.log combined; sendfile on; tcp_nopush on; keepalive_timeout 65; server_tokens off; gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; # Uploads/bodies: the portal is a static SPA, so any request with a body # is almost certainly mis-routed. Cap tight. client_max_body_size 1m; server { listen 3000 default_server; listen [::]:3000 default_server; server_name _; root /opt/currencicombo/webapp/dist; index index.html; # Security headers are also set by NPMplus, but apply them here too # so they survive a direct-to-CT curl for debugging. 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; # Immutable asset bundles. location /assets/ { access_log off; expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; try_files $uri =404; } # Deny sourcemaps in prod. location ~ \.map$ { access_log off; deny all; return 404; } # Guard-rail: if NPMplus fails to path-route /api/*, surface it as a # clean 421 rather than serving index.html and confusing the browser # with a JSON parse error. The SSE endpoint lives at # /api/plans/:id/events/stream, which also sits under /api/, so one # rule covers both. location /api/ { return 421 "NPMplus is misconfigured: /api/* must proxy to orchestrator :8080\n"; add_header Content-Type text/plain always; } # SPA fallback. Must come last. location / { try_files $uri $uri/ /index.html; } } }