Files
MailUI4Agents/plugins/homeagent-mail-bridge/build.sh
JianFeeeee 33488760ce fix(相位/安全): 部署门禁只判产物自证;静默 break 改成出声;内核读数带时间坐标
pi 2026-09-14 的裁定与两条更正,逐条落地。

1. **相位裁定(选 c)**:`packaging`/`build-stamp` 属于**构建相位**,不属于安装相位。
   `run-all.mjs` 现在有相位:`AGENTMAIL_CRITERIA_PHASE=install`(部署门禁用)。
   每条判据登记它读的哪一侧(`ARTIFACT`/`SOURCE`),install 相位里出现 SOURCE 侧判据 → 红;
   被跳过的判据**点名打印**,不静默丢。汇总打 `RESULT phase=build|install`。
   规则入册 `test/CRITERIA.md` §11(含三个真实实例:check-shared-libs 恒红、
   packaging 一改前端就卡死、HOME 在门禁跑完之后才炸)。

   安装相位**真正能判的那一半**:`deploy/install.sh` 读**产物自证**(不重算 dist)——
   `releaseCandidate !== true` → 拒绝;产物 `gitRev` ≠ HEAD → "这个包比源码旧" → 拒绝;
   放行要显式 `--allow-dirty` / `--allow-stale`;`--check` 干跑只报结论不拦。
   实测干跑输出:`产物:gitRev=6702cc2 树=dirty releaseCandidate=false | 当前 HEAD=6702cc2`
   → 报"不是发布候选 + 正式安装会被拒绝 + 要放行请显式说清"。

2. **别解析运行器文本**(pi §5):`broken`/`red` 的判定改成按 TAP 的**名字**——
   文件级失败的测试名就是路径,断言失败的名字是判据名。变异双向验证:
   未定义标识符 → 「跑不起来的判据」;把某条判据条件改成假 → 「红的判据」。
   不再往关键字表里加补丁(那是往文本解析里加补丁,方向是错的)。

3. **静默 break 是安全相关**(pi §3):`session_update` 找不到活动会话时不再静默 break,
   改成出声日志(走 journalctl 那条通道),写清两种成因(此刻没在跑 / **接管会话**重启后无法定位)、
   方向(收紧被延迟)、以及兜底的**前提**("下次投递"要求这条会话还会收到新邮件)。
   `lib/mail-session-id.js` 模块头同步改成安全相关措辞("人以为自己收紧了权限、实际没有"),
   四桥逐字节同源,`check-shared-libs.sh` 退出码 0。

4. 内核读数补时间坐标(pi 13ea2fdf):`BUILD_INFO.txt` 里除原始 `dep`/`=>` 行外,
   现在还有 `kernelBinMtime` 与**正在运行的进程启动时间** —— 二进制会在两次读数之间被换掉,
   没有时间坐标的读数不成立。
2026-09-14 16:54:30 +08:00

135 lines
8.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.

