@claxedo/sandbox-manager 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -4
- package/dist/checkpoint-manager.mjs +8 -0
- package/dist/contract.d.ts +12 -1
- package/dist/driver-ensure-input.d.ts +21 -0
- package/dist/drivers/boat-machine.d.ts +90 -0
- package/dist/drivers/boat-preparation.test-support.d.ts +2 -0
- package/dist/drivers/boat-template.mjs +132 -81
- package/dist/drivers/boat.d.ts +0 -12
- package/dist/drivers/boat.mjs +136 -97
- package/dist/drivers/cloudflare.mjs +7 -3
- package/dist/drivers/docker.mjs +5 -0
- package/dist/drivers/local-brokering.d.ts +12 -0
- package/dist/drivers/local-brokering.mjs +11 -6
- package/dist/drivers/modal.mjs +5 -0
- package/dist/drivers/vercel.mjs +5 -0
- package/dist/image-name.mjs +5 -0
- package/dist/image.d.ts +1 -0
- package/dist/image.mjs +6 -0
- package/dist/index.mjs +82 -50
- package/dist/lease-failure.d.ts +17 -0
- package/dist/runtime-version.d.ts +21 -7
- package/dist/runtime-version.mjs +7 -2
- package/dist/start-telemetry.d.ts +5 -3
- package/package.json +3 -3
package/dist/drivers/boat.mjs
CHANGED
|
@@ -49,6 +49,13 @@ var SandboxRuntimeBootError = class extends Error {
|
|
|
49
49
|
this.name = "SandboxRuntimeBootError";
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
|
+
var SandboxImageDownloadError = class extends Error {
|
|
53
|
+
imageDownloadFailed = true;
|
|
54
|
+
constructor(reason) {
|
|
55
|
+
super(reason);
|
|
56
|
+
this.name = "SandboxImageDownloadError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
52
59
|
var SandboxHostGoneError = class extends Error {
|
|
53
60
|
hostGone = true;
|
|
54
61
|
constructor(reason) {
|
|
@@ -149,6 +156,13 @@ function envFile(input) {
|
|
|
149
156
|
}).join("\n") + "\n";
|
|
150
157
|
}
|
|
151
158
|
|
|
159
|
+
// src/runtime-version.ts
|
|
160
|
+
var SERVED_WORKSPACE_RUNTIME_FLOOR = {
|
|
161
|
+
version: "0.10.1",
|
|
162
|
+
image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
|
|
163
|
+
};
|
|
164
|
+
var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
|
|
165
|
+
|
|
152
166
|
// src/image-name.ts
|
|
153
167
|
function assertSandboxImageReference(image) {
|
|
154
168
|
if (!image || image !== image.trim() || image.startsWith("-") || /\s/.test(image)) {
|
|
@@ -560,6 +574,98 @@ function isTransientDriverError(err, markers) {
|
|
|
560
574
|
}
|
|
561
575
|
var TRANSIENT_DRIVER_RETRY_MS = 2e3;
|
|
562
576
|
|
|
577
|
+
// src/drivers/boat-machine.ts
|
|
578
|
+
var IMAGE_PREPARATION_TIMEOUT_SECONDS = 600;
|
|
579
|
+
var CONTAINER_START_TIMEOUT_SECONDS = 120;
|
|
580
|
+
var DEFAULT_CONTAINER_NAME = "claxedo-runtime";
|
|
581
|
+
var DOCKER_DAEMON_WAIT_SECONDS = 120;
|
|
582
|
+
var RUNTIME_ENV_PATH = ".claxedo-runtime-env";
|
|
583
|
+
var REGISTRY_PASSWORD_PATH = ".claxedo-registry-password";
|
|
584
|
+
var BOOT_LOCK_PATH = "/run/lock/claxedo-boot.lock";
|
|
585
|
+
var BOOT_LOCK_HEADROOM_SECONDS = 60;
|
|
586
|
+
var CONTAINER_ENV_PATH = "/run/claxedo-runtime.env";
|
|
587
|
+
var PERSISTENT_ROOT = "claxedo-persistent";
|
|
588
|
+
var PERSISTENT_HOME_MOUNTS = [["claxedo", "/root/.claxedo"], ["workspace-runtime", "/root/.workspace-runtime"]];
|
|
589
|
+
var IMAGE_TREES = ["root", "usr", "etc", "var", "opt", "home", "srv"];
|
|
590
|
+
var IMAGE_TREE_SEED_EXCLUDES = ["root/.cache", "root/.npm/_cacache"];
|
|
591
|
+
var IMAGE_TREES_COMPLETE_MARKER = ".complete";
|
|
592
|
+
var SEED_CONTAINER_NAME = "claxedo-seed";
|
|
593
|
+
var STUB_IMAGE = "claxedo-runtime-stub";
|
|
594
|
+
var STUB_IMAGE_ARCHIVE = "stub.tar";
|
|
595
|
+
var IMAGE_LABEL = "claxedo.image";
|
|
596
|
+
var IMAGE_ENV_FILE = "image.env";
|
|
597
|
+
var ROOT_LINKS = ["bin", "lib", "lib64", "sbin"];
|
|
598
|
+
var ROOT_DIRECTORIES = ["boot", "dev", "media", "mnt", "proc", "run", "sys", "tmp", ...IMAGE_TREES];
|
|
599
|
+
var BOAT_SAVED_IMAGES = "/var/lib/ascii-docker-images";
|
|
600
|
+
var DOCKER_NAME_IN_USE = "is already in use by container";
|
|
601
|
+
var CONTAINER_CREATE_ATTEMPTS = 5;
|
|
602
|
+
var CONTAINER_SECURITY_OPTIONS = "--security-opt seccomp=unconfined --security-opt apparmor=unconfined";
|
|
603
|
+
var VM_DOCKER_INIT = "/usr/libexec/docker/docker-init";
|
|
604
|
+
var CONTAINER_INIT_PATH = "/claxedo-init";
|
|
605
|
+
function holdingBootLock(commandTimeoutSeconds, script) {
|
|
606
|
+
const wait = commandTimeoutSeconds - BOOT_LOCK_HEADROOM_SECONDS;
|
|
607
|
+
return `exec 9>>${BOOT_LOCK_PATH} && { flock -w ${wait} 9 || { echo "another boot of this machine held ${BOOT_LOCK_PATH} for ${wait} s" >&2; exit 1; }; } && ${script}`;
|
|
608
|
+
}
|
|
609
|
+
function containerHostname(workspaceId) {
|
|
610
|
+
return workspaceId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 63) || "claxedo";
|
|
611
|
+
}
|
|
612
|
+
function bootedImageFile(staged) {
|
|
613
|
+
return `${staged.env}.image`;
|
|
614
|
+
}
|
|
615
|
+
function bootedImage(prepareStdout) {
|
|
616
|
+
return prepareStdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).pop();
|
|
617
|
+
}
|
|
618
|
+
var VERSION_FILE = "opt/workspace-runtime/workspace-runtime-version";
|
|
619
|
+
var stubLabel = `docker image inspect --format '{{index .Config.Labels "${IMAGE_LABEL}"}}' ${STUB_IMAGE} 2>/dev/null`;
|
|
620
|
+
var treesFunctions = [
|
|
621
|
+
`trees_of() { printf '%s/${PERSISTENT_ROOT}/image-%s' "$(pwd)" "$(printf %s "$1" | sed 's/[^A-Za-z0-9._-]/-/g')"; }`,
|
|
622
|
+
`seeded() { [ -f "$1/${IMAGE_TREES_COMPLETE_MARKER}" ] && [ -f "$1/${STUB_IMAGE_ARCHIVE}" ] && [ -f "$1/${IMAGE_ENV_FILE}" ]; }`,
|
|
623
|
+
`served() { version=$(cat "$1/${VERSION_FILE}" 2>/dev/null) && [ -n "$version" ] && [ "$(printf '%s\\n%s\\n' ${MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION} "$version" | sort -V | head -n 1)" = ${MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION} ]; }`
|
|
624
|
+
].join(" && ");
|
|
625
|
+
var IMAGE_DOWNLOAD_INTERRUPTED = "image_download_interrupted";
|
|
626
|
+
var KEPT_IMAGE_SKIPPED = "kept_image_skipped:";
|
|
627
|
+
var PULL_RESUMING = `pull_resuming() { finished=$(mktemp) && while :; do if output=$(docker pull "$1" 2>&1); then rm -f "$finished"; return 0; fi; case "$output" in *"connection reset by peer"*|*"unexpected EOF"*|*"i/o timeout"*|*"TLS handshake timeout"*) ;; *) printf '%s\\n' "$output" >&2; rm -f "$finished"; return 1 ;; esac; before=$(wc -l < "$finished"); { cat "$finished"; printf '%s\\n' "$output" | sed -n -e 's/^\\([0-9a-f]\\{12\\}\\): Download complete$/\\1/p' -e 's/^\\([0-9a-f]\\{12\\}\\): Pull complete$/\\1/p'; } | sort -u > "$finished.next" && mv "$finished.next" "$finished"; [ "$(wc -l < "$finished")" -gt "$before" ] || { printf '%s\\n' "$output" ${IMAGE_DOWNLOAD_INTERRUPTED} >&2; rm -f "$finished"; return 1; }; done; }`;
|
|
628
|
+
function machineBootScripts(boot) {
|
|
629
|
+
const named = shell(boot.image);
|
|
630
|
+
const trees = `"$(trees_of ${named})"`;
|
|
631
|
+
const bootedFile = bootedImageFile(boot.staged);
|
|
632
|
+
const containerOfBooted = `[ "$(docker container inspect --format '{{index .Config.Labels "${IMAGE_LABEL}"}}' ${boot.containerName} 2>/dev/null)" = "$booted" ]`;
|
|
633
|
+
const bootScript = [
|
|
634
|
+
`. ${CONTAINER_ENV_PATH}`,
|
|
635
|
+
`mkdir -p ${shell(boot.directory)}`,
|
|
636
|
+
`cd ${shell(boot.directory)}`,
|
|
637
|
+
`exec ${boot.runtimeCommand}`
|
|
638
|
+
].join(" && ");
|
|
639
|
+
const mounts = [["workspace", boot.directory], ...PERSISTENT_HOME_MOUNTS];
|
|
640
|
+
const run = `docker run -d --name ${boot.containerName} --hostname ${shell(containerHostname(boot.workspaceId))} -p ${boot.port}:${boot.port} -v ${VM_DOCKER_INIT}:${CONTAINER_INIT_PATH}:ro -v "$(pwd)/${RUNTIME_ENV_PATH}:${CONTAINER_ENV_PATH}:ro" --env-file "$trees/${IMAGE_ENV_FILE}" ` + IMAGE_TREES.map((tree) => `-v "$trees/${tree}":/${tree} `).join("") + mounts.map(([source, target]) => `-v "$(pwd)/${PERSISTENT_ROOT}/${source}":${shell(target)} `).join("") + `${CONTAINER_SECURITY_OPTIONS} --entrypoint ${CONTAINER_INIT_PATH} ${STUB_IMAGE} -- sh -lc ${shell(bootScript)}`;
|
|
641
|
+
const seedTrees = `sudo rm -rf ${trees} && sudo mkdir -p ${trees} && { docker rm -f ${SEED_CONTAINER_NAME} >/dev/null 2>&1 || true; } && docker create --name ${SEED_CONTAINER_NAME} ${named} true >/dev/null && for tree in ${IMAGE_TREES.join(" ")}; do docker cp ${SEED_CONTAINER_NAME}:/$tree - | sudo tar -xf - -C ${trees} ${IMAGE_TREE_SEED_EXCLUDES.map((exclude) => `--exclude=${exclude}`).join(" ")} && [ -d ${trees}/$tree ] || exit 1; done && docker rm ${SEED_CONTAINER_NAME} >/dev/null && sudo touch ${trees}/${IMAGE_TREES_COMPLETE_MARKER}`;
|
|
642
|
+
const seedStub = `stub=$(mktemp -d) && docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}' ${named} > "$stub/${IMAGE_ENV_FILE}" && workdir=$(docker image inspect --format '{{or .Config.WorkingDir "/"}}' ${named}) && mkdir "$stub/root" ${ROOT_DIRECTORIES.map((directory) => `"$stub/root/${directory}"`).join(" ")} && ${ROOT_LINKS.map((link) => `ln -s usr/${link} "$stub/root/${link}"`).join(" && ")} && chmod 1777 "$stub/root/tmp" && tar --owner=0 --group=0 --numeric-owner -cf "$stub/skeleton.tar" -C "$stub/root" . && docker import --change "WORKDIR $workdir" --change ${shell(`LABEL ${IMAGE_LABEL}=${boot.image}`)} "$stub/skeleton.tar" ${STUB_IMAGE} >/dev/null && docker save -o "$stub/${STUB_IMAGE_ARCHIVE}" ${STUB_IMAGE} && sudo mv "$stub/${IMAGE_ENV_FILE}" "$stub/${STUB_IMAGE_ARCHIVE}" ${trees}/ && rm -rf "$stub"`;
|
|
643
|
+
const seedNamed = `{ docker image inspect ${named} >/dev/null 2>&1 || pull_resuming ${named}; } && { [ -f ${trees}/${IMAGE_TREES_COMPLETE_MARKER} ] || { ${seedTrees}; }; } && ${seedStub}`;
|
|
644
|
+
const keepPrevious = `previous=$(${stubLabel}) && [ -n "$previous" ] && seeded "$(trees_of "$previous")" && served "$(trees_of "$previous")"`;
|
|
645
|
+
const keptImageSkipped = `echo "${KEPT_IMAGE_SKIPPED} stub=\${previous:-none} seeded=$(seeded "$(trees_of "$previous")" && echo yes || echo no) version=$(cat "$(trees_of "$previous")/${VERSION_FILE}" 2>/dev/null || echo none)" >&2`;
|
|
646
|
+
const removeImage = `if docker image inspect "$booted" >/dev/null 2>&1; then leftovers=$(docker ps -aq --filter ancestor="$booted") && { [ -z "$leftovers" ] || docker rm -f $leftovers >/dev/null; } && id=$(docker image inspect --format '{{.Id}}' "$booted") && docker rmi "$booted" >/dev/null && sudo rm -f ${BOAT_SAVED_IMAGES}/"\${id#sha256:}".tar; fi`;
|
|
647
|
+
const prepare = [
|
|
648
|
+
`chmod 600 ${boot.staged.env}`,
|
|
649
|
+
`find . -maxdepth 1 \\( -name '${RUNTIME_ENV_PATH}.*' -o -name '${REGISTRY_PASSWORD_PATH}.*' \\) -mmin +${boot.staleStagedMinutes} -exec rm -f {} +`,
|
|
650
|
+
`timeout ${DOCKER_DAEMON_WAIT_SECONDS} sh -c 'until docker info >/dev/null 2>&1; do sleep 1; done'`,
|
|
651
|
+
`[ -x ${VM_DOCKER_INIT} ]`,
|
|
652
|
+
...boot.registry && boot.staged.password ? [`{ chmod 600 ${boot.staged.password}; docker login ${shell(boot.registry.server)} --username ${shell(boot.registry.username)} --password-stdin < ${boot.staged.password}; rc=$?; rm -f ${boot.staged.password}; (exit $rc); }`] : [],
|
|
653
|
+
treesFunctions,
|
|
654
|
+
PULL_RESUMING,
|
|
655
|
+
`booted=${named}`,
|
|
656
|
+
`if ! seeded ${trees}; then ` + (boot.keepPreviousImage ? `if ${keepPrevious}; then booted=$previous; else ${keptImageSkipped}; ${seedNamed}; fi` : seedNamed) + `; fi`,
|
|
657
|
+
`{ [ "$(${stubLabel})" = "$booted" ] || docker load -i "$(trees_of "$booted")/${STUB_IMAGE_ARCHIVE}" >/dev/null; }`,
|
|
658
|
+
`sudo find "$(pwd)/${PERSISTENT_ROOT}" -mindepth 1 -maxdepth 1 -name 'image-*' ! -path "$(trees_of "$booted")" -exec rm -rf {} +`,
|
|
659
|
+
`printf '%s\\n' "$booted" > ${bootedFile}`,
|
|
660
|
+
`printf '%s\\n' "$booted"`
|
|
661
|
+
];
|
|
662
|
+
const start = `${treesFunctions} && booted=$(cat ${bootedFile}) && trees=$(trees_of "$booted") && rm -f ${bootedFile} && mv -f ${boot.staged.env} ${RUNTIME_ENV_PATH} && up=; for attempt in $(seq ${CONTAINER_CREATE_ATTEMPTS}); do [ "$attempt" = 1 ] || sleep 1; if ${containerOfBooted}; then failure=$(docker start ${boot.containerName} 2>&1 >/dev/null) && { up=1; break; }; else docker rm -f ${boot.containerName} >/dev/null 2>&1; failure=$(${run} 2>&1 >/dev/null) && { up=1; break; }; fi; case "$failure" in *${shell(DOCKER_NAME_IN_USE)}*) ;; *) break ;; esac; done; [ -n "$up" ] || { echo "$failure" >&2; false; } && ${removeImage}`;
|
|
663
|
+
return {
|
|
664
|
+
prepare: holdingBootLock(IMAGE_PREPARATION_TIMEOUT_SECONDS, prepare.join(" && ")),
|
|
665
|
+
start: holdingBootLock(CONTAINER_START_TIMEOUT_SECONDS, start)
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
563
669
|
// src/drivers/boat.ts
|
|
564
670
|
function formatBoatRuntimeTemplate(template) {
|
|
565
671
|
return JSON.stringify({ image: template.image, shareCode: template.shareCode });
|
|
@@ -579,7 +685,6 @@ function parseBoatRuntimeTemplate(setting) {
|
|
|
579
685
|
}
|
|
580
686
|
var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
|
|
581
687
|
var DEFAULT_WORKSPACE_DIR = "/workspace";
|
|
582
|
-
var DEFAULT_CONTAINER_NAME = "claxedo-runtime";
|
|
583
688
|
var DEFAULT_PROVISION_TIMEOUT_MS = 12e4;
|
|
584
689
|
var DEFAULT_STATE_POLL_MS = { firstMs: 250, maxMs: 2e3 };
|
|
585
690
|
var DEFAULT_HEALTH_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -587,35 +692,8 @@ var DEFAULT_HEALTH_INTERVAL_MS = 100;
|
|
|
587
692
|
var HEALTH_PROBE_WINDOW_SECONDS = 50;
|
|
588
693
|
var HEALTH_PROBE_MARGIN_SECONDS = 10;
|
|
589
694
|
var BOOTING_CONTAINER_STATES = /* @__PURE__ */ new Set(["running", "paused"]);
|
|
590
|
-
var IMAGE_PREPARATION_TIMEOUT_SECONDS = 600;
|
|
591
|
-
var CONTAINER_START_TIMEOUT_SECONDS = 120;
|
|
592
695
|
var HOST_PUBLISH_TIMEOUT_SECONDS = 120;
|
|
593
696
|
var BOOT_TTL_SECONDS = 30 * 60;
|
|
594
|
-
var DOCKER_DAEMON_WAIT_SECONDS = 120;
|
|
595
|
-
var RUNTIME_ENV_PATH = ".claxedo-runtime-env";
|
|
596
|
-
var REGISTRY_PASSWORD_PATH = ".claxedo-registry-password";
|
|
597
|
-
var BOOT_LOCK_PATH = "/run/lock/claxedo-boot.lock";
|
|
598
|
-
var BOOT_LOCK_HEADROOM_SECONDS = 60;
|
|
599
|
-
var CONTAINER_ENV_PATH = "/run/claxedo-runtime.env";
|
|
600
|
-
var PERSISTENT_ROOT = "claxedo-persistent";
|
|
601
|
-
var PERSISTENT_HOME_MOUNTS = [["claxedo", "/root/.claxedo"], ["workspace-runtime", "/root/.workspace-runtime"]];
|
|
602
|
-
var IMAGE_TREES = ["root", "usr", "etc", "var", "opt", "home", "srv"];
|
|
603
|
-
var IMAGE_TREE_SEED_EXCLUDES = ["root/.cache", "root/.npm/_cacache"];
|
|
604
|
-
var IMAGE_TREES_COMPLETE_MARKER = ".complete";
|
|
605
|
-
var SEED_CONTAINER_NAME = "claxedo-seed";
|
|
606
|
-
var STUB_IMAGE = "claxedo-runtime-stub";
|
|
607
|
-
var STUB_IMAGE_ARCHIVE = "stub.tar";
|
|
608
|
-
var IMAGE_LABEL = "claxedo.image";
|
|
609
|
-
var IMAGE_ENV_FILE = "image.env";
|
|
610
|
-
var ROOT_LINKS = ["bin", "lib", "lib64", "sbin"];
|
|
611
|
-
var ROOT_DIRECTORIES = ["boot", "dev", "media", "mnt", "proc", "run", "sys", "tmp", ...IMAGE_TREES];
|
|
612
|
-
var BOAT_SAVED_IMAGES = "/var/lib/ascii-docker-images";
|
|
613
|
-
var BOAT_CONTAINER_RECORD = "/etc/ascii-docker.json";
|
|
614
|
-
var DOCKER_NAME_IN_USE = "is already in use by container";
|
|
615
|
-
var CONTAINER_CREATE_ATTEMPTS = 5;
|
|
616
|
-
var CONTAINER_SECURITY_OPTIONS = "--security-opt seccomp=unconfined --security-opt apparmor=unconfined";
|
|
617
|
-
var VM_DOCKER_INIT = "/usr/libexec/docker/docker-init";
|
|
618
|
-
var CONTAINER_INIT_PATH = "/claxedo-init";
|
|
619
697
|
var CONTAINER_STOP_GRACE_SECONDS = 30;
|
|
620
698
|
var HOST_RELAY_NOT_STOPPED = "The sandbox could not stop the localhost relay of this port. Try again.";
|
|
621
699
|
var STDERR_TAIL_CHARS = 600;
|
|
@@ -627,16 +705,6 @@ var GONE_CODES = /* @__PURE__ */ new Set(["not_found", "snapshot_data_expired"])
|
|
|
627
705
|
var RESUME_UNDER_WAY = "sandbox_resuming";
|
|
628
706
|
var NOT_RUNNING_CODES = /* @__PURE__ */ new Set(["machine_not_running", "sandbox_not_ready"]);
|
|
629
707
|
var TEMPLATE_UNREADABLE_CODES = /* @__PURE__ */ new Set(["snapshot_not_found", "api_key_action_forbidden", "api_key_sandbox_forbidden"]);
|
|
630
|
-
function boatTemplateResetCommand() {
|
|
631
|
-
const bootFiles = [
|
|
632
|
-
`${PERSISTENT_ROOT}/workspace`,
|
|
633
|
-
...PERSISTENT_HOME_MOUNTS.map(([source]) => `${PERSISTENT_ROOT}/${source}`),
|
|
634
|
-
RUNTIME_ENV_PATH,
|
|
635
|
-
`${RUNTIME_ENV_PATH}.*`,
|
|
636
|
-
`${REGISTRY_PASSWORD_PATH}.*`
|
|
637
|
-
];
|
|
638
|
-
return `{ docker rm -f ${DEFAULT_CONTAINER_NAME} >/dev/null 2>&1 || true; } && sudo rm -rf ${bootFiles.join(" ")} && [ -z "$(docker ps -aq)" ] && echo '[]' | sudo tee ${BOAT_CONTAINER_RECORD} >/dev/null && sudo find ${BOAT_SAVED_IMAGES} -mindepth 1 -delete && ls -d ${PERSISTENT_ROOT}/image-*/${IMAGE_TREES_COMPLETE_MARKER}`;
|
|
639
|
-
}
|
|
640
708
|
function hostGoneOrThrow(err) {
|
|
641
709
|
if (err instanceof BoatApiError && GONE_CODES.has(err.code)) throw new SandboxHostGoneError(err.message);
|
|
642
710
|
throw err;
|
|
@@ -649,10 +717,6 @@ function archivedOrThrow(err) {
|
|
|
649
717
|
if (err instanceof BoatApiError && err.code === "stop_failed" && err.sandboxState === "archived") return;
|
|
650
718
|
hostGoneOrThrow(err);
|
|
651
719
|
}
|
|
652
|
-
function holdingBootLock(commandTimeoutSeconds, script) {
|
|
653
|
-
const wait = commandTimeoutSeconds - BOOT_LOCK_HEADROOM_SECONDS;
|
|
654
|
-
return `exec 9>>${BOOT_LOCK_PATH} && { flock -w ${wait} 9 || { echo "another boot of this machine held ${BOOT_LOCK_PATH} for ${wait} s" >&2; exit 1; }; } && ${script}`;
|
|
655
|
-
}
|
|
656
720
|
function sleep(ms) {
|
|
657
721
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
658
722
|
}
|
|
@@ -662,12 +726,6 @@ function scrubbed(text, secrets) {
|
|
|
662
726
|
function scrubbedTail(text, secrets) {
|
|
663
727
|
return scrubbed(text, secrets).trim().slice(-STDERR_TAIL_CHARS);
|
|
664
728
|
}
|
|
665
|
-
function imageTreesDirectory(image) {
|
|
666
|
-
return `image-${image.replace(/[^A-Za-z0-9._-]/g, "-")}`;
|
|
667
|
-
}
|
|
668
|
-
function containerHostname(workspaceId) {
|
|
669
|
-
return workspaceId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 63) || "claxedo";
|
|
670
|
-
}
|
|
671
729
|
var BoatDriverError = class extends Error {
|
|
672
730
|
constructor(message) {
|
|
673
731
|
super(message);
|
|
@@ -748,43 +806,6 @@ function createBoatSandboxDriver(options) {
|
|
|
748
806
|
wait = Math.min(wait * 2, statePollMs.maxMs);
|
|
749
807
|
}
|
|
750
808
|
}
|
|
751
|
-
function containerScripts(input, staged) {
|
|
752
|
-
const port = runtimePort(input);
|
|
753
|
-
const directory = workspaceDirectory(input);
|
|
754
|
-
const reference = resolveImage(input);
|
|
755
|
-
const image = shell(reference);
|
|
756
|
-
const trees = `"$(pwd)/${PERSISTENT_ROOT}/${imageTreesDirectory(reference)}"`;
|
|
757
|
-
const labelledWithImage = (object, name) => `[ "$(docker ${object} inspect --format '{{index .Config.Labels "${IMAGE_LABEL}"}}' ${name} 2>/dev/null)" = ${image} ]`;
|
|
758
|
-
const bootScript = [
|
|
759
|
-
`. ${CONTAINER_ENV_PATH}`,
|
|
760
|
-
`mkdir -p ${shell(directory)}`,
|
|
761
|
-
`cd ${shell(directory)}`,
|
|
762
|
-
`exec ${runtimeCommand}`
|
|
763
|
-
].join(" && ");
|
|
764
|
-
const mounts = [["workspace", directory], ...PERSISTENT_HOME_MOUNTS];
|
|
765
|
-
const run = `docker run -d --name ${containerName} --hostname ${shell(containerHostname(input.workspaceId))} -p ${port}:${port} -v ${VM_DOCKER_INIT}:${CONTAINER_INIT_PATH}:ro -v "$(pwd)/${RUNTIME_ENV_PATH}:${CONTAINER_ENV_PATH}:ro" --env-file ${trees}/${IMAGE_ENV_FILE} ` + IMAGE_TREES.map((tree) => `-v ${trees}/${tree}:/${tree} `).join("") + mounts.map(([source, target]) => `-v "$(pwd)/${PERSISTENT_ROOT}/${source}":${shell(target)} `).join("") + `${CONTAINER_SECURITY_OPTIONS} --entrypoint ${CONTAINER_INIT_PATH} ${STUB_IMAGE} -- sh -lc ${shell(bootScript)}`;
|
|
766
|
-
const seedTrees = `sudo rm -rf ${trees} && sudo mkdir -p ${trees} && { docker rm -f ${SEED_CONTAINER_NAME} >/dev/null 2>&1 || true; } && docker create --name ${SEED_CONTAINER_NAME} ${image} true >/dev/null && for tree in ${IMAGE_TREES.join(" ")}; do docker cp ${SEED_CONTAINER_NAME}:/$tree - | sudo tar -xf - -C ${trees} ${IMAGE_TREE_SEED_EXCLUDES.map((exclude) => `--exclude=${exclude}`).join(" ")} && [ -d ${trees}/$tree ] || exit 1; done && docker rm ${SEED_CONTAINER_NAME} >/dev/null && sudo touch ${trees}/${IMAGE_TREES_COMPLETE_MARKER}`;
|
|
767
|
-
const seedStub = `stub=$(mktemp -d) && docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}' ${image} > "$stub/${IMAGE_ENV_FILE}" && workdir=$(docker image inspect --format '{{or .Config.WorkingDir "/"}}' ${image}) && mkdir "$stub/root" ${ROOT_DIRECTORIES.map((directory2) => `"$stub/root/${directory2}"`).join(" ")} && ${ROOT_LINKS.map((link) => `ln -s usr/${link} "$stub/root/${link}"`).join(" && ")} && chmod 1777 "$stub/root/tmp" && tar --owner=0 --group=0 --numeric-owner -cf "$stub/skeleton.tar" -C "$stub/root" . && docker import --change "WORKDIR $workdir" --change ${shell(`LABEL ${IMAGE_LABEL}=${reference}`)} "$stub/skeleton.tar" ${STUB_IMAGE} >/dev/null && docker save -o "$stub/${STUB_IMAGE_ARCHIVE}" ${STUB_IMAGE} && sudo mv "$stub/${IMAGE_ENV_FILE}" "$stub/${STUB_IMAGE_ARCHIVE}" ${trees}/ && rm -rf "$stub"`;
|
|
768
|
-
const removeImage = `if docker image inspect ${image} >/dev/null 2>&1; then leftovers=$(docker ps -aq --filter ancestor=${image}) && { [ -z "$leftovers" ] || docker rm -f $leftovers >/dev/null; } && id=$(docker image inspect --format '{{.Id}}' ${image}) && docker rmi ${image} >/dev/null && sudo rm -f ${BOAT_SAVED_IMAGES}/"\${id#sha256:}".tar; fi`;
|
|
769
|
-
const seeded = [IMAGE_TREES_COMPLETE_MARKER, STUB_IMAGE_ARCHIVE, IMAGE_ENV_FILE].map((file) => `[ -f ${trees}/${file} ]`).join(" && ");
|
|
770
|
-
const prepare = [
|
|
771
|
-
`chmod 600 ${staged.env}`,
|
|
772
|
-
`find . -maxdepth 1 \\( -name '${RUNTIME_ENV_PATH}.*' -o -name '${REGISTRY_PASSWORD_PATH}.*' \\) -mmin +${BOOT_TTL_SECONDS / 60} -exec rm -f {} +`,
|
|
773
|
-
`timeout ${DOCKER_DAEMON_WAIT_SECONDS} sh -c 'until docker info >/dev/null 2>&1; do sleep 1; done'`,
|
|
774
|
-
`[ -x ${VM_DOCKER_INIT} ]`,
|
|
775
|
-
...options.registryAuth && staged.password ? [`{ chmod 600 ${staged.password}; docker login ${shell(options.registryAuth.server)} --username ${shell(options.registryAuth.username)} --password-stdin < ${staged.password}; rc=$?; rm -f ${staged.password}; (exit $rc); }`] : [],
|
|
776
|
-
// Trees an earlier start marked complete hold what a person or a harness
|
|
777
|
-
// changed since, so a seed missing only its stub or env builds those alone.
|
|
778
|
-
`if ! { ${seeded}; }; then { docker image inspect ${image} >/dev/null 2>&1 || docker pull ${image} >/dev/null; } && { [ -f ${trees}/${IMAGE_TREES_COMPLETE_MARKER} ] || { ${seedTrees}; }; } && ${seedStub}; fi`,
|
|
779
|
-
`{ ${labelledWithImage("image", STUB_IMAGE)} || docker load -i ${trees}/${STUB_IMAGE_ARCHIVE} >/dev/null; }`,
|
|
780
|
-
`sudo find "$(pwd)/${PERSISTENT_ROOT}" -mindepth 1 -maxdepth 1 -name 'image-*' ! -path ${trees} -exec rm -rf {} +`
|
|
781
|
-
];
|
|
782
|
-
const start = `mv -f ${staged.env} ${RUNTIME_ENV_PATH} && up=; for attempt in $(seq ${CONTAINER_CREATE_ATTEMPTS}); do [ "$attempt" = 1 ] || sleep 1; if ${labelledWithImage("container", containerName)}; then failure=$(docker start ${containerName} 2>&1 >/dev/null) && { up=1; break; }; else docker rm -f ${containerName} >/dev/null 2>&1; failure=$(${run} 2>&1 >/dev/null) && { up=1; break; }; fi; case "$failure" in *${shell(DOCKER_NAME_IN_USE)}*) ;; *) break ;; esac; done; [ -n "$up" ] || { echo "$failure" >&2; false; } && ${removeImage}`;
|
|
783
|
-
return {
|
|
784
|
-
prepare: holdingBootLock(IMAGE_PREPARATION_TIMEOUT_SECONDS, prepare.join(" && ")),
|
|
785
|
-
start: holdingBootLock(CONTAINER_START_TIMEOUT_SECONDS, start)
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
809
|
function healthProbe(port, windowSeconds) {
|
|
789
810
|
const booting = [...BOOTING_CONTAINER_STATES].join("|");
|
|
790
811
|
return `end=$(($(date +%s) + ${windowSeconds})); while :; do code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:${port}/global/health); [ "$code" = 200 ] && break; state=$(docker inspect --format '{{.State.Status}}' ${shell(containerName)} 2>/dev/null || echo missing); case "$state" in ${booting}) ;; *) break ;; esac; [ "$(date +%s)" -lt "$end" ] || break; sleep ${healthIntervalMs / 1e3}; done; echo "$code"; echo "$state"`;
|
|
@@ -820,22 +841,41 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
|
|
|
820
841
|
}
|
|
821
842
|
throw new SandboxRuntimeBootError(`Boat ${sandboxId} runtime did not become healthy: ${last}; ${diagnostic}`);
|
|
822
843
|
}
|
|
823
|
-
async function startContainer(sandboxId, input, hostId) {
|
|
844
|
+
async function startContainer(sandboxId, input, hostId, keepPreviousImage) {
|
|
824
845
|
const env = { ...bootEnv(input, hostId), ...await options.env?.(input, { id: hostId }) };
|
|
825
846
|
const boot2 = crypto.randomUUID();
|
|
826
847
|
const staged = {
|
|
827
848
|
env: `${RUNTIME_ENV_PATH}.${boot2}`,
|
|
828
849
|
...options.registryAuth ? { password: `${REGISTRY_PASSWORD_PATH}.${boot2}` } : {}
|
|
829
850
|
};
|
|
830
|
-
const
|
|
851
|
+
const image = resolveImage(input);
|
|
852
|
+
const scripts = machineBootScripts({
|
|
853
|
+
image,
|
|
854
|
+
keepPreviousImage: keepPreviousImage && image === options.image,
|
|
855
|
+
workspaceId: input.workspaceId,
|
|
856
|
+
directory: workspaceDirectory(input),
|
|
857
|
+
port: runtimePort(input),
|
|
858
|
+
runtimeCommand,
|
|
859
|
+
containerName,
|
|
860
|
+
...options.registryAuth ? { registry: { server: options.registryAuth.server, username: options.registryAuth.username } } : {},
|
|
861
|
+
staged,
|
|
862
|
+
staleStagedMinutes: BOOT_TTL_SECONDS / 60
|
|
863
|
+
});
|
|
831
864
|
await client.writeFile(sandboxId, { path: staged.env, content: envFile(env) });
|
|
832
865
|
if (options.registryAuth && staged.password) {
|
|
833
866
|
await client.writeFile(sandboxId, { path: staged.password, content: options.registryAuth.password });
|
|
834
867
|
}
|
|
835
868
|
const secrets = [...workspaceRuntimeBootSecrets(env), ...options.registryAuth ? [options.registryAuth.password] : []];
|
|
836
869
|
let failure;
|
|
870
|
+
let booted;
|
|
837
871
|
try {
|
|
838
|
-
await execOrThrow(sandboxId, scripts.prepare, "runtime image preparation", { timeoutSeconds: IMAGE_PREPARATION_TIMEOUT_SECONDS, secrets })
|
|
872
|
+
const prepared = await execOrThrow(sandboxId, scripts.prepare, "runtime image preparation", { timeoutSeconds: IMAGE_PREPARATION_TIMEOUT_SECONDS, secrets }).catch((error) => {
|
|
873
|
+
throw error instanceof BoatDriverError && error.message.includes(IMAGE_DOWNLOAD_INTERRUPTED) ? new SandboxImageDownloadError(error.message) : error;
|
|
874
|
+
});
|
|
875
|
+
booted = bootedImage(prepared.stdout);
|
|
876
|
+
if (!booted) throw new BoatDriverError(`Boat ${sandboxId} runtime image preparation named no image it booted`);
|
|
877
|
+
const skipped = keepPreviousImage ? prepared.stderr.split("\n").find((line) => line.startsWith(KEPT_IMAGE_SKIPPED)) : void 0;
|
|
878
|
+
if (skipped) console.warn("A Boat wake took the deployment's image instead of the one the machine last booted", { sandboxId, workspaceId: input.workspaceId, found: skipped.slice(KEPT_IMAGE_SKIPPED.length).trim() });
|
|
839
879
|
try {
|
|
840
880
|
await execOrThrow(sandboxId, scripts.start, "container start", { timeoutSeconds: CONTAINER_START_TIMEOUT_SECONDS, secrets });
|
|
841
881
|
} catch (error) {
|
|
@@ -846,19 +886,19 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
|
|
|
846
886
|
}
|
|
847
887
|
try {
|
|
848
888
|
if (options.registryAuth || failure !== void 0) {
|
|
849
|
-
await execOrThrow(sandboxId, `rm -f ${[staged.env, staged.password].filter(Boolean).join(" ")}`, "staged file cleanup", { secrets });
|
|
889
|
+
await execOrThrow(sandboxId, `rm -f ${[staged.env, staged.password, bootedImageFile(staged)].filter(Boolean).join(" ")}`, "staged file cleanup", { secrets });
|
|
850
890
|
}
|
|
851
891
|
} catch (cleanupFailure) {
|
|
852
892
|
if (failure === void 0) throw cleanupFailure;
|
|
853
893
|
if (failure instanceof Error) failure.message = `${failure.message}; ${cleanupFailure instanceof Error ? cleanupFailure.message : String(cleanupFailure)}`;
|
|
854
894
|
}
|
|
855
|
-
if (failure !== void 0) throw failure;
|
|
856
|
-
return secrets;
|
|
895
|
+
if (failure !== void 0 || !booted) throw failure;
|
|
896
|
+
return { secrets, booted };
|
|
857
897
|
}
|
|
858
|
-
async function boot(sandboxId, input, hostId) {
|
|
898
|
+
async function boot(sandboxId, input, hostId, keepPreviousImage) {
|
|
859
899
|
await input.onMachineReady?.();
|
|
860
900
|
const port = runtimePort(input);
|
|
861
|
-
const secrets = await startContainer(sandboxId, input, hostId);
|
|
901
|
+
const { secrets, booted } = await startContainer(sandboxId, input, hostId, keepPreviousImage);
|
|
862
902
|
await input.onImageReady?.();
|
|
863
903
|
await execOrThrow(sandboxId, holdingBootLock(HOST_PUBLISH_TIMEOUT_SECONDS, `host ${port} --public`), "host publish", {
|
|
864
904
|
timeoutSeconds: HOST_PUBLISH_TIMEOUT_SECONDS,
|
|
@@ -878,7 +918,7 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
|
|
|
878
918
|
hostId,
|
|
879
919
|
driverResourceId: sandboxId,
|
|
880
920
|
driver: { id: "boat", resourceId: sandboxId },
|
|
881
|
-
labels: { ...input.labels, ...resolveImage(input) === options.image ? { [SANDBOX_IMAGE_LABEL]:
|
|
921
|
+
labels: { ...input.labels, ...resolveImage(input) === options.image ? { [SANDBOX_IMAGE_LABEL]: booted } : {} }
|
|
882
922
|
};
|
|
883
923
|
}
|
|
884
924
|
function assertNetwork(input) {
|
|
@@ -927,7 +967,7 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
|
|
|
927
967
|
if (failures.length > 1) throw new AggregateError(failures, `Boat ${created.id} resource handoff and deletion failed`);
|
|
928
968
|
const ready = await waitUntilReady(created.id);
|
|
929
969
|
if (!ready) return { provisioning: true, retryAfterMs: 0 };
|
|
930
|
-
return boot(created.id, input, hostId);
|
|
970
|
+
return boot(created.id, input, hostId, false);
|
|
931
971
|
}
|
|
932
972
|
async function resumeHost(input) {
|
|
933
973
|
const sandboxId = input.lease.sandboxId;
|
|
@@ -947,7 +987,7 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
|
|
|
947
987
|
}
|
|
948
988
|
const ready = await waitUntilReady(sandboxId);
|
|
949
989
|
if (!ready) return { provisioning: true, retryAfterMs: 0 };
|
|
950
|
-
return boot(sandboxId, input.ensure, hostId);
|
|
990
|
+
return boot(sandboxId, input.ensure, hostId, !input.ensure.updateImage);
|
|
951
991
|
}
|
|
952
992
|
async function stop(target) {
|
|
953
993
|
await client.command(target.sandboxId, {
|
|
@@ -983,7 +1023,6 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
|
|
|
983
1023
|
};
|
|
984
1024
|
}
|
|
985
1025
|
export {
|
|
986
|
-
boatTemplateResetCommand,
|
|
987
1026
|
createBoatSandboxDriver,
|
|
988
1027
|
formatBoatRuntimeTemplate,
|
|
989
1028
|
parseBoatRuntimeTemplate
|
|
@@ -31,7 +31,7 @@ var cloudflareBilling = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
// ../sandbox-contract/dist/index.mjs
|
|
34
|
-
var sandboxCheckpointUnusableCauses = ["expired", "
|
|
34
|
+
var sandboxCheckpointUnusableCauses = ["expired", "runtime_modified", "not_restorable"];
|
|
35
35
|
function isSandboxCheckpointUnusableCause(value) {
|
|
36
36
|
return sandboxCheckpointUnusableCauses.some((cause) => cause === value);
|
|
37
37
|
}
|
|
@@ -311,7 +311,11 @@ function workspaceRuntimeSourceEnv(input) {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
// src/runtime-version.ts
|
|
314
|
-
var
|
|
314
|
+
var SERVED_WORKSPACE_RUNTIME_FLOOR = {
|
|
315
|
+
version: "0.10.1",
|
|
316
|
+
image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
|
|
317
|
+
};
|
|
318
|
+
var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
|
|
315
319
|
|
|
316
320
|
// src/drivers/cloudflare.ts
|
|
317
321
|
function registryEntries(input) {
|
|
@@ -452,7 +456,7 @@ function createCloudflareSandboxDriver(options) {
|
|
|
452
456
|
// what makes this driver's `list()` (and therefore GC) possible at all.
|
|
453
457
|
// Not container env — labels are control-plane metadata.
|
|
454
458
|
labels: input.labels,
|
|
455
|
-
...input.bootSource?.kind === "driver-snapshot" ? { restore: { snapshotId: input.bootSource.snapshotId }, minimumRuntimeVersion:
|
|
459
|
+
...input.bootSource?.kind === "driver-snapshot" ? { restore: { snapshotId: input.bootSource.snapshotId }, minimumRuntimeVersion: MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION } : {},
|
|
456
460
|
...egress ? { egress } : {}
|
|
457
461
|
}
|
|
458
462
|
).catch((error) => {
|
package/dist/drivers/docker.mjs
CHANGED
|
@@ -91,6 +91,11 @@ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
|
91
91
|
function workspaceRuntimeVersion() {
|
|
92
92
|
return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
93
93
|
}
|
|
94
|
+
var SERVED_WORKSPACE_RUNTIME_FLOOR = {
|
|
95
|
+
version: "0.10.1",
|
|
96
|
+
image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
|
|
97
|
+
};
|
|
98
|
+
var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
|
|
94
99
|
|
|
95
100
|
// src/image-name.ts
|
|
96
101
|
var SNAPSHOT_SCHEMA_VERSION = 8;
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import type { SandboxDriver } from "../contract";
|
|
2
2
|
import { localBrokeringTestDriverCatalogEntry } from "../driver-catalog";
|
|
3
|
+
/**
|
|
4
|
+
* A published runtime image to run instead of `executable`: Docker on the host
|
|
5
|
+
* network, with the paths the runtime reads mounted where they are, and no
|
|
6
|
+
* sandbox-exec network policy, so it runs on Linux. The version-skew suite
|
|
7
|
+
* runs the control plane against the oldest image it still serves this way.
|
|
8
|
+
*/
|
|
9
|
+
export type LocalBrokeringContainer = {
|
|
10
|
+
image: string;
|
|
11
|
+
command: string;
|
|
12
|
+
mounts: readonly string[];
|
|
13
|
+
};
|
|
3
14
|
export type LocalBrokeringDriverOptions = {
|
|
4
15
|
root: string;
|
|
5
16
|
executable: string;
|
|
6
17
|
args: string[];
|
|
18
|
+
container?: LocalBrokeringContainer;
|
|
7
19
|
allowedOrigins: readonly string[];
|
|
8
20
|
directOrigins: readonly string[];
|
|
9
21
|
upstreams?: Readonly<Record<string, string>>;
|
|
@@ -793,6 +793,7 @@ ${lines}` : ""}`);
|
|
|
793
793
|
}
|
|
794
794
|
async function closeHost(host) {
|
|
795
795
|
const child = host.child;
|
|
796
|
+
if (host.container) execFileSync("docker", ["rm", "-f", host.container], { stdio: "ignore" });
|
|
796
797
|
if (child && child.exitCode === null) {
|
|
797
798
|
child.kill("SIGTERM");
|
|
798
799
|
await new Promise((resolve) => child.once("exit", () => resolve()));
|
|
@@ -802,8 +803,14 @@ async function closeHost(host) {
|
|
|
802
803
|
await new Promise((resolve) => host.proxy.close(() => resolve()));
|
|
803
804
|
await fs.rm(host.directory, { recursive: true, force: true });
|
|
804
805
|
}
|
|
806
|
+
function containerRun(container, name, input) {
|
|
807
|
+
const user = `${process.getuid?.() ?? 0}:${process.getgid?.() ?? 0}`;
|
|
808
|
+
const mounts = [.../* @__PURE__ */ new Set([input.root, ...container.mounts])].flatMap((mount) => ["-v", `${mount}:${mount}`]);
|
|
809
|
+
const env = Object.keys(input.env).filter((key) => key !== "PATH").flatMap((key) => ["-e", key]);
|
|
810
|
+
return { name, args: ["run", "--rm", "--name", name, "--network", "host", "--user", user, ...mounts, ...env, "-w", input.workspace, "--entrypoint", container.command, container.image] };
|
|
811
|
+
}
|
|
805
812
|
function createLocalBrokeringSandboxDriver(options) {
|
|
806
|
-
if (process.platform !== "darwin") throw new Error("local brokering test driver requires macOS sandbox-exec network policy");
|
|
813
|
+
if (process.platform !== "darwin" && !options.container) throw new Error("local brokering test driver requires macOS sandbox-exec network policy, or a runtime container");
|
|
807
814
|
const authority = createTestAuthority(options.root);
|
|
808
815
|
const hosts = /* @__PURE__ */ new Map();
|
|
809
816
|
const allowedOrigins = new Set(options.allowedOrigins.map((origin) => new URL(origin).origin));
|
|
@@ -976,11 +983,9 @@ function createLocalBrokeringSandboxDriver(options) {
|
|
|
976
983
|
const log = openSync(logFile, "w", 384);
|
|
977
984
|
let child;
|
|
978
985
|
try {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
stdio: ["ignore", log, log]
|
|
983
|
-
});
|
|
986
|
+
const container = options.container ? containerRun(options.container, `claxedo-local-${target.sandboxId}`, { root: options.root, workspace, env }) : void 0;
|
|
987
|
+
if (container) host.container = container.name;
|
|
988
|
+
child = container ? spawn("docker", container.args, { env: { ...process.env, ...env }, stdio: ["ignore", log, log] }) : spawn("/usr/bin/sandbox-exec", ["-p", networkPolicy, options.executable, ...options.args], { cwd: workspace, env, stdio: ["ignore", log, log] });
|
|
984
989
|
} finally {
|
|
985
990
|
closeSync(log);
|
|
986
991
|
}
|
package/dist/drivers/modal.mjs
CHANGED
|
@@ -73,6 +73,11 @@ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
|
73
73
|
function workspaceRuntimeVersion() {
|
|
74
74
|
return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
75
75
|
}
|
|
76
|
+
var SERVED_WORKSPACE_RUNTIME_FLOOR = {
|
|
77
|
+
version: "0.10.1",
|
|
78
|
+
image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
|
|
79
|
+
};
|
|
80
|
+
var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
|
|
76
81
|
|
|
77
82
|
// src/image-name.ts
|
|
78
83
|
var SNAPSHOT_SCHEMA_VERSION = 8;
|
package/dist/drivers/vercel.mjs
CHANGED
|
@@ -73,6 +73,11 @@ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
|
73
73
|
function workspaceRuntimeVersion() {
|
|
74
74
|
return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
75
75
|
}
|
|
76
|
+
var SERVED_WORKSPACE_RUNTIME_FLOOR = {
|
|
77
|
+
version: "0.10.1",
|
|
78
|
+
image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
|
|
79
|
+
};
|
|
80
|
+
var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
|
|
76
81
|
|
|
77
82
|
// src/command.ts
|
|
78
83
|
function shell(input) {
|
package/dist/image-name.mjs
CHANGED
|
@@ -3,6 +3,11 @@ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
|
3
3
|
function workspaceRuntimeVersion() {
|
|
4
4
|
return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
5
5
|
}
|
|
6
|
+
var SERVED_WORKSPACE_RUNTIME_FLOOR = {
|
|
7
|
+
version: "0.10.1",
|
|
8
|
+
image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
|
|
9
|
+
};
|
|
10
|
+
var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
|
|
6
11
|
|
|
7
12
|
// src/image-name.ts
|
|
8
13
|
var SNAPSHOT_SCHEMA_VERSION = 8;
|
package/dist/image.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { RUNTIME_DIR, WORKSPACE_RUNTIME_PORT, WORKSPACE_DIR } from "./defaults";
|
|
2
2
|
export { SNAPSHOT_SCHEMA_VERSION, snapshotVersion, assertSandboxImageReference, defaultSandboxImage, } from "./image-name";
|
|
3
|
+
export { SERVED_WORKSPACE_RUNTIME_FLOOR } from "./runtime-version";
|
|
3
4
|
export declare const SANDBOX_IMAGE_REPOSITORY: string;
|
|
4
5
|
export declare const SANDBOX_IMAGE: string;
|
package/dist/image.mjs
CHANGED
|
@@ -11,6 +11,11 @@ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
|
11
11
|
function workspaceRuntimeVersion() {
|
|
12
12
|
return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
13
13
|
}
|
|
14
|
+
var SERVED_WORKSPACE_RUNTIME_FLOOR = {
|
|
15
|
+
version: "0.10.1",
|
|
16
|
+
image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
|
|
17
|
+
};
|
|
18
|
+
var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
|
|
14
19
|
|
|
15
20
|
// src/image-name.ts
|
|
16
21
|
var SNAPSHOT_SCHEMA_VERSION = 8;
|
|
@@ -45,6 +50,7 @@ export {
|
|
|
45
50
|
RUNTIME_DIR,
|
|
46
51
|
SANDBOX_IMAGE,
|
|
47
52
|
SANDBOX_IMAGE_REPOSITORY,
|
|
53
|
+
SERVED_WORKSPACE_RUNTIME_FLOOR,
|
|
48
54
|
SNAPSHOT_SCHEMA_VERSION,
|
|
49
55
|
WORKSPACE_DIR,
|
|
50
56
|
WORKSPACE_RUNTIME_PORT,
|