Files
HomeAgent/docs/zh/deploy-runbook.md
JianFeeeee 175fa9dd62 docs: 新增生产部署手册(homed / waiter)
`site-infra-runbook.md` 是**静态站 / nginx / 证书**的手册,不含 homed 与
waiter —— 两次生产部署(19:45 首次、21:50 修复)因此只存在于提交信息里,
查不到。

## 内容

**§1 homed**
- 硬前置:必须 `-tags=onnxruntime`(普通 build 只有 ~28MB,缺 ONNX Runtime)
- ★ **构建参数必须与线上一致**:不要顺手加 `-s -w`。加了产物从 86.8MB 掉到
  78MB,8.8MB 的差会让人误判成"构建坏了",而它只是被 strip 了
- `check` / `deploy` / `rollback` 三条命令与备份位置
- 部署后必核:`multimodal space active: provider=chineseclip` 才是 ONNX
  真加载起来的标志,缺它说明已降级但**不报错**
- ★ 适配器升级的保护语义(`.bundled` 三种情形的判定表),并记 21:50 那次
  实测:手工补过 `stream_index` 的 `openai.lua` 被正确判定为用户修改并保留
- 两次部署的真实数据对照表

**§2 waiter**
- 逐台更新、不可并行(两台连同一网关,同时重启会同时断链)
- 部署后确认 `device waiter-* online`
- 记 2026-09-27 顺手解决的悬案:106 此前无 `online` 而 30 正常,两台配置与
  token 完全相同 ⇒ 差异只可能在旧二进制,8月27日那版落在"未 bind 时收到
  ping 会关连接"的缺陷窗口
- `device_cmd_allowlist` 的替换语义、生效验证、幂等追加方法
- ★ 明确写**白名单只匹配命令名、不看参数**,`find -delete`/`sed -i` 仍能逃
  ⇒ **不要称它为"只读白名单"**

**§3 故障排查**:按"消息没反应 / 命令被拒 / 适配器异常 / 告警是否缺陷"
四条线各给命令;特别标注"群聊 not @bot"与"私聊没回"是两件事

**§4 已知未修**:三项目前是已知限制而非疏漏

文中数字均与现场核对:脚本子命令确实存在(`check`/`deploy`/`rollback`)、
生产白名单确为 22 条、两次二进制大小取自实际部署。
2026-09-27 22:25:58 +08:00

11 KiB
Raw Blame History

HomeAgent 生产部署手册(homed / waiter)

适用范围:本机(.60)的 homeagent.service,以及两台设备桥宿主上的 waiter-remote.service。

静态站 / nginx / 证书的运维见另册 site-infra-runbook.md —— 本册不涉及。

记录日期:2026-09-27。本册所有数字均与现场核对过,不是模板值。


0. 一句话拓扑

                    ┌─────────────────── 本机 .60 ───────────────────┐
                    │ homeagent.service ← /usr/local/bin/homed      │
                    │   (必须 -tags=onnxruntime 构建)              │
  QQ 用户 ─NapCat─► │   :9890  remotedevice 网关                    │
   (在 106 上)    │   /home/newqqagent/  51G 模型资产(部署不动)   │
                    └────────┬──────────────────────┬────────────────┘
                             │ ws 设备桥             │ ws 设备桥
                 ┌───────────▼──────────┐  ┌────────▼─────────┐
                 │ 192.168.2.106 fnnas  │  │ 192.168.2.30     │
                 │ /opt/waiter/waiter   │  │  mainnas         │
                 │ waiter-remote.service│  │ /opt/waiter/...  │
                 │ + NapCat(25570)      │  │                  │
                 └──────────────────────┘  └──────────────────┘

要点:

  • waiter 与 homed 是两个独立部署单元,更新其一不影响其二。
  • 设备桥授权(device_authorized)在 waiter 侧,网关地址也在 waiter 的 waiter.yaml 里。
  • NapCat(QQ 上游)在 106 上,http://192.168.2.106:25570。

1. 部署 homed(本机)

1.1 硬前置:必须 onnxruntime 构建

普通 go build 只有 ~28MB,缺 ONNX Runtime,会让依存句法分析与多模态 向量化失效。生产二进制是 -tags=onnxruntime(约 87MB)。

deploy/packaging/package-linux.sh:139 会显式拒绝非 onnxruntime 构建。

1.2 ★ 构建参数必须与线上一致

