Skip to content

About

DeepSeek Harness mobile access plugin: reverse SSH tunnel + GUI QR pairing + device management in one dsh plugin

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

40 Commits

Folders and files

Repository files navigation

dsh-mobile(CF Worker 网关 + P2P)

DeepSeek Harness 的移动接入插件:网关(Cloudflare Worker, dsh-gateway-worker)只做 配对 + WebRTC 信令,业务流量全部手机 ↔ Mac 直连(DataChannel,DTLS 加密)—— 零中转、零隧道、零 ssh、零服务器。配合移动客户端 DeepseekHarnessApp 使用。

手机 App ──wss(vstream 帧中转;扫码配对启用 E2E 加密)──→ CF Worker 网关
                                                              │
本插件(宿主)──────── wss(vstream 帧中转)──────────────────┘
   └── P2P DataChannel(备选直连:业务流量不经网关,网络允许时可用)

2026-09-10 起默认链路为网关中转(P2P 在办公网/蜂窝不可达,降为备选); 2026-09-12 起扫码配对默认启用端到端加密(PSK 经 QR 带外,网关只见 密文;协议与互操作向量见 PROTOCOL.md §5)。下文「零中转」「仅信令」等 段落为 P2P-only 时代历史描述,以 PROTOCOL.md 为准。

  • 配对:扫码/手输(双向亮码防抢注);设备令牌由 Worker 签发/吊销, 令牌绑定的 host 路由键只是登记标识;
  • 直连:Mac 是 offerer;手机 connect → Worker 派 sid → offer/answer/ ICE 经信令面交换 → DataChannel dsh 建立;
  • 虚拟流:DataChannel 上的 vstream 二进制帧(OPEN/DATA/CLOSE, 16KB 分片)多路复用 TCP 字节流,Mac 侧落地 127.0.0.1:<dsh web 端口>; 手机侧同样起本地回环代理 —— 两端 HTTP/WebSocket 栈零改动;
  • UI:侧栏 foot「移动接入」dialog:P2P 状态(信令通道/活跃会话/ 虚拟流计数)+ 机器名 + 配对 + 设备管理(链路徽章)+ 安全事件横幅 (配对/吊销 OS 通知与角标)。入口在本机可打通的页面可见:回环 http(s) 源(dsh web 的浏览器页)与 dsh desktop 外壳(dsh-app://app/)都挂; 局域网/公网源不挂(管理面只对 loopback 开放,见「安全模型」)。

协议细节(信令消息 + vstream 帧格式)见 PROTOCOL.md。

安装

前置:

  • pnpm 在 PATH 上(dsh plugin 本体只是对 profile 目录的 pnpm 转发器, 没有 pnpm 直接报 127 退出);
  • 网关 Worker 已部署且含信令面(/signal/* + /admin/signal/ticket);
  • 手机 App 为支持 P2P 的版本(探测 /signal/caps 自动走直连)。

1. 装插件

dsh plugin --profile web add github:iptton-ai/dsh-mobile   # 或本地目录: add ./dsh-mobile

本包声明了 dsh.bundle.patch(指向自带的 cordis.patch.yml), 因此它是一个组合包:装好后会出现在 Web 侧栏「插件」页的 Installed 组, 开关即启用状态;被选中时它自带的 patch 作为组合包层自动生效(禁用官方 native 目录选择器 + 挂 browse 双前端 + 插入 dsh-mobile 行)。也可以在插件页用 Add plugin 直接装。

老文档让人手工合并 insert 段 —— 现在不需要了,照做反而会让同一批 id 在 组合树里出现两次。已经手工合并过的,见第 3 步的迁移说明。

报 ERR_PNPM_ADDING_TO_ROOT? profile 目录里已有 pnpm-workspace.yaml (比如已跑过一次 approve-builds),pnpm 会把它当 workspace、拒绝把依赖加到 workspace root。两个解法任选: ① 命令尾追加 -w(dsh 转发器原样透传参数): dsh plugin --profile web add github:iptton-ai/dsh-mobile -w; ② 在 profile 的 pnpm-workspace.yaml 加一行 ignoreWorkspaceRootCheck: true 后重跑原命令(以后 add/update 其他插件也不再问)。

2. 放行 node-datachannel 的安装脚本(必做,跳过 = 插件静默坏)

pnpm ≥ 10 默认不执行依赖的 install 脚本;本插件的 WebRTC 依赖 node-datachannel 全靠 install 脚本下载预编译原生二进制。跳过的后果是 安装期零报错:dsh web 启动时插件 import 崩,侧栏不出现「移动接入」 入口。在 profile 目录(缺省 ~/.dsh/profiles/web/;设了 DSH_HOME 则在其下)执行:

cd ~/.dsh/profiles/web
pnpm approve-builds                     # ⚠️ 交互式命令,不带包名(见下);勾选 node-datachannel
dsh plugin --profile web install        # 重跑安装,补跑被跳过的脚本

报 Command "approve-builds" not found? 两个常见原因: ① 给 approve-builds 传了包名 —— 它是交互式命令不接参数,带参会当成 "执行叫 approve-builds 的命令" 而报 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL; ② pnpm < 10 根本没有这个子命令(pnpm -v 确认)。 跨版本最稳的手写法(不依赖该命令):直接编辑 profile 的 pnpm-workspace.yaml,加入:

onlyBuiltDependencies:
  - node-datachannel

然后重跑 dsh plugin --profile web install。

审批写入 profile 的 pnpm-workspace.yaml 持久生效,后续 dsh plugin --profile web update 不再丢。

验证二进制真的落盘了

第 2 步做完别急着重启,先确认预编译二进制存在(路径里的版本号以实际为准):

ls node_modules/.pnpm/node-datachannel@*/node_modules/node-datachannel/build/Release/
# 应能看到 node_datachannel.node

文件不存在时的排查(典型症状:dsh web 启动报 Cannot find module '../../../build/Release/node_datachannel.node'):

  • pnpm < 10(如 8.x):没有脚本跳过机制,二进制缺失说明 install 脚本 跑了但下载失败——预编译包从 GitHub Releases 下载,国内网络常超时。 强制重跑并挂代理:
    pnpm rebuild node-datachannel
    # 失败/卡住时:
    HTTPS_PROXY=http://127.0.0.1:<代理端口> pnpm rebuild node-datachannel
    若它转而本地编译并报缺 cmake:brew install cmake 后重跑(较慢)。
  • pnpm ≥ 10:检查 yaml 键名拼写与缩进,改完必须重跑 install (只改配置不会补跑脚本)。

3. 填 config

行结构由组合包自带的 patch 提供,不需要手工合并 insert。你只需要在 profile 自己的 cordis.patch.yml 里按 id 覆盖部署相关的 config(用户层在组合包层之后, 同一行的 config 以用户层为准):

- id: dsh-mobile
  name: "dsh-mobile"
  config:
    gateway: https://dsh.example.com
    adminKey: '<ADMIN_KEY>'

键位见下节「配置」。插件页里 dsh-mobile 的开关如果没打开,打开它即可 —— 它就是 profile package.json 的 dsh.profile.bundles 里的 dsh-mobile 行。

已按旧文档手工合并过 patch? 先删掉你自己 cordis.patch.yml 里那段 - insert:(directory-picker-browse / ui-directory-picker-browse / dsh-mobile)和前面的 - id: directory-picker / disabled: true,只留上面这段 id 覆盖的 config,再去插件页把 dsh-mobile 开关打开。否则同一批 id 会在组合树 里出现两次(dsh --profile <name> --dump-config 可见),宿主半边会被挂载两遍。

4. 重启 dsh web 并验证

重启后 dsh web 侧栏底部出现**「移动接入」**入口即装好。没出现时按序查: ① dsh web 日志有无 node-datachannel import 报错(= 第 2 步被跳过); ② 组合包是否启用(插件页 Installed 组的 dsh-mobile 开关 / profile package.json 的 dsh.profile.bundles)+ config 是否填了 gateway; ③ 改完 patch/开关后是否重启了 dsh web(desktop 外壳还需重启应用:窗口的模块 清单是启动快照,且模块根不走 HMR)。

配置

2026-10-05 起四项均可在面板直接编辑:「移动接入」面板新增**「中转网关」区** (gateway + host 输入框),连同既有的「机器名」(label)与「管理密钥」(adminKey) 栏,保存在本机 dsh 用户设置、即时生效 —— 自布中转服务器的使用者全程无需碰 cordis.patch.yml。优先级:env DSH_MOBILE_* > 面板(settings)> 此处 config。 下方表格的 config 键仅作部署模板/回落位。

cordis.patch.yml 的 dsh-mobile 行 config(DSH_MOBILE_* 环境变量可覆盖):

键 说明 默认
gateway CF Worker 网关地址(如 https://dsh.example.com) 必填
adminKey 管理密钥(部署 Worker 时的 ADMIN_KEY,≥16 字符)。优先级:env DSH_MOBILE_ADMIN_KEY > 面板「管理密钥」栏(用户 settings)> 此处 config —— config 是明文模板位,profile yml 常随 dotfiles 同步,能不用就不用 必填*
host 宿主路由键(多宿主各占一个;仅登记标识,无隧道语义) <短主机名>.host
publicUrl 扫码落地页 <gateway>/pair
label 机器名(缺省 hostname;面板可改,持久化) —
iceServers ICE 服务器(JSON 数组字符串;缺省公共 STUN,无 TURN;只作用于 Mac 侧 gather,手机侧由网关 ICE_SERVERS 下发) 公共 STUN

* adminKey 三处任一有效即可;全缺时插件不再拒绝加载(2026-08-29 前 会在启动时 throw),配对/信令以运行时错误文案指路,面板「管理密钥」栏可补。

环境变量:DSH_MOBILE_GATEWAY / DSH_MOBILE_ADMIN_KEY / DSH_MOBILE_HOST / DSH_MOBILE_PUBLIC_URL / DSH_MOBILE_LABEL / DSH_MOBILE_ICE_SERVERS

从中转版迁移

  • 管理通道从「ssh 到网关服务器 loopback」换成「HTTPS+Bearer adminKey 直连 Worker」:不再需要 target/adminPort,也不再依赖任何服务器与 ssh key;
  • adminKey 迁移(2026-08-29 修复):旧版在 webui「管理密钥」栏保存过的 密钥存储在 dsh 用户 settings,升级后自动沿用(优先级 env > settings > config);此前一段时间(78974c0–6f1dd43)settings 值被静默忽略、缺 config 即加载失败 —— 升级到本版后两个问题都不存在,面板密钥栏同步恢复;
  • remotePort(服务器隧道口)→ host(路由键):多宿主仍各占一个, 网关按它把手机信令路由到本宿主;
  • 移除 ssh -R / cloudflared / Rust 网关 / CF Worker 中转面 / Web 远程访问 (网关浏览器登录)/ 多租户免 ssh 通道 —— 服务器带宽占用归零;
  • 旧(中转时代)配对的令牌绑定的是旧 tunnel_host,需重新配对一次。

打洞失败(对称 NAT 等)无 TURN 时连接失败;要 TURN 时给 iceServers 配 (或 Worker env ICE_SERVERS),经 /signal/caps 与 ack 下发。

安全模型

  • 管理 API(/pair/api/*)仅接受 loopback Host + 同源三重门 (Sec-Fetch-Site/Origin 双检、写操作强制 x-dsh-mobile 头、JSON 体限 application/json);
  • 网关管理面信任根 = ADMIN_KEY(Bearer);host ticket 为短时 JWT(900s);
  • 业务通道安全 = DTLS(WebRTC 强制);信令安全 = 设备令牌 + ADMIN_KEY;
  • 配对秘密只存在手机内存;令牌可随时吊销;安全事件(配对成交/吊销) OS 通知 + 面板横幅显性化。

端到端冒烟

npm install
GATEWAY=https://dsh.example.com ADMIN_KEY=<密钥> node tools/e2e-smoke.mjs   # 独立脚本模拟两侧
node tools/plugin-live-smoke.mjs                                            # 插件真实代码路径(读 /tmp/.dshgw_admin_key)

通过判据:消息经 P2P 通道原样返回(业务流量不经过 Worker)。

上游兼容性

已核对 dsh 0.1.6-alpha.2(上一记录版 0.1.6-alpha.1):本插件触点全部 兼容,无代码改动。上游这轮变化集中在本插件不触及的面:session-controller client 半侧大重构(sessions service/projection 拆分,宿主 RPC schema 零变化)、 terminal-controller 新增 bindings/retention(纯增量 namespace)、新增 plugin-manager / office-to-pdf 两个 remote 挂载、client-modules 组合产物从 内容寻址改为 revision 寻址并支持包内懒加载 chunk(注册协议向后兼容: ClientBundleRegistration.chunk 可选)、desktop boot 走 dshDesktopBoot 门控 (served web 路径不变)。逐面核对(对 alpha.1→alpha.2 全量 diff + 已装 alpha.2 安装体验证):

  • packages/api RPC schema —— session-controller 改的是 client 半侧内部 (宿主 contract sessions.ts 形状零变化);terminal-controller/workspace-files 为纯增量或内部实现(readAll 改走 fs.readBytes,错误码不变);gateway/ remotes 仅新增挂载,既有 namespace 零变化;settings-controller 仅版本号;
  • 插件/extension 接口 —— webServer.register({kind:'prefix'})/.port getter(webserver src 零 diff)、settings.register(ns, schema, {base})、 connection.authenticatedUrl、dsh.client 声明解析、 /plugins/<id>/client.js 下发路由、sidebar.footer.action 槽位 (ui-sidebar contract 在位):触点全部在位且形状一致。connection 新增 connection/request waterfall 事件与 streamBaseUrl hook,均为可选增量;
  • cordis.patch 清单结构 —— vendor/loader 零 diff,EntryOptions 与 disabled/insert 写法不变,directory-picker-browse 两包名不变, profile 合并块无需同步;
  • 客户端协议 —— __ModuleLoader__.load({id,factory}) + require('react') 种子 + slots.inject/register 协议不变;chunk 字段为可选增量, 既有单文件 bundle 注册不受影响。

复验(2026-09-16 实测,对真实 @deepseek-ai/dsh@0.1.6-alpha.2 宿主,全部 exit 0):

node --check lib/index.js && node --check lib/client.js   # 语法门
node tools/contract-smoke.mjs                             # 契约门:mock 宿主跑真实 apply(),18 项(零网络)
node tools/host-live-smoke.mjs                            # 真宿主门:已装 dsh 的 WebServer+FileSettingsProvider 起真实 HTTP,15 项(零外网)
node tools/crypto-vectors.mjs                             # E2E 加密向量自检,6 cases(零网络)
node tools/client-guard-smoke.mjs                         # 客户端门:入口可见性矩阵(回环/dsh-app 外壳挂,局域网/公网不挂),15 项(零网络)

host-live-smoke.mjs 是升级后的第一道硬门:用已安装 dsh 的真实 cordis/WebServer/settings 服务跑本插件真实 apply(),真实 HTTP 逐项断言 (真实 prefix 路由、真实 .port、settings 磁盘落盘、管理面三重门、dispose 下线)。connection 服务因拖 credentials 持久化不起真身,给形状桩。定位 已装 dsh 用 DSH_GLOBAL_ROOT,缺省 npm root -g 下的 @deepseek-ai/dsh/node_modules。每次升级 dsh 先跑这四条,再按需跑 上面的端到端冒烟。


历史:0.1.6-alpha.1(上一记录版 0.1.5-rc.2)

alpha 断层,变化量大但触点零变化,无代码改动:新增 terminal-controller / permission-presets 两个 RPC namespace;vendor/loader Entry 大重构但 apply(ctx, config) 契约与 patch 清单写法不变;client-modules 双半边内部 重构(dsh.client 语义、client.js 下发、slot 协议不变);boot profile 行名 改 link 模式物化,升级后开一次 dsh web 确认 insert 行加载(已确认)。

历史:0.1.5-rc.2(上一记录版 0.1.5-rc.1)

本插件触点全部兼容,无代码改动。上游变化集中在自带 client UI 插件内部 (ui-message-feedback 提交语义重构、ui-primitives 图稿拆分、间距微调), packages/api 六包仅版本号 diff,apps/web 客户端 src 零 diff;profile 合并块 无需同步。

历史:0.1.5-rc.1(上一记录版 0.1.5-alpha.2)

本插件触点全部兼容,无代码改动。上游这轮实质变化只有三处,均不触及 本插件:LLM 模型目录(deepseek-v4-flash→deepseek-flash;bundle 基线 cordis.patch.yml 改的是 agent-default-model 的 config 值,清单结构未动)、 web 客户端 UI 打磨(侧栏右栏/文档预览/代码块/统计 pill)、slot-catalog 一处 source 行号注释。逐面核对(对已安装 rc.1 安装体逐一验证): packages/api 六包仅版本号 diff;插件/extension 接口触点(webServer register/.port、settings.register get/watch/update、connection authenticatedUrl、dsh.client 声明、client.js 下发路由、sidebar.footer.action 槽位)全部在位且形状一致;客户端协议 __ModuleLoader__.load({id,factory})

  • require('react') 种子不变。

复验:node --check(语法)+ contract-smoke.mjs(mock 宿主 18 项)+ host-live-smoke.mjs(真宿主 15 项;该工具即本轮新增的升级后第一道硬门)。


历史:0.1.3-alpha.2(上一记录版 0.1.2-rc.1)

本插件触点全部兼容,无代码改动。逐面结论:

  • 宿主服务 —— webServer.register({kind:'prefix',path,handler})、 settings.register(ns, schema, {base}) 的 get/watch/update、 connection.authenticatedUrl():packages/host/webserver、packages/settings/settings 在 rc.1→alpha.2 仅 package.json 版本号 diff,browser-auth.ts 零 diff;
  • 客户端协议 —— window.__ModuleLoader__.load({id,factory}) + require('react') 种子 + slots.inject/register + sidebar.footer.action 槽位不变; dsh.client 声明形状(platform/inject)不变(上游只是把接口挪到 @deepseek-ai/dsh-package-manifest 的 DshClientManifest)。boot 图仍收录 dsh-mobile 条目,实机(0.1.3-alpha.2 GUI)侧栏入口 + dialog 正常;
  • cordis.patch.yml 行结构 —— id/name/config/disabled/insert 不变; web-app bundle 仍有 directory-picker 行,browse 后端/前端两包名未变。 (上游新增行为:绝对路径的 insert.name 也会转 file URL;本插件用包名,不受影响。)

当时的复验门:node --check(语法)+ contract-smoke.mjs(mock 宿主 18 项)。

MIT License.

About

DeepSeek Harness mobile access plugin: reverse SSH tunnel + GUI QR pairing + device management in one dsh plugin

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages