@isol8/core 0.16.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/client/remote.d.ts +11 -1
- package/dist/client/remote.d.ts.map +1 -1
- 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 +268 -126
- package/dist/index.js.map +9 -9
- package/dist/types.d.ts +51 -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
|
});
|
|
@@ -776,6 +783,22 @@ class RemoteIsol8 {
|
|
|
776
783
|
const body = await res.json();
|
|
777
784
|
return Buffer.from(body.content, "base64");
|
|
778
785
|
}
|
|
786
|
+
async listSessions() {
|
|
787
|
+
const res = await this.fetch("/sessions");
|
|
788
|
+
if (!res.ok) {
|
|
789
|
+
const body2 = await res.json().catch(() => ({}));
|
|
790
|
+
throw new Error(`Failed to list sessions: ${body2.error ?? res.statusText}`);
|
|
791
|
+
}
|
|
792
|
+
const body = await res.json();
|
|
793
|
+
return body.sessions;
|
|
794
|
+
}
|
|
795
|
+
async deleteSession(sessionId) {
|
|
796
|
+
const res = await this.fetch(`/session/${sessionId}`, { method: "DELETE" });
|
|
797
|
+
if (!res.ok) {
|
|
798
|
+
const body = await res.json().catch(() => ({}));
|
|
799
|
+
throw new Error(`Failed to delete session: ${body.error ?? res.statusText}`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
779
802
|
async fetch(path, init) {
|
|
780
803
|
return globalThis.fetch(`${this.host}${path}`, {
|
|
781
804
|
...init,
|
|
@@ -812,7 +835,6 @@ var DEFAULT_CONFIG = {
|
|
|
812
835
|
},
|
|
813
836
|
poolStrategy: "fast",
|
|
814
837
|
poolSize: { clean: 1, dirty: 1 },
|
|
815
|
-
dependencies: {},
|
|
816
838
|
security: {
|
|
817
839
|
seccomp: "strict"
|
|
818
840
|
},
|
|
@@ -853,6 +875,7 @@ var DEFAULT_CONFIG = {
|
|
|
853
875
|
defaultTtlMs: 86400000,
|
|
854
876
|
cleanupIntervalMs: 3600000
|
|
855
877
|
},
|
|
878
|
+
prebuiltImages: [],
|
|
856
879
|
debug: false
|
|
857
880
|
};
|
|
858
881
|
function loadConfig(cwd) {
|
|
@@ -887,10 +910,6 @@ function mergeConfig(defaults, overrides) {
|
|
|
887
910
|
},
|
|
888
911
|
poolStrategy: overrides.poolStrategy ?? defaults.poolStrategy,
|
|
889
912
|
poolSize: overrides.poolSize ?? defaults.poolSize,
|
|
890
|
-
dependencies: {
|
|
891
|
-
...defaults.dependencies,
|
|
892
|
-
...overrides.dependencies
|
|
893
|
-
},
|
|
894
913
|
security: {
|
|
895
914
|
seccomp: overrides.security?.seccomp ?? defaults.security.seccomp,
|
|
896
915
|
customProfilePath: overrides.security?.customProfilePath ?? defaults.security.customProfilePath
|
|
@@ -910,6 +929,7 @@ function mergeConfig(defaults, overrides) {
|
|
|
910
929
|
...defaults.auth,
|
|
911
930
|
...overrides.auth
|
|
912
931
|
},
|
|
932
|
+
prebuiltImages: overrides.prebuiltImages ?? defaults.prebuiltImages,
|
|
913
933
|
debug: overrides.debug ?? defaults.debug
|
|
914
934
|
};
|
|
915
935
|
}
|
|
@@ -1329,11 +1349,9 @@ var EMBEDDED_DEFAULT_SECCOMP_PROFILE = JSON.stringify({
|
|
|
1329
1349
|
]
|
|
1330
1350
|
});
|
|
1331
1351
|
|
|
1332
|
-
// src/engine/docker.ts
|
|
1333
|
-
init_image_builder();
|
|
1334
|
-
|
|
1335
1352
|
// src/engine/managers/execution-manager.ts
|
|
1336
1353
|
init_logger();
|
|
1354
|
+
init_utils();
|
|
1337
1355
|
import { PassThrough } from "node:stream";
|
|
1338
1356
|
|
|
1339
1357
|
class ExecutionManager {
|
|
@@ -1437,6 +1455,63 @@ class ExecutionManager {
|
|
|
1437
1455
|
stream.on("error", reject);
|
|
1438
1456
|
});
|
|
1439
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
|
+
}
|
|
1440
1515
|
async* streamExecOutput(stream, exec, container, timeoutMs) {
|
|
1441
1516
|
const queue = [];
|
|
1442
1517
|
let resolve2 = null;
|
|
@@ -1691,6 +1766,7 @@ class NetworkManager {
|
|
|
1691
1766
|
}
|
|
1692
1767
|
}
|
|
1693
1768
|
// src/engine/managers/volume-manager.ts
|
|
1769
|
+
init_utils();
|
|
1694
1770
|
import { PassThrough as PassThrough2 } from "node:stream";
|
|
1695
1771
|
|
|
1696
1772
|
class VolumeManager {
|
|
@@ -1771,13 +1847,13 @@ class VolumeManager {
|
|
|
1771
1847
|
const b64Output = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1772
1848
|
return Buffer.from(b64Output, "base64");
|
|
1773
1849
|
}
|
|
1774
|
-
async getFileFromContainer(container,
|
|
1775
|
-
const stream = await container.getArchive({ path });
|
|
1850
|
+
async getFileFromContainer(container, path2) {
|
|
1851
|
+
const stream = await container.getArchive({ path: path2 });
|
|
1776
1852
|
const chunks = [];
|
|
1777
1853
|
for await (const chunk of stream) {
|
|
1778
1854
|
chunks.push(chunk);
|
|
1779
1855
|
}
|
|
1780
|
-
return extractFromTar(Buffer.concat(chunks),
|
|
1856
|
+
return extractFromTar(Buffer.concat(chunks), path2);
|
|
1781
1857
|
}
|
|
1782
1858
|
async retrieveFiles(container, paths) {
|
|
1783
1859
|
const files = {};
|
|
@@ -1789,19 +1865,19 @@ class VolumeManager {
|
|
|
1789
1865
|
}
|
|
1790
1866
|
return files;
|
|
1791
1867
|
}
|
|
1792
|
-
async putFile(container,
|
|
1868
|
+
async putFile(container, path2, content) {
|
|
1793
1869
|
if (this.readonlyRootFs) {
|
|
1794
|
-
await this.writeFileViaExec(container,
|
|
1870
|
+
await this.writeFileViaExec(container, path2, content);
|
|
1795
1871
|
} else {
|
|
1796
|
-
const tar = createTarBuffer(
|
|
1872
|
+
const tar = createTarBuffer(path2, content);
|
|
1797
1873
|
await container.putArchive(tar, { path: "/" });
|
|
1798
1874
|
}
|
|
1799
1875
|
}
|
|
1800
|
-
async getFile(container,
|
|
1876
|
+
async getFile(container, path2) {
|
|
1801
1877
|
if (this.readonlyRootFs) {
|
|
1802
|
-
return this.readFileViaExec(container,
|
|
1878
|
+
return this.readFileViaExec(container, path2);
|
|
1803
1879
|
}
|
|
1804
|
-
return this.getFileFromContainer(container,
|
|
1880
|
+
return this.getFileFromContainer(container, path2);
|
|
1805
1881
|
}
|
|
1806
1882
|
}
|
|
1807
1883
|
// src/engine/pool.ts
|
|
@@ -2083,6 +2159,7 @@ function calculateResourceDelta(before, after) {
|
|
|
2083
2159
|
}
|
|
2084
2160
|
|
|
2085
2161
|
// src/engine/docker.ts
|
|
2162
|
+
init_utils();
|
|
2086
2163
|
var SANDBOX_WORKDIR = "/sandbox";
|
|
2087
2164
|
var MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
2088
2165
|
|
|
@@ -2107,7 +2184,6 @@ class DockerIsol8 {
|
|
|
2107
2184
|
logNetwork;
|
|
2108
2185
|
poolStrategy;
|
|
2109
2186
|
poolSize;
|
|
2110
|
-
dependencies;
|
|
2111
2187
|
auditLogger;
|
|
2112
2188
|
remoteCodePolicy;
|
|
2113
2189
|
networkManager;
|
|
@@ -2157,7 +2233,6 @@ class DockerIsol8 {
|
|
|
2157
2233
|
this.logNetwork = options.logNetwork ?? false;
|
|
2158
2234
|
this.poolStrategy = options.poolStrategy ?? "fast";
|
|
2159
2235
|
this.poolSize = options.poolSize ?? { clean: 1, dirty: 1 };
|
|
2160
|
-
this.dependencies = options.dependencies ?? {};
|
|
2161
2236
|
this.remoteCodePolicy = options.remoteCode ?? {
|
|
2162
2237
|
enabled: false,
|
|
2163
2238
|
allowedSchemes: ["https"],
|
|
@@ -2201,7 +2276,8 @@ class DockerIsol8 {
|
|
|
2201
2276
|
const adapters2 = typeof prewarm === "object" && prewarm.runtimes?.length ? prewarm.runtimes.map((runtime) => RuntimeRegistry.get(runtime)) : RuntimeRegistry.list();
|
|
2202
2277
|
for (const adapter of adapters2) {
|
|
2203
2278
|
try {
|
|
2204
|
-
|
|
2279
|
+
const resolved = await this.resolveImage(adapter);
|
|
2280
|
+
images.add(resolved.image);
|
|
2205
2281
|
} catch (err) {
|
|
2206
2282
|
logger.debug(`[Pool] Pre-warm image resolution failed for ${adapter.name}: ${err}`);
|
|
2207
2283
|
}
|
|
@@ -2358,17 +2434,17 @@ class DockerIsol8 {
|
|
|
2358
2434
|
} catch {}
|
|
2359
2435
|
return logs;
|
|
2360
2436
|
}
|
|
2361
|
-
async putFile(
|
|
2437
|
+
async putFile(path2, content) {
|
|
2362
2438
|
if (!this.container) {
|
|
2363
2439
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
2364
2440
|
}
|
|
2365
|
-
await this.volumeManager.putFile(this.container,
|
|
2441
|
+
await this.volumeManager.putFile(this.container, path2, content);
|
|
2366
2442
|
}
|
|
2367
|
-
async getFile(
|
|
2443
|
+
async getFile(path2) {
|
|
2368
2444
|
if (!this.container) {
|
|
2369
2445
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
2370
2446
|
}
|
|
2371
|
-
return this.volumeManager.getFile(this.container,
|
|
2447
|
+
return this.volumeManager.getFile(this.container, path2);
|
|
2372
2448
|
}
|
|
2373
2449
|
get containerId() {
|
|
2374
2450
|
return this.container?.id ?? null;
|
|
@@ -2379,7 +2455,9 @@ class DockerIsol8 {
|
|
|
2379
2455
|
const request = await this.resolveExecutionRequest(req);
|
|
2380
2456
|
const adapter = this.getAdapter(request.runtime);
|
|
2381
2457
|
const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
|
|
2382
|
-
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;
|
|
2383
2461
|
const container = await this.docker.createContainer({
|
|
2384
2462
|
Image: image,
|
|
2385
2463
|
Cmd: ["sleep", "infinity"],
|
|
@@ -2396,8 +2474,14 @@ class DockerIsol8 {
|
|
|
2396
2474
|
const ext = request.fileExtension ?? adapter.getFileExtension();
|
|
2397
2475
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2398
2476
|
await this.volumeManager.writeFileViaExec(container, filePath, request.code);
|
|
2399
|
-
if (
|
|
2400
|
-
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);
|
|
2401
2485
|
}
|
|
2402
2486
|
if (request.files) {
|
|
2403
2487
|
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
@@ -2420,7 +2504,7 @@ class DockerIsol8 {
|
|
|
2420
2504
|
Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2421
2505
|
AttachStdout: true,
|
|
2422
2506
|
AttachStderr: true,
|
|
2423
|
-
WorkingDir:
|
|
2507
|
+
WorkingDir: execWorkdir,
|
|
2424
2508
|
User: "sandbox"
|
|
2425
2509
|
});
|
|
2426
2510
|
const execStream = await exec.start({ Tty: false });
|
|
@@ -2438,55 +2522,90 @@ class DockerIsol8 {
|
|
|
2438
2522
|
this.semaphore.release();
|
|
2439
2523
|
}
|
|
2440
2524
|
}
|
|
2441
|
-
async resolveImage(adapter) {
|
|
2525
|
+
async resolveImage(adapter, requestedPackages) {
|
|
2442
2526
|
if (this.overrideImage) {
|
|
2443
|
-
|
|
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
|
+
};
|
|
2444
2539
|
}
|
|
2445
|
-
const cacheKey = adapter.
|
|
2540
|
+
const cacheKey = `${adapter.name}:${(requestedPackages ?? []).join(",")}`;
|
|
2446
2541
|
const cached = this.imageCache.get(cacheKey);
|
|
2447
2542
|
if (cached) {
|
|
2448
|
-
|
|
2449
|
-
}
|
|
2450
|
-
let resolvedImage = adapter.image;
|
|
2451
|
-
const configuredDeps = this.dependencies[adapter.name];
|
|
2452
|
-
const normalizedDeps = configuredDeps ? normalizePackages(configuredDeps) : [];
|
|
2453
|
-
if (normalizedDeps.length > 0) {
|
|
2454
|
-
const hashedCustomTag = getCustomImageTag(adapter.name, normalizedDeps);
|
|
2543
|
+
let imageSetupScript2;
|
|
2455
2544
|
try {
|
|
2456
|
-
await
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
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
|
+
}
|
|
2460
2586
|
}
|
|
2461
2587
|
}
|
|
2462
|
-
if (
|
|
2463
|
-
const legacyCustomTag = `${adapter.image}-custom`;
|
|
2588
|
+
if (bestImage !== baseImage && imageSetupScript === undefined) {
|
|
2464
2589
|
try {
|
|
2465
|
-
await
|
|
2466
|
-
|
|
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;
|
|
2467
2594
|
} catch {}
|
|
2468
2595
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
try {
|
|
2476
|
-
await this.docker.getImage(adapter.image).inspect();
|
|
2477
|
-
} catch {
|
|
2478
|
-
logger.debug(`[ImageBuilder] Base image ${adapter.image} missing. Building...`);
|
|
2479
|
-
await buildBaseImages2(this.docker, undefined, false, [adapter.name]);
|
|
2480
|
-
}
|
|
2481
|
-
logger.debug(`[ImageBuilder] Building custom image for ${adapter.name}...`);
|
|
2482
|
-
await buildCustomImage2(this.docker, adapter.name, normalizedDeps);
|
|
2483
|
-
} else {
|
|
2484
|
-
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));
|
|
2485
2602
|
await buildBaseImages2(this.docker, undefined, false, [adapter.name]);
|
|
2486
2603
|
}
|
|
2487
2604
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2605
|
+
if (remainingPackages.length === 0) {
|
|
2606
|
+
this.imageCache.set(cacheKey, bestImage);
|
|
2607
|
+
}
|
|
2608
|
+
return { image: bestImage, remainingPackages, imageSetupScript };
|
|
2490
2609
|
}
|
|
2491
2610
|
ensurePool() {
|
|
2492
2611
|
if (!this.pool) {
|
|
@@ -2511,7 +2630,9 @@ class DockerIsol8 {
|
|
|
2511
2630
|
async executeEphemeral(req, startTime) {
|
|
2512
2631
|
const adapter = this.getAdapter(req.runtime);
|
|
2513
2632
|
const timeoutMs = req.timeoutMs ?? this.defaultTimeoutMs;
|
|
2514
|
-
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;
|
|
2515
2636
|
const pool = this.ensurePool();
|
|
2516
2637
|
const container = await pool.acquire(image);
|
|
2517
2638
|
let startStats;
|
|
@@ -2542,8 +2663,14 @@ class DockerIsol8 {
|
|
|
2542
2663
|
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2543
2664
|
rawCmd = adapter.getCommand(req.code, filePath);
|
|
2544
2665
|
}
|
|
2545
|
-
if (
|
|
2546
|
-
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);
|
|
2547
2674
|
}
|
|
2548
2675
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2549
2676
|
let cmd;
|
|
@@ -2565,7 +2692,7 @@ class DockerIsol8 {
|
|
|
2565
2692
|
Env: this.executionManager.buildEnv(req.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2566
2693
|
AttachStdout: true,
|
|
2567
2694
|
AttachStderr: true,
|
|
2568
|
-
WorkingDir:
|
|
2695
|
+
WorkingDir: execWorkdir,
|
|
2569
2696
|
User: "sandbox"
|
|
2570
2697
|
});
|
|
2571
2698
|
const start = performance.now();
|
|
@@ -2625,8 +2752,13 @@ class DockerIsol8 {
|
|
|
2625
2752
|
async executePersistent(req, startTime) {
|
|
2626
2753
|
const adapter = this.getAdapter(req.runtime);
|
|
2627
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;
|
|
2628
2758
|
if (!this.container) {
|
|
2629
|
-
await this.startPersistentContainer(adapter);
|
|
2759
|
+
const started = await this.startPersistentContainer(adapter, req.installPackages);
|
|
2760
|
+
remainingPackages = started.remainingPackages;
|
|
2761
|
+
imageSetupScript = started.imageSetupScript;
|
|
2630
2762
|
} else if (this.persistentRuntime?.name !== adapter.name) {
|
|
2631
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.`);
|
|
2632
2764
|
}
|
|
@@ -2640,8 +2772,14 @@ class DockerIsol8 {
|
|
|
2640
2772
|
}
|
|
2641
2773
|
const rawCmd = adapter.getCommand(req.code, filePath);
|
|
2642
2774
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2643
|
-
if (
|
|
2644
|
-
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);
|
|
2645
2783
|
}
|
|
2646
2784
|
let cmd;
|
|
2647
2785
|
if (req.stdin) {
|
|
@@ -2658,7 +2796,7 @@ class DockerIsol8 {
|
|
|
2658
2796
|
Env: execEnv,
|
|
2659
2797
|
AttachStdout: true,
|
|
2660
2798
|
AttachStderr: true,
|
|
2661
|
-
WorkingDir:
|
|
2799
|
+
WorkingDir: execWorkdir,
|
|
2662
2800
|
User: "sandbox"
|
|
2663
2801
|
});
|
|
2664
2802
|
const start = performance.now();
|
|
@@ -2713,10 +2851,10 @@ class DockerIsol8 {
|
|
|
2713
2851
|
async retrieveFiles(container, paths) {
|
|
2714
2852
|
return this.volumeManager.retrieveFiles(container, paths);
|
|
2715
2853
|
}
|
|
2716
|
-
async startPersistentContainer(adapter) {
|
|
2717
|
-
const
|
|
2854
|
+
async startPersistentContainer(adapter, requestedPackages) {
|
|
2855
|
+
const resolved = await this.resolveImage(adapter, requestedPackages);
|
|
2718
2856
|
this.container = await this.docker.createContainer({
|
|
2719
|
-
Image: image,
|
|
2857
|
+
Image: resolved.image,
|
|
2720
2858
|
Cmd: ["sleep", "infinity"],
|
|
2721
2859
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2722
2860
|
Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
@@ -2732,6 +2870,10 @@ class DockerIsol8 {
|
|
|
2732
2870
|
await this.networkManager.startProxy(this.container);
|
|
2733
2871
|
await this.networkManager.setupIptables(this.container);
|
|
2734
2872
|
this.persistentRuntime = adapter;
|
|
2873
|
+
return {
|
|
2874
|
+
remainingPackages: resolved.remainingPackages,
|
|
2875
|
+
imageSetupScript: resolved.imageSetupScript
|
|
2876
|
+
};
|
|
2735
2877
|
}
|
|
2736
2878
|
getAdapter(runtime) {
|
|
2737
2879
|
return RuntimeRegistry.get(runtime);
|
|
@@ -2844,7 +2986,7 @@ init_logger();
|
|
|
2844
2986
|
// package.json
|
|
2845
2987
|
var package_default = {
|
|
2846
2988
|
name: "@isol8/core",
|
|
2847
|
-
version: "0.
|
|
2989
|
+
version: "0.18.0",
|
|
2848
2990
|
description: "Sandboxed code execution engine for AI agents and apps (Docker, runtime and network controls)",
|
|
2849
2991
|
author: "Illusion47586",
|
|
2850
2992
|
license: "MIT",
|
|
@@ -2912,8 +3054,7 @@ var VERSION = package_default.version;
|
|
|
2912
3054
|
export {
|
|
2913
3055
|
logger,
|
|
2914
3056
|
loadConfig,
|
|
2915
|
-
|
|
2916
|
-
buildCustomImages,
|
|
3057
|
+
imageExists,
|
|
2917
3058
|
buildCustomImage,
|
|
2918
3059
|
buildBaseImages,
|
|
2919
3060
|
bashAdapter,
|
|
@@ -2923,9 +3064,10 @@ export {
|
|
|
2923
3064
|
RemoteIsol8,
|
|
2924
3065
|
PythonAdapter,
|
|
2925
3066
|
NodeAdapter,
|
|
3067
|
+
LABELS,
|
|
2926
3068
|
DockerIsol8,
|
|
2927
3069
|
DenoAdapter,
|
|
2928
3070
|
BunAdapter
|
|
2929
3071
|
};
|
|
2930
3072
|
|
|
2931
|
-
//# debugId=
|
|
3073
|
+
//# debugId=014F8E5DF8C3A76364756E2164756E21
|