Device 反向连接
Device 让本机主动建立 WebSocket,把本地 Shell、文件或 SDK provider 挂到远端树。网关不需要反向访问内网,适合开发机、构建节点、内网服务和 Kubernetes Sidecar。
什么时候使用
Section titled “什么时候使用”适合:
- 能力只存在于内网或本机;
- 希望用心跳新鲜度反映连接活性:在线、心跳变陈旧、离线三态可分;
- 结构化任务需要在设备离线时先持久入队,恢复连接后再执行;
- 希望用独立 Device SK 限制注册位置和调用范围;
- 容器或 Sidecar 可以稳定运行一个长驻进程。
不适合:
- 可公开托管的标准 MCP/HTTPS 服务;
- 需要网关主动扫描网络;
- 无法接受长连接和本机命令执行风险的环境。
前置条件:专用最小权限 SK
Section titled “前置条件:专用最小权限 SK”不要把 Admin SK 放进设备。设备 SK 至少需要允许目标路径的注册,并用 registerPaths 收紧位置。具体 tb sk create 参数以当前 CLI 帮助为准;一个典型意图是:
owner: device:build-01registerPaths: [device/build-01]scopes: - device/build-01/**: read,call,registerregisterPaths 只是额外收紧,不能替代 register scope。管理员还应决定谁能对设备路径 read 和 call。
可直接照做的设备 SK 签发与验证流程见权限、SK 与可见性。设备 SK 与上游 Secret 的区别见密钥、出站身份与安全边界。
安全地暴露 Shell 与文件
Section titled “安全地暴露 Shell 与文件”Shell 默认会创建,但 allowlist 缺省为空,所以所有命令均拒绝。只暴露确实需要的命令:
tb connect \ --device-id build-01 \ --path device/build-01 \ --allow uname \ --allow git \ --fs ./shared \ --fs-readonly如果完全不需要 Shell:
tb connect \ --device-id docs-reader-01 \ --path device/docs-reader-01 \ --no-shell \ --fs ./docs \ --fs-readonlytb connect 是前台长驻命令,自带心跳和网络闪断重连;进程崩溃后的拉起可以交给 systemd、Docker 或 Kubernetes。--timeout 是 HTTP 单请求参数,不适用于长驻连接,CLI 会拒绝它。
用常驻 daemon 托管连接(仅 Linux)
Section titled “用常驻 daemon 托管连接(仅 Linux)”在 Linux 上,tb daemon 可以把一次 tb connect 固化成开机自启、崩溃自动重启的后台服务,省去自己写 systemd unit:
tb daemon install \ --device-id build-01 \ --path device/build-01 \ --allow uname \ --fs ./shared \ --fs-readonlytb daemon statusinstall 会在当前用户下生成并启用一个 systemd user unit(tool-bridge-device.service),透传 tb connect 的设备 ID、路径与 --allow/--fs/--fs-readonly/--no-shell 等白名单参数;SK 只写入 0600 权限的配置文件,不进入命令行 argv。其余子命令是 tb daemon status / logs / restart / uninstall。
它只支持 Linux + systemd,并且拒绝以 sudo/root 运行;在其他平台会直接报错退出,macOS/Windows 或非 systemd 环境仍用容器 restart policy 或进程管理器托管 tb connect。uninstall 只移除本机服务,不会吊销服务端的 Device SK——需要时另行在网关侧禁用或删除。精确 flag 以 tb daemon install --help 为准。
让结构化命令支持离线投递
Section titled “让结构化命令支持离线投递”Shell 与文件入口仍按实时连接调用。对于构建、同步、采集等可异步完成的操作,使用 --command-profile 暴露固定 executable/argv 的结构化命令,并把命令 delivery 声明为:
realtime:只允许实时调用,也是缺省值;mailbox:只允许持久化入队;both:允许调用方在实时、直接入队和安全 fallback 之间选择。
CLI/daemon 会在初次连接和每次重连进入 ready 后主动 drain Mailbox;它不会启动固定轮询 timer。完整 profile、tb call --delivery、operation 查询/取消、歧义终态与部署要求见Device 离线投递与 Mailbox。
连接成功会输出确认后的 mountPath。另一个有权限的身份可以检查:
tb device lstb tree device/build-01 --depth 3tb help device/build-01/shelltb help device/build-01/fs调用 Shell 前先读取 ~help 的参数 schema,再原样调用其中的完整 cmds[].path。文件系统作为 Context 暴露,使用 tb ctx ls/cat 等命令;只读挂载不应披露写动词。
成功证据不仅是“WebSocket 已连接”:
- CLI 收到 ready,mountPath 与预期一致;
tb device ls显示online;- 树中只有预期的 shell/fs 节点;
- allowlist 内命令成功,未授权命令被拒;
--fs-readonly下写动词不存在或被拒;- 使用越界 mountPath 的测试连接被
registerPaths拒绝。
presence 是三态,不是在线/离线开关
Section titled “presence 是三态,不是在线/离线开关”设备节点的活性由心跳新鲜度派生,而不是一个连接布尔值。当前有三种状态:
| presence | 含义 |
|---|---|
online |
心跳仍在 TTL 内(约 90s,即 SDK 心跳周期 30s 的 3 倍) |
stale |
连接位还在,但久无心跳,很可能已不可路由 |
offline |
连接已拆除;实时调用不可路由,Mailbox 命令仍可持久入队 |
stale 是有意的中间态:连接标记为在线但超过心跳 TTL(或缺少 lastSeenAt)时,presence 会保守降级为 stale,提示这个设备“很可能调不通”,而不是乐观地当作 online。降级只发生在发现投影阶段,不回写权威状态——presence 是实时路由提示,不是设备身份或 Mailbox 能力的可见性条件。
暴露面各不相同,写脚本时要区分:
~tree的 JSON 里有presence字段;tb tree只对stale/offline打标记,online不额外标注;- device 挂载目录的
~help会在描述里拼上(stale); - Dashboard 用一个 presence 徽标展示三态,并保留离线设备及其 Mailbox-capable 命令;
tb device ls不是三态:它是裸存储态的两列ONLINE(yes/no)+LAST_SEEN,不要把它读成stale/offline。
具体阈值和字段名以目标实例运行时返回为准,不要在客户端写死心跳窗口。
设备收到调用者来源与权威截止时间
Section titled “设备收到调用者来源与权威截止时间”网关转发给设备的调用信封会带上一个 context,包含:
- 调用者来源(provenance):
caller.keyId与caller.owner,是发起调用身份的非敏感标识,不是 SK 明文(displayName字段类型存在,但当前网关不填,别依赖它有值); - trace id:便于把设备侧日志与网关调用对齐;
- 权威 deadline
expiresAt:以网关时钟表示的绝对截止时间,也就是网关真正取消该调用的时刻(约 60s)。
信封里不含 scope、SK 或授权决定——scope 在网关转发之前就已判完。设备据此做本地审计与限时,但不做授权裁决:设备是执行层,不承担 scope 否决。不要在设备端用 provenance 重新实现一套权限模型,也不要因为拿到了调用者身份就放宽本地白名单。
超时后网关只向设备发送一个协作式取消信号(AbortSignal),能否真正停下取决于设备端实现。因此超时不代表副作用已经撤销:expiresAt 之后,这次调用产生的外部影响应按“未知”处理,由设备或上游自己保证幂等与补偿。
大量工具设备的搜索索引溢出
Section titled “大量工具设备的搜索索引溢出”搜索索引对单台设备有节点容量上限(当前约 500 个,以实现为准)。超过时设备照常挂载、照常可调用,只是溢出的工具不会进入全局 ~search。
要注意这个信号的可见范围:溢出只会在网关进程的日志里以 console.warn 出现,tb、Dashboard、HTTP API 和设备 ready 帧里都看不到。它是运维在网关日志中排查的告警,不是终端用户能直接观察到的状态。因此工具很多的设备不要只靠 ~search 发现能力,应结合 ~tree 和节点级 ~help;怀疑有工具“搜不到但能调”时,去查网关日志。
在容器中运行
Section titled “在容器中运行”官方 CLI 镜像包含完整 tb 命令。作为长驻设备时,用运行时 Secret 注入 BaseURL/SK,并同时收紧宿主 volume:
docker run -d \ --name tb-device \ --restart unless-stopped \ -e TB_BASE_URL=https://tb.example.com \ -e TB_SK="$TB_DEVICE_SK" \ -v "$PWD/shared:/workspace:ro" \ ghcr.io/tokenrollai/tool-bridge-cli:<version> \ connect \ --device-id build-01 \ --path device/build-01 \ --allow uname \ --fs /workspace \ --fs-readonly将 <version> 替换为你已经阅读发布说明并验证过的明确版本或 digest,不要改成浮动 tag。
生产环境固定版本或 digest,不使用 edge。Sidecar 只能看到自己的根文件系统以及与业务容器显式共享的 volume;同 Pod 共享网络不等于共享文件。
使用 SDK 暴露本地 provider
Section titled “使用 SDK 暴露本地 provider”SDK 的 connect() 默认把本实例通过 registerTool/registerContext 登记的节点和工具表上报:
const connection = tb.connect( 'https://tb.example.com', process.env.TB_DEVICE_SK!, { deviceId: 'my-service-01' },)
await connection.ready长驻服务应显式设置稳定 deviceId,避免重启后出现新的身份,也让 presence 在重连后落回同一个节点。Linux 上可以用 tb daemon 托管稳定 deviceId 的常驻连接。SDK 默认 expose 面向自定义 nodes;CLI 才提供现成 Shell 和文件 executor。
| 现象 | 处理 |
|---|---|
| ready 前收到权限拒绝 | 检查 register scope、registerPaths、mountPath 和 deviceId |
| 设备反复 reconnect | 检查代理是否保留 WebSocket upgrade、Authorization,以及网络空闲策略 |
| Shell 所有命令都拒绝 | 缺省 allowlist 为空;显式增加最小 --allow |
| Sidecar 看不到业务文件 | 两个容器没有挂同一个 volume,或挂载路径不同 |
| 文件能读不能写 | 使用了 :ro/--fs-readonly,这是预期安全行为 |
| 实时调用返回 503 retryable | 设备处于 stale/offline 或正在重连;需要异步交付时只对声明 mailbox/both 的命令显式选择 delivery,不要把任意失败自动入队 |
| 相同 deviceId 互相替换 | 每个实例应使用唯一、稳定 ID;旧 generation 不会完成新调用 |
正常停止 tb connect(或用 tb daemon uninstall 移除常驻服务、终止容器)即可关闭连接。节点会先进入 stale、连接拆除后转 offline,并由宿主按回收策略清理;不要假设断线立即永久删除树记录。
docker stop tb-devicetb device ls紧急吊销时先禁用或删除该 Device SK,阻止重连;再由管理员通过当前实例的 registry 管理面清理遗留节点。清理命令和权限以 tb help system/registry 为准,不要使用不存在的 tb device rm。
- 只暴露文件:优先
--no-shell --fs ... --fs-readonly; - 能力已有公网 HTTPS/MCP:改用MCP 挂载;
- 需要嵌入业务进程:阅读嵌入现有应用,并核对目标版本类型定义;
- 需要设备离线后继续接受结构化任务:阅读Device 离线投递与 Mailbox;
- 生产 Sidecar 的镜像、架构和 CA 证书要求以当前
tbCLI 容器文档与tb connect --help为准; - ready、WebSocket 或
stale/offlinepresence 状态异常:进入故障排查与升级。