# ───────────────────────────────────────────────────────────────────────────── # 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//versions//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. proxy_set_header Host $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; } }