@isol8/core 0.17.0 → 0.18.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/dist/config.d.ts.map +1 -1
- package/dist/docker/Dockerfile +2 -2
- package/dist/engine/docker.d.ts +1 -2
- package/dist/engine/docker.d.ts.map +1 -1
- package/dist/engine/image-builder.d.ts +10 -22
- package/dist/engine/image-builder.d.ts.map +1 -1
- package/dist/engine/managers/execution-manager.d.ts +2 -0
- package/dist/engine/managers/execution-manager.d.ts.map +1 -1
- package/dist/engine/utils.d.ts +21 -1
- package/dist/engine/utils.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -126
- package/dist/index.js.map +8 -8
- package/dist/types.d.ts +39 -26
- package/dist/types.d.ts.map +1 -1
- package/docker/Dockerfile +2 -2
- package/package.json +1 -1
- package/schema/isol8.config.schema.json +0 -46
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
1
|
var __defProp = Object.defineProperty;
|
|
3
2
|
var __returnValue = (v) => v;
|
|
4
3
|
function __exportSetter(name, newValue) {
|
|
@@ -14,7 +13,6 @@ var __export = (target, all) => {
|
|
|
14
13
|
});
|
|
15
14
|
};
|
|
16
15
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
18
16
|
|
|
19
17
|
// src/runtime/adapter.ts
|
|
20
18
|
var adapters, extensionMap, RuntimeRegistry;
|
|
@@ -200,11 +198,13 @@ var exports_utils = {};
|
|
|
200
198
|
__export(exports_utils, {
|
|
201
199
|
validatePackageName: () => validatePackageName,
|
|
202
200
|
truncateOutput: () => truncateOutput,
|
|
201
|
+
resolveWorkdir: () => resolveWorkdir,
|
|
203
202
|
parseMemoryLimit: () => parseMemoryLimit,
|
|
204
203
|
maskSecrets: () => maskSecrets,
|
|
205
204
|
extractFromTar: () => extractFromTar,
|
|
206
205
|
createTarBuffer: () => createTarBuffer
|
|
207
206
|
});
|
|
207
|
+
import path from "node:path";
|
|
208
208
|
function parseMemoryLimit(limit) {
|
|
209
209
|
const match = limit.match(/^(\d+(?:\.\d+)?)\s*([kmgt]?)b?$/i);
|
|
210
210
|
if (!match) {
|
|
@@ -299,17 +299,24 @@ function validatePackageName(name) {
|
|
|
299
299
|
}
|
|
300
300
|
return name;
|
|
301
301
|
}
|
|
302
|
+
function resolveWorkdir(workdir, sandboxRoot = "/sandbox") {
|
|
303
|
+
const resolved = path.posix.resolve(sandboxRoot, workdir);
|
|
304
|
+
if (resolved !== sandboxRoot && !resolved.startsWith(`${sandboxRoot}/`)) {
|
|
305
|
+
throw new Error("Working directory must be inside /sandbox");
|
|
306
|
+
}
|
|
307
|
+
return resolved;
|
|
308
|
+
}
|
|
309
|
+
var init_utils = () => {};
|
|
302
310
|
|
|
303
311
|
// src/engine/image-builder.ts
|
|
304
312
|
var exports_image_builder = {};
|
|
305
313
|
__export(exports_image_builder, {
|
|
306
314
|
normalizePackages: () => normalizePackages,
|
|
307
315
|
imageExists: () => imageExists,
|
|
308
|
-
getCustomImageTag: () => getCustomImageTag,
|
|
309
316
|
ensureImages: () => ensureImages,
|
|
310
|
-
buildCustomImages: () => buildCustomImages,
|
|
311
317
|
buildCustomImage: () => buildCustomImage,
|
|
312
|
-
buildBaseImages: () => buildBaseImages
|
|
318
|
+
buildBaseImages: () => buildBaseImages,
|
|
319
|
+
LABELS: () => LABELS
|
|
313
320
|
});
|
|
314
321
|
import { createHash as createHash2 } from "node:crypto";
|
|
315
322
|
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
@@ -350,23 +357,21 @@ function computeDockerDirHash() {
|
|
|
350
357
|
}
|
|
351
358
|
return hash.digest("hex");
|
|
352
359
|
}
|
|
353
|
-
function computeDepsHash(runtime, packages) {
|
|
360
|
+
function computeDepsHash(runtime, packages, setupScript) {
|
|
354
361
|
const hash = createHash2("sha256");
|
|
355
362
|
hash.update(runtime);
|
|
356
363
|
for (const pkg of [...packages].sort()) {
|
|
357
364
|
hash.update(pkg);
|
|
358
365
|
}
|
|
366
|
+
if (setupScript) {
|
|
367
|
+
hash.update("setup:");
|
|
368
|
+
hash.update(setupScript);
|
|
369
|
+
}
|
|
359
370
|
return hash.digest("hex");
|
|
360
371
|
}
|
|
361
372
|
function normalizePackages(packages) {
|
|
362
373
|
return [...new Set(packages.map((pkg) => pkg.trim()).filter(Boolean))].sort();
|
|
363
374
|
}
|
|
364
|
-
function getCustomImageTag(runtime, packages) {
|
|
365
|
-
const normalizedPackages = normalizePackages(packages);
|
|
366
|
-
const depsHash = computeDepsHash(runtime, normalizedPackages);
|
|
367
|
-
const shortHash = depsHash.slice(0, 12);
|
|
368
|
-
return `isol8:${runtime}-custom-${shortHash}`;
|
|
369
|
-
}
|
|
370
375
|
async function getImageLabels(docker, imageName) {
|
|
371
376
|
try {
|
|
372
377
|
const image = docker.getImage(imageName);
|
|
@@ -439,33 +444,9 @@ async function buildBaseImages(docker, onProgress, force = false, onlyRuntimes)
|
|
|
439
444
|
}
|
|
440
445
|
}
|
|
441
446
|
}
|
|
442
|
-
async function
|
|
443
|
-
const deps = config.dependencies;
|
|
444
|
-
const python = deps.python ? normalizePackages(deps.python) : [];
|
|
445
|
-
const node = deps.node ? normalizePackages(deps.node) : [];
|
|
446
|
-
const bun = deps.bun ? normalizePackages(deps.bun) : [];
|
|
447
|
-
const deno = deps.deno ? normalizePackages(deps.deno) : [];
|
|
448
|
-
const bash = deps.bash ? normalizePackages(deps.bash) : [];
|
|
449
|
-
if (python.length) {
|
|
450
|
-
await buildCustomImage(docker, "python", python, onProgress, force);
|
|
451
|
-
}
|
|
452
|
-
if (node.length) {
|
|
453
|
-
await buildCustomImage(docker, "node", node, onProgress, force);
|
|
454
|
-
}
|
|
455
|
-
if (bun.length) {
|
|
456
|
-
await buildCustomImage(docker, "bun", bun, onProgress, force);
|
|
457
|
-
}
|
|
458
|
-
if (deno.length) {
|
|
459
|
-
await buildCustomImage(docker, "deno", deno, onProgress, force);
|
|
460
|
-
}
|
|
461
|
-
if (bash.length) {
|
|
462
|
-
await buildCustomImage(docker, "bash", bash, onProgress, force);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
async function buildCustomImage(docker, runtime, packages, onProgress, force = false) {
|
|
447
|
+
async function buildCustomImage(docker, runtime, packages, tag, onProgress, force = false, setupScript) {
|
|
466
448
|
const normalizedPackages = normalizePackages(packages);
|
|
467
|
-
const
|
|
468
|
-
const depsHash = computeDepsHash(runtime, normalizedPackages);
|
|
449
|
+
const depsHash = computeDepsHash(runtime, normalizedPackages, setupScript);
|
|
469
450
|
logger.debug(`[ImageBuilder] ${runtime} custom deps hash: ${depsHash.slice(0, 16)}...`);
|
|
470
451
|
if (!force) {
|
|
471
452
|
const labels = await getImageLabels(docker, tag);
|
|
@@ -491,7 +472,7 @@ async function buildCustomImage(docker, runtime, packages, onProgress, force = f
|
|
|
491
472
|
let installCmd;
|
|
492
473
|
switch (runtime) {
|
|
493
474
|
case "python":
|
|
494
|
-
installCmd = `RUN pip install --no-cache-dir ${normalizedPackages.join(" ")}`;
|
|
475
|
+
installCmd = `RUN pip install --break-system-packages --no-cache-dir ${normalizedPackages.join(" ")}`;
|
|
495
476
|
break;
|
|
496
477
|
case "node":
|
|
497
478
|
installCmd = `RUN npm install -g ${normalizedPackages.join(" ")}`;
|
|
@@ -509,27 +490,50 @@ async function buildCustomImage(docker, runtime, packages, onProgress, force = f
|
|
|
509
490
|
default:
|
|
510
491
|
throw new Error(`Unknown runtime: ${runtime}`);
|
|
511
492
|
}
|
|
493
|
+
let setupLines = "";
|
|
494
|
+
if (setupScript) {
|
|
495
|
+
const escaped = setupScript.replace(/\\/g, "\\\\").replace(/'/g, "'\\''");
|
|
496
|
+
setupLines = [
|
|
497
|
+
`RUN printf '%s\\n' '${escaped}' > /sandbox/.isol8-setup.sh`,
|
|
498
|
+
"RUN chmod +x /sandbox/.isol8-setup.sh"
|
|
499
|
+
].join(`
|
|
500
|
+
`);
|
|
501
|
+
}
|
|
512
502
|
const dockerfileContent = `FROM isol8:${runtime}
|
|
513
503
|
${installCmd}
|
|
504
|
+
${setupLines}
|
|
514
505
|
`;
|
|
515
|
-
const { createTarBuffer: createTarBuffer2, validatePackageName: validatePackageName2 } = await Promise.resolve().then(() => exports_utils);
|
|
516
|
-
const { Readable } = await import("node:stream");
|
|
506
|
+
const { createTarBuffer: createTarBuffer2, validatePackageName: validatePackageName2 } = await Promise.resolve().then(() => (init_utils(), exports_utils));
|
|
517
507
|
normalizedPackages.forEach(validatePackageName2);
|
|
518
508
|
const tarBuffer = createTarBuffer2("Dockerfile", dockerfileContent);
|
|
519
|
-
const
|
|
509
|
+
const imageLabels = {
|
|
510
|
+
[LABELS.depsHash]: depsHash,
|
|
511
|
+
[LABELS.runtime]: runtime.toString(),
|
|
512
|
+
[LABELS.dependencies]: normalizedPackages.join(",")
|
|
513
|
+
};
|
|
514
|
+
if (setupScript) {
|
|
515
|
+
imageLabels[LABELS.setupScript] = setupScript;
|
|
516
|
+
}
|
|
517
|
+
const stream = await docker.buildImage(tarBuffer, {
|
|
520
518
|
t: tag,
|
|
521
519
|
dockerfile: "Dockerfile",
|
|
522
|
-
labels:
|
|
523
|
-
[LABELS.depsHash]: depsHash
|
|
524
|
-
}
|
|
520
|
+
labels: imageLabels
|
|
525
521
|
});
|
|
526
522
|
await new Promise((resolve2, reject) => {
|
|
527
|
-
docker.modem.followProgress(stream, (err) => {
|
|
523
|
+
docker.modem.followProgress(stream, (err, res) => {
|
|
528
524
|
if (err) {
|
|
529
525
|
reject(err);
|
|
526
|
+
} else if (res && res.length > 0 && res.at(-1).error) {
|
|
527
|
+
reject(new Error(res.at(-1).error));
|
|
530
528
|
} else {
|
|
531
529
|
resolve2();
|
|
532
530
|
}
|
|
531
|
+
}, (event) => {
|
|
532
|
+
if (event.stream) {
|
|
533
|
+
process.stdout.write(event.stream);
|
|
534
|
+
} else if (event.error) {
|
|
535
|
+
console.error(event.error);
|
|
536
|
+
}
|
|
533
537
|
});
|
|
534
538
|
});
|
|
535
539
|
if (oldImageId) {
|
|
@@ -564,7 +568,10 @@ var init_image_builder = __esm(() => {
|
|
|
564
568
|
DOCKERFILE_DIR = resolveDockerDir();
|
|
565
569
|
LABELS = {
|
|
566
570
|
dockerHash: "org.isol8.build.hash",
|
|
567
|
-
depsHash: "org.isol8.deps.hash"
|
|
571
|
+
depsHash: "org.isol8.deps.hash",
|
|
572
|
+
runtime: "org.isol8.runtime",
|
|
573
|
+
dependencies: "org.isol8.dependencies",
|
|
574
|
+
setupScript: "org.isol8.setup"
|
|
568
575
|
};
|
|
569
576
|
DOCKER_BUILD_FILES = ["Dockerfile", "proxy.sh", "proxy-handler.sh"];
|
|
570
577
|
});
|
|
@@ -828,7 +835,6 @@ var DEFAULT_CONFIG = {
|
|
|
828
835
|
},
|
|
829
836
|
poolStrategy: "fast",
|
|
830
837
|
poolSize: { clean: 1, dirty: 1 },
|
|
831
|
-
dependencies: {},
|
|
832
838
|
security: {
|
|
833
839
|
seccomp: "strict"
|
|
834
840
|
},
|
|
@@ -869,6 +875,7 @@ var DEFAULT_CONFIG = {
|
|
|
869
875
|
defaultTtlMs: 86400000,
|
|
870
876
|
cleanupIntervalMs: 3600000
|
|
871
877
|
},
|
|
878
|
+
prebuiltImages: [],
|
|
872
879
|
debug: false
|
|
873
880
|
};
|
|
874
881
|
function loadConfig(cwd) {
|
|
@@ -903,10 +910,6 @@ function mergeConfig(defaults, overrides) {
|
|
|
903
910
|
},
|
|
904
911
|
poolStrategy: overrides.poolStrategy ?? defaults.poolStrategy,
|
|
905
912
|
poolSize: overrides.poolSize ?? defaults.poolSize,
|
|
906
|
-
dependencies: {
|
|
907
|
-
...defaults.dependencies,
|
|
908
|
-
...overrides.dependencies
|
|
909
|
-
},
|
|
910
913
|
security: {
|
|
911
914
|
seccomp: overrides.security?.seccomp ?? defaults.security.seccomp,
|
|
912
915
|
customProfilePath: overrides.security?.customProfilePath ?? defaults.security.customProfilePath
|
|
@@ -926,6 +929,7 @@ function mergeConfig(defaults, overrides) {
|
|
|
926
929
|
...defaults.auth,
|
|
927
930
|
...overrides.auth
|
|
928
931
|
},
|
|
932
|
+
prebuiltImages: overrides.prebuiltImages ?? defaults.prebuiltImages,
|
|
929
933
|
debug: overrides.debug ?? defaults.debug
|
|
930
934
|
};
|
|
931
935
|
}
|
|
@@ -1345,11 +1349,9 @@ var EMBEDDED_DEFAULT_SECCOMP_PROFILE = JSON.stringify({
|
|
|
1345
1349
|
]
|
|
1346
1350
|
});
|
|
1347
1351
|
|
|
1348
|
-
// src/engine/docker.ts
|
|
1349
|
-
init_image_builder();
|
|
1350
|
-
|
|
1351
1352
|
// src/engine/managers/execution-manager.ts
|
|
1352
1353
|
init_logger();
|
|
1354
|
+
init_utils();
|
|
1353
1355
|
import { PassThrough } from "node:stream";
|
|
1354
1356
|
|
|
1355
1357
|
class ExecutionManager {
|
|
@@ -1453,6 +1455,63 @@ class ExecutionManager {
|
|
|
1453
1455
|
stream.on("error", reject);
|
|
1454
1456
|
});
|
|
1455
1457
|
}
|
|
1458
|
+
async runSetupScript(container, script, timeoutMs, volumeManager) {
|
|
1459
|
+
const scriptPath = "/sandbox/.isol8-setup.sh";
|
|
1460
|
+
await volumeManager.writeFileViaExec(container, scriptPath, script);
|
|
1461
|
+
const chmodExec = await container.exec({
|
|
1462
|
+
Cmd: ["chmod", "+x", scriptPath],
|
|
1463
|
+
User: "sandbox"
|
|
1464
|
+
});
|
|
1465
|
+
await chmodExec.start({ Detach: true });
|
|
1466
|
+
let chmodInfo = await chmodExec.inspect();
|
|
1467
|
+
while (chmodInfo.Running) {
|
|
1468
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
1469
|
+
chmodInfo = await chmodExec.inspect();
|
|
1470
|
+
}
|
|
1471
|
+
const timeoutSec = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
1472
|
+
const cmd = this.wrapWithTimeout(["bash", scriptPath], timeoutSec);
|
|
1473
|
+
logger.debug(`Running setup script: ${JSON.stringify(cmd)}`);
|
|
1474
|
+
const env = [
|
|
1475
|
+
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
|
|
1476
|
+
];
|
|
1477
|
+
const exec = await container.exec({
|
|
1478
|
+
Cmd: cmd,
|
|
1479
|
+
AttachStdout: true,
|
|
1480
|
+
AttachStderr: true,
|
|
1481
|
+
Env: env,
|
|
1482
|
+
WorkingDir: "/sandbox",
|
|
1483
|
+
User: "sandbox"
|
|
1484
|
+
});
|
|
1485
|
+
const stream = await exec.start({ Detach: false, Tty: false });
|
|
1486
|
+
return new Promise((resolve2, reject) => {
|
|
1487
|
+
let stderr = "";
|
|
1488
|
+
const stdoutStream = new PassThrough;
|
|
1489
|
+
const stderrStream = new PassThrough;
|
|
1490
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1491
|
+
stderrStream.on("data", (chunk) => {
|
|
1492
|
+
const text = chunk.toString();
|
|
1493
|
+
stderr += text;
|
|
1494
|
+
logger.debug(`[setup:stderr] ${text.trimEnd()}`);
|
|
1495
|
+
});
|
|
1496
|
+
stdoutStream.on("data", (chunk) => {
|
|
1497
|
+
const text = chunk.toString();
|
|
1498
|
+
logger.debug(`[setup:stdout] ${text.trimEnd()}`);
|
|
1499
|
+
});
|
|
1500
|
+
stream.on("end", async () => {
|
|
1501
|
+
try {
|
|
1502
|
+
const info = await exec.inspect();
|
|
1503
|
+
if (info.ExitCode !== 0) {
|
|
1504
|
+
reject(new Error(`Setup script failed (exit code ${info.ExitCode}): ${stderr}`));
|
|
1505
|
+
} else {
|
|
1506
|
+
resolve2();
|
|
1507
|
+
}
|
|
1508
|
+
} catch (err) {
|
|
1509
|
+
reject(err);
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
stream.on("error", reject);
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1456
1515
|
async* streamExecOutput(stream, exec, container, timeoutMs) {
|
|
1457
1516
|
const queue = [];
|
|
1458
1517
|
let resolve2 = null;
|
|
@@ -1707,6 +1766,7 @@ class NetworkManager {
|
|
|
1707
1766
|
}
|
|
1708
1767
|
}
|
|
1709
1768
|
// src/engine/managers/volume-manager.ts
|
|
1769
|
+
init_utils();
|
|
1710
1770
|
import { PassThrough as PassThrough2 } from "node:stream";
|
|
1711
1771
|
|
|
1712
1772
|
class VolumeManager {
|
|
@@ -1787,13 +1847,13 @@ class VolumeManager {
|
|
|
1787
1847
|
const b64Output = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1788
1848
|
return Buffer.from(b64Output, "base64");
|
|
1789
1849
|
}
|
|
1790
|
-
async getFileFromContainer(container,
|
|
1791
|
-
const stream = await container.getArchive({ path });
|
|
1850
|
+
async getFileFromContainer(container, path2) {
|
|
1851
|
+
const stream = await container.getArchive({ path: path2 });
|
|
1792
1852
|
const chunks = [];
|
|
1793
1853
|
for await (const chunk of stream) {
|
|
1794
1854
|
chunks.push(chunk);
|
|
1795
1855
|
}
|
|
1796
|
-
return extractFromTar(Buffer.concat(chunks),
|
|
1856
|
+
return extractFromTar(Buffer.concat(chunks), path2);
|
|
1797
1857
|
}
|
|
1798
1858
|
async retrieveFiles(container, paths) {
|
|
1799
1859
|
const files = {};
|
|
@@ -1805,19 +1865,19 @@ class VolumeManager {
|
|
|
1805
1865
|
}
|
|
1806
1866
|
return files;
|
|
1807
1867
|
}
|
|
1808
|
-
async putFile(container,
|
|
1868
|
+
async putFile(container, path2, content) {
|
|
1809
1869
|
if (this.readonlyRootFs) {
|
|
1810
|
-
await this.writeFileViaExec(container,
|
|
1870
|
+
await this.writeFileViaExec(container, path2, content);
|
|
1811
1871
|
} else {
|
|
1812
|
-
const tar = createTarBuffer(
|
|
1872
|
+
const tar = createTarBuffer(path2, content);
|
|
1813
1873
|
await container.putArchive(tar, { path: "/" });
|
|
1814
1874
|
}
|
|
1815
1875
|
}
|
|
1816
|
-
async getFile(container,
|
|
1876
|
+
async getFile(container, path2) {
|
|
1817
1877
|
if (this.readonlyRootFs) {
|
|
1818
|
-
return this.readFileViaExec(container,
|
|
1878
|
+
return this.readFileViaExec(container, path2);
|
|
1819
1879
|
}
|
|
1820
|
-
return this.getFileFromContainer(container,
|
|
1880
|
+
return this.getFileFromContainer(container, path2);
|
|
1821
1881
|
}
|
|
1822
1882
|
}
|
|
1823
1883
|
// src/engine/pool.ts
|
|
@@ -2099,6 +2159,7 @@ function calculateResourceDelta(before, after) {
|
|
|
2099
2159
|
}
|
|
2100
2160
|
|
|
2101
2161
|
// src/engine/docker.ts
|
|
2162
|
+
init_utils();
|
|
2102
2163
|
var SANDBOX_WORKDIR = "/sandbox";
|
|
2103
2164
|
var MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
2104
2165
|
|
|
@@ -2123,7 +2184,6 @@ class DockerIsol8 {
|
|
|
2123
2184
|
logNetwork;
|
|
2124
2185
|
poolStrategy;
|
|
2125
2186
|
poolSize;
|
|
2126
|
-
dependencies;
|
|
2127
2187
|
auditLogger;
|
|
2128
2188
|
remoteCodePolicy;
|
|
2129
2189
|
networkManager;
|
|
@@ -2173,7 +2233,6 @@ class DockerIsol8 {
|
|
|
2173
2233
|
this.logNetwork = options.logNetwork ?? false;
|
|
2174
2234
|
this.poolStrategy = options.poolStrategy ?? "fast";
|
|
2175
2235
|
this.poolSize = options.poolSize ?? { clean: 1, dirty: 1 };
|
|
2176
|
-
this.dependencies = options.dependencies ?? {};
|
|
2177
2236
|
this.remoteCodePolicy = options.remoteCode ?? {
|
|
2178
2237
|
enabled: false,
|
|
2179
2238
|
allowedSchemes: ["https"],
|
|
@@ -2217,7 +2276,8 @@ class DockerIsol8 {
|
|
|
2217
2276
|
const adapters2 = typeof prewarm === "object" && prewarm.runtimes?.length ? prewarm.runtimes.map((runtime) => RuntimeRegistry.get(runtime)) : RuntimeRegistry.list();
|
|
2218
2277
|
for (const adapter of adapters2) {
|
|
2219
2278
|
try {
|
|
2220
|
-
|
|
2279
|
+
const resolved = await this.resolveImage(adapter);
|
|
2280
|
+
images.add(resolved.image);
|
|
2221
2281
|
} catch (err) {
|
|
2222
2282
|
logger.debug(`[Pool] Pre-warm image resolution failed for ${adapter.name}: ${err}`);
|
|
2223
2283
|
}
|
|
@@ -2374,17 +2434,17 @@ class DockerIsol8 {
|
|
|
2374
2434
|
} catch {}
|
|
2375
2435
|
return logs;
|
|
2376
2436
|
}
|
|
2377
|
-
async putFile(
|
|
2437
|
+
async putFile(path2, content) {
|
|
2378
2438
|
if (!this.container) {
|
|
2379
2439
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
2380
2440
|
}
|
|
2381
|
-
await this.volumeManager.putFile(this.container,
|
|
2441
|
+
await this.volumeManager.putFile(this.container, path2, content);
|
|
2382
2442
|
}
|
|
2383
|
-
async getFile(
|
|
2443
|
+
async getFile(path2) {
|
|
2384
2444
|
if (!this.container) {
|
|
2385
2445
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
2386
2446
|
}
|
|
2387
|
-
return this.volumeManager.getFile(this.container,
|
|
2447
|
+
return this.volumeManager.getFile(this.container, path2);
|
|
2388
2448
|
}
|
|
2389
2449
|
get containerId() {
|
|
2390
2450
|
return this.container?.id ?? null;
|
|
@@ -2395,7 +2455,9 @@ class DockerIsol8 {
|
|
|
2395
2455
|
const request = await this.resolveExecutionRequest(req);
|
|
2396
2456
|
const adapter = this.getAdapter(request.runtime);
|
|
2397
2457
|
const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
|
|
2398
|
-
const
|
|
2458
|
+
const resolved = await this.resolveImage(adapter, request.installPackages);
|
|
2459
|
+
const image = resolved.image;
|
|
2460
|
+
const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
|
|
2399
2461
|
const container = await this.docker.createContainer({
|
|
2400
2462
|
Image: image,
|
|
2401
2463
|
Cmd: ["sleep", "infinity"],
|
|
@@ -2412,8 +2474,14 @@ class DockerIsol8 {
|
|
|
2412
2474
|
const ext = request.fileExtension ?? adapter.getFileExtension();
|
|
2413
2475
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2414
2476
|
await this.volumeManager.writeFileViaExec(container, filePath, request.code);
|
|
2415
|
-
if (
|
|
2416
|
-
await this.executionManager.installPackages(container, request.runtime,
|
|
2477
|
+
if (resolved.remainingPackages.length > 0) {
|
|
2478
|
+
await this.executionManager.installPackages(container, request.runtime, resolved.remainingPackages, timeoutMs);
|
|
2479
|
+
}
|
|
2480
|
+
if (resolved.imageSetupScript) {
|
|
2481
|
+
await this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager);
|
|
2482
|
+
}
|
|
2483
|
+
if (request.setupScript) {
|
|
2484
|
+
await this.executionManager.runSetupScript(container, request.setupScript, timeoutMs, this.volumeManager);
|
|
2417
2485
|
}
|
|
2418
2486
|
if (request.files) {
|
|
2419
2487
|
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
@@ -2436,7 +2504,7 @@ class DockerIsol8 {
|
|
|
2436
2504
|
Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2437
2505
|
AttachStdout: true,
|
|
2438
2506
|
AttachStderr: true,
|
|
2439
|
-
WorkingDir:
|
|
2507
|
+
WorkingDir: execWorkdir,
|
|
2440
2508
|
User: "sandbox"
|
|
2441
2509
|
});
|
|
2442
2510
|
const execStream = await exec.start({ Tty: false });
|
|
@@ -2454,55 +2522,90 @@ class DockerIsol8 {
|
|
|
2454
2522
|
this.semaphore.release();
|
|
2455
2523
|
}
|
|
2456
2524
|
}
|
|
2457
|
-
async resolveImage(adapter) {
|
|
2525
|
+
async resolveImage(adapter, requestedPackages) {
|
|
2458
2526
|
if (this.overrideImage) {
|
|
2459
|
-
|
|
2527
|
+
let imageSetupScript2;
|
|
2528
|
+
try {
|
|
2529
|
+
const { LABELS: LABELS2 } = await Promise.resolve().then(() => (init_image_builder(), exports_image_builder));
|
|
2530
|
+
const inspect = await this.docker.getImage(this.overrideImage).inspect();
|
|
2531
|
+
const labels = inspect.Config?.Labels ?? {};
|
|
2532
|
+
imageSetupScript2 = labels[LABELS2.setupScript] || undefined;
|
|
2533
|
+
} catch {}
|
|
2534
|
+
return {
|
|
2535
|
+
image: this.overrideImage,
|
|
2536
|
+
remainingPackages: requestedPackages ?? [],
|
|
2537
|
+
imageSetupScript: imageSetupScript2
|
|
2538
|
+
};
|
|
2460
2539
|
}
|
|
2461
|
-
const cacheKey = adapter.
|
|
2540
|
+
const cacheKey = `${adapter.name}:${(requestedPackages ?? []).join(",")}`;
|
|
2462
2541
|
const cached = this.imageCache.get(cacheKey);
|
|
2463
2542
|
if (cached) {
|
|
2464
|
-
|
|
2465
|
-
}
|
|
2466
|
-
let resolvedImage = adapter.image;
|
|
2467
|
-
const configuredDeps = this.dependencies[adapter.name];
|
|
2468
|
-
const normalizedDeps = configuredDeps ? normalizePackages(configuredDeps) : [];
|
|
2469
|
-
if (normalizedDeps.length > 0) {
|
|
2470
|
-
const hashedCustomTag = getCustomImageTag(adapter.name, normalizedDeps);
|
|
2543
|
+
let imageSetupScript2;
|
|
2471
2544
|
try {
|
|
2472
|
-
await
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2545
|
+
const { LABELS: LABELS2 } = await Promise.resolve().then(() => (init_image_builder(), exports_image_builder));
|
|
2546
|
+
const inspect = await this.docker.getImage(cached).inspect();
|
|
2547
|
+
const labels = inspect.Config?.Labels ?? {};
|
|
2548
|
+
imageSetupScript2 = labels[LABELS2.setupScript] || undefined;
|
|
2549
|
+
} catch {}
|
|
2550
|
+
return { image: cached, remainingPackages: [], imageSetupScript: imageSetupScript2 };
|
|
2551
|
+
}
|
|
2552
|
+
const baseImage = adapter.image;
|
|
2553
|
+
let bestImage = baseImage;
|
|
2554
|
+
let remainingPackages = requestedPackages ?? [];
|
|
2555
|
+
let imageSetupScript;
|
|
2556
|
+
if (requestedPackages && requestedPackages.length > 0) {
|
|
2557
|
+
const { LABELS: LABELS2, normalizePackages: normalizePackages2 } = await Promise.resolve().then(() => (init_image_builder(), exports_image_builder));
|
|
2558
|
+
const normalizedReq = normalizePackages2(requestedPackages);
|
|
2559
|
+
const images = await this.docker.listImages({
|
|
2560
|
+
filters: {
|
|
2561
|
+
label: [`${LABELS2.runtime}=${adapter.name}`]
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2564
|
+
for (const img of images) {
|
|
2565
|
+
if (!img.RepoTags || img.RepoTags.length === 0) {
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
const depsLabel = img.Labels?.[LABELS2.dependencies];
|
|
2569
|
+
if (!depsLabel) {
|
|
2570
|
+
continue;
|
|
2571
|
+
}
|
|
2572
|
+
const imgDeps = depsLabel.split(",");
|
|
2573
|
+
if (img.RepoTags[0] && normalizedReq.length === imgDeps.length && normalizedReq.every((p) => imgDeps.includes(p))) {
|
|
2574
|
+
bestImage = img.RepoTags[0];
|
|
2575
|
+
remainingPackages = [];
|
|
2576
|
+
imageSetupScript = img.Labels?.[LABELS2.setupScript] || undefined;
|
|
2577
|
+
logger.debug(`[Docker] Found exact custom image match: ${bestImage}`);
|
|
2578
|
+
break;
|
|
2579
|
+
}
|
|
2580
|
+
if (img.RepoTags[0] && normalizedReq.every((p) => imgDeps.includes(p))) {
|
|
2581
|
+
bestImage = img.RepoTags[0];
|
|
2582
|
+
remainingPackages = [];
|
|
2583
|
+
imageSetupScript = img.Labels?.[LABELS2.setupScript] || undefined;
|
|
2584
|
+
logger.debug(`[Docker] Found superset custom image match: ${bestImage}`);
|
|
2585
|
+
}
|
|
2476
2586
|
}
|
|
2477
2587
|
}
|
|
2478
|
-
if (
|
|
2479
|
-
const legacyCustomTag = `${adapter.image}-custom`;
|
|
2588
|
+
if (bestImage !== baseImage && imageSetupScript === undefined) {
|
|
2480
2589
|
try {
|
|
2481
|
-
await
|
|
2482
|
-
|
|
2590
|
+
const { LABELS: LABELS2 } = await Promise.resolve().then(() => (init_image_builder(), exports_image_builder));
|
|
2591
|
+
const inspect = await this.docker.getImage(bestImage).inspect();
|
|
2592
|
+
const labels = inspect.Config?.Labels ?? {};
|
|
2593
|
+
imageSetupScript = labels[LABELS2.setupScript] || undefined;
|
|
2483
2594
|
} catch {}
|
|
2484
2595
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
try {
|
|
2492
|
-
await this.docker.getImage(adapter.image).inspect();
|
|
2493
|
-
} catch {
|
|
2494
|
-
logger.debug(`[ImageBuilder] Base image ${adapter.image} missing. Building...`);
|
|
2495
|
-
await buildBaseImages2(this.docker, undefined, false, [adapter.name]);
|
|
2496
|
-
}
|
|
2497
|
-
logger.debug(`[ImageBuilder] Building custom image for ${adapter.name}...`);
|
|
2498
|
-
await buildCustomImage2(this.docker, adapter.name, normalizedDeps);
|
|
2499
|
-
} else {
|
|
2500
|
-
logger.debug(`[ImageBuilder] Building base image for ${adapter.name}...`);
|
|
2596
|
+
if (bestImage === baseImage) {
|
|
2597
|
+
try {
|
|
2598
|
+
await this.docker.getImage(baseImage).inspect();
|
|
2599
|
+
} catch {
|
|
2600
|
+
logger.debug(`[Docker] Base image ${baseImage} not found. Building...`);
|
|
2601
|
+
const { buildBaseImages: buildBaseImages2 } = await Promise.resolve().then(() => (init_image_builder(), exports_image_builder));
|
|
2501
2602
|
await buildBaseImages2(this.docker, undefined, false, [adapter.name]);
|
|
2502
2603
|
}
|
|
2503
2604
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2605
|
+
if (remainingPackages.length === 0) {
|
|
2606
|
+
this.imageCache.set(cacheKey, bestImage);
|
|
2607
|
+
}
|
|
2608
|
+
return { image: bestImage, remainingPackages, imageSetupScript };
|
|
2506
2609
|
}
|
|
2507
2610
|
ensurePool() {
|
|
2508
2611
|
if (!this.pool) {
|
|
@@ -2527,7 +2630,9 @@ class DockerIsol8 {
|
|
|
2527
2630
|
async executeEphemeral(req, startTime) {
|
|
2528
2631
|
const adapter = this.getAdapter(req.runtime);
|
|
2529
2632
|
const timeoutMs = req.timeoutMs ?? this.defaultTimeoutMs;
|
|
2530
|
-
const
|
|
2633
|
+
const resolved = await this.resolveImage(adapter, req.installPackages);
|
|
2634
|
+
const image = resolved.image;
|
|
2635
|
+
const execWorkdir = req.workdir ? resolveWorkdir(req.workdir) : SANDBOX_WORKDIR;
|
|
2531
2636
|
const pool = this.ensurePool();
|
|
2532
2637
|
const container = await pool.acquire(image);
|
|
2533
2638
|
let startStats;
|
|
@@ -2558,8 +2663,14 @@ class DockerIsol8 {
|
|
|
2558
2663
|
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2559
2664
|
rawCmd = adapter.getCommand(req.code, filePath);
|
|
2560
2665
|
}
|
|
2561
|
-
if (
|
|
2562
|
-
await this.executionManager.installPackages(container, req.runtime,
|
|
2666
|
+
if (resolved.remainingPackages.length > 0) {
|
|
2667
|
+
await this.executionManager.installPackages(container, req.runtime, resolved.remainingPackages, timeoutMs);
|
|
2668
|
+
}
|
|
2669
|
+
if (resolved.imageSetupScript) {
|
|
2670
|
+
await this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager);
|
|
2671
|
+
}
|
|
2672
|
+
if (req.setupScript) {
|
|
2673
|
+
await this.executionManager.runSetupScript(container, req.setupScript, timeoutMs, this.volumeManager);
|
|
2563
2674
|
}
|
|
2564
2675
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2565
2676
|
let cmd;
|
|
@@ -2581,7 +2692,7 @@ class DockerIsol8 {
|
|
|
2581
2692
|
Env: this.executionManager.buildEnv(req.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2582
2693
|
AttachStdout: true,
|
|
2583
2694
|
AttachStderr: true,
|
|
2584
|
-
WorkingDir:
|
|
2695
|
+
WorkingDir: execWorkdir,
|
|
2585
2696
|
User: "sandbox"
|
|
2586
2697
|
});
|
|
2587
2698
|
const start = performance.now();
|
|
@@ -2641,8 +2752,13 @@ class DockerIsol8 {
|
|
|
2641
2752
|
async executePersistent(req, startTime) {
|
|
2642
2753
|
const adapter = this.getAdapter(req.runtime);
|
|
2643
2754
|
const timeoutMs = req.timeoutMs ?? this.defaultTimeoutMs;
|
|
2755
|
+
const execWorkdir = req.workdir ? resolveWorkdir(req.workdir) : SANDBOX_WORKDIR;
|
|
2756
|
+
let remainingPackages = req.installPackages ?? [];
|
|
2757
|
+
let imageSetupScript;
|
|
2644
2758
|
if (!this.container) {
|
|
2645
|
-
await this.startPersistentContainer(adapter);
|
|
2759
|
+
const started = await this.startPersistentContainer(adapter, req.installPackages);
|
|
2760
|
+
remainingPackages = started.remainingPackages;
|
|
2761
|
+
imageSetupScript = started.imageSetupScript;
|
|
2646
2762
|
} else if (this.persistentRuntime?.name !== adapter.name) {
|
|
2647
2763
|
throw new Error(`Cannot switch runtime from "${this.persistentRuntime?.name}" to "${adapter.name}". Each persistent container supports a single runtime. Create a new Isol8 instance for a different runtime.`);
|
|
2648
2764
|
}
|
|
@@ -2656,8 +2772,14 @@ class DockerIsol8 {
|
|
|
2656
2772
|
}
|
|
2657
2773
|
const rawCmd = adapter.getCommand(req.code, filePath);
|
|
2658
2774
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2659
|
-
if (
|
|
2660
|
-
await this.executionManager.installPackages(this.container, req.runtime,
|
|
2775
|
+
if (remainingPackages.length > 0) {
|
|
2776
|
+
await this.executionManager.installPackages(this.container, req.runtime, remainingPackages, timeoutMs);
|
|
2777
|
+
}
|
|
2778
|
+
if (imageSetupScript) {
|
|
2779
|
+
await this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager);
|
|
2780
|
+
}
|
|
2781
|
+
if (req.setupScript) {
|
|
2782
|
+
await this.executionManager.runSetupScript(this.container, req.setupScript, timeoutMs, this.volumeManager);
|
|
2661
2783
|
}
|
|
2662
2784
|
let cmd;
|
|
2663
2785
|
if (req.stdin) {
|
|
@@ -2674,7 +2796,7 @@ class DockerIsol8 {
|
|
|
2674
2796
|
Env: execEnv,
|
|
2675
2797
|
AttachStdout: true,
|
|
2676
2798
|
AttachStderr: true,
|
|
2677
|
-
WorkingDir:
|
|
2799
|
+
WorkingDir: execWorkdir,
|
|
2678
2800
|
User: "sandbox"
|
|
2679
2801
|
});
|
|
2680
2802
|
const start = performance.now();
|
|
@@ -2729,10 +2851,10 @@ class DockerIsol8 {
|
|
|
2729
2851
|
async retrieveFiles(container, paths) {
|
|
2730
2852
|
return this.volumeManager.retrieveFiles(container, paths);
|
|
2731
2853
|
}
|
|
2732
|
-
async startPersistentContainer(adapter) {
|
|
2733
|
-
const
|
|
2854
|
+
async startPersistentContainer(adapter, requestedPackages) {
|
|
2855
|
+
const resolved = await this.resolveImage(adapter, requestedPackages);
|
|
2734
2856
|
this.container = await this.docker.createContainer({
|
|
2735
|
-
Image: image,
|
|
2857
|
+
Image: resolved.image,
|
|
2736
2858
|
Cmd: ["sleep", "infinity"],
|
|
2737
2859
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2738
2860
|
Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
@@ -2748,6 +2870,10 @@ class DockerIsol8 {
|
|
|
2748
2870
|
await this.networkManager.startProxy(this.container);
|
|
2749
2871
|
await this.networkManager.setupIptables(this.container);
|
|
2750
2872
|
this.persistentRuntime = adapter;
|
|
2873
|
+
return {
|
|
2874
|
+
remainingPackages: resolved.remainingPackages,
|
|
2875
|
+
imageSetupScript: resolved.imageSetupScript
|
|
2876
|
+
};
|
|
2751
2877
|
}
|
|
2752
2878
|
getAdapter(runtime) {
|
|
2753
2879
|
return RuntimeRegistry.get(runtime);
|
|
@@ -2860,7 +2986,7 @@ init_logger();
|
|
|
2860
2986
|
// package.json
|
|
2861
2987
|
var package_default = {
|
|
2862
2988
|
name: "@isol8/core",
|
|
2863
|
-
version: "0.
|
|
2989
|
+
version: "0.18.0",
|
|
2864
2990
|
description: "Sandboxed code execution engine for AI agents and apps (Docker, runtime and network controls)",
|
|
2865
2991
|
author: "Illusion47586",
|
|
2866
2992
|
license: "MIT",
|
|
@@ -2928,8 +3054,7 @@ var VERSION = package_default.version;
|
|
|
2928
3054
|
export {
|
|
2929
3055
|
logger,
|
|
2930
3056
|
loadConfig,
|
|
2931
|
-
|
|
2932
|
-
buildCustomImages,
|
|
3057
|
+
imageExists,
|
|
2933
3058
|
buildCustomImage,
|
|
2934
3059
|
buildBaseImages,
|
|
2935
3060
|
bashAdapter,
|
|
@@ -2939,9 +3064,10 @@ export {
|
|
|
2939
3064
|
RemoteIsol8,
|
|
2940
3065
|
PythonAdapter,
|
|
2941
3066
|
NodeAdapter,
|
|
3067
|
+
LABELS,
|
|
2942
3068
|
DockerIsol8,
|
|
2943
3069
|
DenoAdapter,
|
|
2944
3070
|
BunAdapter
|
|
2945
3071
|
};
|
|
2946
3072
|
|
|
2947
|
-
//# debugId=
|
|
3073
|
+
//# debugId=014F8E5DF8C3A76364756E2164756E21
|