pi 这一封四个实质点,逐个实测后处理: **C. 口径扩到全部文件**(他给的是算术,不是口味,我认): 原先按后缀取(`.conf/.service/.timer/.bak*`),我说的"零违规就不扩"是把口味当论证。 他把成本量化了:差集极小 ⇒ 多读几次文件(几十 KB),而收益是那个 `0` 从 **"有范围的 0"**(只对我划的圈成立)变成**"闭合的 0"**(对整棵 /etc/systemd 成立)。 他还补了一句我没想到的:这条判据只报**内容里含仓库路径**的文件, 所以含仓库串的 `.dpkg-old`/`~`/无后缀文件**恰恰都是真信号**(过期的旧真相), 不是噪声 —— 我先前"二进制会变成噪声"的担心本来就不成立。 **验收实测:比了 133 个文件(全部,不筛后缀)、命中 0。** 另按他要求把"零违规"这个前提写进注释,并说明"红/WARN 拆分"为什么推迟 (零违规时拆分是重构不是修 bug;出现第一个非白名单命中时再决定分档)。 **反例 1(②b 对 pi 是跑不到的分支)**:确认。pi 的依赖是全局包软链 (`-> /usr/lib/node_modules/@earendil-works/pi-coding-agent`),`cp -a` 保留软链 ⇒ 两侧 realpath 到**同一个 inode**(实测 `statSync(a).ino === statSync(b).ino`) ⇒ 版本集合按构造相等 ⇒ **②b 对 pi 永远不会红**。这正是本文件自己列过的第三种形态 (断言在、区分力不在),比"没写判据"更坏因为它看起来是绿的。 已改:两侧 realpath 相同时**明说"恒等、区分力为零"**并指出它真正覆盖谁(有 vendored 树的宿主), 不再报"版本集合一致"这种让人误以为验过的措辞。自检加了这一条。 **反例 2(① 的 realpath 盲区)**:确认,形状真实且三条判据全都看不见 —— ①只 grep 内容(仓库那份 unit 文本里没有仓库字面量)、②比内容(live 就是 repo 那个 inode, 必然"一致")、④只查固定名单。已加 realpath 判据:被检文件 realpath 落在仓库里 ⇒ 红, 与内容无关。自检加**正反两面**(内容干净但指向仓库 ⇒ 红;指向仓库外 ⇒ 不许红, 否则这条判据恒红)。实测:本机 `/etc/systemd/system` 下 0 条指向仓库的软链 (即这个 0 现在才是闭合的)。 **反例 3(环境假设第四次 ⇒ 建议收成一处)**:采纳。四次的形态一模一样 (HOME ⇒ 又一次 HOME ⇒ TMPDIR ⇒ GOMODCACHE/GOPATH),每次"再加一个预检"只挡已知那一个。 新增 `deploy/lib/env-defaults.sh`:一处给全 HOME/TMPDIR/GOMODCACHE(GOPATH)/PATH, 只设**未设**的变量,注释里写明四次历史与"否则第五次一定会来"; 三个部署脚本开头 source 它;**删掉**我上一轮加的那个分散 go 预检。 实测:在 `HOME`/`TMPDIR`/`GOPATH`/`GOMODCACHE` **全空**的环境里 `bash deploy/install.sh --check` **exit 0**(go vet + go test 自己站起来), 兜住的变量会在 `AGENTMAIL_ENV_DEFAULTS` 里说明。 docs 补两条纪律:13「锚点必须一一对应 —— 连'文件名'都会骗你」(E 的探针教训)、 14「退出码也有量纲」(--self-check 的退出码不是自检的结论)。 验证:npm test exit 0;check-shared-libs exit 0;drift --self-check **35/0**; prune 干跑 exit 0;install.sh --check 空环境 exit 0。
67 lines
3.2 KiB
Bash
67 lines
3.2 KiB
Bash
#!/usr/bin/env bash
|
||
#
|
||
# 部署脚本的**环境自足**声明:一处给全,不再每条命令旁各写一个预检。
|
||
#
|
||
# # 为什么要有这个文件
|
||
#
|
||
# 2026-09-14 这条链上一共修了**四次**同一形状的错,每次形态一模一样:
|
||
# **部署路径上假设了一个环境变量存在**。四次都是"再加一个预检",
|
||
# 而预检只挡**已知的那一个** —— 挡住 `HOME` 之后 `GOMODCACHE` 才露出来
|
||
# (两者其实是同一个根因的两面:`HOME` 决定 go 默认缓存位置)。
|
||
# pi 的原话:**"否则第五次一定会来。"**
|
||
#
|
||
# 四次记录(每次都是实测,不是推测):
|
||
# 1. `HOME` 未设 ⇒ `go` 拒绝工作(`homeagent` 的 build.sh,当次修了一处)
|
||
# 2. `HOME` 未设 ⇒ 同一族再现(`install.sh`,当次又修一处)
|
||
# 3. `TMPDIR` 未设/不可写 ⇒ 构建与测试把临时文件写到意外位置
|
||
# (`redeploy-plugin.sh:160` 曾写成 `TMPDIR=${TMPDIR:-/tmp} npx tsc …`,即就地兜了一次)
|
||
# 4. `GOMODCACHE`/`GOPATH` 都未设(`HOME` 也没设)⇒
|
||
# `go: module cache not found: neither GOMODCACHE nor GOPATH is set`
|
||
#
|
||
# # 纪律
|
||
#
|
||
# · 需要环境的脚本**开头** `source` 本文件,别在命令行里内联兜底;
|
||
# · 本文件只设**未设**的变量(`:-` 语义),绝不覆盖调用者已经给的值;
|
||
# · 失败要**说清是环境问题**并按仓库约定用退出码 2(环境),不要让它冒充代码缺陷。
|
||
#
|
||
# 注意:本文件被 `source`,**不能带 `set -e`**(那会改调用者的语义)。
|
||
# 调用者自己已经有 `set -uo pipefail`(`install.sh` 另有 `-e`),这里不重复声明。
|
||
|
||
# shellcheck shell=bash
|
||
|
||
# ① HOME:`go`/`npm`/`npx` 都会用它推默认缓存与配置位置。
|
||
# 以 root 跑部署时它通常就是 /root;显式给出以免"沙箱/最小环境下 HOME 为空"。
|
||
if [ -z "${HOME:-}" ]; then
|
||
HOME=/root
|
||
export HOME
|
||
AGENTMAIL_ENV_DEFAULTS="${AGENTMAIL_ENV_DEFAULTS:-} HOME=$HOME"
|
||
fi
|
||
|
||
# ② TMPDIR:构建暂存与测试都往这里写。缺省 /tmp 可能与真实挂载不符,
|
||
# 但"空的 TMPDIR"比"指错一个已存在的目录"更糟 —— 后者会让判据看错地方。
|
||
if [ -z "${TMPDIR:-}" ]; then
|
||
TMPDIR=/tmp
|
||
export TMPDIR
|
||
AGENTMAIL_ENV_DEFAULTS="${AGENTMAIL_ENV_DEFAULTS:-} TMPDIR=$TMPDIR"
|
||
fi
|
||
|
||
# ③ GOMODCACHE / GOPATH:go 的模块缓存。**只在 go 存在时**推导,
|
||
# 且只在两者都空的时候推导(有 GOPATH 时 go 自己会算 GOMODCACHE)。
|
||
if command -v go >/dev/null 2>&1; then
|
||
if [ -z "${GOMODCACHE:-}" ] && [ -z "${GOPATH:-}" ]; then
|
||
GOPATH="$HOME/go"
|
||
export GOPATH
|
||
AGENTMAIL_ENV_DEFAULTS="${AGENTMAIL_ENV_DEFAULTS:-} GOPATH=$GOPATH"
|
||
fi
|
||
fi
|
||
|
||
# ④ PATH:只保证常见系统目录在(最小环境里 PATH 可能为空)。
|
||
case ":${PATH:-}:" in
|
||
*:/usr/bin:*) : ;;
|
||
*) PATH="${PATH:+$PATH:}/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; export PATH
|
||
AGENTMAIL_ENV_DEFAULTS="${AGENTMAIL_ENV_DEFAULTS:-} PATH=+系统目录" ;;
|
||
esac
|
||
|
||
# 给调用者一句可打印的话:本次**兜了哪几个**变量(空则说明环境本来就齐)。
|
||
export AGENTMAIL_ENV_DEFAULTS="${AGENTMAIL_ENV_DEFAULTS:-}"
|