fix(deploy): /tmp 占满把部署自己卡死了 —— 收构建暂存 + 构建带 -trimpath + 判据 ⑤
起因是用户让「清理一下」那批带仓库路径的残留。照着清理策略走时撞上更大的事实:
本机 /tmp 是 9.8G 的 tmpfs,**已 100% 满、可用 0 字节**,我自己的 `go build` 当场
ENOSPC 失败 —— 而部署的第一步就是构建。
## 1 谁把 /tmp 占满的(agentmail 自己的那份)
`redeploy-gateway.sh` 把网关构建到 /tmp 再 install 过去(为了原子替换),**用完没人删**:
每次部署留一个 24MB,实测 7 份 / 162MB。加上电子打包的中间物(squashfs-root 283MB、
pkgcheck/deb 291MB)、go-build-agentmail 缓存 172MB、4 个孤儿 go-build 工作目录 50MB
—— agentmail 名下约 960MB。另有别的产品的 /tmp/gocache 4.6G(TrueAgent 的
rebuild-plugins.sh 里 `export GOCACHE=/tmp/gocache`),不是本项目的,没动。
- prune-deploy-artifacts.sh 新增一类「构建暂存」,窗口 KEEP_BUILD_STAGES=1
(正常路径下部署脚本自己会收,留下的只可能是失败那次,正好留现场)。
自检 +1 项、变异验证过(把删除改成永不删 → 恰好那一项红)。
- 本次实际收:删除 8 项 / 释放 164MB(另加手动清 623MB 不可再生的中间物)。
- redeploy-gateway.sh 成功分支上收掉 $STAGE;失败/回滚分支**不删**(要留现场)。
## 2 残留里还藏着两处「旧真相」
- /etc/systemd/system/zcode.service.bak-20260912-145744(+ 同一次改动的
zcode.service.d/10-dbus.conf.bak-…)里躺着 /home/program/agentmail/deploy/
service-failure-notify.mjs —— 就是我上一封报「/etc/systemd 引用仓库 = 0 个文件」时
**判据自己划掉了的那一类**(walk 里 `!name.includes('.bak')`)。已删(在线单元与
deploy/systemd/ 逐字节一致,sha256 核对过),另外 4 个是别的产品的,没动。
- 判据 ① 因此放宽到含 .bak,并补了坏样本(.bak 里引用仓库路径必须判红)。
上一封那句「0 个文件」的边界现在写进判据里了 —— 边界不说出口,就等于报了个假的 0。
## 3 -trimpath:标准目录部署只做了一半
Go 默认把源文件绝对路径编进二进制。对照实验(同一份源码、同一个 go,只差标志):
带 -trimpath 0 处,不带 57 处 —— 而 19:05 那次部署产出的
/opt/agentmail/agentmail-gateway 里就有 57 处 /home/program/agentmail/…。
依赖确实没了,但**源仓库位置还印在产物上**。两个构建点都加上 -trimpath,
并新增判据 ⑤(已安装二进制不得含源码路径,两侧样本都验)。
判据 ⑤ 现在**是红的**,这是存量产物的实情:磁盘上那份要等下一次
redeploy-gateway.sh 才会被换掉。我没替它单独重启网关 —— 会掐断正在跑的会话。
(工作区是多会话共用的,本次只 add 了上面这 4 个 deploy/ 文件。)
This commit is contained in:
@ -510,6 +510,13 @@ export function checkLayout(inject = {}) {
|
||||
};
|
||||
|
||||
// ① 任何 unit/drop-in 都不得引用源码目录
|
||||
//
|
||||
// ★ `.bak` 也算在内。原先这里把它排除了,理由是"systemd 不加载 .bak"——
|
||||
// 运行时确实不加载,但后果是:2026-09-14 我向用户报"`/etc/systemd` 引用仓库 = 0 个文件",
|
||||
// 而 `/etc/systemd/system/zcode.service.bak-20260912-145744` 里就躺着两行
|
||||
// `/home/program/agentmail/deploy/service-failure-notify.mjs`。
|
||||
// 那句话只对我自己划的那个圈成立 —— **判据的边界没说出口,就等于报了个假的 0**。
|
||||
// 留着 .bak 的代价也不是零:它们是"过期的旧真相",`grep` 到它的人会以为改动没生效。
|
||||
const offenders = [];
|
||||
const walk = dir => {
|
||||
let entries = [];
|
||||
@ -517,7 +524,7 @@ export function checkLayout(inject = {}) {
|
||||
for (const e of entries) {
|
||||
const full = `${dir}/${e.name}`;
|
||||
if (e.isDirectory()) walk(full);
|
||||
else if (/\.(conf|service|timer)$/.test(e.name) && !e.name.includes('.bak')) {
|
||||
else if (/\.(conf|service|timer)(\.bak.*)?$/.test(e.name)) {
|
||||
let text = '';
|
||||
try { text = String(readFile(full, 'utf8')); } catch { continue; }
|
||||
if (text.includes(REPO)) offenders.push(full);
|
||||
@ -525,7 +532,7 @@ export function checkLayout(inject = {}) {
|
||||
}
|
||||
};
|
||||
walk(SYS);
|
||||
push('没有任何 unit/drop-in 引用源码目录', offenders.length === 0, offenders.join(' '));
|
||||
push('没有任何 unit/drop-in/.bak 引用源码目录', offenders.length === 0, offenders.join(' '));
|
||||
|
||||
// ② 已安装单元与仓库副本一致(仓库是唯一真相)
|
||||
const drift = [];
|
||||
@ -579,6 +586,25 @@ export function checkLayout(inject = {}) {
|
||||
}
|
||||
push('各服务的工作目录/可执行文件不在源码目录', badHosts.length === 0, badHosts.join(' '));
|
||||
|
||||
// ⑤ 生产二进制里不得嵌源码路径(-trimpath)。
|
||||
//
|
||||
// Go 默认把源文件的**绝对路径**编进二进制。2026-09-14 实测:换到标准目录部署之后,
|
||||
// `/opt/agentmail/agentmail-gateway` 里仍有 57 处 `/home/program/agentmail/…` ——
|
||||
// 构建脚本漏了 `-trimpath`(对照实验:同一份源码、同一个 go,带标志 0 处、不带 57 处)。
|
||||
// 这条是"运行时不再依赖源码目录"的**后半句**:依赖确实没了,但源仓库位置还印在产物上,
|
||||
// 而且它会把"这个二进制是从哪份源码建的"变成只能靠推断的事。
|
||||
const BIN = '/opt/agentmail/agentmail-gateway';
|
||||
let binHits = -1;
|
||||
let binNote = `读不到 ${BIN}(标准位置没有网关二进制)`;
|
||||
try {
|
||||
const text = String(readFile(BIN));
|
||||
binHits = text.split(REPO).length - 1;
|
||||
binNote = binHits === 0
|
||||
? `${BIN} 里一处都没有`
|
||||
: `${BIN} 里有 ${binHits} 处 ${REPO}/…(重新构建即可清零:跑一次 redeploy-gateway.sh)`;
|
||||
} catch { /* binHits 保持 -1 = 读不到 */ }
|
||||
push('已安装的网关二进制不含源码路径(构建带 -trimpath)', binHits === 0, binNote);
|
||||
|
||||
// ⑥ 工作区干净度 —— **WARN,不参与退出码**。
|
||||
//
|
||||
// 判据 ① 比的是「仓库工作区 → 快照」这一跳。它覆盖不到「HEAD → 工作区」
|
||||
@ -630,33 +656,58 @@ export function layoutSelfCheck() {
|
||||
'/etc/systemd/system': [{ name: 'x.service', isDirectory: () => false }],
|
||||
'/etc/systemd/system/x.service': 'ExecStart=/home/program/agentmail/bin/x',
|
||||
'/repo/systemd': [],
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x'
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x',
|
||||
'/opt/agentmail/agentmail-gateway': 'fake-elf'
|
||||
}));
|
||||
// ① 的 .bak 分支:旧备份单元里躺着仓库路径,也必须报出来(原先它被过滤掉了)。
|
||||
const badBak = checkLayout(fake({
|
||||
'/etc/systemd/system': [{ name: 'z.service.bak-20260101-000000', isDirectory: () => false }],
|
||||
'/etc/systemd/system/z.service.bak-20260101-000000': 'ExecStopPost=-/usr/bin/node /home/program/agentmail/deploy/x.mjs',
|
||||
'/repo/systemd': [],
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x',
|
||||
'/opt/agentmail/agentmail-gateway': 'fake-elf'
|
||||
}));
|
||||
// ⑤ 的坏样本:二进制里嵌着源码路径。
|
||||
const badBin = checkLayout(fake({
|
||||
'/etc/systemd/system': [],
|
||||
'/repo/systemd': [],
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x',
|
||||
'/opt/agentmail/agentmail-gateway': 'ELF…/home/program/agentmail/server/cmd/server/main.go…'
|
||||
}));
|
||||
const good = checkLayout(fake({
|
||||
'/etc/systemd/system': [{ name: 'y.service', isDirectory: () => false }],
|
||||
'/etc/systemd/system/y.service': 'ExecStart=/opt/agentmail/agentmail-gateway',
|
||||
'/repo/systemd': [],
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x'
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x',
|
||||
'/opt/agentmail/agentmail-gateway': 'ELF…github.com/agentmail/gateway/cmd/server…'
|
||||
}));
|
||||
const dirty = checkLayout({ ...fake({
|
||||
'/etc/systemd/system': [],
|
||||
'/repo/systemd': [],
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x'
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x',
|
||||
'/opt/agentmail/agentmail-gateway': 'fake-elf'
|
||||
}), ...fakeGit(' M src/pool.mjs\n') });
|
||||
const clean = checkLayout({ ...fake({
|
||||
'/etc/systemd/system': [],
|
||||
'/repo/systemd': [],
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x'
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x',
|
||||
'/opt/agentmail/agentmail-gateway': 'fake-elf'
|
||||
}), ...fakeGit('') });
|
||||
const unreadable = checkLayout({ ...fake({
|
||||
'/etc/systemd/system': [],
|
||||
'/repo/systemd': [],
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x'
|
||||
'/opt/agentmail/bin/service-failure-notify.mjs': 'x',
|
||||
'/opt/agentmail/agentmail-gateway': 'fake-elf'
|
||||
}), git: () => { throw new Error('not a git repo'); } });
|
||||
const fifth = o => o.find(c => c.name.startsWith('已安装的网关二进制'));
|
||||
const sixth = o => o.find(c => c.name.startsWith('工作区干净'));
|
||||
return [
|
||||
{ name: '标准目录:引用源码目录的样本必须判红', ok: bad[0].ok === false },
|
||||
{ name: '标准目录:.bak 里引用源码目录也必须判红', ok: badBak[0].ok === false },
|
||||
{ name: '标准目录:干净样本必须判绿', ok: good[0].ok === true },
|
||||
// 二进制那条两侧都要真:嵌了源码路径必须红,trimpath 的必须绿。
|
||||
{ name: '网关二进制:嵌了源码路径必须判红', ok: fifth(badBin)?.ok === false },
|
||||
{ name: '网关二进制:trimpath 过的必须判绿', ok: fifth(good)?.ok === true },
|
||||
// 编号:id 从 1 连续排到 N,不许跳号(原先只有 ⑥ 带编号,读者会去找不存在的 ⑤)。
|
||||
{ name: '标准目录:id 连续编号,不跳号', ok: good.every((c, i) => c.id === String(i + 1)) },
|
||||
// 「工作区干净」是"说出来但不改结论"的提示:脏 / 干净 / 读不到三种都被报出来,
|
||||
|
||||
Reference in New Issue
Block a user