From 175fa9dd620c1f11efbda54731df3b33d0694dc3 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 27 Sep 2026 22:25:58 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E7=94=9F=E4=BA=A7?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E6=89=8B=E5=86=8C=EF=BC=88homed=20/=20waiter?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 条、两次二进制大小取自实际部署。 --- docs/zh/deploy-runbook.md | 271 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 docs/zh/deploy-runbook.md diff --git a/docs/zh/deploy-runbook.md b/docs/zh/deploy-runbook.md new file mode 100644 index 0000000..40540d2 --- /dev/null +++ b/docs/zh/deploy-runbook.md @@ -0,0 +1,271 @@ +# HomeAgent 生产部署手册(homed / waiter) + +> 适用范围:本机(`.60`)的 `homeagent.service`,以及两台设备桥宿主上的 +> `waiter-remote.service`。 +> +> 静态站 / nginx / 证书的运维见另册 +> [`site-infra-runbook.md`](./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 ★ 构建参数必须与线上一致 + +```bash +CGO_ENABLED=1 CC=cc go build -tags onnxruntime -o /tmp/homed-ort-new \ + -ldflags "-X .../internal/meta.Version= -X .../internal/meta.Commit=" \ + ./cmd/homed +``` + +**不要顺手加 `-s -w`。** 加了会 strip 掉符号,产物从 ~86.8MB 掉到 ~78MB —— +体积差 8.8MB 会让人误判成"构建坏了",而它只是被 strip 了。 +若确实要 strip,须先确认线上也是同样参数,否则两次构建不可比。 + +验证: + +```bash +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 +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 部署后必须核对 + +```bash +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…`),被**正确判定为用户修改并保留**。 + +验证方式(部署前后各跑一次,应完全一致): + +```bash +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 +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 +``` + +**逐台更新,不要并行** —— 两台都连同一网关,同时重启会同时断链。 + +`deploy` 会:备份 `/opt/waiter/waiter` → 替换 → 重启服务 → 验证 +(服务 active、进程时长、**配置 md5 未变**)。 + +### 2.3 部署后确认设备已注册 + +```bash +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` 部署): + +```yaml +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` 的幂等做法(已用于两台): + +```bash +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 "消息发过去没反应" + +按这个顺序查,**别跳步**: + +```bash +# 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 " +# data:[] 是正常的(该网关不列模型),能返回即说明进程活着 +``` + +**"群聊 not @bot"** 与 **"私聊没回"** 是两件事:前者是插件按规则丢弃, +内核压根没收到任务,自然没有"卡死"。 + +### 3.2 设备命令被拒 + +```bash +journalctl -u homeagent.service --since "-10 min" | grep "not in whitelist" +``` + +先确认命令**第一段在不在** `waiter.yaml` 的 `device_cmd_allowlist` 里 +(`netstat`、`systemctl` 不在当前的 22 条内,被拒是**正确行为**)。 + +### 3.3 适配器流式 tool_call 异常 + +```bash +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`](./toolcall-parallel-execution-plan.md) 末节。 + +--- + +## 4. 已知未修 + +| 项 | 性质 | 说明 | +| --- | --- | --- | +| 白名单不看参数 | 功能限制 | `find -delete`/`sed -i` 能逃,用户已知悉并选择先下发 | +| `重复申请 stage 锁` | 插件缺陷 | 需改 SDK 模板并重编 9月14日的 `plugins/qq/plugin.bin` | +| 群聊必须 @ 才触发 | 设计 | 放宽会在群里引发 unwanted 触发 |