Skip to main content
A

dsh-sakurafrp

ankhishtar2-lang/dsh-sakurafrp

Manage the SakuraFrp phone-to-desktop link from the DSH interface - link status, mobile gateway switch, one-time pairing QR code, device revoke and gateway self-heal.

Install

dsh plugin --profile web add github:ankhishtar2-lang/dsh-sakurafrp

README

dsh-sakurafrp

[!WARNING] 本仓库是 vibe coding 产物。 代码由作者与 AI(DeepSeek Harness 会话)对话生成、 多轮迭代而来,没有经过人工逐行审计,也只在作者本机环境(WSL2 + dsh 0.1.5-rc.1 系列) 实测过。请自行审阅后再使用;不要直接用于生产或安全敏感场景。 代码按 MIT「按原样(AS IS)」提供,不附带任何担保,风险自负。

A vibe-coded satellite plugin for dsh-mobile: manage the phone ↔ desktop link (SakuraFrp TCP tunnel + dsh-mobile gateway) entirely from DSH's own UI — status, gateway on/off, one-time pairing QR, device revoke, self-heal.

把「手机通过 SakuraFrp 隧道访问电脑上的 DSH」这条链路图形化: 原先要跑脚本、改 YAML、盯 systemd 才能完成的事,现在在 DSH 会话头部点一个手机图标就能做。


1. 它做什么

能力说明
链路状态一屏可见网关是否在跑、监听端口、公网入口、四个公网节点的实测延迟、已配对设备数、外部守护服务状态
一键开关手机访问直接控制 dsh-mobile 的移动网关(等价于 POST /api/mobile-access/lan/control);关闭会留下抑制标记,自愈不再把它拉回来
配对二维码申请一次性配对窗口(默认 5 分钟有效),二维码与链接内联返回,手机相机/浏览器扫码即可
设备管理逐台撤销、一键清空(清空会带上游要求的 confirm:true);配合 maxDevices 实现「只信任某一台设备」
网关自愈每 15 秒检查移动网关,发现被静默关闭(已知问题)就重新拉起 —— 但用户主动关闭时让路(见下方说明)
公网可达性探测每 60 秒并发探测主入口 + 三个运营商前缀节点,面板直接显示谁通谁不通
外部守护开关可启停 dsh-mobile-lan-watchdog.service(冗余层,插件自带自愈已能独立兜底;它同样尊重「用户已关闭」标记)

「关得掉」是硬要求。 早期版本的自愈是无条件的:你关掉手机访问后,15 秒内它就会被 自动拉起来,连 dsh-mobile 自己持久化的 enabled:false 偏好也一并被覆盖——表现为 「根本关不掉」。现在点「关闭手机访问」会写一个抑制标记(默认 $DSH_HOME/mobile-access/.gateway-user-disabled),插件自愈与外部 watchdog 都会跳过; 面板也会显示「你已主动关闭…」。点「开启手机访问」即删除标记、恢复自愈。

入口:DSH 会话头部工具区的手机图标按钮order: -15,位于「重启」与「打开文件管理器」之间)。 手机端也能打开这个面板,但只能看状态(原因见第 6 节)。


2. 依赖声明(重要)

2.1 运行期硬依赖:dsh-mobile 插件

本插件不自己实现任何网关,它只是 dsh-mobile 的移动访问网关的图形化管理壳。 没有 dsh-mobile 时:/status 会显示网关无响应,/pair 会报「网关未启动或 dsh-mobile 未启用」。

它依赖 dsh-mobile 提供的这些本机(loopback)管理接口

