Files
cma-management/deploy/gitea-cma-deploy-hook.sh
T
wecom-fullstack adfc3ac845
ci/woodpecker/push/main Pipeline was successful
fix(deploy): 部署源改用已提交 origin/main 树(独立检出),根治「开发区任何 modified 即中止」
单 project-20260921-deploy-dirtyguard-rootfix(任总 2026-09-21 10:28「根治」)。

问题:开发区 /root/cma-management → /srv/hermes/shared/cma-management 既是编辑区又是部署源,
deploy.sh [0] 对任何 tracked modified 即 exit 1,多 agent 并行下反复自锁:
- 2026-09-18 frontend/package.json(corepack 生成物)
- 2026-09-21 10:09 batch5 在途 6 文件(放行卡住)
- 2026-09-21 11:07 构建又写出 frontend/components.d.ts

改动:
- deploy.sh:部署内容 = 已提交的 origin/main(独立部署检出 /srv/hermes/shared/cma-deploy:
  fetch -> checkout --detach -f -> clean -fdx);[0] 降级为告警(不再 exit);
  新增 1.1 部署树自检(部署树必须无 tracked 修改)、1.2 运行时资产单源接线(.env/venv/node_modules)、
  1d 后端可导入自检、运行树幂等切换(unit 改写前备份 + 一行回滚,cma-runtime-unit-switch 可抽取块);
  保留 [1b] 鉴权面审计闸门(B 类缺口即中止)与 构建->铺站->重启->冒烟 全流程;
  新增 drill 模式(离线演练:www 必须在 /var/www 之外、systemctl/nginx 必须是 PATH 桩、迁移只 --check)。
- deploy/cma-deploy-dirty-guard.sh:宿主前置闸门降级为告警(仅仓库不可用时失败关闭),不再写开发区。
- deploy/gitea-cma-deploy-hook.sh:[0] 段文案/语义同步(gate-trail 留痕链原样保留)。
- deploy/install-host-deploy-hook.sh:宿主组件安装/升级(备份 + md5 校验 + --check)。

演练证据(26/26 PASS):脏区旧版 [0] exit=1 / 新版整链 exit=0;开发区在途改动逐字节保留;
构造 B 类缺口时 [1b] 中止且未铺站;真机 drill 零生产影响。

未 push(发布须任总放行)。
2026-09-21 11:13:30 +08:00

