69 lines
3.7 KiB
Plaintext
69 lines
3.7 KiB
Plaintext
# ─────────────────────────────────────────────────────────────────────────────
|
|
# sitomat — SINGLE-DOMAIN reverse proxy (Opsi A).
|
|
#
|
|
# ONE public origin serves BOTH the web UI and the API; routing is by PATH. A
|
|
# small, explicit set of API prefixes goes to the API, everything else to the
|
|
# web — which is what lets the deployment expose a single port / domain.
|
|
#
|
|
# WHY ONLY THESE PREFIXES: most API routes (`/datasets`, `/submissions`,
|
|
# `/services`, `/standards`, …) COLLIDE with web pages of the same name. They do
|
|
# not need a public path because the web calls them SERVER-SIDE (Eden →
|
|
# `http://api:13000`) over the internal network. Only endpoints the BROWSER — or
|
|
# an external M2M client — hits directly are routed here:
|
|
#
|
|
# /api/auth/* better-auth (login/session) ← NEXT_PUBLIC_API_URL
|
|
# /api/v1/* documented read API (F09-A)
|
|
# /public/* open-data downloads (F07)
|
|
# /ingest/v1/* push ingest (F09-B)
|
|
# /docs swagger
|
|
# /health health check
|
|
# /datasets/<id>/versions/<v>/download browser download (F02)
|
|
#
|
|
# The last one is a REGEX on purpose: it is the ONLY browser-facing API path at
|
|
# the ROOT, and a plain prefix would swallow the web's `/datasets/[id]` page.
|
|
#
|
|
# With web + API on ONE origin there is no cross-origin request, so CORS is moot
|
|
# and the session cookie stays host-only (no `SESSION_COOKIE_DOMAIN`).
|
|
#
|
|
# `${PUBLIC_HOST}` is substituted by the nginx image's envsubst step.
|
|
# This is HTTP; add `listen 443 ssl` + certificates when TLS terminates here.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
# Uploads (F02) can be several MB — nginx's 1m default would answer 413.
|
|
client_max_body_size 100m;
|
|
|
|
server {
|
|
listen 80;
|
|
server_name ${PUBLIC_HOST};
|
|
|
|
# Inherited by every `location` below.
|
|
#
|
|
# ⚠️ `$http_host`, NOT `$host`: `$host` DROPS the port. Next.js Server
|
|
# Actions compare the forwarded host against the browser's `Origin`, which
|
|
# DOES carry a non-default port — with `$host` they mismatch and Next aborts
|
|
# every action with "Invalid Server Actions request" (a 500). `$http_host`
|
|
# preserves the port, so `Host`/`Origin` agree.
|
|
proxy_set_header Host $http_host;
|
|
proxy_set_header X-Forwarded-Host $http_host;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
# better-auth checks the browser's Origin against WEB_URL; forward it as-is.
|
|
proxy_set_header Origin $http_origin;
|
|
|
|
# ── API ──────────────────────────────────────────────────────────────────
|
|
location = /health { proxy_pass http://api:13000; }
|
|
location /docs { proxy_pass http://api:13000; }
|
|
location /api/ { proxy_pass http://api:13000; }
|
|
location /public/ { proxy_pass http://api:13000; }
|
|
location /ingest/ { proxy_pass http://api:13000; }
|
|
|
|
# The one root-level browser API path (see the header note). Regex locations
|
|
# are evaluated BEFORE the prefix `location /` below, so this wins.
|
|
location ~ ^/datasets/[^/]+/versions/[^/]+/download$ {
|
|
proxy_pass http://api:13000;
|
|
}
|
|
|
|
# ── Web (everything else) ────────────────────────────────────────────────
|
|
location / { proxy_pass http://web:13001; }
|
|
}
|