directory-picker-auto 按服务器侧事实选后端:native 要求「仅绑回环 + 非 SSH + 有显示器」。本插件让官方服务器保持回环,于是局域网部署仍被判为 native,点 「添加工作区」会在服务器本机弹原生对话框,远程操作者看不到。 按官方文档锁定 browse 这一对(host-directory-picker-browse + client-ui-directory-picker-browse),它在页面内列出服务器目录树,可服务远程客户端。 取舍:该 seam 每进程只能挂一个后端,本地页面也会改用页面内浏览;删除 patch 里 最后三行可恢复自适应选择。README 中英双语已补充说明。
8.5 KiB
@sutong/dsh-lan-access
局域网访问 dsh Web UI:一个独立反向代理,在 0.0.0.0:<port> 监听,把 HTTP 与 WebSocket 转发到回环 web 服务器,并保留官方 launch token 登录。
简体中文 | English
为什么需要插件
dsh web --host 0.0.0.0 被官方 CLI 主动拒绝(@deepseek-ai/dsh-web-app/lib/startup.js:会把这台机器的远程代码执行能力暴露到网络)。所以官方 web 服务器始终只绑回环,局域网访问必须由插件在另一个端口上另开监听并转发。
与 dsh-lan-proxy 的区别
社区插件 @wingsky-1/dsh-lan-proxy 也做转发,但设计取向不同:
| 本插件 | dsh-lan-proxy | |
|---|---|---|
| 默认登录 | 必须持有 launch token(横幅/二维码里的链接) | injectToken: true,局域网内任何设备免 token 直入 |
上游 Host |
重写为回环(官方围栏通过) | 重写为回环 |
Set-Cookie |
不改动(浏览器按请求 URL 建立 host-only cookie,转发侧无需干预) | 需配合 token 注入与 401 重放 |
| WebSocket | 握手按字节转发,permessage-deflate 端到端协商,代理不解压任何帧 |
压缩桥接(存在解压放大面) |
| 官方行覆盖 | 不覆盖任何 shipped row,失败/禁用不会影响回环 UI | 插入自身行 + 兼容注入 |
端口冲突:dsh-lan-proxy 默认占用
3081,因此本插件默认用3082。两者可共存,但同时启用等于把 UI 暴露两次;建议只留一个。
安装
dsh plugin --profile web add /path/to/dsh-lan-access
安装后重启一次 dsh web:
# 重启后终端会打印带 token 的局域网链接
或在本仓库内用本地目录安装(Profile → Plugins → 本地安装)。
配置
配置写在插件行的 config 里(Host Profile → Plugins → 本插件 → Configure,或直接改 cordis.patch.yml)。
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关;关闭后不再监听 |
host |
0.0.0.0 |
监听地址 |
port |
3082 |
监听端口(避开 dsh-lan-proxy 的 3081) |
targetHost |
127.0.0.1 |
上游主机。仅接受回环地址,否则插件拒绝启动(防止变成开放转发/SSRF) |
targetPort |
0 |
上游端口;0 = 跟随 webServer 实际端口 |
printBanner |
true |
启动时打印局域网链接 |
使用
-
终端横幅(启动时):
dsh-lan-access: 0.0.0.0:3082 → http://127.0.0.1:3080 (LAN listener) dsh-lan-access: http://192.168.1.5:3082/?token=XXXXXXXX -
Web UI 面板:Settings → 局域网访问(插件自带的设置页)。列出全部局域网地址、可复制、可直接打开,并为你选中的地址渲染二维码,手机扫一下即可进入。
链接里的 token 只在首次访问用到:服务器校验后会铸一个 30 天有效的 cookie 并 302 跳到干净的 ./,token 不会留在地址栏或历史记录里。之后该设备靠 cookie 直接进入。
安全模型
- 认证不放松:不注入 token、不绕过围栏。局域网设备必须拿到横幅或二维码里的链接。这比"信任整个局域网"更保守。
- 官方围栏照旧生效:
/api只接受回环或已声明的 authority,且Origin必须与Host同源;本插件把上游Host/Origin重写为回环,因此这些检查全部通过而不是被绕过。 - DNS 重绑定防护:监听器只接受
localhost、回环地址、本插件公布的局域网 IP 字面量,或它自己的监听地址;域名一律断开。 - 上游锁定回环:
targetHost非回环直接拒绝启动,避免开放代理。 - 无额外解压面:中间不做响应/帧压缩,也不解压 WebSocket 帧。
- 面板数据路由:
/api/dsh-lan-access/summary挂在官方 web 服务器上,并复用官方connection.admit()(同一套围栏 + 浏览器认证)——只有已登录页面能读到 token。 - ⚠️ 风险面:拿到该链接的设备拥有完整 dsh 控制权,包括在宿主机执行命令。请只在可信局域网启用;不使用时卸载或关闭插件。列表里的每一条链接都是一把钥匙。
局域网页面上的设置界面(ownsHostCompat)
DSH 官方把「持久化设置」限制在回环页面:客户端由 location.hostname 推导 isLoopback
(dsh-client-connection/lib/client.js),非回环页面得到 persistence = "memory"
(dsh-client-ui-settings/lib/client.js),于是设置镜像永远没有 view,模型/提供商页会报
settings are unavailable in this browser,General 页的「打开配置文件」也会消失。
这与转发是否正确无关:任何局域网暴露方式都会遇到,用 127.0.0.1:3080 打开则一切正常。
服务端也没有对应的回环门禁(/api 围栏 + token/cookie 才是授权面),所以本插件提供一个开关:
| 键 | 默认 | 说明 |
|---|---|---|
ownsHostCompat |
false |
为非回环页面声明 ownsHost,恢复官方设置界面(回环页面不受影响) |
本 profile 已设为 true。取舍:该页面因此可读写服务器设置(含凭据)。考虑到持有带 token
链接的设备本就拥有完整控制权(含在宿主机执行命令),这不算新增一类能力,但确实绕过了官方的
回环隔离 —— 设为 false 即回到官方姿态,设置请在本机通过 127.0.0.1:3080(或
ssh -L 3080:127.0.0.1:3080 <主机> 隧道)修改。
添加工作区的目录选择器
directory-picker-auto 在启动时按服务器侧事实选一个后端:native 要求「仅绑回环 + 非 SSH +
有可用显示器」,理由是「全网卡绑定意味着存在操作系统对话框够不到的远程浏览器」。本插件刻意让官方
服务器保持回环(LAN 由 3082 独立转发),于是局域网部署仍被判为 native —— 对话框会在服务器
本机弹出,远程操作者既看不到也点不到。
因此本插件的 patch 按官方文档的方式锁定 browse 这一对(directory-picker-auto 的文档写明
「pinning an interaction remains composing that pair directly instead of this row」):
@deepseek-ai/dsh-host-directory-picker-browse(后端)@deepseek-ai/dsh-client-ui-directory-picker-browse(界面)
该后端在页面内列出服务器目录树,官方描述为「serves remote clients the dialog backend cannot」。
取舍:这个 seam 每个进程只能挂一个后端,所以本地页面也会改用页面内浏览,不再弹操作系统
对话框(也就是说"本地原生、远程网页"无法并存)。想恢复自适应选择,删掉 cordis.patch.yml 里
最后那三行即可(- id: directory-picker / disabled: true 与其后的 insert 块)。
排障
| 现象 | 原因与处理 |
|---|---|
终端出现 LAN listener unavailable ... EADDRINUSE |
端口被占用(常见:dsh-lan-proxy 占着 3081)。改 port,或停用另一个插件。面板会同时显示该错误,不会展示不可用的二维码。 |
| 局域网设备打开后 401 | 用的是不带 token 的裸地址,且该设备还没有 cookie。改用横幅/面板里带 ?token= 的链接。 |
| 重启后旧书签失效 | launch token 每次启动都会变。已登录过的设备靠 cookie 仍然有效;否则重新用新链接进入。 |
| 面板显示"未找到非内部 IPv4 地址" | 这台机器当前没有可用的局域网 IPv4(只有回环)。 |
| 扫码无反应 | 部分机型对深色背景上的二维码识别差;面板里的二维码固定渲染在白底卡片上。适当调高屏幕亮度。 |
开发
node scripts/build-client.mjs # 由 src/panel.js + src/qrcode.js 生成 client.js
node scripts/smoke.mjs # 宿主端转发契约(HTTP/WS/Host/Origin/cookie/绑定失败)
node scripts/test-qr.mjs # 与 npm qrcode 参考实现逐模块比对
node scripts/test-client.mjs # 浏览器端契约(槽位注册、渲染、路由一致)
client.js 是生成产物,请改 src/ 后重新构建。
二维码编码器(src/qrcode.js)是零依赖自实现(byte 模式 / ECC M / version 1–10),与 npm qrcode 参考实现做了全版本 × 全掩码的逐模块比对(240/240 一致)。
回退
在 Plugins 页停用或卸载本插件即可;因为本插件不覆盖任何官方行,停用只会让局域网监听消失,回环 dsh web 不受影响。
许可
MIT