补强生产数据路径与备份来源护栏(07-26-internal-ops-recovery-baseline)
ci-e2e / e2e (push) Waiting to run
ci-minimal / check-minimal (push) Waiting to run

这个提交包含在:
2026-07-27 06:37:47 +08:00
父节点 23342035ee
当前提交 64ed844bfe
共修改 14 个文件,包含 256 行新增和 34 行删除
+25 -6
查看文件
@@ -12,7 +12,7 @@
1. 服务器以 root 执行 `bash deploy/trialrun/setup-server.sh`
2. 手工创建 `deploy/trialrun/.env`
3. 手工更新 OA 的 `Caddyfile` 站点块并确认 TLS 正常
4. 本地通过受控配置显式提供 `DEPLOY_SERVER`、`REMOTE_DIR`,再执行 `bash scripts/qa/run-release-gate.sh all`(或至少执行 `bash deploy/trialrun/deploy.sh`)
4. 本地通过受控配置显式提供 `DEPLOY_SERVER`、`REMOTE_DIR`、绝对数据目录和 `DEPLOY_DATA_MODE`,再执行 `bash scripts/qa/run-release-gate.sh all`(或至少执行 `bash deploy/trialrun/deploy.sh`)
## 0B. 历史部署基线(基础设施信息已脱敏)
- SSH 目标、部署账号和机器地址只保存在受控运维配置中,通过 `DEPLOY_SERVER` 显式传入。
@@ -47,7 +47,8 @@ cp deploy/trialrun/.env.example deploy/trialrun/.env
- `BUILD_SHA` / `VITE_BUILD_SHA` 由发布入口按已批准提交临时注入,不写入长期 `.env`;前者供后端运行时核对,后者只允许 12 位短 SHA 进入公网 bundle。
- 推荐使用无引号值,例如 `COOKIE_SECURE=true`、`PORT=3000`。
- `PORT=3000` 是容器内端口;`APP_HOST_PORT` 是宿主回环端口。发布脚本通过 Compose 动态解析后者,Caddy 必须指向相同值。
- `POSTGRES_DATA_DIR` 和 `STORAGE_HOST_DIR` 是宿主数据事实源;默认相对 `deploy/trialrun/`。生产环境更换项目名或任一数据目录只能在同一次获批停机窗口完成,禁止两个 PostgreSQL 实例同时挂载同一 PGDATA。
- `POSTGRES_DATA_DIR` 和 `STORAGE_HOST_DIR` 是宿主数据事实源。示例相对值仅供本地直接 Compose;正式 `deploy.sh` 要求在命令环境中显式提供两个绝对路径和 `DEPLOY_DATA_MODE=existing|initialize`。
- `existing` 要求 PGDATA 已存在且包含 `PG_VERSION`,附件目录也必须已存在;`initialize` 才会创建目录。生产环境更换项目名或任一数据目录只能在同一次获批停机窗口完成,禁止两个 PostgreSQL 实例同时挂载同一 PGDATA。
- 历史上已经写成带引号的 `.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 全部通过后人工恢复;若回滚到旧认证代码,四端点必须继续关闭。
@@ -183,15 +184,19 @@ set +a
mapfile -t DB_CONTAINERS < <(docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env ps -q db)
test "${#DB_CONTAINERS[@]}" -eq 1
DB_CONTAINER="${DB_CONTAINERS[0]}"
SOURCE_IMAGE_DIGEST="$(docker inspect --format '{{.Image}}' <verified-active-server-container>)"
DB_TOOL_MODE=docker \
DB_CONTAINER="$DB_CONTAINER" \
STORAGE_DIR_HOST="<verified-active-storage-host-directory>" \
SOURCE_IMAGE_DIGEST="$SOURCE_IMAGE_DIGEST" \
./scripts/backup-all.sh
```
`backup-all.sh` 可使用宿主 Node.js 或 Python 3 生成同结构 manifest;Docker-only 主机没有 Node.js 时会自动使用 Python 3,两者都没有则失败且不发布完整批次。`DB_CONTAINER` 和 `STORAGE_DIR_HOST` 均无隐式默认值:前者必须由已核验 Compose 项目的 `ps -q db` 解析,后者必须与活动 server 的附件挂载源一致。真实运行环境执行前,不得只相信可能漂移的 `.env`:先脱敏确认所选 PostgreSQL 容器的数据库/用户与至少一个活动应用连接一致,再把已验证的运行参数传给脚本;不在日志中输出参数值。
manifest 必须至少记录一个真实来源标识:活动 `/ready` 有构建标识时显式传 `BUILD_SHA`;没有时,从活动 server 容器 `docker inspect --format '{{.Image}}'` 取得 `sha256:` 镜像 ID并传 `SOURCE_IMAGE_DIGEST`。不得用待部署提交填充旧实例的 `buildSha`。
输出默认在 `deploy/trialrun/backups/hyb-oa-backup-<run-id>/`,其中包含:
- `database.dump`
@@ -233,11 +238,24 @@ RESTORE_SCOPE=shadow \
`restore-db.sh` 和 `restore-storage.sh` 默认只接受 `shadow`。影子库名必须包含独立的 `shadow`、`drill` 或 `restore_test` 标记;数据库确认不匹配、库名不安全、附件目录非空、目标为默认试运行附件目录或归档路径危险时均在写入前退出。
### 1.5.3 真实事故恢复
真实格式影子恢复的验收不仅是 `/ready`:恢复后的 `_prisma_migrations` 成功项必须等于冻结基线(当前预期 58),预先列出的关键业务表计数必须逐表一致,附件解包后的普通文件数必须与活动源目录一致。任一项不一致即判定批次不可作为切换回滚点。
### 1.5.3 生产切换前冻结记录
执行正确备份和影子恢复前,先用只读命令记录:
- `docker compose ls` 实测的旧 Compose project 名;不得按目录名推断,后续停止旧栈必须使用该实测值。
- 活动 server/db 容器 ID、活动镜像 ID(`sha256:`)及可用的 repo digest;回滚至少保留不可变镜像 ID。
- 活动 DB/附件真实宿主挂载源、Caddy 目标端口和环境文件摘要,不输出环境变量正文。
- 成功 migration 数量和固定关键表计数基线。至少覆盖组织人员、角色、采购、报销、发票池、支付批次、合同、项目和附件关联表。
活动数据复制策略在停机窗口前冻结:旧 PostgreSQL 完全停止后,把 PGDATA 和附件复制到预先确认的空绝对目录,并保留所有权、权限、链接和扩展属性;旧目录留在原位且观察期内不再挂载写入。不得移动或改名后只留下单份已迁移数据,也不得复制运行中的 PGDATA。
### 1.5.4 真实事故恢复
真实库/附件恢复不是普通脚本路径。必须另建或更新获批 Trellis task,记录停机窗口、恢复批次、实际目标、两名负责人、当前备份、回滚点和验证清单。当前恢复脚本不会接受 `production` 模式,不得通过修改环境变量或脚本临时绕过。恢复后必须验证 `/ready`、运行 commit、核心权限和 DB/附件关联完整性。
### 1.5.4 小团队上线前演练记录
### 1.5.5 小团队上线前演练记录
上线前至少记录一次演练结果:
- 演练日期:
@@ -275,8 +293,9 @@ RESTORE_SCOPE=shadow \
步骤:
1) 先做备份(见 E14 脚本)
2) 从本地仓库执行 `DEPLOY_SERVER=<ssh-target> REMOTE_DIR=<remote-dir> COMPOSE_PROJECT_NAME=<approved-compose-project> bash deploy/trialrun/deploy.sh`(推荐);脚本只同步已验证提交的 Git archive,不依赖远端 `git pull`,也不会把本地 ignored 文件带入发布。
- 在现网目录统一任务中,新的 `COMPOSE_PROJECT_NAME`、`POSTGRES_DATA_DIR`、`STORAGE_HOST_DIR` 必须与活动数据目录迁移在同一停机窗口启用;本地提交本身不授权切换。
2) 从本地仓库执行 `DEPLOY_SERVER=<ssh-target> REMOTE_DIR=<remote-dir> COMPOSE_PROJECT_NAME=<approved-compose-project> POSTGRES_DATA_DIR=<absolute-pgdata> STORAGE_HOST_DIR=<absolute-storage> DEPLOY_DATA_MODE=existing bash deploy/trialrun/deploy.sh`(推荐);脚本只同步已验证提交的 Git archive,不依赖远端 `git pull`,也不会把本地 ignored 文件带入发布。
- 在现网目录统一任务中,新的 `COMPOSE_PROJECT_NAME`、`POSTGRES_DATA_DIR`、`STORAGE_HOST_DIR` 必须与活动数据目录复制在同一停机窗口启用;本地提交本身不授权切换。
- 旧栈停止命令必须使用冻结阶段由 `docker compose ls` 实测的旧 project;不得拿新 project 名去停止旧容器。
- `artifacts/` 和 `output/` 是运行期证据/截图目录,不作为源码发布输入,也不由 `rsync --delete` 清理远端历史证据。
3) 构建并重启:
```bash