CGO_ENABLED=1 CC=cc go build -tags onnxruntime -o /tmp/homed-ort-new \
  -ldflags "-X .../internal/meta.Version=<v> -X .../internal/meta.Commit=<c>" \
  ./cmd/homed

不要顺手加 -s -w。 加了会 strip 掉符号,产物从 ~86.8MB 掉到 ~78MB —— 体积差 8.8MB 会让人误判成"构建坏了",而它只是被 strip 了。 若确实要 strip,须先确认线上也是同样参数,否则两次构建不可比。

验证:

go version -m /tmp/homed-ort-new | grep onnxruntime   # 必须有 build -tags=onnxruntime
ls -l /tmp/homed-ort-new                             # 与 /usr/local/bin/homed 同量级

1.3 部署

bash deploy-plan.sh check                             # 只读
NEW_BIN=/tmp/homed-ort-new bash deploy-plan.sh deploy # 需输入 yes
bash deploy-plan.sh rollback                          # 回滚
  • check:服务状态、onnxruntime 标签、libonnxruntime.so、模型资产、适配器清单
  • deploy:备份 → 替换二进制 → 重启 → 验证
  • 备份落在 /var/tmp/homed-backup-<时间戳>/,含 ROLLBACK.sh、旧二进制、 适配器、unit 文件
  • 默认候选 /tmp/homed-ort,可用 NEW_BIN= 覆盖

1.4 部署后必须核对

systemctl is-active homeagent.service
journalctl -u homeagent.service --since "-3 min" | grep "multimodal space active"
journalctl -u homeagent.service --since "-3 min" | grep -c "registering tool: seq_"  # 应为 7

multimodal space active: provider=chineseclip dim=512 是 ONNX 链路真的 加载起来的标志 —— 缺它说明已降级,只是没报错。

1.5 ★ 适配器升级的保护语义

/home/newqqagent/adapters/.bundled 记录上次随包带出的版本哈希:

盘上版本 判定 行为
无 .bundled(首次升级) 未知 只补缺失文件,不动已有文件
盘上 == 旧内嵌 未被改过 自动覆盖为新版本
盘上 != 旧内嵌 用户改过 保留用户版本

2026-09-27 21:50 实测:生产 openai.lua 是 8月26日手工补过 stream_index 的版本(盘上 1a649be2… ≠ 旧内嵌 6374c596…),被正确判定为用户修改并保留。

验证方式(部署前后各跑一次,应完全一致):

