# fire-safety-ymd public HTTPS entrypoint (example) # # This file is intended to be included from nginx's http context (normally # /etc/nginx/conf.d/*.conf). Replace the safe app-id in the exact chat # location with the value configured in FIRE_SAFETY_CHAT_COMPAT_APP_ID. # /chat and /chat/ are the optional public test page. They are always # proxied here, while Go's FIRE_SAFETY_CHAT_PAGE_ENABLED switch decides # whether they serve HTML or return 404. # Never put FIRE_SAFETY_CHAT_AUTH_TOKEN, FIRE_SAFETY_MCP_AUTH_TOKEN, a # SuperAgent key, or a database credential in this file. limit_req_zone $binary_remote_addr zone=fire_safety_chat:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=fire_safety_mcp:10m rate=20r/s; upstream fire_safety_ymd_backend { server 127.0.0.1:16587; keepalive 16; } server { listen 80; listen [::]:80; server_name agent.nianxx.com; return 301 https://agent.nianxx.com$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name agent.nianxx.com; ssl_certificate "/cert/agent.nianxx.com.pem"; ssl_certificate_key "/cert/agent.nianxx.com.key"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_protocols TLSv1.2 TLSv1.3; limit_req_status 429; # Optional same-origin public test page. The Go handler serves the page # only when FIRE_SAFETY_CHAT_PAGE_ENABLED=true; otherwise these exact # routes return 404. The page never receives a token from Nginx. location = /chat { limit_req zone=fire_safety_chat burst=20 nodelay; client_max_body_size 16k; proxy_pass http://fire_safety_ymd_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Request-ID $request_id; proxy_buffering on; proxy_cache off; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_next_upstream off; proxy_intercept_errors off; } location = /chat/ { limit_req zone=fire_safety_chat burst=20 nodelay; client_max_body_size 16k; proxy_pass http://fire_safety_ymd_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Request-ID $request_id; proxy_buffering on; proxy_cache off; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_next_upstream off; proxy_intercept_errors off; } # The page keeps CSS and JavaScript as same-origin static resources. Keep # these locations exact and proxy them to Go so the Go page switch applies # to the complete page surface (HTML, CSS and JavaScript). location = /chat/app.css { limit_req zone=fire_safety_chat burst=20 nodelay; client_max_body_size 16k; proxy_pass http://fire_safety_ymd_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Request-ID $request_id; proxy_buffering on; proxy_cache off; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_next_upstream off; proxy_intercept_errors off; } location = /chat/app.js { limit_req zone=fire_safety_chat burst=20 nodelay; client_max_body_size 16k; proxy_pass http://fire_safety_ymd_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Request-ID $request_id; proxy_buffering on; proxy_cache off; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_next_upstream off; proxy_intercept_errors off; } # DashScope-compatible user chat. Keep this exact location restricted to # the one configured app ID; do not replace it with a catch-all regex. # The incoming xtoken is forwarded unchanged and validated by Go. location = /api/v1/apps/replace-with-fire-safety-app-id/completion { limit_req zone=fire_safety_chat burst=20 nodelay; client_max_body_size 128k; proxy_pass http://fire_safety_ymd_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Request-ID $request_id; # X-Accel-Buffering is a response header; the Go handler also sets it. add_header X-Accel-Buffering no always; proxy_buffering off; proxy_request_buffering off; proxy_cache off; gzip off; proxy_connect_timeout 5s; proxy_read_timeout 660s; proxy_send_timeout 660s; # Do not retry a streaming POST upstream and risk a duplicate run. proxy_next_upstream off; proxy_intercept_errors off; } # SuperAgent's inbound MCP callback. Go validates its independent # Authorization: Bearer token; this proxy deliberately does not inject it. location = /mcp { limit_req zone=fire_safety_mcp burst=40 nodelay; client_max_body_size 256k; proxy_pass http://fire_safety_ymd_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Request-ID $request_id; proxy_buffering on; proxy_cache off; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_next_upstream off; proxy_intercept_errors off; } # Optional public liveness check. It is intentionally not a readiness or # database check; omit this location if health must remain private. location = /health { client_max_body_size 1k; proxy_pass http://fire_safety_ymd_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Request-ID $request_id; proxy_connect_timeout 5s; proxy_read_timeout 5s; proxy_send_timeout 5s; proxy_next_upstream off; proxy_intercept_errors off; } # Do not expose every Go route through the public hostname. location / { default_type application/json; return 404 '{"error":{"code":"NOT_FOUND","message":"Not found."}}'; } error_page 429 = @rate_limited; location @rate_limited { internal; default_type application/json; return 429 '{"error":{"code":"RATE_LIMITED","message":"Too many requests."}}'; } }