Skip to content

fix(dashboard): 平台绑定时自动初始化面板 token - #648

Merged
deepcoldy merged 1 commit into
deepcoldy:masterfrom
47seek:fix/dashboard-token-bootstrap
Jul 29, 2026
Merged

fix(dashboard): 平台绑定时自动初始化面板 token#648
deepcoldy merged 1 commit into
deepcoldy:masterfrom
47seek:fix/dashboard-token-bootstrap

Conversation

@47seek

@47seek 47seek commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

背景

平台已绑定但本机尚未生成 ~/.botmux/.dashboard-token 时,平台隧道会注册空 token。此后 Dashboard 的一键升级请求会在进入 npm 更新逻辑前被鉴权拦截,用户只能先手动启动一次 botmux dashboard 来初始化 token。

改动

  • 平台绑定存在且 token 缺失时,在启动平台隧道前生成并持久化初始 token
  • 已有 token 继续复用,不发生轮换
  • 未绑定平台的机器保持原行为,不主动创建 token
  • /__cli/current 仍为只读探针,/api/update/* 鉴权规则不变

验证

  • pnpm exec vitest run test/dashboard-auth.test.ts --project unit:55 passed
  • pnpm exec vitest run --project unit:722 files passed,1 file skipped;11094 tests passed,6 skipped
  • pnpm build:通过
  • git diff --check:通过

无 UI 改动,无需截图。

@47seek
47seek requested a review from deepcoldy as a code owner July 29, 2026 03:42

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 首次 Review — 🟢 无阻塞,可合(待 @codex 复审 + 申晗确认)

这个 PR 在解决什么(白话)

一句话:平台绑定了、但这台机器从没手动跑过 botmux dashboard 时,Dashboard 的「一键升级」按钮点了会 401 失败。

拆开讲因果链:

  1. 用户 botmux bind 把机器绑到中心平台后,daemon 会开一条隧道向平台注册,平台就成了访问 Dashboard 的「认证前门」——它给每个转进来的请求注入 botmux_dashboard_token cookie 来证明「这请求走了平台的认证门」。
  2. 但这枚 token(~/.botmux/.dashboard-token只有跑过 botmux dashboard 才会被铸出来。绑定动作本身不铸。
  3. 所以「绑了平台但没跑过 dashboard」的机器:activeToken = null → 隧道上报空 token → 平台注入空 cookie → 升级请求 /api/update/run|rollback|restart 命中 if (!authed)authed 要求 !!activeToken)→ 401
  4. 用户被迫「先手动 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-based persistToken 在这里就是安全的——本 PR 正确地没有照抄 .dashboard-secret 的 link(2) 选举机制(那套复杂度是因为 secret 会被 daemon 机群 + dashboard 并发创建才需要,token 不存在这个并发)。
  • 无新增安全暴露:token 只经平台认证前门注入,直连者本就拿不到;绑定后平台本就是前门。自动铸 token 只是放行本该能工作的升级路径,不改变「谁能直连」。
  • /__cli/current 无 404 窗口startPlatformTunnelIfBoundlisten().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/master git merge-tree 0 冲突。

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 复审。没有申晗明确确认前不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 passed
  • pnpm 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 改动,无需截图

@deepcoldy
deepcoldy merged commit 3c0d64d into deepcoldy:master Jul 29, 2026
1 check passed
@deepcoldy

Copy link
Copy Markdown
Owner

✅ 已合并(merge commit 3c0d64de

经申晗明确确认后 admin-merge 到 master。

合并前核验

  • head SHA 9b044de8 与双审基线一致,无 drift
  • mergeable: true / mergeable_state: clean
  • CI build:success
  • 双审收敛:Claude 🟢 + codex 🟢,reviewDecision = APPROVED

保留的非阻塞 P3(follow-up 可选):bootstrap 路径 persistToken 失败会被外层 try/catch 吞掉 → 放弃本轮隧道;而 /__cli/rotate 遇同样失败保留内存 token。两路径容错不一致,属 ~/.botmux 不可写的病态态,未在本 PR 处理。

注:GraphQL API 触发限流,合并走 REST 端点(PUT /repos/.../pulls/648/merge)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants