@impz/testing 5.0.0 → 6.0.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.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/dist/index.js +455 -503
  3. package/package.json +44 -22
package/dist/index.js CHANGED
@@ -21711,7 +21711,6 @@ var EnvSchema = z53.object({
21711
21711
  IMP_SESSION_LOG_IMP_MAX_MIB: CountSchema3.default(64),
21712
21712
  IMP_SESSION_LOG_IMP_MAX_LIVE: CountSchema3.default(8),
21713
21713
  IMP_SESSION_LOG_MAX_AGE_DAYS: CountSchema3.default(7),
21714
- DOCKER_HOST: z53.string().optional(),
21715
21714
  IMP_KSM: z53.enum(["0", "1"]).default("0"),
21716
21715
  IMP_KSM_EXEC: z53.string().default("ksm-exec"),
21717
21716
  IMP_KSM_HEADROOM_PERCENT: z53.string().default("100"),
@@ -21876,7 +21875,6 @@ function loadConfig(env) {
21876
21875
  impMaxLive: parsed.IMP_SESSION_LOG_IMP_MAX_LIVE,
21877
21876
  maxAgeMs: parsed.IMP_SESSION_LOG_MAX_AGE_DAYS * 86400000
21878
21877
  },
21879
- dockerHost: parsed.DOCKER_HOST ?? null,
21880
21878
  dns: parsed.IMP_DNS,
21881
21879
  subnet,
21882
21880
  ipv6: parseIpv6Setting(parsed.IMP_SUBNET6),
@@ -21951,7 +21949,7 @@ import { join as join46 } from "path";
21951
21949
  // packages/daemon/package.json
21952
21950
  var package_default = {
21953
21951
  name: "@impz/daemon",
21954
- version: "5.0.0",
21952
+ version: "6.0.0",
21955
21953
  private: true,
21956
21954
  description: "impd: the imp host daemon",
21957
21955
  type: "module",
@@ -21960,6 +21958,7 @@ var package_default = {
21960
21958
  "./testing": "./testing/index.ts",
21961
21959
  "./testing/build-stub-clock": "./testing/platform/build-stub-clock.ts",
21962
21960
  "./testing/data-layout": "./src/vm/storage/data-layout.ts",
21961
+ "./testing/pebble-images": "./testing/network/pebble-images.ts",
21963
21962
  "./testing/start-stub-agent": "./testing/vm/start-stub-agent.ts",
21964
21963
  "./testing/start-test-impd": "./testing/start-test-impd.ts",
21965
21964
  "./testing/test-token": "./testing/test-token.ts",
@@ -31995,11 +31994,7 @@ function createImpServer(options, caller, calls) {
31995
31994
  const printError = options.printError ?? console.error;
31996
31995
  const server = new McpServer({ name: "imp", title: "imp", version: options.version }, {
31997
31996
  capabilities: { tools: { listChanged: false } },
31998
- instructions: [
31999
- 'imp runs persistent Linux microVMs ("imps") that sleep when idle and wake on use.',
32000
- `This server may touch ${caller.guard.summary}.`,
32001
- "Create an imp, run commands and read and write files in it, checkpoint before a risky change, fork to try two fixes, and destroy what you no longer need."
32002
- ].join(" "),
31997
+ instructions: buildInstructions(caller),
32003
31998
  supportedProtocolVersions: [...PROTOCOL_VERSIONS, ...MODERN_PROTOCOL_VERSIONS]
32004
31999
  });
32005
32000
  server.server.fallbackRequestHandler = (request) => Promise.reject(new ProtocolError(ProtocolErrorCode.MethodNotFound, `unknown method: ${request.method}`));
@@ -32055,6 +32050,20 @@ function createImpServer(options, caller, calls) {
32055
32050
  }
32056
32051
  return server;
32057
32052
  }
32053
+ var SCOPE_GUIDES = {
32054
+ read: "This caller has the read scope, so it can only look: list imps, their URLs, the images and the checkpoints. Running commands, reading or writing files, and creating, checkpointing, forking or destroying an imp need a wider grant: exec or manage.",
32055
+ exec: "This caller has the exec scope: list imps, their URLs, the images and the checkpoints, run commands and read and write files in an imp, and put one to sleep. Creating, checkpointing, restoring, forking or destroying an imp needs a manage grant.",
32056
+ manage: "Create an imp, run commands and read and write files in it, checkpoint before a risky change, fork to try two fixes, and destroy what you no longer need."
32057
+ };
32058
+ function buildInstructions(caller) {
32059
+ const names = TOOLS.filter((tool) => hasScope(caller.scope, tool.scope)).map((tool) => tool.definition.name);
32060
+ return [
32061
+ 'imp runs persistent Linux microVMs ("imps") that sleep when idle and wake on use.',
32062
+ `This server may touch ${caller.guard.summary}.`,
32063
+ SCOPE_GUIDES[caller.scope],
32064
+ `Its tools: ${names.join(", ")}.`
32065
+ ].join(" ");
32066
+ }
32058
32067
  function startProgress(ctx, options) {
32059
32068
  const token = ctx.mcpReq._meta?.progressToken;
32060
32069
  if (token === undefined) {
@@ -33440,7 +33449,7 @@ input=$target.in
33440
33449
  next=$target.tmp
33441
33450
 
33442
33451
  mkdir -p "$(dirname "$target")"
33443
- cat >"$input"
33452
+ cat > "$input"
33444
33453
 
33445
33454
  # the first line that is the marker, matched whole and as a fixed string
33446
33455
  split=$(grep -n -x -F -e "$marker" "$input" | head -n 1 | cut -d : -f 1)
@@ -33467,7 +33476,7 @@ done
33467
33476
  fi
33468
33477
  echo
33469
33478
  head -n "$((split - 1))" "$input"
33470
- } >"$next"
33479
+ } > "$next"
33471
33480
 
33472
33481
  rm -f "$input"
33473
33482
 
@@ -47420,7 +47429,7 @@ function createImpdServices(config, deps, parts) {
47420
47429
 
47421
47430
  // packages/daemon/src/imps/images/build/create-image-builds.ts
47422
47431
  import { existsSync as existsSync18, mkdirSync as mkdirSync20, rmSync as rmSync24, statSync as statSync12 } from "fs";
47423
- import { join as join29, posix as posix3 } from "path";
47432
+ import { join as join28, posix as posix3 } from "path";
47424
47433
 
47425
47434
  // packages/local-tar/src/extract-local.ts
47426
47435
  import tar from "tar-stream";
@@ -47885,6 +47894,9 @@ function formatFamiliarRepository(reference) {
47885
47894
  }
47886
47895
 
47887
47896
  // packages/daemon/src/imps/docker-contract/check-registry.ts
47897
+ function checkReferenceRegistry(reference) {
47898
+ return checkRegistry(parseImageReference(reference).registry);
47899
+ }
47888
47900
  function checkRegistry(registry) {
47889
47901
  const host = readRegistryHost(registry).toLowerCase();
47890
47902
  if (host === "localhost" || host.endsWith(".localhost")) {
@@ -47895,9 +47907,6 @@ function checkRegistry(registry) {
47895
47907
  }
47896
47908
  return null;
47897
47909
  }
47898
- function checkReferenceRegistry(reference) {
47899
- return checkRegistry(parseImageReference(reference).registry);
47900
- }
47901
47910
  function readRegistryHost(registry) {
47902
47911
  if (registry.startsWith("[")) {
47903
47912
  return registry.slice(1, registry.indexOf("]"));
@@ -48761,54 +48770,12 @@ class DockerBuildError extends Error {
48761
48770
  // packages/daemon/src/imps/images/build/guest-build.ts
48762
48771
  import * as z86 from "zod";
48763
48772
 
48764
- // packages/daemon/src/vm/storage/count-tree-bytes.ts
48765
- import { lstat as lstat3, readdir as readdir3 } from "fs/promises";
48766
- import { join as join28 } from "path";
48767
- async function countTreeBytes(root, deps = {}) {
48768
- const readDir = deps.readDir ?? readDirEntries;
48769
- const inodes = new Map;
48770
- const dirs = [root];
48771
- for (let directory = dirs.pop();directory !== undefined; directory = dirs.pop()) {
48772
- const entries = await readEntries2(readDir, directory);
48773
- for (const entry of entries) {
48774
- if (entry.isDirectory()) {
48775
- dirs.push(join28(directory, entry.name));
48776
- }
48777
- }
48778
- const files = entries.filter((entry) => entry.isFile());
48779
- const stats = await Promise.all(files.map((entry) => readFileStats(join28(directory, entry.name))));
48780
- for (const stat of stats) {
48781
- if (stat !== undefined) {
48782
- inodes.set(`${String(stat.dev)}:${String(stat.ino)}`, Math.max(0, stat.blocks - 1) * 512);
48783
- }
48784
- }
48785
- }
48786
- return [...inodes.values()].reduce((sum, bytes) => sum + bytes, 0);
48787
- }
48788
- function readDirEntries(directory) {
48789
- return readdir3(directory, { withFileTypes: true });
48790
- }
48791
- async function readEntries2(readDir, directory) {
48792
- try {
48793
- return await readDir(directory);
48794
- } catch (error) {
48795
- if (isMissing(error)) {
48796
- return [];
48797
- }
48798
- throw error;
48799
- }
48800
- }
48801
- function isMissing(error) {
48802
- return error instanceof Error && "code" in error && error.code === "ENOENT";
48803
- }
48804
- async function readFileStats(path) {
48805
- try {
48806
- return await lstat3(path);
48807
- } catch (error) {
48808
- if (isMissing(error)) {
48809
- return;
48810
- }
48811
- throw error;
48773
+ // packages/daemon/src/imps/images/build/image-limit-error.ts
48774
+ class ImageLimitError extends ImpdError {
48775
+ name = "ImageLimitError";
48776
+ constructor(limit, max) {
48777
+ const over = limit === "bytes" ? `over ${String(Math.floor(max / 1024 ** 2))} MiB (IMP_BUILD_IMAGE_MAX_MIB)` : `over ${String(max)} files (IMP_BUILD_IMAGE_MAX_FILES)`;
48778
+ super("BAD_REQUEST", { message: `the built image's filesystem is ${over}` });
48812
48779
  }
48813
48780
  }
48814
48781
 
@@ -48844,73 +48811,9 @@ var UNPACK_TAR_ARGS = [
48844
48811
  "-xpf",
48845
48812
  "-"
48846
48813
  ];
48847
- async function writeExportedTree(containerId, root) {
48848
- const exporter = Bun.spawn(["docker", "export", containerId], {
48849
- stdin: "ignore",
48850
- stdout: "pipe",
48851
- stderr: "pipe",
48852
- env: process.env
48853
- });
48854
- const tar = await spawnTar(root, exporter);
48855
- const state = { hasExportEnded: false, isTarFirst: false, isExportStopped: false };
48856
- const waitForExport = async () => {
48857
- await exporter.exited;
48858
- state.hasExportEnded = true;
48859
- };
48860
- const waitForTar = async () => {
48861
- const code = await tar.exited;
48862
- if (code !== 0 && !state.hasExportEnded) {
48863
- state.isTarFirst = true;
48864
- state.isExportStopped = true;
48865
- exporter.kill();
48866
- }
48867
- };
48868
- waitForExport();
48869
- waitForTar();
48870
- const [exportCode, exportStderr, tarCode, tarStderr] = await Promise.all([
48871
- exporter.exited,
48872
- new Response(exporter.stderr).text(),
48873
- tar.exited,
48874
- new Response(tar.stderr).text()
48875
- ]);
48876
- const failure = findUnpackFailure({
48877
- containerId,
48878
- exported: { exitCode: exportCode, stdout: "", stderr: exportStderr },
48879
- unpacked: { exitCode: tarCode, stdout: "", stderr: tarStderr },
48880
- isTarFirst: state.isTarFirst,
48881
- isExportStopped: state.isExportStopped && exporter.signalCode !== null
48882
- });
48883
- if (failure !== null) {
48884
- throw failure;
48885
- }
48886
- assertUnpacked({ exitCode: tarCode, stdout: "", stderr: tarStderr });
48887
- }
48888
- async function spawnTar(root, exporter) {
48889
- try {
48890
- return Bun.spawn([...UNPACK_TAR_ARGS, "-C", root], {
48891
- stdin: exporter.stdout,
48892
- stdout: "ignore",
48893
- stderr: "pipe",
48894
- env: { ...process.env, LC_ALL: "C" }
48895
- });
48896
- } catch (error) {
48897
- exporter.kill();
48898
- await exporter.exited;
48899
- throw error;
48900
- }
48901
- }
48902
- function findUnpackFailure(outcome) {
48903
- const exportFailure = outcome.exported.exitCode === 0 || outcome.isExportStopped ? null : buildExitError(["docker", "export", outcome.containerId], outcome.exported);
48904
- const tarFailure = outcome.unpacked.exitCode === 0 ? null : buildExitError(["tar"], outcome.unpacked);
48905
- if (exportFailure === null || tarFailure === null) {
48906
- return exportFailure ?? tarFailure;
48907
- }
48908
- const [first, second] = outcome.isTarFirst ? [tarFailure, exportFailure] : [exportFailure, tarFailure];
48909
- return new Error(`${first.message}; ${second.message}`);
48910
- }
48911
48814
  function assertUnpacked(result) {
48912
48815
  if (result.exitCode !== 0) {
48913
- throw buildExitError(["docker", "export", "|", "tar"], result);
48816
+ throw buildExitError(["tar"], result);
48914
48817
  }
48915
48818
  const failure = findXattrFailure(result.stderr);
48916
48819
  if (failure?.missingSetfcap === true) {
@@ -48946,6 +48849,122 @@ function readSetfcapWarning(status = readFileSync23("/proc/self/status", "utf8")
48946
48849
  return `no CAP_SETFCAP, so an image add or build fails on an image with a file capability; ${SETFCAP_HELP}`;
48947
48850
  }
48948
48851
 
48852
+ // packages/daemon/src/imps/images/rootfs/write-streamed-tree.ts
48853
+ var EXPORT_IDLE_MS = 120000;
48854
+ var GROW_STEP_BYTES = 256 * 1024 ** 2;
48855
+ var TAR_STOPPED = new Error("tar stopped before the export ended");
48856
+ async function writeStreamedTree(options) {
48857
+ const root = options.root;
48858
+ const limits = options.limits;
48859
+ const grow = options.grow ?? (() => Promise.resolve());
48860
+ const startTimer = options.startTimer;
48861
+ const maxBytes = limits.maxBytes;
48862
+ const tar = options.startTar([...UNPACK_TAR_ARGS, "-vv", "--quoting-style=escape", "-C", root]);
48863
+ const sent = { bytes: 0 };
48864
+ const unpacked = {};
48865
+ const stopExport = new AbortController;
48866
+ const readMax = (limit) => limit === "bytes" ? maxBytes : limits.maxFiles;
48867
+ const listed = { bytes: 0 };
48868
+ const held = { bytes: 0 };
48869
+ const growHold = async () => {
48870
+ const tree = Math.max(sent.bytes, listed.bytes);
48871
+ const needed = 2 * tree + planRootfsOverhead(tree);
48872
+ if (needed > held.bytes) {
48873
+ held.bytes = (Math.floor(needed / GROW_STEP_BYTES) + 1) * GROW_STEP_BYTES;
48874
+ const writtenBytes = await options.countTreeBytes(root);
48875
+ await grow(held.bytes, writtenBytes);
48876
+ }
48877
+ };
48878
+ const onCounted = (bytes) => {
48879
+ listed.bytes = bytes;
48880
+ };
48881
+ const counting = countUnpacked(tar.stdout, limits, onCounted, (limit) => {
48882
+ unpacked.over = limit;
48883
+ const max = readMax(limit);
48884
+ tar.kill();
48885
+ stopExport.abort(new ImageLimitError(limit, max));
48886
+ });
48887
+ const waitForTar = async () => {
48888
+ const code = await tar.exited;
48889
+ if (code !== 0 && unpacked.ended !== true) {
48890
+ stopExport.abort(TAR_STOPPED);
48891
+ }
48892
+ };
48893
+ waitForTar();
48894
+ const assertWithinLimits = () => {
48895
+ const over = unpacked.over;
48896
+ if (over !== undefined) {
48897
+ throw new ImageLimitError(over, readMax(over));
48898
+ }
48899
+ };
48900
+ const stopIdle = () => {
48901
+ stopExport.abort(new Error(`${options.source} sent nothing in ${String(EXPORT_IDLE_MS / 1000)} s`));
48902
+ };
48903
+ const idle = { cancel: startTimer(stopIdle, EXPORT_IDLE_MS) };
48904
+ const stderrText = new Response(tar.stderr).text();
48905
+ try {
48906
+ const exported = await options.runExport(async (chunk) => {
48907
+ idle.cancel();
48908
+ idle.cancel = startTimer(stopIdle, EXPORT_IDLE_MS);
48909
+ sent.bytes += chunk.byteLength;
48910
+ if (sent.bytes > maxBytes) {
48911
+ throw new ImageLimitError("bytes", maxBytes);
48912
+ }
48913
+ assertWithinLimits();
48914
+ await growHold();
48915
+ await tar.stdin.write(chunk);
48916
+ await tar.stdin.flush();
48917
+ }, AbortSignal.any([options.signal, stopExport.signal]));
48918
+ if (exported.exitCode !== 0) {
48919
+ throw new Error(`${options.source}: ${exported.stderr.trim()}`);
48920
+ }
48921
+ unpacked.ended = true;
48922
+ await tar.stdin.end();
48923
+ const [exitCode, stderr] = await Promise.all([tar.exited, stderrText, counting]);
48924
+ assertWithinLimits();
48925
+ assertUnpacked({ exitCode, stdout: "", stderr });
48926
+ await growHold();
48927
+ } catch (error) {
48928
+ tar.kill();
48929
+ assertWithinLimits();
48930
+ if (stopExport.signal.reason === TAR_STOPPED) {
48931
+ const [exitCode, stderr] = await Promise.all([tar.exited, stderrText]);
48932
+ assertUnpacked({ exitCode, stdout: "", stderr });
48933
+ }
48934
+ throw error;
48935
+ } finally {
48936
+ idle.cancel();
48937
+ }
48938
+ }
48939
+ async function countUnpacked(stdout, limits, onCounted, onOver) {
48940
+ const decoder = new TextDecoder;
48941
+ const counted = { lines: 0, bytes: 0, rest: "" };
48942
+ for await (const chunk of stdout) {
48943
+ const lines = `${counted.rest}${decoder.decode(chunk, { stream: true })}`.split(`
48944
+ `);
48945
+ counted.rest = lines.pop() ?? "";
48946
+ for (const line of lines) {
48947
+ counted.lines += 1;
48948
+ counted.bytes += readAllocatedBytes(line);
48949
+ }
48950
+ onCounted(counted.bytes);
48951
+ if (counted.bytes > limits.maxBytes) {
48952
+ onOver("bytes");
48953
+ return;
48954
+ }
48955
+ if (counted.lines > limits.maxFiles) {
48956
+ onOver("files");
48957
+ return;
48958
+ }
48959
+ }
48960
+ }
48961
+ var BLOCK_BYTES = 4096;
48962
+ function readAllocatedBytes(line) {
48963
+ const size = line.trim().split(/\s+/v)[2] ?? "";
48964
+ const bytes = /^\d+$/v.test(size) ? Number(size) : 0;
48965
+ return Math.max(1, Math.ceil(bytes / BLOCK_BYTES)) * BLOCK_BYTES;
48966
+ }
48967
+
48949
48968
  // packages/daemon/src/imps/images/build/create-build-steps.ts
48950
48969
  var STEP_PATTERN = /^\[(?:\S+ )?\d+\/\d+\] /v;
48951
48970
  function createBuildSteps(setStep) {
@@ -49018,15 +49037,6 @@ function createPlainLines(output) {
49018
49037
  };
49019
49038
  }
49020
49039
 
49021
- // packages/daemon/src/imps/images/build/image-limit-error.ts
49022
- class ImageLimitError extends ImpdError {
49023
- name = "ImageLimitError";
49024
- constructor(limit, max) {
49025
- const over = limit === "bytes" ? `over ${String(Math.floor(max / 1024 ** 2))} MiB (IMP_BUILD_IMAGE_MAX_MIB)` : `over ${String(max)} files (IMP_BUILD_IMAGE_MAX_FILES)`;
49026
- super("BAD_REQUEST", { message: `the built image's filesystem is ${over}` });
49027
- }
49028
- }
49029
-
49030
49040
  // packages/daemon/src/imps/images/build/guest-build.ts
49031
49041
  var GUEST_BUILD_TAG = "imp-build:latest";
49032
49042
  var LOG_MAX_CHARS = 8000;
@@ -49082,55 +49092,9 @@ async function loadGuestImage(exec, options) {
49082
49092
  var DIGEST_PREFIX = "imp-build-";
49083
49093
  var DIGEST_DOMAIN = `imp build image v1
49084
49094
  `;
49085
- var EXPORT_IDLE_MS = 120000;
49086
- var GROW_STEP_BYTES = 256 * 1024 ** 2;
49087
- var TAR_STOPPED = new Error("tar stopped before the export ended");
49088
- function createImageHash(configText) {
49089
- const config = new TextEncoder().encode(configText);
49090
- const length = new Uint8Array(8);
49091
- new DataView(length.buffer).setBigUint64(0, BigInt(config.byteLength));
49092
- const hash = new Bun.CryptoHasher("sha256");
49093
- hash.update(DIGEST_DOMAIN);
49094
- hash.update(length);
49095
- hash.update(config);
49096
- return hash;
49097
- }
49098
- var BLOCK_BYTES = 4096;
49099
- function readAllocatedBytes(line) {
49100
- const size = line.trim().split(/\s+/v)[2] ?? "";
49101
- const bytes = /^\d+$/v.test(size) ? Number(size) : 0;
49102
- return Math.max(1, Math.ceil(bytes / BLOCK_BYTES)) * BLOCK_BYTES;
49103
- }
49104
- async function countUnpacked(stdout, limits, onCounted, onOver) {
49105
- const decoder = new TextDecoder;
49106
- const counted = { lines: 0, bytes: 0, rest: "" };
49107
- for await (const chunk of stdout) {
49108
- const lines = `${counted.rest}${decoder.decode(chunk, { stream: true })}`.split(`
49109
- `);
49110
- counted.rest = lines.pop() ?? "";
49111
- for (const line of lines) {
49112
- counted.lines += 1;
49113
- counted.bytes += readAllocatedBytes(line);
49114
- }
49115
- onCounted(counted.bytes);
49116
- if (counted.bytes > limits.maxBytes) {
49117
- onOver("bytes");
49118
- return;
49119
- }
49120
- if (counted.lines > limits.maxFiles) {
49121
- onOver("files");
49122
- return;
49123
- }
49124
- }
49125
- }
49126
49095
  async function writeGuestTree(options) {
49127
49096
  const exec = options.exec;
49128
- const root = options.root;
49129
- const limits = options.limits;
49130
49097
  const signal = options.signal;
49131
- const grow = options.grow ?? (() => Promise.resolve());
49132
- const startTimer = options.startTimer ?? startRealTimer;
49133
- const maxBytes = limits.maxBytes;
49134
49098
  const inspected = await exec(["docker", "image", "inspect", "--format", "{{json .Config}}", GUEST_BUILD_TAG], { signal });
49135
49099
  if (inspected.exitCode !== 0) {
49136
49100
  throw new Error(`docker image inspect in the builder: ${inspected.stderr.trim()}`);
@@ -49142,94 +49106,36 @@ async function writeGuestTree(options) {
49142
49106
  throw new Error(`docker create in the builder: ${created.stderr.trim()}`);
49143
49107
  }
49144
49108
  const containerId = ContainerIdSchema.parse(created.stdout.trim());
49145
- const tar = Bun.spawn([...UNPACK_TAR_ARGS, "-vv", "--quoting-style=escape", "-C", root], {
49146
- stdin: "pipe",
49147
- stdout: "pipe",
49148
- stderr: "pipe",
49149
- env: { ...process.env, LC_ALL: "C" }
49150
- });
49151
49109
  const hash = createImageHash(configText);
49152
- const sent = { bytes: 0 };
49153
- const unpacked = {};
49154
- const stopExport = new AbortController;
49155
- const readMax = (limit) => limit === "bytes" ? maxBytes : limits.maxFiles;
49156
- const listed = { bytes: 0 };
49157
- const held = { bytes: 0 };
49158
- const growHold = async () => {
49159
- const tree = Math.max(sent.bytes, listed.bytes);
49160
- const needed = 2 * tree + planRootfsOverhead(tree);
49161
- if (needed > held.bytes) {
49162
- held.bytes = (Math.floor(needed / GROW_STEP_BYTES) + 1) * GROW_STEP_BYTES;
49163
- const writtenBytes = await countTreeBytes(root);
49164
- await grow(held.bytes, writtenBytes);
49165
- }
49166
- };
49167
- const onCounted = (bytes) => {
49168
- listed.bytes = bytes;
49169
- };
49170
- const counting = countUnpacked(tar.stdout, limits, onCounted, (limit) => {
49171
- unpacked.over = limit;
49172
- const max = readMax(limit);
49173
- tar.kill();
49174
- stopExport.abort(new ImageLimitError(limit, max));
49175
- });
49176
- const waitForTar = async () => {
49177
- const code = await tar.exited;
49178
- if (code !== 0 && unpacked.ended !== true) {
49179
- stopExport.abort(TAR_STOPPED);
49180
- }
49181
- };
49182
- waitForTar();
49183
- const assertWithinLimits = () => {
49184
- const over = unpacked.over;
49185
- if (over !== undefined) {
49186
- throw new ImageLimitError(over, readMax(over));
49187
- }
49188
- };
49189
- const stopIdle = () => {
49190
- stopExport.abort(new Error(`docker export in the builder sent nothing in ${String(EXPORT_IDLE_MS / 1000)} s`));
49191
- };
49192
- const idle = { cancel: startTimer(stopIdle, EXPORT_IDLE_MS) };
49193
- const stderrText = new Response(tar.stderr).text();
49194
- try {
49195
- const exported = await exec(["docker", "export", containerId], {
49196
- signal: AbortSignal.any([signal, stopExport.signal]),
49110
+ await writeStreamedTree({
49111
+ root: options.root,
49112
+ limits: options.limits,
49113
+ signal,
49114
+ source: "docker export in the builder",
49115
+ grow: options.grow,
49116
+ startTimer: options.startTimer,
49117
+ startTar: options.startTar,
49118
+ countTreeBytes: options.countTreeBytes,
49119
+ runExport: (onStdout, exportSignal) => exec(["docker", "export", containerId], {
49120
+ signal: exportSignal,
49197
49121
  onStdout: async (chunk) => {
49198
- idle.cancel();
49199
- idle.cancel = startTimer(stopIdle, EXPORT_IDLE_MS);
49200
- sent.bytes += chunk.byteLength;
49201
- if (sent.bytes > maxBytes) {
49202
- throw new ImageLimitError("bytes", maxBytes);
49203
- }
49204
- assertWithinLimits();
49205
- await growHold();
49122
+ await onStdout(chunk);
49206
49123
  hash.update(chunk);
49207
- await tar.stdin.write(chunk);
49208
- await tar.stdin.flush();
49209
49124
  }
49210
- });
49211
- if (exported.exitCode !== 0) {
49212
- throw new Error(`docker export in the builder: ${exported.stderr.trim()}`);
49213
- }
49214
- unpacked.ended = true;
49215
- await tar.stdin.end();
49216
- const [exitCode, stderr] = await Promise.all([tar.exited, stderrText, counting]);
49217
- assertWithinLimits();
49218
- assertUnpacked({ exitCode, stdout: "", stderr });
49219
- await growHold();
49220
- } catch (error) {
49221
- tar.kill();
49222
- assertWithinLimits();
49223
- if (stopExport.signal.reason === TAR_STOPPED) {
49224
- const [exitCode, stderr] = await Promise.all([tar.exited, stderrText]);
49225
- assertUnpacked({ exitCode, stdout: "", stderr });
49226
- }
49227
- throw error;
49228
- } finally {
49229
- idle.cancel();
49230
- }
49125
+ })
49126
+ });
49231
49127
  return { digest: `${DIGEST_PREFIX}${hash.digest("hex")}`, config };
49232
49128
  }
49129
+ function createImageHash(configText) {
49130
+ const config = new TextEncoder().encode(configText);
49131
+ const length = new Uint8Array(8);
49132
+ new DataView(length.buffer).setBigUint64(0, BigInt(config.byteLength));
49133
+ const hash = new Bun.CryptoHasher("sha256");
49134
+ hash.update(DIGEST_DOMAIN);
49135
+ hash.update(length);
49136
+ hash.update(config);
49137
+ return hash;
49138
+ }
49233
49139
 
49234
49140
  // packages/daemon/src/imps/images/build/write-context-tar.ts
49235
49141
  import { open as open9 } from "fs/promises";
@@ -49397,7 +49303,7 @@ function createImageBuilds(deps, writer) {
49397
49303
  });
49398
49304
  }
49399
49305
  const uploadsDir = buildUploadsDir(deps.config.dataDir);
49400
- const tarPath = join29(uploadsDir, `${Bun.randomUUIDv7()}.tar`);
49306
+ const tarPath = join28(uploadsDir, `${Bun.randomUUIDv7()}.tar`);
49401
49307
  mkdirSync20(uploadsDir, { recursive: true, mode: 448 });
49402
49308
  return deps.diskBudget.withRoom(tarBytes, async () => {
49403
49309
  try {
@@ -49516,96 +49422,33 @@ function buildImageRuntimeConfig(ociConfig) {
49516
49422
 
49517
49423
  // packages/daemon/src/imps/images/rootfs/create-image-writer.ts
49518
49424
  import { existsSync as existsSync19, mkdirSync as mkdirSync22, rmSync as rmSync26, statSync as statSync13, writeFileSync as writeFileSync8 } from "fs";
49519
- import { join as join31 } from "path";
49520
- import * as z89 from "zod";
49521
-
49522
- // packages/daemon/src/imps/docker-contract/refusal.ts
49523
- import { z as z88 } from "zod";
49524
- var PROXY_PREFIX = "imp-docker-proxy: ";
49525
- var ENGINE_FAILURE_PREFIX = `${PROXY_PREFIX}the engine call failed: `;
49526
- var REFUSAL_MAX_CHARS = 1000;
49527
- var DAEMON_ERROR_PREFIX = "Error response from daemon: ";
49528
- var UNABLE_TO_FIND_LINE = /^Unable to find image '[^'\n]+' locally$/u;
49529
- var RefusalBodySchema = z88.strictObject({ message: z88.string() });
49530
- function readProxyRefusal(text) {
49531
- const body = readJsonMessage(text.trim());
49532
- if (body !== null) {
49533
- return toRefusal(body);
49534
- }
49535
- const errorLine = readErrorLine(text.trimEnd().split(`
49536
- `));
49537
- if (!errorLine.startsWith(DAEMON_ERROR_PREFIX)) {
49538
- return null;
49539
- }
49540
- const answer = errorLine.slice(DAEMON_ERROR_PREFIX.length);
49541
- return toRefusal(readJsonMessage(answer) ?? answer);
49542
- }
49543
- function readErrorLine(lines) {
49544
- const [first = "", second = ""] = lines;
49545
- if (lines.length === 1) {
49546
- return first;
49547
- }
49548
- return lines.length === 2 && UNABLE_TO_FIND_LINE.test(first) ? second : "";
49549
- }
49550
- function toRefusal(message) {
49551
- if (!message.startsWith(PROXY_PREFIX) || message.startsWith(ENGINE_FAILURE_PREFIX)) {
49552
- return null;
49553
- }
49554
- const firstLine = message.split(`
49555
- `, 1)[0] ?? "";
49556
- return firstLine.trim().slice(0, REFUSAL_MAX_CHARS);
49557
- }
49558
- function readJsonMessage(text) {
49559
- if (!text.startsWith("{")) {
49560
- return null;
49561
- }
49562
- try {
49563
- const body = RefusalBodySchema.safeParse(JSON.parse(text));
49564
- return body.success ? body.data.message : null;
49565
- } catch {
49566
- return null;
49567
- }
49568
- }
49425
+ import { join as join30 } from "path";
49569
49426
 
49570
- // packages/daemon/src/imps/images/rootfs/run-docker.ts
49571
- async function runDocker(argv, options = {}) {
49572
- const result = await runCommand(["docker", ...argv], {
49573
- ...options.env !== undefined && { env: options.env },
49574
- ...options.signal !== undefined && { signal: options.signal }
49575
- });
49576
- if (result.exitCode !== 0) {
49577
- const refused = readRefusalError(`docker ${argv[0] ?? ""}`, result.stderr);
49578
- if (refused !== null) {
49579
- throw refused;
49580
- }
49581
- }
49582
- return result;
49583
- }
49584
- function readRefusalError(call, output) {
49585
- const refusal = readProxyRefusal(output);
49586
- if (refusal === null) {
49587
- return null;
49588
- }
49589
- getImpdLogger("image").warning("{call} refused: {refusal}", { call, refusal });
49590
- return new ImpdError("BAD_REQUEST", { message: refusal });
49591
- }
49592
- async function runDockerChecked(argv, options = {}) {
49593
- const result = await runDocker(argv, options);
49427
+ // packages/daemon/src/imps/images/rootfs/read-oci-config.ts
49428
+ import * as z88 from "zod";
49429
+ var OciConfigSchema3 = z88.object({
49430
+ digest: z88.string().regex(/^sha256:[a-f0-9]{64}$/v),
49431
+ config: z88.record(z88.string(), z88.unknown()).nullable()
49432
+ });
49433
+ async function readOciConfig(ref, signal, run) {
49434
+ const argv = ["oci-fetch", "config", ref];
49435
+ const result = await run(argv, { signal });
49436
+ signal.throwIfAborted();
49594
49437
  if (result.exitCode !== 0) {
49595
- throw buildExitError(["docker", ...argv], result);
49438
+ throw buildExitError(argv, result);
49596
49439
  }
49597
- return result.stdout;
49440
+ return OciConfigSchema3.parse(JSON.parse(result.stdout));
49598
49441
  }
49599
49442
 
49600
49443
  // packages/daemon/src/imps/images/rootfs/write-image-config.ts
49601
49444
  import { closeSync as closeSync9, constants as constants5, lstatSync as lstatSync5, mkdirSync as mkdirSync21, openSync as openSync7, rmSync as rmSync25, writeSync as writeSync3 } from "fs";
49602
- import { join as join30 } from "path";
49445
+ import { join as join29 } from "path";
49603
49446
  function writeImageConfig(root, contents) {
49604
- const etc = join30(root, "etc");
49605
- const directory = join30(etc, "imp");
49447
+ const etc = join29(root, "etc");
49448
+ const directory = join29(etc, "imp");
49606
49449
  createDirectory(etc, "/etc");
49607
49450
  createDirectory(directory, "/etc/imp");
49608
- const file = join30(directory, "image.json");
49451
+ const file = join29(directory, "image.json");
49609
49452
  rmSync25(file, { recursive: true, force: true });
49610
49453
  const fd = openSync7(file, constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW, 420);
49611
49454
  try {
@@ -49627,14 +49470,22 @@ function createDirectory(path, name) {
49627
49470
  }
49628
49471
  }
49629
49472
 
49473
+ // packages/daemon/src/imps/images/rootfs/write-oci-tree.ts
49474
+ function writeOciTree(options) {
49475
+ return writeStreamedTree({
49476
+ root: options.root,
49477
+ limits: options.limits,
49478
+ signal: options.signal,
49479
+ source: `oci-fetch export ${options.ref}`,
49480
+ grow: options.grow,
49481
+ startTimer: options.startTimer,
49482
+ startTar: options.startTar,
49483
+ countTreeBytes: options.countTreeBytes,
49484
+ runExport: (onStdout, signal) => options.runStreamed(["oci-fetch", "export", options.ref], onStdout, signal)
49485
+ });
49486
+ }
49487
+
49630
49488
  // packages/daemon/src/imps/images/rootfs/create-image-writer.ts
49631
- var InspectSchema = z89.tuple([
49632
- z89.object({
49633
- Id: z89.string(),
49634
- Config: z89.unknown(),
49635
- Size: z89.number().optional()
49636
- })
49637
- ]);
49638
49489
  function createImageWriter(deps) {
49639
49490
  const storageGate = deps.storageGate;
49640
49491
  const logger = getImpdLogger("image");
@@ -49642,22 +49493,10 @@ function createImageWriter(deps) {
49642
49493
  const publishers = new Map;
49643
49494
  const freshRootfs = new Set;
49644
49495
  const guestRootfsLock = buildKeyedMutex();
49645
- const docker = { env: deps.dockerEnv };
49646
- const readInspect = async (ref, signal) => {
49647
- const options = { ...docker, signal };
49648
- const first = await runDocker(["image", "inspect", ref], options);
49649
- if (first.exitCode === 0) {
49650
- return InspectSchema.parse(JSON.parse(first.stdout))[0];
49651
- }
49652
- await runDockerChecked(["pull", "--quiet", ref], options);
49653
- signal.throwIfAborted();
49654
- const stdout = await runDockerChecked(["image", "inspect", ref], options);
49655
- return InspectSchema.parse(JSON.parse(stdout))[0];
49656
- };
49657
49496
  const makeWorkDir = () => {
49658
49497
  const images = buildImagesDir(deps.config.dataDir);
49659
- const work = join31(images, `.build-${Bun.randomUUIDv7()}`);
49660
- const root = join31(work, "root");
49498
+ const work = join30(images, `.build-${Bun.randomUUIDv7()}`);
49499
+ const root = join30(work, "root");
49661
49500
  mkdirSync22(images, { recursive: true });
49662
49501
  mkdirSync22(work, { mode: 448 });
49663
49502
  mkdirSync22(root, { mode: 493 });
@@ -49668,7 +49507,7 @@ function createImageWriter(deps) {
49668
49507
  const usage = await readTreeUsage(root);
49669
49508
  const plan = planRootfs(usage);
49670
49509
  await deps.storage.createImage(digest, async (directory) => {
49671
- const image = join31(directory, "rootfs.ext4");
49510
+ const image = join30(directory, "rootfs.ext4");
49672
49511
  await runChecked(["truncate", "-s", String(plan.bytes), image]);
49673
49512
  await runChecked([
49674
49513
  "mkfs.ext4",
@@ -49681,29 +49520,10 @@ function createImageWriter(deps) {
49681
49520
  root,
49682
49521
  image
49683
49522
  ]);
49684
- writeFileSync8(join31(directory, "config.json"), JSON.stringify(ociConfig ?? {}, null, 2));
49523
+ writeFileSync8(join30(directory, "config.json"), JSON.stringify(ociConfig ?? {}, null, 2));
49685
49524
  });
49686
49525
  return readDiskUsage(buildImagePaths(deps.config.dataDir, digest).rootfs);
49687
49526
  };
49688
- const createRootfs = async (ref, digest, ociConfig) => {
49689
- const paths = buildImagePaths(deps.config.dataDir, digest);
49690
- if (existsSync19(paths.rootfs)) {
49691
- return readDiskUsage(paths.rootfs);
49692
- }
49693
- const created = await runDockerChecked(["create", ref, "/bin/true"], docker);
49694
- const containerId = created.trim();
49695
- try {
49696
- const workDir = makeWorkDir();
49697
- try {
49698
- await writeExportedTree(containerId, workDir.root);
49699
- return await writeRootfs(workDir.root, digest, ociConfig);
49700
- } finally {
49701
- rmSync26(workDir.work, { recursive: true, force: true });
49702
- }
49703
- } finally {
49704
- await runCommand(["docker", "rm", "-f", containerId]);
49705
- }
49706
- };
49707
49527
  const createRootfsOnce = (digest, create) => building.run(digest, create);
49708
49528
  const writeDockerImageRow = async (imageName, ref, digest, sizeBytes) => {
49709
49529
  const at = deps.clock.now();
@@ -49757,22 +49577,6 @@ function createImageWriter(deps) {
49757
49577
  });
49758
49578
  }
49759
49579
  });
49760
- const createImageOnHost = async (ref, imageName, options) => {
49761
- const inspect = await readInspect(ref, options.signal);
49762
- options.onPulled();
49763
- const treeBytes = inspect.Size ?? 0;
49764
- const buildBytes = 2 * treeBytes + planRootfsOverhead(treeBytes);
49765
- const withRoom = deps.diskBudget.withRoom;
49766
- return withRoom(buildBytes, () => storageGate.join(async () => {
49767
- await holdPublisher(inspect.Id);
49768
- try {
49769
- const sizeBytes = await createRootfsOnce(inspect.Id, () => createRootfs(ref, inspect.Id, inspect.Config));
49770
- return await writeDockerImageRow(imageName, ref, inspect.Id, sizeBytes);
49771
- } finally {
49772
- await releasePublisher(inspect.Id, imageName);
49773
- }
49774
- }));
49775
- };
49776
49580
  const writeGuestRootfsAndRow = async (guest) => {
49777
49581
  const digest = guest.digest;
49778
49582
  const rootfs = buildImagePaths(deps.config.dataDir, digest).rootfs;
@@ -49803,7 +49607,10 @@ function createImageWriter(deps) {
49803
49607
  root,
49804
49608
  limits: { maxBytes, maxFiles: deps.config.build.imageMaxFiles },
49805
49609
  signal,
49806
- grow
49610
+ grow,
49611
+ startTar: deps.startTar,
49612
+ countTreeBytes: deps.countTreeBytes,
49613
+ startTimer: deps.startTimer
49807
49614
  });
49808
49615
  return await writeGuestRootfsAndRow({
49809
49616
  root,
@@ -49818,6 +49625,41 @@ function createImageWriter(deps) {
49818
49625
  }
49819
49626
  }));
49820
49627
  };
49628
+ const createBuilderImage = async (imageName, options) => {
49629
+ const ref = deps.config.build.image;
49630
+ const signal = options.signal;
49631
+ const build = deps.config.build;
49632
+ const limits = { maxBytes: build.imageMaxBytes, maxFiles: build.imageMaxFiles };
49633
+ const image = await readOciConfig(ref, signal, deps.runCommand);
49634
+ return deps.diskBudget.withGrowingRoom((grow) => storageGate.join(async () => {
49635
+ const workDir = makeWorkDir();
49636
+ const root = workDir.root;
49637
+ try {
49638
+ await writeOciTree({
49639
+ ref,
49640
+ root,
49641
+ limits,
49642
+ signal,
49643
+ grow,
49644
+ runStreamed: deps.runStreamed,
49645
+ startTar: deps.startTar,
49646
+ countTreeBytes: deps.countTreeBytes,
49647
+ startTimer: deps.startTimer
49648
+ });
49649
+ options.onFetched();
49650
+ return await writeGuestRootfsAndRow({
49651
+ root,
49652
+ digest: image.digest,
49653
+ config: image.config,
49654
+ imageName,
49655
+ ref,
49656
+ signal: new AbortController().signal
49657
+ });
49658
+ } finally {
49659
+ rmSync26(workDir.work, { recursive: true, force: true });
49660
+ }
49661
+ }));
49662
+ };
49821
49663
  const withPublisher = async (digest, imageName, publish) => {
49822
49664
  await holdPublisher(digest);
49823
49665
  try {
@@ -49826,7 +49668,7 @@ function createImageWriter(deps) {
49826
49668
  await releasePublisher(digest, imageName);
49827
49669
  }
49828
49670
  };
49829
- return { createImageOnHost, writeGuestImage, removeIdleRootfs, removeIdleImage, withPublisher };
49671
+ return { createBuilderImage, writeGuestImage, removeIdleRootfs, removeIdleImage, withPublisher };
49830
49672
  }
49831
49673
  function requireDockerImage(existing) {
49832
49674
  if (existing?.source === "imp") {
@@ -49900,18 +49742,18 @@ function createImageAdds(deps, writer) {
49900
49742
  const createBuilderImage = async (ref) => {
49901
49743
  const pullMs = deps.builderImagePullMs ?? BUILDER_IMAGE_PULL_MS;
49902
49744
  const timeout = deps.startPullLimit?.(pullMs) ?? AbortSignal.timeout(pullMs);
49903
- const step = { pulled: false };
49745
+ const step = { fetched: false };
49904
49746
  try {
49905
- return await writer.createImageOnHost(ref, BUILDER_IMAGE, {
49747
+ return await writer.createBuilderImage(BUILDER_IMAGE, {
49906
49748
  signal: timeout,
49907
- onPulled: () => {
49908
- step.pulled = true;
49749
+ onFetched: () => {
49750
+ step.fetched = true;
49909
49751
  }
49910
49752
  });
49911
49753
  } catch (error) {
49912
- const reason = timeout.aborted && !step.pulled ? `the pull did not finish in ${formatPullLimit(pullMs)}; on a slow link, pull ${ref} on the host engine first (docker pull ${ref}), and impd takes it from there` : getErrorMessage(error);
49754
+ const reason = timeout.aborted && !step.fetched ? `the fetch did not finish in ${formatPullLimit(pullMs)}` : getErrorMessage(error);
49913
49755
  throw new ImpdError("SERVICE_UNAVAILABLE", {
49914
- message: `impd cannot add its builder image ${ref} (IMP_BUILD_IMAGE) from the host engine, so no image add or build can run: ${reason}`
49756
+ message: `impd cannot add its builder image ${ref} (IMP_BUILD_IMAGE) from its registry, so no image add or build can run: ${reason}`
49915
49757
  });
49916
49758
  }
49917
49759
  };
@@ -50103,7 +49945,10 @@ function createImageEviction(deps) {
50103
49945
  // packages/daemon/src/imps/images/image-service.ts
50104
49946
  function createImageService(deps) {
50105
49947
  const clock = deps.clock ?? REAL_CLOCK;
50106
- const writer = createImageWriter({ ...deps, clock });
49948
+ const writer = createImageWriter({
49949
+ ...deps,
49950
+ clock
49951
+ });
50107
49952
  const builds = createImageBuilds({ ...deps, clock }, writer);
50108
49953
  const adds = createImageAdds({ ...deps, clock }, writer);
50109
49954
  const eviction = createImageEviction({
@@ -50190,6 +50035,91 @@ function createImageService(deps) {
50190
50035
  };
50191
50036
  }
50192
50037
 
50038
+ // packages/daemon/src/imps/images/rootfs/start-tar-process.ts
50039
+ function startTarProcess(argv) {
50040
+ return Bun.spawn([...argv], {
50041
+ stdin: "pipe",
50042
+ stdout: "pipe",
50043
+ stderr: "pipe",
50044
+ env: { ...process.env, LC_ALL: "C" }
50045
+ });
50046
+ }
50047
+
50048
+ // packages/daemon/src/platform/process/run-streamed.ts
50049
+ async function runStreamed(argv, onStdout, signal) {
50050
+ const child = Bun.spawn([...argv], {
50051
+ stdin: "ignore",
50052
+ stdout: "pipe",
50053
+ stderr: "pipe",
50054
+ env: process.env,
50055
+ signal
50056
+ });
50057
+ const stderr = new Response(child.stderr).text();
50058
+ try {
50059
+ for await (const chunk of child.stdout) {
50060
+ await onStdout(chunk);
50061
+ }
50062
+ } catch (error) {
50063
+ child.kill();
50064
+ await child.exited;
50065
+ throw error;
50066
+ }
50067
+ const exitCode = await child.exited;
50068
+ signal.throwIfAborted();
50069
+ return { exitCode, stdout: "", stderr: await stderr };
50070
+ }
50071
+
50072
+ // packages/daemon/src/vm/storage/count-tree-bytes.ts
50073
+ import { lstat as lstat3, readdir as readdir3 } from "fs/promises";
50074
+ import { join as join31 } from "path";
50075
+ async function countTreeBytes(root, deps = {}) {
50076
+ const readDir = deps.readDir ?? readDirEntries;
50077
+ const inodes = new Map;
50078
+ const dirs = [root];
50079
+ for (let directory = dirs.pop();directory !== undefined; directory = dirs.pop()) {
50080
+ const entries = await readEntries2(readDir, directory);
50081
+ for (const entry of entries) {
50082
+ if (entry.isDirectory()) {
50083
+ dirs.push(join31(directory, entry.name));
50084
+ }
50085
+ }
50086
+ const files = entries.filter((entry) => entry.isFile());
50087
+ const stats = await Promise.all(files.map((entry) => readFileStats(join31(directory, entry.name))));
50088
+ for (const stat of stats) {
50089
+ if (stat !== undefined) {
50090
+ inodes.set(`${String(stat.dev)}:${String(stat.ino)}`, Math.max(0, stat.blocks - 1) * 512);
50091
+ }
50092
+ }
50093
+ }
50094
+ return [...inodes.values()].reduce((sum, bytes) => sum + bytes, 0);
50095
+ }
50096
+ function readDirEntries(directory) {
50097
+ return readdir3(directory, { withFileTypes: true });
50098
+ }
50099
+ async function readEntries2(readDir, directory) {
50100
+ try {
50101
+ return await readDir(directory);
50102
+ } catch (error) {
50103
+ if (isMissing(error)) {
50104
+ return [];
50105
+ }
50106
+ throw error;
50107
+ }
50108
+ }
50109
+ function isMissing(error) {
50110
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
50111
+ }
50112
+ async function readFileStats(path) {
50113
+ try {
50114
+ return await lstat3(path);
50115
+ } catch (error) {
50116
+ if (isMissing(error)) {
50117
+ return;
50118
+ }
50119
+ throw error;
50120
+ }
50121
+ }
50122
+
50193
50123
  // packages/daemon/src/vm/storage/disk-usage-cache.ts
50194
50124
  var REFRESH_DELAY_MS = 1e4;
50195
50125
  var CHANGES_USAGE = new Set([
@@ -50301,6 +50231,11 @@ function createImpdStorage(config, deps) {
50301
50231
  storageGate,
50302
50232
  diskBudget,
50303
50233
  readBuilders: () => buildersHolder.builders,
50234
+ runCommand,
50235
+ runStreamed,
50236
+ startTar: startTarProcess,
50237
+ countTreeBytes,
50238
+ startTimer: startRealTimer,
50304
50239
  ...deps.now !== undefined && { clock: { now: deps.now } },
50305
50240
  ...deps.images
50306
50241
  });
@@ -50317,11 +50252,11 @@ function createImpdStorage(config, deps) {
50317
50252
  }
50318
50253
 
50319
50254
  // packages/daemon/src/host-net/net/tailscale-status.ts
50320
- import * as z90 from "zod";
50321
- var IpListSchema = z90.array(z90.string()).nullable().optional();
50322
- var StatusSchema = z90.object({
50323
- BackendState: z90.string(),
50324
- Self: z90.object({ HostName: z90.string(), DNSName: z90.string().optional(), TailscaleIPs: IpListSchema }).optional()
50255
+ import * as z89 from "zod";
50256
+ var IpListSchema = z89.array(z89.string()).nullable().optional();
50257
+ var StatusSchema = z89.object({
50258
+ BackendState: z89.string(),
50259
+ Self: z89.object({ HostName: z89.string(), DNSName: z89.string().optional(), TailscaleIPs: IpListSchema }).optional()
50325
50260
  });
50326
50261
  var UNKNOWN = { state: null, hostname: null, dnsName: null, ip: null, ips: [] };
50327
50262
  var STATUS_TTL_MS = 30000;
@@ -50599,18 +50534,18 @@ import { getOAuthProviderApi } from "@better-auth/oauth-provider";
50599
50534
  import { createAuthEndpoint } from "better-auth/api";
50600
50535
  import { setSessionCookie } from "better-auth/cookies";
50601
50536
  import { generateRandomString } from "better-auth/crypto";
50602
- import * as z91 from "zod";
50537
+ import * as z90 from "zod";
50603
50538
  var CLIENT_SCOPES = [...ScopeSchema.options, "offline_access"];
50604
50539
  var APPROVAL_EMAIL_DOMAIN = "approval.imp.invalid";
50605
- var SignInBodySchema = z91.object({
50606
- oauth_query: z91.string(),
50607
- tokenID: z91.string().min(1),
50608
- tokenName: z91.string().min(1),
50609
- imps: z91.array(z91.string()).nullable()
50540
+ var SignInBodySchema = z90.object({
50541
+ oauth_query: z90.string(),
50542
+ tokenID: z90.string().min(1),
50543
+ tokenName: z90.string().min(1),
50544
+ imps: z90.array(z90.string()).nullable()
50610
50545
  });
50611
- var FixedClientBodySchema = z91.object({
50612
- name: z91.string().min(1),
50613
- redirectURIs: z91.array(z91.string()).min(1)
50546
+ var FixedClientBodySchema = z90.object({
50547
+ name: z90.string().min(1),
50548
+ redirectURIs: z90.array(z90.string()).min(1)
50614
50549
  });
50615
50550
  function createApproverPlugin() {
50616
50551
  return {
@@ -50636,7 +50571,7 @@ function createApproverPlugin() {
50636
50571
  }),
50637
50572
  verifyMCPAccessToken: createAuthEndpoint("/imp/verify-access-token", {
50638
50573
  method: "POST",
50639
- body: z91.object({ token: z91.string() }),
50574
+ body: z90.object({ token: z90.string() }),
50640
50575
  metadata: { SERVER_ONLY: true }
50641
50576
  }, async (ctx) => {
50642
50577
  const provider = ctx.context.getPlugin("oauth-provider");
@@ -50680,7 +50615,7 @@ function createApproverPlugin() {
50680
50615
 
50681
50616
  // packages/daemon/src/identity/oauth/create-oauth-handlers.ts
50682
50617
  import { verifyOAuthQueryParams } from "@better-auth/oauth-provider";
50683
- import * as z92 from "zod";
50618
+ import * as z91 from "zod";
50684
50619
 
50685
50620
  // packages/daemon/src/identity/oauth/build-bad-token-request.ts
50686
50621
  function buildBadTokenRequest() {
@@ -51052,9 +50987,9 @@ var MINUTE_MS2 = 60000;
51052
50987
  var CONSENT_MS = 10 * MINUTE_MS2;
51053
50988
  var MAX_FORM_BYTES = 16 * 1024;
51054
50989
  var METADATA_HEADERS = { "cache-control": "no-store" };
51055
- var PKCEMissSchema = z92.object({
51056
- error: z92.literal("invalid_request"),
51057
- error_description: z92.literal("code verification failed")
50990
+ var PKCEMissSchema = z91.object({
50991
+ error: z91.literal("invalid_request"),
50992
+ error_description: z91.literal("code verification failed")
51058
50993
  });
51059
50994
  function createOAuthHandlers(deps) {
51060
50995
  const host = new URL(deps.origin).host;
@@ -51746,8 +51681,8 @@ function loadOrCreateHostId(dataDir) {
51746
51681
 
51747
51682
  // packages/daemon/src/network/tailnet-names/oauth-credential.ts
51748
51683
  import { readFileSync as readFileSync26, statSync as statSync14 } from "fs";
51749
- import * as z93 from "zod";
51750
- var CredentialSchema = z93.object({ clientId: z93.string().min(1), clientSecret: z93.string().min(1) });
51684
+ import * as z92 from "zod";
51685
+ var CredentialSchema = z92.object({ clientId: z92.string().min(1), clientSecret: z92.string().min(1) });
51751
51686
  function readOAuthCredential(path) {
51752
51687
  const mode = statSync14(path).mode & 511;
51753
51688
  if ((mode & 63) !== 0) {
@@ -51768,17 +51703,17 @@ function readJson(path) {
51768
51703
  }
51769
51704
 
51770
51705
  // packages/daemon/src/host-net/net/tailscale-serve.ts
51771
- import * as z94 from "zod";
51772
- var HandlerSchema = z94.object({ Proxy: z94.string().optional() });
51773
- var WebSchema = z94.object({
51774
- Handlers: z94.record(z94.string(), HandlerSchema).nullable().optional()
51706
+ import * as z93 from "zod";
51707
+ var HandlerSchema = z93.object({ Proxy: z93.string().optional() });
51708
+ var WebSchema = z93.object({
51709
+ Handlers: z93.record(z93.string(), HandlerSchema).nullable().optional()
51775
51710
  });
51776
- var ServiceConfigSchema = z94.object({
51777
- Web: z94.record(z94.string(), WebSchema).nullable().optional()
51711
+ var ServiceConfigSchema = z93.object({
51712
+ Web: z93.record(z93.string(), WebSchema).nullable().optional()
51778
51713
  });
51779
- var ServeStatusSchema = z94.object({
51780
- TCP: z94.record(z94.string(), z94.unknown()).optional(),
51781
- Services: z94.record(z94.string(), ServiceConfigSchema).nullable().optional()
51714
+ var ServeStatusSchema = z93.object({
51715
+ TCP: z93.record(z93.string(), z93.unknown()).optional(),
51716
+ Services: z93.record(z93.string(), ServiceConfigSchema).nullable().optional()
51782
51717
  });
51783
51718
  function parseServeStatus(json) {
51784
51719
  try {
@@ -51829,17 +51764,17 @@ function createServiceServe(run = runChecked) {
51829
51764
  }
51830
51765
 
51831
51766
  // packages/daemon/src/network/tailnet-names/services-api.ts
51832
- import * as z95 from "zod";
51767
+ import * as z94 from "zod";
51833
51768
  var TAILSCALE_API = "https://api.tailscale.com/api/v2";
51834
51769
  var TOKEN_MARGIN_MS = 60000;
51835
- var ServiceSchema2 = z95.object({
51836
- name: z95.string(),
51837
- comment: z95.string().optional(),
51838
- ports: z95.array(z95.string()).readonly().optional(),
51839
- tags: z95.array(z95.string()).readonly().nullable().optional()
51770
+ var ServiceSchema2 = z94.object({
51771
+ name: z94.string(),
51772
+ comment: z94.string().optional(),
51773
+ ports: z94.array(z94.string()).readonly().optional(),
51774
+ tags: z94.array(z94.string()).readonly().nullable().optional()
51840
51775
  }).readonly();
51841
- var ServiceListSchema2 = z95.object({ vipServices: z95.array(ServiceSchema2).nullable().optional() });
51842
- var TokenSchema3 = z95.object({ access_token: z95.string(), expires_in: z95.number().positive() });
51776
+ var ServiceListSchema2 = z94.object({ vipServices: z94.array(ServiceSchema2).nullable().optional() });
51777
+ var TokenSchema3 = z94.object({ access_token: z94.string(), expires_in: z94.number().positive() });
51843
51778
 
51844
51779
  class TailscaleApiError extends Error {
51845
51780
  status;
@@ -51928,7 +51863,7 @@ function createServicesApi(options) {
51928
51863
  }
51929
51864
  };
51930
51865
  }
51931
- var ErrorSchema = z95.object({ message: z95.string() });
51866
+ var ErrorSchema = z94.object({ message: z94.string() });
51932
51867
  function readErrorText(text) {
51933
51868
  try {
51934
51869
  return ErrorSchema.parse(JSON.parse(text)).message;
@@ -52186,7 +52121,6 @@ async function listPublicImpNames(db) {
52186
52121
 
52187
52122
  // packages/daemon/src/network/https/acme/acme-issuer.ts
52188
52123
  import { Agent } from "https";
52189
- import * as acme from "acme-client";
52190
52124
 
52191
52125
  // packages/daemon/src/network/https/acme/renewal-policy.ts
52192
52126
  var FIRST_BACKOFF_MS2 = 15 * 60000;
@@ -52228,10 +52162,12 @@ function findRenewalReason(info, domain, now) {
52228
52162
 
52229
52163
  // packages/daemon/src/network/https/acme/acme-issuer.ts
52230
52164
  function createAcmeIssuer(options) {
52231
- if (options.caPem !== null) {
52232
- acme.axios.defaults.httpsAgent = new Agent({ ca: options.caPem });
52233
- }
52234
- const openClient = async () => {
52165
+ const loading = { promise: null };
52166
+ const loadAcme = () => {
52167
+ loading.promise ??= readAcmeClient(options.caPem);
52168
+ return loading.promise;
52169
+ };
52170
+ const openClient = async (acme) => {
52235
52171
  const stored = options.store.readAccount(options.directoryUrl);
52236
52172
  if (stored !== null) {
52237
52173
  return new acme.Client({
@@ -52261,7 +52197,8 @@ function createAcmeIssuer(options) {
52261
52197
  };
52262
52198
  const runOrder = async (domain) => {
52263
52199
  await checkDirectory2(options.directoryUrl, options.caPem);
52264
- const client = await openClient();
52200
+ const acme = await loadAcme();
52201
+ const client = await openClient(acme);
52265
52202
  const names = listCertificateNames(domain);
52266
52203
  const order = await client.createOrder({
52267
52204
  identifiers: names.map((name) => ({ type: "dns", value: name }))
@@ -52310,24 +52247,12 @@ function createAcmeIssuer(options) {
52310
52247
  }
52311
52248
  };
52312
52249
  }
52313
- function readCertificateInfo(chainPem) {
52314
- const info = acme.crypto.readCertificateInfo(chainPem);
52315
- const names = new Set([info.domains.commonName, ...info.domains.altNames]);
52316
- names.delete("");
52317
- return { notBefore: info.notBefore, notAfter: info.notAfter, names: [...names] };
52318
- }
52319
- async function removeRecords(options, records) {
52320
- for (const record of records) {
52321
- try {
52322
- await options.dns.removeTxt(record);
52323
- } catch (error) {
52324
- const reason = getErrorMessage(error);
52325
- getImpdLogger("https").warning("could not remove the TXT record {fqdn}: {reason}", {
52326
- fqdn: record.fqdn,
52327
- reason
52328
- });
52329
- }
52250
+ async function readAcmeClient(caPem) {
52251
+ const acme = await import("acme-client");
52252
+ if (caPem !== null) {
52253
+ acme.axios.defaults.httpsAgent = new Agent({ ca: caPem });
52330
52254
  }
52255
+ return acme;
52331
52256
  }
52332
52257
  async function checkDirectory2(directoryUrl, caPem) {
52333
52258
  try {
@@ -52344,6 +52269,19 @@ async function checkDirectory2(directoryUrl, caPem) {
52344
52269
  });
52345
52270
  }
52346
52271
  }
52272
+ async function removeRecords(options, records) {
52273
+ for (const record of records) {
52274
+ try {
52275
+ await options.dns.removeTxt(record);
52276
+ } catch (error) {
52277
+ const reason = getErrorMessage(error);
52278
+ getImpdLogger("https").warning("could not remove the TXT record {fqdn}: {reason}", {
52279
+ fqdn: record.fqdn,
52280
+ reason
52281
+ });
52282
+ }
52283
+ }
52284
+ }
52347
52285
  function formatAcmeError(error) {
52348
52286
  const message = getErrorMessage(error);
52349
52287
  if (error instanceof TypeError && message.includes("response.config")) {
@@ -52355,14 +52293,14 @@ function formatAcmeError(error) {
52355
52293
  // packages/daemon/src/network/https/acme/cert-store.ts
52356
52294
  import { existsSync as existsSync22, mkdirSync as mkdirSync24, readFileSync as readFileSync27, rmSync as rmSync28 } from "fs";
52357
52295
  import { join as join35 } from "path";
52358
- import * as z96 from "zod";
52359
- var AttemptStateSchema = z96.object({
52360
- failures: z96.int().nonnegative(),
52361
- lastAttemptAt: z96.number().nullable(),
52362
- lastError: z96.string().nullable()
52296
+ import * as z95 from "zod";
52297
+ var AttemptStateSchema = z95.object({
52298
+ failures: z95.int().nonnegative(),
52299
+ lastAttemptAt: z95.number().nullable(),
52300
+ lastError: z95.string().nullable()
52363
52301
  });
52364
- var AccountSchema = z96.object({ directoryUrl: z96.string(), url: z96.string(), keyPem: z96.string() });
52365
- var LegacyAccountSchema = z96.object({ directoryUrl: z96.string(), url: z96.string() });
52302
+ var AccountSchema = z95.object({ directoryUrl: z95.string(), url: z95.string(), keyPem: z95.string() });
52303
+ var LegacyAccountSchema = z95.object({ directoryUrl: z95.string(), url: z95.string() });
52366
52304
  var NO_ATTEMPTS = { failures: 0, lastAttemptAt: null, lastError: null };
52367
52305
  function createCertStore(dataDir) {
52368
52306
  const directory = join35(dataDir, "tls");
@@ -52500,7 +52438,7 @@ function createChalltestsrvProvider(apiUrl) {
52500
52438
  }
52501
52439
 
52502
52440
  // packages/daemon/src/network/https/dns/cloudflare-provider.ts
52503
- import * as z97 from "zod";
52441
+ import * as z96 from "zod";
52504
52442
 
52505
52443
  // packages/daemon/src/network/https/dns/wait-for-txt.ts
52506
52444
  import { Resolver as Resolver2 } from "dns/promises";
@@ -52562,30 +52500,30 @@ var CLOUDFLARE_API = "https://api.cloudflare.com/client/v4";
52562
52500
  var TXT_TTL_S = 60;
52563
52501
  var A_TTL_S = 300;
52564
52502
  var PAGE_SIZE = 500;
52565
- var ApiErrorSchema = z97.object({ code: z97.number().optional(), message: z97.string() });
52566
- var EnvelopeSchema = z97.object({
52567
- success: z97.boolean(),
52568
- errors: z97.array(ApiErrorSchema).default([]),
52569
- result: z97.unknown().optional(),
52570
- result_info: z97.object({ page: z97.number(), total_pages: z97.number() }).optional()
52503
+ var ApiErrorSchema = z96.object({ code: z96.number().optional(), message: z96.string() });
52504
+ var EnvelopeSchema = z96.object({
52505
+ success: z96.boolean(),
52506
+ errors: z96.array(ApiErrorSchema).default([]),
52507
+ result: z96.unknown().optional(),
52508
+ result_info: z96.object({ page: z96.number(), total_pages: z96.number() }).optional()
52571
52509
  });
52572
- var ZoneSchema = z97.object({
52573
- id: z97.string(),
52574
- name: z97.string(),
52575
- name_servers: z97.array(z97.string())
52510
+ var ZoneSchema = z96.object({
52511
+ id: z96.string(),
52512
+ name: z96.string(),
52513
+ name_servers: z96.array(z96.string())
52576
52514
  });
52577
- var RecordSchema = z97.object({
52578
- id: z97.string(),
52579
- name: z97.string().optional(),
52580
- type: z97.string().optional(),
52581
- content: z97.string(),
52582
- proxied: z97.boolean().optional(),
52583
- comment: z97.string().nullable().optional()
52515
+ var RecordSchema = z96.object({
52516
+ id: z96.string(),
52517
+ name: z96.string().optional(),
52518
+ type: z96.string().optional(),
52519
+ content: z96.string(),
52520
+ proxied: z96.boolean().optional(),
52521
+ comment: z96.string().nullable().optional()
52584
52522
  });
52585
52523
  var MANAGED_COMMENT = "managed by impd";
52586
52524
  var ADDRESS_TYPES = new Set(["A", "AAAA", "CNAME"]);
52587
- var ZoneListSchema = z97.array(ZoneSchema);
52588
- var RecordListSchema = z97.array(RecordSchema);
52525
+ var ZoneListSchema = z96.array(ZoneSchema);
52526
+ var RecordListSchema = z96.array(RecordSchema);
52589
52527
  function createCloudflareProvider(options) {
52590
52528
  const base = options.apiUrl ?? CLOUDFLARE_API;
52591
52529
  const send = options.fetch ?? fetch;
@@ -52751,6 +52689,20 @@ function createDnsProvider(config, token) {
52751
52689
  });
52752
52690
  }
52753
52691
 
52692
+ // packages/daemon/src/network/https/acme/read-certificate-info.ts
52693
+ import"reflect-metadata";
52694
+ import * as x5092 from "@peculiar/x509";
52695
+ function readCertificateInfo(chainPem) {
52696
+ const certificate = new x5092.X509Certificate(x5092.PemConverter.decodeFirst(chainPem));
52697
+ const altNames = certificate.getExtension(x5092.SubjectAlternativeNameExtension);
52698
+ const names = new Set([
52699
+ ...certificate.subjectName.getField("CN"),
52700
+ ...altNames?.names.items.map((name) => name.value) ?? []
52701
+ ]);
52702
+ names.delete("");
52703
+ return { notBefore: certificate.notBefore, notAfter: certificate.notAfter, names: [...names] };
52704
+ }
52705
+
52754
52706
  // packages/daemon/src/network/https/acme/cert-manager.ts
52755
52707
  function createCertManager(options) {
52756
52708
  const domain = options.domain;
@@ -55104,8 +55056,8 @@ import {
55104
55056
  statSync as statSync15
55105
55057
  } from "fs";
55106
55058
  import { basename as basename5, dirname as dirname11, join as join37 } from "path";
55107
- import * as z98 from "zod";
55108
- var CopiesSchema = z98.record(z98.string(), z98.object({ ino: z98.string(), ctimeNs: z98.string(), size: z98.string() }));
55059
+ import * as z97 from "zod";
55060
+ var CopiesSchema = z97.record(z97.string(), z97.object({ ino: z97.string(), ctimeNs: z97.string(), size: z97.string() }));
55109
55061
  function createXfsBackups(ctx) {
55110
55062
  const backup = buildBackupPaths(ctx.dataDir);
55111
55063
  const freshCopies = new Set;