106 lines
5.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Gitea post-receive 部署触发器(由 /root/.ssh/authorized_keys 的 forced command 调用)
# 落地:ops-20260918-gitea-deploy-hook-ssh-fix
# 容器侧私钥只能触发本脚本(不能执行任意 root 命令),且来源 IP 限定在 gitea 容器网段。
# HOME 必须显式为 /root:profile HOME 下 corepack 缓存缺 bin/pnpm.cjs → pnpm build 报 MODULE_NOT_FOUND。
# 2026-09-18(0-P1-project-20260918-deploy-dirty-guard):deploy.sh 之前增加「提交纪律」前置闸门
# —— frontend/package.json 的 corepack 生成物(packageManager 单字段)不再永久卡死发布链,
# 其它任何脏改动仍照旧拦截(deploy.sh 第 0 步原闸门保留,双重保险)。
# 2026-09-21(cma-deploy-source-decouple-20260921,单 project-20260921-deploy-dirtyguard-rootfix):
# 根治「开发区任何 modified 即中止」——部署内容改为**已提交的 origin/main 树**(deploy.sh 用独立检出
# 重建;部署过程不再 pull 开发区)⇒ 本前置闸门降级为**告警**:打印开发区状态供留痕,不因 modified 拦截;
# 仅在「仓库不可用」时失败关闭(部署本身仍需这个仓库)。
set -uo pipefail
export HOME=/root
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export LANG=C.UTF-8
# ============================================================
# [gate-trail] 动作前留痕(树外执行体入口,协议 v1.0.0)
# chokepoint : gitea-cma-deploy-hook(channel=remote-trigger)
# 注册表 : /etc/gate-trail/governed-entries.json
# 留痕 : /var/log/gate-trail/trail.jsonl(append-only)
# 覆盖语义 : 父级入口覆盖下游子步骤(deploy.sh → db_migrate.py --apply),
# 单点挂载即覆盖整条链,下游不再各自留痕(见注册表 also_covered_by)
# 依据 : 派单 yanxue-20260921-deploy-channel-gate-trail-mount
# (协议母版 yanxue-20260918-051623-tree-external-unit-gate-trail)
# ⚠️ 失败关闭 : begin 返回 2(HIGH/CRITICAL 且无有效审批)→ 中止部署
# ⚠️ 取值纪律 : 必须先接住输出再取 $?;写成 `gate-trail ... | python3` 会把 exit 2 读成 0
# ⚠️ 收尾纪律 : 用 EXIT trap 闭合(含 dirty-guard 拦截等提前 exit 路径)
# ============================================================
TRAIL_CHANNEL="remote-trigger"
TRAIL_ACTION="Gitea push → post-receive → SSH forced-command → CMA 部署链(deploy.sh)"
TRAIL_CMD="bash /root/cma-management/deploy.sh (trigger: gitea post-receive / authorized_keys forced-command)"
TRAIL_TASK="${GATE_TRAIL_TASK:-}"
TRAIL_ID=""
TRAIL_CLOSED=""
# 收尾:任何退出路径都闭合留痕(BEGIN 之后才注册 trap,避免无 ID 时空转)
trail_closeup() {
local _rc=$?
if [ -z "${TRAIL_CLOSED:-}" ] && [ -n "${TRAIL_ID:-}" ] && command -v gate-trail >/dev/null 2>&1; then
if [ "$_rc" -eq 0 ]; then
gate-trail end --id "$TRAIL_ID" --result pass \
--evidence "deploy-hook exit=0 @ $(date '+%F %T %Z')" >/dev/null 2>&1 || true
else
gate-trail end --id "$TRAIL_ID" --result fail \
--evidence "deploy-hook exit=$_rc @ $(date '+%F %T %Z')(含前置闸门拦截等中止路径)" >/dev/null 2>&1 || true
fi
TRAIL_CLOSED=1
fi
return "$_rc"
}
if command -v gate-trail >/dev/null 2>&1; then
TRAIL_JSON=$(gate-trail begin --channel "$TRAIL_CHANNEL" --action "$TRAIL_ACTION" \
--cmd "$TRAIL_CMD" --task "$TRAIL_TASK" \
--asset /root/cma-management --json 2>/dev/null)
rc=$?
if [ "$rc" -eq 2 ]; then
echo "[deploy-hook] ⛔ gate-trail 拒绝执行(高危且无有效审批)—— 中止部署" >&2
exit 2
fi
TRAIL_ID=$(printf '%s' "$TRAIL_JSON" | python3 -c 'import json,sys
try:
print(json.load(sys.stdin).get("trail_id",""))
except Exception:
print("")')
if [ -z "$TRAIL_ID" ]; then
echo "[deploy-hook] ⛔ gate-trail 未返回 trail_id —— 按失败关闭处理,中止部署" >&2
exit 2
fi
echo "[deploy-hook] 🧾 gate-trail 留痕已开: $TRAIL_ID"
trap trail_closeup EXIT
else
echo "[deploy-hook] ⚠️ gate-trail 不可用,本次动作将无留痕(违反 gate-trail 协议)" >&2
fi
echo "[deploy-hook] 宿主机已接受 SSH(key=gitea-deploy-hook@sxbh.ltd, forced-command)"
echo "[deploy-hook] 请求命令(被忽略/仅记录)=${SSH_ORIGINAL_COMMAND:-<none>}"
cd /root/cma-management || { echo "[deploy-hook] ✗ /root/cma-management 不可用"; exit 1; }
# [0/5] 部署源前置检查(实现见 cma-deploy-dirty-guard.sh)
# 2026-09-21 起:开发区脏净**不再**中止部署(部署内容 = 已提交的 origin/main 树,见 deploy.sh);
# 本步只打印开发区状态供留痕,仅「仓库不可用」失败关闭。
DIRTY_GUARD=/usr/local/sbin/cma-deploy-dirty-guard.sh
echo "[deploy-hook] [0/5] 部署源前置检查(开发区脏净仅告警)..."
if [ ! -r "$DIRTY_GUARD" ]; then
echo "[deploy-hook] ✗ 前置检查脚本缺失或不可读: $DIRTY_GUARD(按失败关闭中止部署)"
exit 1
fi
if ! bash "$DIRTY_GUARD" /root/cma-management; then
echo "[deploy-hook] ✗ 仓库不可用 → 中止部署"
exit 1
fi
bash deploy.sh 2>&1
rc=$?
if [ "$rc" -eq 0 ]; then
echo "[deploy-hook] ✓ 部署成功"
else
echo "[deploy-hook] ✗ 部署失败 (exit: $rc)"
fi
exit "$rc"