Files
DevFlow/docs/02-架构设计/专项设计/u-relay-运维手册-2026-10-10.md
lxy 8df4e33656 文档: 看板销账与登记 + 设计档六份 + 索引补录
- 看板: 907 补注/909~941 登记与销账(含 929 模型停用根因与 4 处次生缺口、931 队列丢失、933/935/937 新需求、939 上下文瓶颈清单、941 C-r0 行为变更待拍板)
- 归档: 全量完成态条目/段落 + 只读核验销账 4 段迁 todo归档/2026-10-10.md(机械搬运零措辞改动,未完成项零丢失)
- 设计档: 输入框列表预览与自动编号 / 定时消息与待发箱持久化(统一 ai_outbox 底座) / 对话内定时延时任务 / 消息渲染管线重构(P0-P2 分期) / 上下文分层组装器落地方案(记账基点单一真值,与并行写者职责划清) / FileTree 大目录方案(推荐后端分页,否决虚拟滚动) / u-relay 运维手册
- 索引与事实修正: docs/INDEX crate 数 8→13 + 四子树补录 25 条 + 专项设计补录 27 条;纠正「项目无独立 ARCHITECTURE.md」等过时声明;统一 bun 口径
- CLAUDE.md: 新增「命名与代码风格」节(变量/方法名精简+准确;DRY·简洁·扩展性·高内聚低耦合·单一真值来源)
2026-10-10 08:09:25 +08:00

