deploy: 标准目录部署 —— 运行时不再依赖源码目录

用户注意到:「当前 agentmail 是在源码目录部署的,应当改为标准目录部署」。
查证后有三处实证(都不是猜测):

1. ★ **失败通知钩子执行的是仓库里的脚本**
   (`/home/program/agentmail/deploy/service-failure-notify.mjs`,8 处引用:
   4 个 drop-in + zcode/zcode-mail-bridge 单元 + agentmail-failure-flush)。
   仓库一挪/一改名,故障通知就**静默失效** —— 而那条管线正是用来报告服务故障的。
2. **opencode-serve 的 cwd 就是源码目录**(`WorkingDirectory=/home/program/agentmail`)。
3. ★ **仓库里的 `deploy/*.service` 是旧的源码目录版本**(ExecStart 指向
   `/home/program/agentmail/plugins/...`),而机器上的已被改过 —— 也就是说
   **谁跑一次 install.sh 就会把部署退回源码目录**。drop-in 更是只存在于 /etc 里,
   仓库完全没有它们。

## 改动

- **唯一真相**:`deploy/systemd/` 镜像 systemd 目录结构,收进全部单元与 drop-in
  (8 个单元 + 12 个 drop-in),路径全部改到 `/opt`。
- 运行时脚本装到 **`/opt/agentmail/bin/service-failure-notify.mjs`**(自包含,
  无相对导入);`install.sh` 与 `redeploy-gateway.sh` 都会幂等地装它。
- opencode 的 cwd 改为 `/opt/agentmail`(与网关一致),已重启生效
  (`/proc/<pid>/cwd` 已核)。
- 删掉 `deploy/*.service` 的旧副本,避免两个真相。
- dsh 的 `cordis.patch.yml` 注释里的安装示例也改到标准位置(运行时用的是环境变量,
  那条注释是唯一残留)。

## 判据(`deploy/check-deploy-drift.mjs` 新增「标准目录部署」四条 + 自检)

① 任何 unit/drop-in 都不得引用源码目录;② 已安装单元与 `deploy/systemd/` 逐字节一致;
③ 通知脚本在标准位置且可执行;④ 各服务的 cwd/ExecStart 不在源码目录
(homeagent/dsh/zcode 是**别的产品**的标准位置,按白名单放行)。
自检两个样本:引用源码目录的必须红、干净样本必须绿(证明不是恒真)。

顺带修掉一处**真漂移**:仓库里 dsh 的 `dist/index.js` 落后于部署件(改了 src 没重建),
重建后 `check-deploy-drift` 报「四个宿主都在跑当前代码」。

## 复核

- `/etc/systemd/system/` 引用仓库:**0** 个文件;`/opt/agentmail` 下只剩旧二进制/备份里
  的构建路径(Go 嵌的源码路径,无害)与一条注释。
- 四个宿主都在跑当前代码;标准目录四项全绿。
- 全部服务 active,opencode/网关 cwd 均已在安装根下。
This commit is contained in:
2026-09-14 08:38:33 +08:00
parent 5b6fef764f
commit 51789ee72e
27 changed files with 450 additions and 40 deletions

View File

@ -0,0 +1,14 @@
[Service]
# 清掉主单元的 DBUS_SESSION_BUS_ADDRESS=disabledElectron 单实例 / 深链转发依赖 DBus
#
# 本文件**只覆盖这一个环境变量**。启动命令ExecStart已含 dbus-run-session 包装)
# 由主单元 /etc/systemd/system/zcode.service 唯一定义。
#
# 历史教训:本文件曾经写成 `ExecStart=` 清空 + 整条重写来加 dbus-run-session 包装。
# 那样做的两个问题:
# 1. 它**静默盖住**主单元的 ExecStart —— 以后改主单元的启动参数不会生效,
# 且看 `systemctl cat` 才能发现(排查时极易误判)
# 2. 主单元那条 ExecStart 原本是「反斜杠续行」的两行,抄过来时续行被压平成空格,
# 一度出现过 `xvfb-run: 200: 0: not found` 的崩溃循环
# 所以包装归主单元drop-in 只管它名字里那件事dbus 环境变量)。
Environment=DBUS_SESSION_BUS_ADDRESS=