chore(deploy): 清理部署残留(1.0GB)+ 把清理写成可复跑脚本
用户说「清理一下」。实测 /opt/agentmail 累计 1.3GB: - 网关旧二进制 33 份 × 24MB ≈ 790MB(每次 redeploy 留一份,从 09-05 起没清过) - 插件快照:opencode 6 份 339MB、dsh 7 份 195MB、zcode 16 份、pi 9 份 - /tmp 里的部署前数据库备份 10 份(tmpfs,占内存) 新增 `deploy/prune-deploy-artifacts.sh`(可复跑,不手敲 rm),两条硬规矩: ① **保留回滚窗口**:网关留最新 3 份、每个插件留最新 3 份快照,`current` 永远保留 (发布纪律要求有回滚目标,所以不是全清); ② ★ **绝不删正在使用的快照**:扫 /proc 的 cmdline 与 cwd,命中就跳过。 执行结果:**/opt/agentmail 1305MB → 302MB(释放 1003MB)**,7 个服务全部 active, `current` 指向未变,`check-deploy-drift` 仍报「四个宿主都在跑当前代码」。 ## 过程中的一处自纠 "在用"闸门第一版有**自我匹配**缺陷:我的测试命令把路径写在命令行里,于是扫 /proc 时 扫到了自己 ⇒ 对不存在的路径也判"在用"(与 `pkill -f` 杀掉自己那条命令同一类)。 已改为排除本进程及其祖先链,并**换正确方式重测**(路径从文件读、不进 cmdline): 在用快照判"在用" ✓、不存在的路径不误判 ✓ —— 判据两侧都验过才敢执行删除。 文档:docs/DEV-TOOLING.md 记了用法与那两条规矩。
This commit is contained in:
107
deploy/prune-deploy-artifacts.sh
Executable file
107
deploy/prune-deploy-artifacts.sh
Executable file
@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# 清理部署残留:旧网关二进制、旧插件快照、/tmp 里的部署前数据库备份。
|
||||
#
|
||||
# # 为什么要脚本而不是手敲 rm
|
||||
#
|
||||
# 这些东西**天天在长**:每次 redeploy 留一个 24MB 的网关二进制、每个插件留一份快照
|
||||
# (opencode/dsh 的快照含 node_modules,一份几十 MB)。2026-09-14 实测:
|
||||
# /opt/agentmail 1.3G,其中旧二进制 ~790MB、插件快照 ~540MB。
|
||||
# 手敲的清理不可复现、下次还得再想一遍,而且很容易漏掉"这一份还在被用"的判断。
|
||||
#
|
||||
# # 两条硬规矩
|
||||
#
|
||||
# ① **保留回滚窗口**:网关二进制留最新 N 份、每个插件留最新 N 份快照
|
||||
# (`current` 永远保留)。发布纪律要求"有回滚目标",所以不是全清。
|
||||
# ② ★ **绝不删正在使用的快照**:任何进程的 cmdline 里出现过的快照路径一律跳过。
|
||||
# 删掉它 = 进程一重启就找不到自己的代码(而这类事故此前真发生过)。
|
||||
#
|
||||
# 用法:
|
||||
# bash deploy/prune-deploy-artifacts.sh # 干跑,只报告
|
||||
# bash deploy/prune-deploy-artifacts.sh --apply # 真删
|
||||
# KEEP_GATEWAY=2 KEEP_SNAPSHOTS=2 bash ... --apply # 调保留窗口
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="${AGENTMAIL_ROOT:-/opt/agentmail}"
|
||||
KEEP_GATEWAY="${KEEP_GATEWAY:-3}"
|
||||
KEEP_SNAPSHOTS="${KEEP_SNAPSHOTS:-3}"
|
||||
KEEP_TMP_DB="${KEEP_TMP_DB:-2}"
|
||||
APPLY=0
|
||||
[ "${1:-}" = "--apply" ] && APPLY=1
|
||||
|
||||
freed_kb=0
|
||||
removed=0
|
||||
skipped=0
|
||||
|
||||
report() { printf ' %s %s\n' "$1" "$2"; }
|
||||
|
||||
# 收集所有"正在被进程使用"的路径(cmdline + cwd),用来做安全闸。
|
||||
# 自己这条命令的 cmdline 里就可能含这个路径(例如调用方把路径写在命令行里),
|
||||
# 于是"检查在用"会永远为真 —— 与 `pkill -f` 杀掉自己那条命令是同一个坑。
|
||||
# 所以先把本进程及其祖先链排除掉。
|
||||
_SELF_CHAIN=" "
|
||||
_p=$$
|
||||
while [ -n "$_p" ] && [ "$_p" != "1" ] && [ "$_p" != "0" ]; do
|
||||
_SELF_CHAIN="$_SELF_CHAIN$_p "
|
||||
_p=$(awk '{print $4}' "/proc/$_p/stat" 2>/dev/null || echo "")
|
||||
done
|
||||
|
||||
in_use() {
|
||||
local needle="$1" p pid
|
||||
for p in /proc/[0-9]*; do
|
||||
pid=${p#/proc/}
|
||||
[ -r "$p/cmdline" ] || continue
|
||||
case "$_SELF_CHAIN" in *" $pid "*) continue;; esac
|
||||
if tr '\0' ' ' <"$p/cmdline" 2>/dev/null | grep -qF "$needle"; then return 0; fi
|
||||
local cwd; cwd=$(readlink "$p/cwd" 2>/dev/null || true)
|
||||
case "$cwd" in *"$needle"*) return 0;; esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
del() {
|
||||
local path="$1"
|
||||
local kb; kb=$(du -sk "$path" 2>/dev/null | cut -f1); kb=${kb:-0}
|
||||
if [ "$APPLY" = "1" ]; then rm -rf "$path"; fi
|
||||
freed_kb=$((freed_kb + kb)); removed=$((removed + 1))
|
||||
report " $([ "$APPLY" = 1 ] && echo 删除 || echo 待删)" "$path($((kb/1024))MB)"
|
||||
}
|
||||
|
||||
echo "==> 网关旧二进制(保留最新 $KEEP_GATEWAY 份)"
|
||||
mapfile -t bins < <(ls -1t "$ROOT"/agentmail-gateway.bak-* 2>/dev/null)
|
||||
for i in "${!bins[@]}"; do
|
||||
[ "$i" -lt "$KEEP_GATEWAY" ] && { report " 保留" "${bins[$i]}"; continue; }
|
||||
if in_use "${bins[$i]}"; then report " ★跳过(正在被使用)" "${bins[$i]}"; skipped=$((skipped+1)); continue; fi
|
||||
del "${bins[$i]}"
|
||||
done
|
||||
|
||||
echo "==> 插件快照(每个插件保留最新 $KEEP_SNAPSHOTS 份,current 永远保留)"
|
||||
for plug in "$ROOT"/plugins/*/; do
|
||||
[ -d "$plug" ] || continue
|
||||
name=$(basename "$plug")
|
||||
mapfile -t snaps < <(ls -1dt "$plug"*/ 2>/dev/null | sed 's:/$::')
|
||||
for i in "${!snaps[@]}"; do
|
||||
s="${snaps[$i]}"
|
||||
[ "$(basename "$s")" = "current" ] && continue
|
||||
# current 指向的那一份也必须保留
|
||||
cur=$(readlink -f "$plug/current" 2>/dev/null || true)
|
||||
[ "$cur" = "$(readlink -f "$s")" ] && { report " 保留" "$s(current 指向)"; continue; }
|
||||
if [ "$i" -lt "$KEEP_SNAPSHOTS" ]; then report " 保留" "$s"; continue; fi
|
||||
if in_use "$s"; then report " ★跳过(正在被使用)" "$s"; skipped=$((skipped+1)); continue; fi
|
||||
del "$s"
|
||||
done
|
||||
done
|
||||
|
||||
echo "==> /tmp 的部署前数据库备份(保留最新 $KEEP_TMP_DB 份)"
|
||||
mapfile -t dbs < <(ls -1t /tmp/agentmail-pre-deploy-*.db 2>/dev/null)
|
||||
for i in "${!dbs[@]}"; do
|
||||
[ "$i" -lt "$KEEP_TMP_DB" ] && { report " 保留" "${dbs[$i]}"; continue; }
|
||||
del "${dbs[$i]}"
|
||||
done
|
||||
|
||||
echo
|
||||
if [ "$APPLY" = "1" ]; then
|
||||
echo " 已删除 $removed 项,释放约 $((freed_kb/1024))MB;跳过(在用)$skipped 项"
|
||||
else
|
||||
echo " 干跑:将删除 $removed 项,可释放约 $((freed_kb/1024))MB;跳过(在用)$skipped 项"
|
||||
echo " 加 --apply 执行。"
|
||||
fi
|
||||
@ -67,3 +67,19 @@ pi-lens 在编辑文件后会走 smart-default 回退选择格式化器(见它
|
||||
|
||||
- `biome.jsonc`:只挡得住 biome,**挡不住 prettier** —— 两者是并列的候选格式化器,
|
||||
各有各的配置。所以真正的开关是 `.pi-lens.json`,它两条改写路径一起关。
|
||||
|
||||
## 部署残留清理(`deploy/prune-deploy-artifacts.sh`)
|
||||
|
||||
部署会留下两类持续增长的东西:网关旧二进制(每份 ~24MB)与插件快照(opencode/dsh 的
|
||||
快照含 node_modules,一份几十 MB)。2026-09-14 实测 `/opt/agentmail` 累计到 **1.3GB**,
|
||||
其中旧二进制 ~790MB、快照 ~540MB。
|
||||
|
||||
```bash
|
||||
bash deploy/prune-deploy-artifacts.sh # 干跑,只报告
|
||||
bash deploy/prune-deploy-artifacts.sh --apply # 真删(默认:网关留 3 份、每插件留 3 份快照)
|
||||
```
|
||||
|
||||
两条硬规矩:**保留回滚窗口**(发布纪律要求有回滚目标,所以不是全清);**绝不删正在使用的
|
||||
快照**(扫 /proc 的 cmdline 与 cwd,命中就跳过 —— 删掉它进程一重启就找不到自己的代码)。
|
||||
注意那个"在用"判断必须排除**本进程及其祖先链**:调用方常把路径写在命令行里,
|
||||
不排除就会出现"永远判为在用"(与 `pkill -f` 杀掉自己那条命令同一个坑,已写进脚本注释)。
|
||||
|
||||
Reference in New Issue
Block a user