#!/usr/bin/env bash
# 构建 homeagent-mail-bridge —— 用**明确指定的** SDK 源码,并记录"这次到底用了哪份代码"。
#
# # 这份脚本被改过一轮,因为它的第一版判错了根因(pi 2026-09-14 晚逐条反驳)
#
# 第一版的错:把 `go version -m <内核>` 里的
# dep ... homeagent-sdk v0.9.2 / => ./third_party/homeagent-sdk (devel)
# 当成"内核链的是 0.9.x 血脉",然后按候选根目录**第一个命中就算**去解析 SDK 源码,
# 命中的是 `/root/ha-test/third_party/homeagent-sdk`(C-ABI 时代的老 checkout,无 plugin.bin 支持)。
# 而本机共有 6+ 份同名 checkout,内核自己用的是 1.3.0 那份。两个教训:
#
# ① **不要用"第一个存在的路径"当权威来源** —— 猜根目录会命中不相关的 checkout,
# "两条血脉"于是成了脚本的产物,而不是事实;
# ② **不要把模块版本字符串当身份** —— `replace => local (devel)` 时那个版本号
# 只是 require 行的文本残留,不代表实际编译的代码。
#
# 所以现在:SDK 源码**只认显式指定**(`HOMEAGENT_SDK_DIR`)。
# 不猜、不试探、不"第一个命中"。要指定内核的构建目录就显式传进来。
#
# # 不变量是什么
#
# 不是"版本号相等"(那正是第一版想抛弃的东西),而是 **wire 协议 + 一次真实握手**:
# 产物要能被内核 `protocol=2` 认下来。这个脚本能做的是"把身份记清楚 + 把明显不对的挡下来",
# 真握手由内核启动时完成(所以部署后要回看一次日志,见末尾提示)。
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SDK_MODULE="gitcode.com/JianFeeeee/homeagent-sdk"
KERNEL_BIN="${HOMEAGENT_KERNEL_BIN:-/usr/local/bin/homed}"
die() { echo "构建失败:$*" >&2; exit 1; }
# cron/非登录 shell 里 HOME 可能没设,Go 会因此拒绝工作(曾经在这里栽过)
export HOME="${HOME:-/root}"
export GOPATH="${GOPATH:-$HOME/go}"
export GOCACHE="${GOCACHE:-${TMPDIR:-/tmp}/go-build-agentmail}"
mkdir -p "$GOCACHE"
# ── 1. SDK 源码:只认显式指定 ───────────────────────────────────────
[[ -n "${HOMEAGENT_SDK_DIR:-}" ]] || die "必须显式指定 SDK 源码树:HOMEAGENT_SDK_DIR=/path/to/homeagent-sdk
为什么不让脚本自己猜:本机有 6+ 份同名 checkout(有的是 C-ABI 时代的老代码),
第一版按候选根目录'第一个命中就算',命中了 /root/ha-test 那份老 checkout,
于是把'内核链老血脉'这个**错误结论**当成了事实写进注释。猜路径 = 制造事实。"
SDK_DIR="$(cd "$HOMEAGENT_SDK_DIR" && pwd)"
[[ -f "$SDK_DIR/go.mod" ]] || die "$SDK_DIR 不是 Go 模块(没有 go.mod)"
# 身份用 **realpath + 内容哈希**,不用版本字符串(版本号可能是 -ldflags 注入的默认值,
# 也可能是注释里的示例 —— 后者我在 electron 侧的判据里刚踩过一次)。
SDK_HASH="$(find "$SDK_DIR" -type f -name '*.go' -print0 2>/dev/null | sort -z | xargs -0 cat 2>/dev/null | sha256sum | cut -c1-16)"
SDK_VER="$(sed 's://.*::' "$SDK_DIR/meta/meta.go" 2>/dev/null | grep -m1 -oE 'Version = "[0-9]+\.[0-9]+\.[0-9]+"' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)"
echo "[build] SDK 源码:$SDK_DIR"
echo "[build] meta.Version(已剥注释)= ${SDK_VER:-未知} | 内容哈希 = ${SDK_HASH:-未知}"
# 内核签名的**提示**(不是判据):按模块名精确联接,不按"输出里第一个 =>"。
# "两张表各缺一半时必须按 id 联接"—— 读 go version -m 的输出同理。
if [[ -x "$KERNEL_BIN" ]]; then
KERNEL_INFO="$(go version -m "$KERNEL_BIN" 2>/dev/null || true)"
KERNEL_SDK_LINE="$(awk -v m="$SDK_MODULE" '$1=="dep" && $2==m {print NR": "$0}' <<<"$KERNEL_INFO" | head -1)"
if [[ -n "$KERNEL_SDK_LINE" ]]; then
KERNEL_SDK_DEP="$(cut -d' ' -f3 <<<"${KERNEL_SDK_LINE#*: }")"
# 二进制本身会变(实测:两次读数之间它被重编过),所以"现在链的是什么"必须带上
# (路径, mtime, 正在运行的进程启动时间) —— 否则那份读数没有时间坐标(pi 2026-09-14)。
KERNEL_MTIME="$(stat -c '%y' "$KERNEL_BIN" 2>/dev/null || echo '?')"
KERNEL_PID_START="$(for d in /proc/[0-9]*; do
c="$(tr '\0' ' ' < "$d/cmdline" 2>/dev/null || true)"
case "$c" in *homed*) stat -c 'pid=%n 启动=%y' "$d" 2>/dev/null; break;; esac
done)"
echo "[build] 内核二进制:$KERNEL_BIN mtime=$KERNEL_MTIME;运行中的进程:${KERNEL_PID_START:-(没在跑)}"
# 只接受**紧跟在 SDK 那一行之后**的 => 行(按位置配对的唯一正确写法)
KERNEL_SDK_REPL="$(awk -v m="$SDK_MODULE" '
$1=="dep" && $2==m {want=NR+1}
want && NR==want && $1=="=>" {print $2; exit}' <<<"$KERNEL_INFO")"
echo "[build] 内核 $KERNEL_BIN 的 dep 行:$KERNEL_SDK_DEP${KERNEL_SDK_REPL:+(本地替换 $KERNEL_SDK_REPL)}"
echo "[build] 注意:这条只是**提示**,不是判据 —— replace 到本地目录时这个版本号只是 require 行的残留,"
echo "[build] 而且它指向的相对路径未必是本机这份源码。要对齐请显式传 HOMEAGENT_SDK_DIR。"
fi
fi
# ── 2. 用临时 modfile 替换,不改动受版本管理的 go.mod ───────────────
cd "$HERE"
TMP_MOD="$(mktemp -d)/go.local.mod"
cp go.mod "$TMP_MOD"
go mod edit -modfile="$TMP_MOD" -replace "$SDK_MODULE=$SDK_DIR"
export GOFLAGS="${GOFLAGS:-} -modfile=$TMP_MOD"
# 注意:这个插件**没有** `func main()`(入口由 hmapdev 生成/包装),
# 所以 `go build ./...` 会报 "function main is undeclared" —— 那是正常的,
# 不能用它来数 API 兼容性错误;真正的编译在 hmapdev 那一步。
# 这条工具会**重写 plugin.json/plg.json**(实测:只把文件末尾的换行吃掉了)。
# 构建不该把源码树弄脏 —— 在 electron 那边我们刚为同一形状定过规矩
# (脏树产物不得自称发布候选)。所以:先记哈希,构建后若被改就还原并出声。
MANIFESTS=(plugin.json plg.json)
SNAP_DIR="$(mktemp -d)"
for f in "${MANIFESTS[@]}"; do [[ -f "$f" ]] && cp "$f" "$SNAP_DIR/$f"; done
hmapdev build || die "hmapdev build 失败(编译错误见上;它才是真正的编译入口)"
for f in "${MANIFESTS[@]}"; do
[[ -f "$SNAP_DIR/$f" ]] || continue
if ! cmp -s "$SNAP_DIR/$f" "$f"; then
cp "$SNAP_DIR/$f" "$f"
echo "[build] 注意:hmapdev 改写了 $f(已还原)—— 构建工具不该动受版本管理的源码;"
echo "[build] 否则每次构建都把树弄脏,而'脏树'在别的端已经是'不是发布候选'的判据了。"
fi
done
rm -rf "$SNAP_DIR"
# ── 3. 产物自报身份 ─────────────────────────────────────────────────
BIN="build/plugin.bin"
[[ -f "$BIN" ]] || die "没找到产物 $BIN"
echo "[build] 产物:$BIN($(stat -c%s "$BIN") 字节)"
echo "[build] 本次身份:SDK=$SDK_DIR hash=$SDK_HASH version=${SDK_VER:-未知}"
# 原始证据与结论放在一起(pi 2026-09-14):这场争论的全部内容就是
# "`dep` 行与 `=>` 行该怎么读",把两行原文贴进来,下一个人不必重演。
cat > build/BUILD_INFO.txt <<EOF
sdkDir=$SDK_DIR
sdkHash=$SDK_HASH
sdkMetaVersion=${SDK_VER:-unknown}
builtAt=$(date -Iseconds)
kernelBin=$KERNEL_BIN
kernelBinMtime=${KERNEL_MTIME:-?}
kernelRunningProcess=${KERNEL_PID_START:-none}
# --- 以下是 \`go version -m $KERNEL_BIN\` 的原文(原样,不改写)---
${KERNEL_INFO:-(取不到:$KERNEL_BIN 不可读或 go 不在 PATH)}
# 怎么读这两行(这一轮踩过的坑):
# · \`dep <mod> vX.Y.Z\` 后面若跟着 \`=> <dir> (devel)\`,那个 vX.Y.Z 只是 require 行
# 的**文本残留**,不代表实际编译用的代码;本次的 SDK 源码以上面的 sdkDir/sdkHash 为准。
# · \`=>\` 必须按**模块名联接**读取(先定位 SDK 的 dep 行,只接受紧跟其后那条),
# 不能取"整份输出里第一个 =>"。
EOF
echo "[build] 已写 build/BUILD_INFO.txt —— 部署后请回看内核日志确认握手:"
echo " grep -a 'mail-bridge' /home/newqqagent/log/homed_*.log | tail -5"
echo " (判据不是版本号相等,而是 protocol=2 建链成功、且没有崩溃重启循环)"