@geminixiang/mikan 1.3.0 → 1.4.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 +48 -0
- package/README.md +7 -8
- package/dist/adapters/commands/login.js +1 -1
- package/dist/adapters/commands/login.js.map +1 -1
- package/dist/adapters/commands/sandbox.js +2 -2
- package/dist/adapters/commands/sandbox.js.map +1 -1
- package/dist/cli/boot.js +1 -1
- package/dist/cli/boot.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +3 -2
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/onboard.d.ts.map +1 -1
- package/dist/cli/onboard.js +15 -13
- package/dist/cli/onboard.js.map +1 -1
- package/dist/cli/pm2-ecosystem.d.ts +1 -2
- package/dist/cli/pm2-ecosystem.d.ts.map +1 -1
- package/dist/cli/pm2-ecosystem.js +2 -3
- package/dist/cli/pm2-ecosystem.js.map +1 -1
- package/dist/env-manifest.d.ts.map +1 -1
- package/dist/env-manifest.js +11 -0
- package/dist/env-manifest.js.map +1 -1
- package/dist/harness/execution-resolver.d.ts +3 -2
- package/dist/harness/execution-resolver.d.ts.map +1 -1
- package/dist/harness/execution-resolver.js +12 -10
- package/dist/harness/execution-resolver.js.map +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +3 -2
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +37 -62
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session.js +1 -1
- package/dist/harness/session.js.map +1 -1
- package/dist/harness/tools/attach.d.ts +3 -2
- package/dist/harness/tools/attach.d.ts.map +1 -1
- package/dist/harness/tools/attach.js +9 -2
- package/dist/harness/tools/attach.js.map +1 -1
- package/dist/harness/tools/index.d.ts +3 -2
- package/dist/harness/tools/index.d.ts.map +1 -1
- package/dist/harness/tools/index.js +6 -4
- package/dist/harness/tools/index.js.map +1 -1
- package/dist/harness/tools/jev-browser.d.ts +2 -2
- package/dist/harness/tools/jev-browser.d.ts.map +1 -1
- package/dist/harness/tools/jev-browser.js +41 -33
- package/dist/harness/tools/jev-browser.js.map +1 -1
- package/dist/harness/tools/sandbox.js +2 -2
- package/dist/harness/tools/sandbox.js.map +1 -1
- package/dist/harness/tools/types.d.ts +4 -0
- package/dist/harness/tools/types.d.ts.map +1 -1
- package/dist/harness/tools/types.js.map +1 -1
- package/dist/harness/tools/web-fetch.d.ts +6 -0
- package/dist/harness/tools/web-fetch.d.ts.map +1 -0
- package/dist/harness/tools/web-fetch.js +136 -0
- package/dist/harness/tools/web-fetch.js.map +1 -0
- package/dist/harness/types.d.ts +4 -3
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/main.js +10 -24
- package/dist/main.js.map +1 -1
- package/dist/migrations/index.js +2 -2
- package/dist/migrations/index.js.map +1 -1
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +19 -0
- package/dist/package-version.js.map +1 -0
- package/dist/sandbox/container.d.ts +3 -17
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +108 -129
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/engine.d.ts +4 -0
- package/dist/sandbox/engine.d.ts.map +1 -0
- package/dist/sandbox/engine.js +30 -0
- package/dist/sandbox/engine.js.map +1 -0
- package/dist/sandbox/host.d.ts +1 -10
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +2 -79
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/provisioner.d.ts +16 -1
- package/dist/sandbox/provisioner.d.ts.map +1 -1
- package/dist/sandbox/provisioner.js +173 -111
- package/dist/sandbox/provisioner.js.map +1 -1
- package/dist/sandbox/published-image.d.ts +3 -0
- package/dist/sandbox/published-image.d.ts.map +1 -0
- package/dist/sandbox/published-image.js +9 -0
- package/dist/sandbox/published-image.js.map +1 -0
- package/dist/sandbox/registry.d.ts +5 -3
- package/dist/sandbox/registry.d.ts.map +1 -1
- package/dist/sandbox/registry.js +36 -27
- package/dist/sandbox/registry.js.map +1 -1
- package/dist/sandbox/types.d.ts +14 -22
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sandbox/utils.d.ts +0 -15
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sandbox/utils.js +0 -72
- package/dist/sandbox/utils.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vault/index.js +1 -1
- package/dist/vault/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/harness/execution-env.d.ts +0 -4
- package/dist/harness/execution-env.d.ts.map +0 -1
- package/dist/harness/execution-env.js +0 -322
- package/dist/harness/execution-env.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { ExecutionError, FileError, err } from "@earendil-works/pi-durable/env";
|
|
6
|
+
import { Connection, RemoteExecutionEnv, packagedDaemon } from "@earendil-works/pi-env";
|
|
7
|
+
import { SandboxError, execSimple } from "./utils.js";
|
|
8
|
+
import { containerEngine } from "./engine.js";
|
|
8
9
|
import { errorMessage } from "../unknown-values.js";
|
|
9
10
|
import * as log from "../log.js";
|
|
10
|
-
const
|
|
11
|
-
const PRIVATE_FILE_MODE = 0o600;
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
12
|
function parseContainerSandboxArg(value) {
|
|
13
13
|
if (!value.startsWith("container:")) {
|
|
14
14
|
return undefined;
|
|
@@ -20,14 +20,15 @@ function parseContainerSandboxArg(value) {
|
|
|
20
20
|
return { type: "container", container };
|
|
21
21
|
}
|
|
22
22
|
async function validateContainerSandbox(config) {
|
|
23
|
+
const engine = containerEngine();
|
|
23
24
|
try {
|
|
24
|
-
await execSimple(
|
|
25
|
+
await execSimple(engine, ["--version"]);
|
|
25
26
|
}
|
|
26
27
|
catch {
|
|
27
|
-
throw new SandboxError(
|
|
28
|
+
throw new SandboxError(`Error: no container engine found; install nerdctl, Podman, or Docker (tried '${engine}')`);
|
|
28
29
|
}
|
|
29
30
|
try {
|
|
30
|
-
const result = await execSimple(
|
|
31
|
+
const result = await execSimple(engine, [
|
|
31
32
|
"inspect",
|
|
32
33
|
"-f",
|
|
33
34
|
"{{.State.Running}}",
|
|
@@ -35,7 +36,7 @@ async function validateContainerSandbox(config) {
|
|
|
35
36
|
]);
|
|
36
37
|
if (result.trim() !== "true") {
|
|
37
38
|
throw new SandboxError(`Error: Container '${config.container}' is not running.`, [
|
|
38
|
-
`Start it with:
|
|
39
|
+
`Start it with: ${engine} start ${config.container}`,
|
|
39
40
|
]);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -44,49 +45,10 @@ async function validateContainerSandbox(config) {
|
|
|
44
45
|
throw error;
|
|
45
46
|
}
|
|
46
47
|
throw new SandboxError(`Error: Container '${config.container}' does not exist.`, [
|
|
47
|
-
`Create it with:
|
|
48
|
+
`Create it with: ${engine} run -d --name ${config.container} -v <workspace>:/workspace docker.io/library/alpine:latest sleep infinity`,
|
|
48
49
|
]);
|
|
49
50
|
}
|
|
50
|
-
console.log(` Container '${config.container}' is running.`);
|
|
51
|
-
}
|
|
52
|
-
const GROUP_FILE_PREFIX = "/tmp/mikan-exec-";
|
|
53
|
-
const RUN_IN_PROCESS_GROUP = [
|
|
54
|
-
"if command -v setsid >/dev/null 2>&1; then",
|
|
55
|
-
`setsid sh -c 'echo "$$ $(cut -d" " -f22 /proc/$$/stat)" > "$1"; exec sh -c "$2"' _ "$1" "$2" & wait $!;`,
|
|
56
|
-
'status=$?; rm -f "$1"; exit $status;',
|
|
57
|
-
'fi; exec sh -c "$2"',
|
|
58
|
-
].join(" ");
|
|
59
|
-
const STOP_RECORDED_GROUP = [
|
|
60
|
-
'stop_group() { read group start < "$1" 2>/dev/null || return 0; rm -f "$1";',
|
|
61
|
-
'[ "$group" -gt 1 ] 2>/dev/null || return 0;',
|
|
62
|
-
'[ "$(cut -d" " -f22 /proc/$group/stat 2>/dev/null)" = "$start" ] || return 0;',
|
|
63
|
-
'kill -s KILL -- -"$group" 2>/dev/null || kill -s KILL -"$group" 2>/dev/null; return 0; };',
|
|
64
|
-
].join(" ");
|
|
65
|
-
const KILL_PROCESS_GROUP = [
|
|
66
|
-
STOP_RECORDED_GROUP,
|
|
67
|
-
'i=0; while [ ! -s "$1" ] && [ $i -lt 10 ]; do sleep 0.1; i=$((i+1)); done;',
|
|
68
|
-
'stop_group "$1"; exit 0',
|
|
69
|
-
].join(" ");
|
|
70
|
-
const SWEEP_PROCESS_GROUPS = [
|
|
71
|
-
STOP_RECORDED_GROUP,
|
|
72
|
-
`for file in ${GROUP_FILE_PREFIX}*; do [ -f "$file" ] && stop_group "$file"; done; exit 0`,
|
|
73
|
-
].join(" ");
|
|
74
|
-
export async function sweepOrphanedCommands(containerName, execFile) {
|
|
75
|
-
try {
|
|
76
|
-
await execFile("docker", ["exec", containerName, "sh", "-c", SWEEP_PROCESS_GROUPS]);
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
log.logWarning(`Could not end leftover commands in ${containerName}`, errorMessage(error));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function buildContainerExecCommand(container, command, groupFile, envFilePath, cwd) {
|
|
83
|
-
const envPart = envFilePath ? `--env-file ${shellEscape(envFilePath)} ` : "";
|
|
84
|
-
const workdir = cwd === undefined ? GUEST_WORKSPACE_ROOT : shellEscape(cwd);
|
|
85
|
-
const script = `${shellEscape(RUN_IN_PROCESS_GROUP)} _ ${shellEscape(groupFile)} ${shellEscape(command)}`;
|
|
86
|
-
return `docker exec ${envPart}-w ${workdir} ${container} sh -c ${script}`;
|
|
87
|
-
}
|
|
88
|
-
function buildKillProcessGroupCommand(container, groupFile) {
|
|
89
|
-
return `docker exec ${container} sh -c ${shellEscape(KILL_PROCESS_GROUP)} _ ${shellEscape(groupFile)}`;
|
|
51
|
+
console.log(` Container '${config.container}' is running (engine: ${engine}).`);
|
|
90
52
|
}
|
|
91
53
|
const GITHUB_CREDENTIAL_KEY = "credential.https://github.com.helper";
|
|
92
54
|
function withGitHubCredentialHelper(env) {
|
|
@@ -105,62 +67,95 @@ function withGitHubCredentialHelper(env) {
|
|
|
105
67
|
function hasGitHubToken(env) {
|
|
106
68
|
return Boolean(env.GH_TOKEN || env.GITHUB_TOKEN);
|
|
107
69
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
70
|
+
const DAEMON_ARCH = {
|
|
71
|
+
x86_64: "x64",
|
|
72
|
+
amd64: "x64",
|
|
73
|
+
aarch64: "arm64",
|
|
74
|
+
arm64: "arm64",
|
|
75
|
+
};
|
|
76
|
+
const INSTALL_DAEMON = '[ -x "$1" ] && exit 0; cat > "$1.$$" && chmod 755 "$1.$$" && mv -f "$1.$$" "$1"';
|
|
77
|
+
async function deployDaemon(engine, container) {
|
|
78
|
+
const { stdout } = await execFileAsync(engine, ["exec", container, "uname", "-m"]);
|
|
79
|
+
const arch = DAEMON_ARCH[stdout.trim()];
|
|
80
|
+
if (!arch)
|
|
81
|
+
throw new Error(`No pi-env daemon for ${stdout.trim()} in container ${container}`);
|
|
82
|
+
const binary = readFileSync(packagedDaemon({ platform: "linux", arch }));
|
|
83
|
+
const digest = createHash("sha256").update(binary).digest("hex").slice(0, 16);
|
|
84
|
+
const path = `/tmp/mikan-pi-env-${digest}`;
|
|
85
|
+
await new Promise((resolve, reject) => {
|
|
86
|
+
const child = execFile(engine, ["exec", "-i", container, "sh", "-c", INSTALL_DAEMON, "sh", path], (error) => (error ? reject(error) : resolve()));
|
|
87
|
+
child.stdin?.on("error", () => { });
|
|
88
|
+
child.stdin?.end(binary);
|
|
89
|
+
});
|
|
90
|
+
return path;
|
|
91
|
+
}
|
|
92
|
+
const connections = new Map();
|
|
93
|
+
function containerConnection(configuredEngine, container, ensureReady) {
|
|
94
|
+
const key = `${configuredEngine ?? ""}\0${container}`;
|
|
95
|
+
const existing = connections.get(key);
|
|
96
|
+
if (existing) {
|
|
97
|
+
existing.ensureReady = ensureReady;
|
|
98
|
+
return existing.connection;
|
|
99
|
+
}
|
|
100
|
+
const entry = {
|
|
101
|
+
ensureReady,
|
|
102
|
+
connection: new Connection({
|
|
103
|
+
command: async () => {
|
|
104
|
+
await entry.ensureReady();
|
|
105
|
+
const engine = configuredEngine ?? containerEngine();
|
|
106
|
+
return [engine, "exec", "-i", container, await deployDaemon(engine, container)];
|
|
107
|
+
},
|
|
108
|
+
onLog: (text) => log.logWarning(`Sandbox ${container}`, text.trim()),
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
111
|
+
connections.set(key, entry);
|
|
112
|
+
return entry.connection;
|
|
113
|
+
}
|
|
114
|
+
const PATH_ONLY = new Set(["absolutePath", "joinPath"]);
|
|
115
|
+
function readiedEnv(env, ensureReady, markUsed) {
|
|
116
|
+
let ready;
|
|
117
|
+
const before = async () => {
|
|
118
|
+
ready ??= ensureReady().catch((error) => {
|
|
119
|
+
ready = undefined;
|
|
132
120
|
throw error;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
121
|
+
});
|
|
122
|
+
await ready;
|
|
123
|
+
markUsed();
|
|
124
|
+
};
|
|
125
|
+
return new Proxy(env, {
|
|
126
|
+
get(target, property, receiver) {
|
|
127
|
+
const value = Reflect.get(target, property, receiver);
|
|
128
|
+
if (typeof value !== "function")
|
|
129
|
+
return value;
|
|
130
|
+
if (PATH_ONLY.has(property))
|
|
131
|
+
return value.bind(target);
|
|
132
|
+
return async (...args) => {
|
|
133
|
+
try {
|
|
134
|
+
await before();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const message = errorMessage(error);
|
|
138
|
+
if (property === "cleanup")
|
|
139
|
+
return undefined;
|
|
140
|
+
return err(property === "exec"
|
|
141
|
+
? new ExecutionError("unknown", message)
|
|
142
|
+
: new FileError("unknown", message));
|
|
143
|
+
}
|
|
144
|
+
return Reflect.apply(value, target, args);
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
export function createContainerExecutionEnv(options) {
|
|
150
|
+
const ensureReady = options.ensureReady ??
|
|
151
|
+
(() => ensureContainerRunning(options.container, options.engine ?? containerEngine()));
|
|
152
|
+
const env = new RemoteExecutionEnv({
|
|
153
|
+
connection: containerConnection(options.engine, options.container, ensureReady),
|
|
154
|
+
id: `container:${options.container}`,
|
|
155
|
+
cwd: options.cwd,
|
|
156
|
+
shellEnv: withGitHubCredentialHelper(options.env),
|
|
157
|
+
});
|
|
158
|
+
return readiedEnv(env, ensureReady, options.markUsed ?? (() => { }));
|
|
164
159
|
}
|
|
165
160
|
export const containerSandboxAdapter = {
|
|
166
161
|
type: "container",
|
|
@@ -168,15 +163,15 @@ export const containerSandboxAdapter = {
|
|
|
168
163
|
workspace: { managedProjection: false },
|
|
169
164
|
parse: parseContainerSandboxArg,
|
|
170
165
|
validate: validateContainerSandbox,
|
|
171
|
-
|
|
166
|
+
createEnv: (config, options) => createContainerExecutionEnv({ ...options, container: config.container }),
|
|
172
167
|
};
|
|
173
|
-
async function ensureContainerRunning(container) {
|
|
168
|
+
async function ensureContainerRunning(container, engine) {
|
|
174
169
|
try {
|
|
175
|
-
const running = await execSimple(
|
|
170
|
+
const running = await execSimple(engine, ["inspect", "-f", "{{.State.Running}}", container]);
|
|
176
171
|
if (running.trim() === "true") {
|
|
177
172
|
return;
|
|
178
173
|
}
|
|
179
|
-
await execSimple(
|
|
174
|
+
await execSimple(engine, ["start", container]);
|
|
180
175
|
}
|
|
181
176
|
catch (error) {
|
|
182
177
|
const details = errorMessage(error);
|
|
@@ -184,20 +179,4 @@ async function ensureContainerRunning(container) {
|
|
|
184
179
|
`Expected a pre-existing container or image provisioning to keep it running.\n${details}`.trim(), { cause: error });
|
|
185
180
|
}
|
|
186
181
|
}
|
|
187
|
-
function createSecureEnvFile(env) {
|
|
188
|
-
const tempDir = mkdtempSync(join(tmpdir(), "mikan-docker-env-"));
|
|
189
|
-
chmodSync(tempDir, PRIVATE_DIR_MODE);
|
|
190
|
-
const envFilePath = join(tempDir, "env.list");
|
|
191
|
-
const content = Object.entries(env)
|
|
192
|
-
.map(([key, value]) => `${key}=${value.replace(/\r?\n/g, "")}`)
|
|
193
|
-
.join("\n") + "\n";
|
|
194
|
-
writeFileSync(envFilePath, content, { encoding: "utf-8", mode: PRIVATE_FILE_MODE });
|
|
195
|
-
chmodSync(envFilePath, PRIVATE_FILE_MODE);
|
|
196
|
-
return {
|
|
197
|
-
envFilePath,
|
|
198
|
-
cleanup: () => {
|
|
199
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
182
|
//# sourceMappingURL=container.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { chmodSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n ContainerSandboxConfig,\n DockerExecFile,\n ExecOptions,\n ExecResult,\n Executor,\n RuntimePathContext,\n SandboxAdapter,\n} from \"./types.js\";\nimport {\n SandboxError,\n createMountedRuntimePathContext,\n execReadFile,\n execReadFileBase64,\n execSimple,\n execWriteFile,\n shellEscape,\n} from \"./utils.js\";\nimport { HostExecutor } from \"./host.js\";\nimport { GUEST_WORKSPACE_ROOT } from \"./layout.js\";\nimport { errorMessage } from \"../unknown-values.js\";\nimport * as log from \"../log.js\";\n\nconst PRIVATE_DIR_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction parseContainerSandboxArg(value: string): ContainerSandboxConfig | undefined {\n if (!value.startsWith(\"container:\")) {\n return undefined;\n }\n\n const container = value.slice(\"container:\".length);\n if (!container) {\n throw new SandboxError(\n \"Error: container sandbox requires container name (e.g., container:mikan-sandbox)\",\n );\n }\n return { type: \"container\", container };\n}\n\nasync function validateContainerSandbox(config: ContainerSandboxConfig): Promise<void> {\n try {\n await execSimple(\"docker\", [\"--version\"]);\n } catch {\n throw new SandboxError(\"Error: Docker is not installed or not in PATH\");\n }\n\n try {\n const result = await execSimple(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n config.container,\n ]);\n if (result.trim() !== \"true\") {\n throw new SandboxError(`Error: Container '${config.container}' is not running.`, [\n `Start it with: docker start ${config.container}`,\n ]);\n }\n } catch (error) {\n if (error instanceof SandboxError) {\n throw error;\n }\n throw new SandboxError(`Error: Container '${config.container}' does not exist.`, [\n `Create it with: docker run -d --name ${config.container} -v <workspace>:/workspace alpine:latest sleep infinity`,\n ]);\n }\n\n console.log(` Container '${config.container}' is running.`);\n}\n\nconst GROUP_FILE_PREFIX = \"/tmp/mikan-exec-\";\n\nconst RUN_IN_PROCESS_GROUP = [\n \"if command -v setsid >/dev/null 2>&1; then\",\n `setsid sh -c 'echo \"$$ $(cut -d\" \" -f22 /proc/$$/stat)\" > \"$1\"; exec sh -c \"$2\"' _ \"$1\" \"$2\" & wait $!;`,\n 'status=$?; rm -f \"$1\"; exit $status;',\n 'fi; exec sh -c \"$2\"',\n].join(\" \");\n\nconst STOP_RECORDED_GROUP = [\n 'stop_group() { read group start < \"$1\" 2>/dev/null || return 0; rm -f \"$1\";',\n '[ \"$group\" -gt 1 ] 2>/dev/null || return 0;',\n '[ \"$(cut -d\" \" -f22 /proc/$group/stat 2>/dev/null)\" = \"$start\" ] || return 0;',\n 'kill -s KILL -- -\"$group\" 2>/dev/null || kill -s KILL -\"$group\" 2>/dev/null; return 0; };',\n].join(\" \");\n\nconst KILL_PROCESS_GROUP = [\n STOP_RECORDED_GROUP,\n 'i=0; while [ ! -s \"$1\" ] && [ $i -lt 10 ]; do sleep 0.1; i=$((i+1)); done;',\n 'stop_group \"$1\"; exit 0',\n].join(\" \");\n\nconst SWEEP_PROCESS_GROUPS = [\n STOP_RECORDED_GROUP,\n `for file in ${GROUP_FILE_PREFIX}*; do [ -f \"$file\" ] && stop_group \"$file\"; done; exit 0`,\n].join(\" \");\n\nexport async function sweepOrphanedCommands(\n containerName: string,\n execFile: DockerExecFile,\n): Promise<void> {\n try {\n await execFile(\"docker\", [\"exec\", containerName, \"sh\", \"-c\", SWEEP_PROCESS_GROUPS]);\n } catch (error) {\n log.logWarning(`Could not end leftover commands in ${containerName}`, errorMessage(error));\n }\n}\n\nfunction buildContainerExecCommand(\n container: string,\n command: string,\n groupFile: string,\n envFilePath?: string,\n cwd?: string,\n): string {\n const envPart = envFilePath ? `--env-file ${shellEscape(envFilePath)} ` : \"\";\n const workdir = cwd === undefined ? GUEST_WORKSPACE_ROOT : shellEscape(cwd);\n const script = `${shellEscape(RUN_IN_PROCESS_GROUP)} _ ${shellEscape(groupFile)} ${shellEscape(command)}`;\n return `docker exec ${envPart}-w ${workdir} ${container} sh -c ${script}`;\n}\n\nfunction buildKillProcessGroupCommand(container: string, groupFile: string): string {\n return `docker exec ${container} sh -c ${shellEscape(KILL_PROCESS_GROUP)} _ ${shellEscape(groupFile)}`;\n}\n\nconst GITHUB_CREDENTIAL_KEY = \"credential.https://github.com.helper\";\n\nfunction withGitHubCredentialHelper(\n env?: Record<string, string>,\n): Record<string, string> | undefined {\n if (!env || !hasGitHubToken(env) || env.GIT_CONFIG_COUNT !== undefined) {\n return env;\n }\n return {\n ...env,\n GIT_CONFIG_COUNT: \"2\",\n GIT_CONFIG_KEY_0: GITHUB_CREDENTIAL_KEY,\n GIT_CONFIG_VALUE_0: \"\",\n GIT_CONFIG_KEY_1: GITHUB_CREDENTIAL_KEY,\n GIT_CONFIG_VALUE_1: \"!gh auth git-credential\",\n };\n}\n\nfunction hasGitHubToken(env: Record<string, string>): boolean {\n return Boolean(env.GH_TOKEN || env.GITHUB_TOKEN);\n}\n\nexport class ContainerExecutor implements Executor {\n private readonly hostExecutor = new HostExecutor();\n\n constructor(\n private container: string,\n private env?: Record<string, string>,\n private ensureReady?: () => Promise<void>,\n ) {}\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n if (this.ensureReady) {\n await this.ensureReady();\n } else {\n await ensureContainerRunning(this.container);\n }\n\n const env = withGitHubCredentialHelper(this.env);\n const temp = env ? createSecureEnvFile(env) : undefined;\n const groupFile = `${GROUP_FILE_PREFIX}${randomUUID()}`;\n try {\n const dockerCmd = buildContainerExecCommand(\n this.container,\n command,\n groupFile,\n temp?.envFilePath,\n options?.cwd,\n );\n const { cwd: _cwd, ...hostOptions } = options ?? {};\n return await this.hostExecutor.exec(dockerCmd, options ? hostOptions : undefined);\n } catch (error) {\n await this.killProcessGroup(groupFile);\n throw error;\n } finally {\n temp?.cleanup();\n }\n }\n\n private async killProcessGroup(groupFile: string): Promise<void> {\n try {\n await this.hostExecutor.exec(buildKillProcessGroupCommand(this.container, groupFile));\n } catch (error) {\n log.logWarning(\n `Could not stop a command in container ${this.container}`,\n errorMessage(error),\n );\n }\n }\n\n readFile(path: string, options?: ExecOptions): Promise<string> {\n return execReadFile(this, path, options);\n }\n\n readFileBase64(path: string, options?: ExecOptions): Promise<string> {\n return execReadFileBase64(this, path, options);\n }\n\n writeFile(path: string, content: string, options?: ExecOptions): Promise<void> {\n return execWriteFile(this, path, content, options);\n }\n\n getWorkspacePath(_hostPath: string): string {\n return GUEST_WORKSPACE_ROOT;\n }\n\n getPathContext(hostWorkspaceRoot: string): RuntimePathContext {\n return createMountedRuntimePathContext(hostWorkspaceRoot, GUEST_WORKSPACE_ROOT);\n }\n\n getSandboxConfig(): ContainerSandboxConfig {\n return { type: \"container\", container: this.container };\n }\n}\n\nexport const containerSandboxAdapter: SandboxAdapter<ContainerSandboxConfig> = {\n type: \"container\",\n credentials: { env: true, fileMounts: false },\n workspace: { managedProjection: false },\n parse: parseContainerSandboxArg,\n validate: validateContainerSandbox,\n createExecutor: (config, env, ensureReady) =>\n new ContainerExecutor(config.container, env, ensureReady),\n};\n\nasync function ensureContainerRunning(container: string): Promise<void> {\n try {\n const running = await execSimple(\"docker\", [\"inspect\", \"-f\", \"{{.State.Running}}\", container]);\n if (running.trim() === \"true\") {\n return;\n }\n await execSimple(\"docker\", [\"start\", container]);\n } catch (error) {\n const details = errorMessage(error);\n throw new Error(\n `Container \"${container}\" is not available. ` +\n `Expected a pre-existing container or image provisioning to keep it running.\\n${details}`.trim(),\n { cause: error },\n );\n }\n}\n\nfunction createSecureEnvFile(env: Record<string, string>): {\n envFilePath: string;\n cleanup: () => void;\n} {\n const tempDir = mkdtempSync(join(tmpdir(), \"mikan-docker-env-\"));\n chmodSync(tempDir, PRIVATE_DIR_MODE);\n const envFilePath = join(tempDir, \"env.list\");\n const content =\n Object.entries(env)\n .map(([key, value]) => `${key}=${value.replace(/\\r?\\n/g, \"\")}`)\n .join(\"\\n\") + \"\\n\";\n writeFileSync(envFilePath, content, { encoding: \"utf-8\", mode: PRIVATE_FILE_MODE });\n chmodSync(envFilePath, PRIVATE_FILE_MODE);\n\n return {\n envFilePath,\n cleanup: () => {\n rmSync(tempDir, { recursive: true, force: true });\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { promisify } from \"node:util\";\nimport { ExecutionError, FileError, err, type ExecutionEnv } from \"@earendil-works/pi-durable/env\";\nimport { Connection, RemoteExecutionEnv, packagedDaemon } from \"@earendil-works/pi-env\";\nimport type {\n ContainerExecutionEnvOptions,\n ContainerSandboxConfig,\n SandboxAdapter,\n} from \"./types.js\";\nimport { SandboxError, execSimple } from \"./utils.js\";\nimport { containerEngine } from \"./engine.js\";\nimport { errorMessage } from \"../unknown-values.js\";\nimport * as log from \"../log.js\";\n\nconst execFileAsync = promisify(execFile);\n\nfunction parseContainerSandboxArg(value: string): ContainerSandboxConfig | undefined {\n if (!value.startsWith(\"container:\")) {\n return undefined;\n }\n\n const container = value.slice(\"container:\".length);\n if (!container) {\n throw new SandboxError(\n \"Error: container sandbox requires container name (e.g., container:mikan-sandbox)\",\n );\n }\n return { type: \"container\", container };\n}\n\nasync function validateContainerSandbox(config: ContainerSandboxConfig): Promise<void> {\n const engine = containerEngine();\n try {\n await execSimple(engine, [\"--version\"]);\n } catch {\n throw new SandboxError(\n `Error: no container engine found; install nerdctl, Podman, or Docker (tried '${engine}')`,\n );\n }\n\n try {\n const result = await execSimple(engine, [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n config.container,\n ]);\n if (result.trim() !== \"true\") {\n throw new SandboxError(`Error: Container '${config.container}' is not running.`, [\n `Start it with: ${engine} start ${config.container}`,\n ]);\n }\n } catch (error) {\n if (error instanceof SandboxError) {\n throw error;\n }\n throw new SandboxError(`Error: Container '${config.container}' does not exist.`, [\n `Create it with: ${engine} run -d --name ${config.container} -v <workspace>:/workspace docker.io/library/alpine:latest sleep infinity`,\n ]);\n }\n\n console.log(` Container '${config.container}' is running (engine: ${engine}).`);\n}\n\nconst GITHUB_CREDENTIAL_KEY = \"credential.https://github.com.helper\";\n\nfunction withGitHubCredentialHelper(\n env?: Record<string, string>,\n): Record<string, string> | undefined {\n if (!env || !hasGitHubToken(env) || env.GIT_CONFIG_COUNT !== undefined) {\n return env;\n }\n return {\n ...env,\n GIT_CONFIG_COUNT: \"2\",\n GIT_CONFIG_KEY_0: GITHUB_CREDENTIAL_KEY,\n GIT_CONFIG_VALUE_0: \"\",\n GIT_CONFIG_KEY_1: GITHUB_CREDENTIAL_KEY,\n GIT_CONFIG_VALUE_1: \"!gh auth git-credential\",\n };\n}\n\nfunction hasGitHubToken(env: Record<string, string>): boolean {\n return Boolean(env.GH_TOKEN || env.GITHUB_TOKEN);\n}\n\nconst DAEMON_ARCH: Record<string, \"x64\" | \"arm64\"> = {\n x86_64: \"x64\",\n amd64: \"x64\",\n aarch64: \"arm64\",\n arm64: \"arm64\",\n};\n\nconst INSTALL_DAEMON =\n '[ -x \"$1\" ] && exit 0; cat > \"$1.$$\" && chmod 755 \"$1.$$\" && mv -f \"$1.$$\" \"$1\"';\n\nasync function deployDaemon(engine: string, container: string): Promise<string> {\n const { stdout } = await execFileAsync(engine, [\"exec\", container, \"uname\", \"-m\"]);\n const arch = DAEMON_ARCH[stdout.trim()];\n if (!arch) throw new Error(`No pi-env daemon for ${stdout.trim()} in container ${container}`);\n const binary = readFileSync(packagedDaemon({ platform: \"linux\", arch }));\n const digest = createHash(\"sha256\").update(binary).digest(\"hex\").slice(0, 16);\n const path = `/tmp/mikan-pi-env-${digest}`;\n await new Promise<void>((resolve, reject) => {\n const child = execFile(\n engine,\n [\"exec\", \"-i\", container, \"sh\", \"-c\", INSTALL_DAEMON, \"sh\", path],\n (error) => (error ? reject(error) : resolve()),\n );\n child.stdin?.on(\"error\", () => {});\n child.stdin?.end(binary);\n });\n return path;\n}\n\ninterface ContainerConnection {\n connection: Connection;\n ensureReady: () => Promise<void>;\n}\n\nconst connections = new Map<string, ContainerConnection>();\n\nfunction containerConnection(\n configuredEngine: string | undefined,\n container: string,\n ensureReady: () => Promise<void>,\n): Connection {\n const key = `${configuredEngine ?? \"\"}\\0${container}`;\n const existing = connections.get(key);\n if (existing) {\n existing.ensureReady = ensureReady;\n return existing.connection;\n }\n const entry: ContainerConnection = {\n ensureReady,\n connection: new Connection({\n command: async () => {\n await entry.ensureReady();\n const engine = configuredEngine ?? containerEngine();\n return [engine, \"exec\", \"-i\", container, await deployDaemon(engine, container)];\n },\n onLog: (text) => log.logWarning(`Sandbox ${container}`, text.trim()),\n }),\n };\n connections.set(key, entry);\n return entry.connection;\n}\n\nconst PATH_ONLY = new Set<PropertyKey>([\"absolutePath\", \"joinPath\"]);\n\nfunction readiedEnv(\n env: RemoteExecutionEnv,\n ensureReady: () => Promise<void>,\n markUsed: () => void,\n): ExecutionEnv {\n let ready: Promise<void> | undefined;\n const before = async () => {\n ready ??= ensureReady().catch((error: unknown) => {\n ready = undefined;\n throw error;\n });\n await ready;\n markUsed();\n };\n return new Proxy(env, {\n get(target, property, receiver) {\n const value: unknown = Reflect.get(target, property, receiver);\n if (typeof value !== \"function\") return value;\n if (PATH_ONLY.has(property)) return value.bind(target);\n return async (...args: unknown[]) => {\n try {\n await before();\n } catch (error) {\n const message = errorMessage(error);\n if (property === \"cleanup\") return undefined;\n return err(\n property === \"exec\"\n ? new ExecutionError(\"unknown\", message)\n : new FileError(\"unknown\", message),\n );\n }\n return Reflect.apply(value, target, args);\n };\n },\n });\n}\n\nexport function createContainerExecutionEnv(options: ContainerExecutionEnvOptions): ExecutionEnv {\n const ensureReady =\n options.ensureReady ??\n (() => ensureContainerRunning(options.container, options.engine ?? containerEngine()));\n const env = new RemoteExecutionEnv({\n connection: containerConnection(options.engine, options.container, ensureReady),\n id: `container:${options.container}`,\n cwd: options.cwd,\n shellEnv: withGitHubCredentialHelper(options.env),\n });\n return readiedEnv(env, ensureReady, options.markUsed ?? (() => {}));\n}\n\nexport const containerSandboxAdapter: SandboxAdapter<ContainerSandboxConfig> = {\n type: \"container\",\n credentials: { env: true, fileMounts: false },\n workspace: { managedProjection: false },\n parse: parseContainerSandboxArg,\n validate: validateContainerSandbox,\n createEnv: (config, options) =>\n createContainerExecutionEnv({ ...options, container: config.container }),\n};\n\nasync function ensureContainerRunning(container: string, engine: string): Promise<void> {\n try {\n const running = await execSimple(engine, [\"inspect\", \"-f\", \"{{.State.Running}}\", container]);\n if (running.trim() === \"true\") {\n return;\n }\n await execSimple(engine, [\"start\", container]);\n } catch (error) {\n const details = errorMessage(error);\n throw new Error(\n `Container \"${container}\" is not available. ` +\n `Expected a pre-existing container or image provisioning to keep it running.\\n${details}`.trim(),\n { cause: error },\n );\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/sandbox/engine.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAQ9D,wBAAgB,qBAAqB,CACnC,KAAK,GAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAyB,GACnD,MAAM,CAQR;AAID,wBAAgB,eAAe,IAAI,MAAM,CAKxC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readEnv } from "../env-manifest.js";
|
|
3
|
+
const CONTAINER_ENGINE_PRIORITY = ["nerdctl", "podman", "docker"];
|
|
4
|
+
const ENGINE_PROBE_TIMEOUT_MS = 15_000;
|
|
5
|
+
function probeEngine(engine) {
|
|
6
|
+
const result = spawnSync(engine, ["info"], { stdio: "ignore", timeout: ENGINE_PROBE_TIMEOUT_MS });
|
|
7
|
+
if (result.error && "code" in result.error && result.error.code === "ENOENT")
|
|
8
|
+
return "missing";
|
|
9
|
+
return result.status === 0 ? "running" : "installed";
|
|
10
|
+
}
|
|
11
|
+
export function detectContainerEngine(probe = probeEngine) {
|
|
12
|
+
let firstInstalled;
|
|
13
|
+
for (const engine of CONTAINER_ENGINE_PRIORITY) {
|
|
14
|
+
const state = probe(engine);
|
|
15
|
+
if (state === "running")
|
|
16
|
+
return engine;
|
|
17
|
+
if (state === "installed")
|
|
18
|
+
firstInstalled ??= engine;
|
|
19
|
+
}
|
|
20
|
+
return firstInstalled ?? "docker";
|
|
21
|
+
}
|
|
22
|
+
let detected;
|
|
23
|
+
export function containerEngine() {
|
|
24
|
+
const configured = readEnv("CONTAINER_ENGINE");
|
|
25
|
+
if (configured)
|
|
26
|
+
return configured;
|
|
27
|
+
detected ??= detectContainerEngine();
|
|
28
|
+
return detected;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/sandbox/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,MAAM,yBAAyB,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAE3E,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAIvC,SAAS,WAAW,CAAC,MAAc;IACjC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC;IAClG,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC/F,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,KAAK,GAAoC,WAAW;IAEpD,IAAI,cAAkC,CAAC;IACvC,KAAK,MAAM,MAAM,IAAI,yBAAyB,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACvC,IAAI,KAAK,KAAK,WAAW;YAAE,cAAc,KAAK,MAAM,CAAC;IACvD,CAAC;IACD,OAAO,cAAc,IAAI,QAAQ,CAAC;AACpC,CAAC;AAED,IAAI,QAA4B,CAAC;AAEjC,MAAM,UAAU,eAAe;IAC7B,MAAM,UAAU,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC/C,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAClC,QAAQ,KAAK,qBAAqB,EAAE,CAAC;IACrC,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { readEnv } from \"../env-manifest.js\";\n\nconst CONTAINER_ENGINE_PRIORITY = [\"nerdctl\", \"podman\", \"docker\"] as const;\n\nconst ENGINE_PROBE_TIMEOUT_MS = 15_000;\n\nexport type EngineState = \"running\" | \"installed\" | \"missing\";\n\nfunction probeEngine(engine: string): EngineState {\n const result = spawnSync(engine, [\"info\"], { stdio: \"ignore\", timeout: ENGINE_PROBE_TIMEOUT_MS });\n if (result.error && \"code\" in result.error && result.error.code === \"ENOENT\") return \"missing\";\n return result.status === 0 ? \"running\" : \"installed\";\n}\n\nexport function detectContainerEngine(\n probe: (engine: string) => EngineState = probeEngine,\n): string {\n let firstInstalled: string | undefined;\n for (const engine of CONTAINER_ENGINE_PRIORITY) {\n const state = probe(engine);\n if (state === \"running\") return engine;\n if (state === \"installed\") firstInstalled ??= engine;\n }\n return firstInstalled ?? \"docker\";\n}\n\nlet detected: string | undefined;\n\nexport function containerEngine(): string {\n const configured = readEnv(\"CONTAINER_ENGINE\");\n if (configured) return configured;\n detected ??= detectContainerEngine();\n return detected;\n}\n"]}
|
package/dist/sandbox/host.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare class HostExecutor implements Executor {
|
|
3
|
-
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
4
|
-
readFile(path: string): Promise<string>;
|
|
5
|
-
readFileBase64(path: string): Promise<string>;
|
|
6
|
-
writeFile(path: string, content: string): Promise<void>;
|
|
7
|
-
getWorkspacePath(hostPath: string): string;
|
|
8
|
-
getPathContext(hostWorkspaceRoot: string): RuntimePathContext;
|
|
9
|
-
getSandboxConfig(): HostSandboxConfig;
|
|
10
|
-
}
|
|
1
|
+
import type { HostSandboxConfig, SandboxAdapter } from "./types.js";
|
|
11
2
|
export declare const hostSandboxAdapter: SandboxAdapter<HostSandboxConfig>;
|
|
12
3
|
//# sourceMappingURL=host.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/sandbox/host.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/sandbox/host.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AASpE,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,iBAAiB,CAMhE,CAAC"}
|
package/dist/sandbox/host.js
CHANGED
|
@@ -1,92 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
|
-
import { createMountedRuntimePathContext, killProcessTree, linkAbortSignal } from "./utils.js";
|
|
5
|
-
const MAX_CAPTURE_CHARS = 10 * 1024 * 1024;
|
|
1
|
+
import { NodeExecutionEnv } from "@earendil-works/pi-durable/env/node";
|
|
6
2
|
function parseHostSandboxArg(value) {
|
|
7
3
|
if (value === "host") {
|
|
8
4
|
return { type: "host" };
|
|
9
5
|
}
|
|
10
6
|
return undefined;
|
|
11
7
|
}
|
|
12
|
-
export class HostExecutor {
|
|
13
|
-
async exec(command, options) {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
const child = spawnShell(command, options?.cwd);
|
|
16
|
-
const capture = captureOutput(child);
|
|
17
|
-
let timedOut = false;
|
|
18
|
-
const timeoutHandle = options?.timeout && options.timeout > 0
|
|
19
|
-
? setTimeout(() => {
|
|
20
|
-
timedOut = true;
|
|
21
|
-
killProcessTree(child.pid);
|
|
22
|
-
}, options.timeout * 1000)
|
|
23
|
-
: undefined;
|
|
24
|
-
const unlinkSignal = linkAbortSignal(options?.signal, () => {
|
|
25
|
-
if (child.pid)
|
|
26
|
-
killProcessTree(child.pid);
|
|
27
|
-
});
|
|
28
|
-
child.on("close", (code) => {
|
|
29
|
-
if (timeoutHandle)
|
|
30
|
-
clearTimeout(timeoutHandle);
|
|
31
|
-
unlinkSignal();
|
|
32
|
-
const { stdout, stderr } = capture;
|
|
33
|
-
if (options?.signal?.aborted) {
|
|
34
|
-
reject(new Error(`${stdout}\n${stderr}\nCommand aborted`.trim()));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (timedOut) {
|
|
38
|
-
reject(new Error(`${stdout}\n${stderr}\nCommand timed out after ${options?.timeout} seconds`.trim()));
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
resolve({ stdout, stderr, code: code ?? 0 });
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
async readFile(path) {
|
|
46
|
-
return readFile(path, "utf-8");
|
|
47
|
-
}
|
|
48
|
-
async readFileBase64(path) {
|
|
49
|
-
return (await readFile(path)).toString("base64");
|
|
50
|
-
}
|
|
51
|
-
async writeFile(path, content) {
|
|
52
|
-
await mkdir(dirname(path), { recursive: true });
|
|
53
|
-
const stage = `${path}.mikan-stage`;
|
|
54
|
-
await writeFile(stage, content, "utf-8");
|
|
55
|
-
await rename(stage, path);
|
|
56
|
-
}
|
|
57
|
-
getWorkspacePath(hostPath) {
|
|
58
|
-
return hostPath;
|
|
59
|
-
}
|
|
60
|
-
getPathContext(hostWorkspaceRoot) {
|
|
61
|
-
return createMountedRuntimePathContext(hostWorkspaceRoot, hostWorkspaceRoot);
|
|
62
|
-
}
|
|
63
|
-
getSandboxConfig() {
|
|
64
|
-
return { type: "host" };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function spawnShell(command, cwd) {
|
|
68
|
-
const isWindows = process.platform === "win32";
|
|
69
|
-
return spawn(isWindows ? "cmd" : "sh", [isWindows ? "/c" : "-c", command], {
|
|
70
|
-
detached: true,
|
|
71
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
72
|
-
cwd: cwd ? cwd : undefined,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
function captureOutput(child) {
|
|
76
|
-
const capture = { stdout: "", stderr: "" };
|
|
77
|
-
for (const stream of ["stdout", "stderr"]) {
|
|
78
|
-
child[stream]?.setEncoding("utf8");
|
|
79
|
-
child[stream]?.on("data", (chunk) => {
|
|
80
|
-
capture[stream] = (capture[stream] + chunk).slice(0, MAX_CAPTURE_CHARS);
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
return capture;
|
|
84
|
-
}
|
|
85
8
|
export const hostSandboxAdapter = {
|
|
86
9
|
type: "host",
|
|
87
10
|
credentials: { env: false, fileMounts: false },
|
|
88
11
|
workspace: { managedProjection: false },
|
|
89
12
|
parse: parseHostSandboxArg,
|
|
90
|
-
|
|
13
|
+
createEnv: (_config, options) => new NodeExecutionEnv({ cwd: options.cwd }),
|
|
91
14
|
};
|
|
92
15
|
//# sourceMappingURL=host.js.map
|
package/dist/sandbox/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/sandbox/host.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/sandbox/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAGvE,SAAS,mBAAmB,CAAC,KAAa;IACxC,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAsC;IACnE,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE;IAC9C,SAAS,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE;IACvC,KAAK,EAAE,mBAAmB;IAC1B,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,gBAAgB,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;CAC5E,CAAC","sourcesContent":["import { NodeExecutionEnv } from \"@earendil-works/pi-durable/env/node\";\nimport type { HostSandboxConfig, SandboxAdapter } from \"./types.js\";\n\nfunction parseHostSandboxArg(value: string): HostSandboxConfig | undefined {\n if (value === \"host\") {\n return { type: \"host\" };\n }\n return undefined;\n}\n\nexport const hostSandboxAdapter: SandboxAdapter<HostSandboxConfig> = {\n type: \"host\",\n credentials: { env: false, fileMounts: false },\n workspace: { managedProjection: false },\n parse: parseHostSandboxArg,\n createEnv: (_config, options) => new NodeExecutionEnv({ cwd: options.cwd }),\n};\n"]}
|
|
@@ -9,12 +9,19 @@ export declare class DockerContainerManager {
|
|
|
9
9
|
private static readonly VAULT_ID_LABEL_KEY;
|
|
10
10
|
private static readonly CONVERSATION_ID_LABEL_KEY;
|
|
11
11
|
private static readonly MOUNT_SIGNATURE_LABEL_KEY;
|
|
12
|
+
private static readonly NETWORK_LABEL_KEY;
|
|
13
|
+
private static readonly IMAGE_ID_LABEL_KEY;
|
|
12
14
|
private readonly limits?;
|
|
13
15
|
private readonly boostLimits?;
|
|
14
16
|
private readonly boostedKeys;
|
|
15
17
|
private readonly overrideLimits;
|
|
16
18
|
private readonly execFileImpl;
|
|
19
|
+
private readonly configuredEngine;
|
|
20
|
+
private readonly timeZone;
|
|
21
|
+
private imagePull?;
|
|
17
22
|
constructor(image: string, options?: DockerContainerManagerOptions);
|
|
23
|
+
private get engine();
|
|
24
|
+
private run;
|
|
18
25
|
static containerName(containerKey: string): string;
|
|
19
26
|
static networkName(containerKey: string): string;
|
|
20
27
|
private serialize;
|
|
@@ -25,6 +32,7 @@ export declare class DockerContainerManager {
|
|
|
25
32
|
getLimitStatus(containerKey: string): SandboxLimitStatus;
|
|
26
33
|
getDefaultLimits(): ResourceLimits | undefined;
|
|
27
34
|
getBoostLimits(): ResourceLimits | undefined;
|
|
35
|
+
markUsed(containerKey: string): void;
|
|
28
36
|
stop(containerKey: string): Promise<void>;
|
|
29
37
|
private stopInner;
|
|
30
38
|
remove(containerKey: string): Promise<void>;
|
|
@@ -46,18 +54,25 @@ export declare class DockerContainerManager {
|
|
|
46
54
|
private hasMountSignatureDrift;
|
|
47
55
|
private mountSignature;
|
|
48
56
|
private mountSourceFingerprint;
|
|
57
|
+
private label;
|
|
49
58
|
private inspectBindMounts;
|
|
50
59
|
private hasImageDrift;
|
|
51
60
|
private localImageId;
|
|
61
|
+
prepareImage(): Promise<void>;
|
|
62
|
+
private pullImage;
|
|
63
|
+
private imageLeftBehind;
|
|
64
|
+
private isUnnamedImage;
|
|
65
|
+
private removeImageIfUnused;
|
|
66
|
+
private pulledImageId;
|
|
52
67
|
private replaceContainer;
|
|
53
68
|
private hasNetworkModeDrift;
|
|
54
69
|
private ensureNetwork;
|
|
70
|
+
private containerExists;
|
|
55
71
|
private inspectStatus;
|
|
56
72
|
private listContainerNamesByLabel;
|
|
57
73
|
private parseNameLines;
|
|
58
74
|
private inspectContainerDetails;
|
|
59
75
|
private normalizeDockerValue;
|
|
60
|
-
private parseDockerTimestamp;
|
|
61
76
|
private containerKeyFromContainerName;
|
|
62
77
|
private forceRemoveContainer;
|
|
63
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provisioner.d.ts","sourceRoot":"","sources":["../../src/sandbox/provisioner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"provisioner.d.ts","sourceRoot":"","sources":["../../src/sandbox/provisioner.ts"],"names":[],"mappings":"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"}
|