@yezack/dsh-remote-mobile 1.9.2 → 1.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  # dsh-remote-mobile
8
8
 
9
- **DeepSeek Harness (DSH) 远程与移动端安全网关插件**
9
+ **DeepSeek Harness (DSH) 远程与移动端安全接入代理插件**
10
10
 
11
11
  [![npm version](https://img.shields.io/npm/v/dsh-remote-mobile.svg?style=flat-square&color=3b82f6)](https://www.npmjs.com/package/dsh-remote-mobile)
12
12
  [![npm downloads](https://img.shields.io/npm/dm/dsh-remote-mobile.svg?style=flat-square&color=22c55e)](https://www.npmjs.com/package/dsh-remote-mobile)
@@ -16,7 +16,7 @@
16
16
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/IceApriler/dsh-remote-mobile/pulls)
17
17
 
18
18
  <p align="center">
19
- <b>零改动底层代码 · 局域网 / Tailscale 安全接入 · 扫码配对 · 移动端样式适配 · 工作区能力复用 · 实时状态推送</b>
19
+ <b>无需修改 DSH 源码(运行时插件接管) · 局域网 / Tailscale 安全接入 · 扫码配对 · 移动端样式适配 · 工作区能力复用 · 实时状态推送</b>
20
20
  </p>
21
21
 
22
22
  <p align="center">
@@ -31,7 +31,8 @@
31
31
  <a href="#about">这是什么</a> •
32
32
  <a href="#advantages">核心优势</a> •
33
33
  <a href="#quick-start">快速开始</a> •
34
- <a href="#config">必备配置</a> •
34
+ <a href="#modes">接入模式</a> •
35
+ <a href="#config">网络配置</a> •
35
36
  <a href="#preview">界面预览</a> •
36
37
  <a href="#features">功能特性</a> •
37
38
  <a href="#install">安装与更新</a> •
@@ -49,7 +50,7 @@
49
50
 
50
51
  `dsh-remote-mobile` 是专为 **DeepSeek Harness (DSH)** 深度定制的远程与移动端安全治理插件。
51
52
 
52
- DSH 核心服务出于安全考虑默认仅监听本地回环地址(`127.0.0.1`),手机、平板或其他电脑无法从外部访问 Web 控制台。本项目通过**访问控制中间件**与**请求隔离技术**,安全地开放了 **Tailscale 虚拟私网** 以及 **本地局域网 (Wi-Fi/LAN)** 访问能力,并提供传输加密、扫码配对、长期密码认证与防暴力破解审计体系。
53
+ DSH 核心服务出于安全考虑默认仅监听本地回环地址(`127.0.0.1`),手机、平板或其他电脑无法从外部访问 Web 控制台。本项目通过**访问控制中间件**与**请求隔离技术**,安全地开放了 **Tailscale 虚拟私网** 以及 **本地局域网 (Wi-Fi/LAN)** 访问能力,并提供登录凭证 RSA 加密、扫码配对、长期密码认证与防暴力破解审计体系。插件本身不提供全链路 TLS;完整传输加密需依赖 Tailscale 或外层 HTTPS。
53
54
 
54
55
  手机端访问时,插件还针对 DSH 界面做了**移动端样式适配**:侧边栏抽屉折叠与可拖拽展开把手、新版官方三栏 Grid 布局与右侧边栏兼容、设置弹窗居中微缩、对话正文高密度排版等,让手机上的使用体验更贴近桌面端。
55
56
 
@@ -60,10 +61,10 @@ DSH 核心服务出于安全考虑默认仅监听本地回环地址(`127.0.0.1
60
61
 
61
62
  * 🚀 **突破网络壁垒,复用桌面端能力**:用于在局域网 / Tailscale 下访问 DSH Web。在移动端或外部设备访问时,**支持新建工作区、切换工作区、执行终端命令等常用桌面端操作**。
62
63
  * 📱 **手机端专属适配与同源复用**:直接复用 DSH 官方 Web 前端(手机与桌面是同一套界面)与各类生态插件(如侧边栏、任务看板、终端面板等),无需单独维护手机端后台。插件已针对移动端做好了基础响应式适配(如侧边栏抽屉折叠、设置弹窗居中、对话正文字号与行距精细调小以提升一屏信息量),并内置**样式片段覆写功能**,支持开箱即用或按需注入自定义 CSS 样式小插件。
63
- * 🛡️ **安全门禁与传输加密**:内置 RSA 非对称公钥加密、`scrypt` 加盐慢哈希密码落盘、连续输错自动熔断锁定 IP,拦截公网/局域网未经授权的访问。
64
- * 📲 **扫码快速配对**:自动识别并生成 Tailscale CGNAT 与局域网专属访问链接与二维码,手机扫码即可完成长效授权。
65
- * 🔄 **SSE 实时状态推送**:设备上线、下线、注销、IP 锁定等安全事件即时推送,无需前端轮询。
66
- * 🤝 **插件通用共存保护**:与其他远程/Web 接入类插件并存时自动让出共享服务、保证正常启动,设置页提供冲突警示横幅与一键复制的「诊断与修复报告」。
64
+ * 🛡️ **安全门禁与登录凭证加密**:登录凭证使用 RSA 非对称公钥加密,`scrypt` 加盐慢哈希密码落盘,连续输错自动熔断锁定 IP,拦截公网/局域网未经授权的访问。
65
+ * 📲 **扫码快速配对**:自动识别并生成 Tailscale CGNAT 与局域网专属访问链接与二维码,手机扫码打开已预填配对码的登录页,确认后完成授权。
66
+ * 🔄 **SSE 实时状态推送**:设备接入、重连、注销、IP 锁定等事件通过 SSE 推送;设置页仍保留低频轮询作为兜底。
67
+ * 🤝 **插件通用共存保护**:与其他远程/Web 接入类插件并存时自动让出共享服务,尽量避免共存冲突导致启动失败,设置页提供冲突警示横幅与一键复制的「诊断与修复报告」。
67
68
 
68
69
  ---
69
70
 
@@ -84,23 +85,53 @@ dsh plugin --profile web add dsh-remote-mobile
84
85
  ---
85
86
 
86
87
  <span id="config"></span>
87
- ### 2. 前置配置(开放外部监听)
88
+ ### 2. 前置配置(插件 1.10.0+ 无需 webserver 配置)
88
89
 
89
- 由于 DSH 默认仅监听 `127.0.0.1`,为了使 Tailscale 私网或局域网设备能够正常连通,请确保在 `~/.dsh/profiles/web/cordis.patch.yml`(Windows 对应 `C:\Users\<用户名>\.dsh\profiles\web\cordis.patch.yml` 或在资源管理器直接输入 `%USERPROFILE%\.dsh`)中包含以下配置:
90
+ 本插件 1.10.0+ 不需要修改 `webserver` 或 `cordis.patch.yml`:
91
+
92
+ - DSH 0.2.1+ 保持默认 `127.0.0.1:3080`;
93
+ - 本插件自动启动 `3081` 安全接入代理;
94
+ - 手机扫码或使用长期密码即可接入。
95
+
96
+ > 本节适用于本插件 1.10.0+。
97
+
98
+ > **安全权衡说明**:安全接入代理默认监听 `0.0.0.0:3081`(暂不含 TLS)。若在公共 Wi-Fi 或未经保护的网络下使用,建议配合 Tailscale(WireGuard 隧道加密)或外层 HTTPS。
99
+
100
+ <details>
101
+ <summary><b>插件 1.9.0 之前的配置迁移(DSH 0.2.1+)</b></summary>
102
+
103
+ 本插件 1.9.0 之前的配置,或曾手动配置 `webserver.host: 0.0.0.0` 的配置,需要按以下步骤迁移。本插件 1.10.0+ 可直接跳过本节。
104
+
105
+ DSH 0.2.1+ 会拒绝 `0.0.0.0`,并在插件加载前直接启动失败。请删除或注释整个 `webserver` 配置块:
90
106
 
91
107
  ```yaml
92
- # 1. 允许 webserver 监听外部网络连接(必选)
93
- - id: webserver
94
- name: '@deepseek-ai/dsh-host-webserver'
95
- inject: [webStartup]
96
- config:
97
- host: '0.0.0.0'
98
- port: 3080
99
-
100
- # 2. (推荐)只保留一款远程/Web 接入类插件,避免功能重复与入口混乱
101
- # 本插件与其他远程接入类插件共存时也能正常启动:检测到配对共享服务被占用
102
- # 会自动让出,并在设置页顶部展示警示横幅说明详情。
103
- # 示例:禁用 @linxin666/dsh-web-ui-all 内置的远程插件
108
+ # ❌ DSH 0.2.1+ 下会启动失败
109
+ # - id: webserver
110
+ # host: '0.0.0.0'
111
+
112
+ # ✅ 保持默认回环监听
113
+ # 无需配置 webserver;DSH 使用 127.0.0.1:3080,
114
+ # dsh-remote-mobile 自动使用 3081 安全接入代理。
115
+ ```
116
+
117
+ 不确定当前配置时:
118
+
119
+ ```bash
120
+ grep -n -A5 'id: webserver' ~/.dsh/profiles/web/cordis.patch.yml
121
+ ```
122
+
123
+ 若看到 `host: '0.0.0.0'`,删除对应配置块;没有则无需处理。
124
+
125
+ DSH 0.1.7 ~ 0.2.0 可以保留 `0.0.0.0`,插件会回退 `direct` 模式;若通过自定义域名或公网 IP 访问,请配置 `trustedHosts`。
126
+ </details>
127
+
128
+ 如果您安装了其他可能产生冲突的插件,推荐在 `~/.dsh/profiles/web/cordis.patch.yml`(Windows 对应 `%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml`)中禁用冲突项:
129
+
130
+ ```yaml
131
+ # (推荐)只保留一款远程/Web 接入类插件,避免功能重复与入口混乱
132
+ # 本插件与其他远程接入类插件共存时也能正常启动:检测到配对共享服务被占用
133
+ # 会自动让出,并在设置页顶部展示警示横幅说明详情。
134
+ # 示例:禁用 @linxin666/dsh-web-ui-all 内置的远程插件
104
135
  - id: web-ui-remote-web-ui
105
136
  disabled: true
106
137
  ```
@@ -118,12 +149,77 @@ dsh plugin --profile web add dsh-remote-mobile
118
149
  dsh web --no-open
119
150
  ```
120
151
 
121
- 启动后,在电脑浏览器打开 DSH Web 控制台,进入 **设置 ⚙️ -> 远程与移动端**,使用手机微信或系统相机扫描二维码即可立即开启移动端接入!
152
+ 启动后,在电脑浏览器打开 DSH Web 控制台,进入 **设置 ⚙️ -> 远程与移动端**,使用手机微信或系统相机扫描二维码进入配对/登录页,完成授权后即可使用。
122
153
 
123
154
  > 设置面板按 **「接入 / 设备与安全 / 样式覆写 / 本地数据」** 四个页签分组显示,避免长页面;页签选择会被记住,切换不重置配对码等状态。
124
155
 
125
156
  ---
126
157
 
158
+ <span id="modes"></span>
159
+ ## 🧭 版本与接入模式
160
+
161
+ 本插件 1.10.0+ 默认使用 `proxy`,不需要配置 `webserver`:
162
+
163
+ | 场景 | DSH 核心监听 | 生效模式 | 手机访问 |
164
+ |---|---|---|---|
165
+ | DSH 0.2.1+ + 插件 1.10.0+ | `127.0.0.1:3080` | `proxy` | `3081` |
166
+ | DSH 0.1.7 ~ 0.2.0 + `0.0.0.0` 配置 | `0.0.0.0:3080` | `direct`(自动回退) | `3080` |
167
+ | DSH 0.2.1+ + 具体网卡 IP | `192.x.x.x:3080` | `direct` | `3080` |
168
+ | 同机反向代理 | `127.0.0.1:3080` + 显式 `direct` | `direct` | 反代入口 |
169
+
170
+ - **`proxy`**:推荐;核心保持回环,插件在 `3081` 提供认证与转发。
171
+ - **`direct`**:不启动代理;要求核心本身对外可达,主要用于 DSH 0.1.7 ~ 0.2.0 的 `0.0.0.0` 配置或高级部署。
172
+ - **`direct` + 回环核心**:DSH 只监听 `127.0.0.1`,手机无法直接访问;只有再通过同机 Nginx / Caddy / Tailscale Serve 等反向代理转发到 `3080` 时才有意义,否则只能本机使用。
173
+
174
+ #### 访问链路与认证判定
175
+
176
+ 判定不能只看 TCP 来源 IP(socket 地址):Host 与 `X-Forwarded-*` 也会改变结果。`socket IP` 是服务器看到的 TCP 对端地址,不是 Host,也不是 `X-Forwarded-For`。
177
+
178
+ | 访问方式 | 链路 | 判定 | 插件登录 |
179
+ |---|---|---|---|
180
+ | A 本机直连 | PC → `127.0.0.1:3081` | 本机回环(loopback) | 不需要 |
181
+ | B 手机直连 | 手机 → `192.x.x.x:3081` / `100.x.x.x:3081` | 外部直连(external) | 需要(可开 `allowLan`/`allowTailscale` 免密) |
182
+ | C 同机反向代理 | 手机 → Nginx → `127.0.0.1:3081` | 反向代理(reverse-proxy) | 需要(不享受免密) |
183
+
184
+ 这里的“反向代理”指用户侧的 Nginx / Caddy / Tailscale Serve,不是插件的 3081 安全接入代理。
185
+
186
+ `127.0.0.1` 仅在本机直连、且无外部 Host/Forwarded 时跳过登录,**当前没有开关**;跳过的只是插件登录,Host/Origin/CSRF 仍生效。LAN/Tailscale 默认认证,开启 `allowLan`/`allowTailscale` 后对应网络才免密;反向代理流量不享受这些免密。
187
+
188
+ <details>
189
+ <summary><b>展开:请求头、Nginx 配置与常见错误</b></summary>
190
+
191
+ 请求头示例(A/B/C 对应上表):
192
+
193
+ - A:`Host: 127.0.0.1:3081`
194
+ - B:`Host: 192.x.x.x:3081` 或 `Host: 100.x.x.x:3081`
195
+ - C:`Host: dsh.example.com`,并携带 `X-Forwarded-For: 100.x.x.x` / `X-Forwarded-Host` / `X-Forwarded-Proto: https`
196
+
197
+ C 场景即使 Nginx 从 `127.0.0.1` 连入,仍判定为反向代理,需要认证。
198
+
199
+ ```nginx
200
+ location / {
201
+ proxy_pass http://127.0.0.1:3081;
202
+ proxy_set_header Host $host;
203
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
204
+ proxy_set_header X-Forwarded-Host $host;
205
+ proxy_set_header X-Forwarded-Proto $scheme;
206
+ }
207
+ ```
208
+
209
+ 自定义域名需加入 `trustedHosts`:
210
+
211
+ ```json
212
+ {
213
+ "trustedHosts": ["dsh.example.com"]
214
+ }
215
+ ```
216
+
217
+ 常见错误:Nginx 把 Host 改写成 `127.0.0.1` 并删除全部 `X-Forwarded-*`,插件会误判为 A 并跳过登录。Tailscale Serve 也是同机反代,同样要保留这些头。
218
+
219
+ </details>
220
+
221
+ ---
222
+
127
223
  <span id="preview"></span>
128
224
  ## 🖼️ 界面预览
129
225
 
@@ -149,7 +245,7 @@ dsh web --no-open
149
245
  ## 🌟 功能特性
150
246
 
151
247
  ### 1. 网络接入支持
152
- - **Tailscale 虚拟私网**:自动识别本机 Tailscale IP(`100.64.0.0/10` CGNAT 网段),生成专属访问二维码,支持开启免密直连(底层传输加密由 Tailscale WireGuard 隧道保障)。
248
+ - **Tailscale 虚拟私网**:自动识别本机 Tailscale IP(`100.64.0.0/10` CGNAT 网段),生成专属访问二维码,支持开启免密直连(底层传输加密由 Tailscale WireGuard 隧道提供)。
153
249
  - **本地局域网 (LAN / Wi-Fi)**:自动识别 RFC 1918 私有 IP(如 `192.168.x.x`、`10.x.x.x`、`172.16-31.x.x`),提供局域网专属二维码与直达链接预览,附带醒目的高危风险提示。
154
250
  - **二维码快速配对**:支持手机相机或微信扫码直达授权页面。
155
251
 
@@ -160,25 +256,27 @@ dsh web --no-open
160
256
  - **设备会话管理**:实时查看已连接设备的类型、操作系统、浏览器、来源 IP 及最近活跃时间,支持单设备注销与一键注销全部设备。
161
257
 
162
258
  ### 3. 安全防护
163
- - **传输层 RSA 非对称加密**:登录认证接口在客户端即完成 RSA 加密,敏感密码与配对码加密后才经网络传输。
259
+ - **应用层登录凭证 RSA 加密**:Web 登录页在客户端完成 RSA 加密,敏感密码与配对码加密后才提交到验证接口;加密失败时会停止提交并提示,不会回退明文。
164
260
  - **HTTPS 等安全上下文**:优先使用 Web Crypto 原生 `RSA-OAEP-SHA256`;
165
- - **DSH 默认的 HTTP 局域网 / Tailscale 访问(非安全上下文)**:回退至纯 JS 垫片,并使用 `crypto.getRandomValues()` 做密码学安全随机数填充。
261
+ - **DSH 默认的 HTTP 局域网 / Tailscale 访问(浏览器非 Secure Context;Tailscale 隧道本身仍有 WireGuard 加密)**:回退至纯 JS 垫片,并使用 `crypto.getRandomValues()` 做密码学安全随机数填充。
262
+
263
+ > 插件不自带 TLS;登录后的页面、API 与 WebSocket 流量需要依靠 Tailscale 或外层 HTTPS 才能获得完整传输加密。
166
264
  - **scrypt 慢哈希存储**:服务端采用 `scrypt` 加盐慢哈希算法(`scrypt:${salt}:${hash}`)对密码进行落盘存储,比对过程采用 `crypto.timingSafeEqual` 恒定时间比较以防范时序侧信道攻击。
167
265
  - **防暴力破解与限频保护**:
168
266
  - 连续输错凭证达到阈值(默认 5 次)自动锁定该 IP 15 分钟,拦截后续验证请求并返回 HTTP 429;
169
267
  - 滑动窗口限频(默认 60 次/分钟),防范高频恶意刷量探测;
170
268
  - 访问审计与锁定状态持久化落盘,服务重启后自动恢复;
171
269
  - 支持管理员在管理面板中一键解锁指定 IP。
172
- - **智能静态资源放行机制**:安全门禁内置智能前端静态扩展名识别(放行 `.js`、`.css`、`.png`、`.svg`、`.woff2` 等 20 种合法资源),拦截无扩展名或动态管理 API 请求(如 `/plugins/xxx/admin`),确保第三方生态插件前端正常展示的同时严密保护后台动态接口,无需用户在设置界面手动维护白名单。(DSH 核心静态目录 `/assets/` 属官方资源,单独无条件放行。)
173
- - **高可靠原子持久化与防抖**:采用毫秒级写盘防抖节流保护磁盘 I/O(会话数据 500ms、样式片段 300ms),配合 `beforeExit` 进程退出 Flush 保证数据不丢失;`settings.json` 与 `style-snippets.json` 采用临时文件原子替换(`renameSync`),防止异常中断损坏数据。
174
- - **真实 IP 安全提取**:仅信任底层 Socket 真实连接地址,防范伪造的 `X-Forwarded-For` 欺骗攻击。
175
- - **本机回环 CSRF 防御**:插件的变更类 API 会校验浏览器同源信号(`Origin` / `Sec-Fetch-Site`),恶意网页驱使浏览器向 `127.0.0.1` 发起的跨站写请求将被直接拒绝;curl / 本机脚本等无浏览器信号的客户端不受影响。
176
- - **本机回环请求不做上下文改写**:上下文虚拟化仅作用于外部来源流量;本机回环请求保留原始 Host / Origin(不"洗白"),使 DSH 自带的 DNS-Rebinding 与同源校验对外部域名继续生效。SSE 事件流仅限同源连接,不开放跨域读取。
270
+ - **智能静态资源放行机制**:安全门禁内置前端静态扩展名识别(放行 `.js`、`.css`、`.png`、`.svg`、`.woff2` 等 20 种合法资源),拦截无扩展名或动态管理 API 请求(如 `/plugins/xxx/admin`),在兼容第三方生态插件前端资源的同时降低后台动态接口暴露面,无需用户在设置界面手动维护白名单。(DSH 核心静态目录 `/assets/` 属官方资源,单独无条件放行。)
271
+ - **高可靠原子持久化与防抖**:采用毫秒级写盘防抖节流保护磁盘 I/O(会话数据 500ms、样式片段 300ms),配合 `beforeExit` 进程退出 Flush,降低异常中断导致数据丢失或配置损坏的风险;`settings.json` 与 `style-snippets.json` 采用临时文件原子替换(`renameSync`)。
272
+ - **真实 IP 安全提取**:默认仅信任底层 Socket 真实连接地址;当请求被识别为受信反向代理(Host / `X-Forwarded-*`)时,才采用 `X-Forwarded-For` 首个合法 IP,兼顾真实来源与防伪造。
273
+ - **跨站写请求防御**:所有携带浏览器同源信号的写请求都会校验(`Origin` / `Sec-Fetch-Site`),恶意网页驱使浏览器向 `127.0.0.1` 发起的跨站写请求会被直接拒绝;curl / 本机脚本等无浏览器信号的客户端不受此限制。注意:该策略也会拦截第三方插件的跨域 webhook / OAuth callback,以及发送 `Origin: null` 的旧 WebView;使用此类集成时请先做兼容性验证。
274
+ - **上下文化与反代边界**:同源/本地变体 Host、Origin 成对规范化;恶意跨站 Origin 原样保留供下游 CSRF 检查。同机 127 反代通过外部 Host、LAN/Tailscale 反代通过 `X-Forwarded-*` 识别为代理流量,不再被误判为免密客户端,必须通过插件 Token 鉴权;受信代理通过内部 Secret 例外。插件自身为 loopback 请求注入的 127 Forwarded 头会被识别为内部头,不会误伤本机 3080 根路径的 connection 鉴权。SSE 事件流仅限同源连接,不开放跨域读取。
177
275
 
178
276
  ### 4. 实时状态同步与国际化
179
277
  - **SSE 实时事件推送**:基于 Server-Sent Events 实现新设备接入、设备重连、会话撤销及安全告警的实时通知,内置连接双向关闭监听与幂等清理。
180
278
  - **全界面中英文双语自适应**:根据 DSH 全局语言偏好与浏览器环境,动态自适应中英文面板、提示及手机端界面。
181
- - **非 HTTPS 兼容补丁**:自动注入 `crypto.randomUUID` 与 `navigator.clipboard` Polyfill,解决移动端浏览器在 HTTP 非安全上下文下缺少原生 API 的报错(`clipboard` polyfill 保证非安全上下文下复制/样式复制功能正常)。
279
+ - **非 HTTPS 兼容补丁**:自动注入 `crypto.randomUUID` 与 `navigator.clipboard` Polyfill,解决移动端浏览器在 HTTP 非安全上下文下缺少原生 API 的报错(`clipboard` polyfill 在非安全上下文下提供复制/样式复制能力)。
182
280
 
183
281
  ### 5. 移动端样式片段(样式小插件)
184
282
 
@@ -195,7 +293,7 @@ DSH Web 界面在手机上仍有不少沿袭桌面端的样式问题,插件内
195
293
 
196
294
  ### 6. 插件通用共存保护
197
295
 
198
- - **启动零冲突**:`remoteWebUiPairing` 配对共享服务被其他远程/Web 接入类插件占用时,本插件自动让出(延迟裁决:等待激活窗口结束后检测服务名归属),避免服务重名导致的整树回滚崩溃——从插件市场安装后可直接启动,无需任何手动配置;
296
+ - **共存冲突自动让出**:`remoteWebUiPairing` 配对共享服务被其他远程/Web 接入类插件占用时,本插件自动让出(延迟裁决:等待激活窗口结束后检测服务名归属),避免服务重名导致的整树回滚崩溃——从插件市场安装后通常无需手动禁用对方;
199
297
  - **冲突状态可见化**:检测到共存时,设置页顶部展示可关闭的警示横幅(中英文),说明让出行为与「只保留其一」的建议;处理完成后刷新页面即不再出现;
200
298
  - **一键诊断报告**:横幅内 **「📋 复制诊断与修复信息」** 按钮可复制完整诊断报告——运行时自动识别占用方插件包名与 loader entry id,报告含精确到行的修复配置与命令(禁用对方 / 卸载本插件二选一),粘贴给 AI 助手即可按步骤处理。
201
299
 
@@ -287,7 +385,17 @@ dsh plugin --profile web remove dsh-remote-mobile
287
385
  <span id="advanced"></span>
288
386
  ## ⚙️ 高级配置
289
387
 
290
- 配置持久化于插件自有文件 `~/.dsh/remote-mobile/settings.json`,可在 Web 界面中直观调整,也可直接编辑该文件:
388
+ 配置持久化于插件自有文件 `~/.dsh/remote-mobile/settings.json`。Web 设置页中的修改位置如下:
389
+
390
+ | 配置项 | 位置 | 生效方式 |
391
+ |---|---|---|
392
+ | `mode` / `proxyPort` / `proxyHost` / `trustedHosts` | 接入 → 接入代理配置 | 重启 DSH |
393
+ | Tailscale / 局域网免密开关 | 接入 → 网络接入地址与免密直连管理 | 即时 |
394
+ | 长期访问密码 | 接入 → 设置长期访问密码(可选) | 即时 |
395
+ | 限频 / 失败阈值 / 锁定时间 | 设备与安全 → 全局高级安全参数配置 | 即时 |
396
+ | 样式片段 | 样式覆写 | 下次页面加载 |
397
+
398
+ 也可以直接编辑 `settings.json`。修改接入模式或监听参数后需重启 DSH:
291
399
 
292
400
  ```jsonc
293
401
  {
@@ -296,10 +404,28 @@ dsh plugin --profile web remove dsh-remote-mobile
296
404
  "secretHash": "", // string,默认空:长期访问密码的 scrypt 加盐哈希值
297
405
  "maxVisitsPerMinute": 60, // number,默认 60:单 IP 每分钟最大访问登录页次数
298
406
  "maxFailedAttempts": 5, // number,默认 5:触发封禁的连续认证失败最大次数
299
- "lockDurationMs": 900000 // number,默认 900000 (15分钟):IP 锁定持续时间(毫秒)
407
+ "lockDurationMs": 900000, // number,默认 900000 (15分钟):IP 锁定持续时间(毫秒)
408
+ "proxyPort": 3081, // number,默认 3081:安全接入代理端口
409
+ "proxyHost": "0.0.0.0", // string,默认 0.0.0.0:安全接入代理监听地址
410
+ "mode": "proxy", // proxy | direct:proxy 默认(推荐)
411
+ "trustedHosts": [ // string[],默认 []:受信任域名白名单(如 Tailscale MagicDNS 或自定义反代域名)
412
+ "my-device.tailnet.ts.net"
413
+ ]
300
414
  }
301
415
  ```
302
416
 
417
+ > **术语说明**:本文中的“安全接入代理(Proxy)”指插件在 `3081` 提供的反向代理入口,负责认证、Host/Origin 校验与请求转发,**不是**网络默认网关。
418
+ >
419
+ > **关于 `trustedHosts` 受信任域名**:若您通过 Tailscale MagicDNS 域名(如 `xxx.tailnet.ts.net`)或外部反向代理域名访问 DSH,请在该数组中添加裸域名。安全接入代理与底座会自动将其纳入受信任白名单,防止被代理 Host 检查或第三方插件安全围栏拦截(报 400 或 403)。该白名单同样适用于 **direct 模式**:通过自定义域名、公网 IP 或非本机网卡地址访问时,必须把实际 Host 加入 `trustedHosts`;通过本机网卡 IP / 回环访问会自动识别。
420
+ >
421
+ > **`proxyPort` / `proxyHost` / `mode` 说明**:通常保持默认即可。`mode: 'proxy'` 是默认值,要求底座监听在回环地址;如果检测到核心已对外监听(`0.0.0.0` 或具体网卡 IP),会自动回退 `direct` 并输出警告。`mode: 'direct'` 不启动安全接入代理,要求核心本身可被外部访问;若核心是回环地址,`direct` 仅适合同机反向代理,外部手机无法直连。修改这些高级配置后需重启 DSH。
422
+ >
423
+ > **反向代理 HTTPS 注意**:若通过 Nginx/Caddy/Tailscale Serve 等反代以 HTTPS 访问,请确保:
424
+ > 1. **保留外部 Host**(Nginx 使用 `proxy_set_header Host $host`,不要把 Host 改写成 `127.0.0.1`);
425
+ > 2. 透传 `X-Forwarded-For`、`X-Forwarded-Host`、`X-Forwarded-Proto: https`(Caddy/Tailscale 通常自动处理)。
426
+ >
427
+ > 插件会用这些头做协议/来源同源校验与 302 重定向;当反代携带 Forwarded 头并被识别为代理时,即使位于 LAN/Tailscale 网段也会禁用 `allowLan`/`allowTailscale` 免密,改为正常 Token 鉴权。若反代把 Host 改写为 `127.0.0.1` 且不发送任何 `X-Forwarded-*` 头,插件无法区分“本机浏览器”和“同机反代”;LAN/Tailscale 反代若缺少这些头,也可能被误判为真实局域网客户端,请务必按上述配置修正。
428
+
303
429
  > **关于配置存储**:DSH 自 **0.1.7** 起(最早见于 v0.1.7-alpha.1 的 release notes,0.2.x 延续)官方已弃用 `~/.dsh/settings.yaml` 作为**实时配置源**——该文件会在启动时被一次性迁移进当前 profile 的 `cordis.patch.yml`,并改名为 `settings.yaml.imported`(详见 [Q8](#faq))。因此本插件不再写入 settings.yaml,而是持久化到上述自有 `settings.json`;首次启动时会自动从旧 `settings.yaml` / `settings.yaml.imported` 迁移一次(把旧长期密码、限频等配置救回)。
304
430
 
305
431
  ### 🎨 移动端样式片段(可选)
@@ -338,29 +464,31 @@ dsh plugin --profile web remove dsh-remote-mobile
338
464
 
339
465
  | 文件路径 | 说明 | 安全级别 |
340
466
  |---|---|---|
341
- | `~/.dsh/remote-mobile/settings.json` | 插件配置:免密开关、长期密码哈希与限频/锁定策略 | 本地落盘,权限 `0o600`(仅当前用户可读写) |
467
+ | `~/.dsh/remote-mobile/settings.json` | 插件配置:免密开关、长期密码哈希、限频/锁定策略、接入模式与代理监听参数 | 本地落盘,权限 `0o600`(仅当前用户可读写) |
342
468
  | `~/.dsh/remote-mobile/devices.json` | 已授权设备会话、IP 访问计数与安全审计数据(内含长效 Token,权限 `0o600`) | 本地落盘,仅当前用户可读写 |
343
469
  | `~/.dsh/remote-mobile/rsa-keys.json` | 服务端 RSA 密钥对文件(含公钥与私钥) | 本地落盘,权限 `0o600`(仅当前用户可读写) |
344
470
  | `~/.dsh/remote-mobile/style-snippets.json` | 移动端样式片段(内置预设启停状态 + 用户自定义 CSS 小插件) | 本地落盘持久化 |
345
471
 
472
+ > `0o600` 是 POSIX 权限;Windows 使用用户目录 ACL,不按 POSIX 权限位表示。
473
+
346
474
  ---
347
475
 
348
476
  <span id="faq"></span>
349
477
  ## ❓ 常见问题 (FAQ)
350
478
 
351
479
  <details>
352
- <summary><b>Q0: 启动报错 <code>listen EADDRINUSE: address already in use 0.0.0.0:3080</code>?</b></summary>
480
+ <summary><b>Q0: 启动报错 <code>listen EADDRINUSE: address already in use 0.0.0.0:3080 / :3081</code>?</b></summary>
353
481
 
354
- **答**:这是 **3080 端口被其他进程占用**(绝大多数是上一个 `dsh web` 实例尚未完全退出,例如桌面快捷方式拉起的隐藏实例、或重启时新旧进程交叠),**与安装了哪个插件无关**。排查步骤:
482
+ **答**:这是 **3080(DSH 核心)或 3081(插件安全接入代理)端口被其他进程占用**(绝大多数是上一个 `dsh web` 实例尚未完全退出,例如桌面快捷方式拉起的隐藏实例、或重启时新旧进程交叠),**与安装了哪个插件无关**。排查步骤:
355
483
 
356
484
  1. **查找占用端口的进程**:
357
- * **macOS / Linux**:`lsof -nP -iTCP:3080`
358
- * **Windows**:`netstat -ano | findstr 3080`(最后一列为 PID)
485
+ * **macOS / Linux**:`lsof -nP -iTCP:3080` / `lsof -nP -iTCP:3081`
486
+ * **Windows**:`netstat -ano | findstr 3080` / `netstat -ano | findstr 3081`(最后一列为 PID)
359
487
  2. **结束旧实例**:
360
488
  * **macOS / Linux**:`kill <PID>`
361
489
  * **Windows**:`taskkill /F /PID <PID>`(或在任务管理器中结束对应 Node 进程)
362
490
  3. 重新启动 `dsh web` 即可。
363
- 4. **注意**:桌面快捷方式(dsh-desktop-launcher 生成的后台实例)与终端手动启动的实例不能同时运行。
491
+ 4. **注意**:桌面快捷方式(dsh-desktop-launcher 生成的后台实例)与终端手动启动的实例不能同时运行。若只是 3081 被占用,插件默认会自动递增尝试下一个端口(最多 10 个),以设置页二维码/终端 Banner 显示的实际端口为准;3080 被占用通常需要先结束旧 DSH 实例。
364
492
 
365
493
  另:若日志显示的是 `service "remoteWebUiPairing" has been registered` 类错误,请升级本插件至最新版本——新版已内置通用共存保护,与其他远程接入类插件并存时可正常启动并自动让出服务,并在设置面板顶部提示冲突详情。
366
494
  </details>
@@ -369,30 +497,30 @@ dsh plugin --profile web remove dsh-remote-mobile
369
497
  <summary><b>Q1: 手机扫码后提示「连接被拒绝」或无法打开页面?</b></summary>
370
498
 
371
499
  **答**:请按以下步骤逐一排查:
372
- 1. **配置检查**:确保电脑上的 `cordis.patch.yml` 中已配置 `host: '0.0.0.0'`,且 DSH 已经重启;
373
- 2. **同一局域网**:局域网访问时,确保手机与电脑连接在同一个 Wi-Fi / 路由器下,且未开启手机端的独立代理/VPN(避免流量绕过局域网);
500
+ 1. **配置检查**:DSH 0.2.1+ **不要**在 `cordis.patch.yml` 中配置 `host: '0.0.0.0'`(会启动失败);保持 DSH 默认的 `127.0.0.1:3080`,插件会自动启动 `3081` 安全接入代理。DSH 0.1.7 ~ 0.2.0 可使用 `0.0.0.0`;显式使用 `direct` 模式时需绑定具体网卡 IP。修改配置后必须重启 DSH。
501
+ 2. **同一局域网**:局域网访问时,确保手机与电脑连接在同一个 Wi-Fi / 路由器下,且未开启手机端的独立代理/VPN(避免流量绕过局域网)。
374
502
  3. **Windows 防火墙排查(Windows 用户常见原因)**:
375
503
  * 启动 DSH 时如弹出「Windows Defender 防火墙」警报,请务必勾选 **「专用网络」** 和 **「公用网络」** 并允许访问;
376
- * 若错过弹窗或依然无法连通,可使用**管理员权限打开 PowerShell** 执行以下命令一键放行端口:
504
+ * 若错过弹窗或依然无法连通,可使用**管理员权限打开 PowerShell** 放行代理端口(默认 `3081`;若使用 `direct` 模式则放行核心端口 `3080`):
377
505
  ```powershell
378
- New-NetFirewallRule -DisplayName "DSH Web 3080" -Direction Inbound -LocalPort 3080 -Protocol TCP -Action Allow
506
+ New-NetFirewallRule -DisplayName "DSH Remote Proxy 3081" -Direction Inbound -LocalPort 3081 -Protocol TCP -Action Allow
379
507
  ```
380
- * **检查 Wi-Fi 网络类型**:在 Windows「设置 -> 网络和 Internet -> Wi-Fi」中,确保当前 Wi-Fi 网络配置文件类型已设为 **「专用网络」**(若为「公用网络」,Windows 防火墙会默认拦截局域网入站请求);
381
- 4. **多网卡环境(WSL / 虚拟机)**:Windows 如安装了 WSL2 或 Hyper-V,可能会生成虚拟网卡 IP(如 `172.x.x.x`)。请在插件设置面板中确认使用的是物理无线网卡(WLAN / Wi-Fi)对应的局域网 IP 或 Tailscale 二维码。
508
+ * **检查 Wi-Fi 网络类型**:在 Windows「设置 -> 网络和 Internet -> Wi-Fi」中,确保当前 Wi-Fi 网络配置文件类型已设为 **「专用网络」**(若为「公用网络」,Windows 防火墙会默认拦截局域网入站请求)。
509
+ 4. **多网卡环境(WSL / 虚拟机)**:Windows 如安装了 WSL2 或 Hyper-V,可能会生成虚拟网卡 IP(如 `172.x.x.x`)。请在插件设置面板中确认使用的是物理无线网卡(WLAN / Wi-Fi)对应的局域网 IP 或 Tailscale 二维码;若代理端口曾自动递增,以设置页/终端 Banner 显示的实际端口为准。
382
510
  </details>
383
511
 
384
512
  <details>
385
513
  <summary><b>Q2: Tailscale 免密直连和局域网免密有什么区别?</b></summary>
386
514
 
387
515
  **答**:
388
- * **Tailscale 免密**:高度安全。因为 Tailscale 是基于 WireGuard 的传输加密虚拟私网,只有你自己登录了同一账号的设备才能连通。
389
- * **局域网免密**:存在安全隐患。任何连入你家 Wi-Fi 的设备(包括访客或蹭网设备)均可直接控制你的工作区,因此非完全可信环境**切勿开启**。
516
+ * **Tailscale 免密**:安全性较高。Tailscale 基于 WireGuard 传输加密;默认只有同一 tailnet 或被 ACL/共享授权的设备可连通。
517
+ * **局域网免密**:存在安全隐患。开启后,任何能访问网关端口的局域网设备(包括访客或蹭网设备)都可能直接控制你的工作区;访客网络隔离或防火墙可能阻断,但不要把它当作安全边界,因此非完全可信环境**切勿开启**。
390
518
  </details>
391
519
 
392
520
  <details>
393
521
  <summary><b>Q3: 手机端切换或新建工作区能正常使用吗?</b></summary>
394
522
 
395
- **答**:**支持**。本插件通过请求隔离技术,使手机端与 PC 桌面端拥有一致的工作区管理与执行能力。
523
+ **答**:**支持**。本插件通过请求隔离技术,使手机端与 PC 桌面端拥有一致的工作区管理与执行能力。若点“选择文件夹”时弹窗出现在 PC 而不是手机,这是 DSH 目录选择器的 auto 判定问题,见 [Q12](#q12)。
396
524
  </details>
397
525
 
398
526
  <details>
@@ -417,7 +545,7 @@ dsh plugin --profile web remove dsh-remote-mobile
417
545
 
418
546
  **答**:主要是基于**避免功能冲突**与**环境轻量解耦**的考量:
419
547
  1. **避免移动端入口重复与手势交互冲突**:全家桶插件会将十几个独立功能插件的前端代码编译打包在同一个 `client.js` 中,其内置的移动端自适应逻辑在模块加载时会自执行。即使在 `cordis.patch.yml` 中配置了 `web-ui-remote-web-ui: disabled: true`,浏览器加载脚本后仍可能渲染出小鲸鱼悬浮按钮(`#dshRemoteWhale`),导致两个插件的移动端悬浮入口重叠以及触控手势冲突。
420
- 2. **轻量可控与独立升级**:按需单独安装所需插件,既能保证环境轻盈,也便于各个插件独立升级、回滚与维护,避免依赖绑定。推荐按需安装各自独立的单体包,例如:
548
+ 2. **轻量可控与独立升级**:按需单独安装所需插件,有助于保持环境轻盈,也便于各个插件独立升级、回滚与维护,避免依赖绑定。推荐按需安装各自独立的单体包,例如:
421
549
  ```bash
422
550
  # 需要哪个装哪个,职责清晰、互不干扰
423
551
  dsh plugin --profile web add @linxin666/dsh-client-ui-task-board
@@ -433,13 +561,14 @@ dsh plugin --profile web remove dsh-remote-mobile
433
561
 
434
562
  **答**:
435
563
  1. **子路径与前缀自适应**:自 v1.8.0 起,前端已原生适配 `<base href="...">`,服务端重定向已支持 `X-Forwarded-Prefix` 请求头。经网关子路径代理访问时(如 `http://<ip>:5666/app/dsh/`),各项请求都会自动保持在当前子路径下:
436
- * **设置面板与 SSE 长连接**:基于 `<base>` 动态解析 API 路径,保存配置、生成配对码、事件推送均不会丢失 `/app/dsh` 前缀(旧版本此处会表现为「点击毫无反应」);
564
+ * **设置面板与 SSE 长连接**:基于 `<base>` 动态解析 API 路径,保存配置、生成配对码、事件推送均不会丢失 `/app/dsh` 前缀(1.8.0 之前此处会表现为「点击毫无反应」);
437
565
  * **独立手机登录页与 302 重定向**:登录校验、登录成功后的跳转,以及门禁 / `/auth` 的重定向都会保留当前子路径,不再跳回宿主根目录;
438
566
  * **注意**:服务端 302 重定向依赖反向代理透传 `X-Forwarded-Prefix`;若代理未透传该头,重定向将回退到安全默认路径 `/auth`。
439
- 2. **网关回环 (127.0.0.1) 放行机制**:
440
- * 出于严密的安全防护考量(防止外部恶意客户端伪造 `X-Forwarded-For` 请求头绕过认证),插件门禁严格基于底层物理 Socket 连接判断客户端真实来源。
441
- * 当反向代理/宿主网关直接从本机 `127.0.0.1` 代理至 DSH 时,请求会被识别为本地回环连接并直接放行——此时由网关/NAS 宿主自身的登录与权限隔离机制进行安全兜底。
442
- * 若你希望通过外部统一域名/网关访问时仍强制要求输入本插件的动态配对码或长期访问密码,建议配置反向代理透明透传客户端源 IP,或直接通过 DSH 开放的外部端口(如默认的 `3080` 或自定义映射端口)直连访问。
567
+ 2. **反向代理来源识别与鉴权**:
568
+ * 插件默认仅信任底层物理 Socket;**同机 `127.0.0.1` 反代携带外部 Host**,或**任意反代携带 `X-Forwarded-*`** 时,会被识别为**代理流量**,不再走 `loopback` / `allowLan` / `allowTailscale` 免密,必须使用插件 Token(动态配对码或长期密码)鉴权。
569
+ * 请确保反向代理**保留外部 Host**(不要改写为 `127.0.0.1`),并透传 `X-Forwarded-For`、`X-Forwarded-Host`、`X-Forwarded-Proto: https`;Caddy / Tailscale Serve 通常会自动处理,Nginx 需显式配置。若 Host 被改写为 `127.0.0.1` 且没有任何 Forwarded 头,插件无法区分“本机浏览器”与“同机反代”。
570
+ * 若希望外部统一域名访问时仍强制要求输入本插件的配对码/密码,请按上述方式配置反代,让插件能够识别真实来源并执行 Token 鉴权;不要依赖 NAS/宿主网关自身的登录来替代插件认证。
571
+ * 若你不需要 proxy 模式,也可以使用 `direct` 模式绑定具体网卡 IP;但 DSH 0.2.1+ 下不能使用 `0.0.0.0` 通配地址。
443
572
  </details>
444
573
 
445
574
  <details>
@@ -452,6 +581,94 @@ dsh plugin --profile web remove dsh-remote-mobile
452
581
  - **本插件自 v1.9.0 起**已改为把自身配置持久化到 `~/.dsh/remote-mobile/settings.json`,并在首次启动时**自动从旧 `settings.yaml` / `settings.yaml.imported` 迁移一次**(把长期密码哈希、免密开关、限频阈值等救回),不再依赖 DSH 的 settings.yaml。若你曾发现"长期密码失效/限频重置",升级到 v1.9.0 后会自动恢复。
453
582
  </details>
454
583
 
584
+ <details>
585
+ <summary><b>Q9: DSH 0.2.1+ 启动失败:<code>host "0.0.0.0" is an unspecified (wildcard) address</code>?</b></summary>
586
+
587
+ **答**:这是 DSH 0.2.1+ 的官方安全限制,和插件版本无关。该错误发生在插件加载之前,插件安全接入代理此时还没有机会接管,因此必须手动处理:
588
+
589
+ 1. 打开 `~/.dsh/profiles/web/cordis.patch.yml`(Windows:`%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml`);
590
+ 2. 删除或注释掉之前为远程访问添加的 `webserver.host: '0.0.0.0'`(如整段 `- id: webserver ... host: '0.0.0.0'` 仅用于该配置,可整段删除);
591
+ 3. 重启 `dsh web --no-open`。DSH 会恢复 `127.0.0.1:3080`,本插件自动启动 `0.0.0.0:3081` 安全接入代理,手机按设置页二维码访问即可。
592
+
593
+ > 1.10.0+ 在命中该通配地址错误退出后,会追加一条 `[dsh-remote-mobile] 启动失败提示`,包含具体配置文件路径和处理方法;其他启动错误不会触发。
594
+ </details>
595
+
596
+ <details>
597
+ <summary><b>Q10: 访问 <code>http://127.0.0.1:3080/</code> 提示 <code>dsh web authentication required; reopen the URL printed by dsh web</code>,但 3081 正常?</b></summary>
598
+
599
+ **答**:这是 **DSH 官方的 launch-token 鉴权**,不是插件的 `/auth` 登录页。正常情况下,本插件会接管底座 connection 鉴权,让本机回环请求直接放行;如果 3080 根路径出现该提示,而以下两项正常:
600
+
601
+ - `http://127.0.0.1:3080/api/remote-mobile/status` 返回 200;
602
+ - `http://127.0.0.1:3081/` 返回 200;
603
+
604
+ 说明插件已加载、安全接入代理已生效,但当前进程里的 connection 鉴权仍未放行本机根路径。常见原因是 **热重载/热更新没有重新加载插件代码**,或运行的是旧构建。
605
+
606
+ 处理步骤:
607
+
608
+ 1. 确认安装的是 **1.10.0+**,然后**完整停止并重新启动** `dsh web`(仅刷新浏览器页面不够);
609
+ 2. 临时可先用 `http://127.0.0.1:3081/`(插件安全接入代理)访问,或打开 `dsh web` 启动时打印的带 `?token=...` 的 URL 一次,让官方写入 cookie;
610
+ 3. 若使用反向代理,请按 Q7 保留外部 Host 并透传 `X-Forwarded-*`;
611
+ 4. 如果重启后仍然出现,请附上 DSH 版本、插件版本与启动日志反馈。
612
+
613
+ > 1.10.0 已修复“插件自身为 loopback 请求注入的 127 Forwarded 头被误判为反向代理、导致 3080 根路径被官方 401 拦截”的回归;重启后本机 3080 应恢复正常。
614
+ </details>
615
+
616
+ <details>
617
+ <summary><b>Q11: 设备列表里出现很多 <code>127.0.0.1 / 📱 iPhone (Browser)</code>,但我没有用过 iPhone?</b></summary>
618
+
619
+ **答**:这些通常不是真实设备,而是 **1.10.0 之前测试套件写入的假 session**:
620
+
621
+ - `test/gateway.test.js` 曾用 `store.verify(code, 'iPhone', '127.0.0.1')` 做配对测试;
622
+ - 当时的 `SessionStore` 在测试环境只隔离了 `settings.json`,没有隔离 `devices.json`;
623
+ - 每运行一次 `npm test`,就会向真实的 `~/.dsh/remote-mobile/devices.json` 增加一条 `127.0.0.1 / User-Agent: iPhone / 扫码配对码` 的 session;
624
+ - 真实 iPhone Safari 的 User-Agent 会是很长的 `Mozilla/5.0 (iPhone; ...) ... Safari/...`,不会只有 `iPhone`。
625
+
626
+ 1.10.0 已增加测试隔离:测试环境默认使用临时 `/tmp/dsh-rm-test-devices-*.json`,不会再污染真实设备文件。已有的假 session 可以通过“一键撤销所有设备”后重新配对当前设备清理,或停止 DSH 后从 `devices.json` 中删除对应条目;操作前建议先备份该文件。
627
+ </details>
628
+
629
+ <details id="q12">
630
+ <summary><b>Q12: 手机通过 Tailscale / 3081 访问,点“选择文件夹”为什么在 PC 上弹窗,手机没有弹窗?</b></summary>
631
+
632
+ **答**:这不是 Tailscale 或手机浏览器的问题,而是 DSH 官方目录选择器的“本机/远程”判定导致的:
633
+
634
+ 1. **工作区目录在 PC 上**:DSH 的“选择文件夹”选的是运行 DSH 的电脑上的目录,不是手机本地目录。手机浏览器无法直接弹出 PC 的系统文件选择器。
635
+ 2. **auto 只按启动环境判定**:`dsh-host-directory-picker-auto` 在启动时检查 `webServer.host`。当核心绑定 `127.0.0.1`、非 SSH 启动且系统有桌面时,它判定为 `native`,调用 PC 的 macOS / Windows 原生目录选择器。
636
+ 3. **安全接入代理让远程请求看起来像本机**:本插件让 DSH 核心保持 `127.0.0.1:3080`,由 `0.0.0.0:3081` 安全接入代理对外服务。手机通过 Tailscale 访问 3081 时,核心收到的仍是回环请求,auto 误判“操作者就坐在 PC 前”,于是把 native 窗口弹在 PC 上;手机端不会渲染这个系统窗口,只会等待。
637
+
638
+ **修复(推荐一键开启,也支持手动配置)**:
639
+
640
+ - **方式一(Web 界面一键开启 / 还原,自动幂等去重)**:打开 DSH Web 界面 -> **「设置」->「远程与移动端」**,在顶部醒目提醒横幅(或「接入」页签底部的「移动端添加工作区(目录选择器模式)」区域)点击 **「⚡ 一键开启网页目录选择器」**。插件会自动在 `~/.dsh/profiles/web/cordis.patch.yml` 中安全写入配置并严格去重;后续如需切回电脑原生窗口,也可随时点击 **「🔄 恢复 PC 原生目录选择器」** 一键还原。
641
+ - **方式二(手动编辑配置文件)**:固定使用应用内 `browse` 目录选择器。编辑 `~/.dsh/profiles/web/cordis.patch.yml`(Windows:`%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml`),在末尾追加:
642
+
643
+ ```yaml
644
+ - id: directory-picker
645
+ disabled: true
646
+ - insert:
647
+ - id: directory-picker-browse
648
+ name: '@deepseek-ai/dsh-host-directory-picker-browse'
649
+ - id: ui-directory-picker-browse
650
+ name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'
651
+ ```
652
+
653
+ > **注意**:DSH 的 patch 层里 `name` 是校验已有插件名,不是重命名。不能只把 `directory-picker` 的 `name` 改成 browse,否则该 patch 会被跳过。必须先 `disabled: true`,再用无 id 的根层 `insert` 插入 browse 后端和浏览器端 UI。
654
+
655
+ 保存后,启用了 HMR 的 DSH 通常会自动重载;否则完整重启 `dsh web`。手机端强制刷新页面后,点“选择文件夹”会出现应用内目录浏览器(面包屑、目录列表、新建子目录),手机和 PC 都会使用网页选择器。
656
+
657
+ **取舍**:这样 PC 本地也不再弹原生窗口。如果希望 PC 用 native、手机用 browse,需要单独建一个 remote profile 或自定义混合选择器;DSH 的 auto 不支持按客户端切换。browse 仍受现有 DSH / 插件鉴权保护,不会绕过认证。
658
+ </details>
659
+
660
+ ---
661
+
662
+ ## Star History
663
+
664
+ <a href="https://www.star-history.com/?repos=iceapriler%2Fdsh-remote-mobile&type=date&legend=bottom-right">
665
+ <picture>
666
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=iceapriler/dsh-remote-mobile&type=date&theme=dark&legend=bottom-right" />
667
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=iceapriler/dsh-remote-mobile&type=date&legend=bottom-right" />
668
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=iceapriler/dsh-remote-mobile&type=date&legend=bottom-right" />
669
+ </picture>
670
+ </a>
671
+
455
672
  ---
456
673
 
457
674
  ## 📄 开源许可证