md5sum /home/newqqagent/adapters/*.lua | md5sum

1.6 两次部署的真实记录(2026-09-27)

19:45 首次 21:50 修复后
二进制 86,496,624 → 86,784,400 86,784,400 → 86,811,464
onnxruntime ✓ ✓
seq_* 工具 0 → 7 7
适配器 无 .bundled ⇒ 一律不动 有清单 ⇒ 保护用户修改
备份 homed-backup-20260927-194554 homed-backup-20260927-215026
has empty arguments 误报 24 次(仍在增长) 0 次

首次部署前生产二进制构建于当天 06:36,而 seq 插件引入于更晚的提交 ⇒ 旧实例的 strings /usr/local/bin/homed | grep -c internal/plugins/seq 为 0。 它在 QQ 上如实回答"没有编排工具"不是说谎,是确实没有。

这类"实例自述与代码状态不一致",先查二进制构建时间与内含符号, 别急着怀疑提示词或模型。


2. 部署 waiter(设备桥,106 / 30)

2.1 现状

两台配置相同(同一 device_gateway、同一 device_token、device_authorized: true), 差异只在登录方式:106 走 admin@ + sudo,30 走 root@。

2.2 更新

bash deploy-waiter.sh check                # 两台一起,只读
bash deploy-waiter.sh deploy 192.168.2.106 # 需输入 yes
bash deploy-waiter.sh deploy 192.168.2.30  # 上一台验证通过后再做
bash deploy-waiter.sh rollback <ip>

逐台更新,不要并行 —— 两台都连同一网关,同时重启会同时断链。

deploy 会:备份 /opt/waiter/waiter → 替换 → 重启服务 → 验证 (服务 active、进程时长、配置 md5 未变)。

2.3 部署后确认设备已注册

journalctl -u homeagent.service --since "-2 min" | grep "device waiter-"
# 期望:device waiter-fnnas online / device waiter-mainnas online

2026-09-27 顺手解决的一个悬案:106 此前一直没有 online 日志而 30 正常, 两台配置与 token 完全相同 ⇒ 差异只可能在旧 waiter 二进制。8月27日那版落在 "未 bind 时收到 ping 会关连接"的缺陷窗口里,更新后即正常。

2.4 命令白名单(device_cmd_allowlist)

waiter 的命令白名单原本是源码里硬编码的正则(18 个命令),waiter.yaml 里 没有任何键能改它 ⇒ find/grep/sed/sort/tr 这些排查问题最常用的 只读命令一律被拒,报错:

device_ctl_cmdrun  device_id:waiter-fnnas  error: command not in whitelist

现改为 waiter.yaml 可配置(2026-09-27 随 7193446 部署):

device_cmd_allowlist:
  - ls
  - find
  - grep
  - sed
  • 替换默认集而非追加 —— 避免"以为加了 find、结果还留着 python3 -c 任意执行"
  • 留空 ⇒ 用内置默认集(绝不能变成"全放行",那等于静默拆掉闸门)
  • 生效验证:重启后启动日志应打印 device cmd allowlist: 22 条(来自 waiter.yaml)

当前生产配置:22 条,只读为主(ls pwd cat du df free ps ip uname uptime date hostname find grep sed sort tr wc head tail stat file)。

已知局限:白名单只匹配命令名、不看参数 ⇒ find -delete、 find -exec rm {} ;、sed -i、sort -o 仍能放行。 这是"命令名清单",不是"只读保证"。参数级拦截是后续项。 ⇒ 文档与对话里都不要把它称作"只读白名单",那会让人以为写操作被挡住了。

追加到 waiter.yaml 的幂等做法(已用于两台):

Y=/opt/waiter/waiter.yaml
cp -a "$Y" "$Y.bak-$(date +%Y%m%d-%H%M%S)"
grep -q '^device_cmd_allowlist:' "$Y" || cat >> "$Y" <<'CFG'
device_cmd_allowlist:
  - ls
  - find
  - grep
CFG
systemctl restart waiter-remote.service

3. 故障排查

3.1 "消息发过去没反应"

按这个顺序查,别跳步:

# 1) 消息到了吗
journalctl -u homeagent.service --since "-5 min" | grep -E "interrupt from|webhook recv"
#    群聊里没 @bot 会打 not @bot —— 那不是 bug,是设计

# 2) 任务执行了吗(tools=[] 说明模型没发 tool_call)
journalctl -u homeagent.service --since "-5 min" | grep "→ response"

# 3) LLM 通吗
journalctl -u homeagent.service --since "-10 min" | grep -i unreachable

# 4) 代理层活着吗
curl -s --max-time 8 http://127.0.0.1:8081/v1/models -H "Authorization: Bearer <key>"
#    data:[] 是正常的(该网关不列模型),能返回即说明进程活着

"群聊 not @bot" 与 "私聊没回" 是两件事:前者是插件按规则丢弃, 内核压根没收到任务,自然没有"卡死"。

3.2 设备命令被拒

journalctl -u homeagent.service --since "-10 min" | grep "not in whitelist"

先确认命令第一段在不在 waiter.yaml 的 device_cmd_allowlist 里 (netstat、systemctl 不在当前的 22 条内,被拒是正确行为)。

3.3 适配器流式 tool_call 异常

journalctl -u homeagent.service --since "-10 min" | grep -E "finish_reason=length|unmarshal unified"

非法 JSON 帧 出现在启动握手期属正常(插件启动时的非 JSON 帧), 只在运行期持续出现才是问题。

3.4 有告警但不确定是不是缺陷

先看是不是内核的兜底在工作。例:重复申请 stage 锁 是 SDK 模板在每个 stage handler 入口自动加锁 + 锁不可重入所致,插件侧问题;内核的强制释放 是有意设计(避免后续插件死锁),不要当内核缺陷去修。详见 toolcall-parallel-execution-plan.md 末节。


4. 已知未修

项 性质 说明
白名单不看参数 功能限制 find -delete/sed -i 能逃,用户已知悉并选择先下发
重复申请 stage 锁 插件缺陷 需改 SDK 模板并重编 9月14日的 plugins/qq/plugin.bin
群聊必须 @ 才触发 设计 放宽会在群里引发 unwanted 触发