dsh-sakurafrp
dsh-mobile satellite: manage the SakuraFrp phone-desktop link from DSH UI (status, gateway toggle, pairing QR, devices, self-heal). Vibe-coded.
- Stars
- 0
- Language
- JavaScript
- Created
- Sep 11, 2026
- Updated
- Sep 11, 2026
Introduction
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 分钟有效),二维码与链接内联返回,手机相机/浏览器扫码即可 |
| 设备管理 | 逐台撤销、一键清空;配合 maxDevices 实现「只信任某一台设备」 |
| 网关自愈 | 每 15 秒检查移动网关,发现被静默关闭(已知问题)立刻重新拉起 |
| 公网可达性探测 | 每 60 秒并发探测主入口 + 三个运营商前缀节点,面板直接显示谁通谁不通 |
| 外部守护开关 | 可启停 dsh-mobile-lan-watchdog.service(冗余层,插件自带自愈已能独立兜底) |
入口: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(用到全局 fetch、AbortSignal.timeout) |
| dsh 核心 | 在 0.1.5-rc.1 系列的 web profile 上验证;其它版本自测 |
| dsh profile | web($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.json 的 dsh.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 的坑(作者踩过)
pairingCaFile配了却漏了instanceId:插件会拿sha256(stateFile 路径)当 instanceId, 永远对不上 CA 指纹 →start()抛错 → loader fail-fast → 整个 dsh web 起不来。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_ORIGIN | https://node.example.com:33782(占位符) | 必填:你自己的公网入口。占位符不改的话探测与配对都会失败 |
DSH_SAKURA_AUTHORITIES | 空 | 额外探测的公网权威(逗号分隔),主入口自动取自 origin;SakuraFrp 的 yd./dx./lt. 前缀节点写在这里 |
DSH_SAKURA_SELFHEAL_MS | 15000 | 网关自愈轮询间隔 |
DSH_SAKURA_PROBE_MS | 60000 | 公网节点探测间隔 |
DSH_SAKURA_LOG | $HOME/dsh/memory/dsh-sakurafrp.log | 事件日志(自动以 0600 创建) |
DSH_SAKURA_WATCHDOG_UNIT | dsh-mobile-lan-watchdog.service | 外部守护服务单元名 |
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 | 否(手机端会脱敏) | 聚合状态:网关/探测/设备/守护/自愈计数 |
| 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 | ✅ | 清空全部配对设备 |
| 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/pair | 200(手机可自行铸造配对码、再拉设备进来) | 403 desktop_only |
POST /dsh-sakurafrp/watchdog {"active":false} | 200(守护服务被停 → 自愈失效) | 403 desktop_only |
POST /dsh-whale-tools/restart | 200(dsh 真的被重启) | 403 desktop_only |
闸门怎么工作:网关转发时会用白名单重建请求头(只放行
accept* / content-* / if-* / range / user-agent / origin / sec-fetch-site),
所以任何自定义头经网关都会被剥掉,而电脑端浏览器直连 3080 时原样送达。
本插件的电脑端请求都带 x-dsh-sakurafrp-desktop: 1,服务端对变更类路由要求该头。
- 逃生舱:
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 | 可复跑的安全审查:匿名面 / 文件权限 / 「已配对设备越权」实测 / 自动清理设备记录(刻意非破坏性:不碰 watchdog 与 restart) |
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/ # 安装/卸载/守护/安全审计/早期配对脚本
├── LICENSE # MIT
└── README.md
11. 许可
MIT © 2026 ankhishtar2-lang —— 见 LICENSE。 再次提醒:vibe coding 产物,未经人工逐行审计,按「原样」提供,风险自负。