方法路径用途
GET/POST/api/mobile-access/lan/control网关开关({running: bool}
GET/api/mobile-access/lan/status网关/配对/设备数/资源占用
GET/api/mobile-access/lan/devices设备列表
POST/api/mobile-access/lan/devices/revoke撤销设备({deviceId},32 位小写 hex)
POST/api/mobile-access/lan/devices/reset清空设备
POST/api/mobile-access/lan/pairing/open申请一次性配对窗口,返回 pairUrl + qrSvg

2.2 运行期硬依赖:SakuraFrp 隧道(非 npm)

公网入口由 SakuraFrp 启动器常驻的 frpc 提供。本插件不启动、不配置、也绝不接触访问密钥。 面板预建一条 TCP 隧道,并且:

  • 隧道远程端口必须等于 profile 里 mobile-access.listenPort(插件硬约束:显式 authority 的端口必须等于监听端口);
  • 隧道不要设置「访问密码」——那会让 frpc 接管 TLS 去渲染认证页,破坏端到端自签证书与 Host 校验;
  • Windows 端 frpc 经 WSL2 networkingMode=mirrored 的 localhost 直通,回连 WSL 回环监听。

2.3 运行期软依赖

项目必需用途
dsh-mobile-lan-watchdog.service(本仓库 scripts/dsh-mobile-lan-watchdog.sh网关被静默关闭时的第二层自愈;插件自带 15s 自愈已可独立兜底
systemctl --user外部守护服务的启停

2.4 平台与版本

项目要求
操作系统Linux / WSL2(插件用 systemctl --user、路径按 POSIX 拼)
Node>= 20(用到全局 fetchAbortSignal.timeout
dsh 核心0.1.5-rc.1 系列的 web profile 上验证;其它版本自测
dsh profileweb$DSH_HOME/profiles/web

2.5 dsh-dependencies 声明

除 README 外,package.json 里也带了机器可读的声明:

"peerDependencies": { "dsh-mobile": "*" },
"dshDependencies": {
  "core": ">=0.1.5-rc.1",
  "plugins": [{ "name": "dsh-mobile", "required": true, "provides": "..." }],
  "programs": [{ "name": "SakuraFrp 启动器 / frpc", "required": true, "platform": "Windows" }],
  "services": [{ "name": "dsh-mobile-lan-watchdog.service", "required": false }],
  "platform": ["Linux", "WSL2"], "node": ">=20"
}

3. 安装

3.1 一键安装(推荐)

git clone https://github.com/ankhishtar2-lang/dsh-sakurafrp.git
cd dsh-sakurafrp
bash scripts/install.sh          # 拷贝到 profile + 注册 bundles + 静态自检
systemctl --user restart dsh-web-profiled.service   # 生效(由你手动执行)

scripts/install.sh 只做三件事:把包拷进 $DSH_HOME/profiles/web/node_modules/、 把 dsh-sakurafrp 追加进 profile package.jsondsh.profile.bundles、做静态自检 (host 是否有 apply 导出是硬红线:缺了会让整棵插件树 boot 失败)。它也不会替你重启。

3.2 手动安装

cp -r dsh-sakurafrp "$DSH_HOME/profiles/web/node_modules/"
# 再把 "dsh-sakurafrp" 加进 $DSH_HOME/profiles/web/package.json 的 dsh.profile.bundles
dsh --profile web --dump-config >/dev/null && echo 配置可解析
systemctl --user restart dsh-web-profiled.service

3.3 前置:配置 dsh-mobile 的移动网关

$DSH_HOME/profiles/web/cordis.patch.yml 里加一段(值都要换成你自己的):

- id: mobile-access
  config:
    initiallyEnabled: true
    maxDevices: 1                     # 只信任一台设备就写 1
    pairingTtlMs: 300000              # 配对窗口 5 分钟
    listenHost: 127.0.0.1
    listenPort: 33782                 # 必须等于 SakuraFrp 隧道的远程端口
    publicAuthorities:
      - node.example.com:33782        # 手机访问时用的 Host,必须与隧道入口一致
      - yd.node.example.com:33782     # SakuraFrp 运营商前缀节点(可选)
    allowedCidrs: ['127.0.0.0/8', '::1/128']   # 隧道过来的源地址恒为回环
    upstreamOrigin: http://127.0.0.1:3080
    tls:
      mode: provided
      certFile: !!js dshHomePath('mobile-access/tls/server.crt')
      keyFile: !!js dshHomePath('mobile-access/tls/server.key')
    pairingCaFile: !!js dshHomePath('mobile-access/tls/ca.crt')
    instanceId: '<你的自签 CA 的 SHA-256 指纹,小写去冒号>'   # 必须与 CA 指纹完全一致

⚠️ 两个会让你起不来 dsh 的坑(作者踩过)

  1. pairingCaFile 配了却漏了 instanceId:插件会拿 sha256(stateFile 路径) 当 instanceId, 永远对不上 CA 指纹 → start() 抛错 → loader fail-fast → 整个 dsh web 起不来
  2. maxDevices 在「配对准入」时只数活跃设备,但在启动加载时数全部记录(含已撤销)。 撤销过设备后忘记清理、又把 maxDevices 改小,重启即崩。本仓库的 scripts/dsh-mobile-security-audit.sh 会自动清理已撤销记录。

安装方式补充:从 npm 安装(可选)

本包的 package.json 已按 npm 发布要求准备好(去掉 private、用 files 白名单控制内容)。 发布到 npm 后即可用 dsh 自己的命令安装(dsh plugin add 本质就是 pnpm add):

dsh plugin --profile web add dsh-sakurafrp

自己发布(需要你自己的 npm 账号scripts/publish.sh 不接触也不保存任何 token):

npm login
bash scripts/publish.sh --dry     # 自检 + 列出将要发布的文件,不发布
bash scripts/publish.sh           # 真正发布(改过代码要先升 version,同版本号不可覆盖)

发布后请同步更新 README 与上游收录表的描述,保持「描述属实」这一条成立。

4. 配置(环境变量)

不想改 profile 的话,全部行为都可以用环境变量覆盖(在 systemd unit 或启动脚本里设):

变量默认值说明
DSH_SAKURA_ORIGINhttps://node.example.com:33782(占位符)必填:你自己的公网入口。占位符不改的话探测与配对都会失败
DSH_SAKURA_AUTHORITIES额外探测的公网权威(逗号分隔),主入口自动取自 origin;SakuraFrp 的 yd./dx./lt. 前缀节点写在这里
DSH_SAKURA_SELFHEAL_MS15000网关自愈轮询间隔
DSH_SAKURA_PROBE_MS60000公网节点探测间隔
DSH_SAKURA_LOG$HOME/dsh/memory/dsh-sakurafrp.log事件日志(自动以 0600 创建)
DSH_SAKURA_WATCHDOG_UNITdsh-mobile-lan-watchdog.service外部守护服务单元名
DSH_SAKURA_SUPPRESS_FILE$DSH_HOME/mobile-access/.gateway-user-disabled「用户已关闭手机访问」的抑制标记路径;存在时自愈与 watchdog 都让路
DSH_SAKURA_ADMIN_TOKEN未设设了之后变更类路由的标识头必须是这个值(把公开的 1 换成真凭证)
DSH_SAKURA_ALLOW_REMOTE_ADMIN未设设为 1取消「仅电脑端」闸门(见第 6 节,不建议)

📌 与本机实际运行版本的差异:作者本机那份把 DSH_SAKURA_ORIGIN 默认值写成了自己的隧道入口 (省去配置环境变量)。为了不公开个人隧道地址,本仓库把默认值改成了占位符 https://node.example.com:33782,并且默认不再探测 yd./dx./lt. 前缀节点。 因此从本仓库安装后,必须显式设置 DSH_SAKURA_ORIGIN(必要时再设 DSH_SAKURA_AUTHORITIES)。


5. HTTP 路由

全部注册在 dsh web(默认 3080)上,并且只接受回环来源(非回环一律 403):

方法路径仅电脑端说明
GET/dsh-sakurafrp/status否(手机端会脱敏)聚合状态:网关(含 suppressed)/探测/设备/守护/自愈计数
POST/dsh-sakurafrp/control{running: bool} 开关手机访问;关闭会写抑制标记,开启会清除
POST/dsh-sakurafrp/pair申请配对窗口,返回 pairUrl + 内联 qrDataUrl
GET/dsh-sakurafrp/qr.svg最近一次二维码 SVG(调试用;前端已改用内联 data URL)
POST/dsh-sakurafrp/revoke{deviceId} 撤销设备
POST/dsh-sakurafrp/devices/reset清空全部配对设备(向 dsh-mobile 发送 confirm: true
POST/dsh-sakurafrp/selfheal立刻执行一次「确保网关在跑」;用户已关闭则跳过
POST/dsh-sakurafrp/probe立刻探测全部公网节点
POST/dsh-sakurafrp/watchdog{active: bool} 启停外部守护服务

6. 安全说明(请务必读)

6.1 为什么有「仅电脑端」闸门

dsh-mobile 的移动网关是一个带特权上游 cookie 的反向代理:它把除 /mobile-access/* 以外的 一切路径转发到 dsh web(3080),并附上它用 DSH 进程内 launch token 换来的认证 cookie。 后果是:任何已配对设备都能触达 3080 上注册的全部插件路由,而来源地址看上去都是 127.0.0.1

作者在安全审查中实测过(用真实配对设备从公网):

请求加固前加固后
POST /dsh-sakurafrp/pair200(手机可自行铸造配对码、再拉设备进来)403 desktop_only
POST /dsh-sakurafrp/watchdog {"active":false}200(守护服务被停 → 自愈失效)403 desktop_only
POST /dsh-whale-tools/restart200(dsh 真的被重启403 desktop_only

闸门怎么工作(三道,全部要过)

  1. 来源地址必须是回环;
  2. Origin / Referer 若存在,必须是回环(127.0.0.1 / ::1 / localhost)。 网关转发时会用白名单重建请求头(只放行 accept* / content-* / if-* / range / user-agent / origin / sec-fetch-site)—— 注意 origin 在白名单里的,所以手机经隧道访问时服务端能看到的 Origin隧道域名,而不是回环;恶意网页则是攻击者域名。两者都会被拒。 这两个头由浏览器自己填,页面脚本改不了;
  3. 请求头 x-dsh-sakurafrp-desktop: 1。设了 DSH_SAKURA_ADMIN_TOKEN 时, 这里必须是那个令牌的值(真凭证)。

它是什么、不是什么(请如实理解):第 3 条那个头是区分器,不是认证——它的值公开写在 本 README 与源码里。真正的边界是:3080 只绑 127.0.0.1 + 网关会剥掉自定义头 + 能在本机发请求的进程本就拥有同等用户权限(可以直接调 loopback 管理接口)。 所以本闸门挡的是浏览器跨站请求与经公网隧道的设备,不是把本机进程沙箱化。

  • 逃生舱:DSH_SAKURA_ALLOW_REMOTE_ADMIN=1 会让手机端也能管理(等于把上面三道全关掉,不建议)。
  • 本插件不接触任何密钥:SakuraFrp 访问密钥始终只在 Windows 启动器手里; 前端只处理一次性、短时有效的配对 token。
  • 事件日志以 0600 创建,内容不含密钥与 token。

6.2 安全边界在哪

真正的安全边界是「设备配对」本身:配对成功后,那台设备就拥有完整的 DSH 会话 (能驱动 agent、读写会话)——这是移动访问的功能本意。本插件收紧的是控制面 (铸造配对码、开关网关、撤销设备、停守护、重启 dsh),不是会话面。 手机丢失/借人 ≈ 把 DSH 交出去;要收回就在电脑端面板撤销该设备。


7. 仓库脚本

脚本用途
scripts/install.sh幂等安装(拷贝 + 注册 bundles + 静态自检)
scripts/uninstall.sh卸载(移除 bundles + 删目录)
scripts/dsh-mobile-lan-watchdog.sh移动网关守护:发现 running:false 就拉起(systemd user 服务用)
scripts/dsh-mobile-security-audit.sh可复跑的审查脚本:匿名面 / 文件权限 / 「已配对设备越权」实测。⚠️ 它并不是完全非破坏性的:为了实测越权,它会真的在本机配对一台临时设备,因此会改写 ~/.dsh/mobile-access/devices.json(0600)。脚本结束时会清理该记录,但中途被打断会留下一条有效设备记录——跑完请用面板确认设备列表,或直接跑 devices/reset
scripts/legacy/dsh-mobile-pair.sh早期命令行配对脚本,已被插件面板取代,留作备用

审计脚本用法:

bash scripts/dsh-mobile-security-audit.sh [公网origin]
# 关键项全过 → 退出码 0;发现越权/暴露 → 1

8. 已验证 / 已知限制

已在作者本机验证

  • 4 个公网节点探测全部 200 {"ok":true},延迟 161–235 ms(真实隧道,自签证书校验关闭);
  • 真实配对 → 带会话拉取手机端首页 200;手机端所有控制面请求 403;
  • host 半桩测试 37 项 + 浏览器半 bundle 复核;dsh --profile web --dump-config 退出码 0;
  • 网关被静默关闭后 15 秒内自愈(多次实测)。

已知限制

  • 只做「管理壳」,不提供隧道:隧道断了要去看 SakuraFrp 启动器/面板;
  • 依赖 dsh-mobile 的内部管理接口,上游改路径就会失效(接口非公开契约);
  • 公网探测会周期性向公网节点发 HTTPS 请求(每 60 秒 4 条,可 DSH_SAKURA_PROBE_MS 调大或关掉插件);
  • 面板为原生 DOM + 内联样式,深色/浅色主题都做了变量兜底,但主题差异大的皮肤下观感可能一般;
  • 手机端只能读状态,这是设计而非缺陷。

9. 卸载

bash scripts/uninstall.sh
systemctl --user restart dsh-web-profiled.service

卸载本插件不会影响:SakuraFrp 隧道、mobile-access 配置与自签证书、设备状态文件、 外部守护服务。这些要单独处理(见第 3.3 节的配置段)。


10. 目录结构

dsh-sakurafrp/
├── package.json          # dsh.bundle / dsh.client / peerDependencies / dshDependencies
├── cordis.patch.yml      # loader 条目:id=sakurafrp, name=dsh-sakurafrp
├── lib/
│   ├── index.js          # host 半:9 条路由 + 网关自愈(尊重用户关闭)+ 公网探测 + 桌面闸门
│   └── client.js         # 浏览器半:头部手机按钮 + 管理面板 + 每秒倒计时
├── scripts/              # install.sh / uninstall.sh / publish.sh / 守护 / 安全审计 / 早期配对
├── LICENSE               # MIT
└── README.md

11. 许可

MIT © 2026 ankhishtar2-lang —— 见 LICENSE。 再次提醒:vibe coding 产物,未经人工逐行审计,按「原样」提供,风险自负。

Related plugins