dsh-server-connector 0.2.2 → 0.3.0
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 +88 -0
- package/LICENSE +0 -0
- package/README.md +49 -7
- package/cordis.patch.yml +6 -6
- package/icon.svg +15 -15
- package/lib/audit.js +469 -455
- package/lib/client.js +2389 -1720
- package/lib/config.js +89 -0
- package/lib/index.js +316 -266
- package/lib/known-hosts.js +377 -377
- package/lib/pending.js +766 -753
- package/lib/policy.js +590 -590
- package/lib/pool.js +1496 -1464
- package/lib/registry.js +968 -968
- package/lib/routes.js +14 -13
- package/lib/tools.js +1367 -1357
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +105 -105
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 0.3.0 — 2026-11-09
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- **设置页的「全局设置」无法修改**。九个键此前一个 `.volatile()` 都没有,而 DSH 0.2.2
|
|
8
|
+
只通过 `volatileForm()` 把插件配置投影成设置表单:一个 volatile 字段都没有时整份
|
|
9
|
+
schema 投影为 `undefined`,`settings.describe()` 不会列出这个 row,
|
|
10
|
+
`ctx.configForms.get('dsh-server-connector')` 永远是 `unavailable`。于是那一块只能
|
|
11
|
+
显示九个只读数字——不是「值来自 profile」,而是**这个 row 从来没被标记成可编辑**。
|
|
12
|
+
|
|
13
|
+
现在九键全部 `.volatile()`,设置页用 `ctx.configForms` 渲染真正的表单:改完点保存,
|
|
14
|
+
`mutate(ops, revision)` 一次性写入,**立即生效、不重启、不断开现有连接**。
|
|
15
|
+
可编辑表单拿不到时(旧 Host、或该部署没有这个 row)降级为只读视图并明确说明原因,
|
|
16
|
+
不再渲染一排死掉的输入框。
|
|
17
|
+
|
|
18
|
+
- **volatile 值必须按 cell 读,否则保存等于没保存**。`.volatile()` 字段解析出来的是
|
|
19
|
+
cosmokit 的 cell 而不是值,loader 直接**原地改写这个 cell** 并发
|
|
20
|
+
`loader/volatile-update`,插件不会被重新挂载、`apply()` 不会再执行一次。
|
|
21
|
+
新增 `lib/config.js` 的 `readConfig()` 作为唯一读取入口(各单元在**用值时**才读),
|
|
22
|
+
并在 `apply()` 里监听 `loader/volatile-update`,把新值转发给已经持有快照的
|
|
23
|
+
`Pool.configure()` / `AuditLog.configure()` / `PendingQueue.configure()`。
|
|
24
|
+
这些 `configure()` 都是原地生效:改超时不会断开连接,关审计不会删掉已有日志,
|
|
25
|
+
改 TTL 只影响之后入队的记录(已入队的截止时间不变)。
|
|
26
|
+
|
|
27
|
+
### 变更
|
|
28
|
+
|
|
29
|
+
- **删除确认改为详情页原地两段式**。此前点「删除」只是入队,然后必须切到设置页找到
|
|
30
|
+
那条记录再确认;现在详情页点「删除」会在本页展开确认区:显示目标、后果,勾选确认框后
|
|
31
|
+
「确认删除」才可点。安全模型没变——请求仍然先入队(`DELETE` 只入队返回 202),
|
|
32
|
+
再调用 `POST /pending/:id/confirm` 才真正删除。设置页的待确认区保留,作为异时确认的
|
|
33
|
+
第二入口。
|
|
34
|
+
|
|
35
|
+
- **更新后面板消失,重启才回来 —— 已按 dsh-context 的做法修好本插件这一半**。
|
|
36
|
+
参照对象不是猜的:同一台机器上 `dsh-context` 更新时面板**先消失后自己回来**,
|
|
37
|
+
而本插件只能重启。差别只有一处——
|
|
38
|
+
|
|
39
|
+
`dsh-context` 每次挂载前先问注册表「我这个 id 是不是已经在位」:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
function alreadyMounted(ctx, slot, id) {
|
|
43
|
+
const entries = ctx.slots.entries(slot) // 注册表的读取面
|
|
44
|
+
return entries.some((entry) => entry.options?.id === id)
|
|
45
|
+
}
|
|
46
|
+
// apply() 里
|
|
47
|
+
if (alreadyMounted(ctx, n_, r_)) return
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
本插件此前是**盲注册**。而 DSH 的插槽注册表对同一 id、同一优先级会直接抛:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
list slot "settings.section" already has an entry with id "server-connector"
|
|
54
|
+
keyed slot "main" already has an entry for key "server-connector"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
(`ui-slots`:list 比 `options.id`,keyed 比 `options.key`。)这个抛错落在
|
|
58
|
+
`slots.inject` 的回调里,而该注入**一旦抛出就在本 fiber 余生不再重试**——
|
|
59
|
+
于是新代码挂上、入口却没注册,页面要等进程重启才恢复。这正是「先消失、
|
|
60
|
+
重启才回来」在本插件侧的那一环。
|
|
61
|
+
|
|
62
|
+
现在三个入口都先查在位情况,在位就原样复用、不再注册;`alreadyMounted()` 同时
|
|
63
|
+
兼容 `entriesOfSlot()` 与 `entries()` 两种读取面,读不到就退回盲注册(旧 Host 仍能工作)。
|
|
64
|
+
回归测试按真实注册表的重复判定(list 比 id、keyed 比 key、拒绝时抛错)搭了替身,
|
|
65
|
+
并断言第二次 `apply()` **一次 register 都不发起**。
|
|
66
|
+
|
|
67
|
+
保留上一版的兜底:注册真的失败时只丢那一个入口,不会顺带带走整批插件。
|
|
68
|
+
|
|
69
|
+
### 文档
|
|
70
|
+
|
|
71
|
+
- README 与 `docs/design.md` 的 §8.2 / §8.3 / §10 更新为上述行为。
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 0.2.3 — 2026-11-06
|
|
76
|
+
|
|
77
|
+
### 修复
|
|
78
|
+
|
|
79
|
+
- **深色主题下启用状态的开关显示为一条纯白条**。0.2.2 把滑块写死成白色,而深色主题下
|
|
80
|
+
轨道解析为 `#f9fafb`(近白)——白滑块落在白轨道上,整个控件看起来没有滑块。
|
|
81
|
+
|
|
82
|
+
DSH 原生开关的做法是**轨道与滑块成对反转**:轨道用 `--dsw-alias-label-primary`,
|
|
83
|
+
滑块用 `--dsw-alias-label-primary-inverted`。浅色下是深轨道+白滑块,深色下是白轨道+深滑块,
|
|
84
|
+
两种主题都保证对比度。现已改为这一对 token,并在两种主题下实测确认。
|
|
85
|
+
|
|
86
|
+
- 关闭态轨道改用 `--dsw-alias-border-l4`(深色下自动变为半透明白),滑块用
|
|
87
|
+
`--dsw-alias-label-tertiary`,不再依赖写死的颜色。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
3
91
|
## 0.2.2 — 2026-11-06
|
|
4
92
|
|
|
5
93
|
### 修复
|
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ DSH(DeepSeek Harness)插件:在本机配置好可运维的 SSH 服务器
|
|
|
40
40
|
| 避免 agent 手滑执行破坏性命令 | 20 条内置高危规则先判定:`block` 直接拒绝,`confirm` 弹审批框,`allow` 直接执行 |
|
|
41
41
|
| 事后追查谁执行了什么 | 每次 `srv_exec` 写一行 JSON 到 `audit.log`,面板里也能看到最近 20 条 |
|
|
42
42
|
| 长命令不阻塞对话 | 超过同步窗口自动转后台 job,模型用 `srv_job_output` 增量读取、`srv_job_kill` 取消 |
|
|
43
|
-
| 删服务器、关主机密钥校验这类危险改动先缓一缓 |
|
|
43
|
+
| 删服务器、关主机密钥校验这类危险改动先缓一缓 | 先进「待确认」,确认后才真正生效(删除在详情页原地确认) |
|
|
44
44
|
|
|
45
45
|
**不做什么**:不做文件上传/SFTP、端口转发、跳板机、Kerberos(见[已知限制](#已知限制))。
|
|
46
46
|
另外,策略是**遏制不是安全边界**——真正的硬边界仍然是远端那个低权限用户与 sudo 白名单。
|
|
@@ -201,7 +201,11 @@ dsh --profile web --dump-config | grep -A2 dsh-server-connector
|
|
|
201
201
|
|
|
202
202
|
### 详情
|
|
203
203
|
|
|
204
|
-
连接字段只读、逐条高危策略的当前取值、最近 20 条审计,以及底部的
|
|
204
|
+
连接字段只读、逐条高危策略的当前取值、最近 20 条审计,以及底部的 **删除**。
|
|
205
|
+
|
|
206
|
+
点「删除」不会立刻删掉任何东西:本页展开确认区,显示目标与后果,
|
|
207
|
+
勾选确认框后「确认删除」才可点。确认时插件先把请求写入待确认区(`DELETE` 只入队),
|
|
208
|
+
紧接着确认这一条;服务器在你按下「确认删除」之前一直可用。
|
|
205
209
|
|
|
206
210
|

|
|
207
211
|
|
|
@@ -397,7 +401,7 @@ srv_exec(command)
|
|
|
397
401
|
### 面板操作的「待确认区」
|
|
398
402
|
|
|
399
403
|
面板本身不在 turn 内,没法弹审批框,所以三类危险操作改成两段式:
|
|
400
|
-
先记入 `pending.json
|
|
404
|
+
先记入 `pending.json`(目标保持原样),看到完整上下文后再确认。
|
|
401
405
|
|
|
402
406
|
| 操作 | 请求后立即 | 确认后 |
|
|
403
407
|
|---|---|---|
|
|
@@ -405,11 +409,47 @@ srv_exec(command)
|
|
|
405
409
|
| 主机密钥改为 `off` | 仍按原策略校验 | 改为 `off`,审计写 `hostkey-disabled` |
|
|
406
410
|
| 停用某条高危规则 | 规则仍生效 | 加入 `disabledRules`,审计写 `rule-disabled` |
|
|
407
411
|
|
|
412
|
+
删除的确认在**详情页原地**完成(点删除 → 确认区 → 勾选后确认),不必再切面板;
|
|
413
|
+
另外两类仍在设置页的「待确认操作」里确认,那里也是所有请求的统一入口,
|
|
414
|
+
包括删除这种「关了页面才想起来」的情况。
|
|
415
|
+
|
|
408
416
|
每条记录 24 小时后自动过期(`pendingTtlHours`),过期后必须重新申请;过期记录无法确认。
|
|
409
417
|
|
|
410
418
|

|
|
411
419
|
|
|
412
|
-
|
|
420
|
+
设置页同时提供**可编辑的全局设置**:九个键直接改、点保存即生效,
|
|
421
|
+
不用重启插件,也不会断开已经在用的连接。
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 全局设置(可编辑)
|
|
426
|
+
|
|
427
|
+
在「设置 → 服务器连接器」里直接改,保存后立即生效。
|
|
428
|
+
|
|
429
|
+
```yaml
|
|
430
|
+
config:
|
|
431
|
+
commandTimeoutMs: 90000
|
|
432
|
+
auditEnabled: false
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
| 键 | 默认 | 说明 |
|
|
436
|
+
|---|---|---|
|
|
437
|
+
| `connectTimeoutMs` | `15000` | SSH 连接超时 |
|
|
438
|
+
| `commandTimeoutMs` | `60000` | 单条命令默认超时,工具参数 `timeoutMs` 可覆盖 |
|
|
439
|
+
| `syncTimeoutMs` | `20000` | 同步等待窗口,超过就转后台 job |
|
|
440
|
+
| `maxOutputChars` | `200000` | stdout、stderr **各自**的输出上限 |
|
|
441
|
+
| `idleTimeoutMs` | `600000` | 连接空闲多久后关闭 |
|
|
442
|
+
| `auditEnabled` | `true` | 是否写审计(关闭后命令照常执行) |
|
|
443
|
+
| `auditMaxBytes` | `0` | `0` 表示不轮转;大于 0 时超限轮转一次 |
|
|
444
|
+
| `pendingTtlHours` | `24` | 待确认记录的有效期(只影响之后入队的记录) |
|
|
445
|
+
| `maxListedServers` | `8` | 系统提示里最多列几台服务器,其余折叠成「另有 N 台」 |
|
|
446
|
+
|
|
447
|
+
所有键都有默认值,空配置也能启动。
|
|
448
|
+
|
|
449
|
+
九个键都声明为 **volatile**:DSH 只把 volatile 字段投影成设置表单,一个都没有时这一页
|
|
450
|
+
只剩只读展示。volatile 字段由 loader 原地改写并广播 `loader/volatile-update`,
|
|
451
|
+
插件不会被重新挂载,因此生效是即时的,而不是「保存后重启」。
|
|
452
|
+
面板手里的表单拿不到时(旧版 DSH 等),这一块会明确显示为只读,而不是渲染一排点不动的输入框。
|
|
413
453
|
|
|
414
454
|
---
|
|
415
455
|
|
|
@@ -453,7 +493,8 @@ srv_exec(command)
|
|
|
453
493
|
|
|
454
494
|
## 配置
|
|
455
495
|
|
|
456
|
-
|
|
496
|
+
平时在「设置 → 服务器连接器」里改(见上面的[全局设置](#全局设置可编辑)),保存即生效。
|
|
497
|
+
也可以直接写 profile 的 `cordis.patch.yml`(Web 界面里是「打开配置文件」),按插件行 id 覆盖:
|
|
457
498
|
|
|
458
499
|
```yaml
|
|
459
500
|
- id: dsh-server-connector
|
|
@@ -471,10 +512,11 @@ srv_exec(command)
|
|
|
471
512
|
| `idleTimeoutMs` | `600000` | 连接空闲多久后关闭 |
|
|
472
513
|
| `auditEnabled` | `true` | 是否写审计(关闭后命令照常执行) |
|
|
473
514
|
| `auditMaxBytes` | `0` | `0` 表示不轮转;大于 0 时超限轮转一次 |
|
|
474
|
-
| `pendingTtlHours` | `24` |
|
|
515
|
+
| `pendingTtlHours` | `24` | 待确认记录的有效期(只影响之后入队的记录) |
|
|
475
516
|
| `maxListedServers` | `8` | 系统提示里最多列几台服务器,其余折叠成「另有 N 台」 |
|
|
476
517
|
|
|
477
|
-
|
|
518
|
+
所有键都有默认值,空配置也能启动。两条路径都改的是同一份 profile 配置:
|
|
519
|
+
面板保存走 `configEditor`,把它写回 YAML 并保留注释;手改文件同样即时生效。
|
|
478
520
|
|
|
479
521
|
### 数据文件
|
|
480
522
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# dsh-server-connector bundle patch: inserts only this plugin into a profile's
|
|
2
|
-
# layer stack. `name` is the module specifier the loader imports, so it must
|
|
3
|
-
# equal package.json's `name` verbatim; `id` is the row handle in the config tree.
|
|
4
|
-
- insert:
|
|
5
|
-
- id: dsh-server-connector
|
|
6
|
-
name: 'dsh-server-connector'
|
|
1
|
+
# dsh-server-connector bundle patch: inserts only this plugin into a profile's
|
|
2
|
+
# layer stack. `name` is the module specifier the loader imports, so it must
|
|
3
|
+
# equal package.json's `name` verbatim; `id` is the row handle in the config tree.
|
|
4
|
+
- insert:
|
|
5
|
+
- id: dsh-server-connector
|
|
6
|
+
name: 'dsh-server-connector'
|
package/icon.svg
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<g transform="translate(1.5 1.5) scale(0.9166666667)">
|
|
3
|
-
<path d="M6 8.5C6 6.57 9.13 5 13 5H23C26.87 5 30 6.57 30 8.5V13.5C30 15.43 26.87 17 23 17H13C9.13 17 6 15.43 6 13.5V8.5Z" fill="url(#a)" fill-opacity="0.85" />
|
|
4
|
-
<path d="M6 22.5C6 20.57 9.13 19 13 19H23C26.87 19 30 20.57 30 22.5V27.5C30 29.43 26.87 31 23 31H13C9.13 31 6 29.43 6 27.5V22.5Z" fill="url(#a)" fill-opacity="0.45" />
|
|
5
|
-
<circle cx="12.5" cy="11" r="1.8" fill="#FFFFFF" />
|
|
6
|
-
<circle cx="12.5" cy="25" r="1.8" fill="#FFFFFF" />
|
|
7
|
-
<path d="M17 11H24M17 25H24" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" />
|
|
8
|
-
</g>
|
|
9
|
-
<defs>
|
|
10
|
-
<linearGradient id="a" x1="18" y1="5" x2="18" y2="31" gradientUnits="userSpaceOnUse">
|
|
11
|
-
<stop stop-color="#7AC2FF" />
|
|
12
|
-
<stop offset="1" stop-color="#4A65E8" />
|
|
13
|
-
</linearGradient>
|
|
14
|
-
</defs>
|
|
15
|
-
</svg>
|
|
1
|
+
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g transform="translate(1.5 1.5) scale(0.9166666667)">
|
|
3
|
+
<path d="M6 8.5C6 6.57 9.13 5 13 5H23C26.87 5 30 6.57 30 8.5V13.5C30 15.43 26.87 17 23 17H13C9.13 17 6 15.43 6 13.5V8.5Z" fill="url(#a)" fill-opacity="0.85" />
|
|
4
|
+
<path d="M6 22.5C6 20.57 9.13 19 13 19H23C26.87 19 30 20.57 30 22.5V27.5C30 29.43 26.87 31 23 31H13C9.13 31 6 29.43 6 27.5V22.5Z" fill="url(#a)" fill-opacity="0.45" />
|
|
5
|
+
<circle cx="12.5" cy="11" r="1.8" fill="#FFFFFF" />
|
|
6
|
+
<circle cx="12.5" cy="25" r="1.8" fill="#FFFFFF" />
|
|
7
|
+
<path d="M17 11H24M17 25H24" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" />
|
|
8
|
+
</g>
|
|
9
|
+
<defs>
|
|
10
|
+
<linearGradient id="a" x1="18" y1="5" x2="18" y2="31" gradientUnits="userSpaceOnUse">
|
|
11
|
+
<stop stop-color="#7AC2FF" />
|
|
12
|
+
<stop offset="1" stop-color="#4A65E8" />
|
|
13
|
+
</linearGradient>
|
|
14
|
+
</defs>
|
|
15
|
+
</svg>
|