完善企微开通与部署端口契约(07-26-internal-ops-recovery-baseline)

这个提交包含在:
2026-07-26 23:44:44 +08:00
父节点 9a624700d0
当前提交 130518f471
共修改 21 个文件,包含 1350 行新增和 55 行删除
+3
查看文件
@@ -23,6 +23,8 @@ APP_ORIGIN=https://oa.example.com
APP_TIMEZONE=Asia/Shanghai
HOST=0.0.0.0
PORT=3000
# 宿主机回环端口;容器内仍固定监听 PORT,Caddy 必须指向此端口。
APP_HOST_PORT=3000
TRUST_PROXY_HOPS=1
COOKIE_SECURE=true
OPENAPI_DOCS_ENABLED=false
@@ -76,6 +78,7 @@ WECOM_CORP_ID=
WECOM_AGENT_ID=
WECOM_AGENT_SECRET=
WECOM_OAUTH_ENABLED=false
WECOM_OAUTH_ONBOARDING_ENABLED=false
WECOM_REDIRECT_URI=https://oa.example.com/api/auth/wecom/callback
WECOM_ALLOWED_RETURN_HOSTS=oa.example.com
WECOM_CHECKIN_SYNC_ENABLED=false
+4 -4
查看文件
@@ -14,7 +14,7 @@
- [ ] 已确认 `deploy/trialrun/.env` 作为部署事实源,新增环境变量优先补到该文件
- [ ] 已尽量使用无引号 env 值;历史带引号值当前兼容,但建议逐步清理
- [ ] 已将 OA `Caddyfile` 站点块配置到 `/etc/caddy/Caddyfile`
- [ ] 已确认 Caddy 反代到 `127.0.0.1:3000`,并用 `header_up X-Forwarded-For {remote_host}` 覆盖客户端 XFF
- [ ] 已确认 Caddy 反代到 `.env` 中 `APP_HOST_PORT` 对应的宿主回环端口,并用 `header_up X-Forwarded-For {remote_host}` 覆盖客户端 XFF
- [ ] 已执行 `systemctl reload caddy`
- [ ] 已显式提供 `DEPLOY_SERVER`、`REMOTE_DIR`,并从本地执行 `bash scripts/qa/run-release-gate.sh all` 或至少执行 `bash deploy/trialrun/deploy.sh`
- [ ] 已通过 `https://<oa-domain>/ready` 或首页登录完成最小探活
@@ -22,7 +22,7 @@
- [ ] 若要导数据,仅准备最小主数据:部门、员工、角色、在用资产、活跃项目、合同元数据
- [ ] 不导入历史审批单、通知历史、审计日志、聊天记录和涉密正文附件
- [ ] 已确认远端部署目录不是 git 工作树,发布优先走 Git archive 模式的 `bash deploy/trialrun/deploy.sh`
- [ ] 已确认 OA 前端静态资源由 server 容器内 `/app/client/dist` 提供,Caddy 只反代到 `127.0.0.1:3000`
- [ ] 已确认 OA 前端静态资源由 server 容器内 `/app/client/dist` 提供,Caddy 只反代到 `127.0.0.1:<APP_HOST_PORT>`
## A. 发布前(Pre-flight)
- [ ] 确认当前版本已打 tag:`git tag -a trialrun-YYYYMMDD-NN -m "trialrun release"`
@@ -44,7 +44,7 @@
- [ ] 标准发布通过 `bash deploy/trialrun/deploy.sh` 执行;脚本自动注入完整后端 SHA 与 12 位前端 SHA,并完成构建、init 与 smoke
- [ ] 若为首次初始化或受控应急而直接使用 Compose,已显式设置 `BUILD_SHA=<approved-full-commit>` 与 `VITE_BUILD_SHA=<matching-12-char-commit>`;未使用 `unknown`
- [ ] 探活:
- [ ] `curl -fsS http://127.0.0.1:3000/ready`
- [ ] 已用 `docker-compose ... port server 3000` 解析实际宿主端口,并对该端口执行 `/ready`,未依赖当前 shell 中可能缺失的 `APP_HOST_PORT`
- [ ] Runtime smoke:
- [ ] `docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env exec -T server node src/scripts/smoke-trialrun-runtime.js`
- [ ] 若 runtime smoke 报 `announcements_v1` 或 `global_search_v1` 为 `force_off`,已记录为发布阻塞,不忽略通过
@@ -77,7 +77,7 @@
- [ ] 回到上个版本:`git checkout <previous-tag>`
- [ ] 重启并探活:
- [ ] `docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server`
- [ ] `curl -fsS http://127.0.0.1:3000/ready`
- [ ] 已重新用 `docker-compose ... port server 3000` 解析实际宿主端口并执行 `/ready`
- [ ] 若仅为 OCR 应急降级,已把远端 `.env` 的 `OCR_PROVIDER` 临时切回 `mock`,重启 server,并在 Issue/发布记录中写明原因、影响范围和恢复时间
+8 -2
查看文件
@@ -193,9 +193,15 @@ if ! "${compose_cmd[@]}" exec -T server sh -lc 'cd /app/server && npx prisma mig
fi
echo ">>> 4.7 等待 /ready 并核对构建标识"
server_port_output="$("${compose_cmd[@]}" port server 3000)"
server_host_port="${server_port_output##*:}"
if [[ ! "$server_host_port" =~ ^[0-9]+$ ]]; then
echo "[ERROR] 无法从 Compose 解析 server 宿主端口" >&2
exit 1
fi
ready_ok=0
for i in $(seq 1 60); do
if ready_payload="$(curl -fsS http://127.0.0.1:3000/ready 2>/dev/null)"; then
if ready_payload="$(curl -fsS "http://127.0.0.1:${server_host_port}/ready" 2>/dev/null)"; then
if READY_PAYLOAD="$ready_payload" EXPECTED_BUILD_SHA="${BUILD_SHA:0:12}" node -e '
const payload = JSON.parse(process.env.READY_PAYLOAD || "{}");
if (payload?.data?.buildSha !== process.env.EXPECTED_BUILD_SHA) process.exit(1);
@@ -263,4 +269,4 @@ echo ">>> 10. 部署完成"
echo " 来源提交: ${SOURCE_COMMIT}"
echo " 已自动执行:契约校验 + /ready + external deps smoke + runtime smoke + 公网前端资源文案扫描"
echo " 若本地已配置 LIVE_SMOKE_*,部署后会自动补跑网页 live smoke"
echo " 请确认 Caddy 或等效反向代理已把 OA 域名反代到 127.0.0.1:3000"
echo " 请确认 Caddy 或等效反向代理已把 OA 域名反代到 Compose 暴露的回环端口"
+1 -1
查看文件
@@ -69,7 +69,7 @@ services:
NODE_ENV: production
BUILD_SHA: ${BUILD_SHA:?set BUILD_SHA to the approved full commit}
ports:
- "127.0.0.1:3000:3000"
- "127.0.0.1:${APP_HOST_PORT:-3000}:3000"
volumes:
- ./data/storage:${STORAGE_DIR}
+14 -9
查看文件
@@ -6,7 +6,7 @@
- 官网继续保持 `hyinda.com / www.hyinda.com` 静态托管,不要改官网目录和配置。
- OA 新增独立二级域名,例如 `oa.hyinda.com`。
- OA 部署目录由受控环境中的 `REMOTE_DIR` 指定,不在仓库固化机器路径。
- OA 推荐访问方式:`Caddy(443) -> 127.0.0.1:3000 -> docker-compose(server)`。
- OA 推荐访问方式:`Caddy(443) -> 127.0.0.1:<APP_HOST_PORT> -> docker-compose(server:3000)`。
- Caddy 必须覆盖客户端传入的 `X-Forwarded-For`。OA 站点的 `reverse_proxy` 中应设置 `header_up X-Forwarded-For {remote_host}`,不得保留不可信客户端链。
- 首次初始化时:
1. 服务器以 root 执行 `bash deploy/trialrun/setup-server.sh`
@@ -16,7 +16,7 @@
## 0B. 历史部署基线(基础设施信息已脱敏)
- SSH 目标、部署账号和机器地址只保存在受控运维配置中,通过 `DEPLOY_SERVER` 显式传入。
- 反向代理:`caddy` 正在运行,`oa.hyinda.com` 已反代到 `127.0.0.1:3000`
- 反向代理:`caddy` 正在运行,`oa.hyinda.com` 应反代到 `.env` 中 `APP_HOST_PORT` 对应的宿主回环端口。
- 部署目录通过 `REMOTE_DIR` 显式传入。
- 远端目录当前不是 git 工作树;发布应优先从本地执行 `bash deploy/trialrun/deploy.sh`,由脚本 `rsync` 到服务器
- 远端容器编排当前实际为 `docker-compose 1.29.2`;本 runbook 命令以 `docker-compose` 为准
@@ -45,6 +45,7 @@ cp deploy/trialrun/.env.example deploy/trialrun/.env
- `deploy/trialrun/.env` 现在是 trialrun 部署事实源,`server / worker / init` 统一通过 `env_file` 注入。
- `BUILD_SHA` / `VITE_BUILD_SHA` 由发布入口按已批准提交临时注入,不写入长期 `.env`;前者供后端运行时核对,后者只允许 12 位短 SHA 进入公网 bundle。
- 推荐使用无引号值,例如 `COOKIE_SECURE=true`、`PORT=3000`。
- `PORT=3000` 是容器内端口;`APP_HOST_PORT` 是宿主回环端口。发布脚本通过 Compose 动态解析后者,Caddy 必须指向相同值。
- 历史上已经写成带引号的 `.env` 当前仍兼容,但建议后续逐步改成无引号。
- 正式部署前可先执行 `node deploy/trialrun/validate-env-contract.js` 做静态契约检查。
- **EXTERNAL_GATED**:涉及认证安全迁移时,发布或回滚前必须由操作人先在边缘暂停 `/api/auth/forgot-password`、`/api/auth/reset-password`、`/api/auth/email-login/code`、`/api/auth/email-login/verify` 四个 challenge 端点,再执行“新镜像配置预检 -> additive migration -> 新应用切换”。部署脚本不会自动修改边缘配置;未人工确认四端点已关闭不得发布或回滚开放。仅在安全版本及 smoke 全部通过后人工恢复;若回滚到旧认证代码,四端点必须继续关闭。
@@ -102,9 +103,11 @@ docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/
### 1.4 验收
```bash
curl -fsS http://127.0.0.1:3000/health
curl -fsS http://127.0.0.1:3000/ready
curl -fsS http://127.0.0.1:3000/metrics | head
SERVER_HOST_PORT="$(docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env port server 3000)"
SERVER_HOST_PORT="${SERVER_HOST_PORT##*:}"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/health"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/metrics" | head
docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env exec -T server node src/scripts/smoke-trialrun-runtime.js
```
@@ -119,7 +122,7 @@ docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/
- 2026-05-05 公网复核显示:`OCR_PROVIDER=tencent` 可用,真实发票上传后进入 `available`,provider 为 `tencent`;后续发布不能再把 OCR 回退到 mock 而不记录原因。
浏览器访问:
- `http://127.0.0.1:3000`(单域托管前端)
- `docker-compose ... port server 3000` 返回的宿主回环端口(单域托管前端)。
### 1.4.1 腾讯 OCR 试运行核查
远端 `.env` 是 OCR 配置事实源,禁止提交或复制密钥明文。只做以下脱敏检查:
@@ -273,13 +276,14 @@ docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/
```
### 2.2 前端部署一致性排障
当前 `oa.hyinda.com` 由 Caddy 反代到 `127.0.0.1:3000`,前端静态资源由 server 容器内的 `/app/client/dist` 提供,不是 `/etc/caddy/Caddyfile` 里的独立静态目录。
`oa.hyinda.com` 由 Caddy 反代到 `.env` 中 `APP_HOST_PORT` 对应的宿主回环端口,前端静态资源由 server 容器内的 `/app/client/dist` 提供,不是 `/etc/caddy/Caddyfile` 里的独立静态目录。
如果出现“本地源码已有功能,但公网仍显示旧 UI”,先按以下顺序核查:
```bash
# 1) 确认 Caddy 仍是反代到 3000
# 1) 确认 Caddy 指向 Compose 实际发布的宿主回环端口
sudo sed -n '/oa.hyinda.com/,/}/p' /etc/caddy/Caddyfile
docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env port server 3000
# 2) 比对公网首页引用的 asset hash
curl -fsS https://oa.hyinda.com/ | grep -o '/assets/[^" ]*' | head
@@ -295,7 +299,8 @@ docker exec hyb-oa-trialrun-server sh -lc \
cd /opt/hyb-oa-trialrun
docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server
docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env run --rm -T init </dev/null
curl -fsS http://127.0.0.1:3000/ready
SERVER_HOST_PORT="$(docker-compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env port server 3000)"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT##*:}/ready"
```
重建后必须补做:
+7 -1
查看文件
@@ -8,8 +8,14 @@ set -euo pipefail
DEPLOY_USER="${DEPLOY_USER:-deploy}"
REMOTE_DIR="${REMOTE_DIR:-/opt/hyb-oa-trialrun}"
OA_HOST="${OA_HOST:-oa.hyinda.com}"
APP_HOST_PORT="${APP_HOST_PORT:-3000}"
CADDY_CONFIG_PATH="${CADDY_CONFIG_PATH:-/etc/caddy/Caddyfile}"
if [[ ! "$APP_HOST_PORT" =~ ^[0-9]+$ ]] || (( APP_HOST_PORT < 1 || APP_HOST_PORT > 65535 )); then
echo "[ERROR] APP_HOST_PORT 必须是 1-65535 的端口号" >&2
exit 1
fi
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "[ERROR] 缺少命令: $1" >&2
@@ -63,7 +69,7 @@ mkdir -p "${REMOTE_DIR}/deploy/trialrun/data/minio"
chown -R "${DEPLOY_USER}:${DEPLOY_USER}" "${REMOTE_DIR}"
echo ">>> 4. 初始化完成,后续手工操作:"
echo " 1) 在 ${CADDY_CONFIG_PATH} 增加站点块:${OA_HOST} { reverse_proxy 127.0.0.1:3000 }"
echo " 1) 在 ${CADDY_CONFIG_PATH} 增加站点块:${OA_HOST} { reverse_proxy 127.0.0.1:${APP_HOST_PORT} }"
echo " 2) 执行 systemctl reload caddy"
echo " 3) 在 ${REMOTE_DIR}/deploy/trialrun/.env 写入 OA 环境变量"
echo " 4) 切换到 deploy 用户后,从本地执行 deploy/trialrun/deploy.sh"
+2 -2
查看文件
@@ -185,8 +185,8 @@ for (const serviceName of requiredServices) {
}
const serverBlock = extractServiceBlock(composeContent, 'server');
if (!serverBlock || !/^\s{6}-\s*["']127\.0\.0\.1:3000:3000["']\s*$/m.test(serverBlock)) {
errors.push('服务 server 的 3000 端口必须只绑定宿主回环地址');
if (!serverBlock || !/^\s{6}-\s*["']127\.0\.0\.1:\$\{APP_HOST_PORT:-3000\}:3000["']\s*$/m.test(serverBlock)) {
errors.push('服务 server 必须通过 APP_HOST_PORT 将容器 3000 端口只绑定到宿主回环地址');
}
for (const { filePath, content } of nginxContents) {