移除 nginx 容器,改由 Node 直接終結 TLS,合併成單一容器
摘要: 拿掉 360-player-web(nginx)服務與 docker/nginx/,改在 server.js 用 node:https 直接提供 HTTPS,部署從兩個容器變成一個。 根本原因: 先前為了 TLS 而多開一個 nginx 容器。分開的主因是「憑證續期時能 reload 而不重啟 app」——重啟會殺掉正在跑的 ffmpeg 轉檔,一部 4K 360 影片動輒數小時。 但 Node 的 https.Server 本來就有 setSecureContext(),可以熱換憑證不重啟行程, 這個理由不成立,多一個容器只是多一層維護成本。 影響: - 需維護額外的 nginx 映像檔與 entrypoint.sh - 影片經反向代理多一跳,且必須小心處理 proxy_buffering 與 SSE 逾時, 設錯會讓 Range 串流被寫進暫存檔、或讓轉檔進度停止更新 修法: - server.js 新增 TLS:讀取 SSL_CERT_DIR 的憑證,以 fs.watch 監看該資料夾, 檔案變動時 debounce 1 秒後呼叫 setSecureContext() 熱套用 - 中介憑證串進 cert 而非 ca:Node 只送出 cert 的內容,ca 是驗證對方用的 信任庫、不會送給瀏覽器,放錯會導致憑證鏈不完整 - 串接前正規化 PEM(去 CRLF、補結尾換行),沿用原 nginx entrypoint 的處理 - config.json 新增 httpsPort(預設 0 = 停用)與 certDir,本機開發不需憑證; 憑證讀不到時退回只提供 HTTP 並印警告,不讓服務起不來 - docker-compose.yml 併回單一服務,憑證改掛 /certs;Dockerfile 補上 HTTPS_PORT、SSL_CERT_DIR,EXPOSE 改為 8443 - 刪除 docker/nginx/ 驗證(實機執行,非僅靜態檢查): - 以 SSL_CERT_DIR=/volume1/docker/certs 啟動,log 顯示 「HTTPS:jianmiau.tk — 14 天後到期」,https 的 /api/config 回 200 - openssl s_client 確認送出完整三層憑證鏈 (jianmiau.tk → Let's Encrypt YR2 → ISRG Root YR) - HTTPS 上的 Range 請求正常:檔頭與中段各取一段皆回 206 且長度正確 - 熱換測試:換上 CN=hotswap-test.local 的自簽憑證後,log 出現 「憑證已重新載入」,s_client 讀到新 CN,且行程 PID 與啟動時間不變 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,14 +48,14 @@ npm start # 或 node server.js
|
||||
|
||||
## 部署到 NAS(Docker)
|
||||
|
||||
兩個容器:`360-player` 是應用本體(`node:22-alpine`,內含 ffmpeg),
|
||||
`360-player-web` 是 nginx,負責終結 TLS 再轉給前者。資料夾都用 volume 掛進容器:
|
||||
單一容器 `360-player`:以 `node:22-alpine` 為基礎,內含 ffmpeg,TLS 由 Node 自己終結。
|
||||
資料夾都用 volume 掛進容器:
|
||||
|
||||
| 容器 | 容器內路徑 | 用途 | 對應環境變數 |
|
||||
|---|---|---|---|
|
||||
| `360-player` | `/videos` | 影片資料夾(唯讀) | `VIDEO_DIR` |
|
||||
| `360-player` | `/cache` | 轉檔輸出與 probe 快取(需可寫) | `CACHE_DIR` |
|
||||
| `360-player-web` | `/etc/nginx/certs` | SSL 憑證(唯讀) | `SSL_CERT_DIR` |
|
||||
| 容器內路徑 | 用途 | 對應環境變數 |
|
||||
|---|---|---|
|
||||
| `/videos` | 影片資料夾(唯讀) | `VIDEO_DIR` |
|
||||
| `/cache` | 轉檔輸出與 probe 快取(需可寫) | `CACHE_DIR` |
|
||||
| `/certs` | SSL 憑證(唯讀) | `SSL_CERT_DIR` |
|
||||
|
||||
```bash
|
||||
# 1. 把整個專案資料夾複製到 NAS,例如 /volume1/docker/360_player
|
||||
@@ -71,9 +71,9 @@ Synology Container Manager:「專案」→「新增」→ 選這個資料夾
|
||||
|
||||
### SSL
|
||||
|
||||
TLS 由 `360-player-web`(nginx)終結,Node 那端維持純 HTTP,所以應用程式碼裡沒有任何憑證邏輯。
|
||||
憑證放在 `SSL_CERT_DIR`(預設 `/volume1/docker/certs`,和其他專案共用同一份),
|
||||
唯讀掛進 nginx 容器:
|
||||
TLS 由 Node 直接處理(`server.js`),沒有額外的反向代理容器。
|
||||
憑證放在 `SSL_CERT_DIR`(NAS 上預設 `/volume1/docker/certs`,和其他專案共用同一份),
|
||||
唯讀掛進容器的 `/certs`:
|
||||
|
||||
```
|
||||
cert.pem 伺服器憑證
|
||||
@@ -81,21 +81,27 @@ chain.pem 中介憑證
|
||||
privkey.pem 私鑰
|
||||
```
|
||||
|
||||
檔名可用 `SSL_CERT_FILE_NAME` / `SSL_CHAIN_FILE_NAME` / `SSL_KEY_FILE_NAME` 改。
|
||||
容器啟動時 entrypoint 會把 `cert.pem` + `chain.pem` 接成 fullchain(順便去掉 CRLF),
|
||||
並用 `inotifywait` 盯著這個資料夾 —— **DSM 續期後直接覆蓋檔案就好,nginx 會自己 reload,不用重開容器**。
|
||||
啟動 log 會印出憑證的 subject 和到期日。
|
||||
檔名可用 `SSL_CERT_FILE_NAME` / `SSL_CHAIN_FILE_NAME` / `SSL_KEY_FILE_NAME` 改
|
||||
(DSM 匯出的是 `RSA-cert.pem` 這種名字)。啟動 log 會印出憑證的網域與剩餘天數。
|
||||
|
||||
兩點注意:
|
||||
**續期不用重啟。** `server.js` 用 `fs.watch` 盯著憑證資料夾,檔案一被覆蓋就呼叫
|
||||
`server.setSecureContext()` 熱套用新憑證 —— 這點很重要,因為重啟會殺掉正在跑的
|
||||
ffmpeg 轉檔,而一部 4K 360 影片動輒轉好幾小時。
|
||||
|
||||
- **對外只有 8443 一個入口**:app 容器只 `expose` 不 `ports`,明文那條不會離開容器內網。
|
||||
代價是憑證綁網域,區網也得用網域連(`https://jianmiau.tk:8443`)而不是 IP —— 用 IP 連
|
||||
會跳憑證警告。真的想要區網免警告的快速通道,把 app 的 `ports` 加回來並綁死區網介面
|
||||
(`"192.168.0.15:8360:8360"`),不要開成 `0.0.0.0`,否則等於在外網開了一條明文路徑。
|
||||
- nginx 這邊關掉了 `proxy_buffering` 並把 `proxy_max_temp_file_size` 設為 0。
|
||||
影片是靠 HTTP Range 串流的,開著緩衝會讓 nginx 先把整段回應寫成暫存檔再吐出去,
|
||||
幾 GB 的來源會直接塞爆容器磁碟。`/api/events`(SSE)另外把 `proxy_read_timeout`
|
||||
拉到 24 小時,否則轉檔跑到一半進度就不再更新。
|
||||
兩個實作細節:
|
||||
|
||||
- 中介憑證要放進 `cert` 而不是 `ca`。Node 只會把 `cert` 裡的內容送給瀏覽器,
|
||||
`ca` 是用來驗證對方憑證的信任庫、不會送出;放錯位置會變成不完整的憑證鏈,
|
||||
部分客戶端(尤其 Android)會驗證失敗。程式裡是把 cert 和 chain 串成 fullchain。
|
||||
- 串接前會正規化 PEM(去掉 CRLF、補上結尾換行),否則兩個檔黏成一行會解析不出來。
|
||||
|
||||
`httpsPort` 設 0(`config.json` 的預設)就完全不啟用 HTTPS,本機 `npm start`
|
||||
開發時不必準備憑證。憑證讀不到時會印警告並退回只提供 HTTP,不會讓服務起不來。
|
||||
|
||||
**對外只有 HTTPS 一個入口**:容器內的 8360(HTTP)不對外開,只給 HEALTHCHECK 用。
|
||||
代價是憑證綁網域,區網也得用網域連(`https://jianmiau.tk:8443`)而不是 IP —— 用 IP 連
|
||||
會跳憑證警告。真的想要區網免警告的快速通道,在 compose 的 `ports` 補一條並綁死區網介面
|
||||
(`"192.168.0.15:8360:8360"`),不要開成 `0.0.0.0`,否則等於在外網開了一條明文路徑。
|
||||
|
||||
**NAS 上轉檔速度**
|
||||
- 沒有 GPU 時走 `libx264`(CPU)。NAS 的 CPU 把 4K 360 影片同時轉成三種畫質大約只有個位數 fps,
|
||||
@@ -148,11 +154,10 @@ privkey.pem 私鑰
|
||||
## 專案結構
|
||||
|
||||
```
|
||||
server.js Express:清單、Range 串流、轉檔 API、SSE
|
||||
server.js Express:清單、Range 串流、轉檔 API、SSE、TLS(憑證熱換)
|
||||
lib/probe.js ffprobe 包裝 + 永續快取(含 360 metadata 判斷)
|
||||
lib/transcode.js ffmpeg 工作佇列(一次解碼多輸出;CUDA → NVENC → CPU 備援)
|
||||
lib/spherical.js 把 Spherical Video V1 metadata 注回 MP4
|
||||
public/ 前端(Three.js 球體貼圖播放器)
|
||||
docker/nginx/ nginx 映像檔:終結 TLS,反向代理到 app(憑證變更自動 reload)
|
||||
cache/ 轉檔輸出與 probe 快取(已 gitignore)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user