@dofe/infra-docker 0.1.78 → 0.1.80
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 +29 -0
- package/dist/docker-client.factory.d.ts +14 -0
- package/dist/docker-client.factory.d.ts.map +1 -0
- package/dist/docker-client.factory.js +68 -0
- package/dist/docker-client.factory.js.map +1 -0
- package/dist/docker-image-puller.d.ts +28 -0
- package/dist/docker-image-puller.d.ts.map +1 -0
- package/dist/docker-image-puller.js +116 -0
- package/dist/docker-image-puller.js.map +1 -0
- package/dist/docker-sandbox-command.d.ts +9 -0
- package/dist/docker-sandbox-command.d.ts.map +1 -0
- package/dist/docker-sandbox-command.js +70 -0
- package/dist/docker-sandbox-command.js.map +1 -0
- package/dist/docker-sandbox-runner.d.ts +3 -0
- package/dist/docker-sandbox-runner.d.ts.map +1 -0
- package/dist/docker-sandbox-runner.js +66 -0
- package/dist/docker-sandbox-runner.js.map +1 -0
- package/dist/docker-sandbox.types.d.ts +35 -0
- package/dist/docker-sandbox.types.d.ts.map +1 -0
- package/dist/docker-sandbox.types.js +3 -0
- package/dist/docker-sandbox.types.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/package.json +28 -4
- package/test/image-puller.smoke.mjs +156 -0
- package/test/sandbox.smoke.mjs +114 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @dofe/infra-docker
|
|
2
|
+
|
|
3
|
+
Shared Docker primitives for Dofe services.
|
|
4
|
+
|
|
5
|
+
## Owns
|
|
6
|
+
|
|
7
|
+
- Docker client creation and daemon probing.
|
|
8
|
+
- Image inspect and pull helpers, including registry auth matching and safe
|
|
9
|
+
error redaction.
|
|
10
|
+
- Product-neutral Docker sandbox command/profile/runner primitives for
|
|
11
|
+
short-lived execution.
|
|
12
|
+
- Generic container lifecycle helpers retained by the existing `DockerService`.
|
|
13
|
+
|
|
14
|
+
## Does Not Own
|
|
15
|
+
|
|
16
|
+
- Bot lifecycle, OpenClaw Gateway config sync, Gateway tokens, provider routing,
|
|
17
|
+
or product container labels.
|
|
18
|
+
- Loop shards, review evidence, browser QA, operator UX, or Loop-specific image
|
|
19
|
+
defaults.
|
|
20
|
+
- Model provider keys, routing decisions, usage, or proxy behavior.
|
|
21
|
+
|
|
22
|
+
## Compatibility Note
|
|
23
|
+
|
|
24
|
+
Some historical exports in `src/types.ts` are Bot/OpenClaw-shaped compatibility
|
|
25
|
+
stubs. They remain for existing consumers, but new public APIs must use
|
|
26
|
+
product-neutral names and types.
|
|
27
|
+
|
|
28
|
+
See `docs/0627/shared-primitives-boundary.md` in the infra repository for the
|
|
29
|
+
cross-package boundary.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Docker from 'dockerode';
|
|
2
|
+
export interface DockerClientOptions {
|
|
3
|
+
dockerHost?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface DockerDaemonProbe {
|
|
6
|
+
ok: boolean;
|
|
7
|
+
version?: string;
|
|
8
|
+
message?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function createDockerClient(options?: DockerClientOptions): Docker;
|
|
11
|
+
export declare function probeDockerDaemon(docker: Docker, timeoutMs?: number): Promise<DockerDaemonProbe>;
|
|
12
|
+
export declare function withDockerTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T>;
|
|
13
|
+
export declare function safeDockerMessage(error: unknown, maxLength?: number): string;
|
|
14
|
+
//# sourceMappingURL=docker-client.factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-client.factory.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/docker-client.factory.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,WAAW,CAAC;AAK/B,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAI5E;AAuBD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,SAAqB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,CAqB5B;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,CAAC,CAAC,CAaZ;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,SAAM,GAAG,MAAM,CAGzE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createDockerClient = createDockerClient;
|
|
7
|
+
exports.probeDockerDaemon = probeDockerDaemon;
|
|
8
|
+
exports.withDockerTimeout = withDockerTimeout;
|
|
9
|
+
exports.safeDockerMessage = safeDockerMessage;
|
|
10
|
+
const dockerode_1 = __importDefault(require("dockerode"));
|
|
11
|
+
const DEFAULT_DOCKER_HOST = '/var/run/docker.sock';
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 8000;
|
|
13
|
+
function createDockerClient(options = {}) {
|
|
14
|
+
return new dockerode_1.default(getDockerConnectionOptions(options.dockerHost ?? DEFAULT_DOCKER_HOST));
|
|
15
|
+
}
|
|
16
|
+
function getDockerConnectionOptions(hostOrUrl) {
|
|
17
|
+
const value = (hostOrUrl ?? '').trim();
|
|
18
|
+
if (!value) {
|
|
19
|
+
return { socketPath: DEFAULT_DOCKER_HOST };
|
|
20
|
+
}
|
|
21
|
+
const tcpMatch = value.match(/^(?:tcp|http|https):\/\/([^:/]+):(\d+)$/i);
|
|
22
|
+
if (tcpMatch) {
|
|
23
|
+
const protocol = value.toLowerCase().startsWith('https') ? 'https' : 'http';
|
|
24
|
+
return {
|
|
25
|
+
protocol: protocol,
|
|
26
|
+
host: tcpMatch[1],
|
|
27
|
+
port: parseInt(tcpMatch[2], 10),
|
|
28
|
+
socketPath: null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const socketPath = value.startsWith('unix://') ? value.slice(7) : value;
|
|
32
|
+
return { socketPath };
|
|
33
|
+
}
|
|
34
|
+
async function probeDockerDaemon(docker, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
35
|
+
try {
|
|
36
|
+
await withDockerTimeout(docker.ping(), timeoutMs, 'docker ping');
|
|
37
|
+
const dockerWithVersion = docker;
|
|
38
|
+
const version = await withDockerTimeout(dockerWithVersion.version(), timeoutMs, 'docker version');
|
|
39
|
+
return {
|
|
40
|
+
ok: true,
|
|
41
|
+
version: (version.ServerVersion ?? version.Version)?.slice(0, 120),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
message: safeDockerMessage(error),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function withDockerTimeout(promise, timeoutMs, label) {
|
|
52
|
+
let timer;
|
|
53
|
+
const timeout = new Promise((_, reject) => {
|
|
54
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
return await Promise.race([promise, timeout]);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
if (timer)
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function safeDockerMessage(error, maxLength = 240) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
return message.slice(0, maxLength);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=docker-client.factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-client.factory.js","sourceRoot":"","sources":["../../../packages/docker/src/docker-client.factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Docker from 'dockerode';
|
|
2
|
+
export interface DockerRegistryAuth {
|
|
3
|
+
username: string;
|
|
4
|
+
password: string;
|
|
5
|
+
serveraddress: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DockerPullOptions {
|
|
8
|
+
image: string;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
platform?: string;
|
|
11
|
+
registryAuth?: DockerRegistryAuth;
|
|
12
|
+
}
|
|
13
|
+
export interface DockerPullOutcome {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DockerImagePresence {
|
|
18
|
+
present: boolean;
|
|
19
|
+
imageId: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function inspectDockerImage(docker: Docker | null, image: string, timeoutMs?: number): Promise<DockerImagePresence>;
|
|
22
|
+
export declare function pullDockerImage(docker: Docker | null, options: DockerPullOptions): Promise<DockerPullOutcome>;
|
|
23
|
+
export declare function registryAuthFromEnv(image: string, env?: NodeJS.ProcessEnv): DockerRegistryAuth | undefined;
|
|
24
|
+
export declare function redactDockerAuth(message: string, auth: DockerRegistryAuth | undefined): string;
|
|
25
|
+
export declare function registryFromImage(image: string): string;
|
|
26
|
+
export declare function stripRegistryScheme(server: string): string;
|
|
27
|
+
export declare function normalizeRegistryServer(server: string): string;
|
|
28
|
+
//# sourceMappingURL=docker-image-puller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-image-puller.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/docker-image-puller.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,WAAW,CAAC;AAK/B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,MAAM,EACb,SAAS,SAAO,GACf,OAAO,CAAC,mBAAmB,CAAC,CAe9B;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAmB5B;AAeD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,kBAAkB,GAAG,SAAS,CAkBhC;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,MAAM,CAMR;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG9D"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inspectDockerImage = inspectDockerImage;
|
|
4
|
+
exports.pullDockerImage = pullDockerImage;
|
|
5
|
+
exports.registryAuthFromEnv = registryAuthFromEnv;
|
|
6
|
+
exports.redactDockerAuth = redactDockerAuth;
|
|
7
|
+
exports.registryFromImage = registryFromImage;
|
|
8
|
+
exports.stripRegistryScheme = stripRegistryScheme;
|
|
9
|
+
exports.normalizeRegistryServer = normalizeRegistryServer;
|
|
10
|
+
const docker_client_factory_1 = require("./docker-client.factory");
|
|
11
|
+
const DEFAULT_PULL_TIMEOUT_MS = 300_000;
|
|
12
|
+
async function inspectDockerImage(docker, image, timeoutMs = 8000) {
|
|
13
|
+
if (!docker) {
|
|
14
|
+
return { present: false, imageId: null };
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const imageId = await (0, docker_client_factory_1.withDockerTimeout)(getLocalImageId(docker, image), timeoutMs, 'docker image inspect');
|
|
18
|
+
return { present: imageId !== null, imageId };
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return { present: false, imageId: null };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function pullDockerImage(docker, options) {
|
|
25
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_PULL_TIMEOUT_MS;
|
|
26
|
+
if (!docker) {
|
|
27
|
+
return { ok: false, message: 'Docker is not available.' };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
await pullWithDockerode(docker, options, timeoutMs);
|
|
31
|
+
return {
|
|
32
|
+
ok: true,
|
|
33
|
+
message: `Image ${options.image} pulled successfully.`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const message = redactDockerAuth((0, docker_client_factory_1.safeDockerMessage)(error), options.registryAuth);
|
|
38
|
+
return { ok: false, message };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function getLocalImageId(docker, imageName) {
|
|
42
|
+
try {
|
|
43
|
+
const image = docker.getImage(imageName);
|
|
44
|
+
const info = await image.inspect();
|
|
45
|
+
return info.Id || null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function registryAuthFromEnv(image, env = process.env) {
|
|
52
|
+
const username = env.DOCKER_REGISTRY_USERNAME?.trim();
|
|
53
|
+
const password = env.DOCKER_REGISTRY_PASSWORD?.trim();
|
|
54
|
+
if (!username || !password)
|
|
55
|
+
return undefined;
|
|
56
|
+
const configuredServer = stripRegistryScheme(env.DOCKER_REGISTRY_SERVER?.trim() ?? '');
|
|
57
|
+
const imageRegistry = registryFromImage(image);
|
|
58
|
+
const target = configuredServer || imageRegistry;
|
|
59
|
+
if (!target)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (configuredServer && configuredServer !== imageRegistry)
|
|
62
|
+
return undefined;
|
|
63
|
+
return {
|
|
64
|
+
username,
|
|
65
|
+
password,
|
|
66
|
+
serveraddress: normalizeRegistryServer(target),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function redactDockerAuth(message, auth) {
|
|
70
|
+
if (!auth)
|
|
71
|
+
return message;
|
|
72
|
+
let redacted = message;
|
|
73
|
+
if (auth.password)
|
|
74
|
+
redacted = redacted.split(auth.password).join('***');
|
|
75
|
+
if (auth.username)
|
|
76
|
+
redacted = redacted.split(auth.username).join('***');
|
|
77
|
+
return redacted;
|
|
78
|
+
}
|
|
79
|
+
function registryFromImage(image) {
|
|
80
|
+
const host = image.split('/')[0] ?? '';
|
|
81
|
+
return /[.:]/.test(host) || host === 'localhost' ? host : '';
|
|
82
|
+
}
|
|
83
|
+
function stripRegistryScheme(server) {
|
|
84
|
+
return server.replace(/^https?:\/\//i, '');
|
|
85
|
+
}
|
|
86
|
+
function normalizeRegistryServer(server) {
|
|
87
|
+
if (!server)
|
|
88
|
+
return '';
|
|
89
|
+
return /^https?:\/\//i.test(server) ? server : `https://${server}`;
|
|
90
|
+
}
|
|
91
|
+
function pullWithDockerode(docker, options, timeoutMs) {
|
|
92
|
+
const pull = new Promise((resolve, reject) => {
|
|
93
|
+
docker.pull(options.image, {
|
|
94
|
+
...(options.platform ? { platform: options.platform } : {}),
|
|
95
|
+
...(options.registryAuth ? { authconfig: options.registryAuth } : {}),
|
|
96
|
+
}, (error, stream) => {
|
|
97
|
+
if (error) {
|
|
98
|
+
reject(error);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (!stream) {
|
|
102
|
+
resolve();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
docker.modem.followProgress(stream, (progressError) => {
|
|
106
|
+
if (progressError) {
|
|
107
|
+
reject(progressError);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
resolve();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
return (0, docker_client_factory_1.withDockerTimeout)(pull, timeoutMs, 'docker pull');
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=docker-image-puller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-image-puller.js","sourceRoot":"","sources":["../../../packages/docker/src/docker-image-puller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DockerSandboxProfile, DockerSandboxRunOptions, DockerSandboxValidation } from './docker-sandbox.types';
|
|
2
|
+
export declare function buildDockerSandboxRunCommand(opts: DockerSandboxRunOptions): string[];
|
|
3
|
+
export declare function buildDockerSandboxedExec(opts: DockerSandboxRunOptions): {
|
|
4
|
+
command: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function describeDockerSandboxProfile(opts: DockerSandboxRunOptions): DockerSandboxProfile;
|
|
8
|
+
export declare function validateDockerSandboxProfile(opts: DockerSandboxRunOptions): DockerSandboxValidation;
|
|
9
|
+
//# sourceMappingURL=docker-sandbox-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-sandbox-command.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/docker-sandbox-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,uBAAuB,GAC5B,MAAM,EAAE,CAuBV;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,uBAAuB,GAC5B;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAGrC;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,uBAAuB,GAC5B,oBAAoB,CActB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,uBAAuB,GAC5B,uBAAuB,CAkBzB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDockerSandboxRunCommand = buildDockerSandboxRunCommand;
|
|
4
|
+
exports.buildDockerSandboxedExec = buildDockerSandboxedExec;
|
|
5
|
+
exports.describeDockerSandboxProfile = describeDockerSandboxProfile;
|
|
6
|
+
exports.validateDockerSandboxProfile = validateDockerSandboxProfile;
|
|
7
|
+
function buildDockerSandboxRunCommand(opts) {
|
|
8
|
+
const cmd = ['docker', 'run', '--rm'];
|
|
9
|
+
cmd.push(`--network=${opts.networkMode}`);
|
|
10
|
+
if (opts.readonlyRootfs)
|
|
11
|
+
cmd.push('--read-only');
|
|
12
|
+
const capDrop = opts.capDrop.length > 0 ? opts.capDrop : ['ALL'];
|
|
13
|
+
for (const cap of capDrop)
|
|
14
|
+
cmd.push(`--cap-drop=${cap}`);
|
|
15
|
+
for (const cap of opts.capAdd ?? [])
|
|
16
|
+
cmd.push(`--cap-add=${cap}`);
|
|
17
|
+
if (opts.memoryLimitMb)
|
|
18
|
+
cmd.push(`--memory=${opts.memoryLimitMb}m`);
|
|
19
|
+
if (opts.cpuLimit)
|
|
20
|
+
cmd.push(`--cpus=${opts.cpuLimit}`);
|
|
21
|
+
cmd.push('-v', `${opts.mountPath}:${opts.workdir}`);
|
|
22
|
+
cmd.push('-w', opts.workdir);
|
|
23
|
+
cmd.push('--security-opt=no-new-privileges:true');
|
|
24
|
+
for (const [key, value] of Object.entries(opts.envVars ?? {})) {
|
|
25
|
+
cmd.push('-e', `${key}=${value}`);
|
|
26
|
+
}
|
|
27
|
+
cmd.push(opts.image);
|
|
28
|
+
cmd.push(opts.command);
|
|
29
|
+
if (opts.args?.length)
|
|
30
|
+
cmd.push(...opts.args);
|
|
31
|
+
return cmd;
|
|
32
|
+
}
|
|
33
|
+
function buildDockerSandboxedExec(opts) {
|
|
34
|
+
const fullCmd = buildDockerSandboxRunCommand(opts);
|
|
35
|
+
return { command: fullCmd[0], args: fullCmd.slice(1) };
|
|
36
|
+
}
|
|
37
|
+
function describeDockerSandboxProfile(opts) {
|
|
38
|
+
return {
|
|
39
|
+
network: opts.networkMode === 'none'
|
|
40
|
+
? 'deny'
|
|
41
|
+
: opts.networkMode === 'bridge'
|
|
42
|
+
? 'allowlist'
|
|
43
|
+
: 'open-with-approval',
|
|
44
|
+
writeScope: opts.readonlyRootfs ? 'artifact-only' : 'repo',
|
|
45
|
+
shellEnforcement: opts.capDrop.includes('ALL'),
|
|
46
|
+
secretMode: opts.envVars?.SECRET_REDACT ? 'redacted' : 'blocked',
|
|
47
|
+
memoryLimitMb: opts.memoryLimitMb ?? 512,
|
|
48
|
+
cpuLimit: opts.cpuLimit ?? 1,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function validateDockerSandboxProfile(opts) {
|
|
52
|
+
const warnings = [];
|
|
53
|
+
if (opts.networkMode !== 'none') {
|
|
54
|
+
warnings.push('Network is not fully denied; consider --network=none');
|
|
55
|
+
}
|
|
56
|
+
if (!opts.readonlyRootfs) {
|
|
57
|
+
warnings.push('Root filesystem is writable; consider --read-only');
|
|
58
|
+
}
|
|
59
|
+
if (!opts.capDrop.includes('ALL')) {
|
|
60
|
+
warnings.push('Not all capabilities dropped; add --cap-drop=ALL');
|
|
61
|
+
}
|
|
62
|
+
if ((opts.capAdd ?? []).includes('SYS_ADMIN')) {
|
|
63
|
+
warnings.push('SYS_ADMIN is dangerous in sandbox');
|
|
64
|
+
}
|
|
65
|
+
if (opts.memoryLimitMb && opts.memoryLimitMb > 4096) {
|
|
66
|
+
warnings.push(`Memory limit (${opts.memoryLimitMb}MB) exceeds 4GB`);
|
|
67
|
+
}
|
|
68
|
+
return { valid: warnings.length === 0, warnings };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=docker-sandbox-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-sandbox-command.js","sourceRoot":"","sources":["../../../packages/docker/src/docker-sandbox-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DockerSandboxRunOptions, DockerSandboxRunResult } from './docker-sandbox.types';
|
|
2
|
+
export declare function executeDockerSandbox(opts: DockerSandboxRunOptions, maxOutputBytes?: number): Promise<DockerSandboxRunResult>;
|
|
3
|
+
//# sourceMappingURL=docker-sandbox-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-sandbox-runner.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/docker-sandbox-runner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,wBAAwB,CAAC;AAIhC,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,uBAAuB,EAC7B,cAAc,SAA2B,GACxC,OAAO,CAAC,sBAAsB,CAAC,CAuDjC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeDockerSandbox = executeDockerSandbox;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const docker_sandbox_command_1 = require("./docker-sandbox-command");
|
|
6
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
7
|
+
function executeDockerSandbox(opts, maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES) {
|
|
8
|
+
const profile = (0, docker_sandbox_command_1.describeDockerSandboxProfile)(opts);
|
|
9
|
+
const startedAt = Date.now();
|
|
10
|
+
const redactions = sandboxSecretRedactions(opts);
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const args = (0, docker_sandbox_command_1.buildDockerSandboxRunCommand)(opts).slice(1);
|
|
13
|
+
const child = (0, node_child_process_1.spawn)('docker', args, {
|
|
14
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
15
|
+
timeout: opts.timeoutSec ? opts.timeoutSec * 1000 : undefined,
|
|
16
|
+
});
|
|
17
|
+
let stdout = '';
|
|
18
|
+
let stderr = '';
|
|
19
|
+
child.stdout?.on('data', (chunk) => {
|
|
20
|
+
stdout = appendBounded(stdout, redactSandboxOutput(chunk.toString('utf8'), redactions), maxOutputBytes);
|
|
21
|
+
});
|
|
22
|
+
child.stderr?.on('data', (chunk) => {
|
|
23
|
+
stderr = appendBounded(stderr, redactSandboxOutput(chunk.toString('utf8'), redactions), maxOutputBytes);
|
|
24
|
+
});
|
|
25
|
+
child.on('error', (error) => {
|
|
26
|
+
resolve({
|
|
27
|
+
exitCode: -1,
|
|
28
|
+
stdout,
|
|
29
|
+
stderr: appendBounded(`Spawn error: ${error.message}\n`, stderr, maxOutputBytes),
|
|
30
|
+
durationMs: Date.now() - startedAt,
|
|
31
|
+
sandboxProfile: profile,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
child.on('close', (code) => {
|
|
35
|
+
resolve({
|
|
36
|
+
exitCode: code ?? -1,
|
|
37
|
+
stdout,
|
|
38
|
+
stderr,
|
|
39
|
+
durationMs: Date.now() - startedAt,
|
|
40
|
+
sandboxProfile: profile,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function appendBounded(current, next, maxBytes) {
|
|
46
|
+
const combined = current + next;
|
|
47
|
+
if (combined.length <= maxBytes)
|
|
48
|
+
return combined;
|
|
49
|
+
return combined.slice(0, maxBytes);
|
|
50
|
+
}
|
|
51
|
+
function sandboxSecretRedactions(opts) {
|
|
52
|
+
return Object.entries(opts.envVars ?? {})
|
|
53
|
+
.filter(([key, value]) => isSecretEnvKey(key) && value.length > 0)
|
|
54
|
+
.map(([, value]) => value);
|
|
55
|
+
}
|
|
56
|
+
function isSecretEnvKey(key) {
|
|
57
|
+
return /SECRET|TOKEN|PASSWORD|API[_-]?KEY|PRIVATE[_-]?KEY/i.test(key);
|
|
58
|
+
}
|
|
59
|
+
function redactSandboxOutput(output, redactions) {
|
|
60
|
+
let redacted = output;
|
|
61
|
+
for (const value of redactions) {
|
|
62
|
+
redacted = redacted.split(value).join('***');
|
|
63
|
+
}
|
|
64
|
+
return redacted;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=docker-sandbox-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-sandbox-runner.js","sourceRoot":"","sources":["../../../packages/docker/src/docker-sandbox-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface DockerSandboxRunOptions {
|
|
2
|
+
image: string;
|
|
3
|
+
command: string;
|
|
4
|
+
args?: string[];
|
|
5
|
+
workdir: string;
|
|
6
|
+
mountPath: string;
|
|
7
|
+
networkMode: 'none' | 'host' | 'bridge';
|
|
8
|
+
readonlyRootfs: boolean;
|
|
9
|
+
capDrop: string[];
|
|
10
|
+
capAdd?: string[];
|
|
11
|
+
memoryLimitMb?: number;
|
|
12
|
+
cpuLimit?: number;
|
|
13
|
+
timeoutSec?: number;
|
|
14
|
+
envVars?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
export interface DockerSandboxRunResult {
|
|
17
|
+
exitCode: number;
|
|
18
|
+
stdout: string;
|
|
19
|
+
stderr: string;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
sandboxProfile: DockerSandboxProfile;
|
|
22
|
+
}
|
|
23
|
+
export interface DockerSandboxProfile {
|
|
24
|
+
network: 'deny' | 'allowlist' | 'open-with-approval';
|
|
25
|
+
writeScope: 'workspace' | 'repo' | 'artifact-only';
|
|
26
|
+
shellEnforcement: boolean;
|
|
27
|
+
secretMode: 'redacted' | 'blocked';
|
|
28
|
+
memoryLimitMb: number;
|
|
29
|
+
cpuLimit: number;
|
|
30
|
+
}
|
|
31
|
+
export interface DockerSandboxValidation {
|
|
32
|
+
valid: boolean;
|
|
33
|
+
warnings: string[];
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=docker-sandbox.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-sandbox.types.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/docker-sandbox.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IACxC,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,oBAAoB,CAAC;CACtC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,oBAAoB,CAAC;IACrD,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,eAAe,CAAC;IACnD,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-sandbox.types.js","sourceRoot":"","sources":["../../../packages/docker/src/docker-sandbox.types.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,4 +8,9 @@ export { DockerOrphanCleanerService } from './docker-orphan-cleaner.service';
|
|
|
8
8
|
export type { SandboxOrphanInfo, SandboxCleanupReport, } from './docker-orphan-cleaner.service';
|
|
9
9
|
export * from './types';
|
|
10
10
|
export * from './docker.utils';
|
|
11
|
+
export * from './docker-client.factory';
|
|
12
|
+
export * from './docker-image-puller';
|
|
13
|
+
export * from './docker-sandbox.types';
|
|
14
|
+
export * from './docker-sandbox-command';
|
|
15
|
+
export * from './docker-sandbox-runner';
|
|
11
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,YAAY,EACV,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,YAAY,EACV,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -31,4 +31,9 @@ var docker_orphan_cleaner_service_1 = require("./docker-orphan-cleaner.service")
|
|
|
31
31
|
Object.defineProperty(exports, "DockerOrphanCleanerService", { enumerable: true, get: function () { return docker_orphan_cleaner_service_1.DockerOrphanCleanerService; } });
|
|
32
32
|
__exportStar(require("./types"), exports);
|
|
33
33
|
__exportStar(require("./docker.utils"), exports);
|
|
34
|
+
__exportStar(require("./docker-client.factory"), exports);
|
|
35
|
+
__exportStar(require("./docker-image-puller"), exports);
|
|
36
|
+
__exportStar(require("./docker-sandbox.types"), exports);
|
|
37
|
+
__exportStar(require("./docker-sandbox-command"), exports);
|
|
38
|
+
__exportStar(require("./docker-sandbox-runner"), exports);
|
|
34
39
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../packages/docker/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AACrB,iEAA8D;AAArD,4HAAA,mBAAmB,OAAA;AAC5B,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,mDAAiD;AAAxC,+GAAA,aAAa,OAAA;AACtB,+DAA4D;AAAnD,0HAAA,kBAAkB,OAAA;AAC3B,+DAA4D;AAAnD,0HAAA,kBAAkB,OAAA;AAC3B,iFAA6E;AAApE,2IAAA,0BAA0B,OAAA;AAKnC,0CAAwB;AACxB,iDAA+B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../packages/docker/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AACrB,iEAA8D;AAArD,4HAAA,mBAAmB,OAAA;AAC5B,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,mDAAiD;AAAxC,+GAAA,aAAa,OAAA;AACtB,+DAA4D;AAAnD,0HAAA,kBAAkB,OAAA;AAC3B,+DAA4D;AAAnD,0HAAA,kBAAkB,OAAA;AAC3B,iFAA6E;AAApE,2IAAA,0BAA0B,OAAA;AAKnC,0CAAwB;AACxB,iDAA+B;AAC/B,0DAAwC;AACxC,wDAAsC;AACtC,yDAAuC;AACvC,2DAAyC;AACzC,0DAAwC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared type stubs for the Docker package.
|
|
3
3
|
* Kept local to avoid depending on @repo/contracts (agents-specific).
|
|
4
|
+
*
|
|
5
|
+
* Legacy compatibility note:
|
|
6
|
+
* these Bot/OpenClaw-shaped exports predate the cross-project infra boundary.
|
|
7
|
+
* New shared Docker primitives must not add product-domain types here; prefer
|
|
8
|
+
* product-neutral modules such as docker-client.factory and docker-image-puller.
|
|
4
9
|
*/
|
|
5
10
|
export type BotType = string;
|
|
6
11
|
export type ProviderVendor = string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../packages/docker/src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAG7B,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAM,CAAC;AAEnE,gFAAgF;AAChF,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,CAEhE;AAGD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,YAAY;IAC3B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB"}
|
package/dist/types.js
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Shared type stubs for the Docker package.
|
|
4
4
|
* Kept local to avoid depending on @repo/contracts (agents-specific).
|
|
5
|
+
*
|
|
6
|
+
* Legacy compatibility note:
|
|
7
|
+
* these Bot/OpenClaw-shaped exports predate the cross-project infra boundary.
|
|
8
|
+
* New shared Docker primitives must not add product-domain types here; prefer
|
|
9
|
+
* product-neutral modules such as docker-client.factory and docker-image-puller.
|
|
5
10
|
*/
|
|
6
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
12
|
exports.PROVIDER_CONFIGS = void 0;
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../packages/docker/src/types.ts"],"names":[],"mappings":";AAAA;;;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../packages/docker/src/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAkBH,8DAEC;AALY,QAAA,gBAAgB,GAAmC,EAAE,CAAC;AAEnE,gFAAgF;AAChF,SAAgB,yBAAyB,CAAC,SAAiB;IACzD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dofe/infra-docker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.80",
|
|
4
4
|
"description": "Docker container management — create/start/stop containers, port allocation, orphan cleanup with grace period",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"default": "./dist/index.js"
|
|
11
11
|
},
|
|
12
|
+
"./docker-client.factory": {
|
|
13
|
+
"types": "./dist/docker-client.factory.d.ts",
|
|
14
|
+
"default": "./dist/docker-client.factory.js"
|
|
15
|
+
},
|
|
16
|
+
"./docker-image-puller": {
|
|
17
|
+
"types": "./dist/docker-image-puller.d.ts",
|
|
18
|
+
"default": "./dist/docker-image-puller.js"
|
|
19
|
+
},
|
|
12
20
|
"./docker-image.module": {
|
|
13
21
|
"types": "./dist/docker-image.module.d.ts",
|
|
14
22
|
"default": "./dist/docker-image.module.js"
|
|
@@ -25,6 +33,18 @@
|
|
|
25
33
|
"types": "./dist/docker-runtime.module.d.ts",
|
|
26
34
|
"default": "./dist/docker-runtime.module.js"
|
|
27
35
|
},
|
|
36
|
+
"./docker-sandbox-command": {
|
|
37
|
+
"types": "./dist/docker-sandbox-command.d.ts",
|
|
38
|
+
"default": "./dist/docker-sandbox-command.js"
|
|
39
|
+
},
|
|
40
|
+
"./docker-sandbox-runner": {
|
|
41
|
+
"types": "./dist/docker-sandbox-runner.d.ts",
|
|
42
|
+
"default": "./dist/docker-sandbox-runner.js"
|
|
43
|
+
},
|
|
44
|
+
"./docker-sandbox.types": {
|
|
45
|
+
"types": "./dist/docker-sandbox.types.d.ts",
|
|
46
|
+
"default": "./dist/docker-sandbox.types.js"
|
|
47
|
+
},
|
|
28
48
|
"./docker-stats.service": {
|
|
29
49
|
"types": "./dist/docker-stats.service.d.ts",
|
|
30
50
|
"default": "./dist/docker-stats.service.js"
|
|
@@ -55,8 +75,8 @@
|
|
|
55
75
|
"reflect-metadata": "^0.2.0",
|
|
56
76
|
"rxjs": "^7.8.0",
|
|
57
77
|
"winston": "^3.13.0",
|
|
58
|
-
"@dofe/infra-common": "^0.1.
|
|
59
|
-
"@dofe/infra-utils": "^0.1.
|
|
78
|
+
"@dofe/infra-common": "^0.1.80",
|
|
79
|
+
"@dofe/infra-utils": "^0.1.80"
|
|
60
80
|
},
|
|
61
81
|
"devDependencies": {
|
|
62
82
|
"rimraf": "^6.1.3",
|
|
@@ -67,13 +87,17 @@
|
|
|
67
87
|
"access": "public"
|
|
68
88
|
},
|
|
69
89
|
"files": [
|
|
70
|
-
"dist"
|
|
90
|
+
"dist",
|
|
91
|
+
"test",
|
|
92
|
+
"package.json"
|
|
71
93
|
],
|
|
72
94
|
"license": "UNLICENSED",
|
|
73
95
|
"scripts": {
|
|
74
96
|
"build": "tsc",
|
|
75
97
|
"typecheck": "tsc --noEmit",
|
|
76
98
|
"clean": "rimraf dist",
|
|
99
|
+
"test": "pnpm run test:smoke",
|
|
100
|
+
"test:smoke": "node --test test/*.smoke.mjs",
|
|
77
101
|
"postbuild": "node ../../scripts/generate-exports.mjs"
|
|
78
102
|
}
|
|
79
103
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
probeDockerDaemon,
|
|
5
|
+
} from '../dist/docker-client.factory.js';
|
|
6
|
+
import {
|
|
7
|
+
inspectDockerImage,
|
|
8
|
+
normalizeRegistryServer,
|
|
9
|
+
pullDockerImage,
|
|
10
|
+
redactDockerAuth,
|
|
11
|
+
registryAuthFromEnv,
|
|
12
|
+
registryFromImage,
|
|
13
|
+
stripRegistryScheme,
|
|
14
|
+
} from '../dist/docker-image-puller.js';
|
|
15
|
+
|
|
16
|
+
function fakeDocker(overrides = {}) {
|
|
17
|
+
const calls = {
|
|
18
|
+
pull: [],
|
|
19
|
+
followProgress: 0,
|
|
20
|
+
getImage: [],
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
calls,
|
|
24
|
+
ping: async () => undefined,
|
|
25
|
+
version: async () => ({ ServerVersion: '25.0.3-test-build-extra-long-version' }),
|
|
26
|
+
getImage: (image) => {
|
|
27
|
+
calls.getImage.push(image);
|
|
28
|
+
return {
|
|
29
|
+
inspect: async () => ({ Id: `sha256:${image}` }),
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
pull: (image, options, callback) => {
|
|
33
|
+
calls.pull.push({ image, options });
|
|
34
|
+
callback(null, { fakeStream: true });
|
|
35
|
+
},
|
|
36
|
+
modem: {
|
|
37
|
+
followProgress: (_stream, callback) => {
|
|
38
|
+
calls.followProgress += 1;
|
|
39
|
+
callback(null);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
...overrides,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test('probeDockerDaemon returns safe ready status and trimmed version', async () => {
|
|
47
|
+
const result = await probeDockerDaemon(fakeDocker(), 100);
|
|
48
|
+
assert.equal(result.ok, true);
|
|
49
|
+
assert.equal(result.version, '25.0.3-test-build-extra-long-version');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('probeDockerDaemon redacts failure shape into a bounded message', async () => {
|
|
53
|
+
const result = await probeDockerDaemon(
|
|
54
|
+
fakeDocker({
|
|
55
|
+
ping: async () => {
|
|
56
|
+
throw new Error('daemon unavailable '.repeat(40));
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
100,
|
|
60
|
+
);
|
|
61
|
+
assert.equal(result.ok, false);
|
|
62
|
+
assert.ok(result.message.length <= 240);
|
|
63
|
+
assert.match(result.message, /daemon unavailable/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('inspectDockerImage reports local image presence without a daemon dependency', async () => {
|
|
67
|
+
const result = await inspectDockerImage(fakeDocker(), 'registry.local/app:1', 100);
|
|
68
|
+
assert.deepEqual(result, {
|
|
69
|
+
present: true,
|
|
70
|
+
imageId: 'sha256:registry.local/app:1',
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('registry helpers only produce auth for matching configured registries', () => {
|
|
75
|
+
assert.equal(registryFromImage('registry.local:5000/team/app:1'), 'registry.local:5000');
|
|
76
|
+
assert.equal(registryFromImage('library/ubuntu:latest'), '');
|
|
77
|
+
assert.equal(stripRegistryScheme('https://registry.local'), 'registry.local');
|
|
78
|
+
assert.equal(normalizeRegistryServer('registry.local'), 'https://registry.local');
|
|
79
|
+
|
|
80
|
+
assert.deepEqual(
|
|
81
|
+
registryAuthFromEnv('registry.local/team/app:1', {
|
|
82
|
+
DOCKER_REGISTRY_USERNAME: 'alice',
|
|
83
|
+
DOCKER_REGISTRY_PASSWORD: 'secret',
|
|
84
|
+
DOCKER_REGISTRY_SERVER: 'https://registry.local',
|
|
85
|
+
}),
|
|
86
|
+
{
|
|
87
|
+
username: 'alice',
|
|
88
|
+
password: 'secret',
|
|
89
|
+
serveraddress: 'https://registry.local',
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
assert.equal(
|
|
94
|
+
registryAuthFromEnv('other.local/team/app:1', {
|
|
95
|
+
DOCKER_REGISTRY_USERNAME: 'alice',
|
|
96
|
+
DOCKER_REGISTRY_PASSWORD: 'secret',
|
|
97
|
+
DOCKER_REGISTRY_SERVER: 'registry.local',
|
|
98
|
+
}),
|
|
99
|
+
undefined,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('pullDockerImage passes platform and matching authconfig to Dockerode', async () => {
|
|
104
|
+
const docker = fakeDocker();
|
|
105
|
+
const registryAuth = {
|
|
106
|
+
username: 'alice',
|
|
107
|
+
password: 'secret',
|
|
108
|
+
serveraddress: 'https://registry.local',
|
|
109
|
+
};
|
|
110
|
+
const result = await pullDockerImage(docker, {
|
|
111
|
+
image: 'registry.local/team/app:1',
|
|
112
|
+
platform: 'linux/amd64',
|
|
113
|
+
registryAuth,
|
|
114
|
+
timeoutMs: 100,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
assert.equal(result.ok, true);
|
|
118
|
+
assert.equal(docker.calls.followProgress, 1);
|
|
119
|
+
assert.deepEqual(docker.calls.pull, [
|
|
120
|
+
{
|
|
121
|
+
image: 'registry.local/team/app:1',
|
|
122
|
+
options: {
|
|
123
|
+
platform: 'linux/amd64',
|
|
124
|
+
authconfig: registryAuth,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('pullDockerImage redacts auth details from Docker failures', async () => {
|
|
131
|
+
const registryAuth = {
|
|
132
|
+
username: 'alice',
|
|
133
|
+
password: 'secret',
|
|
134
|
+
serveraddress: 'https://registry.local',
|
|
135
|
+
};
|
|
136
|
+
const docker = fakeDocker({
|
|
137
|
+
pull: (_image, _options, callback) => {
|
|
138
|
+
callback(new Error('login failed for alice with password secret'), null);
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const result = await pullDockerImage(docker, {
|
|
143
|
+
image: 'registry.local/team/app:1',
|
|
144
|
+
registryAuth,
|
|
145
|
+
timeoutMs: 100,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
assert.equal(result.ok, false);
|
|
149
|
+
assert.equal(result.message.includes('alice'), false);
|
|
150
|
+
assert.equal(result.message.includes('secret'), false);
|
|
151
|
+
assert.match(result.message, /\*\*\*/);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('redactDockerAuth leaves unauthenticated messages untouched', () => {
|
|
155
|
+
assert.equal(redactDockerAuth('plain docker error', undefined), 'plain docker error');
|
|
156
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { chmod, mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
buildDockerSandboxRunCommand,
|
|
8
|
+
buildDockerSandboxedExec,
|
|
9
|
+
describeDockerSandboxProfile,
|
|
10
|
+
validateDockerSandboxProfile,
|
|
11
|
+
} from '../dist/docker-sandbox-command.js';
|
|
12
|
+
import { executeDockerSandbox as runSandbox } from '../dist/docker-sandbox-runner.js';
|
|
13
|
+
|
|
14
|
+
const baseOptions = {
|
|
15
|
+
image: 'example/sandbox:latest',
|
|
16
|
+
command: 'node',
|
|
17
|
+
args: ['script.js'],
|
|
18
|
+
workdir: '/workspace',
|
|
19
|
+
mountPath: '/host/repo',
|
|
20
|
+
networkMode: 'none',
|
|
21
|
+
readonlyRootfs: true,
|
|
22
|
+
capDrop: ['ALL'],
|
|
23
|
+
memoryLimitMb: 256,
|
|
24
|
+
cpuLimit: 0.5,
|
|
25
|
+
timeoutSec: 2,
|
|
26
|
+
envVars: {
|
|
27
|
+
API_TOKEN: 'secret-token',
|
|
28
|
+
PUBLIC_FLAG: 'visible',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
test('buildDockerSandboxRunCommand locks down the default sandbox shape', () => {
|
|
33
|
+
const cmd = buildDockerSandboxRunCommand(baseOptions);
|
|
34
|
+
|
|
35
|
+
assert.deepEqual(cmd.slice(0, 3), ['docker', 'run', '--rm']);
|
|
36
|
+
assert.ok(cmd.includes('--network=none'));
|
|
37
|
+
assert.ok(cmd.includes('--read-only'));
|
|
38
|
+
assert.ok(cmd.includes('--cap-drop=ALL'));
|
|
39
|
+
assert.ok(cmd.includes('--security-opt=no-new-privileges:true'));
|
|
40
|
+
assert.ok(cmd.includes('--memory=256m'));
|
|
41
|
+
assert.ok(cmd.includes('--cpus=0.5'));
|
|
42
|
+
assert.ok(cmd.includes('/host/repo:/workspace'));
|
|
43
|
+
assert.ok(cmd.includes('-w'));
|
|
44
|
+
assert.ok(cmd.includes('/workspace'));
|
|
45
|
+
assert.ok(cmd.includes('-e'));
|
|
46
|
+
assert.ok(cmd.includes('API_TOKEN=secret-token'));
|
|
47
|
+
assert.deepEqual(cmd.slice(-3), ['example/sandbox:latest', 'node', 'script.js']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('buildDockerSandboxedExec separates docker command from argv', () => {
|
|
51
|
+
const exec = buildDockerSandboxedExec(baseOptions);
|
|
52
|
+
assert.equal(exec.command, 'docker');
|
|
53
|
+
assert.equal(exec.args[0], 'run');
|
|
54
|
+
assert.equal(exec.args.includes('docker'), false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('describe and validate sandbox profiles expose security posture', () => {
|
|
58
|
+
assert.deepEqual(describeDockerSandboxProfile(baseOptions), {
|
|
59
|
+
network: 'deny',
|
|
60
|
+
writeScope: 'artifact-only',
|
|
61
|
+
shellEnforcement: true,
|
|
62
|
+
secretMode: 'blocked',
|
|
63
|
+
memoryLimitMb: 256,
|
|
64
|
+
cpuLimit: 0.5,
|
|
65
|
+
});
|
|
66
|
+
assert.deepEqual(validateDockerSandboxProfile(baseOptions), {
|
|
67
|
+
valid: true,
|
|
68
|
+
warnings: [],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const unsafe = validateDockerSandboxProfile({
|
|
72
|
+
...baseOptions,
|
|
73
|
+
networkMode: 'bridge',
|
|
74
|
+
readonlyRootfs: false,
|
|
75
|
+
capDrop: [],
|
|
76
|
+
capAdd: ['SYS_ADMIN'],
|
|
77
|
+
memoryLimitMb: 8192,
|
|
78
|
+
});
|
|
79
|
+
assert.equal(unsafe.valid, false);
|
|
80
|
+
assert.equal(unsafe.warnings.length, 5);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('executeDockerSandbox captures exit code, truncates output, and redacts secret env values', async () => {
|
|
84
|
+
const oldPath = process.env.PATH;
|
|
85
|
+
const binDir = path.join(os.tmpdir(), `infra-docker-fake-${Date.now()}`);
|
|
86
|
+
await mkdir(binDir, { recursive: true });
|
|
87
|
+
const dockerPath = path.join(binDir, 'docker');
|
|
88
|
+
await writeFile(
|
|
89
|
+
dockerPath,
|
|
90
|
+
[
|
|
91
|
+
'#!/bin/sh',
|
|
92
|
+
'printf "stdout:%s:%s" "secret-token" "$*"',
|
|
93
|
+
'printf "stderr:%s:%s" "secret-token" "$*" 1>&2',
|
|
94
|
+
'exit 7',
|
|
95
|
+
'',
|
|
96
|
+
].join('\n'),
|
|
97
|
+
);
|
|
98
|
+
await chmod(dockerPath, 0o755);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
process.env.PATH = `${binDir}:${oldPath ?? ''}`;
|
|
102
|
+
const result = await runSandbox(baseOptions, 80);
|
|
103
|
+
assert.equal(result.exitCode, 7);
|
|
104
|
+
assert.equal(result.stdout.includes('secret-token'), false);
|
|
105
|
+
assert.equal(result.stderr.includes('secret-token'), false);
|
|
106
|
+
assert.match(result.stdout, /\*\*\*/);
|
|
107
|
+
assert.match(result.stderr, /\*\*\*/);
|
|
108
|
+
assert.ok(result.stdout.length <= 80);
|
|
109
|
+
assert.ok(result.stderr.length <= 80);
|
|
110
|
+
assert.equal(result.sandboxProfile.network, 'deny');
|
|
111
|
+
} finally {
|
|
112
|
+
process.env.PATH = oldPath;
|
|
113
|
+
}
|
|
114
|
+
});
|