123 lines
7.2 KiB
Markdown
Raw Permalink 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.
# u-relay 运维手册(token 轮换 · 一通道一 agent · 心跳约束)
> 适用:u-relay 跨端中继的日常运维纪律。协议细节见
> [u-relay Wire 协议规格](./u-relay-wire-protocol-2026-10-04.md),本手册只讲**运维动作与约束**。
> 来源:协议评估遗留三项(单共享 token 可冒充 / 心跳间隔未定值 / 同 channel 多 agent 全投递)。
> 本手册**不含**任何 token 值、主机地址等敏感信息——真值只在部署机 `/etc/u-relay.env` 与各客户端本地配置里。
---
## 1. 心跳间隔约束
心跳间隔须显著小于**链路最短空闲超时**(反代 `proxy_read_timeout`,配置样例 300s;生产实时值可能更大),
以容忍连续数次心跳丢失仍不断链。**约定 30–60s**,各方实现见协议规格 §4.4:
- Agent(df-tunnel):`HEARTBEAT_INTERVAL` 常量,30s(WS 协议层 Ping)
- Commander 参考(u-ppt):30s(应用层 `control_kind:"ping"`)
> 改反代 `proxy_read_timeout` 时须同步评估心跳间隔:若把超时压到 300s 以下,间隔也要相应收窄并保持「超时 / 间隔 ≥ 5」量级的余量。
---
## 2. 单共享 token 轮换 Runbook
### 2.1 现状与风险
u-relay 鉴权是**单一共享 token**(env `U_RELAY_TOKEN`,服务端常量时间比较),**不做 per-channel 隔离**:
任何持有该 token 的客户端可声明**任意 `channel_id`** 冒充任一通道。故 token 属敏感凭据,
泄露即等于整条中继失守;轮换是唯一的止损手段。
### 2.2 涉及方与各自持有位置
token 值散落在「服务端 + 多个客户端」,**没有集中下发通道**:
| 方 | 角色 | token 持有位置 |
|---|---|---|
| u-relay 服务端 | 鉴权源 | 部署机 `/etc/u-relay.env`(`U_RELAY_TOKEN`,权限 600)或 systemd unit 的 `Environment=` |
| devflow 桌面端 | Agent | `relay_token` 设置项(运行时读,缺省回退硬编码默认值) |
| devflow 小程序 | Commander | `apps/df-miniapp/src/config.ts` 缺省 + 设置页可改 |
| devflow ask_uclaw | Commander | 设置页 `uclaw_token`(与 relay token 同源) |
| u-claw | Agent/Runner | u-claw 侧配置 |
| u-ppt | Commander | `relay.ts` 配置 |
> 新增任何接入端都要登记到本表——漏一端的后果见下节。
### 2.3 锁步成本(务必先读)
因为**服务端只认一个 token、客户端各自缓存**,轮换天然是一个**锁步切换**:
- **无重叠窗口**:服务端不能同时接受新旧两个 token,故不存在「灰度换端」的过渡期。
- **顺序无解**:先改服务端 → 全部旧客户端立即 `auth_failed` 掉线;先改客户端 → 全部客户端在服务端切换前拿新 token 一样 `auth_failed`。两条路都有中断窗口。
- **漏端即静默失联**:任一客户端未同步换 token,会持续 `auth_failed` 被拒连,表现为**该端静默离线**(无业务错误、仅 relay 日志有 `auth_failed`),排查成本高。
- **不可逐端滚动**:无法像多 token 系统那样「先切一半、观察、再切一半」。
结论:**轮换是一次计划内的协调停机**,中断窗口 = 从「首个客户端改值」到「服务端重启加载新值」的间隔。执行要点是**把客户端改值的动作压到最短、紧接着重启服务端**。
### 2.4 执行步骤
前置:准备好新 token,确认上表**每一端**都有人能改、改完可验证。
1. **写服务端新值(先不重启)**:在部署机把 `/etc/u-relay.env`(或 unit `Environment=`)的 `U_RELAY_TOKEN` 改为新值,保持服务仍在跑旧值。此时新值尚未生效。
2. **集中改用各客户端**:将上表每一端改到新值。此步开始后各端会 `auth_failed`,**中断窗口从这里起算**。
3. **重启服务端加载新值**:`systemctl restart u-relay`。重启后已改端的客户端重连即通。
4. **验证**:见 2.5。
5. **未改端收尾**:对仍 `auth_failed` 的端补改(此时只有漏改者受影响,不再影响大局)。
> 若某些端的 token 是**编译期硬编码**(无运行时配置项),须提前把「新 token + 重新打包」的产物备好,否则第 2 步无法即时完成,窗口会被拉长。
### 2.5 验证
- 服务端:`systemctl status u-relay` 正常,`ss -tlnp | grep 9180` 在听。
- 连接:拿新 token 走一次握手冒烟(`/ws/agent` 或 `/ws/commander`,收 `hello_ack` 即通)。
- 日志:确认无 `auth_failed` 残留——有则说明还有端没换到新 token。
### 2.6 回滚
轮换失败(如某端无法改)时,**把服务端 env 回退到旧 token 并再重启一次**,回到轮换前状态;客户端已改的端随之 `auth_failed`,需一并回退。回滚与轮换同样是锁步中断。
### 2.7 根治方向
根治是**去掉单共享 token**:服务端改为 per-channel / per-client token(客户端拿自己的凭据只能绑自己的 channel),
届时任一端轮换互不影响,锁步成本消失。在此之前,本 runbook 的锁步纪律是必须遵守的现状约束。
---
## 3. 运维纪律:一通道一 agent
### 3.1 现象与根因
u-relay 路由按 `channel_id` 配对,且**同 channel_id 允许多个 agent 并存、命令全投递**(Vec 索引,无去重、无选举)。
即:同 `channel_id` 上跑两个 agent 实例(如两个 devflow 桌面端、或 devflow + u-claw runner 用了同一通道),
一条 commander 指令会被**两端各执行一次**——写文件、跑命令、调外部接口全部重复,且两端事件回传互相交错。
### 3.2 危害
- 指令重复执行:副作用翻倍(重复落库 / 重复发消息 / 重复改文件),且难以察觉。
- 事件串台:commander 收到两份进度/结果,流式 UI 错乱;两 agent 的上下文互相污染。
- 隐性化:两端都「看起来正常工作」,问题只在副作用层面暴露,事后才追溯得到。
### 3.3 纪律
- **一通道一 agent**:每个 `channel_id` 在任一时刻**只允许一个 active agent**。通道不是「实例池」。
- **启动前先查**:起第二个 agent 前,确认目标 `channel_id` 上没有在跑的 agent(换一个独立 `channel_id`,或用不同通道前缀)。
- **换装/重启要收尾**:旧 agent 若未干净退出(进程被杀但连接残留),WS 连接可能存活到对端超时,期间新旧两连接同通道并存 = 短暂双投递。换装脚本须确认旧连接已断、或等旧连接超时后再起新实例。
- **命名隔离**:不同用途用不同 `channel_id`(如按机器 / 按项目 / 按用户拆通道),从命名上杜绝复用。
### 3.4 排查手法
怀疑重复执行时:
- 看 relay 日志:同一 `channel_id` 是否同时有 >1 条 agent 连接。
- 看 commander 侧:同一 `request_id` 是否收到两份 `result` / 事件重复。
- 看副作用:文件 mtime、DB 记录、外部调用日志是否成对出现。
确认后,断开多余 agent 连接(停掉重复实例)即可恢复单投递。
---
## 附:关联
- 协议全貌:[u-relay Wire 协议规格](./u-relay-wire-protocol-2026-10-04.md)(§4 心跳、§5 部署、§3.3 路由)
- u-relay 仓 README / 部署技能:中继二进制与 systemd 发布流程
- 看板登记:`docs/todo.md`「新能力引入评估纪律」下三项 P3