From 9cfd515b8f33af6cb3d1a092232858d102c36f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=80=BB=E5=B7=A5?= Date: Sun, 30 Aug 2026 22:49:35 +0800 Subject: [PATCH] =?UTF-8?q?HEL-271:=20compose=20=E5=A4=9A=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E9=9A=94=E7=A6=BB=E5=8F=82=E6=95=B0=E5=8C=96=20+=20?= =?UTF-8?q?=E6=AD=A3=E5=BC=8F=20HTTPS=20=E5=8F=8D=E4=BB=A3=EF=BC=88nginx/t?= =?UTF-8?q?ls=20profile=EF=BC=89=E4=B8=8E=E8=AF=81=E4=B9=A6=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: multica-agent --- .gitignore | 6 +++++ deploy/.env.example | 13 ++++++++++ deploy/README.md | 49 ++++++++++++++++++++++++++++++++++++++ deploy/compose.yaml | 54 ++++++++++++++++++++++++++++++++++++++++-- deploy/tls/gen-cert.sh | 45 +++++++++++++++++++++++++++++++++++ deploy/tls/nginx.conf | 31 ++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 deploy/tls/gen-cert.sh create mode 100644 deploy/tls/nginx.conf diff --git a/.gitignore b/.gitignore index 1ec9146..c641248 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,9 @@ package-lock.json # local vendor for agent test env (not shipped) .vendor/ vendor_wheels/ + +# 正式环境部署产物(证书私钥、备份) +deploy/tls/certs/ +backups/ +ALERT.log +backup.log diff --git a/deploy/.env.example b/deploy/.env.example index ebf972a..8091236 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -16,3 +16,16 @@ APP_BOOTSTRAP_ADMIN_PASSWORD=change-me-in-local-env # 内网测试可暂时关闭登录失败锁定;正式环境不得开启 # APP_LOGIN_RATE_LIMIT_DISABLED=1 + +# ---- 同宿主机第二套环境(正式)必须设置的隔离参数 ---- +# compose 项目名(默认 deploy;正式环境改为 caiwuzongzhang-prod) +# COMPOSE_PROJECT_NAME=caiwuzongzhang-prod +# 应用容器名与宿主机端口映射(正式默认只绑本机回环,由反代对外) +# APP_CONTAINER_NAME=caiwuzongzhang-prod-app +# APP_PORT_MAP=127.0.0.1:4174:4173 + +# ---- 正式 HTTPS 反代(COMPOSE_PROFILES=tls 启用 proxy 服务)---- +# COMPOSE_PROFILES=tls +# PROXY_CONTAINER_NAME=caiwuzongzhang-proxy +# PROXY_PORT_MAP=8443:8443 +# 首次启用前先执行 ./tls/gen-cert.sh 生成证书 diff --git a/deploy/README.md b/deploy/README.md index b62e726..2563e79 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -46,6 +46,45 @@ docker exec caiwuzongzhang-app python -c "import sqlite3; s=sqlite3.connect('/ap docker cp caiwuzongzhang-app:/tmp/b.db ../backups/app.db.$STAMP ``` +## 3.1 正式环境部署(同宿主机隔离,2026-08-30 首次上线采用) + +测试与正式同宿主机并行,靠 compose 项目名/容器名/端口三隔离: + +| 项 | 测试环境 | 正式环境 | +|---|---|---| +| 目录 | `/home/leefer/caiwuzongzhang-test` | `/home/leefer/caiwuzongzhang-prod` | +| compose 项目 | `deploy`(默认) | `COMPOSE_PROJECT_NAME=caiwuzongzhang-prod` | +| 应用容器 | `caiwuzongzhang-app` | `caiwuzongzhang-prod-app` | +| 端口 | `4173`(对外) | `127.0.0.1:4174`(仅本机,反代上游) | +| HTTPS | 无 | nginx 反代 `8443`(`COMPOSE_PROFILES=tls`) | +| 访问 | http://192.168.200.36:4173/ | https://192.168.200.36:8443/ | + +首次部署顺序: + +```bash +git clone /home/leefer/caiwuzongzhang-prod && cd /home/leefer/caiwuzongzhang-prod +git checkout release/prod +mkdir -p data/files backups && chown -R 10001:10001 data # 容器内 app uid +cd deploy +cp .env.example .env # 按下述差异填写 +./tls/gen-cert.sh # 生成内部 CA + 服务器证书(SAN 含对外 IP) +TAG=<发布镜像tag> docker compose up -d # 不带 --build,直接用已验收镜像 +docker logs <应用容器> 2>&1 | grep "shown once" # 首启一次性初始口令,只出现一次 +``` + +正式 `.env` 与测试的差异(铁律): + +- `COMPOSE_PROJECT_NAME=caiwuzongzhang-prod`、`APP_CONTAINER_NAME=caiwuzongzhang-prod-app`、`APP_PORT_MAP=127.0.0.1:4174:4173` +- `COMPOSE_PROFILES=tls`、`PROXY_CONTAINER_NAME=caiwuzongzhang-proxy`、`PROXY_PORT_MAP=8443:8443` +- `APP_UID=10001` / `APP_GID=10001`(data/ 已 chown 10001) +- 管理员用户名自定(如 `jinniu-admin`);**不设** `APP_BOOTSTRAP_ADMIN_PASSWORD`(首启控制台取随机一次性口令,首登立即改密) +- **不设** `APP_LOGIN_RATE_LIMIT_DISABLED`(限流默认开启) + +证书与信任分发: + +- `tls/certs/ca.crt` 发给各公司电脑安装到「受信任的根证书颁发机构」(安装指引见交接文档);`ca.key`/`server.key` 永不离开宿主机。 +- 服务器证书有效期 5 年,到期前用 `./tls/gen-cert.sh` 重签并 `docker compose restart proxy`。 + ## 4. 回滚(应用层,5 分钟内) ```bash @@ -68,6 +107,16 @@ cd deploy && ./rollback.sh <上一个镜像tag> cron `*/5 * * * *` 运行 `deploy/monitor.sh`;连续失败或磁盘 ≥80% 时查看 `deploy/ALERT.log` 并按需扩容/清理。容器重启策略 `unless-stopped`。 +宿主机 crontab(leefer,2026-08-30 起,正式环境): + +```cron +15 2 * * * cd /home/leefer/caiwuzongzhang-prod/deploy && CONTAINER=caiwuzongzhang-prod-app ./backup.sh >> backup.log 2>&1 +*/5 * * * * cd /home/leefer/caiwuzongzhang-prod/deploy && CONTAINER=caiwuzongzhang-prod-app HEALTH_URL=http://127.0.0.1:4174/ ./monitor.sh >/dev/null 2>&1 +01 3 1 * * rsync -a /home/leefer/caiwuzongzhang-prod/backups/monthly/ /vol1/caiwuzongzhang-backups/monthly/ +``` + +(第三行为月度异盘副本:`/vol1` 是与系统盘不同的物理卷;如后续提供真正的异机目标,改为该目标。) + ## 7. 升级数据库 应用启动时自动执行迁移(`MIGRATIONS`,当前版本 10)。迁移只前不改写历史;回退 schema 用对应 down 迁移,先备份后操作。 diff --git a/deploy/compose.yaml b/deploy/compose.yaml index 78ca61c..c181e4e 100644 --- a/deploy/compose.yaml +++ b/deploy/compose.yaml @@ -1,10 +1,16 @@ # 测试/正式环境部署编排(正式口令不得写入本文件)。 # 使用:复制 .env.example 为 .env 填写;TAG 指定镜像标签(默认取当前提交 sha)。 # 例:TAG=v1.0.0-rc1 docker compose -f compose.yaml up -d --build +# +# 同宿主机多套环境(测试 + 正式)必须各自设置: +# COMPOSE_PROJECT_NAME / APP_CONTAINER_NAME / APP_PORT_MAP 互不相同。 +# 正式环境启用 HTTPS 反代:.env 中设 COMPOSE_PROFILES=tls(先跑 tls/gen-cert.sh)。 + +name: ${COMPOSE_PROJECT_NAME:-deploy} services: app: - container_name: caiwuzongzhang-app + container_name: ${APP_CONTAINER_NAME:-caiwuzongzhang-app} image: caiwuzongzhang:${TAG:-latest} build: context: .. @@ -15,7 +21,7 @@ services: # APP_UID/APP_GID,正式与测试环境各自填写,默认镜像内 app=10001) user: "${APP_UID:-10001}:${APP_GID:-10001}" ports: - - "4173:4173" + - "${APP_PORT_MAP:-4173:4173}" volumes: - ../data:/app/data init: true @@ -30,9 +36,53 @@ services: pids_limit: 128 mem_limit: 512m cpus: 1.0 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:4173/', timeout=2)"] interval: 15s timeout: 3s retries: 5 start_period: 10s + + # 可选 HTTPS 反向代理(正式环境用):COMPOSE_PROFILES=tls 启用。 + # 证书由 tls/gen-cert.sh 生成的内部 CA 签发(内网自受管信任),公司电脑 + # 安装 tls/certs/ca.crt 一次即可无告警访问。 + proxy: + container_name: ${PROXY_CONTAINER_NAME:-caiwuzongzhang-proxy} + image: nginx:1.27-alpine + profiles: ["tls"] + ports: + - "${PROXY_PORT_MAP:-8443:8443}" + volumes: + - ./tls/nginx.conf:/etc/nginx/conf.d/default.conf:ro + - ./tls/certs:/etc/nginx/tls:ro + depends_on: + - app + restart: unless-stopped + read_only: true + user: "101:101" + tmpfs: + - /var/cache/nginx:size=16m + - /var/run:size=1m + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + pids_limit: 64 + mem_limit: 64m + cpus: 0.5 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + healthcheck: + test: ["CMD", "wget", "-q", "--no-check-certificate", "-O", "/dev/null", "https://127.0.0.1:8443/"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s diff --git a/deploy/tls/gen-cert.sh b/deploy/tls/gen-cert.sh new file mode 100644 index 0000000..04c6792 --- /dev/null +++ b/deploy/tls/gen-cert.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# 生成内网专用 CA 与服务器证书(自受管内部信任,不涉及公网域名)。 +# 用法:在正式环境 deploy/ 目录执行 ./tls/gen-cert.sh +# 可用环境变量覆盖: +# CERT_IP 对外访问 IP(默认 192.168.200.36) +# CERT_DNS 额外 DNS 名称(空格分隔,默认 caiwuzongzhang.jinniu.internal) +# 输出:tls/certs/{ca.crt,ca.key,server.crt,server.key}(key 永不入库、不分发) +# 分发:仅把 ca.crt 发给公司电脑安装(受信任的根证书颁发机构)。 +set -euo pipefail +ROOT="$(cd "$(dirname "$0")" && pwd)" +CERTS="$ROOT/certs" +mkdir -p "$CERTS" +cd "$CERTS" + +CERT_IP="${CERT_IP:-192.168.200.36}" +CERT_DNS="${CERT_DNS:-caiwuzongzhang.jinniu.internal}" + +SAN="IP:${CERT_IP}" +for d in $CERT_DNS; do SAN="$SAN,DNS:$d"; done + +if [[ ! -f ca.key ]]; then + # 内部根 CA:10 年,仅本系统使用 + openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \ + -keyout ca.key -out ca.crt -days 3650 -nodes -subj "/CN=Jinniu Ledger Internal CA" \ + -addext "basicConstraints=critical,CA:TRUE" \ + -addext "keyUsage=critical,keyCertSign,cRLSign" + chmod 600 ca.key + echo "ca 已生成" +fi + +openssl req -newkey ec -pkeyopt ec_paramgen_curve:P-256 \ + -keyout server.key -out server.csr -nodes \ + -subj "/CN=${CERT_DNS%% *}" >/dev/null 2>&1 +cat > server.ext </dev/null 2>&1 +chmod 600 server.key +rm -f server.csr server.ext ca.srl +echo "server 证书已生成(SAN: ${SAN}),有效期 5 年" +echo "分发文件:${CERTS}/ca.crt(其余文件不得离开本机)" diff --git a/deploy/tls/nginx.conf b/deploy/tls/nginx.conf new file mode 100644 index 0000000..d94c662 --- /dev/null +++ b/deploy/tls/nginx.conf @@ -0,0 +1,31 @@ +# 正式环境 HTTPS 反代(nginx)。证书挂载自 deploy/tls/certs/(gen-cert.sh 生成)。 +# 上游为同 compose 网络内的 app:4173;对外仅暴露本代理的 8443。 +server { + listen 8443 ssl; + listen [::]:8443 ssl; + http2 on; + server_name _; + + ssl_certificate /etc/nginx/tls/server.crt; + ssl_certificate_key /etc/nginx/tls/server.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:2m; + ssl_session_timeout 1h; + + # 银行流水 Excel 上传上限 + client_max_body_size 25m; + + proxy_http_version 1.1; + proxy_read_timeout 120s; + proxy_send_timeout 120s; + + location / { + proxy_pass http://app:4173; + 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; + } +}