新增 nginx 容器終結 TLS,讓播放器可用 HTTPS 連線

摘要:
比照 badminton-scoreboard 的做法,加一個 nginx 容器負責 SSL,
反向代理到原本的 Node 應用;Node 端維持純 HTTP,不碰憑證。

根本原因:
播放器原本只提供 HTTP。專案內原先想直接 symlink www/certificate 進來,
但那份憑證是 2026-04-16 從 DSM 複製出來的靜態副本,已於 2026-07-01 過期,
且 DSM 續期後不會自動更新副本。共用目錄 /volume1/docker/certs 才是
其他專案在用、且持續被續期的來源(目前效期至 2026-09-04)。

影響:
- 外網連線全程明文
- 若沿用 www/certificate,瀏覽器會直接因憑證過期而擋下連線

修法:
- 新增 docker/nginx/(Dockerfile + entrypoint.sh):啟動時把 cert.pem 與
  chain.pem 正規化並串成 fullchain,依環境變數產生 nginx 設定,
  再用 inotifywait 監看憑證目錄,續期後自動 reload,不需重啟容器
- docker-compose.yml 拆成 360-player(app)與 360-player-web(nginx)兩個服務,
  憑證以唯讀掛載 ${SSL_CERT_DIR:-/volume1/docker/certs}
- 保留 app 的 HTTP 埠:憑證綁網域,區網用 IP 連 HTTPS 必定跳警告,
  維持 區網走 http / 外網走 https 兩條路
- .env.example 補上 SSL_* 與 HTTPS_PORT 設定項

針對本專案調整(與 badminton-scoreboard 不同之處):
- proxy_buffering off + proxy_max_temp_file_size 0:影片走 HTTP Range 串流,
  開著緩衝 nginx 會把整段回應先寫成暫存檔,數 GB 來源會塞爆容器磁碟
- proxy_read_timeout/send_timeout 24h:/api/events 是 SSE,轉檔動輒數小時,
  預設 60 秒會被切斷導致進度停止更新
- 移除 websocket 的 Upgrade 標頭:本專案用 SSE,沒有 websocket

驗證:
以 entrypoint 相同的 awk 邏輯在本機組出 fullchain,openssl 解析得到完整三層
憑證鏈(jianmiau.tk → Let's Encrypt YR2 → ISRG Root YR),且 cert 與 privkey
的 modulus 相符。docker compose config 展開後的路徑與埠號皆正確。
註:無 docker daemon 權限,未實際 build 與啟動容器。

順帶修正 README:videoDir 範例改為 NAS 路徑、VAAPI 一節更新為已實機驗證。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 11:07:29 +08:00
co-authored by Claude Opus 5
parent 2a9e59357e
commit fa220b00eb
5 changed files with 221 additions and 11 deletions
+29
View File
@@ -7,7 +7,11 @@ services:
image: 360-player:latest
restart: unless-stopped
ports:
# 區網用 IP 直連的 HTTP(憑證是網域名,IP 連一定會跳警告,所以留一條明文)。
# 想只走 HTTPS 的話把這兩行刪掉,改用下面的 expose 就好。
- "${PORT:-8360}:8360"
expose:
- "8360"
volumes:
# 影片資料夾(NAS 上的實際路徑)→ 容器內 /videos,唯讀
- "${VIDEO_DIR:-/volume1/photo/Badminton}:/videos:ro"
@@ -21,3 +25,28 @@ services:
# 換到沒有 /dev/dri 的機器時要把下面兩行註解掉,否則容器起不來。
devices:
- /dev/dri:/dev/dri
360-player-web:
container_name: 360-player-web
build:
context: .
dockerfile: docker/nginx/Dockerfile
image: 360-player-web:latest
restart: unless-stopped
depends_on:
- 360-player
ports:
- "${HTTPS_PORT:-8443}:8443"
environment:
TZ: ${TZ:-Asia/Taipei}
NGINX_PORT: 8443
NGINX_SERVER_NAME: ${NGINX_SERVER_NAME:-_}
SSL_CERT_DIR: /etc/nginx/certs
SSL_CERT_FILE_NAME: ${SSL_CERT_FILE_NAME:-cert.pem}
SSL_CHAIN_FILE_NAME: ${SSL_CHAIN_FILE_NAME:-chain.pem}
SSL_KEY_FILE_NAME: ${SSL_KEY_FILE_NAME:-privkey.pem}
UPSTREAM_HOST: 360-player
UPSTREAM_PORT: 8360
volumes:
# 憑證來源與 badminton-scoreboard 共用;DSM 續期後 entrypoint 會自動 reload
- "${SSL_CERT_DIR:-/volume1/docker/certs}:/etc/nginx/certs:ro"