fix(dashboard): 平台绑定时自动初始化面板 token - #648
Conversation
deepcoldy
left a comment
There was a problem hiding this comment.
Claude 首次 Review — 🟢 无阻塞,可合(待 @codex 复审 + 申晗确认)
这个 PR 在解决什么(白话)
一句话:平台绑定了、但这台机器从没手动跑过 botmux dashboard 时,Dashboard 的「一键升级」按钮点了会 401 失败。
拆开讲因果链:
- 用户
botmux bind把机器绑到中心平台后,daemon 会开一条隧道向平台注册,平台就成了访问 Dashboard 的「认证前门」——它给每个转进来的请求注入botmux_dashboard_tokencookie 来证明「这请求走了平台的认证门」。 - 但这枚 token(
~/.botmux/.dashboard-token)只有跑过botmux dashboard才会被铸出来。绑定动作本身不铸。 - 所以「绑了平台但没跑过 dashboard」的机器:
activeToken = null→ 隧道上报空 token → 平台注入空 cookie → 升级请求/api/update/run|rollback|restart命中if (!authed)(authed要求!!activeToken)→ 401。 - 用户被迫「先手动
botmux dashboard初始化一次 token」才能用一键升级,体验割裂。
修法:在 startPlatformTunnelIfBound() 启动隧道之前,如果 activeToken 为空,就 loadOrCreatePersistedToken(TOKEN_PATH) 铸一枚并落盘(0600)。新增的 helper 语义是「有则复用(不轮转)、无则 generate+persist」。这样隧道注册时上报的就是一枚真 token。
我核验过的点(为什么判无阻塞)
- 单写者 → 无竞态:
.dashboard-token只由 dashboard 进程写(/__cli/rotate+ 本 PR 的 bootstrap),worker 只读。而 dashboard 是 singleton pm2 app(botmux-dashboard),所以只有一个写者进程。简单的 rename-basedpersistToken在这里就是安全的——本 PR 正确地没有照抄.dashboard-secret的 link(2) 选举机制(那套复杂度是因为 secret 会被 daemon 机群 + dashboard 并发创建才需要,token 不存在这个并发)。 - 无新增安全暴露:token 只经平台认证前门注入,直连者本就拿不到;绑定后平台本就是前门。自动铸 token 只是放行本该能工作的升级路径,不改变「谁能直连」。
/__cli/current无 404 窗口:startPlatformTunnelIfBound在listen().then()里同步跑完 token 赋值才让出事件循环,绑定机 post-listen 不会再命中 404。- 未绑定机保持原行为:
readPlatformBinding()返回 null 时早退,不主动建 token。 - 测试/构建绿:
pnpm exec vitest run test/dashboard-auth.test.ts --project unit→ 55/55 passed;两个新测(create+persist / reuse-not-rotate)单独跑也通过(每测mkdtempSync隔离,无状态泄漏);pnpm build通过;与origin/mastergit merge-tree0 冲突。
1 个 P3 nit(非阻塞,供参考)
bootstrap 路径若 persistToken 抛错(如 ~/.botmux 不可写),异常被 startPlatformTunnelIfBound 的外层 try/catch 吞掉 → 整条隧道不启动(机器在「我的机器」显示离线)。而 /__cli/rotate handler 把 persist 失败当非致命(保留内存 token + warn,链接照常返回)。两条路径对「落盘失败」的容错不一致。
更稳妥的写法是镜像 rotate:先在内存铸 token → try persist → 即便落盘失败也让隧道带着内存 token 起来。不过这属于病态失败态(home 不可写的话 secret/port/schedules 全都废了),现实几乎不触发,故仅列 P3、不阻塞。
接下来交给 @codex 复审。没有申晗明确确认前不合码。
|
To use Codex here, create a Codex account and connect to github. |
deepcoldy
left a comment
There was a problem hiding this comment.
Codex 独立复审 — 🟢 APPROVE(head 9b044de8)
未发现 P1/P2 阻塞问题,同意合入;本次只提交 review,不执行 merge,继续等待申晗明确确认。
独立核对
- 启动与热绑定两条入口都先确认
readPlatformBinding();未绑定机器仍直接返回,不会主动创建 token。 - token 缺失时在构造 tunnel client 前同步
loadOrCreatePersistedToken(),因此首次 register 不再上报空 token;已有 token 原样复用,不会轮转。 botmux bind的/__cli/reload-binding→/__cli/current顺序成立:bootstrap 为同步读写,handler 返回前activeToken已就绪。/__cli/rotate仍是唯一主动轮转入口;worker 继续按请求从同一~/.botmux/.dashboard-token只读,不改变终端 owner gate。- 新建 token 经现有 atomic write 落盘并强制
0600;无新增 token 输出面或直连绕过。 - 影响面限于 dashboard 平台隧道启动/绑定重载和 token persistence helper;未触碰 CLI adapter、PTY/Tmux、话题/群/adopt/restore 会话、IM 路由。Node/fs/path 用法无新增 macOS/Linux 分叉。
非阻塞 P3
确认现有容错不一致:bootstrap 的 persist 失败会落入 startPlatformTunnelIfBound() 外层 catch,导致本轮隧道不启动;/__cli/rotate 则保留内存 token 继续服务。该状态通常意味着 ~/.botmux 已不可写,属病态环境,不作为本 PR 阻塞项;后续可统一为“内存 token 先可用、落盘失败告警”。
实际验证
pnpm exec vitest run test/dashboard-auth.test.ts test/tunnel-client-ip-family.test.ts test/terminal-write-auth.test.ts test/dashboard-endpoint.test.ts --project unit:4 files / 112 tests passedpnpm build:通过(含 TypeScript、dashboard bundle、dist audit)git diff --check upstream/master...HEAD:通过git merge-tree --write-tree upstream/master HEAD:exit 0- 构建后 helper smoke:首次生成、二次复用、磁盘值一致、权限
0600全部通过 - 工作树保持干净;无 UI 改动,无需截图
✅ 已合并(merge commit
|
背景
平台已绑定但本机尚未生成
~/.botmux/.dashboard-token时,平台隧道会注册空 token。此后 Dashboard 的一键升级请求会在进入 npm 更新逻辑前被鉴权拦截,用户只能先手动启动一次botmux dashboard来初始化 token。改动
/__cli/current仍为只读探针,/api/update/*鉴权规则不变验证
pnpm exec vitest run test/dashboard-auth.test.ts --project unit:55 passedpnpm exec vitest run --project unit:722 files passed,1 file skipped;11094 tests passed,6 skippedpnpm build:通过git diff --check:通过无 UI 改动,无需截图。