@bakers-build/addon-runtime-docker 0.9.1-next.5 → 0.9.1-next.6
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/dist/src/args.d.ts +4 -5
- package/dist/src/args.js +5 -23
- package/dist/src/backend.d.ts +0 -1
- package/dist/src/backend.js +25 -45
- package/package.json +4 -4
package/dist/src/args.d.ts
CHANGED
|
@@ -13,11 +13,10 @@ export interface DockerOptions {
|
|
|
13
13
|
}
|
|
14
14
|
/** Host facts the launch depends on, resolved by the backend. */
|
|
15
15
|
export interface HostPaths {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
gitLink?: string;
|
|
16
|
+
/** The run clone's Git directory, mounted as `<workspace>/.git`. */
|
|
17
|
+
runGitDir?: string;
|
|
18
|
+
/** A `shared` clone's parent object store, mounted read-only at its own path. */
|
|
19
|
+
sharedObjects?: string;
|
|
21
20
|
/** The per-run home, mounted at its host path. */
|
|
22
21
|
runHome: string;
|
|
23
22
|
/** The bakers CLI build, mounted read-only at its host path. */
|
package/dist/src/args.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
1
|
/** The image's PATH, behind the run home's `bin` (the `bakers` shim lives there). */
|
|
3
2
|
export const RUN_PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`;
|
|
4
3
|
export const PROXY_PORTS = { egress: 3128, api: 8081, tools: 8082 };
|
|
@@ -37,9 +36,6 @@ export function dockerArgs(input, opts, where) {
|
|
|
37
36
|
!overridden.has(k) &&
|
|
38
37
|
// Allowlist mode keeps the model credential in the proxy only.
|
|
39
38
|
!(allowlist && credentials.includes(k)));
|
|
40
|
-
const common = where.gitCommonDir;
|
|
41
|
-
const gitDir = where.gitDir;
|
|
42
|
-
const linked = gitDir !== common;
|
|
43
39
|
const ro = (path) => bind(path, path, ':ro');
|
|
44
40
|
const argv = [
|
|
45
41
|
opts.docker,
|
|
@@ -58,30 +54,16 @@ export function dockerArgs(input, opts, where) {
|
|
|
58
54
|
...computed.flatMap((c) => ['-e', c]),
|
|
59
55
|
...forwarded.flatMap((k) => ['-e', k]),
|
|
60
56
|
...bind(ws),
|
|
61
|
-
|
|
62
|
-
...bind(
|
|
63
|
-
|
|
64
|
-
// Git dir: host git in these repos would run what the run chose.
|
|
65
|
-
...ro(join(common, 'hooks')),
|
|
66
|
-
...ro(join(common, 'config')),
|
|
67
|
-
// Read by host git in the main checkout once `extensions.worktreeConfig` is on.
|
|
68
|
-
...ro(join(common, 'config.worktree')),
|
|
69
|
-
...ro(join(common, 'modules')),
|
|
70
|
-
...ro(join(common, 'worktrees')),
|
|
71
|
-
...(linked
|
|
72
|
-
? [
|
|
73
|
-
// Docker mounts by depth, so this reopens only the run's own Git dir.
|
|
74
|
-
...bind(gitDir),
|
|
75
|
-
...ro(join(gitDir, 'commondir')),
|
|
76
|
-
...ro(join(gitDir, 'gitdir')),
|
|
77
|
-
...ro(join(gitDir, 'config.worktree')),
|
|
78
|
-
]
|
|
79
|
-
: []),
|
|
57
|
+
// The only Git the run sees; the parent repository is not mounted.
|
|
58
|
+
...(where.runGitDir ? bind(where.runGitDir, `${ws}/.git`) : []),
|
|
59
|
+
...(where.sharedObjects ? ro(where.sharedObjects) : []),
|
|
80
60
|
...bind(where.cliRoot, undefined, ':ro'),
|
|
81
61
|
...opts.mounts.flatMap((m) => bind(m, m, ':ro')),
|
|
82
62
|
...bind(where.runHome),
|
|
83
63
|
...(input.readPaths ?? []).flatMap((path) => ro(path)),
|
|
84
64
|
...(input.writePaths ?? []).flatMap((path) => bind(path)),
|
|
65
|
+
// Shared host files over their placeholders in the run home.
|
|
66
|
+
...(input.bindPaths ?? []).flatMap(({ source, target }) => bind(source, target)),
|
|
85
67
|
'-w',
|
|
86
68
|
ws,
|
|
87
69
|
opts.image,
|
package/dist/src/backend.d.ts
CHANGED
|
@@ -31,6 +31,5 @@ export interface BackendDeps {
|
|
|
31
31
|
}
|
|
32
32
|
/** The first 12 hex chars of the sha256 of `text`. */
|
|
33
33
|
export declare const shortHash: (text: string) => string;
|
|
34
|
-
export { quarantinePlanted } from '@bakers-build/core';
|
|
35
34
|
export declare const realExec: BackendDeps['exec'];
|
|
36
35
|
export declare function dockerRuntime(options: RuntimeDockerOptions, deps: BackendDeps): BbRuntime;
|
package/dist/src/backend.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, mkdirSync,
|
|
3
|
+
import { existsSync, mkdirSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import { isAbsolute, join, resolve, sep } from 'node:path';
|
|
5
|
+
import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
|
-
import {
|
|
7
|
+
import { shellQuote } from '@bakers-build/core';
|
|
8
8
|
import { API_KEY_PLACEHOLDER, dockerArgs, PROXY_PORTS, } from './args.js';
|
|
9
9
|
const EGRESS_NETWORK = 'bb-runtime-egress';
|
|
10
10
|
/** The first 12 hex chars of the sha256 of `text`. */
|
|
11
11
|
export const shortHash = (text) => createHash('sha256').update(text).digest('hex').slice(0, 12);
|
|
12
|
-
export { quarantinePlanted } from '@bakers-build/core';
|
|
13
12
|
const run = promisify(execFile);
|
|
14
13
|
export const realExec = (file, args) => run(file, args);
|
|
15
14
|
export function dockerRuntime(options, deps) {
|
|
@@ -35,8 +34,6 @@ export function dockerRuntime(options, deps) {
|
|
|
35
34
|
throw new Error(`runtime-docker: invalid run uuid ${JSON.stringify(uuid)}`);
|
|
36
35
|
return join(cacheHome, 'bakers/runtime', uuid);
|
|
37
36
|
};
|
|
38
|
-
// Beside the run home, never mounted: cleanup trusts it to find the worktree.
|
|
39
|
-
const recordOf = (uuid) => `${runHomeOf(uuid)}.workspace.json`;
|
|
40
37
|
// One image per agent install; built once each.
|
|
41
38
|
const imagesReady = new Map();
|
|
42
39
|
const ensureImage = async (image, install) => {
|
|
@@ -111,13 +108,6 @@ export function dockerRuntime(options, deps) {
|
|
|
111
108
|
]);
|
|
112
109
|
await deps.exec(docker, ['network', 'connect', opts.runNetwork, proxyName]);
|
|
113
110
|
};
|
|
114
|
-
const git = async (ws, flag) => (await deps.exec('git', [
|
|
115
|
-
'-C',
|
|
116
|
-
ws,
|
|
117
|
-
'rev-parse',
|
|
118
|
-
'--path-format=absolute',
|
|
119
|
-
flag,
|
|
120
|
-
])).stdout.trim();
|
|
121
111
|
const credentialsFor = (agent) => {
|
|
122
112
|
const api = agent.modelApi;
|
|
123
113
|
if (allowlist && !api)
|
|
@@ -176,14 +166,21 @@ export function dockerRuntime(options, deps) {
|
|
|
176
166
|
await proxyReady.ready;
|
|
177
167
|
}
|
|
178
168
|
const ws = launch.workspacePath;
|
|
179
|
-
const
|
|
180
|
-
const gitDir = await git(ws, '--git-dir');
|
|
181
|
-
const gitLink = join(ws, '.git');
|
|
169
|
+
const runGitDir = launch.gitDir;
|
|
182
170
|
const within = (path, root) => path === root || path.startsWith(root + sep);
|
|
171
|
+
if (within(realpathSync(dirname(runHome)), realpathSync(ws)))
|
|
172
|
+
throw new Error('runtime-docker: private state must stay outside the worktree');
|
|
173
|
+
if (runGitDir && within(realpathSync(runGitDir), realpathSync(ws)))
|
|
174
|
+
throw new Error('runtime-docker: the run Git directory must stay outside the workspace');
|
|
183
175
|
const canonical = (path) => existsSync(path) ? realpathSync(path) : resolve(path);
|
|
176
|
+
const binds = launch.bindPaths ?? [];
|
|
177
|
+
for (const { target } of binds)
|
|
178
|
+
if (target.includes(':') || !within(resolve(target), resolve(runHome)))
|
|
179
|
+
throw new Error(`runtime-docker: shared file target outside the run home ${target}`);
|
|
184
180
|
for (const [paths, write] of [
|
|
185
181
|
[launch.readPaths ?? [], false],
|
|
186
182
|
[launch.writePaths ?? [], true],
|
|
183
|
+
[binds.map(({ source }) => source), true],
|
|
187
184
|
]) {
|
|
188
185
|
for (const path of paths) {
|
|
189
186
|
if (!isAbsolute(path) || path.includes(':') || !existsSync(path))
|
|
@@ -193,21 +190,19 @@ export function dockerRuntime(options, deps) {
|
|
|
193
190
|
(deps.env.HOME && within(canonical(deps.env.HOME), actual)))
|
|
194
191
|
throw new Error(`runtime-docker: refusing broad path ${path}`);
|
|
195
192
|
if (write &&
|
|
196
|
-
[
|
|
193
|
+
[
|
|
194
|
+
...(runGitDir ? [runGitDir] : []),
|
|
195
|
+
...(launch.sharedObjects ? [launch.sharedObjects] : []),
|
|
196
|
+
dirname(runHome),
|
|
197
|
+
join(ws, '.git'),
|
|
198
|
+
deps.cliRoot,
|
|
199
|
+
...(launch.readPaths ?? []),
|
|
200
|
+
...opts.mounts,
|
|
201
|
+
].some((root) => within(actual, canonical(root)) ||
|
|
197
202
|
within(canonical(root), actual)))
|
|
198
203
|
throw new Error(`runtime-docker: protected write path ${path}`);
|
|
199
204
|
}
|
|
200
205
|
}
|
|
201
|
-
// Bound read-only, so each must exist before docker would create it as root.
|
|
202
|
-
for (const d of ['hooks', 'modules', 'worktrees'])
|
|
203
|
-
mkdirSync(join(gitCommonDir, d), { recursive: true });
|
|
204
|
-
for (const dir of new Set([gitCommonDir, gitDir])) {
|
|
205
|
-
const worktreeConfig = join(dir, 'config.worktree');
|
|
206
|
-
if (!existsSync(worktreeConfig))
|
|
207
|
-
writeFileSync(worktreeConfig, '');
|
|
208
|
-
}
|
|
209
|
-
mkdirSync(runHome, { recursive: true });
|
|
210
|
-
writeFileSync(recordOf(launch.run.uuid), JSON.stringify({ workspace: ws, gitCommonDir }));
|
|
211
206
|
// The CLI build is mounted read-only; this puts the conductor's own CLI on PATH.
|
|
212
207
|
if (deps.bakersEntry) {
|
|
213
208
|
mkdirSync(join(runHome, 'bin'), { recursive: true });
|
|
@@ -219,10 +214,9 @@ export function dockerRuntime(options, deps) {
|
|
|
219
214
|
for (const k of credentials)
|
|
220
215
|
env[k] = deps.env[k];
|
|
221
216
|
const { argv, env: runEnv } = dockerArgs({ ...launch, env }, { ...opts, image }, {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
? { gitLink }
|
|
217
|
+
...(runGitDir ? { runGitDir } : {}),
|
|
218
|
+
...(launch.sharedObjects
|
|
219
|
+
? { sharedObjects: launch.sharedObjects }
|
|
226
220
|
: {}),
|
|
227
221
|
runHome,
|
|
228
222
|
cliRoot: deps.cliRoot,
|
|
@@ -253,20 +247,6 @@ export function dockerRuntime(options, deps) {
|
|
|
253
247
|
deps.logger.warn({ run: r.uuid, err: String(err) }, 'runtime container cleanup failed');
|
|
254
248
|
return;
|
|
255
249
|
}
|
|
256
|
-
// After the container is gone, so the run cannot plant anything behind the scan.
|
|
257
|
-
const record = recordOf(r.uuid);
|
|
258
|
-
if (existsSync(record)) {
|
|
259
|
-
try {
|
|
260
|
-
const { workspace, gitCommonDir } = JSON.parse(readFileSync(record, 'utf8'));
|
|
261
|
-
const moved = await quarantinePlanted(workspace, gitCommonDir, deps.exec);
|
|
262
|
-
if (moved.length > 0)
|
|
263
|
-
deps.logger.warn({ run: r.uuid, moved }, 'runtime quarantined repositories the run planted');
|
|
264
|
-
rmSync(record);
|
|
265
|
-
}
|
|
266
|
-
catch (err) {
|
|
267
|
-
deps.logger.error({ run: r.uuid, err: String(err) }, 'runtime quarantine failed: host git in the worktree may run what the run planted');
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
250
|
rmSync(runHomeOf(r.uuid), { recursive: true, force: true });
|
|
271
251
|
},
|
|
272
252
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bakers-build/addon-runtime-docker",
|
|
3
|
-
"version": "0.9.1-next.
|
|
3
|
+
"version": "0.9.1-next.6",
|
|
4
4
|
"description": "Start each agent run in its own Docker or Podman container.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"directory": "bakers-cli/addons/runtime-docker"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@bakers-build/conductor": "^0.9.1-next.
|
|
27
|
-
"@bakers-build/core": "^0.9.1-next.
|
|
26
|
+
"@bakers-build/conductor": "^0.9.1-next.6",
|
|
27
|
+
"@bakers-build/core": "^0.9.1-next.6"
|
|
28
28
|
},
|
|
29
29
|
"bakersBuild": {
|
|
30
|
-
"commit": "
|
|
30
|
+
"commit": "e7d9921d51d5c045f29ca7e35d1e6a348e6ddf08",
|
|
31
31
|
"channel": "next"
|
|
32
32
|
}
|
|
33
33
|
}
|