docs(08-08-production-deployment-validation): 补齐部署验证规范与证据边界
这个提交包含在:
@@ -69,9 +69,13 @@
|
||||
|
||||
## B. 发布(Deploy)
|
||||
- [ ] 本地工作树 clean,目标 commit 已进入 upstream;部署日志中的 commit 与 `/ready.data.buildSha` 一致
|
||||
- [ ] **生产迁移先行门禁**:仅通过进程环境显式提供 `EXPECTED_PRODUCTION_ORIGIN`、`EXPECTED_PRODUCTION_DB_NAME`、`EXPECTED_PENDING_MIGRATIONS`;未把目标值写入仓库、日志或报告
|
||||
- [ ] **生产 target gate**:已确认 HTTPS origin 与数据库身份均为预期生产目标,明确排除回环、本地回归库、staging/trialrun;不得套用本地任务“必须是回环地址”的判据
|
||||
- [ ] **pre-status 在写入前**:运行时 `DATABASE_URL` host/DB 身份先通过;`prisma migrate status` 原始输出仅留受控现场、脱敏副本已保存并人工阅读;候选 manifest 与 `_prisma_migrations` 对账通过,pending 与批准 allowlist 精确一致,status 报出的 pending ID 均属于候选 manifest;failed/rolled-back/diverged/未知项为 0
|
||||
- [ ] pre-status、target gate 或对账失败时保持 PAUSED,未进入 `deploy.sh`;未执行 `migrate dev`、reset、db push、resolve、手工 DDL、seed 或业务写入
|
||||
- [ ] **EXTERNAL_GATED**:操作人已在边缘暂停 `/api/auth/forgot-password`、`/api/auth/reset-password`、`/api/auth/email-login/code`、`/api/auth/email-login/verify`;部署脚本不会自动修改边缘配置,未人工确认四端点关闭不得发布,配置预检、迁移、新应用和 smoke 全部通过后才人工恢复
|
||||
- [ ] 标准路径已成功刷新 upstream;若因受控离线恢复使用 unpublished override,日志包含批准原因码和 `UNPUBLISHED-OVERRIDE` 标识
|
||||
- [ ] 已有部署通过 `DEPLOY_SERVER=<deploy-ssh-target> DEPLOY_REFRESH_SERVER=<forced-command-ssh-target> bash deploy/trialrun/deploy-existing.sh` 执行;首次初始化才使用完整显式参数的 `deploy.sh`
|
||||
- [ ] 已有部署通过 `deploy/trialrun/deploy-existing.sh` 执行;生产迁移已按 §2.0 提供三个 `EXPECTED_*` 进程变量;首次初始化才使用完整显式参数的 `deploy.sh`
|
||||
- [ ] forced-command 使用独立专用 key,同一 key 不存在不受限 root 授权;root-owned target 只接受精确 `probe/refresh`,不调用部署用户可写仓库脚本
|
||||
- [ ] 13 项聚合体检在任何 mkdir/rsync 前全部执行并汇总;报告只有固定检查名与 PASS/FAIL,任一失败均阻断同步
|
||||
- [ ] 未从 Compose `working_dir` 猜仓库根目录;同步前 preflight 已确认仓库根标识、普通远端 `.env`、PG_VERSION 与附件目录,且该检查发生在 rsync 之前
|
||||
@@ -97,6 +101,9 @@
|
||||
- [ ] 登录验证已由 live smoke 与 release gate 覆盖;仅首次接入或第三方后台配置变更时补做人工确认
|
||||
|
||||
## C. 发布后(Post-check)
|
||||
- [ ] wrapper 已在合同 refresh 前以空 pending allowlist 复跑只读 preflight,并保存/人工阅读部署后的第二份 `prisma migrate status` 脱敏副本(原始输出仅留受控现场);候选 migration 显示 applied/schema up to date,未只看退出码
|
||||
- [ ] 权限负向路径、合同详情只读检查和 `/ready`/smoke 均在任何真实 `terminated` 写入前完成;本次没有主动终止写入
|
||||
- [ ] 已记录 `rollbackWindow=OPEN|CLOSED`;首条已提交 `terminated` 记录产生时关闭,关闭后只允许 roll-forward/兼容 hotfix,不自动 drop 三列
|
||||
- [ ] 公网 live smoke(只读,不做写操作):`bash scripts/qa/run-web-gates.sh smoke-live`
|
||||
- [ ] 若本地已配置 `LIVE_SMOKE_*`,确认 `bash deploy/trialrun/deploy.sh` 输出中包含 live smoke 结果
|
||||
- [ ] 已确认 release gate / live smoke 结果为通过;关键链路不再作为每次发布的手工必做项
|
||||
|
||||
+47
-2
@@ -71,7 +71,7 @@ curl -fsS <shadow-ready-url>
|
||||
- 反向代理:`caddy` 正在运行,`oa.hyinda.com` 应反代到 `.env` 中 `APP_HOST_PORT` 对应的宿主回环端口。
|
||||
- 部署目录通过 `REMOTE_DIR` 显式传入。
|
||||
- 远端目录当前不是 git 工作树;发布应优先从本地执行 `bash deploy/trialrun/deploy.sh`,由脚本 `rsync` 到服务器
|
||||
- 已有部署优先执行 `DEPLOY_SERVER=<deploy-ssh-target> DEPLOY_REFRESH_SERVER=<forced-command-ssh-target> bash deploy/trialrun/deploy-existing.sh`;包装脚本只从受控 automatic-backup systemd unit 读取 allowlist 参数且不回显路径。两个 SSH target 是独立身份:前者按部署用户执行同步/Compose,后者只能执行 root-owned forced-command 的 `probe/refresh`。不得把 Compose `com.docker.compose.project.working_dir` 当仓库根目录,该标签可能指向 `deploy/trialrun` 配置目录。
|
||||
- 已有部署优先执行 `deploy/trialrun/deploy-existing.sh`;涉及生产迁移时按 §2.0 额外提供三个 `EXPECTED_*` 进程变量。包装脚本只从受控 automatic-backup systemd unit 读取 allowlist 参数且不回显路径。两个 SSH target 是独立身份:前者按部署用户执行同步/Compose,后者只能执行 root-owned forced-command 的 `probe/refresh`。不得把 Compose `com.docker.compose.project.working_dir` 当仓库根目录,该标签可能指向 `deploy/trialrun` 配置目录。
|
||||
- 远端容器编排当前实际为 `docker-compose 1.29.2`;本 runbook 命令以 `docker-compose` 为准
|
||||
- 所有 Compose 命令必须显式使用同一个 `COMPOSE_PROJECT_NAME` 和 `-p`;不得依赖目录名推导项目身份,也不得写死容器名。
|
||||
- 当前公网 `.env` 已确认:`APP_ORIGIN=https://oa.hyinda.com`、`COOKIE_SECURE=true`、`PUBLIC_SECURITY_HARDENING_ENABLED=true`
|
||||
@@ -411,6 +411,51 @@ systemctl status hyb-oa-public-app-health.service --no-pager
|
||||
建议版本化方式:
|
||||
- 每次发布打 tag,例如 `trialrun-2026-02-08-01`
|
||||
|
||||
### 2.0 生产迁移先行门禁(08-08)
|
||||
|
||||
涉及生产 schema 迁移的既有部署,必须先使用 `deploy-existing.sh` 的只读
|
||||
production preflight,再进入 `deploy.sh`。执行时由受控进程环境显式提供:
|
||||
|
||||
```bash
|
||||
EXPECTED_PRODUCTION_ORIGIN=<approved-https-origin> \
|
||||
EXPECTED_PRODUCTION_DB_NAME=<approved-production-db-name> \
|
||||
EXPECTED_PENDING_MIGRATIONS=<approved-comma-separated-migration-ids-or-empty> \
|
||||
DEPLOY_SERVER=<deploy-ssh-target> \
|
||||
DEPLOY_REFRESH_SERVER=<forced-command-ssh-target> \
|
||||
bash deploy/trialrun/deploy-existing.sh
|
||||
```
|
||||
|
||||
该生产验证 wrapper 明确拒绝 `DEPLOY_ALLOW_UNPUBLISHED_COMMIT=1` 及其原因码;离线
|
||||
灾备或远端故障恢复若需未发布提交,必须退出本门禁并走另行批准的恢复流程。
|
||||
|
||||
这里的生产目标判据与本地迁移检查相反:必须确认目标是预期生产 origin 和
|
||||
数据库身份,且不是回环、本地回归库、staging 或 trialrun 身份。目标值只留在
|
||||
进程环境和受控执行现场,不写入仓库、任务记录或输出;脚本只记录稳定的
|
||||
PASS/FAIL code。
|
||||
|
||||
门禁顺序固定为:候选 commit/13 项既有部署体检 → target identity(含运行时
|
||||
`DATABASE_URL` host/DB 校验)→
|
||||
`prisma migrate status` 原始输出(只在受控现场保留、人工阅读其脱敏副本)→ `_prisma_migrations`
|
||||
与候选 manifest 对账 → `terminated` 行计数 → `deploy.sh`。pending 集合必须
|
||||
与 `EXPECTED_PENDING_MIGRATIONS` 精确一致;failed、rolled-back、diverged、
|
||||
未知 migration 或无法解释的 status 立即 PAUSED。preflight 不执行
|
||||
`migrate dev`、reset、db push、resolve、seed、手工 DDL 或业务写入。
|
||||
|
||||
从 preflight 到 `deploy.sh` 的执行窗口必须有远端 `.env`、Compose project 和数据库
|
||||
身份变更冻结;无法保证冻结时应在写入前 PAUSED,不能把部署后的 status 当作并发
|
||||
改目标的补救。
|
||||
|
||||
部署过程中 wrapper 以 `DEPLOY_STATUS_OUTPUT_MODE=quiet` 调用既有 `deploy.sh`,不把
|
||||
历史 status 命令的 datasource 原文打到终端;部署完成后 wrapper 会在合同 refresh 前复用只读 preflight,并以空 pending allowlist
|
||||
生成独立的 post artifact;仍须人工核对第二份 `prisma migrate status` 脱敏证据副本,确认
|
||||
候选 migration 已 applied/schema up to date;原始输出只留在受控现场,退出码不能替代原始输出。权限负向
|
||||
路径、合同详情只读检查和 `/ready`/smoke 必须排在任何真实终止写入之前。
|
||||
|
||||
回滚窗口按状态而非时间定义:部署前 `terminated` 行数为 0 时记为 `OPEN`;
|
||||
第一条已提交 `terminated` 记录产生的瞬间关闭窗口。窗口关闭后不得回退到不认识
|
||||
该状态的旧应用,也不自动 drop 三列,只能 roll-forward 或兼容 hotfix。本任务
|
||||
不主动制造终止记录。
|
||||
|
||||
### 2.1 网页自动化门禁(R28)
|
||||
发布前后统一使用三层网页自动化:
|
||||
- `bash scripts/qa/run-web-gates.sh smoke-local`:本地/PR 快速门禁,只覆盖登录、Portal、导航、企微入口 UI。
|
||||
@@ -424,7 +469,7 @@ systemctl status hyb-oa-public-app-health.service --no-pager
|
||||
|
||||
步骤:
|
||||
1) 先做备份(见 E14 脚本)
|
||||
2) 已有部署从本地仓库执行 `DEPLOY_SERVER=<deploy-ssh-target> DEPLOY_REFRESH_SERVER=<forced-command-ssh-target> bash deploy/trialrun/deploy-existing.sh`(推荐);它从受控 systemd unit 读取既有 `REMOTE_DIR`、env、Compose project 与数据目录合同,不从 Docker 标签猜路径,也不回显参数值。首次初始化或 unit 不存在时,才使用完整显式参数执行 `deploy.sh`。
|
||||
2) 已有部署从本地仓库执行 `deploy/trialrun/deploy-existing.sh`(推荐;生产迁移按 §2.0 提供 target/pending 进程变量);它从受控 systemd unit 读取既有 `REMOTE_DIR`、env、Compose project 与数据目录合同,不从 Docker 标签猜路径,也不回显参数值。首次初始化或 unit 不存在时,才使用完整显式参数执行 `deploy.sh`。
|
||||
- 正式使用新版入口前,root 在单独获批窗口执行 `DEPLOY_REFRESH_PUBLIC_KEY_FILE=<dedicated-public-key-file> bash deploy/trialrun/install-deployment-contract-refresh.sh install`。专用 key 不得同时存在不受限 root 授权;installer 会安装 root-owned 自包含 target 和 forced-command 行,但 installer 本身不进入免密授权。
|
||||
- 新版入口先运行 `check-existing-deployment.sh` 的 13 项只读聚合体检。报告只含固定项名与 PASS/FAIL;任何失败都在 mkdir/rsync 前阻断,且前项失败不会跳过后续独立检查。
|
||||
- `DEPLOY_REFRESH_SERVER` 必须是只携带专用 forced-command key 的 SSH alias。不得用 `sudo -n true` 代替该身份检查,也不得复用仍具有不受限 root 登录能力的同一 key。
|
||||
|
||||
在新工单中引用
屏蔽一个用户