chore(部署): 发布前拦截会覆盖线上已发布提交的版本

今天连续发生多次热修复/旧版本发布把别人刚上线的改动冲掉(种菜损耗运费口径被撤回两次)。
发布脚本读取线上 .deployed-sha 后:本地没有该提交先 git fetch 再判断;线上提交不是本次发布版本的祖先时直接拒绝(服务器不做任何变更);
新增 -AllowRollback / --allow-rollback 仅供负责人决定撤回时使用。AGENTS.md 第 8 节补充热修复须基于线上版本并立即合入 main。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VANWYsNUbu2piMtCKymoyd
This commit is contained in:
limeng 2026-10-19 18:00:07 +08:00
parent 17429d65ab
commit e4d82dab2a
3 changed files with 42 additions and 1 deletions

View File

@ -64,6 +64,7 @@
## 8. 构建与生产部署
- 发布脚本会拒绝覆盖“不在本次版本里”的线上提交(防止互相冲掉已上线的改动)。热修复必须基于线上当前版本构建,发布后立即合入 `main`;遇到拦截先 `git fetch` 并合入最新 `main` 或线上热修复分支,不得随手加 `-AllowRollback`(`--allow-rollback`),撤回线上发布需负责人明确决定。
- 普通代码发布由任务 Codex 在完成验证与合并后直接执行,不要求用户再次说“部署、上线”;用户明确要求仅本地开发、仅审查或暂不上线时遵循其范围。正式发布只使用已合并的最新 `main`。
- Windows 开发机统一使用 `scripts/deploy-from-workstation.ps1`,macOS 开发机使用 `bash scripts/deploy-from-mac.sh`;两者流程一致(本机构建前端与镜像、迁移拦截、增量推送服务器本机 Registry、激活与健康检查、双版本保留)。服务器只运行开发机已构建的 `caiwu_akaunting:<commit>` 镜像,禁止在服务器执行 Composer、npm 或 Docker 构建。
- 新人只需表达部署需求;Codex 使用默认生产入口运行脚本。首次通过本机窗口提供一次服务器密码后,加密保存在本人电脑供后续自动使用,不要求新人准备私钥、执行命令或每次重输密码。已有私钥认证仍兼容。

View File

@ -17,6 +17,7 @@
# --configure-dingtalk-callback
# --allow-non-main 允许非 main 分支(测试用)
# --allow-migrations 允许执行待办数据库迁移(需先按 AGENTS.md 核对)
# --allow-rollback 允许覆盖不在本次版本里的线上提交(仅负责人决定撤回时使用)
# --skip-regctl 跳过 regctl,直接用 docker 直传镜像
# --keep-images <数量> 默认 2
# --app-version <版本> 默认 3.1.15
@ -45,6 +46,7 @@ bootstrap_server=0
configure_dingtalk_callback=0
allow_non_main=0
allow_migrations=0
allow_rollback=0
skip_regctl=0
app_version='3.1.15'
# 国内网络访问 GitHub 受限时,composer 的 GitHub dist 会失败;默认走腾讯云镜像。
@ -71,6 +73,7 @@ while [ "$#" -gt 0 ]; do
--configure-dingtalk-callback) configure_dingtalk_callback=1 ;;
--allow-non-main) allow_non_main=1 ;;
--allow-migrations) allow_migrations=1 ;;
--allow-rollback) allow_rollback=1 ;;
--skip-regctl) skip_regctl=1 ;;
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
*) echo "未知参数:$1" >&2; exit 2 ;;
@ -502,11 +505,27 @@ done
previous_sha="$(ssh "${ssh_options[@]}" "$ssh_target" "cat '$remote_path/.deployed-sha' 2>/dev/null || true" | tr -d '[:space:]')"
if [ -n "$previous_sha" ]; then
if git cat-file -e "${previous_sha}^{commit}" 2>/dev/null; then
previous_sha_exists=1
else
# 线上可能是别人刚发的热修复分支,先拉取再判断。
git fetch origin --quiet 2>/dev/null || true
if git cat-file -e "${previous_sha}^{commit}" 2>/dev/null; then
previous_sha_exists=1
else
previous_sha_exists=0
fi
fi
# 防覆盖:线上当前版本必须包含在本次要发布的版本里,否则会撤掉别人已上线的改动。
if [ "$allow_rollback" -eq 0 ]; then
if [ "$previous_sha_exists" -eq 0 ]; then
fail "线上当前版本 ${previous_sha} 在本地和 origin 都找不到(可能是未推送的分支),发布会把它覆盖。请让发布人先推送并合并进 main;确需撤回时显式添加 --allow-rollback。"
fi
if ! git merge-base --is-ancestor "$previous_sha" "$deploy_sha"; then
fail "线上当前版本($(git log -1 --format='%h %an %ad %s' --date=format:'%m-%d %H:%M' "$previous_sha"))不在本次发布的版本里,继续发布会把它及之后别人上线的改动撤掉。请先 git fetch 并把最新 main(或线上热修复分支)合入当前分支后重试;确需撤回时显式添加 --allow-rollback。"
fi
fi
if [ "$previous_sha_exists" -eq 0 ] && [ "$allow_migrations" -eq 0 ]; then
fail "本地缺少服务器当前提交 ${previous_sha},无法安全比较数据库迁移。请先 git fetch 并核对全部待执行迁移,按 AGENTS.md 完成备份后使用 --allow-migrations。"

View File

@ -22,6 +22,8 @@ param(
[switch]$ConfigureDingTalkCallback,
[switch]$AllowNonMain,
[switch]$AllowMigrations,
# 仅在负责人明确决定撤回线上某次发布时使用;默认禁止覆盖不在本次版本里的线上提交。
[switch]$AllowRollback,
[switch]$SkipRegctl
)
@ -426,6 +428,25 @@ try {
$previousShaExists = $LASTEXITCODE -eq 0
}
if ($previousSha -and -not $previousShaExists) {
# 线上可能是别人刚发的热修复分支,先拉取再判断,避免误判。
git fetch origin --quiet 2>$null
git cat-file -e "${previousSha}^{commit}" 2>$null
$previousShaExists = $LASTEXITCODE -eq 0
}
# 防覆盖:线上当前版本必须包含在本次要发布的版本里,否则发布会把别人已上线的改动撤掉。
if ($previousSha -and -not $AllowRollback) {
if (-not $previousShaExists) {
throw "线上当前版本 $previousSha 在本地和 origin 都找不到(可能是未推送的分支),发布会把它覆盖。请让发布人先推送并合并进 main;确需撤回时显式添加 -AllowRollback。服务器未发生任何变更。"
}
git merge-base --is-ancestor $previousSha $deploySha 2>$null
if ($LASTEXITCODE -ne 0) {
$previousInfo = (git log -1 --format='%h %an %ad %s' --date=format:'%m-%d %H:%M' $previousSha) -join ''
throw "线上当前版本($previousInfo)不在本次发布的版本里,继续发布会把它及之后别人上线的改动撤掉。请先 git fetch 并把最新 main(或线上热修复分支)合入当前分支后重试;确需撤回时显式添加 -AllowRollback。服务器未发生任何变更。"
}
}
if ($previousSha -and -not $previousShaExists -and -not $AllowMigrations) {
throw "本地缺少服务器当前提交 $previousSha,无法安全比较数据库迁移。请先 git fetch 并核对全部待执行迁移,按 AGENTS.md 完成备份后使用 -AllowMigrations。"
}