@mrrisega/dsh-remote 0.6.6 → 0.6.7-beta.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 +30 -8
- package/clients/dsh-remote/dsh-bridge.mjs +1 -1
- package/clients/dsh-remote/e2ee-shim.mjs +4 -4
- package/clients/dsh-remote/mobile-adapter.mjs +1 -1
- package/clients/dsh-remote/test/e2ee-shim.test.mjs +4 -4
- package/docs/self-hosting.md +27 -9
- package/dsh-setup.mjs +593 -41
- package/package.json +3 -3
- package/packages/dsh-remote-web/lib/client.js +57 -6
- package/packages/dsh-remote-web/lib/index.js +578 -70
- package/packages/dsh-remote-web/package.json +1 -1
- package/packages/dsh-remote-web/test/community-qr-ui.test.mjs +1 -1
- package/packages/dsh-remote-web/test/connect-loop.test.mjs +1 -1
- package/packages/dsh-remote-web/test/connect-ui.test.mjs +3 -3
- package/packages/dsh-remote-web/test/installer-cli.test.mjs +82 -0
- package/packages/dsh-remote-web/test/plugin-link.test.mjs +400 -0
- package/packages/dsh-remote-web/test/plugin-manifest.test.mjs +3 -1
- package/packages/dsh-remote-web/test/quota-absent.test.mjs +94 -0
- package/packages/dsh-remote-web/test/settings-entry.test.mjs +1 -1
- package/packages/dsh-remote-web/test/telemetry.test.mjs +2 -2
- package/packages/dsh-remote-web/test/windows-compat.test.mjs +466 -0
package/README.md
CHANGED
|
@@ -23,11 +23,33 @@ dsh-remote 是一个轻量的**隧道模式**远程控制方案:电脑端运
|
|
|
23
23
|
│
|
|
24
24
|
nginx (HTTPS)
|
|
25
25
|
├─ /app/ → 静态 PWA (native.html)
|
|
26
|
-
├─ /_devices /
|
|
26
|
+
├─ /_devices /_login /remote/ /_bridge / → relay-router
|
|
27
27
|
└─ /_bridge → relay-router (WebSocket)
|
|
28
28
|
└→ bridge (电脑端) → 127.0.0.1:3080 (dsh web)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
## 0.6.7 速览(Windows 可用版)
|
|
32
|
+
|
|
33
|
+
0.6.6 及更早的插件半整套「服务状态 / 启停 / 重启」是按 macOS/Linux 写死的
|
|
34
|
+
(launchctl / systemd / pgrep / ps / /bin/sh),**在 Windows 上安装一切正常、运行期必死**:
|
|
35
|
+
面板红字「读取状态失败: process.getuid is not a function」、状态永远停在「查询中…」、
|
|
36
|
+
运行环境补不上、点「重启 DeepSeek harness」还会把 dsh web 打挂。本轮全部修掉:
|
|
37
|
+
|
|
38
|
+
- **Windows 不再 500**:`process.getuid` 在 Windows 上根本不存在(不是返回 undefined),
|
|
39
|
+
旧的 `launchTarget()` 无条件调它 → 所有读状态接口抛错。现在非 macOS 直接短路 launchd 查询。
|
|
40
|
+
- **Windows 能补上运行环境**:`spawn("npx.cmd")` 在 Node ≥20.12 起不带 shell 会抛 EINVAL。
|
|
41
|
+
现在用**当前 node 直接跑 npm 自带的 `npx-cli.js`**(不经 cmd.exe),退回 `.cmd` 时必带 `shell: true`。
|
|
42
|
+
- **「重启 DeepSeek harness」不再打挂进程**:Windows 改用 node 跑的 `.mjs` 助手(不再 `/bin/sh`),
|
|
43
|
+
且**先校验 node/入口/工作目录存在再动旧进程**,spawn 真正成功才回报成功。
|
|
44
|
+
- **Windows 进程发现**:安装器落 `.dsh-watcher.pid` / `.dsh-bridge.pid`,插件按 pid 判活
|
|
45
|
+
(不再依赖 Windows 上不存在的 `pgrep`/`ps`),必要时用 PowerShell 扫 node 进程兜底。
|
|
46
|
+
- **Windows 自启动**:安装器在**任务计划程序**注册登录任务 `dsh-remote-bridge`,卸载时一并删除;
|
|
47
|
+
插件自愈也会隐藏地把 bridge 拉起来(日常使用看不到控制台窗口)。
|
|
48
|
+
- **面板不再无限转圈**:`bridge-status` 轮询连续失败 3 次会把原因摆到连接卡上(原先静默 catch)。
|
|
49
|
+
|
|
50
|
+
> 平台支持:**macOS / Linux / Windows** 均可运行。自启动方式按平台分别是
|
|
51
|
+
> launchd、systemd --user、任务计划程序(Windows)。macOS/Linux 的行为与 0.6.6 完全一致。
|
|
52
|
+
|
|
31
53
|
## 0.6.4 速览(首次安装不再卡)
|
|
32
54
|
|
|
33
55
|
- **登录后 bridge 自动连上,全程零刷新**:面板自动补运行环境 → 拉起 bridge → 显示「正在连接中继…」,连上后自动变成「已连接 ✅」并刷新二维码与设备列表;失败有可读原因、自动重试倒计时和「复制诊断信息」。
|
|
@@ -48,7 +70,7 @@ dsh-remote 是一个轻量的**隧道模式**远程控制方案:电脑端运
|
|
|
48
70
|
**0.6.1 起已有**
|
|
49
71
|
|
|
50
72
|
- **首次安装体验修复**:装完插件立即登录,二维码与已授权设备列表不再报「尚未登录」红字;中继未就绪时面板自动退避重试,不用再手动刷新页面。
|
|
51
|
-
- **企微交流群**:README
|
|
73
|
+
- **企微交流群**:README 底部扫码入群;服务端可在管理后台「推广」页配置交流群二维码,设置面板「加入交流群」按钮与用户反馈页会同步展示。
|
|
52
74
|
|
|
53
75
|
**0.6.0 起已有**
|
|
54
76
|
|
|
@@ -74,7 +96,6 @@ dsh-remote 是一个轻量的**隧道模式**远程控制方案:电脑端运
|
|
|
74
96
|
| 服务器 | 你自己部署(任意有公网 IP 的机器) | 由服务商托管 |
|
|
75
97
|
| 账号体系 | 无需账号:访问密钥认证(`/_login` 换本地 JWT) | 手机号 + 短信验证码 |
|
|
76
98
|
| 后台管理 | 无(密钥即实例管理员) | 超管后台(用户/套餐/审计) |
|
|
77
|
-
| 流量配额 | 可选(环境变量覆盖默认值) | 按套餐分层限速限流 |
|
|
78
99
|
| 许可证 | 本仓库(见下文 License) | 商业授权,闭源 |
|
|
79
100
|
|
|
80
101
|
两者可随时切换:电脑端插件面板「连接模式」一键切换,互不影响。
|
|
@@ -85,8 +106,9 @@ dsh-remote 是一个轻量的**隧道模式**远程控制方案:电脑端运
|
|
|
85
106
|
|
|
86
107
|
## 安装
|
|
87
108
|
|
|
88
|
-
需要 Node.js ≥ 20
|
|
89
|
-
|
|
109
|
+
需要 Node.js ≥ 20(macOS / Linux / Windows 均可)。电脑端**一条命令**完成安装:
|
|
110
|
+
自动安装 bridge 与 dsh web 插件、写入配置、创建开机自启(macOS launchd / Linux systemd --user /
|
|
111
|
+
Windows 任务计划程序):
|
|
90
112
|
|
|
91
113
|
```bash
|
|
92
114
|
npx @mrrisega/dsh-remote
|
|
@@ -138,8 +160,8 @@ npx @mrrisega/dsh-remote setup --server wss://<你的域名>:端口 --key <访
|
|
|
138
160
|
**边界与建议(如实告知)**
|
|
139
161
|
|
|
140
162
|
- E2EE 保护的是**内容**:HTTPS 下的静态页面壳与路由元数据(页面骨架、路径、大小、时间、
|
|
141
|
-
|
|
142
|
-
- E2EE
|
|
163
|
+
是否加密)仍对中继可见;既有登录与审计不受影响。
|
|
164
|
+
- E2EE 为**分阶段开启的功能**:以服务端开关逐步放量(默认关闭 = 走 HTTPS 明文回退),面板会显示
|
|
143
165
|
当前加密状态与原因(已启用 / 等待服务端开启 / 普通安全连接等),不会静默降级。
|
|
144
166
|
- 建议上线前用**真机回归**一次完整链路:手机登录解锁 → 🔒 加密访问(对话/工具/审批/凭据)→
|
|
145
167
|
明文回退提示 → 修改密码后旧会话全部失效、重新登录恢复。
|
|
@@ -217,7 +239,7 @@ export DSH_REMOTE_TELEMETRY=0 # 完全关闭匿名装机统计:不生成
|
|
|
217
239
|
|
|
218
240
|
| 组件 | 位置 | 说明 |
|
|
219
241
|
|---|---|---|
|
|
220
|
-
| relay-router | `packages/relay-router/` | 中继服务器:bridge 注册表、实时设备列表、HTTP/WS
|
|
242
|
+
| relay-router | `packages/relay-router/` | 中继服务器:bridge 注册表、实时设备列表、HTTP/WS 透明代理 |
|
|
221
243
|
| bridge | `clients/dsh-remote/` | 电脑端守护进程:连 router 注册,把转发帧代理到本地 `dsh web`;心跳自愈 |
|
|
222
244
|
| PWA | `clients/dsh-web/native.html` | 手机端:登录 / 注册 / 设备选择(单文件,零构建) |
|
|
223
245
|
| dsh web 插件 | `packages/dsh-remote-web/` | 设置页「远程访问」面板:连接模式 / 账号 / bridge 启停 / 反馈 |
|
|
@@ -992,7 +992,7 @@ async function registerDeviceInAccount(token) {
|
|
|
992
992
|
device_name: os.hostname() || "dsh-bridge",
|
|
993
993
|
pub_key: pubKey,
|
|
994
994
|
machine_fp: MACHINE_FP, // v6 同机识别:服务端据此自动顶替旧设备(重装不再被设备数卡死)
|
|
995
|
-
// 安装口径(
|
|
995
|
+
// 安装口径(服务端存到设备行,用于区分「市场安装/自愈补装」与「安装器 npx」):
|
|
996
996
|
// install_source: npx(安装器注入) | plugin_market(插件拉起/自愈,默认) | unknown(白名单外)
|
|
997
997
|
install_source: INSTALL_SOURCE,
|
|
998
998
|
install_version: INSTALL_VERSION,
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
*
|
|
22
22
|
* 注入开关:
|
|
23
23
|
* - DSH_E2EE_SHIM=0 关闭注入(默认开启);
|
|
24
|
-
* -
|
|
25
|
-
* (
|
|
24
|
+
* - 开关:bridge 端 e2ee.enabled 为 false 时 dsh-bridge 不调用本模块 → 零注入、零行为
|
|
25
|
+
* (默认关 = 本 shim 默认不出现,镜像页保持既有明文路径)。
|
|
26
26
|
* - 「桌面宽屏/明文零行为」:未交接时数据面不拦截;桌面直连 dsh web 不经 bridge → 无注入。
|
|
27
27
|
*
|
|
28
28
|
* 测试:本模块的纯函数契约 + e2ee-shim-script.js 抽取的
|
|
@@ -108,7 +108,7 @@ export function shimScriptText() {
|
|
|
108
108
|
return cachedScript;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/** 环境开关:DSH_E2EE_SHIM=0 关闭注入(默认开启;叠加桥端 e2ee.enabled
|
|
111
|
+
/** 环境开关:DSH_E2EE_SHIM=0 关闭注入(默认开启;叠加桥端 e2ee.enabled 开关)。 */
|
|
112
112
|
export function e2eeShimEnabled() {
|
|
113
113
|
return process.env.DSH_E2EE_SHIM !== "0";
|
|
114
114
|
}
|
|
@@ -134,7 +134,7 @@ export function injectE2eeShim(html) {
|
|
|
134
134
|
/**
|
|
135
135
|
* 判断上游响应是否应注入 shim(与 mobile-adapter.shouldInjectHtml 同 gate:
|
|
136
136
|
* 仅官方 dsh web 的 text/html,含 </head>、无替换字符)。e2ee.enabled 由调用方(dsh-bridge)
|
|
137
|
-
* 决定,本函数只做类型/特征门 ——
|
|
137
|
+
* 决定,本函数只做类型/特征门 —— 「开关关 = 不调用 = 零注入」。
|
|
138
138
|
*/
|
|
139
139
|
export function shouldInjectE2eeShim({ contentType, html }) {
|
|
140
140
|
const ct = String(contentType || "");
|
|
@@ -328,11 +328,11 @@ test("fetch 包装:明文响应(无信封标记)→ 原样透传;篡改响应
|
|
|
328
328
|
const peer = bridgePeer(seS.sessId, seS.shk, seS.saltH);
|
|
329
329
|
const ORIGIN = "http://relay.local";
|
|
330
330
|
|
|
331
|
-
// 1) 明文响应(如 router
|
|
332
|
-
const plainTransport = async () => new Response('{"error":"
|
|
331
|
+
// 1) 明文响应(如 router 的错误页/JSON 错误体)
|
|
332
|
+
const plainTransport = async () => new Response('{"error":"upstream_error"}', { status: 502, headers: { "content-type": "application/json" } });
|
|
333
333
|
const r1 = await shim.seFetchWrapper(plainTransport, { sess: seS, origin: ORIGIN }, ORIGIN + "/api/x", { method: "GET" });
|
|
334
|
-
assert.equal(r1.status,
|
|
335
|
-
assert.equal(await r1.text(), '{"error":"
|
|
334
|
+
assert.equal(r1.status, 502);
|
|
335
|
+
assert.equal(await r1.text(), '{"error":"upstream_error"}');
|
|
336
336
|
|
|
337
337
|
// 2) 篡改密文 → 502 + x-dsh-e2ee-error + ⚠(绝不静默/绝不误转发)
|
|
338
338
|
const tamperTransport = async (_input, init) => {
|
package/docs/self-hosting.md
CHANGED
|
@@ -12,13 +12,12 @@ The self-hosted stack:
|
|
|
12
12
|
│
|
|
13
13
|
你的 nginx (HTTPS)
|
|
14
14
|
├─ /app/ → 静态 PWA (clients/dsh-web/native.html)
|
|
15
|
-
├─ /_devices /
|
|
15
|
+
├─ /_devices /_login /remote/ /_bridge / → relay-router
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
- **relay-router** is the only server component. It registers bridges over
|
|
19
|
-
WebSocket (`/_bridge`), serves the real-time device list (`/_devices`),
|
|
20
|
-
HTTP/WS traffic to the right device (`/remote/<deviceId>/…`)
|
|
21
|
-
per-plan bandwidth/traffic quotas.
|
|
19
|
+
WebSocket (`/_bridge`), serves the real-time device list (`/_devices`), and
|
|
20
|
+
proxies HTTP/WS traffic to the right device (`/remote/<deviceId>/…`).
|
|
22
21
|
- Authentication: `DSH_LOCAL_ACCESS_KEYS` (comma-separated) → `POST /_login`
|
|
23
22
|
→ 2h local JWT. The router also accepts SaaS JWTs if
|
|
24
23
|
`DSH_ENTERPRISE_JWT_SECRET` is set, so a router can serve both modes at once.
|
|
@@ -79,9 +78,10 @@ lists online devices from `/_devices` and routes into `dsh web` through the tunn
|
|
|
79
78
|
npx @mrrisega/dsh-remote setup --server wss://你的域名:端口 --key 你的访问密钥
|
|
80
79
|
```
|
|
81
80
|
|
|
82
|
-
This writes the local config, verifies the key against `/_login`, and installs
|
|
83
|
-
|
|
84
|
-
`dsh
|
|
81
|
+
This writes the local config, verifies the key against `/_login`, and installs an
|
|
82
|
+
autostart entry that keeps the bridge alive — launchd on macOS, `systemd --user`
|
|
83
|
+
on Linux, and a logon task in **Task Scheduler** (`dsh-remote-bridge`) on Windows.
|
|
84
|
+
The bridge auto-starts when `dsh web` (127.0.0.1:3080) is up.
|
|
85
85
|
|
|
86
86
|
## Configuration reference (open.env)
|
|
87
87
|
|
|
@@ -91,8 +91,26 @@ launchd/systemd service that keeps the bridge alive. The bridge auto-starts when
|
|
|
91
91
|
| `DSH_LOCAL_JWT_SECRET` | JWT secret for local (self-hosted) auth |
|
|
92
92
|
| `DSH_LOCAL_ACCESS_KEYS` | Comma-separated access keys for `/_login` |
|
|
93
93
|
| `DSH_ROUTER_PORT` | Listen port (default 13444) |
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
94
|
+
| `DSH_LOG_DIR` | **Where logs are written to disk.** Unset = stdout only. Set it in production — see below. |
|
|
95
|
+
| `DSH_LOG_MAX_MB` | Max size of a single log file before rotating (default `10`) |
|
|
96
|
+
| `DSH_LOG_KEEP` | Number of rotated files to keep (default `5`) |
|
|
97
|
+
| `DSH_LOG_LEVEL` | `debug` \| `info` \| `warn` \| `error` (default `info`) |
|
|
98
|
+
|
|
99
|
+
## Logs (must be persisted)
|
|
100
|
+
|
|
101
|
+
Logs are **not** allowed to live only in a container's stdout or in memory: a
|
|
102
|
+
recreated container or a restarted process would lose them, and audit trails are
|
|
103
|
+
exactly what you need *after* something went wrong. So the router writes to disk
|
|
104
|
+
itself and rotates by size — no host `logrotate` required.
|
|
105
|
+
|
|
106
|
+
- Set `DSH_LOG_DIR` (e.g. `./logs/router`); the directory is created on demand.
|
|
107
|
+
- Rotated files are `router.log`, `router.log.1`, `router.log.2`, … bounded by
|
|
108
|
+
`DSH_LOG_MAX_MB` × `DSH_LOG_KEEP` (default ≈ 60 MB per service).
|
|
109
|
+
- Docker deployment already mounts this directory as a volume
|
|
110
|
+
(`./logs/router:/var/log/dsh-remote`) **and** caps the container's own
|
|
111
|
+
json-file log driver — see `docker-compose.yml`.
|
|
112
|
+
- If the directory is unwritable (full disk, bad permissions) the logger degrades
|
|
113
|
+
to stdout-only and reports it **once**; it never takes the service down.
|
|
96
114
|
|
|
97
115
|
## Security notes
|
|
98
116
|
|