fix(deploy): flock 没登记进 AGENTMAIL_REQUIRE("缺命令"被报成"另一个部署在跑")+ 中断 trap + 两条欠账入册
**1. 自指缺口:新能力带的新依赖没登记回表(pi 抓到)**
我加"同时性"那一列时引入了 `flock`,**却没把 `flock` 加进三个脚本的 `AGENTMAIL_REQUIRE`**。
后果实测:
PATH 里没有 flock ⇒ `flock: command not found`(127)⇒ `! flock` 为真
⇒ 打印"**另一个部署正在跑(锁被占用)**"
退出码事后是对的(2),但**诊断是错的** —— 而照着它做的是"等另一个部署结束":**永远等不到**。
三个脚本各加一个词;并在 `env-defaults.sh` 的 ③b 注释里写明这条规矩
(**新增任何外部命令时回到 `AGENTMAIL_REQUIRE` 登记**)与这个实例。
→ docs 第 19 条:「表与被表的东西不同步」。
**2. 第六列候选:中断(信号)—— 已按 pi 的建议修 `redeploy-gateway.sh`**
原子 `mv` 修的是"半截二进制",**没修"服务停着而脚本死了"**:
第 250 行 stop 与第 267 行 start 之间被外部信号打断(Ctrl-C、宿主杀进程、会话回收、OOM)
⇒ 脚本直接退出、**服务留在停止状态而什么也不说** ⇒ "邮件全停 + 无人告知"。
已加 `trap … INT TERM HUP`:进窗口前置位 `_SERVICE_STOPPED`,出窗口复位并摘 trap;
**trap 只在"确实还停着"时才动手**(否则会多起一次服务);回滚分支也维护该标志。
**用 stub `systemctl` + 探针真喂过四个分支**:
stopped=1 + SIGINT ⇒ 调了 `systemctl start`、退出码 130、打印点名
stopped=0 + SIGINT ⇒ **没有**调用 start(不误起)
(探针里两次 harness 自身的错也一并记下:`sed`/`awk` 的区间端点选错,
把 `trap -` 也取进来,导致"trap 没生效"的假象 —— 是探针错,不是代码错。)
★ 顺带修掉自己写的一处:`printf '… $SERVICE …'` 用**单引号**包裹 ⇒ `$SERVICE` **不展开**,
原样打出字面量(探针里实测看到)。改双引号传参。这类"消息里有变量但没展开"会让读者
以为服务名真叫 `$SERVICE`。
**3. `install -d -m` 对已存在目录的行为:实测会改(pi 的疑问)**
mkdir -p 建 755 → `install -d -m 0700 <同一目录>` → **700**
所以**下一次部署就会收紧** `/opt/agentmail/data` 与 `/etc/agentmail`,不需要额外的
`chmod 0700` 动作,也不必为此单开一次"人按一下"。
(我按这条如实回报,因为 pi 说过"若不会改就需要显式 chmod,且安全意义比
`user-question.js` 高" —— 结论是不需要。)
**4. 两条欠账入 `docs/DEBTS.json`(按 pi 的界线:只修新机制自己引入且会误报的缺口)**
· `deploy-space-prefix-fs`:空间列只铺了 `$TMPDIR`,没铺 `$PREFIX` 所在文件系统
(属"列内没铺满",不是新列)。
· `deploy-interrupt-trap-other-scripts`:trap 只在 `redeploy-gateway.sh`;
`install.sh`/`redeploy-plugin.sh` 被打断同样会留半成品(没有"服务停着"那种后果,故低优先)。
→ docs 第 20 条同时记下 trap 这条纪律与它的可喂判据写法。
验证:install.sh --check exit 0;npm test exit 0;prune 自检 22/22;drift 自检 35/0;
check-shared-libs exit 0;全部 deploy 脚本 bash -n 通过;DEBTS.json 有效(13 条)。
This commit is contained in:
@ -17,7 +17,7 @@ REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# 本脚本依赖的外部命令:缺一个就 exit 2 + 人话(见 env-defaults.sh 的 ③b 一节)。
|
||||
# 为什么必须显式声明:**"命令不在"与"命令在但输出为空"必须分开** ——
|
||||
# 下游把前者读成后者时就会产出假绿(journalctl 那两处就是:工具缺失被读成"无 panic")。
|
||||
AGENTMAIL_REQUIRE="git go npm npx node curl systemctl"
|
||||
AGENTMAIL_REQUIRE="git go npm npx node curl systemctl flock"
|
||||
agentmail_env_report
|
||||
|
||||
# 与 `redeploy-gateway.sh` / `reset-demo.sh` 同源(pi 评审 2026-09-14 对出来的):
|
||||
|
||||
@ -178,6 +178,15 @@ unset _am_avail_kb
|
||||
#
|
||||
# 调用者声明自己那组(`AGENTMAIL_REQUIRE`,空格分隔),缺一个就 exit 2。
|
||||
# 与四次史的处理**同形**,只是对象从变量换成了命令。
|
||||
#
|
||||
# ★ **新增任何外部命令时,回到调用者的 `AGENTMAIL_REQUIRE` 登记**(pi 评审 2026-09-14)。
|
||||
# 这条规矩不是形式主义,有一个我自己刚踩的实例:我加了"同时性"那一列(部署锁)、
|
||||
# 引入了新命令 `flock`,**却没回到这张表登记**。后果实测:
|
||||
# PATH 里没有 flock ⇒ `flock: command not found`(127)⇒ `! flock` 为真
|
||||
# ⇒ 打印"**另一个部署正在跑(锁被占用)**" —— 退出码事后是对的(2),
|
||||
# 但**诊断是错的**,而照着它做的是"等另一个部署结束":**永远等不到**。
|
||||
# 即"**表与被表的东西不同步**":新能力自带的依赖没有登记回表。
|
||||
# 所以每次给部署路径加一个新命令,都要回来加一个词。
|
||||
if [ -n "${AGENTMAIL_REQUIRE:-}" ]; then
|
||||
_am_missing=""
|
||||
for _am_c in $AGENTMAIL_REQUIRE; do
|
||||
|
||||
@ -43,7 +43,7 @@ PREFIX="${AGENTMAIL_PREFIX:-/opt/agentmail}"
|
||||
# 本脚本依赖的外部命令:缺一个就 exit 2 + 人话(见 env-defaults.sh 的 ③b 一节)。
|
||||
# 为什么必须显式声明:**"命令不在"与"命令在但输出为空"必须分开** ——
|
||||
# 下游把前者读成后者时就会产出假绿(journalctl 那两处就是:工具缺失被读成"无 panic")。
|
||||
AGENTMAIL_REQUIRE="git go npm node curl systemctl journalctl sqlite3 install"
|
||||
AGENTMAIL_REQUIRE="git go npm node curl systemctl journalctl sqlite3 install flock"
|
||||
agentmail_env_report
|
||||
|
||||
# ★ root 断言(pi 评审 2026-09-14 指出本脚本缺它,只有 install.sh 有):
|
||||
@ -235,6 +235,35 @@ fi
|
||||
|
||||
# ---------------------------------------------------------------- 6 原子替换
|
||||
say "6. 停服 → 原子替换 → 起服"
|
||||
|
||||
# ★ **信号兜底:服务停着而脚本死了,是这套流程最危险的窗口**(pi 评审 2026-09-14)。
|
||||
#
|
||||
# 原子 `mv` 修的是"半截二进制",**没有修"服务停着没人知道"**:
|
||||
# 第 250 行 stop、第 267 行 start 之间若被外部信号打断(Ctrl-C、宿主杀部署进程、
|
||||
# 会话被回收、OOM),脚本直接退出 ⇒ **服务留在停止状态,而脚本什么也不说** ⇒
|
||||
# 后果是"邮件全停 + 无人告知",比半截二进制更难被发现。
|
||||
# 所以:进入窗口前挂 trap,出了窗口就摘掉;trap 只在"确实还停着"时才动手。
|
||||
#
|
||||
# 为什么这是第六列的候选(pi 的分类):它既不是变量、命令、空间、身份,也不是
|
||||
# 并发(那是"同时有两个部署"),而是**"过程被中断"** —— 环境前提表里没有这一类。
|
||||
_SERVICE_STOPPED=0
|
||||
_restore_on_signal() {
|
||||
local sig="$1"
|
||||
if [ "$_SERVICE_STOPPED" = "1" ]; then
|
||||
# ⚠️ 用双引号:单引号会让 `$SERVICE` **不展开**、原样打出去(我第一版就是这样,
|
||||
# 探针里实测看到字面量 `$SERVICE`)。这是"消息里带了变量但没展开"的形状 ——
|
||||
# 看的人会以为服务名真的叫 `$SERVICE`。
|
||||
printf '\n [WARN] 收到 %s 且 %s 仍处于停止状态 —— 正在起回来(否则邮件会一直停)\n' \
|
||||
"$sig" "$SERVICE" >&2
|
||||
systemctl start "$SERVICE" 2>/dev/null || \
|
||||
printf ' [FAIL] 起服务失败:请手工 systemctl start %s\n' "$SERVICE" >&2
|
||||
printf ' 原子替换未完成;目标二进制可能仍是旧版(原子 mv 保证不会半截)。\n' >&2
|
||||
fi
|
||||
exit 130
|
||||
}
|
||||
trap '_restore_on_signal INT' INT
|
||||
trap '_restore_on_signal TERM' TERM
|
||||
trap '_restore_on_signal HUP' HUP
|
||||
# 为什么仍要 stop:SQLite 单写者,且换掉二进制后旧进程还在跑旧代码,
|
||||
# 与新库 schema 可能不一致。下面保证的是「文件替换本身」原子,
|
||||
# 不代表可以热换正在服务的进程。
|
||||
@ -248,6 +277,7 @@ say "6. 停服 → 原子替换 → 起服"
|
||||
# 对照:`redeploy-plugin.sh` 的 `mv "$STAGING" "$SNAP"` 是**真原子**(两者都在 `$DEST` 下、同 fs),
|
||||
# 同一个仓库里原先两套"原子切换",一套真、一套名义上的。
|
||||
run "systemctl stop '$SERVICE'"
|
||||
_SERVICE_STOPPED=1 # ← 从这里开始,"脚本死了但服务停着"就是事故
|
||||
_NEW="$TARGET.new.$$"
|
||||
# 复制到**目标同目录**:这一步慢/失败都无所谓,因为 `$_NEW` 还没有任何人用。
|
||||
if ! run "install -m 0755 '$STAGE' '$_NEW'"; then
|
||||
@ -265,6 +295,8 @@ if ! run "mv -f '$_NEW' '$TARGET'"; then
|
||||
fi
|
||||
unset _NEW
|
||||
run "systemctl start '$SERVICE'"
|
||||
_SERVICE_STOPPED=0 # ← 服务已回来,危险窗口结束
|
||||
trap - INT TERM HUP
|
||||
run "sleep 6"
|
||||
|
||||
# ---------------------------------------------------------------- 7 后置验证
|
||||
@ -355,8 +387,10 @@ if [ "$CHECK_FAIL" -gt 0 ]; then
|
||||
say "结论: 验证有 $CHECK_FAIL 项失败 —— 正在回滚,不要「先上着再修」"
|
||||
if [ -n "$BINBAK" ]; then
|
||||
run "systemctl stop '$SERVICE'"
|
||||
_SERVICE_STOPPED=1
|
||||
run "install -m 0755 '$BINBAK' '$TARGET'"
|
||||
run "systemctl start '$SERVICE'"
|
||||
_SERVICE_STOPPED=0
|
||||
ok "已回滚到 $BINBAK"
|
||||
else
|
||||
warn "无旧二进制可回滚"
|
||||
|
||||
@ -53,7 +53,7 @@ REPO=${REPO:-/home/program/agentmail}
|
||||
# 本脚本依赖的外部命令:缺一个就 exit 2 + 人话(见 env-defaults.sh 的 ③b 一节)。
|
||||
# 为什么必须显式声明:**"命令不在"与"命令在但输出为空"必须分开** ——
|
||||
# 下游把前者读成后者时就会产出假绿(journalctl 那两处就是:工具缺失被读成"无 panic")。
|
||||
AGENTMAIL_REQUIRE="git node npx systemctl journalctl sqlite3"
|
||||
AGENTMAIL_REQUIRE="git node npx systemctl journalctl sqlite3 flock"
|
||||
agentmail_env_report
|
||||
|
||||
# ★ **部署锁**(pi 评审 2026-09-14):环境前提里原先缺的第五列 —— **同时性**。
|
||||
|
||||
Reference in New Issue
Block a user