dsh-remote-agent 0.9.1 → 0.9.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/CHANGELOG.md +102 -0
- package/README.en.md +10 -3
- package/README.md +37 -10
- package/cordis.patch.yml +31 -24
- package/lib/client.js +9 -2
- package/lib/env-check.js +24 -5
- package/lib/hub/index.js +5 -2
- package/package.json +1 -1
- package/scripts/check-doc-drift.mjs +46 -15
- package/scripts/diag-worker.ps1 +148 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,108 @@
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## [0.9.2] - 2026-10-18
|
|
13
|
+
|
|
14
|
+
> **紧急修复:0.9.0/0.9.1 会让新版 DSH 启动崩溃。**
|
|
15
|
+
> 如果你装了 0.9.x 并升级过 DSH(0.2.1-rc.2 及以后),**先升级到本版**。
|
|
16
|
+
|
|
17
|
+
### 🔴 严重事故:包内 patch 绑 `host: '0.0.0.0'` 导致 DSH 启动崩溃
|
|
18
|
+
|
|
19
|
+
**这是我的错。** 0.9.0 为了让"副机连得上",在包内 patch 里加了:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
- id: webserver
|
|
23
|
+
config:
|
|
24
|
+
host: '0.0.0.0'
|
|
25
|
+
port: 19387
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
新版 `@deepseek-ai/dsh-host-webserver` 在 **schema 校验阶段**就硬拒绝通配地址:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
webserver: host "0.0.0.0" is an unspecified (wildcard) address, which is not
|
|
32
|
+
supported: binding every interface would expose remote code execution to the
|
|
33
|
+
network; bind one concrete IPv4 or IPv6 address of a local interface instead
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**后果链**:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
webserver(required)激活失败
|
|
40
|
+
→ webServer 服务缺失
|
|
41
|
+
→ 至少 6 个插件排队等它
|
|
42
|
+
(web-runtime / open-in-app / directory-picker / client-hmr / remote-agent …)
|
|
43
|
+
→ host 启动失败
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
实测在用户机器上产生 **10 份 crash 日志**(`crash-*.log`)。
|
|
47
|
+
|
|
48
|
+
**这比它想修的"副机连不上"严重得多:连 DSH 都起不来了。**
|
|
49
|
+
|
|
50
|
+
### 修复
|
|
51
|
+
|
|
52
|
+
**包内 patch 不再碰 `webserver` 的任何字段。** 理由:
|
|
53
|
+
|
|
54
|
+
1. `host` 必须是**具体某个本机接口地址**(Tailscale IP / 局域网 IP),
|
|
55
|
+
而这个值因人而异 —— 包里写死任何具体 IP 都是错的。
|
|
56
|
+
2. 插件在 `apply()` 里**也不能**自己读网卡再拼配置:loader 的 patch 是
|
|
57
|
+
启动前静态合并的,改不了自己所在那一行的 config。
|
|
58
|
+
3. 实际做法:**由用户在 profile 里配一次**,插件负责**检测并说清该怎么配**。
|
|
59
|
+
|
|
60
|
+
配套修正(四处面向用户的文案,它们此前都在建议 `0.0.0.0`):
|
|
61
|
+
|
|
62
|
+
- `lib/env-check.js` 的 reachability 修法 → 改为"具体的本机 IP",并明确警告
|
|
63
|
+
不能用 0.0.0.0,教会用户 `tailscale ip -4` / `ipconfig` 查地址
|
|
64
|
+
- `lib/client.js` 的红色横幅 → 同上
|
|
65
|
+
- `lib/hub/index.js` 的可达性自检 hint 与 log fix → 同上
|
|
66
|
+
- `README.md` / `README.en.md` 的安装段 → 重写,把"必须绑具体 IP"讲清楚
|
|
67
|
+
|
|
68
|
+
### 断言方向反转(79 / 新增 79b)
|
|
69
|
+
|
|
70
|
+
**原来的断言 79 是错的** —— 它要求包内 patch **绑** `0.0.0.0`。现在反过来:
|
|
71
|
+
|
|
72
|
+
- **79**:包内 patch 的**生效行**不得出现 `- id: webserver` 或
|
|
73
|
+
`host: '0.0.0.0'`,且必须把事故写进注释留档
|
|
74
|
+
- **79b**:所有面向用户的修法文案都不得建议 `host: 0.0.0.0`,
|
|
75
|
+
且必须包含"不能用 0.0.0.0"的明确警告
|
|
76
|
+
|
|
77
|
+
79 只扫**非注释行** —— 注释里必须保留事故原文(含 `0.0.0.0` 示例),
|
|
78
|
+
那是给后来者的警示,不该被判违规。
|
|
79
|
+
|
|
80
|
+
### 现在"副机连不上"怎么办
|
|
81
|
+
|
|
82
|
+
包内 patch 不再代你配 `webserver`,所以**新装用户要自己填一次 host**。
|
|
83
|
+
但这件事不再是静默失败:
|
|
84
|
+
|
|
85
|
+
- 面板加载时**自动体检**,绑 loopback 会显示红色横幅
|
|
86
|
+
- 「环境体检」里的 reachability 项会告诉你**当前绑在哪**、**该改成什么**
|
|
87
|
+
- 提示里直接教怎么查自己的 IP
|
|
88
|
+
|
|
89
|
+
**填什么**:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
- id: webserver
|
|
93
|
+
config:
|
|
94
|
+
host: '100.x.x.x' # ← 换成你自己的地址(推荐 Tailscale IP)
|
|
95
|
+
port: 19387
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
查地址:`tailscale ip -4`(跨网,固定不变)或 `ipconfig`(同一局域网)。
|
|
99
|
+
|
|
100
|
+
### 其他
|
|
101
|
+
|
|
102
|
+
- 新增 `scripts/diag-worker.ps1` —— 副机诊断脚本(**只读**,不改任何文件),
|
|
103
|
+
输出 profile 列表、`remote-agent` 配置、插件版本、面板端点探测、
|
|
104
|
+
配置备份时间线。用于排查"每次重启都回到引导页"这类问题。
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
packaging 4 项
|
|
108
|
+
doc-drift 121/121
|
|
109
|
+
diag:inject 11 项
|
|
110
|
+
tests 949(948 pass / 0 fail / 1 skip)
|
|
111
|
+
lint 干净
|
|
112
|
+
```
|
|
113
|
+
|
|
12
114
|
## [0.9.1] - 2026-10-17
|
|
13
115
|
|
|
14
116
|
> **0.9.0 发布约 40 分钟后补发**。两个 High 都是真实功能失效
|
package/README.en.md
CHANGED
|
@@ -44,9 +44,16 @@ memory badges, task dispatch, task states, result summaries, workspace changes,
|
|
|
44
44
|
change diffs (unified diffs of text-file edits), final artifacts, log tail,
|
|
45
45
|
**environment health-check**, **workspace sharing (SMB)**, model dropdown.
|
|
46
46
|
|
|
47
|
-
> **Works out of the box**: restart DSH after install and
|
|
48
|
-
>
|
|
49
|
-
>
|
|
47
|
+
> **Works out of the box**: restart DSH after install and the bundled patch
|
|
48
|
+
> defaults `role: auto` (no `hubUrl` ⇒ hub), which takes effect automatically.
|
|
49
|
+
>
|
|
50
|
+
> **For workers to connect, the hub's `webserver` must bind one concrete local
|
|
51
|
+
> IP** (DSH binds `127.0.0.1` by default, which workers can never reach). You
|
|
52
|
+
> fill this in once — the panel's *environment health-check* tells you exactly
|
|
53
|
+
> what to put there.
|
|
54
|
+
>
|
|
55
|
+
> ⚠ **Never use `0.0.0.0`**: newer DSH rejects wildcard addresses and will
|
|
56
|
+
> **fail to start**. See *Install* below.
|
|
50
57
|
|
|
51
58
|
<p align="center">
|
|
52
59
|
<img src="docs/screenshots/panel-main.png" width="720" alt="Web panel: nodes, dispatch form, task list">
|
package/README.md
CHANGED
|
@@ -42,7 +42,13 @@
|
|
|
42
42
|
**工作区共享(SMB)**、模型下拉。
|
|
43
43
|
|
|
44
44
|
> **开箱即用**:装完重启 DSH 就能用。包内 patch 默认 `role: auto`(没配
|
|
45
|
-
> `hubUrl`
|
|
45
|
+
> `hubUrl` 就是主机),自动生效。
|
|
46
|
+
>
|
|
47
|
+
> **副机要能连上,主机的 `webserver` 必须绑一个具体的本机 IP**(DSH 默认只绑
|
|
48
|
+
> `127.0.0.1`,那样副机永远连不上)。这一步需要你填一次 —— 打开面板时
|
|
49
|
+
> 「环境体检」会检查并告诉你该填什么。
|
|
50
|
+
>
|
|
51
|
+
> ⚠ **不能用 `0.0.0.0`**:新版 DSH 会硬拒绝通配地址,导致 DSH **启动崩溃**。
|
|
46
52
|
> 详见下方「安装」。
|
|
47
53
|
|
|
48
54
|
<p align="center">
|
|
@@ -210,20 +216,41 @@ npm install -g dsh-remote-agent
|
|
|
210
216
|
|
|
211
217
|
或在 DSH 的插件对话框里填 `github:zbzbhs/dsh-remote-agent`。
|
|
212
218
|
|
|
213
|
-
**装完重启 DSH
|
|
219
|
+
**装完重启 DSH 就能用**:
|
|
214
220
|
|
|
215
221
|
- 包内 patch 默认 `role: auto`。**没配 `hubUrl` 就是主机**,自动生效。
|
|
216
|
-
- 包内 patch 把 `webserver` 绑到 `0.0.0.0:19387`,**副机才能连上**
|
|
217
|
-
(DSH 默认只绑 `127.0.0.1`,那样副机永远连不上且主机侧毫无异常)。
|
|
218
222
|
- 打开 Web 面板「远程同事」,点「副机接入」生成配对码,发给副机。
|
|
219
223
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
**唯一需要你填一次的是 `webserver` 的 host** —— 副机要能连上,主机必须绑一个
|
|
225
|
+
**具体的本机 IP**(DSH 默认只绑 `127.0.0.1`,那样副机永远连不上,而且主机侧
|
|
226
|
+
毫无异常)。在 profile 的 `cordis.patch.yml` 里:
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
- id: webserver
|
|
230
|
+
config:
|
|
231
|
+
host: '100.x.x.x' # ← 换成你自己的地址
|
|
232
|
+
port: 19387
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**怎么查自己的地址**:
|
|
236
|
+
|
|
237
|
+
- 跨网(推荐):`tailscale ip -4` —— Tailscale IP 固定不变、跨网可达
|
|
238
|
+
- 同一局域网:`ipconfig` 看 WLAN/以太网那块网卡的 IPv4
|
|
239
|
+
|
|
240
|
+
> ⚠ **绝对不能写 `0.0.0.0`**。新版 `@deepseek-ai/dsh-host-webserver` 在 schema
|
|
241
|
+
> 校验阶段就会硬拒绝通配地址:
|
|
242
|
+
>
|
|
243
|
+
> ```
|
|
244
|
+
> host "0.0.0.0" is an unspecified (wildcard) address, which is not supported:
|
|
245
|
+
> binding every interface would expose remote code execution to the network;
|
|
246
|
+
> bind one concrete IPv4 or IPv6 address of a local interface instead
|
|
247
|
+
> ```
|
|
248
|
+
>
|
|
249
|
+
> 后果是 **DSH 启动崩溃**(`webserver` 是 required 插件,它挂了 `webServer`
|
|
250
|
+
> 服务就缺失,至少 6 个插件排队等它,包括本插件)。
|
|
225
251
|
>
|
|
226
|
-
>
|
|
252
|
+
> 面板的「环境体检」会自动检测这一项,绑错了会明确告诉你当前绑在哪、
|
|
253
|
+
> 该改成什么 —— 面板加载时也会自动体检一次。
|
|
227
254
|
|
|
228
255
|
### 副机(worker)—— 贴个配对码就行
|
|
229
256
|
|
package/cordis.patch.yml
CHANGED
|
@@ -99,36 +99,43 @@
|
|
|
99
99
|
role: auto
|
|
100
100
|
|
|
101
101
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
102
|
-
#
|
|
102
|
+
# ⚠⚠ 绝对不要覆盖 webserver 的 host(2026-10-18 事故:我犯过这个错)
|
|
103
103
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
104
104
|
#
|
|
105
|
-
# ──
|
|
105
|
+
# ── 事故经过 ────────────────────────────────────────────────────────────
|
|
106
106
|
#
|
|
107
|
-
#
|
|
108
|
-
# 而那个 WebSocket upgrade 是注册在 DSH 的 `ctx.webServer` 上的
|
|
109
|
-
# (`ctx.webServer.registerUpgrade(...)`)—— **没有独立端口选项**。
|
|
107
|
+
# v0.9.0 为了让"副机连得上",我在包内 patch 里加了:
|
|
110
108
|
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
109
|
+
# - id: webserver
|
|
110
|
+
# config:
|
|
111
|
+
# host: '0.0.0.0'
|
|
112
|
+
# port: 19387
|
|
113
|
+
#
|
|
114
|
+
# **结果:DSH 直接启动崩溃。** 新版 `@deepseek-ai/dsh-host-webserver` 在
|
|
115
|
+
# schema 校验阶段就硬拒绝通配地址:
|
|
116
|
+
#
|
|
117
|
+
# webserver: host "0.0.0.0" is an unspecified (wildcard) address, which is
|
|
118
|
+
# not supported: binding every interface would expose remote code execution
|
|
119
|
+
# to the network; bind one concrete IPv4 or IPv6 address of a local
|
|
120
|
+
# interface instead
|
|
121
|
+
#
|
|
122
|
+
# 后果链:webserver(required)激活失败 → `webServer` 服务缺失 → 至少 6 个
|
|
123
|
+
# 插件排队等它(web-runtime / open-in-app / directory-picker / client-hmr /
|
|
124
|
+
# **remote-agent** …)→ host 启动失败。实测产生了 10 份 crash 日志。
|
|
113
125
|
#
|
|
114
|
-
#
|
|
115
|
-
# 毫无异常(healthz 200、面板能开、工具都在)。用户只能猜"地址错了?
|
|
116
|
-
# token 错了?防火墙?" —— 2026-10-17 实测踩到,代价是好几轮排查。
|
|
126
|
+
# 这个错误比它想修的"副机连不上"严重得多:**连 DSH 都起不来了**。
|
|
117
127
|
#
|
|
118
|
-
# ──
|
|
128
|
+
# ── 所以现在的做法 ──────────────────────────────────────────────────────
|
|
119
129
|
#
|
|
120
|
-
#
|
|
121
|
-
# 节点端点)。DSH 自带 Host/Origin 围栏 + 浏览器 cookie 鉴权,但暴露面确实
|
|
122
|
-
# 变大了。所以:
|
|
123
|
-
# · README 与面板都有明确提示
|
|
124
|
-
# · 环境体检把这条列出来
|
|
125
|
-
# · 用户可在 profile 里覆盖回 127.0.0.1(此时请改用 relay 模式,否则副机连不上)
|
|
130
|
+
# **本包不碰 webserver 的任何字段。** 理由:
|
|
126
131
|
#
|
|
127
|
-
#
|
|
132
|
+
# 1. host 必须是**具体某个本机接口地址**(如 Tailscale IP / 局域网 IP),
|
|
133
|
+
# 而这个值因人而异 —— 包里写死任何具体 IP 都是错的。
|
|
134
|
+
# 2. 插件在 `apply()` 里也**不能**自己读网卡再拼配置:loader 的 patch 是
|
|
135
|
+
# 启动前静态合并的,改不了自己所在那一行的 config。
|
|
136
|
+
# 3. 实际做法:**主机的 webserver 由用户/DSh 自己配**,我们只在
|
|
137
|
+
# 「环境体检」里检测"是否绑在可达地址",绑 loopback 就明确报错并给出
|
|
138
|
+
# 该填什么(含如何查自己的 Tailscale / 局域网 IP)。
|
|
128
139
|
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
- id: webserver
|
|
132
|
-
config:
|
|
133
|
-
host: '0.0.0.0'
|
|
134
|
-
port: 19387
|
|
140
|
+
# 这就是 0575c6b 的可达性自检 + c4459c5 的环境体检的价值 —— **诊断而不是
|
|
141
|
+
# 代改配置**。2026-10-17 我想"帮用户改好",结果改出了启动崩溃。
|
package/lib/client.js
CHANGED
|
@@ -1736,8 +1736,15 @@ window.__ModuleLoader__.load({
|
|
|
1736
1736
|
h('div', null, '现在监听在 ', h('code', null, reach.origin || ''),
|
|
1737
1737
|
',只有本机能访问。副机用任何地址都连不上。'),
|
|
1738
1738
|
h('div', { style: { marginTop: '6px' } }, '修法:在 profile 的 ',
|
|
1739
|
-
h('code', null, 'cordis.patch.yml'), '
|
|
1740
|
-
'
|
|
1739
|
+
h('code', null, 'cordis.patch.yml'), ' 里把 ', h('code', null, 'webserver'),
|
|
1740
|
+
' 的 ', h('code', null, 'host'), ' 改成一个具体的本机 IP,然后重启 DSH。'),
|
|
1741
|
+
// ⚠ 修法**不能写 0.0.0.0**(2026-10-18 事故):新版 dsh-host-webserver
|
|
1742
|
+
// 在 schema 校验阶段就拒绝通配地址,会让 **DSH 启动崩溃**
|
|
1743
|
+
// (webserver 是 required 插件)。必须绑一个具体的本机接口地址。
|
|
1744
|
+
h('div', { className: 'dra-dim', style: { marginTop: '2px' } },
|
|
1745
|
+
'⚠ 不能用 0.0.0.0 —— 新版 DSH 会拒绝并导致启动失败。'
|
|
1746
|
+
+ '查地址:Tailscale 用 tailscale ip -4,局域网用 ipconfig。'
|
|
1747
|
+
+ '推荐绑 Tailscale IP(固定不变、跨网可达)。'),
|
|
1741
1748
|
reach.hint
|
|
1742
1749
|
? h('div', { className: 'dra-dim', style: { marginTop: '4px' } }, reach.hint)
|
|
1743
1750
|
: null,
|
package/lib/env-check.js
CHANGED
|
@@ -206,11 +206,30 @@ export function envChecks({
|
|
|
206
206
|
// 修法给**可直接粘贴的片段**(v0.9.0 审查 L3:不假装有一键修复)。
|
|
207
207
|
// 这是用户 profile 的配置,插件客观上不能静默代改 ——
|
|
208
208
|
// 用户 profile 优先于包内 patch 是明确的设计。
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
//
|
|
210
|
+
// ⚠ **绝对不要建议 `0.0.0.0`**(2026-10-18 事故)。
|
|
211
|
+
//
|
|
212
|
+
// 新版 `dsh-host-webserver` 在 schema 校验阶段就硬拒绝通配地址:
|
|
213
|
+
// host "0.0.0.0" is an unspecified (wildcard) address, which is not
|
|
214
|
+
// supported: binding every interface would expose remote code
|
|
215
|
+
// execution to the network; bind one concrete IPv4 or IPv6 address
|
|
216
|
+
// of a local interface instead
|
|
217
|
+
//
|
|
218
|
+
// 后果是 **DSH 启动崩溃**(webserver 是 required 插件,它挂了
|
|
219
|
+
// `webServer` 服务就缺失,至少 6 个插件排队等它)。v0.9.0 曾把这个
|
|
220
|
+
// 建议写进包内 patch,实测产生 10 份 crash 日志。
|
|
221
|
+
//
|
|
222
|
+
// 正确做法:绑**一个具体的本机接口地址**。推荐 Tailscale IP ——
|
|
223
|
+
// 它固定不变、跨网可达,正好是本插件的使用场景。
|
|
224
|
+
fix: '把 profile 的 cordis.patch.yml 里 webserver 的 host 改成'
|
|
225
|
+
+ '**一个具体的本机 IP**(不能用 0.0.0.0 —— 新版 DSH 会拒绝并启动失败),'
|
|
226
|
+
+ '然后**重启 DSH**。\n'
|
|
227
|
+
+ ' 查地址:Tailscale 用 `tailscale ip -4`,局域网用 `ipconfig`。\n'
|
|
228
|
+
+ ' 推荐绑 Tailscale IP(固定不变、跨网可达):\n'
|
|
229
|
+
+ ' - id: webserver\n'
|
|
230
|
+
+ ' config:\n'
|
|
231
|
+
+ " host: '<你的具体 IP,例如 100.x.x.x>'\n"
|
|
232
|
+
+ ' port: 19387',
|
|
214
233
|
detail: loop
|
|
215
234
|
? `当前只监听 ${reachability.bound || '127.0.0.1'}(${reachability.origin || ''})—— 副机用任何地址都连不上`
|
|
216
235
|
: `当前监听 ${reachability.origin || ''}`,
|
package/lib/hub/index.js
CHANGED
|
@@ -305,14 +305,17 @@ export function applyHub(ctx, config = {}) {
|
|
|
305
305
|
origin,
|
|
306
306
|
loopback,
|
|
307
307
|
hint: loopback
|
|
308
|
-
|
|
308
|
+
// ⚠ 不能建议 0.0.0.0(2026-10-18 事故):新版 dsh-host-webserver 在
|
|
309
|
+
// schema 校验阶段就拒绝通配地址 → webserver 无法激活 → webServer
|
|
310
|
+
// 服务缺失 → DSH 启动崩溃。必须绑一个**具体的本机接口地址**。
|
|
311
|
+
? 'webServer 绑在 loopback,副机无法接入。在 profile 的 cordis.patch.yml 里把 webserver 的 host 改成一个具体的本机 IP(推荐 Tailscale IP:tailscale ip -4),然后重启 DSH。不能用 0.0.0.0 —— 新版 DSH 会拒绝并启动失败。'
|
|
309
312
|
: '',
|
|
310
313
|
}
|
|
311
314
|
if (loopback) {
|
|
312
315
|
log('warn', 'hub 绑在 loopback:副机将无法接入', {
|
|
313
316
|
origin,
|
|
314
317
|
bound: hostname,
|
|
315
|
-
fix: "
|
|
318
|
+
fix: "把 webserver 的 host 改成具体的本机 IP(如 Tailscale IP),不能用 0.0.0.0",
|
|
316
319
|
})
|
|
317
320
|
} else {
|
|
318
321
|
log('info', 'hub reachable from other hosts', { origin, bound: hostname })
|
package/package.json
CHANGED
|
@@ -929,37 +929,55 @@ check('65', 'eslint 严格档:lint script + 配置文件 + CI job 三件套齐
|
|
|
929
929
|
'lib/env-check.js(可达性/inject/roots/role + fix + 不代下载边界)')
|
|
930
930
|
}
|
|
931
931
|
|
|
932
|
-
/* ── 79~80. 开箱即用(2026-10-17
|
|
932
|
+
/* ── 79~80. 开箱即用(2026-10-17,2026-10-18 修订)──
|
|
933
933
|
*
|
|
934
934
|
* 产品标准:用户装完就该能用,不该读文档、改 YAML、猜字段。
|
|
935
935
|
*
|
|
936
|
-
* 此前三道门槛:
|
|
937
|
-
* 1. `role` 默认 '' → 两台机器都要操作一次
|
|
938
|
-
* 2. `webserver` 没绑 0.0.0.0 → 副机永远连不上(且完全静默)
|
|
939
|
-
* 3. 副机表单 4 个字段 → 小白不知道节点 ID / 工作区填什么
|
|
940
|
-
*
|
|
941
936
|
* 核心设计:`role` 默认 **auto**,按 `hubUrl` 推断。
|
|
942
937
|
* · 默认 hub → 副机装完以 hub 身份启动,注册无鉴权端点(**攻击面**)
|
|
943
938
|
* · 默认 '' → 两台都要操作,不是开箱即用
|
|
944
939
|
* · 默认 auto → 主机零操作、副机贴完配对码零操作,且不给副机开错端点
|
|
940
|
+
*
|
|
941
|
+
* ⚠ **2026-10-18 事故修订**:79 号断言原来**要求**包内 patch 绑
|
|
942
|
+
* `host: '0.0.0.0'` —— 那正是导致 **DSH 启动崩溃**的写法。新版
|
|
943
|
+
* `dsh-host-webserver` 在 schema 校验阶段就硬拒绝通配地址:
|
|
944
|
+
*
|
|
945
|
+
* host "0.0.0.0" is an unspecified (wildcard) address, which is not
|
|
946
|
+
* supported: binding every interface would expose remote code execution
|
|
947
|
+
* to the network; bind one concrete IPv4 or IPv6 address ...
|
|
948
|
+
*
|
|
949
|
+
* 后果:webserver(required)激活失败 → webServer 服务缺失 → 至少 6 个插件
|
|
950
|
+
* 排队等它 → host 启动失败(实测 10 份 crash 日志)。
|
|
951
|
+
*
|
|
952
|
+
* 所以断言方向**反过来**:现在要求包内 patch **不碰** webserver 的任何字段,
|
|
953
|
+
* 且所有提示文案都不得建议 0.0.0.0。
|
|
945
954
|
*/
|
|
946
955
|
{
|
|
947
956
|
const indexSrc = read('lib/index.js')
|
|
948
957
|
const patchSrc = read('cordis.patch.yml')
|
|
949
958
|
const clientSrc3 = read('lib/client.js')
|
|
950
959
|
const setupSrc = read('lib/setup-routes.js')
|
|
951
|
-
|
|
952
|
-
|
|
960
|
+
const envSrc = read('lib/env-check.js')
|
|
961
|
+
const hubSrc = read('lib/hub/index.js')
|
|
962
|
+
|
|
963
|
+
// ⚠ 只扫**非注释行**:注释里必须保留那次事故的原文(含 `0.0.0.0` 与
|
|
964
|
+
// `- id: webserver` 示例),那是给后来者的警示 —— 不该被断言判违规。
|
|
965
|
+
// 这一点和 L3 断言踩过的坑同源(扫全文会命中"正确留档的注释")。
|
|
966
|
+
const patchCode = patchSrc
|
|
967
|
+
.split('\n')
|
|
968
|
+
.filter((l) => !/^\s*#/.test(l))
|
|
969
|
+
.join('\n')
|
|
970
|
+
|
|
971
|
+
check('79', '开箱即用:role 默认 auto;且包内 patch **不得**绑 0.0.0.0(会让 DSH 崩溃)',
|
|
953
972
|
/\.default\('auto'\)/.test(indexSrc)
|
|
954
973
|
&& /ROLE\.AUTO/.test(indexSrc)
|
|
955
974
|
&& /config\.hubUrl/.test(indexSrc)
|
|
956
|
-
//
|
|
957
|
-
&&
|
|
958
|
-
&&
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
'lib/index.js(auto 推断)+ cordis.patch.yml(webserver 0.0.0.0:19387 + 安全披露)')
|
|
975
|
+
// ⚠ 关键:包内 patch 的**生效行**不得 override webserver 的 host
|
|
976
|
+
&& !/^- id: webserver$/m.test(patchCode)
|
|
977
|
+
&& !/host:\s*'0\.0\.0\.0'/.test(patchCode)
|
|
978
|
+
// 必须把这个坑写进注释(防止后来者又"帮用户改好")
|
|
979
|
+
&& /启动崩溃/.test(patchSrc),
|
|
980
|
+
'lib/index.js(auto 推断)+ cordis.patch.yml(生效行不碰 webserver + 事故留档)')
|
|
963
981
|
|
|
964
982
|
check('80', '开箱即用:副机表单简化 + 工作区自动创建 + 首次自动体检',
|
|
965
983
|
// 工作区预填
|
|
@@ -972,6 +990,19 @@ check('65', 'eslint 严格档:lint script + 配置文件 + CI job 三件套齐
|
|
|
972
990
|
&& /mkdirSync\(r, \{ recursive: true \}\)/.test(setupSrc)
|
|
973
991
|
&& /warnings: applyWarnings/.test(setupSrc),
|
|
974
992
|
'lib/client.js(预填/折叠/自动体检)+ lib/setup-routes.js(自动建目录)')
|
|
993
|
+
|
|
994
|
+
// ── 79b:**所有面向用户的修法文案**都不得建议 0.0.0.0 ──────────────
|
|
995
|
+
//
|
|
996
|
+
// 这是本次事故最有价值的一条守卫:代码自己不能犯那个错,提示文案也不能。
|
|
997
|
+
// 检查的是字符串字面量(`host: '0.0.0.0'` 这种形态),不是注释里的说明。
|
|
998
|
+
check('79b', '所有修法文案都不得建议 host 0.0.0.0(会让 DSH 启动崩溃)',
|
|
999
|
+
!/host:\s*'0\.0\.0\.0'/.test(clientSrc3)
|
|
1000
|
+
&& !/host:\s*'0\.0\.0\.0'/.test(envSrc)
|
|
1001
|
+
&& !/加 host:\s*'0\.0\.0\.0'/.test(hubSrc)
|
|
1002
|
+
// 但要**明确警告不能用**它(否则用户可能自己想到这个"标准做法")
|
|
1003
|
+
&& /不能用 0\.0\.0\.0/.test(envSrc)
|
|
1004
|
+
&& /不能用 0\.0\.0\.0/.test(clientSrc3),
|
|
1005
|
+
'lib/client.js + lib/env-check.js + lib/hub/index.js(修法不含 0.0.0.0,且有明确警告)')
|
|
975
1006
|
}
|
|
976
1007
|
|
|
977
1008
|
/* ── 81~82. 工作区 SMB 共享(2026-10-17)──
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# dsh-remote-agent worker diagnostic
|
|
2
|
+
#
|
|
3
|
+
# Read-only: changes nothing. Paste into PowerShell on the WORKER machine:
|
|
4
|
+
# powershell -ExecutionPolicy Bypass -File .\diag-worker.ps1
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
7
|
+
$profiles = Join-Path $env:USERPROFILE '.dsh\profiles'
|
|
8
|
+
|
|
9
|
+
function Line { param($t) Write-Host $t }
|
|
10
|
+
|
|
11
|
+
Write-Host ''
|
|
12
|
+
Write-Host '=========================================='
|
|
13
|
+
Write-Host ' dsh-remote-agent WORKER DIAGNOSTIC'
|
|
14
|
+
Write-Host '=========================================='
|
|
15
|
+
Write-Host ''
|
|
16
|
+
|
|
17
|
+
# ---- 1. profile dirs ----
|
|
18
|
+
Write-Host '[1] DSH profile directories'
|
|
19
|
+
if (Test-Path $profiles) {
|
|
20
|
+
Get-ChildItem $profiles -Directory | ForEach-Object {
|
|
21
|
+
$patch = Join-Path $_.FullName 'cordis.patch.yml'
|
|
22
|
+
if (Test-Path $patch) {
|
|
23
|
+
$f = Get-Item $patch
|
|
24
|
+
Line (" {0} patch={1}B mtime={2}" -f $_.Name, $f.Length, $f.LastWriteTime.ToString('MM-dd HH:mm:ss'))
|
|
25
|
+
} else {
|
|
26
|
+
Line (" {0} (no cordis.patch.yml)" -f $_.Name)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if ($env:DSH_PROFILE) {
|
|
30
|
+
Line (" env DSH_PROFILE = " + $env:DSH_PROFILE)
|
|
31
|
+
} else {
|
|
32
|
+
Line ' env DSH_PROFILE = (not set -> code defaults to "desktop")'
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
Line ' ERROR: no profile directory'
|
|
36
|
+
}
|
|
37
|
+
Write-Host ''
|
|
38
|
+
|
|
39
|
+
# ---- 2. remote-agent config per profile ----
|
|
40
|
+
Write-Host '[2] remote-agent config in each profile'
|
|
41
|
+
if (Test-Path $profiles) {
|
|
42
|
+
Get-ChildItem $profiles -Directory | ForEach-Object {
|
|
43
|
+
$patch = Join-Path $_.FullName 'cordis.patch.yml'
|
|
44
|
+
if (-not (Test-Path $patch)) { return }
|
|
45
|
+
$c = Get-Content $patch -Raw
|
|
46
|
+
if ($c -notmatch 'remote-agent') { return }
|
|
47
|
+
|
|
48
|
+
$roleM = [regex]::Match($c, '(?m)^\s*role:\s*(\S+)')
|
|
49
|
+
$injectM = [regex]::Match($c, '(?m)^\s*inject:\s*(.+)$')
|
|
50
|
+
$hubM = [regex]::Match($c, '(?m)^\s*hubUrl:\s*(\S+)')
|
|
51
|
+
$wsM = [regex]::Match($c, '(?ms)- id:\s*webserver.*?host:\s*(\S+)')
|
|
52
|
+
$tokM = [regex]::Match($c, '(?m)^\s*nodeToken:\s*(\S+)')
|
|
53
|
+
|
|
54
|
+
Line (' --- ' + $_.Name + ' ---')
|
|
55
|
+
if ($roleM.Success) { Line (' role = ' + $roleM.Groups[1].Value) } else { Line ' role = (NOT SET -> package default)' }
|
|
56
|
+
if ($injectM.Success) { Line (' inject = ' + $injectM.Groups[1].Value.Trim()) } else { Line ' inject = (not set)' }
|
|
57
|
+
if ($hubM.Success) { Line (' hubUrl = ' + $hubM.Groups[1].Value) } else { Line ' hubUrl = (not set)' }
|
|
58
|
+
if ($wsM.Success) { Line (' webserver = host=' + $wsM.Groups[1].Value) } else { Line ' webserver = (no webserver section)' }
|
|
59
|
+
if ($tokM.Success) { Line ' nodeToken = (set)' } else { Line ' nodeToken = (not set)' }
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
Line ' (skipped)'
|
|
63
|
+
}
|
|
64
|
+
Write-Host ''
|
|
65
|
+
|
|
66
|
+
# ---- 3. installed plugin version ----
|
|
67
|
+
Write-Host '[3] installed dsh-remote-agent version'
|
|
68
|
+
$found = $false
|
|
69
|
+
if (Test-Path $profiles) {
|
|
70
|
+
Get-ChildItem $profiles -Directory | ForEach-Object {
|
|
71
|
+
$nm = Join-Path $_.FullName 'node_modules\dsh-remote-agent'
|
|
72
|
+
$pj = Join-Path $nm 'package.json'
|
|
73
|
+
if (Test-Path $pj) {
|
|
74
|
+
$script:found = $true
|
|
75
|
+
$v = (Get-Content $pj -Raw | ConvertFrom-Json).version
|
|
76
|
+
$hasEnvCheck = Test-Path (Join-Path $nm 'lib\env-check.js')
|
|
77
|
+
$hasShare = Test-Path (Join-Path $nm 'lib\node\share-service.js')
|
|
78
|
+
$pkgPatch = Join-Path $nm 'cordis.patch.yml'
|
|
79
|
+
$pkgRole = ''
|
|
80
|
+
$hasWs = $false
|
|
81
|
+
if (Test-Path $pkgPatch) {
|
|
82
|
+
$pc = Get-Content $pkgPatch -Raw
|
|
83
|
+
$m = [regex]::Match($pc, '(?m)^\s*role:\s*(\S+)')
|
|
84
|
+
if ($m.Success) { $pkgRole = $m.Groups[1].Value }
|
|
85
|
+
$hasWs = $pc -match '(?m)^- id:\s*webserver'
|
|
86
|
+
}
|
|
87
|
+
Line (' ' + $_.Name + ': version ' + $v)
|
|
88
|
+
Line (' has lib/env-check.js (0.9+) : ' + $hasEnvCheck)
|
|
89
|
+
Line (' has lib/node/share-service.js : ' + $hasShare)
|
|
90
|
+
if ($pkgRole -ne '') { Line (' package default role : ' + $pkgRole) } else { Line ' package default role : (not found)' }
|
|
91
|
+
Line (' package has webserver binding : ' + $hasWs)
|
|
92
|
+
if (-not $hasEnvCheck) {
|
|
93
|
+
Line ' WARNING: this is pre-0.9, no "works out of the box"'
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (-not $script:found) {
|
|
99
|
+
Line ' ERROR: dsh-remote-agent not found under any profile'
|
|
100
|
+
}
|
|
101
|
+
Write-Host ''
|
|
102
|
+
|
|
103
|
+
# ---- 4. panel endpoints ----
|
|
104
|
+
Write-Host '[4] panel endpoint probe (is the plugin loaded at all?)'
|
|
105
|
+
$ports = @()
|
|
106
|
+
$dshPids = (Get-Process 'DeepSeek Harness').Id
|
|
107
|
+
if ($dshPids) {
|
|
108
|
+
$ports += (Get-NetTCPConnection -State Listen | Where-Object { $dshPids -contains $_.OwningProcess } | ForEach-Object { $_.LocalPort })
|
|
109
|
+
}
|
|
110
|
+
$ports += 19387
|
|
111
|
+
$ports = $ports | Sort-Object -Unique
|
|
112
|
+
foreach ($port in $ports) {
|
|
113
|
+
foreach ($path in @('/api/remote-agent/setup/state', '/api/remote-agent/status')) {
|
|
114
|
+
$url = 'http://127.0.0.1:' + $port + $path
|
|
115
|
+
try {
|
|
116
|
+
$r = Invoke-WebRequest $url -UseBasicParsing -TimeoutSec 3
|
|
117
|
+
$body = $r.Content
|
|
118
|
+
if ($body.Length -gt 240) { $body = $body.Substring(0, 240) + '...' }
|
|
119
|
+
Line (' [' + $port + '] ' + $path + ' -> HTTP ' + $r.StatusCode)
|
|
120
|
+
Line (' ' + $body)
|
|
121
|
+
} catch {
|
|
122
|
+
$code = $_.Exception.Response.StatusCode.value__
|
|
123
|
+
if ($code) {
|
|
124
|
+
Line (' [' + $port + '] ' + $path + ' -> HTTP ' + $code)
|
|
125
|
+
} else {
|
|
126
|
+
Line (' [' + $port + '] ' + $path + ' -> no listener')
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
Write-Host ''
|
|
132
|
+
|
|
133
|
+
# ---- 5. recent backups ----
|
|
134
|
+
Write-Host '[5] recent config backups (was the file rewritten?)'
|
|
135
|
+
if (Test-Path $profiles) {
|
|
136
|
+
Get-ChildItem $profiles -Recurse -File -Filter 'cordis.patch.yml*' |
|
|
137
|
+
Sort-Object LastWriteTime -Descending |
|
|
138
|
+
Select-Object -First 8 |
|
|
139
|
+
ForEach-Object {
|
|
140
|
+
Line (' ' + $_.LastWriteTime.ToString('MM-dd HH:mm:ss') + ' ' + $_.Length + 'B ' + $_.Name)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
Write-Host ''
|
|
144
|
+
|
|
145
|
+
Write-Host '=========================================='
|
|
146
|
+
Write-Host ' END. Paste the whole output back.'
|
|
147
|
+
Write-Host '=========================================='
|
|
148
|
+
Write-Host ''
|