起因是用户让「清理一下」那批带仓库路径的残留。照着清理策略走时撞上更大的事实:
本机 /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/ 文件。)
332 lines
16 KiB
Bash
Executable File
332 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# 把 AgentMail Gateway 与各平台的 mail-bridge 安装为 systemd 服务。
|
||
#
|
||
# sudo ./deploy/install.sh
|
||
#
|
||
# 幂等:重复执行等价于「重新构建 + 重启」。已存在的 env 文件不会被覆盖,
|
||
# 因为里面有管理员密码与 Agent secret,重装不该把它们冲掉。
|
||
set -euo pipefail
|
||
|
||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
PREFIX=/opt/agentmail
|
||
ETC=/etc/agentmail
|
||
|
||
#
|
||
# ./deploy/install.sh --check # 干跑:只跑所有门禁与前置检查,**不写系统目录**
|
||
#
|
||
# 为什么要有 --check(pi 2026-09-14):发布路径是**最少被跑的门** ——
|
||
# `check-shared-libs.sh` 一直红着没人发现,正是因为它只挂在 install.sh 里。
|
||
# 而"最后一个能跑这一步的人"(有 root 的那个人)每次都在**改完一堆东西之后**才第一次跑它,
|
||
# 于是第一次失败就发生在最贵的时候。干跑把"门自己跑得动吗"提前暴露出来。
|
||
CHECK_ONLY=0
|
||
ALLOW_DIRTY=0
|
||
ALLOW_STALE=0
|
||
for a in "$@"; do
|
||
case "$a" in
|
||
--check) CHECK_ONLY=1 ;;
|
||
--allow-dirty) ALLOW_DIRTY=1 ;;
|
||
--allow-stale) ALLOW_STALE=1 ;;
|
||
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
||
*) echo "未知参数:$a(支持 --check / --allow-dirty / --allow-stale)" >&2; exit 2 ;;
|
||
esac
|
||
done
|
||
|
||
if [[ $CHECK_ONLY -eq 0 ]]; then
|
||
[[ $EUID -eq 0 ]] || { echo "需要 root:sudo $0(只验证不安装用 ./deploy/install.sh --check)" >&2; exit 1; }
|
||
else
|
||
echo "==> 干跑模式(--check):不会写 $PREFIX 或 $ETC"
|
||
# 写权限是这一步唯一的硬门槛,先说清楚 —— 下一个拿到权限的人要一眼看到需要什么
|
||
for d in "$PREFIX" "$ETC"; do
|
||
if [[ -e "$d" && ! -w "$d" ]]; then
|
||
echo " 注意:$d 存在但当前用户不可写(正式安装需要 root/sudo 或先放行该目录)"
|
||
fi
|
||
done
|
||
fi
|
||
|
||
echo "==> 构建前端"
|
||
# 干跑不装依赖(装依赖要写 node_modules/缓存,干跑的意义是"门能不能跑",不是"装得上装不上")。
|
||
if [[ $CHECK_ONLY -eq 1 ]]; then
|
||
[[ -d "$REPO/client/electron/node_modules" ]] || {
|
||
echo " 干跑需要一个已装好依赖的工作树(client/electron/node_modules 不存在)" >&2; exit 1; }
|
||
echo " 跳过 npm ci/install(干跑不写依赖);用的是当前 node_modules"
|
||
else
|
||
( cd "$REPO/client/electron" && npm ci --no-audit --no-fund 2>/dev/null || npm install --no-audit --no-fund )
|
||
fi
|
||
# 相位(pi 2026-09-14 裁定):部署门禁问的是"**别人已经产出的产物**能不能装到这台机器上",
|
||
# 不是"产物是不是从当前源码新鲜构建的"。后者属于构建相位(`packaging`/`build-stamp`),
|
||
# 挂在这里会**恒红**(部署路径不重新打包)—— 门挂错了相位。所以这里显式声明相位。
|
||
( cd "$REPO/client/electron" && npm run typecheck && AGENTMAIL_CRITERIA_PHASE=install npm test && npm run build )
|
||
|
||
# ── 安装相位真正能判的那一半:**读产物自证**(不是重算 dist)──
|
||
# 同一个不变量("这份产物是谁")在两个相位要两种判据:构建相位重新计算,安装相位读它自己说的。
|
||
if [[ -f "$REPO/client/electron/dist/BUILD_INFO.json" ]]; then
|
||
echo "==> 产物自证(安装相位)"
|
||
read -r A_REV A_DIRTY A_RC < <(node -e '
|
||
const d = require("'"$REPO"'/client/electron/dist/BUILD_INFO.json");
|
||
console.log([d.gitRev ?? "?", d.gitDirty ? "dirty" : "clean", d.releaseCandidate === undefined ? "?" : String(d.releaseCandidate)].join(" "));
|
||
')
|
||
HEAD_REV="$(git -C "$REPO" rev-parse --short HEAD)"
|
||
echo " 产物:gitRev=$A_REV 树=$A_DIRTY releaseCandidate=$A_RC | 当前 HEAD=$HEAD_REV"
|
||
refuse=0
|
||
if [[ "$A_RC" == "false" ]]; then
|
||
echo " ✗ 这个包**不是发布候选**(构建时工作树是脏的)—— 它可能含着别人未提交的半成品。" >&2
|
||
refuse=1
|
||
fi
|
||
if [[ "$A_REV" != "$HEAD_REV" ]]; then
|
||
echo " ✗ 这个包比源码旧:产物 gitRev=$A_REV ≠ HEAD=$HEAD_REV。" >&2
|
||
refuse=1
|
||
fi
|
||
if [[ $refuse -eq 1 ]]; then
|
||
[[ $A_RC == "false" && $ALLOW_DIRTY -eq 1 ]] || [[ $A_REV != "$HEAD_REV" && $ALLOW_STALE -eq 1 ]] || true
|
||
if [[ $CHECK_ONLY -eq 1 ]]; then
|
||
echo " (干跑:正式安装会被拒绝。要放行就显式说清:--allow-dirty / --allow-stale)" >&2
|
||
elif { [[ "$A_RC" != "false" || $ALLOW_DIRTY -eq 1 ]] && [[ "$A_REV" == "$HEAD_REV" || $ALLOW_STALE -eq 1 ]]; }; then
|
||
echo " (你显式放行了:--allow-dirty/--allow-stale —— 风险由你承担)" >&2
|
||
else
|
||
echo " 拒绝安装。放行要显式:sudo $0 --allow-dirty --allow-stale" >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
else
|
||
echo "==> 产物自证:dist/BUILD_INFO.json 不存在(还没构建过)—— 本相位只能报"不知道",不能报"没问题"" >&2
|
||
fi
|
||
|
||
echo "==> 校验插件共用模块同源"
|
||
# lib/ 下的纯函数模块在三个插件里逐字节相同(见 docs/PLUGIN-CONTRACT.md 第六节)。
|
||
# 一侧改了另一侧没改,几个平台的行为就会悄悄分叉。
|
||
"$REPO/deploy/check-shared-libs.sh"
|
||
|
||
# 插件的纯函数测试(自动转发去重等)。
|
||
# 插件不参与构建产物,但它的逆行为会直接变成用户收件箱里的重复邮件,
|
||
# 因此也纳入部署前的门禁。zod 已在 node_modules 里就不重装。
|
||
if [[ -d "$REPO/plugins/opencode-mail-bridge/node_modules/zod" ]]; then
|
||
( cd "$REPO/plugins/opencode-mail-bridge" && npm test )
|
||
else
|
||
( cd "$REPO/plugins/opencode-mail-bridge" && npm install --no-audit --no-fund && npm test )
|
||
fi
|
||
# DSH 插件:先构建再跑测试。
|
||
#
|
||
# 它是 TypeScript 写的,package.json 的 main 指向 dist/index.js,而 dist/ 不进版本库
|
||
# (与 client/electron/dist 同理)—— 新克隆里不先 tsc,DSH 加载插件时会直接找不到入口。
|
||
# 测试本身只碰 lib/ 下的纯函数(不依赖 dist),但先构建能把类型错误也当成门禁。
|
||
#
|
||
# 盖住的三类约定都是「错了不当场报错、只在深处炸一个无关错误」:
|
||
# followup 消息形状、工作目录解析、会话快照的 subagent 过滤。
|
||
if [[ -d "$REPO/plugins/dsh-mail-bridge/node_modules/typescript" ]]; then
|
||
( cd "$REPO/plugins/dsh-mail-bridge" && npx tsc && npm test )
|
||
else
|
||
( cd "$REPO/plugins/dsh-mail-bridge" \
|
||
&& npm install --no-audit --no-fund && npx tsc && npm test )
|
||
fi
|
||
# pi 插件:纯 ESM,无构建步骤,唯一的外部依赖是全局装的 pi SDK。
|
||
#
|
||
# 它不 npm install:@earendil-works/pi-coding-agent 是全局包(peerDependency),
|
||
# 装在项目里会得到第二份 SDK,两份各自维护 ~/.pi/agent 的会话索引缓存。
|
||
# 因此这里只软链一次;SDK 不在时跳过测试(turn/naming 是纯函数,
|
||
# 但 lib 的测试也一起跑,没必要为缺 SDK 的机器留半套门禁)。
|
||
PI_SDK=/usr/lib/node_modules/@earendil-works/pi-coding-agent
|
||
if [[ -d "$PI_SDK" ]]; then
|
||
install -d "$REPO/plugins/pi-mail-bridge/node_modules/@earendil-works"
|
||
ln -sfn "$PI_SDK" "$REPO/plugins/pi-mail-bridge/node_modules/@earendil-works/pi-coding-agent"
|
||
( cd "$REPO/plugins/pi-mail-bridge" && npm test )
|
||
else
|
||
echo " 未找到 pi SDK($PI_SDK),跳过 pi 插件测试"
|
||
fi
|
||
|
||
# pi 邮件工具扩展:给**交互式**会话装上 send_mail / read_inbox。
|
||
#
|
||
# 守护进程(pi-mail-bridge.service)用 noExtensions:true 起会话,它的邮件工具
|
||
# 只给模型在邮件会话里用;人在 TUI 里敲的 pi 拿不到。结果是平台的建设者自己
|
||
# 收不到邮件,只能绕到 curl + 密钥直连 Gateway。这个扩展补上那一侧 ——
|
||
# TUI 与邮箱是同一条 AgentMail 身份(agent pi)的两个入口。
|
||
#
|
||
# 为什么不会重复注册:noExtensions:true 只加载 CLI `-e` 传的扩展,
|
||
# settings.json 的 extensions 数组会被排除(见 SDK resource-loader.js),
|
||
# 因此守护进程的 worker 里零扩展、本扩展只作用于交互式会话。
|
||
if [[ -d "$PI_SDK" ]]; then
|
||
# `set -u` 下 `$HOME` 未设会直接杀掉部署(实测:`HOME: unbound variable`,
|
||
# 而它发生在所有门禁跑完之后 —— 最贵的位置)。cron / env -i / 某些 sudo 配置下
|
||
# HOME 就是没有的,所以给一个兜底。同一形状我这轮在 homeagent 的 build.sh 里
|
||
# 也修过一次(cron 里 HOME 未设 → Go 拒绝工作)——**这是这轮第三次**,
|
||
# 说明"环境变量在部署路径上不存在"这条要当成常态而不是意外。
|
||
PI_HOME="${SUDO_USER:+$(getent passwd "$SUDO_USER" | cut -d: -f6)}"
|
||
PI_HOME="${PI_HOME:-${HOME:-/root}}"
|
||
PI_SETTINGS="$PI_HOME/.pi/agent/settings.json"
|
||
EXT_PATH="$REPO/plugins/pi-mail-bridge/extension/index.ts"
|
||
if [[ $CHECK_ONLY -eq 1 ]]; then
|
||
echo " [干跑] 跳过:把邮件工具扩展注册进 $PI_SETTINGS(要写工作区外的 home 目录)"
|
||
elif [[ -d "$PI_HOME/.pi/agent" || -f "$PI_SETTINGS" ]]; then
|
||
install -d "$(dirname "$PI_SETTINGS")"
|
||
node -e '
|
||
const fs = require("fs");
|
||
const [path, target] = process.argv.slice(1);
|
||
let cfg = {};
|
||
try { cfg = JSON.parse(fs.readFileSync(path, "utf8")); } catch { /* 新建 */ }
|
||
const list = Array.isArray(cfg.extensions) ? cfg.extensions : [];
|
||
if (list.includes(target)) { console.log(" 已注册,幂等跳过"); process.exit(0); }
|
||
list.push(target);
|
||
cfg.extensions = list;
|
||
fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n");
|
||
console.log(" 已注册 " + target);
|
||
' "$PI_SETTINGS" "$EXT_PATH"
|
||
else
|
||
echo " 未找到 $PI_HOME/.pi/agent,跳过(手动在 settings.json 加 extensions: [\"$EXT_PATH\"])"
|
||
fi
|
||
fi
|
||
|
||
if [[ $CHECK_ONLY -eq 1 ]]; then
|
||
cat <<'EOF'
|
||
|
||
==> 干跑结束:**所有会红的门禁都跑过了**(前端 typecheck/test/build、共用模块同源、
|
||
各插件测试、插件构建)。干跑的成功只能说明"门是好的",不能说明"装得上"。
|
||
|
||
下面这些步骤干跑**没有执行**(它们要写工作区外的目录,正式安装需要 root 或先放行):
|
||
· 前端产物 → server/internal/static/static/
|
||
· 构建 Gateway(go vet + go test + go build -trimpath -o server/agentmail-gateway)
|
||
· install -m 0755 server/agentmail-gateway → /opt/agentmail/agentmail-gateway
|
||
· 生成 /etc/agentmail/*.env(已存在的不覆盖)+ chmod 0600
|
||
· 装 systemd unit → /etc/systemd/system/,daemon-reload + enable --now
|
||
· 把邮件工具扩展注册进 ~/.pi/agent/settings.json
|
||
|
||
拿不准就先跑 ./deploy/install.sh --check;正式安装用 sudo ./deploy/install.sh。
|
||
EOF
|
||
exit 0
|
||
fi
|
||
|
||
echo "==> 前端产物嵌入 Gateway"
|
||
# 只清构建产物,不能 rm -rf 整个目录:
|
||
# placeholder.html 在版本库里(让 go:embed 在新克隆里能编译),
|
||
# 删掉它会让 git 看到一个本地删除,下一次 commit -a 就把它从仓库里带走了。
|
||
rm -rf "$REPO/server/internal/static/static/assets"
|
||
rm -f "$REPO/server/internal/static/static/index.html"
|
||
cp -r "$REPO/client/electron/dist/." "$REPO/server/internal/static/static/"
|
||
|
||
echo "==> 构建 Gateway(单二进制,内含前端 + SQLite)"
|
||
# 先删再建:go build -o 到已存在的路径时可能拿到 stale 二进制(此坑中过多次)
|
||
# `-trimpath` 与 redeploy-gateway.sh 同源:不带它时 Go 会把源文件绝对路径编进
|
||
# 生产二进制(2026-09-14 实测 57 处 /home/program/agentmail/…)。
|
||
rm -f "$REPO/server/agentmail-gateway"
|
||
( cd "$REPO/server" && go vet ./... && go test ./... && go build -trimpath -o "$REPO/server/agentmail-gateway" ./cmd/server )
|
||
|
||
echo "==> 安装到 $PREFIX"
|
||
install -d "$PREFIX" "$PREFIX/data" "$ETC"
|
||
install -m 0755 "$REPO/server/agentmail-gateway" "$PREFIX/agentmail-gateway"
|
||
|
||
# ---- env 文件:仅在缺失时生成,密码随机 ----
|
||
if [[ ! -f "$ETC/gateway.env" ]]; then
|
||
ADMIN_PASS="$(head -c 18 /dev/urandom | base64 | tr -d '/+=' | head -c 20)"
|
||
cat > "$ETC/gateway.env" <<EOF
|
||
# AgentMail Gateway 环境变量
|
||
ADMIN_USER=admin
|
||
ADMIN_PASSWORD=$ADMIN_PASS
|
||
|
||
# 生产环境走 HTTPS 时置 true,Cookie 才会带 Secure 标记
|
||
SECURE_COOKIE=false
|
||
|
||
# 允许的前端跨域来源(逗号分隔);单二进制自带前端时通常无需配置
|
||
# CORS_ORIGINS=https://mail.example.com
|
||
EOF
|
||
chmod 0600 "$ETC/gateway.env"
|
||
echo " 已生成 $ETC/gateway.env(管理员 admin / $ADMIN_PASS)"
|
||
else
|
||
echo " $ETC/gateway.env 已存在,保留不动"
|
||
fi
|
||
|
||
if [[ ! -f "$ETC/opencode.env" ]]; then
|
||
cat > "$ETC/opencode.env" <<EOF
|
||
# opencode mail-bridge 插件配置
|
||
AGENTMAIL_GATEWAY_URL=http://127.0.0.1:8180
|
||
AGENTMAIL_AGENT_NAME=opencode
|
||
|
||
# 接入密钥:留空时插件会在 \$AGENTMAIL_CONFIG_DIR/agent.key 本地生成一把并打印到日志,
|
||
# 拿着它到 Web 后台「Agent 密钥」登记即可接入(journalctl -u opencode-serve | grep mail-bridge)。
|
||
# 也可以先在后台签发密钥,再把它填在这里。
|
||
AGENTMAIL_AGENT_KEY=
|
||
AGENTMAIL_CONFIG_DIR=/opt/agentmail/agent-config
|
||
|
||
# 处理来信时使用的模型
|
||
AGENTMAIL_REPLY_PROVIDER=llmsproxy
|
||
AGENTMAIL_REPLY_MODEL=AUTO
|
||
|
||
# opencode serve 绑在 127.0.0.1,但同机任何进程都能调它开会话,
|
||
# 因此仍然设置访问密码。
|
||
OPENCODE_SERVER_PASSWORD=$(head -c 18 /dev/urandom | base64 | tr -d '/+=' | head -c 24)
|
||
EOF
|
||
chmod 0600 "$ETC/opencode.env"
|
||
install -d -m 0700 "$PREFIX/agent-config"
|
||
echo " 已生成 $ETC/opencode.env(server password 为随机值;接入密钥首启时本地生成)"
|
||
else
|
||
echo " $ETC/opencode.env 已存在,保留不动"
|
||
fi
|
||
|
||
if [[ ! -f "$ETC/pi.env" ]]; then
|
||
cat > "$ETC/pi.env" <<EOF
|
||
# pi mail-bridge 插件配置
|
||
AGENTMAIL_GATEWAY_URL=http://127.0.0.1:8180
|
||
AGENTMAIL_AGENT_NAME=pi
|
||
|
||
# 接入密钥:留空时插件会在 \$AGENTMAIL_CONFIG_DIR/agent.key 本地生成一把并打印到日志,
|
||
# 拿着它到 Web 后台「Agent 密钥」登记即可接入(journalctl -u pi-mail-bridge | grep 密钥)。
|
||
AGENTMAIL_AGENT_KEY=
|
||
AGENTMAIL_CONFIG_DIR=/opt/agentmail/pi-config
|
||
|
||
# 处理来信时首选的模型。留空表示用 pi 自己的默认(~/.pi/agent/settings.json)。
|
||
# 管理员在后台划定模型范围时,范围优先于这里的设置(B-2.2)。
|
||
AGENTMAIL_REPLY_PROVIDER=llmsproxy
|
||
AGENTMAIL_REPLY_MODEL=AUTO
|
||
|
||
# 单轮上限。超时**不算失败**:pi 仍在后台跑,转发挂在 agent_end 上,
|
||
# 说完了自然会转出去。这个值只决定「多久之后不再阻塞投递流水线」。
|
||
AGENTMAIL_TURN_TIMEOUT_MS=60000
|
||
EOF
|
||
chmod 0600 "$ETC/pi.env"
|
||
install -d -m 0700 "$PREFIX/pi-config"
|
||
echo " 已生成 $ETC/pi.env(接入密钥首启时本地生成)"
|
||
else
|
||
echo " $ETC/pi.env 已存在,保留不动"
|
||
fi
|
||
|
||
echo "==> 安装 systemd 单元"
|
||
# 单元与 drop-in 的**唯一真相**在 deploy/systemd/(镜像 systemd 的目录结构)。
|
||
#
|
||
# 以前这里逐个 install 三个 .service,而 drop-in(失败通知/重启退避等)只存在于
|
||
# /etc 里 —— 于是"仓库里的是旧的、机器上的是新的",谁跑一次本脚本就把部署退回
|
||
# 源码目录(旧版 ExecStart 指向 /home/program/agentmail/plugins/...)。
|
||
install -d /opt/agentmail/bin
|
||
install -m 0755 "$REPO/deploy/service-failure-notify.mjs" /opt/agentmail/bin/service-failure-notify.mjs
|
||
while IFS= read -r f; do
|
||
rel="${f#"$REPO/deploy/systemd/"}"
|
||
install -D -m 0644 "$f" "/etc/systemd/system/$rel"
|
||
done < <(find "$REPO/deploy/systemd" -type f)
|
||
systemctl daemon-reload
|
||
|
||
echo "==> 启用并启动"
|
||
systemctl enable --now agentmail-gateway.service
|
||
if command -v opencode >/dev/null 2>&1; then
|
||
systemctl enable --now opencode-serve.service
|
||
else
|
||
echo " 未找到 opencode,跳过 opencode-serve(装好后执行:systemctl enable --now opencode-serve)"
|
||
fi
|
||
if [[ -d "$PI_SDK" ]]; then
|
||
systemctl enable --now pi-mail-bridge.service
|
||
else
|
||
echo " 未找到 pi SDK,跳过 pi-mail-bridge(装好后执行:systemctl enable --now pi-mail-bridge)"
|
||
fi
|
||
|
||
sleep 3
|
||
echo
|
||
echo "==> 状态"
|
||
systemctl --no-pager --lines=0 status agentmail-gateway.service || true
|
||
curl -sf -m 5 http://127.0.0.1:8180/health && echo " 健康检查通过" || echo " 健康检查失败,查看:journalctl -u agentmail-gateway -n 50"
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 日常只改后端时不需要重跑本脚本(它会重装 npm 依赖、重写 systemd 单元、
|
||
# 重新生成 env)。换二进制走:
|
||
#
|
||
# bash deploy/redeploy-gateway.sh # 含测试与后置验证
|
||
# bash deploy/redeploy-gateway.sh --dry-run # 先看要做什么
|
||
#
|
||
# 那个脚本用 sqlite3 .backup 备份数据库、install -m 0755 原子替换二进制,
|
||
# 并在验证不过时自动回滚。
|