diff --git a/docker/nginx.stage.conf b/docker/nginx.stage.conf index 0ea8cbc..d779457 100644 --- a/docker/nginx.stage.conf +++ b/docker/nginx.stage.conf @@ -346,3 +346,175 @@ server { 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; + } + + # ── 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; + } +}