dsh-mobile 0.4.5 → 0.4.7

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/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  Notable changes to DSH Mobile are recorded here. GitHub Releases remain the source for downloadable packages and complete generated commit notes.
4
4
 
5
+ ## 0.4.7 - 2026-09-25
6
+
7
+ - Expose `panelInfo` on the mobile layout service for DSH `0.1.7-rc.2`, so built-in panels such as Plugin Manager can mount without breaking the dedicated mobile frontend (thanks @chintoleung for [PR #104](https://github.com/saya-ch/dsh-mobile/pull/104) and [#103](https://github.com/saya-ch/dsh-mobile/issues/103)).
8
+ - Derive mobile boot batches in a canonical entry order. The same module set now produces stable batch URLs, response bodies and ETags when upstream entry order changes, avoiding unnecessary new cache entries (thanks @xhwxt for [PR #105](https://github.com/saya-ch/dsh-mobile/pull/105)).
9
+ - Remove the original application-batch preload when the dedicated mobile page replaces that batch, preventing a duplicate multi-megabyte transfer. On WebView 151+, raise this app's HTTP cache quota to at least 64 MiB so large versioned scripts can be reused after a cold start; older WebViews retain their default policy (thanks @xhwxt for [#107](https://github.com/saya-ch/dsh-mobile/issues/107)).
10
+ - Extend source-contract and isolated browser-startup checks to DSH `0.1.7-rc.2`, alongside `0.1.7-alpha.2` and `0.1.7-rc.1`.
11
+ - Add opt-in `excludedClientModules` for mobile-only boot slimming. Exact package ids are checked against the live graph; boot-critical entries and modules still required by retained entries are refused. The default graph, desktop page, and stock frontend remain unchanged (thanks @xhwxt for [#108](https://github.com/saya-ch/dsh-mobile/issues/108)).
12
+ - Let the right sidebar fill the phone viewport instead of clipping file and plugin panels behind a narrow overlay edge; keep the wide-screen column layout and a reachable collapse control.
13
+ - Keep the on-screen keyboard closed when opening the composer Add menu, without affecting other listbox controls (thanks @ayiejosh for [PR #109](https://github.com/saya-ch/dsh-mobile/pull/109)).
14
+ - In the Android App, use plain Enter to add a draft line when the on-screen keyboard is visible and no hardware keyboard is attached; retain DSH's original Enter behavior for browsers, unknown keyboard state, menus and IME composition (thanks @ayiejosh for [PR #110](https://github.com/saya-ch/dsh-mobile/pull/110)).
15
+ - Clear the Android page-load timer when WebView reports the same root document with a normalized URL, preventing a completed load from becoming a delayed false timeout. After native session renewal, keep a live DSH WebView instead of rebuilding it; reload only when the document is unavailable, and bound repeated renderer-crash recovery.
16
+
17
+ ## 0.4.6 - 2026-09-24
18
+
19
+ - Use the configured HTTP proxy as a second, bounded remote-health probe only when the direct probe fails. The diagnostic respects `NO_PROXY`, reports proxy-only reachability as a warning rather than proof that a phone can connect, and retains the direct result if both routes fail (thanks @abworks-dev for [PR #99](https://github.com/saya-ch/dsh-mobile/pull/99)).
20
+ - Add a zero-SSH path for attaching to an existing frps without installing, changing, or restarting it. Users can choose a publicly trusted Caddy HTTPS entry through a loopback-only HTTP vhost, or a raw TCP entry whose self-signed gateway CA the 0.4.6 Android app pins during pairing. The latter uses a public IPv4 address and a finite-lived CA and leaf; older apps cannot use this entry (thanks @liudasheng for [PR #100](https://github.com/saya-ch/dsh-mobile/pull/100)).
21
+ - Require a CA-bound `dsh2` pairing key for self-signed ingress, prevent saved FRP tokens from being returned to page scripts, retain the CA identity across restarts, and renew the ingress leaf without replacing its CA.
22
+ - Keep Node-only imports out of the built mobile client so DSH can activate the App frontend; add a build check for this regression.
23
+ - Resolve Windows `whoami.exe` and `icacls.exe` from the system directory rather than `PATH`, fixing activation when Git for Windows places GNU tools first (thanks @jueruibo for [#101](https://github.com/saya-ch/dsh-mobile/issues/101)).
24
+ - Restore provider-specific recovery guidance in remote diagnostics for the supported error codes across all three languages; remove an unused Android restore-target ordering path without changing paired-device startup.
25
+ - Add an isolated DSH browser check for both 0.1.7-alpha.2 and 0.1.7-rc.1 that pairs a test client, mounts the mobile layout, and requires a workspace baseline over the authenticated `/api/remote.mux` WebSocket.
26
+ - Bound FRP discovery probes through the complete response body and reject bodyless HTTP statuses, so stalled or malformed public endpoints cannot leave startup pending indefinitely or throw from the response callback.
27
+
5
28
  ## 0.4.5 - 2026-09-23
6
29
 
7
30
  - Support the document-relative plugin URLs introduced in DSH 0.1.7 without weakening the same-origin `/plugins/` check. This fixes mobile boot batches returning `502 upstream_unavailable`; local and remote gateways were verified against DSH 0.1.7-alpha.2 (thanks @azri57806-design for [#97](https://github.com/saya-ch/dsh-mobile/issues/97)).
package/CONTRIBUTORS.md CHANGED
@@ -2,19 +2,23 @@
2
2
 
3
3
  GitHub 的 [Contributors 图表](https://github.com/saya-ch/dsh-mobile/graphs/contributors) 按进入默认分支的提交自动统计,无法手动加入仅提交 issue 或 PR 提案的成员。本页按实际参与方式感谢社区成员;仓库维护者与自动依赖更新仍可在 GitHub 历史记录中查看。
4
4
 
5
- GitHub's Contributors graph counts commits on the default branch; issue reports and PR proposals cannot be manually added to it. This page credits community members by how they contributed, through 2026-09-23. The repository maintainer and automated dependency updates remain visible in GitHub's history.
5
+ GitHub's Contributors graph counts commits on the default branch; issue reports and PR proposals cannot be manually added to it. This page credits community members by how they contributed, through 2026-09-25. The repository maintainer and automated dependency updates remain visible in GitHub's history.
6
6
 
7
- ## 已合并 PR / Merged pull requests
7
+ ## 已发布版本中的合并 PR / Merged PRs in released versions
8
8
 
9
9
  | 社区成员 / Community member | 已合并 PR / Merged PRs |
10
10
  | --- | --- |
11
11
  | [@1624318455](https://github.com/1624318455) | [#93](https://github.com/saya-ch/dsh-mobile/pull/93) |
12
- | [@abworks-dev](https://github.com/abworks-dev) | [#91](https://github.com/saya-ch/dsh-mobile/pull/91), [#98](https://github.com/saya-ch/dsh-mobile/pull/98) |
12
+ | [@abworks-dev](https://github.com/abworks-dev) | [#91](https://github.com/saya-ch/dsh-mobile/pull/91), [#98](https://github.com/saya-ch/dsh-mobile/pull/98), [#99](https://github.com/saya-ch/dsh-mobile/pull/99) |
13
+ | [@ayiejosh](https://github.com/ayiejosh) | [#109](https://github.com/saya-ch/dsh-mobile/pull/109), [#110](https://github.com/saya-ch/dsh-mobile/pull/110) |
14
+ | [@chintoleung](https://github.com/chintoleung) | [#104](https://github.com/saya-ch/dsh-mobile/pull/104) |
13
15
  | [@idoall](https://github.com/idoall) | [#61](https://github.com/saya-ch/dsh-mobile/pull/61), [#66](https://github.com/saya-ch/dsh-mobile/pull/66), [#67](https://github.com/saya-ch/dsh-mobile/pull/67), [#77](https://github.com/saya-ch/dsh-mobile/pull/77) |
14
16
  | [@JackRushante](https://github.com/JackRushante) | [#16](https://github.com/saya-ch/dsh-mobile/pull/16) |
17
+ | [@liudasheng](https://github.com/liudasheng) | [#100](https://github.com/saya-ch/dsh-mobile/pull/100) |
15
18
  | [@longisland-icetea](https://github.com/longisland-icetea) | [#59](https://github.com/saya-ch/dsh-mobile/pull/59), [#60](https://github.com/saya-ch/dsh-mobile/pull/60), [#63](https://github.com/saya-ch/dsh-mobile/pull/63) |
16
19
  | [@qzyqmzn](https://github.com/qzyqmzn) | [#38](https://github.com/saya-ch/dsh-mobile/pull/38), [#75](https://github.com/saya-ch/dsh-mobile/pull/75) |
17
20
  | [@StrawberryAO](https://github.com/StrawberryAO) | [#34](https://github.com/saya-ch/dsh-mobile/pull/34), [#40](https://github.com/saya-ch/dsh-mobile/pull/40) |
21
+ | [@xhwxt](https://github.com/xhwxt) | [#105](https://github.com/saya-ch/dsh-mobile/pull/105) |
18
22
  | [@xingleiwu](https://github.com/xingleiwu) | [#79](https://github.com/saya-ch/dsh-mobile/pull/79), [#84](https://github.com/saya-ch/dsh-mobile/pull/84), [#85](https://github.com/saya-ch/dsh-mobile/pull/85), [#86](https://github.com/saya-ch/dsh-mobile/pull/86) |
19
23
 
20
24
  ## 已吸收但未直接合并的 PR / Incorporated without a direct merge
@@ -35,13 +39,16 @@ GitHub's Contributors graph counts commits on the default branch; issue reports
35
39
  | [@azri57806-design](https://github.com/azri57806-design) | [#97](https://github.com/saya-ch/dsh-mobile/issues/97) |
36
40
  | [@CESAEREE](https://github.com/CESAEREE) | [#1](https://github.com/saya-ch/dsh-mobile/issues/1) |
37
41
  | [@CharlesLueng](https://github.com/CharlesLueng) | [#68](https://github.com/saya-ch/dsh-mobile/issues/68) |
42
+ | [@chintoleung](https://github.com/chintoleung) | [#103](https://github.com/saya-ch/dsh-mobile/issues/103) |
38
43
  | [@chmzs](https://github.com/chmzs) | [#73](https://github.com/saya-ch/dsh-mobile/issues/73) |
39
44
  | [@idoall](https://github.com/idoall) | [#42](https://github.com/saya-ch/dsh-mobile/issues/42), [#45](https://github.com/saya-ch/dsh-mobile/issues/45), [#46](https://github.com/saya-ch/dsh-mobile/issues/46), [#47](https://github.com/saya-ch/dsh-mobile/issues/47), [#62](https://github.com/saya-ch/dsh-mobile/issues/62), [#64](https://github.com/saya-ch/dsh-mobile/issues/64) |
40
45
  | [@IvyC-zz](https://github.com/IvyC-zz) | [#58](https://github.com/saya-ch/dsh-mobile/issues/58), [#78](https://github.com/saya-ch/dsh-mobile/issues/78) |
41
46
  | [@jillkayat](https://github.com/jillkayat) | [#10](https://github.com/saya-ch/dsh-mobile/issues/10) |
47
+ | [@jueruibo](https://github.com/jueruibo) | [#101](https://github.com/saya-ch/dsh-mobile/issues/101) |
42
48
  | [@KMGTPEZY](https://github.com/KMGTPEZY) | [#95](https://github.com/saya-ch/dsh-mobile/issues/95) |
43
49
  | [@LWping](https://github.com/LWping) | [#94](https://github.com/saya-ch/dsh-mobile/issues/94) |
44
50
  | [@oliverwan97](https://github.com/oliverwan97) | [#26](https://github.com/saya-ch/dsh-mobile/issues/26) |
51
+ | [@optttt](https://github.com/optttt) | [#111](https://github.com/saya-ch/dsh-mobile/issues/111) |
45
52
  | [@rgshendong](https://github.com/rgshendong) | [#87](https://github.com/saya-ch/dsh-mobile/issues/87) |
46
53
  | [@RonaldinhoL](https://github.com/RonaldinhoL) | [#89](https://github.com/saya-ch/dsh-mobile/issues/89) |
47
54
  | [@shengyvself](https://github.com/shengyvself) | [#13](https://github.com/saya-ch/dsh-mobile/issues/13), [#14](https://github.com/saya-ch/dsh-mobile/issues/14) |
@@ -51,5 +58,6 @@ GitHub's Contributors graph counts commits on the default branch; issue reports
51
58
  | [@Surpass567](https://github.com/Surpass567) | [#80](https://github.com/saya-ch/dsh-mobile/issues/80), [#81](https://github.com/saya-ch/dsh-mobile/issues/81) |
52
59
  | [@taki-ohh](https://github.com/taki-ohh) | [#19](https://github.com/saya-ch/dsh-mobile/issues/19) |
53
60
  | [@Xingcheng-wang](https://github.com/Xingcheng-wang) | [#76](https://github.com/saya-ch/dsh-mobile/issues/76) |
61
+ | [@xhwxt](https://github.com/xhwxt) | [#107](https://github.com/saya-ch/dsh-mobile/issues/107), [#108](https://github.com/saya-ch/dsh-mobile/issues/108) |
54
62
  | [@Yurzi](https://github.com/Yurzi) | [#17](https://github.com/saya-ch/dsh-mobile/issues/17) |
55
63
  | [@Ztyss](https://github.com/Ztyss) | [#22](https://github.com/saya-ch/dsh-mobile/issues/22), [#23](https://github.com/saya-ch/dsh-mobile/issues/23) |
package/README.en.md CHANGED
@@ -31,19 +31,19 @@
31
31
 
32
32
  > DSH Mobile is a DeepSeek Harness community plugin; the native app supports Android only.
33
33
  >
34
- > **0.4.5 update**: support DSH `0.1.7-alpha.2` mobile boot URLs, fix reconnect failures and long-lived caching of failed resources, and add an exit from App connection restoration. [Details](CHANGELOG.md).
34
+ > **Current version: 0.4.7**. It supports DSH `0.1.7-rc.2`, avoids duplicate mobile boot downloads, and improves Android recovery, the phone-width sidebar, and input handling. [Release notes](CHANGELOG.md#047---2026-09-25).
35
35
  >
36
- > **Upgrade reminder**: update both the plugin and Android app to 0.4.5 when practical. Existing pairings are retained and older apps can still connect, but Retry and Device list during connection restoration require the new app. [Compatibility notes](#compatibility).
36
+ > **Upgrade reminder**: update both the plugin and Android app to 0.4.7 when practical. Existing pairings remain intact. The self-signed FRP entry requires at least the 0.4.6 app; older apps can still use existing LAN and trusted-certificate remote connections. [Compatibility notes](#compatibility).
37
37
 
38
38
  <p align="center">
39
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.5/dsh-mobile-android-v0.4.5.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
40
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.5/dsh-mobile-android-v0.4.5.apk"><strong>Download Android app 0.4.5</strong></a><br>
41
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.5">Release notes and checksums</a></sub>
39
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.7/dsh-mobile-android-v0.4.7.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
40
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.7/dsh-mobile-android-v0.4.7.apk"><strong>Download Android app 0.4.7</strong></a><br>
41
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.7">Release notes and checksums</a></sub>
42
42
  </p>
43
43
 
44
44
  DSH Mobile is a DeepSeek Harness plugin that lets a mobile browser or the Android app connect over a protected LAN or an optional Tailscale Funnel, cpolar, cloudflared, self-hosted FRP, or own reverse-proxy remote path. Local and remote access keep the same sessions, Workspaces, messages, and tools while using separate switches and paired-device stores without modifying DeepSeek Harness source.
45
45
 
46
- Mobile access runs on its own HTTPS origin with pinned certificates; only paired devices pass validation.
46
+ Mobile access uses a dedicated HTTPS origin and device pairing. The Android app pins the private LAN CA; public remote paths use platform-trusted certificates. The self-signed FRP entry additionally requires the 0.4.6 app to pin a remote CA during pairing.
47
47
 
48
48
  It also lets you customize the phone from a DSH conversation: `/mobile <what you want>`.
49
49
 
@@ -56,7 +56,7 @@ It also lets you customize the phone from a DSH conversation: `/mobile <what you
56
56
  - **Pairing and multi-device**: pair once via QR code, link, or key; Wi-Fi, hotspot, or IP changes normally recover automatically; the app shows all paired computers together in one device list (LAN and every remote), each with live reachability — switch, re-pair, or delete in one tap.
57
57
  - **One-click diagnostics and approval**: check versions, gateway, network interface, firewall, and the remote path with a redacted report; approve blocked third-party plugin connections per exact path.
58
58
  - **Task system notifications**: completion and pending-input alerts via Android system notifications, enabled from DSH General settings inside the app, with redacted lock-screen text.
59
- - **Defense in depth**: dedicated HTTPS with pinned certificates, Keystore-backed credentials, device tokens sent only to their exact Origin, third-party WebSockets blocked by default.
59
+ - **Defense in depth**: a private CA pinned for LAN, trusted HTTPS for public remote paths, and a remote CA pinned by the 0.4.6 app for the self-signed FRP entry. Credentials are Keystore-backed, device tokens go only to their exact Origin, and third-party WebSockets are blocked by default.
60
60
 
61
61
  A paired device is fully trusted and can operate the DSH on the computer. Use this only on a trusted home or office LAN, or a trusted VPN.
62
62
 
@@ -143,7 +143,7 @@ Remote providers may impose bandwidth and connection limits: the [cpolar Free pl
143
143
 
144
144
  Tailscale Funnel has broad reach but may be unreliable from mainland China. Its runtime ties the public listener to the parent process and a bounded control channel; parent exit, channel closure, or an explicit stop ends the current generation and cleans up its resources. cpolar is better suited to mainland networks, while self-hosted FRP fits users who already have a VPS and want to avoid public-provider bandwidth quotas. cloudflared runs in two modes: a quick tunnel needs no account or sign-in, but its hostname is random, changes on every reconnect, and is positioned by Cloudflare for testing with no uptime guarantee, so it suits temporary or verification use rather than a permanent channel; a named tunnel uses a Cloudflare account token and keeps one fixed public hostname across restarts. An unregistered domain on a mainland-China VPS may be intercepted by the cloud provider; public IPv4 mode avoids that dependency. The plugin validates pinned on-demand components, stores their configuration and programs entirely under `$DSH_HOME/mobile-access/`, and can remove them completely from the panel.
145
145
 
146
- Self-hosted FRP generates only one HTTP vhost to the DSH loopback gateway. It exposes no arbitrary FRP configuration, TCP/UDP proxy, or FRP plugin. The VPS plaintext vhost must bind to `127.0.0.1`, with Caddy providing public HTTPS; the plugin rejects a publicly reachable plaintext port and reports readiness only after public discovery identifies the current computer.
146
+ Managed self-hosted FRP uses an HTTP vhost to the DSH loopback gateway. Its plaintext VPS listener must be loopback-only, with Caddy providing public HTTPS. Version 0.4.6 adds existing-frps attachment without installing or changing the server: its public-CA mode still needs a restricted HTTP vhost and Caddy, while its self-signed mode forwards raw TCP to a computer-side HTTPS gateway whose CA the 0.4.6 Android app pins during pairing. The self-signed mode currently uses public IPv4 and is unavailable to older apps. Neither mode exposes arbitrary FRP configuration. Because frps `proxyBindAddr` governs proxy listeners globally, do not change it for the new TCP entry without checking existing plaintext vhosts. See the [attachment guide](docs/ATTACH_EXISTING_FRPS.en.md).
147
147
 
148
148
  The own-proxy HTTP backend must remain on a trusted private network: **never port-forward it publicly or bypass it by proxying to DSH or the existing LAN 3443 gateway**. CIDRs match the proxy's direct TCP peer, not forwarded headers. Preserve the external Host (including port), Origin, cookies and WebSocket. Clearing proxy settings keeps paired remote devices.
149
149
 
@@ -184,7 +184,7 @@ The examples above, applied:
184
184
 
185
185
  ## Device management
186
186
 
187
- The Android app shows multiple computers at once in one **Paired computers** list: LAN, cpolar, cloudflared, Tailscale Funnel, and self-hosted FRP pairings together. The first upgrade migrates the legacy LAN and remote credentials without requiring another pairing; when an address changes, the app merges the row by the DSH installation's stable `instanceId` and keeps its custom name. Device tokens and LAN CAs remain encrypted by Android Keystore and never appear in the list or QR code.
187
+ The Android app shows multiple computers at once in one **Paired computers** list: LAN, cpolar, cloudflared, Tailscale Funnel, and self-hosted FRP pairings together. The first upgrade migrates the legacy LAN and remote credentials without requiring another pairing; an address change merges into a record with the same `instanceId` and keeps its custom name. Self-signed FRP uses its own CA fingerprint as identity, so its first pairing may appear as a separate row from LAN for the same computer. Android Keystore encrypts device tokens and LAN CAs; the 0.4.6 app also encrypts that self-signed entry's pinned remote CA. These values never appear in the list or QR code.
188
188
 
189
189
  Each row shows its custom name, transport, Origin, live reachability, and last connection time. A green dot means **Reachable**; a gray dot means **Checking**, **Temporarily unreachable**, **Pairing expired**, or **Removed on computer**. The check validates the DSH Gateway over HTTPS instead of using ICMP, so a temporary network outage is not mistaken for computer-side revocation.
190
190
 
@@ -216,11 +216,23 @@ Compatibility and WebSocket rules:
216
216
 
217
217
  Proxied pages allow HTTP frames for compatibility with some community plugins; those pages are unencrypted and can be altered, and browsers may still block them as mixed content. Use HTTPS for sensitive work. The same warning appears at the top of the remote panel when it is opened over HTTPS.
218
218
 
219
- - Version 0.4.5 has been contract-checked and boot-tested against DSH `0.1.7-alpha.2` (renderer-v2). The DSH page must expose the standard session, `main`/`panelInfo`, and `rightbar` slots; the community plugin must register its panel or sidebar content through DSH's standard entry points.
219
+ - Version 0.4.7 has been contract-checked and boot-tested against DSH `0.1.7-alpha.2`, `0.1.7-rc.1`, and `0.1.7-rc.2` (renderer-v2). The DSH page must expose the standard session, `main`/`panelInfo`, and `rightbar` slots; the community plugin must register its panel or sidebar content through DSH's standard entry points.
220
220
  - The gateway allows first-party DSH WebSocket paths by default, including `/sidebar/ws/terminal`. Other paths used by community sidebar plugins are blocked by default and appear in Diagnostics; the `/sidebar/ws/agent-opens` and `/sidebar/ws/agent-terminals` paths in the image are examples that must be reviewed for the actual plugin.
221
221
  - In **Connection diagnostics → Third-party WebSocket paths**, select **Allow** only for an exact path you have verified. Query strings and fuzzy prefixes are rejected; **Allow all** is not recommended. Approved paths can be removed at any time, and the same policy applies to LAN and remote connections.
222
222
  - Approval only lets that path pass through the authenticated, same-origin DSH Mobile gateway. It does not open arbitrary TCP/UDP ports or bypass device pairing. If a community plugin still fails, check the path recorded by Diagnostics and approve one path at a time.
223
223
 
224
+ If another remote-access plugin shows its own “not paired” page inside DSH Mobile, the two authorization systems are separate. Temporarily turn off the other plugin's remote access on the computer to check whether the Mobile path recovers; do not enter a DSH Mobile pairing key on that page. Excluding its client module below does not necessarily remove a request-rewriting script injected before client boot. See [#111](https://github.com/saya-ch/dsh-mobile/issues/111).
225
+
226
+ Advanced users can reduce mobile startup traffic by adding exact package ids to `excludedClientModules` in the current profile's `mobile-access` plugin configuration, then restarting DSH. This changes only the dedicated mobile page served through the gateway; desktop and `?frontend=stock` pages are unaffected. If both packages are present in the current boot graph, this example removes document preview and its dependent “Open In…” feature:
227
+
228
+ ```yaml
229
+ excludedClientModules:
230
+ - '@deepseek-ai/dsh-client-ui-sidebar-documentpreview'
231
+ - '@deepseek-ai/dsh-client-ui-open-in-app'
232
+ ```
233
+
234
+ There is no default exclusion list. The plugin rejects unknown or boot-critical modules and modules still referenced through `inject` or `external` by retained entries. If a DSH or community-plugin update invalidates the selection, the mobile page returns `409 excluded_client_modules_invalid` with the conflicting module; the computer also warns so you can adjust or remove the option. Bundle sizes and dependencies change between DSH installations and versions; another user's savings are not a prediction for yours.
235
+
224
236
  <table>
225
237
  <tr>
226
238
  <td align="center" valign="top" width="50%">
@@ -268,7 +280,7 @@ Three layers: the Host face for discovery, pairing, HTTPS, loopback proxying, an
268
280
  - A remote origin is publicly reachable, but unpaired requests cannot enter DSH; turn the remote switch off when it is not needed.
269
281
  - cpolar downloads a pinned official build only after confirmation and verifies its size and SHA-256. It installs no system service, PATH entry, or startup task, and plugin cleanup removes its managed files.
270
282
  - cloudflared likewise downloads a pinned build from the official GitHub Release only after confirmation and verifies the exact size and SHA-256, and it launches the client with automatic updates disabled so the running binary is always the verified one. A quick tunnel needs no account, token, or DNS record; a named tunnel token is stored only in the plugin private directory and reaches cloudflared through the environment, and cleanup deletes every file it manages.
271
- - Self-hosted FRP downloads pinned official `frpc` only after confirmation and verifies the origin, exact size, SHA-256, archive paths, and executable version. The shared token never appears in status, diagnostics, or logs. Copying the server template places it on the system clipboard, so clear the clipboard after use; local cleanup removes only plugin-managed files, while the VPS is cleaned separately with the uninstall script or one-click server cleanup. Automatic deployment and server cleanup both display the SSH host keys, which must be verified against the VPS console before continuing.
283
+ - Self-hosted FRP downloads pinned official `frpc` only after confirmation and verifies the origin, exact size, SHA-256, archive paths, and executable version. The shared token never appears in status, diagnostics, or attachment-plan responses. Copying a server template, or explicitly copying a token-bearing attach config after re-entering its token, places it on the system clipboard; clear it after use. Local cleanup removes only plugin-managed files. Managed VPS deployments require separate uninstall-script or one-click cleanup; attaching to an existing frps neither changes nor cleans up that VPS. Automatic deployment and server cleanup require SSH host-key verification against the VPS console.
272
284
  - A paired device is a fully trusted DeepSeek Harness operator and can run tools on the computer; revoke lost devices from the computer.
273
285
  - The LAN gateway listens only while Mobile Access is enabled; with it off, DSH keeps running normally on the computer.
274
286
 
@@ -276,6 +288,7 @@ See [SECURITY.md](SECURITY.md).
276
288
 
277
289
  ## Troubleshooting
278
290
 
291
+ - **Development diagnostics say “reachable through the computer's proxy”**: the check tries a direct request first, then an HTTP proxy from the DSH process environment only if direct access fails; `NO_PROXY` may exclude the target. This proves only that the computer completed an HTTPS probe through the proxy, not that the phone or actual tunnel can connect. Test from the phone's mobile network. If both paths fail, diagnostics continue to report the endpoint unreachable instead of treating an offline route as ready.
279
292
  - **Boot fails with `saved LAN interface "XXX" is not connected`**: the
280
293
  computer switched networks (Wi-Fi/Ethernet/dock) and the previously saved
281
294
  adapter is down. Either reconnect that network, re-run setup on the new
@@ -303,6 +316,8 @@ On macOS, local network, self-hosted FRP, and the own reverse proxy work; the th
303
316
 
304
317
  | DSH Mobile plugin | Verified DeepSeek Harness version |
305
318
  | --- | --- |
319
+ | `0.4.7` | `0.1.7-alpha.2`, `0.1.7-rc.1`, and `0.1.7-rc.2` (source contract, isolated pairing, and workspace baseline over WebSocket) |
320
+ | `0.4.6` | `0.1.7-alpha.2` and `0.1.7-rc.1` (source contract, isolated pairing, and workspace baseline over WebSocket) |
306
321
  | `0.4.5` | `0.1.7-alpha.2` (source contract check, local and remote gateway integration) |
307
322
  | `0.4.4` | `0.1.6-alpha.2` (local source and renderer-v2 contract check) |
308
323
  | `0.4.3` | `0.1.6-alpha.2` (local source and renderer-v2 contract check) |
@@ -318,6 +333,8 @@ On macOS, local network, self-hosted FRP, and the own reverse proxy work; the th
318
333
 
319
334
  Existing apps (0.3.3 and later) do not need re-pairing. cpolar users should use app 0.3.15 or later because earlier apps may time out before a slow first load over the free route finishes; earlier apps also use a different status-bar strategy. The 0.4.0 app adds the multi-device list, startup behavior, and computer-side revocation status; older apps continue to connect to their saved single device. App 0.1.3 or earlier requires reinstalling and pairing again.
320
335
 
336
+ GitHub Release APKs use a stable signing certificate, so an older official APK with the same signer can be upgraded in place while retaining pairings. A locally built Debug APK with a different signer cannot be overwritten by the official APK; plan to pair again when switching between them.
337
+
321
338
  ## Uninstall
322
339
 
323
340
  ```powershell
@@ -344,4 +361,14 @@ npm ci
344
361
  npm run verify
345
362
  ```
346
363
 
364
+ The real browser-startup smoke uses a temporary DSH home, OS-assigned loopback ports, and Chromium pairing. It neither reads an existing user profile nor sends a model request. CI tests DSH `0.1.7-alpha.2`, `0.1.7-rc.1`, and `0.1.7-rc.2`; install rc.2 in a separate directory locally so it does not replace the plugin's development dependencies:
365
+
366
+ ```powershell
367
+ $dshMobileTestRuntime = Join-Path $env:TEMP 'dsh-mobile-test-runtime'
368
+ npm install --prefix $dshMobileTestRuntime --no-save --package-lock=false @deepseek-ai/dsh@0.1.7-rc.2
369
+ $env:DSH_BOOT_SMOKE_BIN = Join-Path $dshMobileTestRuntime 'node_modules/@deepseek-ai/dsh/lib/bin.js'
370
+ npx playwright install chromium --only-shell
371
+ npm run smoke:dsh-boot
372
+ ```
373
+
347
374
  See the [Android guide](https://github.com/saya-ch/dsh-mobile/blob/main/apps/mobile/README.md). Licensed under [Apache-2.0](LICENSE).
package/README.md CHANGED
@@ -31,19 +31,19 @@
31
31
 
32
32
  > DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
33
33
  >
34
- > **0.4.5 更新**:适配 DSH `0.1.7-alpha.2` 的移动端启动脚本地址,修复重连异常与失败资源的长期缓存,并为 App 的连接恢复增加退出入口。[详细记录](CHANGELOG.md)。
34
+ > **当前版本:0.4.7**。适配 DSH `0.1.7-rc.2`,减少移动端启动资源的重复下载,并改善 Android 连接恢复、窄屏侧栏与输入体验。[更新记录](CHANGELOG.md#047---2026-09-25)。
35
35
  >
36
- > **升级提醒**:建议插件与 Android App 同步更新至 0.4.5;已有配对会保留,旧版 App 仍可连接,但需要新版 App 才能使用连接恢复时的重试和设备列表入口。[兼容说明](#兼容性)。
36
+ > **升级提醒**:建议插件与 Android App 同步更新至 0.4.7,已有配对会保留。自签 FRP 入口需要至少 0.4.6 App;更早的 App 仍可使用原有局域网和受信任证书的远程连接。[兼容说明](#兼容性)。
37
37
 
38
38
  <p align="center">
39
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.5/dsh-mobile-android-v0.4.5.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
40
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.5/dsh-mobile-android-v0.4.5.apk"><strong>下载 Android App 0.4.5</strong></a><br>
41
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.5">版本说明与校验文件</a></sub>
39
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.7/dsh-mobile-android-v0.4.7.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
40
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.7/dsh-mobile-android-v0.4.7.apk"><strong>下载 Android App 0.4.7</strong></a><br>
41
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.7">版本说明与校验文件</a></sub>
42
42
  </p>
43
43
 
44
44
  DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、cloudflared、自建 FRP 或自有反向代理远程通道连接电脑,继续使用同一份会话、工作区、消息和工具。局域网与远程访问分别启停、分别管理设备,且都不修改 DeepSeek Harness 源码。
45
45
 
46
- 移动访问使用独立的 HTTPS 与证书固定,只有配对过的设备能通过校验接入。
46
+ 移动访问使用独立 HTTPS 与设备配对;Android App 固定局域网私有 CA,公开远程通道使用系统信任的证书;自签 FRP 入口还需由 0.4.6 App 在配对时固定远程 CA。
47
47
 
48
48
  它还能在 DSH 对话里用 `/mobile <需求>` 定制手机端。
49
49
 
@@ -56,7 +56,7 @@ DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App
56
56
  - **配对与多设备**:扫码、链接或密钥配对一次;切换 Wi-Fi、热点或 IP 后通常自动恢复;App 在一个设备列表中同时显示多台已配对电脑(局域网与全部远程),每台实时显示可达状态,一键切换、重新配对或删除。
57
57
  - **一键诊断与放行**:检查版本、网关、网卡、防火墙和远程通道,生成脱敏报告;被拦截的第三方插件连接按确切路径一键放行。
58
58
  - **任务系统通知**:任务完成与待输入以 Android 系统通知提醒,可在 App 内的 DSH 常规设置中开启,锁屏文本脱敏。
59
- - **纵深安全**:独立 HTTPS 与证书固定,凭据存 Keystore,设备令牌只发往精确 Origin,第三方 WS 默认拦截。
59
+ - **纵深安全**:局域网固定私有 CA,公开远程入口使用受信任 HTTPS;自签 FRP 入口由 0.4.6 App 固定远程 CA。凭据存 Keystore,设备令牌只发往精确 Origin,第三方 WS 默认拦截。
60
60
 
61
61
  配对设备被视为完全信任,可以操作电脑上的 DSH;建议只在可信的家庭、办公局域网或可信 VPN 中使用。
62
62
 
@@ -143,7 +143,7 @@ dsh plugin --profile web add dshmarket
143
143
 
144
144
  Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。其运行组件把公开监听生命周期绑定到父进程和受限控制通道;父进程退出、控制通道关闭或显式停止时会结束当前代次并清理资源。cpolar 更适合国内网络;自建 FRP 适合已有 VPS、希望避开公共服务带宽限制的用户。cloudflared 有两种模式:快速隧道不需要账号或登录,但地址随机、每次重连都会变化,官方定位为测试用途且无可用性保证,因此只适合临时或验证场景;命名隧道使用 Cloudflare 账号令牌,能保留重启后不变的固定公网域名。中国大陆 VPS 上的未备案域名可能被云厂商拦截,此时可使用公网 IPv4 模式。插件会校验按需下载的固定版本组件,配置与程序均保存在 `$DSH_HOME/mobile-access/`,可随时在面板中彻底清除。
145
145
 
146
- 自建 FRP 只生成一个指向 DSH 回环网关的 HTTP vhost,不提供任意 FRP 配置、TCP/UDP 代理或 FRP 插件。VPS 的明文 vhost 必须只监听 `127.0.0.1`,由 Caddy 提供公网 HTTPS;插件会拒绝可从公网访问的明文端口,并在公开发现接口确认连接到当前电脑后才显示“已就绪”。
146
+ 自建 FRP 的托管部署使用指向 DSH 回环网关的 HTTP vhost:VPS 上的明文 vhost 只允许回环访问,由 Caddy 提供公网 HTTPS。0.4.6 新增“接入既有 frps”,不会安装或改动服务器;公开 CA 档仍需受限 HTTP vhost 与 Caddy,自签档则用 frps TCP 透传至电脑端 HTTPS 网关,须由 0.4.6 Android App 在配对时固定 CA。自签档目前面向公网 IPv4,旧版 App 不支持;两档都不开放任意 FRP 配置。frps 的 `proxyBindAddr` 是全局代理监听设置,不能只为新增 TCP 入口调整而忽略已有明文 vhost。[接入指南](docs/ATTACH_EXISTING_FRPS.md)。
147
147
 
148
148
  自有反向代理的 HTTP 后端只允许留在可信私网;**不要把它映射到公网,也不要绕过它直连 DSH 或现有 LAN 3443**。来源 CIDR 匹配代理的直接 TCP 来源,不信任转发头;反代须保留外部 Host(含端口)、Origin、Cookie 和 WebSocket。清除代理配置不会删除已配对远程设备。
149
149
 
@@ -184,7 +184,7 @@ Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。
184
184
 
185
185
  ## 设备管理
186
186
 
187
- Android App 用一个“已配对设备”列表同时显示多台电脑:局域网、cpolar、cloudflared、Tailscale Funnel 和自建 FRP 配对共处一处。首次升级会自动迁移旧版局域网与远程凭据,不要求重新配对;地址变化时按 DSH 安装的稳定 `instanceId` 合并原记录,保留自定义名称。设备 Token 和局域网 CA 继续由 Android Keystore 加密保存,不会显示在列表或二维码中。
187
+ Android App 用一个“已配对设备”列表同时显示多台电脑:局域网、cpolar、cloudflared、Tailscale Funnel 和自建 FRP 配对共处一处。首次升级会自动迁移旧版局域网与远程凭据,不要求重新配对;地址变化且 `instanceId` 与原记录一致时会合并条目,保留自定义名称。自签 FRP 使用独立 CA 指纹作为身份,首次与局域网配对时可能显示为另一条记录。设备 Token 和局域网 CA 由 Android Keystore 加密保存;0.4.6 App 还会加密保存自签 FRP 入口固定的远程 CA。这些信息不会显示在设备列表或二维码中。
188
188
 
189
189
  每条记录显示自定义名称、连接方式、Origin、实时可达状态和最近连接时间。绿色状态点表示“可达”,灰色状态点表示“检测中”“暂不可达”“配对已过期”或“电脑端已移除”;可达性检查直接验证 DSH Gateway,不依赖 ICMP,也不会把暂时断网误判成电脑端撤销。
190
190
 
@@ -216,11 +216,23 @@ Android App 用一个“已配对设备”列表同时显示多台电脑:局
216
216
 
217
217
  代理页面为兼容部分社区插件允许嵌入 HTTP 页面;这类内容未加密,可能被篡改,浏览器也可能因混合内容策略拦截。处理敏感内容时请使用 HTTPS。通过 HTTPS 管理入口打开远程面板时,页面顶部会显示相同提醒。
218
218
 
219
- - 0.4.5 已按 DSH `0.1.7-alpha.2` 做 renderer-v2 契约检查与启动包联调。DSH 页面需要提供标准的会话、`main`/`panelInfo` 和 `rightbar` 插槽;插件自身需要通过 DSH 的标准面板或侧边栏入口注册内容。
219
+ - 0.4.7 已按 DSH `0.1.7-alpha.2`、`0.1.7-rc.1` 和 `0.1.7-rc.2` 做 renderer-v2 契约检查与隔离启动联调。DSH 页面需要提供标准的会话、`main`/`panelInfo` 和 `rightbar` 插槽;插件自身需要通过 DSH 的标准面板或侧边栏入口注册内容。
220
220
  - 网关默认只允许 DSH 内置的第一方 WebSocket 路径。社区侧边栏插件使用的其他路径默认拦截,通常会在诊断页显示为待处理项目;截图中的`/sidebar/ws/agent-opens` 和`/sidebar/ws/agent-terminals` 就属于这类需要按实际插件确认的路径。
221
221
  - 在 **连接诊断 → 第三方 WebSocket 路径** 中,只对确认过的精确路径点击 **允许**。系统不接受带查询字符串或模糊前缀的路径;不建议使用“全部允许”。已允许的路径可以随时移除,局域网和远程连接使用同一套规则。
222
222
  - 放行只代表该路径可以通过已认证、同源的 DSH Mobile 网关,不会开放任意 TCP/UDP 端口,也不会绕过设备配对。若社区插件仍然连接失败,先看诊断页的实际拦截路径,再按一条路径放行。
223
223
 
224
+ 若另一个远程访问插件在 DSH Mobile 页面上显示自己的“未配对”提示,那是两套不同的授权。可先在电脑端暂时关闭另一插件的远程访问,确认 Mobile 链路是否恢复;不要把 DSH Mobile 密钥输入它的配对页。仅用下面的模块排除选项不能保证移除该插件在页面启动前注入的请求改写脚本,参见 [#111](https://github.com/saya-ch/dsh-mobile/issues/111)。
225
+
226
+ 若某个不需要的客户端插件显著增加手机首次加载流量,高级用户可在当前 profile 的 `mobile-access` 插件配置中设置 `excludedClientModules`,填入启动清单中的**准确包名**,重启 DSH 后生效。它只改变经网关提供的专用移动页面;电脑端和 `?frontend=stock` 页面不变。例如,若当前启动清单同时包含以下两个包,下面的配置会移除文档预览及依赖它的“在应用中打开”功能:
227
+
228
+ ```yaml
229
+ excludedClientModules:
230
+ - '@deepseek-ai/dsh-client-ui-sidebar-documentpreview'
231
+ - '@deepseek-ai/dsh-client-ui-open-in-app'
232
+ ```
233
+
234
+ 这是按安装环境选择的高级配置,没有默认排除列表。插件会拒绝不存在的包、启动必需模块,以及仍被保留模块通过 `inject` 或 `external` 引用的包;配置不匹配当前 DSH 或社区插件时,移动页面返回 `409 excluded_client_modules_invalid` 和冲突详情,电脑端也会提示,移除或调整配置即可恢复。模块内容与依赖会随 DSH 版本变化,不能把其他人的节省比例当作自己的预期。
235
+
224
236
  <table>
225
237
  <tr>
226
238
  <td align="center" valign="top" width="50%">
@@ -270,7 +282,7 @@ flowchart LR
270
282
  - 远程地址可从公网到达,但未配对请求无法进入 DSH;不使用时应关闭远程开关。
271
283
  - cpolar 仅在用户确认后下载固定官方版本并校验大小和 SHA-256;不会安装系统服务、写入 PATH 或设置开机启动,插件清理会删除其托管文件。
272
284
  - cloudflared 同样仅在用户确认后从官方 GitHub Release 下载固定版本并校验精确大小和 SHA-256,且启动时关闭自动更新,以保证运行的始终是已校验的那份二进制;快速隧道不需要账号、Token 或 DNS 记录;命名隧道令牌只存插件私有目录、只经环境变量传给 cloudflared,清理时删除插件托管的全部文件。
273
- - 自建 FRP 仅在用户确认后从官方 Release 下载固定版本 `frpc`,校验来源、精确大小、SHA-256、压缩包路径和可执行文件版本;共享 Token 不会出现在状态、诊断或日志中。复制服务器模板时 Token 会进入系统剪贴板,请粘贴后及时清除;本机清理只删除插件管理的文件,VPS 需要用面板提供的卸载脚本或一键清理单独清除。自动部署与一键清理前都会展示 SSH 主机指纹,必须到 VPS 控制台核对后才能继续。
285
+ - 自建 FRP 仅在用户确认后从官方 Release 下载固定版本 `frpc`,校验来源、精确大小、SHA-256、压缩包路径和可执行文件版本;共享 Token 不会出现在状态、诊断或接入清单响应中。复制服务器模板,或重新输入 Token 后显式复制含 Token 的接入配置时,它会进入系统剪贴板,请粘贴后及时清除。本机清理只删除插件管理的文件;托管部署的 VPS 需用面板卸载脚本或一键清理单独清除,接入既有 frps 则不会改动或清理你的 VPS。自动部署与一键清理前都需核对 SSH 主机指纹。
274
286
  - 配对设备拥有控制电脑端 DeepSeek Harness 的能力,应视为完全可信设备;丢失手机后应在电脑端撤销设备。
275
287
  - 移动网关开启时才监听局域网;关闭后 DeepSeek Harness 仍正常在电脑本机运行。
276
288
 
@@ -278,6 +290,7 @@ flowchart LR
278
290
 
279
291
  ## 故障排查
280
292
 
293
+ - **开发版远程诊断显示“电脑经代理可访问”**:诊断先直连,失败后才尝试 DSH 进程环境变量中的 HTTP 代理;`NO_PROXY` 可排除目标。此结果只说明电脑经代理完成 HTTPS 探测,不代表手机或实际隧道可用。请用手机流量实际连接;若直连和代理都失败,报告仍为不可达,不会把离线误报为就绪。
281
294
  - **启动报 `saved LAN interface "XXX" is not connected` 且 DSH 起不来**:
282
295
  电脑切换过网络(WiFi/有线/扩展坞),之前保存的网卡当前未连接。处理方式
283
296
  (三选一):连回原来的网络;按新网络重跑一遍 setup;暂时不用手机访问时,
@@ -303,6 +316,8 @@ macOS 上局域网、自建 FRP 与自有反向代理可用;三个托管组件
303
316
 
304
317
  | DSH Mobile 插件 | 验证支持的 DeepSeek Harness 版本 |
305
318
  | ----------------------------------------- | -------------------------------------------------------------- |
319
+ | `0.4.7` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`(源码契约、隔离配对及 WebSocket 工作区读取) |
320
+ | `0.4.6` | `0.1.7-alpha.2`、`0.1.7-rc.1`(源码契约、隔离配对及 WebSocket 工作区读取) |
306
321
  | `0.4.5` | `0.1.7-alpha.2`(源码契约检查、本机局域网及远程网关联调) |
307
322
  | `0.4.4` | `0.1.6-alpha.2`(本机源码与 renderer-v2 契约检查) |
308
323
  | `0.4.3` | `0.1.6-alpha.2`(本机源码与 renderer-v2 契约检查) |
@@ -318,6 +333,8 @@ macOS 上局域网、自建 FRP 与自有反向代理可用;三个托管组件
318
333
 
319
334
  现有 App(0.3.3 及更新)无需重新配对;cpolar 用户应使用 0.3.15 或更新 App,较早版本可能在免费线路的慢速首次加载完成前超时;更早的 App 还使用不同的状态栏策略。App 0.4.0 才支持多设备列表、启动行为设置和电脑端撤销状态同步;旧版 App 仍可连接已保存的单台设备。App 0.1.3 及更早版本需卸载重装并重新配对。
320
335
 
336
+ GitHub Release 的正式 APK 使用固定签名,可从同一签名的旧正式版原位升级并保留配对。自行构建的 Debug APK 若使用不同签名,不能直接覆盖安装正式版;切换前请准备重新配对。
337
+
321
338
  ## 卸载
322
339
 
323
340
  ```powershell
@@ -344,6 +361,16 @@ npm ci
344
361
  npm run verify
345
362
  ```
346
363
 
364
+ 真实启动冒烟另用临时 DSH Home、随机回环端口和 Chromium 配对,不访问现有用户配置,也不发送模型请求。CI 分别测试 DSH `0.1.7-alpha.2`、`0.1.7-rc.1` 与 `0.1.7-rc.2`;本机可把 rc.2 装在独立目录,避免替换插件的开发依赖:
365
+
366
+ ```powershell
367
+ $dshMobileTestRuntime = Join-Path $env:TEMP 'dsh-mobile-test-runtime'
368
+ npm install --prefix $dshMobileTestRuntime --no-save --package-lock=false @deepseek-ai/dsh@0.1.7-rc.2
369
+ $env:DSH_BOOT_SMOKE_BIN = Join-Path $dshMobileTestRuntime 'node_modules/@deepseek-ai/dsh/lib/bin.js'
370
+ npx playwright install chromium --only-shell
371
+ npm run smoke:dsh-boot
372
+ ```
373
+
347
374
  Android 构建见 [App 文档](https://github.com/saya-ch/dsh-mobile/blob/main/apps/mobile/README.zh-CN.md)。
348
375
 
349
376
  Apache-2.0,详见 [LICENSE](LICENSE)。
package/SECURITY.md CHANGED
@@ -19,15 +19,17 @@ The maintainer will acknowledge a complete report within seven days. Publication
19
19
  - The desktop admin API may accept an RFC1918 or IPv4 link-local Host only when the TCP peer is loopback, for a trusted local reverse proxy or headless host. Do not publicly forward `/api/mobile-access`; Host and loopback checks are not a replacement for proxy access control. Mutating admin requests must carry a same-origin `Origin` header.
20
20
  - DNS-SD/mDNS, periodic UDP announcements, active UDP query replies, and HTTPS discovery return only the device name, public HTTPS origin, port, protocol version, and stable non-secret installation identifier. Discovery never returns the CA, a pairing key, a device token, Cookies, credentials, or private configuration.
21
21
  - For LAN pairing, only after a user selects a device and enters the fingerprint-bound pairing key may Android fetch the public CA from that exact HTTPS origin. The bootstrap GET sends no key or credential. The app retains the CA in its encrypted credential record and never adds it to Android's system trust settings. Native requests use a private trust store; WebView accepts only the otherwise-untrusted leaf signed by that CA, for the exact origin and validity period. Every other TLS error is cancelled.
22
- - Public remote origins provided by Funnel, cpolar, cloudflared, self-hosted FRP, or an operator-managed reverse proxy use platform-trusted HTTPS. Android stores no private CA for those credentials and cancels every TLS error. It sends a persisted device token only to the exact Origin that previously received it; a changed remote Origin requires a current one-time pairing token before the app replaces the saved credential. A QR-provided installation identifier alone never authorizes credential renewal. Browser clients likewise require a certificate trusted by their platform.
22
+ - Public remote origins provided by Funnel, cpolar, cloudflared, the FRP public-CA entry, or an operator-managed reverse proxy use platform-trusted HTTPS. Android stores no private CA for those credentials and cancels every TLS error. It sends a persisted device token only to the exact Origin that previously received it; a changed remote Origin requires a current one-time pairing token before the app replaces the saved credential. A QR-provided installation identifier alone never authorizes credential renewal. Browser clients likewise require a certificate trusted by their platform.
23
+ - The 0.4.6 existing-frps self-signed entry instead forwards raw TCP to the computer-side HTTPS gateway. Its QR link and bare App key carry a versioned `dsh2` CA-required marker, which Android 0.4.5 and earlier reject. During explicit remote pairing, the 0.4.6 app retrieves the gateway's public CA without sending a credential, checks its validity and SHA-256 fingerprint against the pairing key, and pins it to the exact Origin; HTTP 404, a missing, changed, or untrusted CA cannot downgrade this flow to platform trust. Re-pairing an existing pinned identity retains that CA requirement. The existing exact-Origin device-token restriction still applies: a CA fingerprint alone cannot renew a device credential. The CA is not installed in Android's system trust store. The 0.4.5 app lacks this remote CA-pinning path, and ordinary phone browsers will not trust the self-signed certificate.
23
24
  - Keep pairing closed except during a short local onboarding action.
24
25
  - Revoke a lost device immediately and rotate the device registry if credential theft is suspected.
25
- - Do not expose the LAN gateway through router port forwarding. Funnel, cpolar, cloudflared, and self-hosted FRP use a separate loopback gateway behind the selected remote path. The own reverse-proxy provider instead exposes a plugin-owned private HTTP origin restricted by its bind address and allowed source CIDRs; the operator's proxy must provide public HTTPS. The outer provider or proxy terminates public TLS, while DSH pairing, device authentication, CSRF checks, and session revocation remain enforced by the plugin gateway.
26
+ - Do not expose the LAN gateway through router port forwarding. Funnel, cpolar, cloudflared, and FRP reach a separate loopback gateway through their selected remote path. The own reverse-proxy provider instead exposes a plugin-owned private HTTP origin restricted by its bind address and allowed source CIDRs; the operator's proxy must provide public HTTPS. Funnel, cpolar, cloudflared, Caddy, or the own proxy terminates public TLS in their respective modes; the 0.4.6 self-signed FRP entry terminates TLS on the computer. DSH pairing, device authentication, CSRF checks, and session revocation remain enforced by the plugin gateway.
26
27
  - The Funnel node stores its Tailscale login state under `$DSH_HOME/mobile-access/remote/tailscale/`. The plugin does not request or store a Tailscale password, Auth Key, or OAuth secret.
28
+ - The proxy-assisted remote diagnostic probes only the public `/mobile-access/health` path. It tries direct HTTPS first and, only after direct failure, may use an HTTP CONNECT proxy named by the DSH process environment unless `NO_PROXY` excludes the target. The probe sends no device token or session Cookie; configured proxy credentials are sent to that proxy for CONNECT, not placed in the diagnostic report. A proxy-only success is a warning to test from the phone, not proof that the phone or the provider route is available. This diagnostic does not change the remote provider's traffic path.
27
29
  - cpolar is downloaded only after confirmation from a pinned official artifact whose size and SHA-256 are verified. Its Authtoken is stored in a private, self-update-disabled configuration under `$DSH_HOME/mobile-access/`, never returned by the admin API or written to logs. Cleanup removes the managed executable, configuration, logs, and independent remote device registry.
28
- - Self-hosted FRP accepts only a VPS address, frps control port, operator-generated high-entropy shared token, and standard-port public HTTPS origin. The plugin validates the token's length and format; the operator remains responsible for its entropy. It generates one HTTP vhost pointed at the plugin's ephemeral loopback gateway; arbitrary FRP configuration, TCP/UDP forwarding, FRP plugins, services, PATH entries, and startup tasks are not supported.
29
- - The generated frps template binds its plaintext vhost to `127.0.0.1` and expects Caddy to provide public HTTPS. Before starting `frpc`, the plugin rejects a vhost port reachable through the configured VPS address. It reports readiness only when the public discovery response carries this computer's exact non-secret installation identifier.
30
- - The FRP token stays in a private file under `$DSH_HOME/mobile-access/`, is never returned by status or diagnostic responses, and is never logged. Copying the generated server template intentionally places the token on the system clipboard and into the VPS configuration; clear the clipboard after use and protect the VPS file. The FRP cleanup action removes the managed `frpc`, local token, runtime configuration, staging files, and logs. Server-side artifacts are removed only through the reviewable uninstall script or the separately confirmed one-click VPS cleanup, both of which delete solely DSH Mobile-owned files, services, and tagged firewall rules.
30
+ - Self-hosted FRP accepts a VPS address, frps control port, operator-generated high-entropy shared token, and standard-port public HTTPS origin. The 0.4.6 self-signed entry additionally selects a non-reserved public TCP port on a globally routable IPv4 address. The plugin validates token length and format; the operator remains responsible for its entropy. Managed deployment creates one HTTP vhost and Caddy; existing-frps attachment changes no VPS files or services and allows either a restricted HTTP vhost with a public CA or one TCP passthrough to a computer-side HTTPS gateway. Arbitrary FRP configuration, general TCP/UDP forwarding, FRP plugins, PATH entries, and startup tasks are not supported.
31
+ - For an HTTP-vhost entry, the generated managed-frps template binds the plaintext listener to `127.0.0.1` and uses Caddy for public HTTPS. An existing frps must have its actual `vhostHTTPPort` entered explicitly and its plaintext vhost kept off the public network; the plugin probes the configured port before starting `frpc`. frps `proxyBindAddr` governs proxy listeners globally: exposing a TCP passthrough on the same instance must not accidentally expose another plaintext vhost. A separate frps instance may be required. For self-signed ingress, the computer-side CA lasts five years and the leaf lasts 397 days; the leaf is renewed under the same CA, but an expired or replaced CA is not silently trusted and requires fresh pairing. A local readiness probe does not establish phone reachability; verify the public entry from an independent network.
32
+ - The FRP token stays in a private file under `$DSH_HOME/mobile-access/`, is never returned by status, diagnostic, or attachment-plan responses, and is never logged. Copying a server template or an attach-mode `frpc.toml` after re-entering its token puts that token on the system clipboard; clear it after use and protect the VPS configuration. Local FRP cleanup removes the managed `frpc`, token, runtime configuration, ingress certificate material, staging files, and logs. Server-side cleanup scripts apply only to plugin-managed deployment and delete solely DSH Mobile-owned files, services, and tagged firewall rules; attach mode never deletes the operator's existing frps or Caddy files.
31
33
  - Automatic VPS deployment and one-click VPS cleanup first read the server's SSH host keys and require the operator to confirm every `SHA256:` fingerprint against the VPS console before any authenticated connection. All SSH/SCP channels pin `StrictHostKeyChecking=yes` to a temporary known_hosts file built from the confirmed keys; unknown, rotated, or extra keys abort the operation instead of being silently accepted. SSH credentials themselves are key-or-agent only, never passwords; the private key path is convenience state in the current browser profile, and key material is never uploaded or logged.
32
34
  - Disabling remote access stops the selected provider process without affecting LAN access. Resetting remote access also removes provider state and the independent remote device registry.
33
35
  - Treat every paired device as a fully trusted operator. Stock DSH methods reached through the authenticated loopback proxy may read configuration or run tools with the desktop user's authority.
@@ -0,0 +1,44 @@
1
+ # Attach to an existing frps
2
+
3
+ > **Requires plugin 0.4.6**; the self-signed entry also requires the 0.4.6 Android app. Android apps 0.3.3–0.4.5 can use the public-CA entry but not the self-signed entry.
4
+
5
+ Use this path when you already run frps on a public VPS. The plugin does not install, edit, or restart that frps and does not automatically change your Caddyfile; public-CA mode requires **you** to add a Caddy snippet and import. Inspect the server's listeners before choosing an entry mode. The panel's **Copy attachment plan** action only produces a masked local `frpc.toml` preview and VPS instructions; it neither connects to the VPS nor writes configuration. To copy a token-bearing local config, re-enter the token and explicitly click its copy button; a saved token is never returned to the page.
6
+
7
+ ## Choose the HTTPS entry
8
+
9
+ | Entry | Public route | VPS requirements | Client trust |
10
+ | --- | --- | --- | --- |
11
+ | **Public CA** (`public-ip-cert`, default) | HTTPS 443 → Caddy → loopback HTTP vhost → FRP | Enter the real `vhostHTTPPort`; keep that plaintext listener on loopback. A domain uses Caddy-managed certificates; public IPv4 needs a short-lived Let's Encrypt IP certificate and operator-managed renewal. | Phone browsers and Android app 0.3.3+ use the platform trust store and still require DSH pairing. |
12
+ | **Self-signed passthrough** (`self-signed`) | HTTPS on `publicPort` (default 33080) → frps raw TCP proxy → computer-side HTTPS gateway | Use a globally routable public IPv4 address. The frps TCP listener must be publicly reachable; no new Caddy site, public certificate, or HTTP vhost is needed for this entry. | Requires the 0.4.6 Android app with remote CA pinning. Older apps cannot pair; ordinary phone browsers do not trust the self-signed certificate. |
13
+
14
+ frps `proxyBindAddr` is global to proxy listeners, not a per-entry switch. Binding it to `127.0.0.1` protects the HTTP vhost but also makes a TCP `remotePort` unreachable from the public network. Binding it publicly for the TCP entry may expose plaintext vhosts already running on that frps. Inspect every listener and firewall rule; use a separate frps instance or the public-CA mode when the requirements conflict. Neither mode permits arbitrary FRP proxy configuration or editing the operator's server from the plugin.
15
+
16
+ The [official frps configuration reference](https://gofrp.org/en/docs/reference/server-configures/) defines `proxyBindAddr` as the proxy listening address and `vhostHTTPPort` as the HTTP-proxy listening port; use the values actually configured on your server.
17
+
18
+ ## Public-CA mode
19
+
20
+ 1. In **Self-hosted FRP**, choose **Attach to my existing frps** and **Public CA certificate**. Enter the VPS address, frps control port, high-entropy shared token, public domain or IPv4 HTTPS origin, and the frps instance's actual `vhostHTTPPort`. Do not assume the managed deployment's default of 7080.
21
+ 2. Copy the attachment plan. Confirm the plaintext vhost is loopback-only, then add the generated Caddy snippet and `import` manually. Caddy manages domain certificates. For public IPv4, the current plan pins Certbot 5.8.0 and uses `--standalone --preferred-profile shortlived --ip-address` for a [roughly six-day Let's Encrypt IP certificate](https://letsencrypt.org/2026/03/11/shorter-certs-certbot). Standalone issuance briefly stops Caddy to free port 80 and may interrupt other sites on the VPS; schedule a maintenance window, choose a domain, or use the 0.4.6 app's self-signed TCP mode if that interruption is unacceptable. The attach path only checks an existing `certbot.timer`; it does not install renewal or a certificate-copy/reload hook. Arrange renewal, installation into Caddy, and reload before expiry yourself. Copying the plan does not change the VPS.
22
+ 3. Install the pinned official `frpc` on the computer, save and verify the connection, and test `/mobile-access/discovery` from an independent external network with normal certificate validation. Confirm HTTP 200 and this computer's installation identifier before pairing through the app's **Remote access** flow or a phone browser. A local self-check alone does not verify public reachability.
23
+
24
+ ## Self-signed passthrough mode
25
+
26
+ The computer-side gateway terminates TLS with a self-signed CA valid for five years and a public-IPv4 leaf valid for 397 days. The leaf can be reissued under the same CA. An expired or replaced CA is not silently accepted: inspect the certificate status in the panel, confirm a new fingerprint, and pair the phone again. The QR link, copied link, and bare App key carry a `dsh2` marker requiring CA pinning; older apps reject that format. The CA is private to the 0.4.6 app's connection record, not installed in Android's system trust store.
27
+
28
+ 1. In **Self-hosted FRP**, enter the VPS address, frps control port, token, and `https://YOUR_PUBLIC_IPV4`. Choose **Attach to my existing frps**, **Self-signed passthrough**, and the public TCP port (default 33080; not 3080, 3443, or 3444).
29
+ 2. Copy the attachment plan and review its masked local `frpc.toml` and VPS checklist. This is read-only. After you install `frpc` and select **Save and verify connection**, the plugin writes the real config under its private `remote/frp/config/` directory (Unix mode `0600`; restricted ACL on Windows). If you re-entered the token and explicitly copied a token-bearing config, clear the clipboard promptly.
30
+ 3. On the VPS, allow the chosen TCP entry port in its host firewall and cloud security group; the existing frps control port must already be reachable from the computer. Confirm frps is running and the TCP proxy listens on an address reachable from the public network. The plugin does not change the server's frps configuration or firewall.
31
+ 4. From another network, check the entry with `curl -k -sS -o /dev/null -w '%{http_code}\n' https://YOUR_PUBLIC_IPV4:33080/mobile-access/discovery`. Replace the host and port first. HTTP 200 only shows transport reachability: `-k` bypasses curl's certificate verification and does **not** verify the gateway identity. Pair from the 0.4.6 Android app's **Remote access** flow, which fetches the public CA without a device credential, checks its fingerprint against the `dsh2` key, and pins it to that exact Origin. A 404 CA response cannot fall back to platform trust in this flow. A changed Origin or CA requires a new pairing.
32
+
33
+ ## Troubleshooting and cleanup
34
+
35
+ | Symptom | Check |
36
+ | --- | --- |
37
+ | `frp_attach_mode_requires_vhost_port` | Public-CA mode needs the existing frps instance's real `vhostHTTPPort`; a guessed value can make the plaintext exposure probe check the wrong port. |
38
+ | `frp_vhost_publicly_reachable` | The public-CA entry's plaintext vhost is reachable from outside. Restrict it without exposing another listener; a separate frps instance may be safer than changing global `proxyBindAddr`. |
39
+ | `frp_attach_cert_unknown` | The self-signed certificate is absent or unreadable. Inspect the panel's certificate status and the private `remote/ingress/` files; do not bypass fingerprint validation. |
40
+ | `frp_ingress_ca_expired` | The pinned CA expired. Reconfigure the self-signed entry and pair the phone again after checking the new fingerprint. |
41
+ | Browser reports an untrusted certificate | Expected for the self-signed entry. Use the 0.4.6 Android app and explicit pairing; use public-CA mode for a browser. |
42
+ | Public entry unreachable | Check the chosen port, cloud and host firewalls, frps listener address, running `frpc`, and the external-network probe. A computer-side probe is not proof that a phone can connect. |
43
+
44
+ **Remove FRP completely** deletes only plugin-managed files on the computer, including the local `frpc` configuration and self-signed ingress material. The attach path never deletes or restarts your existing frps, Caddy site, certificates, or firewall rules on the VPS. Managed deployment and its separate server cleanup are described in the [self-hosted FRP guide](SELF_HOSTED_FRP.en.md).
@@ -0,0 +1,98 @@
1
+ # 接入你既有的 frps(插件不自动改动服务器)
2
+
3
+ > **需要 0.4.6 插件**;自签档还必须使用 0.4.6 Android App。Android App 0.3.3–0.4.5 可使用公开证书档,但不支持自签档。
4
+
5
+ 本文面向**已经在一台公网 VPS 上跑着 frps**的用户。插件不会安装、修改或重启你的 frps,也不会自动改动 Caddyfile;若选择公开 CA 档,你需要自行新增 Caddy 片段和 `import`。本机侧不改动 DSH 自身配置。接入前须核对既有 frps 的监听;若它不满足所选入口档的要求,插件不会代你修改。
6
+
7
+ > 只读预览:面板「自建 FRP → 步骤 2」里的**复制接入清单**按钮不连接 VPS,也不写入配置文件;预览中的 Token 默认遮盖。要复制含 Token 的本机配置,须在面板重新输入 Token 并显式点击对应按钮;已保存的 Token 不会回传页面。
8
+
9
+ ## 三个必读项
10
+
11
+ | 项 | 含义 | 你要做什么 |
12
+ |---|---|---|
13
+ | **`vhostHTTPPort`** | 你 frps 上「Caddy ↔ frps」之间的**回环明文 HTTP** 端口(上游默认 7080) | 在面板填写**你真实的那个值**。插件不会替你假定 7080:端口填错会让「明文 vhost 公网可达」的安全闸门失效 |
14
+ | **`proxyBindAddr`** | frps 的全局代理监听地址,同时影响 HTTP vhost 和 TCP 代理 | A 档的明文 HTTP vhost 必须只监听回环;B 档的 TCP `remotePort` 要能从公网访问。若既有 frps 设置为 `127.0.0.1`,仅放行防火墙也不会使 B 档公网可达;不要为 B 档盲目改为 `0.0.0.0`,以免暴露同一 frps 上已有的明文 vhost。需先核对整个 frps 的服务与防火墙,必要时改用另一实例或 A 档。 |
15
+ | **入口证书档 `entryTls`** | 谁终止公网 TLS | 见下两档,按需二选一 |
16
+
17
+ [FRP 官方服务端配置](https://gofrp.org/zh-cn/docs/reference/server-configures/)将 `proxyBindAddr` 定义为代理监听地址,将 `vhostHTTPPort` 定义为 HTTP 类型代理监听端口;两者都由你既有 frps 的实际配置决定。
18
+
19
+ ## 两档入口证书
20
+
21
+ ### A. 公开 CA 证书档(`public-ip-cert`,缺省)
22
+
23
+ - 公网入口:`443`,由 **VPS 上的 Caddy** 终止 TLS,它再反代到 `127.0.0.1:<vhostHTTPPort>`。
24
+ - 证书:域名入口由 Caddy 自动申请与续期;公网 IPv4 入口使用 Certbot 5.8.0 的 `--standalone --preferred-profile shortlived --ip-address` 签发 [Let's Encrypt 的约 6 天 IP 证书](https://letsencrypt.org/2026/03/11/shorter-certs-certbot)。此 attach 流程**不会**安装续期定时器或部署 hook;你须在到期前续签、将新证书安装到 Caddy 并重载它。
25
+ - 受信任的公开证书可供手机浏览器与支持自定义远程 Origin 的 Android App(0.3.3 及更新)使用;仍须完成设备配对。
26
+ - VPS 侧要做:手动新增 Caddy 片段(`/etc/caddy/dsh-mobile-dsh.caddy`)和 Caddyfile 顶部一行 `import`;公网 IPv4 还需安排证书初次签发与持续续期。当前清单的 Certbot `--standalone` 命令会短暂停止 Caddy 以占用 80 端口,可能中断同机原有网站;请安排维护窗口。若不能接受中断,优先选域名公开证书档,或使用 0.4.6 App 的自签 TCP 档。
27
+
28
+ ### B. 自签穿透档(`self-signed`)——**不需要公开证书**
29
+
30
+ - 公网入口:`publicPort`(缺省 **33080**,可改;不得为 3080/3443/3444)。frps 只做**纯 TCP 透传,不解密**。
31
+ - TLS 由**运行 DSH 的电脑上的网关终止**,当前仅使用公网 IPv4 入口。插件自签 CA 有效期 5 年,公网 IPv4 叶证书有效期 397 天;叶证书可在保留同一 CA 时续签,CA 到期会阻止连接,不能静默换成新 CA。应留意面板证书状态,CA 到期或更换后须重新配对。
32
+ - 此档需要 **0.4.6 Android App**:二维码、链接和 App 配对密钥以 `dsh2` 标记必须固定 CA;App 会从网关的 `GET /mobile-access/ca.cer` 读取 CA,并核对密钥中的指纹,若接口返回 404 也不会降级为系统信任。旧版 App 不支持此档,仍可使用 A 档。
33
+ - 手机**浏览器**访问会提示证书不受信任(这是自签的必然结果),请用 App 扫码配对使用。
34
+ - VPS 不需要为此入口配置 Caddy、certbot 或新的 HTTP vhost;但既有 frps 的代理监听地址必须允许该 TCP 入口公网访问,且不得因此暴露其他明文服务。
35
+
36
+ > 拓扑:`手机 ──HTTPS(:33080)──▶ frps TCP 代理(透传)──加密隧道──▶ 本机 frpc ──▶ 127.0.0.1:<网关HTTPS端口> ──▶ dsh web 127.0.0.1:3080`
37
+
38
+ ## 公开 CA 证书档:操作与验收
39
+
40
+ 1. 在面板选「接入我已有的 frps」和「公开 CA 证书」,填入真实公网域名或公网 IPv4、frps 控制端口、共享 Token,以及既有 frps 的实际 `vhostHTTPPort`。公网入口使用 HTTPS 443,不填写自签 TCP 入口端口。
41
+ 2. 复制接入清单,先核对 frps 的明文 vhost 只在回环监听,再按清单手动添加 Caddy 片段与 `import`。域名证书由 Caddy 管理;公网 IPv4 请在维护窗口运行清单固定的 Certbot 5.8.0 `--standalone` 命令,它会短暂停止 Caddy。此 attach 流程只查询现有 `certbot.timer`,不代你配置续期、证书复制或重载 hook;须自行保持这些步骤运行。插件不会代你修改 VPS。
42
+ 3. 在电脑端按需安装官方 `frpc`,保存并验证连接。在独立外部网络打开公开地址的 `/mobile-access/discovery`,确认可信证书、HTTP 200 与当前电脑的安装标识;然后从 App 的「远程访问」或手机浏览器完成配对。仅在电脑端的自检通过不代表外网可达。
43
+
44
+ ## 操作步骤(自签穿透档,共 7 步)
45
+
46
+ ### 本机(3 步)
47
+
48
+ 1. 面板「自建 FRP → 步骤 1」填写:VPS 地址、frps 端口(你 frps 的 `bindPort`)、Token、公网入口
49
+ `https://<公网IP>`;**置备方式 = 接入我已有的 frps**;**入口证书档 = 自签穿透**;公网入口端口 = `33080`(或你选的端口)。
50
+ 2. 「复制接入清单」,核对其中脱敏的 `frpc.toml` 和 VPS 待办;复制本身不会保存配置。完成下一步并点击「保存并验证连接」后,插件才将实际配置写入私有目录 `…/remote/frp/config/frpc.toml`(Unix 为 `0600`,Windows 使用受限 ACL)。若重新输入 Token 并明确复制含 Token 的本机配置,请及时清除剪贴板。
51
+ 3. 装官方 frpc(面板「步骤 3」)→ 点「保存并验证连接」。启动前插件会自检 `frpc verify -c <配置>`。
52
+
53
+ ### VPS(2 步)
54
+
55
+ 4. 在既有 frps 控制端口已经可从电脑连接的前提下,为**新增 TCP 入口**放行公网端口:
56
+ ```sh
57
+ ufw allow 33080/tcp
58
+ ufw status | grep 33080
59
+ ```
60
+ 若用 firewalld 或云厂商安全组,放行同样的 **TCP 33080**。
61
+ 5. 确认既有 frps 已就绪,并核对 TCP 入口实际监听地址(插件不会改它的配置;若不是 systemd 服务,使用你自己的管理方式检查进程):
62
+ ```sh
63
+ systemctl is-active frps || true
64
+ ss -lnt | grep -E ':(<你的 bindPort>|33080)\b' || true
65
+ ```
66
+
67
+ ### 手机(2 步)
68
+
69
+ 6. 在电脑面板「远程访问」生成**远程配对二维码 / 配对链接**(含 CA 指纹、一次性 Token 和必须固定 CA 的 `dsh2` 标记)。
70
+ 7. 用 Android App 扫码(或粘贴链接)完成配对,即可远程进入 DSH。
71
+
72
+ ### 端到端验证(在 VPS 或任意外部机器执行)
73
+
74
+ ```sh
75
+ # 先把 YOUR_PUBLIC_IPV4 与 33080 换成实际公网入口,再从外部网络执行。
76
+ curl -k -sS -o /dev/null -w '%{http_code}\n' https://YOUR_PUBLIC_IPV4:33080/mobile-access/discovery
77
+ # 期望输出 200;-k 仅跳过 curl 的证书校验,用于检查连通性,不证明入口身份。
78
+ ```
79
+
80
+ 面板「步骤 2 → 运行自检」还会显示:入口证书剩余天数、CA 指纹、frps 控制端口可达性、公网入口可达性。
81
+ **本机探测 ≠ 公网验证**:请在外部再跑一次上面的 curl(手机流量即可),然后让 0.4.6 App 校验配对密钥里的 CA 指纹并实际建立会话。
82
+
83
+ ## 常见问题
84
+
85
+ | 现象 | 原因与处理 |
86
+ |---|---|
87
+ | `frp_attach_mode_requires_vhost_port` | 选了公开 CA 证书档却没填你 frps 的真实 `vhostHTTPPort`。填上真实值即可(插件不会替你假定 7080) |
88
+ | `frp_vhost_publicly_reachable` | 公开 CA 档的明文 vhost 端口从公网可达。限制其监听或改用独立 frps 实例;若同一实例还要提供自签档公网 TCP 入口,不要盲目把全局 `proxyBindAddr` 改为回环或公网地址。 |
89
+ | `frp_attach_cert_unknown` | 自签入口证书缺失或不可读;检查面板证书状态与私有目录的 `remote/ingress/`,不要绕过指纹验证。 |
90
+ | `frp_ingress_ca_expired` | 自签 CA 已过期,插件不会静默替换受信任 CA;重新生成 CA 后,手机须核对新指纹并重新配对。 |
91
+ | 手机浏览器提示证书不受信任 | 自签档的正常现象;请在 App 内配对使用 |
92
+ | 自检显示「公网入口不可达」 | 端口未放行、frpc 未启动,或 frps 未把该 `remotePort` 转发出来 |
93
+ | App 报「连接到另一台 DSH」 | 公网入口指向了别的 DSH:检查 `remotePort` 与 frpc 是否在本机运行 |
94
+
95
+ ## 与其他通道的关系
96
+
97
+ - **`origin` 自有反代通道**(默认 3444)是另一条独立路径,TLS 由**你自己的外部反代**终止,与本档互不影响。
98
+ - **deploy 模式**(由插件安装 frps + Caddy)保持不变,仍需要 SSH;attach 模式**零 SSH**。