兩個 server block(公網 stage-9527 + 內網 IP 直連 192.168.0.130)各加 一條 location /ws/ → proxy_pass api-server(:3721)。缺這條時 /ws/* 會落到 catch-all → Next.js → 404,擋住前端推論頁 WS 握手。 - WS upgrade 三要素(http/1.1 + Upgrade + Connection $connection_upgrade) - 長 timeout 86400s + proxy_buffering off(比照 /tunnel/connect) - 不影響既有 /tunnel/connect(不同 path 前綴、互不 shadow) - 兩 block header 對齊(含 X-Forwarded-Host) 驗證:/ws/devices/test-id/inference 不帶 cookie → 401(過 backend auth) 不再 404;/tunnel/connect 回歸 401;preset 下載 200/206;demo 設定保留。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
583 lines
26 KiB
Plaintext
583 lines
26 KiB
Plaintext
# visionA — stage 環境 nginx 設定
|
||
#
|
||
# 角色定位:
|
||
# 公司 host nginx ── HTTPS :9527(LE 證書) ──→ container :80(本檔)
|
||
# ↓
|
||
# ┌───────────────────────────────────────────────────────────────┐
|
||
# │ container 內 nginx :80 │
|
||
# │ /healthz → 直接回 200(給 docker healthcheck) │
|
||
# │ /api/* → 127.0.0.1:3721 (api-server) │
|
||
# │ /storage/* → 127.0.0.1:3721 (api-server presigned URL) │
|
||
# │ /tunnel/connect→ 127.0.0.1:3800 (remote-proxy WS upgrade) │
|
||
# │ /_next/static/ → 127.0.0.1:3000 (next standalone, 1y cache) │
|
||
# │ / → 127.0.0.1:3000 (next standalone server.js) │
|
||
# └───────────────────────────────────────────────────────────────┘
|
||
#
|
||
# 上游 process 由 entrypoint.stage.sh 啟動,全在同一 container loopback。
|
||
|
||
# ────────── upstream 定義 ──────────
|
||
# 在 server block 外層 alias,讓 proxy_pass 可重用。
|
||
|
||
upstream visiona_api {
|
||
server 127.0.0.1:3721;
|
||
keepalive 32;
|
||
}
|
||
|
||
upstream visiona_tunnel {
|
||
server 127.0.0.1:3800;
|
||
keepalive 16;
|
||
}
|
||
|
||
upstream visiona_frontend {
|
||
server 127.0.0.1:3000;
|
||
keepalive 32;
|
||
}
|
||
|
||
# WebSocket upgrade map(給 /tunnel/connect 用)
|
||
# 在 http context 用,server block 內也可繼承。
|
||
map $http_upgrade $connection_upgrade {
|
||
default upgrade;
|
||
'' close;
|
||
}
|
||
|
||
# ────────── Host header 白名單(M2 — trust boundary) ──────────
|
||
#
|
||
# 公司 host nginx 已 termination HTTPS(stage-9527.innovedus.com:9527),
|
||
# 但 reverse proxy 把原始 Host header 透傳進來。為避免 backend 拿到偽造 Host
|
||
# 來組 absolute URL(return_to / email link / cache key),這層強制白名單:
|
||
#
|
||
# - 任何不符合 server_name 的 host(含直接打 IP、攻擊者偽造 Host)→ 444 close
|
||
# - 命中 stage-9527.innovedus.com 的請求才進真正的 server block
|
||
#
|
||
# 444 = nginx 專屬 status,直接關連線、不回 response,不給攻擊者反饋。
|
||
server {
|
||
listen 80 default_server;
|
||
listen [::]:80 default_server;
|
||
server_name _;
|
||
|
||
# /healthz 例外:Docker healthcheck 從 container 內打 localhost/healthz
|
||
# (Host: localhost 不命中 stage-9527 白名單,但內部源頭可信任)
|
||
# 限制 source = 127.0.0.0/8 防止外部偽造 Host 跳過白名單
|
||
#
|
||
# ⚠️ 為什麼這層不能用 server-level `return 444`(修正前的 bug):
|
||
# server context 的 `return` 在 nginx rewrite phase 執行,會「先於」location
|
||
# 匹配短路掉所有請求 —— 包含這個 `location = /healthz`。修正前 default_server
|
||
# 結尾寫 `return 444;`,導致 docker healthcheck(Host: localhost、來源 127.0.0.1)
|
||
# 的 /healthz 也被打成 444 → container 長期 unhealthy(false 444)。
|
||
# 正解:把 catch-all 444 收進 `location /`,讓 exact-match `location = /healthz`
|
||
# 依 nginx location 優先序勝出。
|
||
location = /healthz {
|
||
allow 127.0.0.0/8;
|
||
allow ::1/128;
|
||
deny all;
|
||
access_log off;
|
||
# 直接內部回 200,不轉到 api-server(淺層:只證明 nginx 活著)
|
||
return 200 "ok\n";
|
||
add_header Content-Type "text/plain" always;
|
||
}
|
||
|
||
# 其他任何 host header 不符合白名單 → 444 close(不回 response 給攻擊者反饋)
|
||
# 用 location / 包起來(而非 server-level return),才不會 shadow 掉上面的 /healthz。
|
||
location / {
|
||
return 444;
|
||
}
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name stage-9527.innovedus.com;
|
||
|
||
# ============================================================
|
||
# 全域行為
|
||
# ============================================================
|
||
|
||
# 模型上傳上限(PRD §8.4 — Phase 0 為 100 MB)。
|
||
# /api/models/* 走 multipart/form-data,這個值是上限。
|
||
client_max_body_size 100M;
|
||
|
||
# 大量 long-lived 連線(tunnel WS、SSE、模型轉檔輪詢)— 拉長 read timeout
|
||
proxy_read_timeout 3600s;
|
||
proxy_send_timeout 3600s;
|
||
|
||
# gzip — 對 JSON / JS / CSS 有效,避免重複壓縮二進位資源
|
||
gzip on;
|
||
gzip_min_length 1024;
|
||
gzip_types
|
||
text/plain
|
||
text/css
|
||
text/xml
|
||
text/javascript
|
||
application/json
|
||
application/javascript
|
||
application/xml
|
||
image/svg+xml;
|
||
|
||
# 安全 headers(nginx 已有公司 host 那層 HTTPS,這層補通用安全)
|
||
# X-Frame-Options 預設 SAMEORIGIN 給 iframe 防護(agent / pair view 不嵌 iframe)
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
# 注意:不在這層加 HSTS(HTTPS termination 在公司 host nginx,由那層加)
|
||
|
||
# ============================================================
|
||
# 健康檢查(淺層)— 不打到 backend
|
||
# 用途:「nginx 程序活著」的最廉價證明。docker healthcheck(走 default_server
|
||
# 那條)與「只想確認反代層在線」的外部探針用這條。
|
||
# ⚠️ 注意:這條「不」反映 DB / Redis 健康 —— load balancer 若要在 DB 掛掉時
|
||
# 把本實例踢出輪替,必須打下面的 /healthz/deep,不能打這條。
|
||
# ============================================================
|
||
location = /healthz {
|
||
access_log off;
|
||
return 200 "ok\n";
|
||
add_header Content-Type text/plain;
|
||
}
|
||
|
||
# ============================================================
|
||
# 健康檢查(深層)— proxy 到 backend /healthz,會 ping Postgres + Redis
|
||
# 用途:load balancer / 監控的 readiness 探針。
|
||
# - PG + Redis 都健康 → 200 {"status":"ok","checks":{"postgres":"ok","redis":"ok"}}
|
||
# - 任一依賴 ping 失敗 → 503 {"status":"unavailable","checks":{...:"down"}}
|
||
# 讓上游 LB 在 DB 掛掉時把本實例拉出輪替,而非繼續送流量進來碰 503 / 假資料。
|
||
#
|
||
# 設計取捨:
|
||
# - proxy_pass 改寫 path → 後端命中的是 /healthz(後端只實作這一個健康端點)。
|
||
# - timeout 全部壓短(2s):健康探針不該 hang;backend 自身 ping 逾時也是 2s,
|
||
# 這層再加一道 nginx 短逾時,避免單一探針卡住 worker。
|
||
# - access_log off:高頻探針不洗版 access log(與淺層一致)。
|
||
# - 不繼承 server-level 的 proxy_read_timeout 3600s(那是給長連線用的)。
|
||
# ============================================================
|
||
location = /healthz/deep {
|
||
access_log off;
|
||
proxy_pass http://visiona_api/healthz;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
# 健康探針要快、不該 hang —— 壓短所有 timeout(覆蓋 server-level 3600s)
|
||
proxy_connect_timeout 2s;
|
||
proxy_read_timeout 2s;
|
||
proxy_send_timeout 2s;
|
||
|
||
# 探針結果不可被任何中間層 cache
|
||
proxy_no_cache 1;
|
||
proxy_cache_bypass 1;
|
||
add_header Cache-Control "no-store" always;
|
||
}
|
||
|
||
# ============================================================
|
||
# /api/* → api-server :3721
|
||
# 包含 /api/auth/*(OIDC callback)、/api/devices、/api/models、/api/pairing 等
|
||
# ============================================================
|
||
location /api/ {
|
||
proxy_pass http://visiona_api;
|
||
proxy_http_version 1.1;
|
||
proxy_buffering off; # 模型上傳串流 / SSE friendly
|
||
|
||
# ── Proxy headers ──
|
||
# X-Forwarded-Proto = https:讓 backend 產的 redirect / cookie Secure 判斷正確
|
||
# (公司 host nginx 已 termination;container 收到的是 HTTP)
|
||
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 https;
|
||
proxy_set_header X-Forwarded-Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
# ── Cache 防護(M3) ──
|
||
# /api/auth/me 等含 PII;/api/auth/callback 帶 code/state
|
||
# 一律禁止任何中間 proxy / browser cache(含 BFCache)
|
||
proxy_no_cache 1;
|
||
proxy_cache_bypass 1;
|
||
add_header Cache-Control "no-store, no-cache, must-revalidate, private" always;
|
||
add_header Pragma "no-cache" always;
|
||
|
||
# nginx add_header 在 location level 會完全覆蓋 server level(不 merge)
|
||
# 因此 server level 的安全 header 必須在這裡 re-add
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# ============================================================
|
||
# /storage/* → api-server :3721
|
||
# presigned URL 走這條(HMAC 簽章 query string、不帶 cookie)
|
||
# 雛形 LocalFS backend 的下載端點
|
||
# ============================================================
|
||
location /storage/ {
|
||
proxy_pass http://visiona_api;
|
||
proxy_http_version 1.1;
|
||
proxy_buffering off;
|
||
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-Proto https;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header Connection "";
|
||
|
||
# ── Cache 防護(M3 — 保守作法) ──
|
||
# presigned URL 含短期 HMAC、理論上不可重複使用,但內容是用戶上傳模型(敏感)。
|
||
# 一律禁止中間 proxy 共用 cache,避免 query string 漏進 cache key 時被旁人取得。
|
||
# 若未來改 S3 backend + presigned 直連,這條 location 會被拆掉,屆時改 backend 自行決定 cache 策略。
|
||
proxy_no_cache 1;
|
||
proxy_cache_bypass 1;
|
||
add_header Cache-Control "private, no-store" always;
|
||
|
||
# nginx location level 的 add_header 會完全覆蓋 server level — re-add 安全 header
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# ============================================================
|
||
# /preset-models/* → api-server :3721 (B8 預設模型 .nef 公用下載)
|
||
#
|
||
# 7 個系統預設模型 .nef 由 api-server 直接讀 image 內 /app/assets/preset-models
|
||
# 串流(GET /preset-models/{id}.nef,不簽 token、preset 公用)。
|
||
#
|
||
# ⚠️ 為什麼必須有這條 location:
|
||
# 下方 catch-all `location /` 反代到 Next.js frontend (:3000)。若沒有這條,
|
||
# /preset-models/* 會落到 frontend → Next 沒有此 route → 404,preset 下載斷掉。
|
||
# 必須在 catch-all 之前用前綴 location 攔下來,導到 api-server。
|
||
#
|
||
# 比照 /storage/ pattern(同為 api-server 大檔下載端點):
|
||
# - proxy_buffering off:.nef 1-13MB 二進位,串流直送、不在 nginx 全 buffer 後才回
|
||
# - X-Forwarded-Proto https:backend 組對外 absolute URL 時判斷正確
|
||
# - server-level client_max_body_size 100M 是「上傳」上限,對下載無影響
|
||
# - cache:preset 公用、內容固定(baked 進 image,id 對應檔不變)→ 允許瀏覽器
|
||
# 快取 1 天減少重複下載大檔;非用戶私有資料、無 PII,不需 no-store。
|
||
# ============================================================
|
||
location /preset-models/ {
|
||
proxy_pass http://visiona_api;
|
||
proxy_http_version 1.1;
|
||
proxy_buffering off;
|
||
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-Proto https;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header Connection "";
|
||
|
||
# preset 公用、內容固定 → 可被瀏覽器快取(非敏感、非用戶私有)
|
||
expires 1d;
|
||
add_header Cache-Control "public, max-age=86400" always;
|
||
|
||
# nginx location level 的 add_header 會完全覆蓋 server level — re-add 安全 header
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# ============================================================
|
||
# /tunnel/connect — WebSocket upgrade,由 visionA Agent 連入
|
||
# 要 long-lived(24h 心跳級別);拉到 86400s。
|
||
# 注意:path 必須是 /tunnel/connect(不是 /tunnel/)— remote-proxy 只開這個 endpoint
|
||
# ============================================================
|
||
location /tunnel/connect {
|
||
proxy_pass http://visiona_tunnel;
|
||
proxy_http_version 1.1;
|
||
|
||
# WebSocket upgrade headers
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection $connection_upgrade;
|
||
|
||
# tunnel 連線可長達數小時,且心跳由 yamux 處理;nginx 不要中途斷
|
||
proxy_read_timeout 86400s;
|
||
proxy_send_timeout 86400s;
|
||
|
||
# 不 buffer,避免延遲 WS 訊框
|
||
proxy_buffering off;
|
||
|
||
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 https;
|
||
}
|
||
|
||
# ============================================================
|
||
# /ws/* → api-server :3721 (推論 inference WS + 既有 pairing/events/system WS)
|
||
#
|
||
# ⚠️ 為什麼必須有這條 location:
|
||
# 下方 catch-all `location /` 反代到 Next.js frontend (:3000)。若沒有這條,
|
||
# /ws/devices/:id/inference 等所有 /ws/* 會落到 frontend → Next 沒有此 route
|
||
# → 404,前端推論頁的 WS 握手斷掉。必須在 catch-all 之前用前綴 location 攔下,
|
||
# 導到 api-server (:3721),該處掛 wsAuthGroup(/ws/devices/:id/inference 需 auth)
|
||
# 與 registerWebSocketStubs(其餘 /ws/* 目前 501 stub)。
|
||
#
|
||
# 與 /tunnel/connect 的關係:兩者是不同 path 前綴(/ws/ vs /tunnel/connect),
|
||
# nginx 前綴 location 各自最長匹配、互不 shadow。/ws/ 不會吃到 /tunnel/connect。
|
||
#
|
||
# WS upgrade 三要素 + 長 timeout + buffering off,比照 /tunnel/connect。
|
||
# $connection_upgrade 繼承自 http-context map(檔頭 38-41 行)。
|
||
# cookie 由 proxy_pass 預設透傳(inference WS 走 cookie/session auth)。
|
||
# ============================================================
|
||
location /ws/ {
|
||
proxy_pass http://visiona_api;
|
||
proxy_http_version 1.1;
|
||
|
||
# WebSocket upgrade headers
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection $connection_upgrade;
|
||
|
||
# 推論 WS 為 long-lived 串流;拉長 timeout(比照 /tunnel/connect)
|
||
proxy_read_timeout 86400s;
|
||
proxy_send_timeout 86400s;
|
||
|
||
# 不 buffer,避免延遲 WS 訊框
|
||
proxy_buffering off;
|
||
|
||
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 https;
|
||
proxy_set_header X-Forwarded-Host $host;
|
||
}
|
||
|
||
# ============================================================
|
||
# Next.js hashed static assets — 永久 cache
|
||
# /_next/static/{hash}.js 等
|
||
# ============================================================
|
||
location /_next/static/ {
|
||
proxy_pass http://visiona_frontend;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
# Hash 帶在路徑裡,內容變了路徑就變 → 可以 immutable 1 年
|
||
expires 1y;
|
||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||
}
|
||
|
||
# ============================================================
|
||
# 其他靜態資源(public/ 下的圖片、字型等)— 1 day cache
|
||
# ============================================================
|
||
location ~* ^/(favicon\.ico|.*\.(?:png|jpg|jpeg|gif|svg|webp|ico|woff|woff2|ttf|eot))$ {
|
||
proxy_pass http://visiona_frontend;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
expires 1d;
|
||
add_header Cache-Control "public, max-age=86400" always;
|
||
}
|
||
|
||
# ============================================================
|
||
# 全部其他請求 → Next.js standalone server (:3000)
|
||
# 包含:
|
||
# - / (首頁)
|
||
# - /login, /register, /account, /clusters, /devices, /devices/[id],
|
||
# /devices/pair, /models, /models/[id], /workspace/[deviceId], /settings
|
||
# - /_next/data/* (RSC payload)
|
||
# - /_next/image (Next image optimizer,雖然 standalone 預設啟用 sharp)
|
||
# 不在這裡做 SPA fallback — Next.js server 自己會處理 404 與動態 route
|
||
# ============================================================
|
||
location / {
|
||
proxy_pass http://visiona_frontend;
|
||
proxy_http_version 1.1;
|
||
|
||
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 https;
|
||
proxy_set_header X-Forwarded-Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
# SSE / streaming 友善
|
||
proxy_buffering off;
|
||
proxy_cache off;
|
||
}
|
||
}
|
||
|
||
# ============================================================
|
||
# 內網 IP 直連 server block(demo 用)
|
||
# ------------------------------------------------------------
|
||
# 目的:讓 visionA Agent / 內網瀏覽器以 http://192.168.0.130:9527
|
||
# 直連 container(繞過公網邊界 proxy,那層剝 WS upgrade header、
|
||
# 碰不到 /tunnel/connect)。
|
||
#
|
||
# 為什麼安全(見 spec §4 完整評估):
|
||
# - 公網邊界 proxy 進來的 Host 一律被改寫成 stage-9527.innovedus.com
|
||
# → 永遠走上面那個 block,外部偽造 Host:192.168.0.130 也打不進這條。
|
||
# - 這條只有「能直接連到 130:9527 的內網來源」可命中。
|
||
# - default_server 仍是 server_name _ 那條,本 block 不加 default_server。
|
||
#
|
||
# DRY 取捨:務實複製 stage-9527 block 的 location。WS upgrade map
|
||
# $connection_upgrade 在 http context 定義(檔頭 38-41 行),本 block 直接繼承。
|
||
#
|
||
# X-Forwarded-Proto:實際是 http 進來,但 backend 不讀 XFP(已 code 確認),
|
||
# 設 http 或 https 都不影響。這裡設 http 以符合實際(避免 backend 未來
|
||
# 萬一改讀 XFP 時組出錯誤的 https absolute URL)。
|
||
#
|
||
# ⚠️ demo 專用,正式上線前應移除,或改用 allow <內網網段>; deny all; 限制來源。
|
||
# ============================================================
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name 192.168.0.130;
|
||
|
||
# 與 stage-9527 block 對齊的全域行為
|
||
client_max_body_size 100M;
|
||
proxy_read_timeout 3600s;
|
||
proxy_send_timeout 3600s;
|
||
|
||
gzip on;
|
||
gzip_min_length 1024;
|
||
gzip_types
|
||
text/plain text/css text/xml text/javascript
|
||
application/json application/javascript application/xml
|
||
image/svg+xml;
|
||
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
|
||
# ── 淺層健康檢查 ──
|
||
location = /healthz {
|
||
access_log off;
|
||
return 200 "ok\n";
|
||
add_header Content-Type text/plain;
|
||
}
|
||
|
||
# ── /api/* → api-server :3721(含 /api/pairing/exchange、models、devices)──
|
||
location /api/ {
|
||
proxy_pass http://visiona_api;
|
||
proxy_http_version 1.1;
|
||
proxy_buffering off;
|
||
|
||
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 http;
|
||
proxy_set_header X-Forwarded-Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
proxy_no_cache 1;
|
||
proxy_cache_bypass 1;
|
||
add_header Cache-Control "no-store, no-cache, must-revalidate, private" always;
|
||
add_header Pragma "no-cache" always;
|
||
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# ── /storage/* → api-server :3721(presigned 下載)──
|
||
location /storage/ {
|
||
proxy_pass http://visiona_api;
|
||
proxy_http_version 1.1;
|
||
proxy_buffering off;
|
||
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-Proto http;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header Connection "";
|
||
|
||
proxy_no_cache 1;
|
||
proxy_cache_bypass 1;
|
||
add_header Cache-Control "private, no-store" always;
|
||
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# ── /preset-models/* → api-server :3721(B8 預設模型 .nef)──
|
||
location /preset-models/ {
|
||
proxy_pass http://visiona_api;
|
||
proxy_http_version 1.1;
|
||
proxy_buffering off;
|
||
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-Proto http;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header Connection "";
|
||
|
||
expires 1d;
|
||
add_header Cache-Control "public, max-age=86400" always;
|
||
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# ── /tunnel/connect — WebSocket upgrade(最關鍵)──
|
||
# 完整複製 stage-9527 block 的 WS 設定:http/1.1 + Upgrade + Connection
|
||
# + 86400s timeout + buffering off。$connection_upgrade 繼承自 http-context map。
|
||
location /tunnel/connect {
|
||
proxy_pass http://visiona_tunnel;
|
||
proxy_http_version 1.1;
|
||
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection $connection_upgrade;
|
||
|
||
proxy_read_timeout 86400s;
|
||
proxy_send_timeout 86400s;
|
||
|
||
proxy_buffering off;
|
||
|
||
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 http;
|
||
}
|
||
|
||
# ── /ws/* → api-server :3721(推論 inference WS + 其餘 /ws/* stub)──
|
||
# 完整比照 stage-9527 block 的 /ws/ 設定:http/1.1 + Upgrade + Connection
|
||
# + 86400s timeout + buffering off。$connection_upgrade 繼承自 http-context map。
|
||
# 不會 shadow 上面的 /tunnel/connect(不同 path 前綴)。
|
||
location /ws/ {
|
||
proxy_pass http://visiona_api;
|
||
proxy_http_version 1.1;
|
||
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection $connection_upgrade;
|
||
|
||
proxy_read_timeout 86400s;
|
||
proxy_send_timeout 86400s;
|
||
|
||
proxy_buffering off;
|
||
|
||
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 http;
|
||
proxy_set_header X-Forwarded-Host $host;
|
||
}
|
||
|
||
# ── Next.js hashed static(內網瀏覽器用)──
|
||
location /_next/static/ {
|
||
proxy_pass http://visiona_frontend;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
expires 1y;
|
||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||
}
|
||
|
||
# ── 其他靜態資源 ──
|
||
location ~* ^/(favicon\.ico|.*\.(?:png|jpg|jpeg|gif|svg|webp|ico|woff|woff2|ttf|eot))$ {
|
||
proxy_pass http://visiona_frontend;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
expires 1d;
|
||
add_header Cache-Control "public, max-age=86400" always;
|
||
}
|
||
|
||
# ── catch-all → Next.js frontend :3000(內網瀏覽器用)──
|
||
location / {
|
||
proxy_pass http://visiona_frontend;
|
||
proxy_http_version 1.1;
|
||
|
||
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 http;
|
||
proxy_set_header X-Forwarded-Host $host;
|
||
proxy_set_header Connection "";
|
||
|
||
proxy_buffering off;
|
||
proxy_cache off;
|
||
}
|
||
}
|