- 看板: 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·简洁·扩展性·高内聚低耦合·单一真值来源)
123 lines
7.2 KiB
Markdown
123 lines
7.2 KiB
Markdown
# 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
|