調整 Docker SSL 入口與部署說明
This commit is contained in:
@@ -6,3 +6,7 @@ DB_DATABASE=badminton
|
|||||||
DB_TABLE=badminton
|
DB_TABLE=badminton
|
||||||
DB_HISTORY_TABLE=history
|
DB_HISTORY_TABLE=history
|
||||||
SERVER_PORT=8788
|
SERVER_PORT=8788
|
||||||
|
NGINX_SERVER_NAME=_
|
||||||
|
SSL_CERT_FILE_NAME=RSA-cert.pem
|
||||||
|
SSL_CHAIN_FILE_NAME=RSA-chain.pem
|
||||||
|
SSL_KEY_FILE_NAME=RSA-privkey.pem
|
||||||
|
|||||||
102
README.md
102
README.md
@@ -12,16 +12,28 @@
|
|||||||
- 比賽結算後可選擇是否上傳戰績到 `history` 資料表
|
- 比賽結算後可選擇是否上傳戰績到 `history` 資料表
|
||||||
- 歷史戰績頁直接從 DB 顯示列表,點擊可查看得分過程
|
- 歷史戰績頁直接從 DB 顯示列表,點擊可查看得分過程
|
||||||
|
|
||||||
## 開發 Port
|
## Port 說明
|
||||||
|
|
||||||
|
### 本機開發
|
||||||
|
|
||||||
- Client: `3501`
|
- Client: `3501`
|
||||||
- Server API: `8788`
|
- Server API: `8788`
|
||||||
|
|
||||||
本機開發模式:
|
開發模式入口:
|
||||||
|
|
||||||
- 前端:`http://localhost:3501`
|
- 前端:`http://localhost:3501`
|
||||||
- API:`http://localhost:8788`
|
- API:`http://localhost:8788`
|
||||||
|
|
||||||
|
### Docker / NAS 部署
|
||||||
|
|
||||||
|
- 對外入口:`3501`
|
||||||
|
- 內部 Node app:`8788`
|
||||||
|
|
||||||
|
也就是說:
|
||||||
|
|
||||||
|
- Docker 部署後,使用者要連的是 `3501`
|
||||||
|
- `8788` 是容器內部 app port,給 Nginx 反向代理用
|
||||||
|
|
||||||
## 本機開發
|
## 本機開發
|
||||||
|
|
||||||
安裝套件:
|
安裝套件:
|
||||||
@@ -56,6 +68,10 @@ DB_DATABASE=badminton
|
|||||||
DB_TABLE=badminton
|
DB_TABLE=badminton
|
||||||
DB_HISTORY_TABLE=history
|
DB_HISTORY_TABLE=history
|
||||||
SERVER_PORT=8788
|
SERVER_PORT=8788
|
||||||
|
NGINX_SERVER_NAME=_
|
||||||
|
SSL_CERT_FILE_NAME=RSA-cert.pem
|
||||||
|
SSL_CHAIN_FILE_NAME=RSA-chain.pem
|
||||||
|
SSL_KEY_FILE_NAME=RSA-privkey.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
## 資料表
|
## 資料表
|
||||||
@@ -99,14 +115,12 @@ npm run build
|
|||||||
|
|
||||||
## Docker 單次啟動
|
## Docker 單次啟動
|
||||||
|
|
||||||
建置映像:
|
如果你只是要單獨跑 Node app,可用:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t badminton-scoreboard .
|
docker build -t badminton-scoreboard .
|
||||||
```
|
```
|
||||||
|
|
||||||
啟動容器:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name badminton-scoreboard \
|
--name badminton-scoreboard \
|
||||||
@@ -122,15 +136,56 @@ docker run -d \
|
|||||||
badminton-scoreboard
|
badminton-scoreboard
|
||||||
```
|
```
|
||||||
|
|
||||||
|
這種方式只會直接提供 Node app 本身,不含 Nginx SSL 入口。
|
||||||
|
|
||||||
## NAS 部署
|
## NAS 部署
|
||||||
|
|
||||||
這個專案現在已經補上 [docker-compose.yml](./docker-compose.yml),所以在 NAS 上可以直接使用:
|
這個專案已提供 [docker-compose.yml](./docker-compose.yml),會啟動兩個服務:
|
||||||
|
|
||||||
|
1. `badminton-scoreboard`
|
||||||
|
說明:Node app,內部使用 `8788`
|
||||||
|
|
||||||
|
2. `badminton-scoreboard-web`
|
||||||
|
說明:Nginx SSL 入口,對外使用 `3501`
|
||||||
|
|
||||||
|
在 NAS 專案目錄中,直接執行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo docker compose up -d --build
|
sudo docker compose up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
但前提是你要先在 NAS 的專案目錄準備好 `.env`,至少要有:
|
這樣就可以部署。
|
||||||
|
|
||||||
|
## SSL 憑證掛載
|
||||||
|
|
||||||
|
Nginx 會直接掛載這個 NAS 目錄:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/volume1/homes/JianMiau/www/certificate/
|
||||||
|
```
|
||||||
|
|
||||||
|
目前預設使用你現有的檔名:
|
||||||
|
|
||||||
|
- `RSA-cert.pem`
|
||||||
|
- `RSA-chain.pem`
|
||||||
|
- `RSA-privkey.pem`
|
||||||
|
|
||||||
|
Nginx 會在容器內自動組合:
|
||||||
|
|
||||||
|
- `RSA-cert.pem` + `RSA-chain.pem` => fullchain
|
||||||
|
- `RSA-privkey.pem` => private key
|
||||||
|
|
||||||
|
而且已經加上憑證檔變更監看,所以你之後只要更新:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/volume1/homes/JianMiau/www/certificate/
|
||||||
|
```
|
||||||
|
|
||||||
|
容器內的 Nginx 就會自動 reload,不需要重建 image。
|
||||||
|
|
||||||
|
## NAS `.env` 範例
|
||||||
|
|
||||||
|
部署前請先在 NAS 專案目錄準備 `.env`,至少要有:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
DB_HOST=192.168.0.15
|
DB_HOST=192.168.0.15
|
||||||
@@ -140,21 +195,36 @@ DB_PASSWORD=你的密碼
|
|||||||
DB_DATABASE=badminton
|
DB_DATABASE=badminton
|
||||||
DB_TABLE=badminton
|
DB_TABLE=badminton
|
||||||
DB_HISTORY_TABLE=history
|
DB_HISTORY_TABLE=history
|
||||||
|
NGINX_SERVER_NAME=你的網域
|
||||||
|
SSL_CERT_FILE_NAME=RSA-cert.pem
|
||||||
|
SSL_CHAIN_FILE_NAME=RSA-chain.pem
|
||||||
|
SSL_KEY_FILE_NAME=RSA-privkey.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
部署後會對外提供:
|
## NAS 對外入口
|
||||||
|
|
||||||
|
部署後請從這個入口使用:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
http://NAS_IP:8788
|
https://你的網域:3501
|
||||||
```
|
```
|
||||||
|
|
||||||
## NAS 部署注意事項
|
如果你的憑證是簽給特定網域,請不要用 IP 直接開,否則瀏覽器會跳憑證警告。
|
||||||
|
|
||||||
- 這個專案在正式部署時沒有獨立的 `3501` 前端埠,前端建置後由 Node server 一起從 `8788` 提供。
|
## 注意事項
|
||||||
- 如果 NAS 上已經有其他服務佔用 `8788`,要先改 `docker-compose.yml` 的左側對外埠。
|
|
||||||
- 指令要完整寫成 `sudo docker compose up -d --build`,不是 `--buil`。
|
|
||||||
- 第一次部署前,建議先確認 NAS 已安裝 Docker / Container Manager,且帳號可執行 `sudo docker compose`。
|
|
||||||
|
|
||||||
## Git 記錄
|
- `sudo docker compose up -d --build` 這條指令現在可以直接用,因為專案已經補上 compose 檔
|
||||||
|
- Docker/NAS 對外入口是 `3501`,不是 `8788`
|
||||||
|
- `8788` 是 Node app 內部服務埠,不是給使用者直接連的
|
||||||
|
- 如果 NAS 上 `3501` 已被其他服務使用,請改 `docker-compose.yml` 左側對外埠
|
||||||
|
- 若你的憑證檔名之後改了,只要更新 `.env` 對應的 `SSL_*_FILE_NAME` 即可
|
||||||
|
|
||||||
這個專案後續提交我會使用中文 commit 訊息,並已將本地 repo 的 git 中文編碼輸出設定好,方便直接看中文 log。
|
## Git 設定
|
||||||
|
|
||||||
|
這個 repo 已設定:
|
||||||
|
|
||||||
|
- `i18n.commitEncoding=utf-8`
|
||||||
|
- `i18n.logOutputEncoding=utf-8`
|
||||||
|
- `core.quotepath=false`
|
||||||
|
|
||||||
|
之後可直接使用中文 commit 訊息與中文 git log。
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: badminton-scoreboard:latest
|
image: badminton-scoreboard:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
expose:
|
||||||
- "8788:8788"
|
- "8788"
|
||||||
environment:
|
environment:
|
||||||
PORT: 8788
|
PORT: 8788
|
||||||
DB_HOST: ${DB_HOST:-192.168.0.15}
|
DB_HOST: ${DB_HOST:-192.168.0.15}
|
||||||
@@ -17,3 +17,26 @@ services:
|
|||||||
DB_DATABASE: ${DB_DATABASE:-badminton}
|
DB_DATABASE: ${DB_DATABASE:-badminton}
|
||||||
DB_TABLE: ${DB_TABLE:-badminton}
|
DB_TABLE: ${DB_TABLE:-badminton}
|
||||||
DB_HISTORY_TABLE: ${DB_HISTORY_TABLE:-history}
|
DB_HISTORY_TABLE: ${DB_HISTORY_TABLE:-history}
|
||||||
|
|
||||||
|
badminton-scoreboard-web:
|
||||||
|
container_name: badminton-scoreboard-web
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/nginx/Dockerfile
|
||||||
|
image: badminton-scoreboard-web:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- badminton-scoreboard
|
||||||
|
ports:
|
||||||
|
- "3501:3501"
|
||||||
|
environment:
|
||||||
|
NGINX_PORT: 3501
|
||||||
|
NGINX_SERVER_NAME: ${NGINX_SERVER_NAME:-_}
|
||||||
|
SSL_CERT_DIR: /etc/nginx/certs
|
||||||
|
SSL_CERT_FILE_NAME: ${SSL_CERT_FILE_NAME:-RSA-cert.pem}
|
||||||
|
SSL_CHAIN_FILE_NAME: ${SSL_CHAIN_FILE_NAME:-RSA-chain.pem}
|
||||||
|
SSL_KEY_FILE_NAME: ${SSL_KEY_FILE_NAME:-RSA-privkey.pem}
|
||||||
|
UPSTREAM_HOST: badminton-scoreboard
|
||||||
|
UPSTREAM_PORT: 8788
|
||||||
|
volumes:
|
||||||
|
- /volume1/homes/JianMiau/www/certificate:/etc/nginx/certs:ro
|
||||||
|
|||||||
9
docker/nginx/Dockerfile
Normal file
9
docker/nginx/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM nginx:1.27-alpine
|
||||||
|
|
||||||
|
RUN apk add --no-cache inotify-tools
|
||||||
|
|
||||||
|
COPY docker/nginx/entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
96
docker/nginx/entrypoint.sh
Normal file
96
docker/nginx/entrypoint.sh
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
NGINX_PORT="${NGINX_PORT:-3501}"
|
||||||
|
NGINX_SERVER_NAME="${NGINX_SERVER_NAME:-_}"
|
||||||
|
SSL_CERT_DIR="${SSL_CERT_DIR:-/etc/nginx/certs}"
|
||||||
|
SSL_CERT_FILE_NAME="${SSL_CERT_FILE_NAME:-RSA-cert.pem}"
|
||||||
|
SSL_CHAIN_FILE_NAME="${SSL_CHAIN_FILE_NAME:-RSA-chain.pem}"
|
||||||
|
SSL_KEY_FILE_NAME="${SSL_KEY_FILE_NAME:-RSA-privkey.pem}"
|
||||||
|
UPSTREAM_HOST="${UPSTREAM_HOST:-badminton-scoreboard}"
|
||||||
|
UPSTREAM_PORT="${UPSTREAM_PORT:-8788}"
|
||||||
|
|
||||||
|
GENERATED_DIR="/etc/nginx/generated"
|
||||||
|
GENERATED_CERT_PATH="${GENERATED_DIR}/fullchain.pem"
|
||||||
|
GENERATED_KEY_PATH="${GENERATED_DIR}/privkey.pem"
|
||||||
|
|
||||||
|
mkdir -p "${GENERATED_DIR}"
|
||||||
|
|
||||||
|
build_cert_bundle() {
|
||||||
|
cert_path="${SSL_CERT_DIR}/${SSL_CERT_FILE_NAME}"
|
||||||
|
chain_path="${SSL_CERT_DIR}/${SSL_CHAIN_FILE_NAME}"
|
||||||
|
key_path="${SSL_CERT_DIR}/${SSL_KEY_FILE_NAME}"
|
||||||
|
|
||||||
|
if [ ! -f "${cert_path}" ]; then
|
||||||
|
echo "Missing certificate file: ${cert_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "${chain_path}" ]; then
|
||||||
|
echo "Missing chain file: ${chain_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "${key_path}" ]; then
|
||||||
|
echo "Missing key file: ${key_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat "${cert_path}" "${chain_path}" > "${GENERATED_CERT_PATH}"
|
||||||
|
cp "${key_path}" "${GENERATED_KEY_PATH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_nginx_config() {
|
||||||
|
cat > /etc/nginx/conf.d/default.conf <<EOF
|
||||||
|
server {
|
||||||
|
listen ${NGINX_PORT} ssl;
|
||||||
|
server_name ${NGINX_SERVER_NAME};
|
||||||
|
|
||||||
|
ssl_certificate ${GENERATED_CERT_PATH};
|
||||||
|
ssl_certificate_key ${GENERATED_KEY_PATH};
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
client_max_body_size 20m;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://${UPSTREAM_HOST}:${UPSTREAM_PORT};
|
||||||
|
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 Upgrade \$http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
watch_cert_updates() {
|
||||||
|
while inotifywait -qq -e close_write,create,delete,move "${SSL_CERT_DIR}"; do
|
||||||
|
echo "Certificate files changed, reloading nginx..."
|
||||||
|
build_cert_bundle
|
||||||
|
nginx -s reload
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
build_cert_bundle
|
||||||
|
write_nginx_config
|
||||||
|
nginx -t
|
||||||
|
|
||||||
|
watch_cert_updates &
|
||||||
|
WATCHER_PID=$!
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
kill "${WATCHER_PID}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup INT TERM
|
||||||
|
|
||||||
|
nginx -g 'daemon off;' &
|
||||||
|
NGINX_PID=$!
|
||||||
|
|
||||||
|
wait "${NGINX_PID}"
|
||||||
Reference in New Issue
Block a user