# ============================================================================= # PolyglotAlpha — nginx reverse-proxy config for Hugging Face Spaces (W23) # ============================================================================= # HF Spaces exposes exactly ONE port externally (the value of `app_port` in # the README frontmatter — we use 7860). nginx fans the single ingress out # to two backends: # # /api/*, /sse/*, /trigger/*, /events/*, /leaderboard/*, # /operators/*, /agents/*, /health -> uvicorn :8000 (FastAPI) # everything else -> next start :3001 (Next.js) # # Two routing concerns to keep in mind: # 1. SSE streams need `proxy_buffering off` AND a long read timeout — the # default 60 s would kill the lifecycle progress stream mid-event. # 2. Next.js HMR/websocket upgrades need the Upgrade/Connection headers # even in `next start` (the React server channel uses them). # ----------------------------------------------------------------------------- worker_processes 1; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server_tokens off; # Lift the default 1 MB request body limit a touch — trigger payloads # with mock_bids can comfortably fit in 64 kB, but 4 MB gives headroom # for future demo features without surprising 413s. client_max_body_size 4m; upstream polyglot_backend { server 127.0.0.1:8000; } upstream polyglot_frontend { server 127.0.0.1:3001; } server { listen 7860 default_server; listen [::]:7860 default_server; server_name _; # ─── /api/sse/ (SSE — most specific, NO buffering) ───────────────── # Has to come BEFORE /api/ generic so the no-buffer settings apply. # `rewrite ... break` strips the /api prefix before forwarding to # FastAPI (which serves at /sse/events). location ^~ /api/sse/ { rewrite ^/api(/.*)$ $1 break; proxy_pass http://polyglot_backend; proxy_http_version 1.1; proxy_buffering off; proxy_cache off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Connection ""; add_header X-Accel-Buffering no always; } # ─── /api/* — all other backend routes ───────────────────────────── # nginx strips the /api prefix so FastAPI's existing `/events`, # `/trigger`, `/leaderboard`, `/operators`, `/agents`, etc. # routes serve as-is. The UI's `NEXT_PUBLIC_API_BASE=/api` means # `fetch("/events/6")` becomes `fetch("/api/events/6")` from the # browser, which nginx routes here. # # WITHOUT this prefix, `/events/6` would be claimed by Next.js's # page router (which has its own `/events/[id]` route) — the # backend JSON response would never reach the UI's React tree. location ^~ /api/ { rewrite ^/api(/.*)$ $1 break; proxy_pass http://polyglot_backend; proxy_read_timeout 600s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Health check used by the entrypoint pre-seed loop AND by HF Spaces # readiness probing. location = /health { proxy_pass http://polyglot_backend; proxy_read_timeout 5s; access_log off; } location = /healthz { return 200 "ok\n"; add_header Content-Type text/plain; access_log off; } # ---- Frontend: everything else ------------------------------------- # Next.js handles its own routing (App Router); the websocket/upgrade # headers are required for streaming server components and any # future websocket transport. location / { proxy_pass http://polyglot_frontend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; proxy_read_timeout 60s; } } }