@stardustlc/dsh-docker 0.3.0 → 0.3.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.en.md +10 -8
- package/README.md +10 -8
- package/cordis.patch.yml +1 -0
- package/lib/config.d.ts +2 -0
- package/lib/config.js +2 -1
- package/lib/exec.d.ts +1 -0
- package/lib/exec.js +4 -1
- package/lib/index.d.ts +23 -18
- package/lib/index.js +33 -44
- package/lib/tools.d.ts +1 -2
- package/lib/tools.js +30 -23
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
# dsh-docker
|
|
4
4
|
|
|
5
|
-
> **Your agent can manage containers now**:
|
|
5
|
+
> **Your agent can manage containers now**: seven tools covering containers, images, logs, inspection, in-container exec, lifecycle management, and health checks.
|
|
6
6
|
|
|
7
|
-
DSH (DeepSeek Harness) container-management plugin: runs the docker CLI through the official subprocess service with shell-free argv arrays,
|
|
7
|
+
DSH (DeepSeek Harness) container-management plugin: runs the docker CLI through the official subprocess service with shell-free argv arrays, approval gates on `docker_exec` and destructive lifecycle actions, and **zero runtime dependencies**.
|
|
8
8
|
|
|
9
|
-
   
|
|
9
|
+
   
|
|
10
10
|
|
|
11
11
|
[](https://awesome-dsh-plugin.com)
|
|
12
12
|
|
|
13
13
|
## Compatibility
|
|
14
14
|
|
|
15
|
-
Verified against `@deepseek-ai/dsh@0.1.
|
|
15
|
+
Verified against the official `@deepseek-ai/dsh@0.1.3-alpha.2` on 2026-09-08, including an 18-component co-load and the full tool registration/invocation contract. Built for the cordis patch-bundle model (`cordis.patch.yml` + `dsh.bundle.patch`) with no runtime imports of `@deepseek-ai/*` internals.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -41,6 +41,7 @@ Then restart the web service. To clean up fully, also remove the plugin entry fr
|
|
|
41
41
|
dockerPath: docker # optional; or use the DSH_DOCKER_PATH env var
|
|
42
42
|
timeoutMs: 60000 # per-operation timeout (default 60s, 5s - 10min)
|
|
43
43
|
# execApproval: false # disable the docker_exec approval gate (default true)
|
|
44
|
+
# manageApproval: false # disable approval for stop/restart/rm (default true; not recommended)
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
## Tools
|
|
@@ -52,7 +53,8 @@ Then restart the web service. To clean up fully, also remove the plugin entry fr
|
|
|
52
53
|
| `docker_logs` | Tail container logs (line clamp, short follow) | — |
|
|
53
54
|
| `docker_inspect` | Container details (image/state/ports) | — |
|
|
54
55
|
| `docker_exec` | Run a command inside a container | Approval gate + container-name validation |
|
|
55
|
-
| `docker_manage` | start / stop / restart / rm |
|
|
56
|
+
| `docker_manage` | start / stop / restart / rm | Approval gate for stop/restart/rm |
|
|
57
|
+
| `docker_health` | Check Docker daemon and safety settings | — |
|
|
56
58
|
|
|
57
59
|
### Examples
|
|
58
60
|
|
|
@@ -69,7 +71,7 @@ docker_manage { container: web, action: restart }
|
|
|
69
71
|
## Safety
|
|
70
72
|
|
|
71
73
|
- **No shell**: every argument is its own argv element — command injection is impossible
|
|
72
|
-
- **Approval
|
|
74
|
+
- **Approval gates**: docker_exec and docker_manage stop/restart/rm ask first; headless environments without an approval channel are denied
|
|
73
75
|
- **Container-name validation**: only `[A-Za-z0-9][A-Za-z0-9_.:-]*` accepted — no argument injection
|
|
74
76
|
- **Timeout clamps**: 5s - 10min per operation; follow mode capped at an extra 30s
|
|
75
77
|
- **Log clamping**: tail 1-2000 lines
|
|
@@ -78,9 +80,9 @@ docker_manage { container: web, action: restart }
|
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
82
|
pnpm install
|
|
81
|
-
pnpm test # build +
|
|
83
|
+
pnpm test # build + 35 tests
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
## License
|
|
85
87
|
|
|
86
|
-
MIT
|
|
88
|
+
MIT
|
package/README.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
# dsh-docker
|
|
4
4
|
|
|
5
|
-
> **你的 agent
|
|
5
|
+
> **你的 agent 会管容器了**:七个工具覆盖容器/镜像列表、日志、详情、容器内执行、生命周期管理与健康检查。
|
|
6
6
|
|
|
7
|
-
DSH(DeepSeek Harness)容器管理插件:走官方 subprocess 服务跑 docker CLI,argv 数组无 shell 注入,`docker_exec`
|
|
7
|
+
DSH(DeepSeek Harness)容器管理插件:走官方 subprocess 服务跑 docker CLI,argv 数组无 shell 注入,`docker_exec` 与破坏性生命周期操作默认审批门,**零运行时依赖**。
|
|
8
8
|
|
|
9
|
-
   
|
|
9
|
+
   
|
|
10
10
|
|
|
11
11
|
[](https://awesome-dsh-plugin.com)
|
|
12
12
|
|
|
13
13
|
## 兼容性
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
已在官方 `@deepseek-ai/dsh@0.1.3-alpha.2` 下验证(2026-09-08):18 个组件同载,工具注册与调用契约全部通过。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
|
|
16
16
|
|
|
17
17
|
## 安装
|
|
18
18
|
|
|
@@ -41,6 +41,7 @@ dsh plugin --profile web remove @stardustlc/dsh-docker
|
|
|
41
41
|
dockerPath: docker # 可选;也可用环境变量 DSH_DOCKER_PATH
|
|
42
42
|
timeoutMs: 60000 # 单次操作超时(默认 60 秒,5 秒 - 10 分钟)
|
|
43
43
|
# execApproval: false # 关闭 docker_exec 审批门(默认 true)
|
|
44
|
+
# manageApproval: false # 关闭 stop/restart/rm 审批门(默认 true,不推荐)
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
## 工具一览
|
|
@@ -52,7 +53,8 @@ dsh plugin --profile web remove @stardustlc/dsh-docker
|
|
|
52
53
|
| `docker_logs` | 查看日志尾部(行数钳制,可短时 follow)| — |
|
|
53
54
|
| `docker_inspect` | 容器详情(镜像/状态/端口)| — |
|
|
54
55
|
| `docker_exec` | 容器内执行命令 | 审批门 + 容器名白名单校验 |
|
|
55
|
-
| `docker_manage` | start / stop / restart / rm |
|
|
56
|
+
| `docker_manage` | start / stop / restart / rm | stop/restart/rm 审批门 |
|
|
57
|
+
| `docker_health` | Docker daemon 与安全配置自检 | — |
|
|
56
58
|
|
|
57
59
|
### 示例
|
|
58
60
|
|
|
@@ -69,7 +71,7 @@ docker_manage { container: web, action: restart }
|
|
|
69
71
|
## 安全设计
|
|
70
72
|
|
|
71
73
|
- **无 shell**:全部参数独立 argv 数组,命令注入不可能
|
|
72
|
-
- **审批门**:docker_exec
|
|
74
|
+
- **审批门**:docker_exec 与 docker_manage 的 stop/restart/rm 默认弹审批;headless 无审批通道时拒绝
|
|
73
75
|
- **容器名校验**:只允许 `[A-Za-z0-9][A-Za-z0-9_.:-]*`,杜绝参数注入
|
|
74
76
|
- **超时钳制**:单次操作 5 秒 - 10 分钟;follow 模式额外限 30 秒
|
|
75
77
|
- **日志钳制**:tail 1-2000 行
|
|
@@ -78,9 +80,9 @@ docker_manage { container: web, action: restart }
|
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
82
|
pnpm install
|
|
81
|
-
pnpm test # 构建 +
|
|
83
|
+
pnpm test # 构建 + 35 个测试
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
## License
|
|
85
87
|
|
|
86
|
-
MIT
|
|
88
|
+
MIT
|
package/cordis.patch.yml
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
# # dockerPath: C:\Program Files\Docker\Docker\resources\bin\docker.exe
|
|
10
10
|
# timeoutMs: 60000 # 单次操作超时(默认 60 秒,5 秒 - 10 分钟)
|
|
11
11
|
# # execApproval: false # 关闭 docker_exec 的审批门(默认 true)
|
|
12
|
+
# # manageApproval: false # 关闭 stop/restart/rm 审批门(默认 true,不推荐)
|
|
12
13
|
#
|
|
13
14
|
- insert:
|
|
14
15
|
- id: docker
|
package/lib/config.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface DockerConfig {
|
|
|
9
9
|
timeoutMs?: number;
|
|
10
10
|
graceMs?: number;
|
|
11
11
|
execApproval?: boolean;
|
|
12
|
+
manageApproval?: boolean;
|
|
12
13
|
}
|
|
13
14
|
/** 解析后的配置。 */
|
|
14
15
|
export interface ResolvedDockerConfig {
|
|
@@ -16,6 +17,7 @@ export interface ResolvedDockerConfig {
|
|
|
16
17
|
timeoutMs: number;
|
|
17
18
|
graceMs: number;
|
|
18
19
|
execApproval: boolean;
|
|
20
|
+
manageApproval: boolean;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* 解析并校验配置。
|
package/lib/config.js
CHANGED
|
@@ -22,7 +22,8 @@ export function resolveConfig(config, env = process.env) {
|
|
|
22
22
|
graceMs = Math.min(120000, Math.max(1000, Math.round(cfg.graceMs)));
|
|
23
23
|
}
|
|
24
24
|
const execApproval = cfg.execApproval !== false;
|
|
25
|
-
|
|
25
|
+
const manageApproval = cfg.manageApproval !== false;
|
|
26
|
+
return { dockerPath, timeoutMs, graceMs, execApproval, manageApproval };
|
|
26
27
|
}
|
|
27
28
|
/** 校验容器名/ID,防参数注入(只允许字母数字与 _-.:/ 之外的拒绝)。 */
|
|
28
29
|
export function assertContainerRef(value) {
|
package/lib/exec.d.ts
CHANGED
package/lib/exec.js
CHANGED
|
@@ -12,6 +12,9 @@ export function createSubprocessRunner(spawn, graceMs, defaultTimeoutMs) {
|
|
|
12
12
|
const timeoutMs = options?.timeoutMs ?? defaultTimeoutMs;
|
|
13
13
|
const controller = new AbortController();
|
|
14
14
|
const timer = setTimeout(() => controller.abort(new Error('docker operation timed out')), timeoutMs);
|
|
15
|
+
const signal = options?.signal === undefined
|
|
16
|
+
? controller.signal
|
|
17
|
+
: AbortSignal.any([options.signal, controller.signal]);
|
|
15
18
|
let handle;
|
|
16
19
|
try {
|
|
17
20
|
handle = spawn({
|
|
@@ -23,7 +26,7 @@ export function createSubprocessRunner(spawn, graceMs, defaultTimeoutMs) {
|
|
|
23
26
|
stderr: { maxBytes: COLLECT_BYTES },
|
|
24
27
|
},
|
|
25
28
|
graceMs,
|
|
26
|
-
signal
|
|
29
|
+
signal,
|
|
27
30
|
});
|
|
28
31
|
const outcome = await handle.done;
|
|
29
32
|
const stdout = handle.collected.stdout?.readFrom(0).text ?? '';
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-docker —— 容器管理工具插件(node 半身,配置走 cordis.patch.yml)。
|
|
3
3
|
*
|
|
4
|
-
* 插件导出 apply(ctx, config)
|
|
5
|
-
* docker_images / docker_inspect / docker_exec / docker_manage)。进程执行走 DSH 官方 subprocess 服务
|
|
6
|
-
* (argv 数组、无 shell),docker_exec 默认走宿主审批门。零运行时依赖。
|
|
4
|
+
* 插件导出 apply(ctx, config):注册七个面向模型的工具(docker_ps / docker_logs /
|
|
5
|
+
* docker_images / docker_inspect / docker_exec / docker_manage / docker_health)。进程执行走 DSH 官方 subprocess 服务
|
|
6
|
+
* (argv 数组、无 shell),docker_exec 与破坏性 docker_manage 默认走宿主审批门。零运行时依赖。
|
|
7
7
|
*
|
|
8
8
|
* @module dsh-docker
|
|
9
9
|
*/
|
|
@@ -13,31 +13,36 @@ import { type DockerToolDefinition } from './tools.js';
|
|
|
13
13
|
/** cordis 服务注入:apply 里要用 ctx.subprocess 与 ctx.tools。 */
|
|
14
14
|
export declare const name = "docker";
|
|
15
15
|
export declare const inject: string[];
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
/** Harness `tools/pre-execute` 的决策结果。 */
|
|
17
|
+
type DockerPreToolDecision = {
|
|
18
|
+
kind: 'allow';
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'deny';
|
|
21
|
+
reason: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'ask';
|
|
24
|
+
reason?: string;
|
|
25
|
+
};
|
|
26
|
+
/** 审批策略需要读取的工具执行字段。 */
|
|
27
|
+
interface DockerToolExecution {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly arguments: unknown;
|
|
25
30
|
}
|
|
31
|
+
/** Harness `tools/pre-execute` waterfall 监听器。 */
|
|
32
|
+
type DockerPreExecuteListener = (exec: DockerToolExecution, next: () => Promise<DockerPreToolDecision>) => Promise<DockerPreToolDecision>;
|
|
26
33
|
/** 插件所需的最小 ctx 面。 */
|
|
27
34
|
export interface DockerPluginContext {
|
|
28
35
|
subprocess: {
|
|
29
36
|
spawn: SubprocessSpawnLike;
|
|
30
37
|
};
|
|
31
38
|
tools: {
|
|
32
|
-
register(definition: DockerToolDefinition
|
|
33
|
-
prepend?: boolean;
|
|
34
|
-
}): () => void;
|
|
39
|
+
register(definition: DockerToolDefinition): () => void;
|
|
35
40
|
};
|
|
36
|
-
|
|
37
|
-
on
|
|
41
|
+
on(event: 'tools/pre-execute', listener: DockerPreExecuteListener): () => void;
|
|
42
|
+
on(event: 'dispose', listener: () => void): () => void;
|
|
38
43
|
}
|
|
39
44
|
/**
|
|
40
|
-
*
|
|
45
|
+
* 插件入口:解析配置、封装执行器、注册七工具;为 docker_exec 与破坏性 docker_manage 注入审批门。
|
|
41
46
|
*/
|
|
42
47
|
export declare function apply(ctx: DockerPluginContext, config?: DockerConfig | null): void;
|
|
43
48
|
export * from './args.js';
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-docker —— 容器管理工具插件(node 半身,配置走 cordis.patch.yml)。
|
|
3
3
|
*
|
|
4
|
-
* 插件导出 apply(ctx, config)
|
|
5
|
-
* docker_images / docker_inspect / docker_exec / docker_manage)。进程执行走 DSH 官方 subprocess 服务
|
|
6
|
-
* (argv 数组、无 shell),docker_exec 默认走宿主审批门。零运行时依赖。
|
|
4
|
+
* 插件导出 apply(ctx, config):注册七个面向模型的工具(docker_ps / docker_logs /
|
|
5
|
+
* docker_images / docker_inspect / docker_exec / docker_manage / docker_health)。进程执行走 DSH 官方 subprocess 服务
|
|
6
|
+
* (argv 数组、无 shell),docker_exec 与破坏性 docker_manage 默认走宿主审批门。零运行时依赖。
|
|
7
7
|
*
|
|
8
8
|
* @module dsh-docker
|
|
9
9
|
*/
|
|
@@ -14,55 +14,44 @@ import { buildDockerTools } from './tools.js';
|
|
|
14
14
|
export const name = 'docker';
|
|
15
15
|
export const inject = ['subprocess', 'tools'];
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* 插件入口:解析配置、封装执行器、注册七工具;为 docker_exec 与破坏性 docker_manage 注入审批门。
|
|
18
18
|
*/
|
|
19
19
|
export function apply(ctx, config) {
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
cfg = resolveConfig(config);
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
console.warn('[dsh-docker] ' + (error instanceof Error ? error.message : String(error)));
|
|
26
|
-
cfg = resolveConfig(null);
|
|
27
|
-
}
|
|
20
|
+
const cfg = resolveConfig(config);
|
|
28
21
|
const runner = createSubprocessRunner(ctx.subprocess.spawn, cfg.graceMs, cfg.timeoutMs);
|
|
29
22
|
const tools = buildDockerTools(cfg, runner);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const approval = ctx.get?.('approval');
|
|
35
|
-
if (approval === undefined) {
|
|
36
|
-
return { kind: 'deny', reason: 'docker_exec 需要确认,但当前环境没有审批通道(如 headless)。如确定安全,可在配置中设置 execApproval: false 后直接执行。' };
|
|
37
|
-
}
|
|
38
|
-
const record = (typeof exec === 'object' && exec !== null ? exec : {});
|
|
39
|
-
const args = (typeof record.args === 'object' && record.args !== null ? record.args : {});
|
|
23
|
+
if (cfg.execApproval || cfg.manageApproval) {
|
|
24
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
25
|
+
const args = (typeof exec.arguments === 'object' && exec.arguments !== null ? exec.arguments : {});
|
|
26
|
+
if (exec.name === 'docker_exec' && cfg.execApproval) {
|
|
40
27
|
const command = typeof args.command === 'string' ? args.command : '';
|
|
41
28
|
const container = typeof args.container === 'string' ? args.container : '';
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
disposers.push(ctx.tools.register(definition, { prepend: true }));
|
|
59
|
-
}
|
|
60
|
-
if (typeof ctx.on === 'function') {
|
|
61
|
-
ctx.on('dispose', () => {
|
|
62
|
-
for (const dispose of disposers)
|
|
63
|
-
dispose();
|
|
29
|
+
return {
|
|
30
|
+
kind: 'ask',
|
|
31
|
+
reason: 'docker_exec 需要确认:在容器 ' + (container || '(未指定)') + ' 内执行:' + command.slice(0, 200) + (command.length > 200 ? '…' : ''),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (exec.name === 'docker_manage' && cfg.manageApproval) {
|
|
35
|
+
const action = typeof args.action === 'string' ? args.action : '';
|
|
36
|
+
if (action === 'stop' || action === 'restart' || action === 'rm') {
|
|
37
|
+
const container = typeof args.container === 'string' ? args.container : '';
|
|
38
|
+
return {
|
|
39
|
+
kind: 'ask',
|
|
40
|
+
reason: 'docker_manage 需要确认:对容器 ' + (container || '(未指定)') + ' 执行破坏性操作 ' + action + '。',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return next();
|
|
64
45
|
});
|
|
65
46
|
}
|
|
47
|
+
const disposers = [];
|
|
48
|
+
for (const definition of tools) {
|
|
49
|
+
disposers.push(ctx.tools.register(definition));
|
|
50
|
+
}
|
|
51
|
+
ctx.on('dispose', () => {
|
|
52
|
+
for (const dispose of disposers)
|
|
53
|
+
dispose();
|
|
54
|
+
});
|
|
66
55
|
}
|
|
67
56
|
export * from './args.js';
|
|
68
57
|
export * from './config.js';
|
package/lib/tools.d.ts
CHANGED
|
@@ -17,8 +17,7 @@ export interface DockerToolDefinition {
|
|
|
17
17
|
render(args: unknown, value: unknown): ContentBlock[];
|
|
18
18
|
};
|
|
19
19
|
execute(args: unknown, exec: unknown): Promise<unknown>;
|
|
20
|
-
gate?(exec: unknown, next: () => Promise<unknown>): Promise<unknown>;
|
|
21
20
|
timeoutMs?: number;
|
|
22
21
|
}
|
|
23
|
-
/**
|
|
22
|
+
/** 构建七个工具定义。 */
|
|
24
23
|
export declare function buildDockerTools(config: ResolvedDockerConfig, runner: ProcessRunner): DockerToolDefinition[];
|
package/lib/tools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 七个面向模型的容器工具:docker_ps / docker_logs / docker_images / docker_inspect /
|
|
3
|
+
* docker_exec / docker_manage / docker_health。
|
|
3
4
|
*
|
|
4
5
|
* @module dsh-docker/tools
|
|
5
6
|
*/
|
|
@@ -34,8 +35,14 @@ function requiredString(args, key, label) {
|
|
|
34
35
|
throw new Error(label + '(参数 ' + key + ')为必填,请提供非空字符串。');
|
|
35
36
|
return value;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
function executionSignal(exec) {
|
|
39
|
+
if (typeof exec !== 'object' || exec === null)
|
|
40
|
+
return undefined;
|
|
41
|
+
const signal = exec.signal;
|
|
42
|
+
return signal instanceof AbortSignal ? signal : undefined;
|
|
43
|
+
}
|
|
44
|
+
async function runChecked(runner, argv, timeoutMs, label, signal) {
|
|
45
|
+
const result = await runner.run(argv, { timeoutMs, ...(signal === undefined ? {} : { signal }) });
|
|
39
46
|
if (result.exitCode !== 0) {
|
|
40
47
|
const tail = result.stderr.trim().split(/\r?\n/).slice(-6).join(' | ');
|
|
41
48
|
throw new Error(label + '失败(退出码 ' + String(result.exitCode ?? 'null') + '):' + (tail || '无错误输出') + '。请确认 docker 可用(docker version)。');
|
|
@@ -77,7 +84,7 @@ const manageSchema = {
|
|
|
77
84
|
properties: { container: { type: 'string' }, action: { type: 'string' }, output: { type: 'string' } },
|
|
78
85
|
additionalProperties: true,
|
|
79
86
|
};
|
|
80
|
-
/**
|
|
87
|
+
/** 构建七个工具定义。 */
|
|
81
88
|
export function buildDockerTools(config, runner) {
|
|
82
89
|
const cfg = config;
|
|
83
90
|
const timeout = cfg.timeoutMs;
|
|
@@ -101,10 +108,10 @@ export function buildDockerTools(config, runner) {
|
|
|
101
108
|
return [{ type: 'text', text: lines.join('\n') }];
|
|
102
109
|
},
|
|
103
110
|
},
|
|
104
|
-
async execute(rawArgs) {
|
|
111
|
+
async execute(rawArgs, exec) {
|
|
105
112
|
const args = asRecord(rawArgs);
|
|
106
113
|
const all = args.all === true;
|
|
107
|
-
const result = await runChecked(runner, psArgs(cfg.dockerPath, all, optionalString(args, 'name')), timeout, 'docker ps');
|
|
114
|
+
const result = await runChecked(runner, psArgs(cfg.dockerPath, all, optionalString(args, 'name')), timeout, 'docker ps', executionSignal(exec));
|
|
108
115
|
const containers = parsePsJson(result.stdout);
|
|
109
116
|
return { count: containers.length, all, containers };
|
|
110
117
|
},
|
|
@@ -125,13 +132,13 @@ export function buildDockerTools(config, runner) {
|
|
|
125
132
|
return [{ type: 'text', text: '容器 ' + rec.container + ' 最近日志:\n' + rec.text }];
|
|
126
133
|
},
|
|
127
134
|
},
|
|
128
|
-
async execute(rawArgs) {
|
|
135
|
+
async execute(rawArgs, exec) {
|
|
129
136
|
const args = asRecord(rawArgs);
|
|
130
137
|
const container = assertContainerRef(requiredString(args, 'container', '容器名'));
|
|
131
138
|
const tailRaw = args.tail;
|
|
132
139
|
const tail = typeof tailRaw === 'number' && Number.isInteger(tailRaw) ? Math.min(2000, Math.max(1, tailRaw)) : 100;
|
|
133
140
|
const follow = args.follow === true;
|
|
134
|
-
const result = await runChecked(runner, logsArgs(cfg.dockerPath, container, tail, follow), follow ? Math.min(timeout, 30000) : timeout, 'docker logs');
|
|
141
|
+
const result = await runChecked(runner, logsArgs(cfg.dockerPath, container, tail, follow), follow ? Math.min(timeout, 30000) : timeout, 'docker logs', executionSignal(exec));
|
|
135
142
|
return { container, tail, text: result.stdout };
|
|
136
143
|
},
|
|
137
144
|
timeoutMs: timeout,
|
|
@@ -155,10 +162,10 @@ export function buildDockerTools(config, runner) {
|
|
|
155
162
|
return [{ type: 'text', text: lines.join('\n') }];
|
|
156
163
|
},
|
|
157
164
|
},
|
|
158
|
-
async execute(rawArgs) {
|
|
165
|
+
async execute(rawArgs, exec) {
|
|
159
166
|
const args = asRecord(rawArgs);
|
|
160
167
|
const dangling = args.dangling === true;
|
|
161
|
-
const result = await runChecked(runner, imagesArgs(cfg.dockerPath, dangling), timeout, 'docker images');
|
|
168
|
+
const result = await runChecked(runner, imagesArgs(cfg.dockerPath, dangling), timeout, 'docker images', executionSignal(exec));
|
|
162
169
|
const images = parseImagesJson(result.stdout);
|
|
163
170
|
return { count: images.length, dangling, images };
|
|
164
171
|
},
|
|
@@ -178,10 +185,10 @@ export function buildDockerTools(config, runner) {
|
|
|
178
185
|
return [{ type: 'text', text: '容器 ' + info.name + '(' + info.image + '):' + (info.running === true ? '运行中' : '已停止') + ',端口:' + (Array.isArray(info.ports) ? info.ports.join(', ') : '无') }];
|
|
179
186
|
},
|
|
180
187
|
},
|
|
181
|
-
async execute(rawArgs) {
|
|
188
|
+
async execute(rawArgs, exec) {
|
|
182
189
|
const args = asRecord(rawArgs);
|
|
183
190
|
const container = assertContainerRef(requiredString(args, 'container', '容器名'));
|
|
184
|
-
const result = await runChecked(runner, inspectArgs(cfg.dockerPath, container), timeout, 'docker inspect');
|
|
191
|
+
const result = await runChecked(runner, inspectArgs(cfg.dockerPath, container), timeout, 'docker inspect', executionSignal(exec));
|
|
185
192
|
return { container, info: parseInspectJson(result.stdout) };
|
|
186
193
|
},
|
|
187
194
|
timeoutMs: timeout,
|
|
@@ -200,23 +207,20 @@ export function buildDockerTools(config, runner) {
|
|
|
200
207
|
return [{ type: 'text', text: '容器 ' + rec.container + ' 执行完成(退出码 ' + rec.exitCode + '):\n' + rec.stdout }];
|
|
201
208
|
},
|
|
202
209
|
},
|
|
203
|
-
async execute(rawArgs) {
|
|
210
|
+
async execute(rawArgs, exec) {
|
|
204
211
|
const args = asRecord(rawArgs);
|
|
205
212
|
const container = assertContainerRef(requiredString(args, 'container', '容器名'));
|
|
206
213
|
const command = splitCommand(requiredString(args, 'command', '命令'));
|
|
207
214
|
if (command.length === 0)
|
|
208
215
|
throw new Error('command 不能为空。');
|
|
209
|
-
const result = await runChecked(runner, execArgs(cfg.dockerPath, container, command), timeout, 'docker exec');
|
|
216
|
+
const result = await runChecked(runner, execArgs(cfg.dockerPath, container, command), timeout, 'docker exec', executionSignal(exec));
|
|
210
217
|
return { container, exitCode: result.exitCode, stdout: result.stdout };
|
|
211
218
|
},
|
|
212
|
-
gate(exec, next) {
|
|
213
|
-
return next();
|
|
214
|
-
},
|
|
215
219
|
timeoutMs: timeout,
|
|
216
220
|
};
|
|
217
221
|
const dockerManage = {
|
|
218
222
|
name: 'docker_manage',
|
|
219
|
-
description: '容器生命周期管理:start / stop / restart / rm(rm
|
|
223
|
+
description: '容器生命周期管理:start / stop / restart / rm(rm 强制删除)。stop/restart/rm 默认进入审批门,批准后才执行。',
|
|
220
224
|
parameters: compileParameters({
|
|
221
225
|
container: { type: 'string', required: true, description: '容器名或 ID(必填)。' },
|
|
222
226
|
action: { type: 'string', required: true, description: '操作:start / stop / restart / rm(必填)。' },
|
|
@@ -228,20 +232,20 @@ export function buildDockerTools(config, runner) {
|
|
|
228
232
|
return [{ type: 'text', text: '容器 ' + rec.container + ' 已执行 ' + rec.action + '。' }];
|
|
229
233
|
},
|
|
230
234
|
},
|
|
231
|
-
async execute(rawArgs) {
|
|
235
|
+
async execute(rawArgs, exec) {
|
|
232
236
|
const args = asRecord(rawArgs);
|
|
233
237
|
const container = assertContainerRef(requiredString(args, 'container', '容器名'));
|
|
234
238
|
const action = requiredString(args, 'action', '操作');
|
|
235
239
|
if (!['start', 'stop', 'restart', 'rm'].includes(action))
|
|
236
240
|
throw new Error('action 必须是 start / stop / restart / rm 之一(当前:' + action + ')。');
|
|
237
|
-
const result = await runChecked(runner, manageArgs(cfg.dockerPath, action, container), timeout, 'docker ' + action);
|
|
241
|
+
const result = await runChecked(runner, manageArgs(cfg.dockerPath, action, container), timeout, 'docker ' + action, executionSignal(exec));
|
|
238
242
|
return { container, action, output: result.stdout.trim() };
|
|
239
243
|
},
|
|
240
244
|
timeoutMs: timeout,
|
|
241
245
|
};
|
|
242
246
|
const dockerHealth = {
|
|
243
247
|
name: 'docker_health',
|
|
244
|
-
description: 'dsh-docker 自检:验证 docker CLI 与守护进程是否可用(执行 docker version),并汇总安全配置(exec 审批门、超时)。遇到问题时先运行本工具定位。',
|
|
248
|
+
description: 'dsh-docker 自检:验证 docker CLI 与守护进程是否可用(执行 docker version),并汇总安全配置(exec/破坏性 manage 审批门、超时)。遇到问题时先运行本工具定位。',
|
|
245
249
|
parameters: compileParameters({}),
|
|
246
250
|
output: {
|
|
247
251
|
schema: { type: 'object', additionalProperties: true },
|
|
@@ -256,18 +260,21 @@ export function buildDockerTools(config, runner) {
|
|
|
256
260
|
return [{ type: 'text', text: lines.join('\n') }];
|
|
257
261
|
},
|
|
258
262
|
},
|
|
259
|
-
async execute() {
|
|
263
|
+
async execute(_rawArgs, exec) {
|
|
260
264
|
const checks = [];
|
|
261
265
|
let ok = true;
|
|
262
266
|
try {
|
|
263
|
-
const result = await runChecked(runner, [cfg.dockerPath, 'version', '--format', '{{.Server.Version}}'], 15000, 'docker version');
|
|
267
|
+
const result = await runChecked(runner, [cfg.dockerPath, 'version', '--format', '{{.Server.Version}}'], 15000, 'docker version', executionSignal(exec));
|
|
264
268
|
checks.push({ name: 'docker daemon', ok: true, detail: 'server ' + result.stdout.trim() });
|
|
265
269
|
}
|
|
266
270
|
catch (error) {
|
|
271
|
+
if (executionSignal(exec)?.aborted === true)
|
|
272
|
+
throw error;
|
|
267
273
|
ok = false;
|
|
268
274
|
checks.push({ name: 'docker daemon', ok: false, detail: error instanceof Error ? error.message : String(error) });
|
|
269
275
|
}
|
|
270
276
|
checks.push({ name: 'exec 审批门', ok: true, detail: cfg.execApproval === true ? '开启' : '关闭' });
|
|
277
|
+
checks.push({ name: 'manage 审批门', ok: true, detail: cfg.manageApproval === true ? '开启' : '关闭' });
|
|
271
278
|
checks.push({ name: '超时配置', ok: true, detail: 'timeoutMs=' + cfg.timeoutMs + ', graceMs=' + cfg.graceMs });
|
|
272
279
|
return { ok, plugin: 'dsh-docker', checks };
|
|
273
280
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardustlc/dsh-docker",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "DSH
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "DSH 容器管理插件:七工具覆盖容器与镜像管理,走官方 subprocess 服务、argv 无 shell 注入,exec 与破坏性 manage 默认审批。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|