@impz/testing 5.0.0 → 6.0.1

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 +8 -17
  2. package/dist/index.js +670 -527
  3. package/package.json +45 -23
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.1",
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",
@@ -26653,6 +26652,12 @@ import { FFIType, dlopen, read } from "bun:ffi";
26653
26652
  var CORE_SYMBOLS = {
26654
26653
  ioctl: { args: [FFIType.i32, FFIType.u64, FFIType.ptr], returns: FFIType.i32 },
26655
26654
  lseek: { args: [FFIType.i32, FFIType.i64, FFIType.i32], returns: FFIType.i64 },
26655
+ mmap: {
26656
+ args: [FFIType.u64, FFIType.u64, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.i64],
26657
+ returns: FFIType.i64
26658
+ },
26659
+ munmap: { args: [FFIType.u64, FFIType.u64], returns: FFIType.i32 },
26660
+ mlock: { args: [FFIType.u64, FFIType.u64], returns: FFIType.i32 },
26656
26661
  __errno_location: { args: [], returns: FFIType.ptr }
26657
26662
  };
26658
26663
  var PIDFD_SYMBOLS = {
@@ -31995,11 +32000,7 @@ function createImpServer(options, caller, calls) {
31995
32000
  const printError = options.printError ?? console.error;
31996
32001
  const server = new McpServer({ name: "imp", title: "imp", version: options.version }, {
31997
32002
  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(" "),
32003
+ instructions: buildInstructions(caller),
32003
32004
  supportedProtocolVersions: [...PROTOCOL_VERSIONS, ...MODERN_PROTOCOL_VERSIONS]
32004
32005
  });
32005
32006
  server.server.fallbackRequestHandler = (request) => Promise.reject(new ProtocolError(ProtocolErrorCode.MethodNotFound, `unknown method: ${request.method}`));
@@ -32055,6 +32056,20 @@ function createImpServer(options, caller, calls) {
32055
32056
  }
32056
32057
  return server;
32057
32058
  }
32059
+ var SCOPE_GUIDES = {
32060
+ 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.",
32061
+ 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.",
32062
+ 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."
32063
+ };
32064
+ function buildInstructions(caller) {
32065
+ const names = TOOLS.filter((tool) => hasScope(caller.scope, tool.scope)).map((tool) => tool.definition.name);
32066
+ return [
32067
+ 'imp runs persistent Linux microVMs ("imps") that sleep when idle and wake on use.',
32068
+ `This server may touch ${caller.guard.summary}.`,
32069
+ SCOPE_GUIDES[caller.scope],
32070
+ `Its tools: ${names.join(", ")}.`
32071
+ ].join(" ");
32072
+ }
32058
32073
  function startProgress(ctx, options) {
32059
32074
  const token = ctx.mcpReq._meta?.progressToken;
32060
32075
  if (token === undefined) {
@@ -33440,7 +33455,7 @@ input=$target.in
33440
33455
  next=$target.tmp
33441
33456
 
33442
33457
  mkdir -p "$(dirname "$target")"
33443
- cat >"$input"
33458
+ cat > "$input"
33444
33459
 
33445
33460
  # the first line that is the marker, matched whole and as a fixed string
33446
33461
  split=$(grep -n -x -F -e "$marker" "$input" | head -n 1 | cut -d : -f 1)
@@ -33467,7 +33482,7 @@ done
33467
33482
  fi
33468
33483
  echo
33469
33484
  head -n "$((split - 1))" "$input"
33470
- } >"$next"
33485
+ } > "$next"
33471
33486
 
33472
33487
  rm -f "$input"
33473
33488
 
@@ -37239,8 +37254,9 @@ function createRamGovernor(deps) {
37239
37254
  const listed = await deps.listAwake();
37240
37255
  const awake = listed.filter((imp) => imp.id !== excludeId);
37241
37256
  const byImp = new Map;
37242
- let usedMib = 0;
37243
- let effectiveMib = 0;
37257
+ const lockedMib = deps.readLockedMib?.() ?? 0;
37258
+ let usedMib = lockedMib;
37259
+ let effectiveMib = lockedMib;
37244
37260
  const readings = await Promise.all(awake.map((imp) => readVmMib(imp.pid, imp.apiSocket)));
37245
37261
  for (const [index, imp] of awake.entries()) {
37246
37262
  const measured = readings[index] ?? 0;
@@ -38938,6 +38954,89 @@ async function setupVm(api, config) {
38938
38954
  await api.putBalloon(VM_DEVICES.balloon);
38939
38955
  }
38940
38956
 
38957
+ // packages/daemon/src/vm/templates/template-locks.ts
38958
+ import { match as match6 } from "ts-pattern";
38959
+
38960
+ // packages/daemon/src/vm/templates/can-admit-lock.ts
38961
+ var MAX_LOCKED_PERCENT = 10;
38962
+ var MIB3 = 1024 * 1024;
38963
+ function canAdmitLock(admission) {
38964
+ const maxLockedBytes = Math.floor(admission.ramBudgetMib * MAX_LOCKED_PERCENT / 100) * MIB3;
38965
+ return admission.heldBytes + admission.lockBytes <= maxLockedBytes;
38966
+ }
38967
+
38968
+ // packages/daemon/src/vm/templates/pick-lock-outcome.ts
38969
+ import { P as P3, match as match5 } from "ts-pattern";
38970
+ var MIB4 = 1024 * 1024;
38971
+ function pickLockOutcome(result, isCurrent) {
38972
+ return match5(result).with({ errno: P3.number }, (failed) => ({ kind: "failed", errno: failed.errno })).with({ declinedBytes: P3.number }, (declined) => ({
38973
+ kind: "declined",
38974
+ lockMib: Math.ceil(declined.declinedBytes / MIB4)
38975
+ })).with({ lock: P3.select() }, (lock) => isCurrent ? { kind: "held", lock, lockMib: Math.ceil(lock.lockedBytes / MIB4) } : { kind: "stale", lock }).exhaustive();
38976
+ }
38977
+
38978
+ // packages/daemon/src/vm/templates/template-locks.ts
38979
+ var MIB5 = 1024 * 1024;
38980
+ function createTemplateLocks(deps) {
38981
+ const locks = new Map;
38982
+ const locking = createSingleFlight();
38983
+ const sizedBytes = new Map;
38984
+ const state = { isStopping: false };
38985
+ const readHeldBytes = () => [...locks.values()].reduce((total, held) => total + held.lockedBytes, 0) + [...sizedBytes.values()].reduce((total, bytes) => total + bytes, 0);
38986
+ const runLock = async (target) => {
38987
+ const key = target.key.slice(0, 12);
38988
+ try {
38989
+ const result = await deps.lockMemory(target.memFile, (lockBytes) => {
38990
+ const isAdmitted = canAdmitLock({
38991
+ heldBytes: readHeldBytes(),
38992
+ lockBytes,
38993
+ ramBudgetMib: deps.ramBudgetMib
38994
+ });
38995
+ if (isAdmitted) {
38996
+ sizedBytes.set(target.buildId, lockBytes);
38997
+ }
38998
+ return isAdmitted;
38999
+ });
39000
+ match6(pickLockOutcome(result, deps.probeCurrent(target))).with({ kind: "failed" }, (failed) => {
39001
+ deps.logger.warning("boot template {key} memory not locked (errno {errno}): a restore reads what the host dropped", { key, errno: String(failed.errno) });
39002
+ }).with({ kind: "declined" }, (declined) => {
39003
+ deps.logger.info("boot template {key} memory not locked: {lockMib} MiB would take the locks past a tenth of the RAM budget", { key, lockMib: String(declined.lockMib) });
39004
+ }).with({ kind: "stale" }, (stale) => {
39005
+ stale.lock.release();
39006
+ }).with({ kind: "held" }, (held) => {
39007
+ locks.set(target.key, held.lock);
39008
+ deps.logger.info("boot template {key} memory locked: {lockMib} MiB", {
39009
+ key,
39010
+ lockMib: String(held.lockMib)
39011
+ });
39012
+ }).exhaustive();
39013
+ } catch (error) {
39014
+ deps.logger.warning("boot template {key} memory not locked: {error}", {
39015
+ key,
39016
+ error: getErrorMessage(error)
39017
+ });
39018
+ } finally {
39019
+ sizedBytes.delete(target.buildId);
39020
+ }
39021
+ };
39022
+ return {
39023
+ lock: (target) => state.isStopping || locks.has(target.key) ? Promise.resolve() : locking.run(target.buildId, () => runLock(target)),
39024
+ release: (key) => {
39025
+ locks.get(key)?.release();
39026
+ locks.delete(key);
39027
+ },
39028
+ readLockedMib: () => Math.ceil(readHeldBytes() / MIB5),
39029
+ stop: async () => {
39030
+ state.isStopping = true;
39031
+ await locking.waitForAll();
39032
+ for (const held of locks.values()) {
39033
+ held.release();
39034
+ }
39035
+ locks.clear();
39036
+ }
39037
+ };
39038
+ }
39039
+
38941
39040
  // packages/daemon/src/vm/templates/boot-templates.ts
38942
39041
  var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
38943
39042
  var TEMPLATE_ADDRESS = {
@@ -38956,7 +39055,7 @@ var BUILD_JAIL_ID = "tpl-build";
38956
39055
  var BUILD_CPU = { limit: null, weight: 100 };
38957
39056
  var PLACEHOLDER_BYTES = 1024 * 1024;
38958
39057
  var PLACEHOLDER_NAME = "placeholder.ext4";
38959
- var MIB3 = 1024 * 1024;
39058
+ var MIB6 = 1024 * 1024;
38960
39059
  var MIN_MISSES = 2;
38961
39060
  var MAX_TEMPLATES = 4;
38962
39061
  var MAX_BUILD_FAILURES = 3;
@@ -39030,6 +39129,7 @@ function createBootTemplates(deps) {
39030
39129
  };
39031
39130
  const listTemplateKeys = () => existsSync9(root) ? readdirSync3(root).filter((name) => name !== PLACEHOLDER_NAME && !name.startsWith(".")) : [];
39032
39131
  const removeDir = (name) => {
39132
+ templateLocks.release(name);
39033
39133
  const gone = join18(root, `.gone-${Bun.randomUUIDv7()}`);
39034
39134
  try {
39035
39135
  renameSync3(join18(root, name), gone);
@@ -39039,6 +39139,12 @@ function createBootTemplates(deps) {
39039
39139
  rmSync15(gone, { recursive: true, force: true });
39040
39140
  lastUsed.delete(name);
39041
39141
  };
39142
+ const templateLocks = createTemplateLocks({
39143
+ lockMemory: deps.lockMemory,
39144
+ probeCurrent: (target) => readMeta(target.key)?.buildId === target.buildId,
39145
+ ramBudgetMib: deps.ramBudgetMib,
39146
+ logger: getImpdLogger("boot")
39147
+ });
39042
39148
  const readLastUsed = (key) => {
39043
39149
  const used = lastUsed.get(key);
39044
39150
  if (used !== undefined) {
@@ -39129,7 +39235,7 @@ function createBootTemplates(deps) {
39129
39235
  key: key.slice(0, 12),
39130
39236
  durationMs: String(durationMs)
39131
39237
  });
39132
- return {
39238
+ const files = {
39133
39239
  key,
39134
39240
  buildId,
39135
39241
  vmstate: join18(root, key, "vmstate"),
@@ -39137,6 +39243,8 @@ function createBootTemplates(deps) {
39137
39243
  systemDrivePath: meta.systemDrivePath,
39138
39244
  placeholderPath: placeholder
39139
39245
  };
39246
+ templateLocks.lock(files);
39247
+ return files;
39140
39248
  };
39141
39249
  const runBuild = async (shape, key) => {
39142
39250
  const work = join18(root, `.build-${Bun.randomUUIDv7()}`);
@@ -39155,7 +39263,7 @@ function createBootTemplates(deps) {
39155
39263
  memoryMib: shape.memoryMib,
39156
39264
  maySleepImps: false
39157
39265
  });
39158
- return await (deps.diskBudget === undefined ? writeAdmitted() : deps.diskBudget.withRoom(shape.memoryMib * MIB3, writeAdmitted));
39266
+ return await (deps.diskBudget === undefined ? writeAdmitted() : deps.diskBudget.withRoom(shape.memoryMib * MIB6, writeAdmitted));
39159
39267
  } catch (error) {
39160
39268
  if (progress.isBuilding) {
39161
39269
  throw error;
@@ -39270,10 +39378,98 @@ function createBootTemplates(deps) {
39270
39378
  const meta = readMeta(key);
39271
39379
  return meta === null ? [] : [meta.systemDrivePath];
39272
39380
  }),
39273
- stop: () => building.waitForAll()
39381
+ lockKept: async () => {
39382
+ for (const key of listTemplateKeys()) {
39383
+ const ready = readReady(key);
39384
+ if (ready !== null) {
39385
+ await templateLocks.lock(ready);
39386
+ }
39387
+ }
39388
+ },
39389
+ readLockedMib: templateLocks.readLockedMib,
39390
+ stop: async () => {
39391
+ await Promise.all([templateLocks.stop(), building.waitForAll()]);
39392
+ }
39274
39393
  };
39275
39394
  }
39276
39395
 
39396
+ // packages/daemon/src/vm/templates/create-memory-lock.ts
39397
+ import { closeSync as closeSync8, fstatSync as fstatSync3, openSync as openSync6 } from "fs";
39398
+ var PROT_READ = 1;
39399
+ var MAP_SHARED = 1;
39400
+ async function createMemoryLock(path, onSized) {
39401
+ const libc = readLibc();
39402
+ if (libc === null) {
39403
+ return { errno: -1 };
39404
+ }
39405
+ const fd = openSync6(path, "r");
39406
+ try {
39407
+ const size = fstatSync3(fd).size;
39408
+ const runs = readDataRuns(libc, fd, size);
39409
+ if ("errno" in runs) {
39410
+ return runs;
39411
+ }
39412
+ const lockedBytes = runs.reduce((total, run) => total + run.length, 0);
39413
+ if (!onSized(lockedBytes)) {
39414
+ return { declinedBytes: lockedBytes };
39415
+ }
39416
+ const address = Number(libc.mmap(0, size, PROT_READ, MAP_SHARED, fd, 0));
39417
+ if (address === -1) {
39418
+ return { errno: readErrno() };
39419
+ }
39420
+ const mapping = { isMapped: true };
39421
+ const release = () => {
39422
+ if (mapping.isMapped) {
39423
+ mapping.isMapped = false;
39424
+ libc.munmap(address, size);
39425
+ }
39426
+ };
39427
+ for (const chunk of runs.flatMap((run) => splitRun(run))) {
39428
+ if (libc.mlock(address + chunk.offset, chunk.length) !== 0) {
39429
+ const errno = readErrno();
39430
+ release();
39431
+ return { errno };
39432
+ }
39433
+ await Bun.sleep(0);
39434
+ }
39435
+ return { lock: { lockedBytes, release } };
39436
+ } finally {
39437
+ closeSync8(fd);
39438
+ }
39439
+ }
39440
+ var SEEK_DATA2 = 3;
39441
+ var SEEK_HOLE2 = 4;
39442
+ var ENXIO2 = 6;
39443
+ function readDataRuns(libc, fd, size) {
39444
+ const runs = [];
39445
+ let offset = 0;
39446
+ while (offset < size) {
39447
+ const data = Number(libc.lseek(fd, offset, SEEK_DATA2));
39448
+ if (data < 0) {
39449
+ const errno = readErrno();
39450
+ if (errno === ENXIO2) {
39451
+ break;
39452
+ }
39453
+ return { errno };
39454
+ }
39455
+ const hole = Number(libc.lseek(fd, data, SEEK_HOLE2));
39456
+ if (hole < 0) {
39457
+ return { errno: readErrno() };
39458
+ }
39459
+ const end = Math.min(hole, size);
39460
+ runs.push({ offset: data, length: end - data });
39461
+ offset = end;
39462
+ }
39463
+ return runs;
39464
+ }
39465
+ var CHUNK_BYTES = 4 * 1024 * 1024;
39466
+ function splitRun(run) {
39467
+ return Array.from({ length: Math.ceil(run.length / CHUNK_BYTES) }, (_unused, index) => {
39468
+ const offset = run.offset + index * CHUNK_BYTES;
39469
+ return { offset, length: Math.min(CHUNK_BYTES, run.offset + run.length - offset) };
39470
+ });
39471
+ }
39472
+
39277
39473
  // packages/daemon/src/vm/vmm/vm-stats.ts
39278
39474
  import { readFileSync as readFileSync19, readdirSync as readdirSync4 } from "fs";
39279
39475
  import { readFile as readFile3 } from "fs/promises";
@@ -40846,7 +41042,9 @@ function createDefaultTemplates(deps, cgroups) {
40846
41042
  kernelPath: deps.config.kernelPath,
40847
41043
  minGuestUptimeMs: deps.config.sleepMinGuestUptimeMs,
40848
41044
  bootReservePercent: deps.config.bootReservePercent,
41045
+ ramBudgetMib: deps.config.ramBudgetMib,
40849
41046
  buildVm: deps.vms.createTemplateVm,
41047
+ lockMemory: createMemoryLock,
40850
41048
  taps: deps.taps,
40851
41049
  jail: deps.config.jailerBin === null ? null : { uid: TEMPLATE_BUILD_UID, gid: TEMPLATE_BUILD_UID },
40852
41050
  cgroups,
@@ -42455,12 +42653,12 @@ function readNumber(parsed, key) {
42455
42653
  // packages/daemon/src/vm/vmm/jail.ts
42456
42654
  import {
42457
42655
  chmodSync as chmodSync6,
42458
- closeSync as closeSync8,
42656
+ closeSync as closeSync9,
42459
42657
  existsSync as existsSync12,
42460
42658
  lchownSync as lchownSync2,
42461
42659
  lstatSync as lstatSync3,
42462
42660
  mkdirSync as mkdirSync14,
42463
- openSync as openSync6,
42661
+ openSync as openSync7,
42464
42662
  readdirSync as readdirSync7,
42465
42663
  rmSync as rmSync19,
42466
42664
  statSync as statSync9,
@@ -42549,7 +42747,7 @@ function createJails(deps) {
42549
42747
  };
42550
42748
  const setupReadOnlyBind = async (source, target) => {
42551
42749
  mkdirSync14(dirname9(target), { recursive: true });
42552
- closeSync8(openSync6(target, "a"));
42750
+ closeSync9(openSync7(target, "a"));
42553
42751
  await runMount(["mount", "--bind", source, target]);
42554
42752
  await runMount(["mount", "-o", "remount,bind,ro,nosuid,nodev", target]);
42555
42753
  };
@@ -42696,7 +42894,7 @@ function setupOwnership(paths, user, isDiskLate) {
42696
42894
  }
42697
42895
  function setupScratchFile(source, target, user) {
42698
42896
  mkdirSync14(dirname9(target), { recursive: true });
42699
- closeSync8(openSync6(target, "wx", 384));
42897
+ closeSync9(openSync7(target, "wx", 384));
42700
42898
  truncateSync3(target, statSync9(source).size);
42701
42899
  lchownSync2(target, user.uid, user.gid);
42702
42900
  }
@@ -44311,6 +44509,7 @@ function createImpdImps(config, deps, parts) {
44311
44509
  readRamMib: readRamMibAsync,
44312
44510
  readResidentMib,
44313
44511
  readHeadroomMib,
44512
+ readLockedMib: () => imps.bootTemplates?.readLockedMib() ?? 0,
44314
44513
  isBusy,
44315
44514
  trySleepImp: imps.trySleepImp,
44316
44515
  readDiskFullError: imps.readDiskFullError,
@@ -45787,7 +45986,7 @@ function readSettled2(row, imp) {
45787
45986
 
45788
45987
  // packages/daemon/src/imp-ops/moves/move-sender.ts
45789
45988
  import invariant4 from "tiny-invariant";
45790
- import { match as match5 } from "ts-pattern";
45989
+ import { match as match7 } from "ts-pattern";
45791
45990
 
45792
45991
  // packages/daemon/src/platform/db/move-send-steps.ts
45793
45992
  var MOVE_SEND_STEPS = [
@@ -46349,7 +46548,7 @@ function createMoveSender(deps) {
46349
46548
  };
46350
46549
  const recoverSend = async (imp, row) => {
46351
46550
  const log = getImpdLogger("move").with({ imp: imp.name });
46352
- await match5(pickSendRecovery(imp.moveState, row?.step)).with("unmark", () => removeMark(imp)).with("wait", () => {
46551
+ await match7(pickSendRecovery(imp.moveState, row?.step)).with("unmark", () => removeMark(imp)).with("wait", () => {
46353
46552
  log.info("a prepared send waits for its send after a restart");
46354
46553
  }).with("abort", async () => {
46355
46554
  const isUndone = await resetSend(imp, row?.peer_url ?? null, row?.ticket ?? null);
@@ -46471,7 +46670,7 @@ function buildPublicError(name) {
46471
46670
  }
46472
46671
  function pickSendRecovery(moveState, step) {
46473
46672
  if (moveState === "sending") {
46474
- return match5(step).with(undefined, () => "unmark").with("prepared", () => "wait").with("streaming", () => "abort").with("sent", () => "commit").exhaustive();
46673
+ return match7(step).with(undefined, () => "unmark").with("prepared", () => "wait").with("streaming", () => "abort").with("sent", () => "commit").exhaustive();
46475
46674
  }
46476
46675
  return moveState === "moved" && step === "sent" ? "commit" : "none";
46477
46676
  }
@@ -46523,7 +46722,7 @@ function createMoveService(deps) {
46523
46722
  // packages/daemon/src/surface/moves/create-move-peer-handler.ts
46524
46723
  import { OpenAPIHandler } from "@orpc/openapi/fetch";
46525
46724
  import { ORPCError as ORPCError7, implement as implement3, onError as onError3 } from "@orpc/server";
46526
- import { match as match6 } from "ts-pattern";
46725
+ import { match as match8 } from "ts-pattern";
46527
46726
  function createMovePeerHandler(deps) {
46528
46727
  const withTicket = async (step, context, call) => {
46529
46728
  const startedAt = deps.now();
@@ -46574,7 +46773,7 @@ function createMovePeerHandler(deps) {
46574
46773
  const respondReceive = async (context) => {
46575
46774
  try {
46576
46775
  const received = await withTicket("receive", context, (checked) => deps.calls.receive(checked, context.request));
46577
- return match6(received).with({ kind: "part" }, (sent) => Response.json({ part: sent.part }, { status: 202 })).with({ kind: "receipt" }, (sent) => Response.json(sent.receipt)).exhaustive();
46776
+ return match8(received).with({ kind: "part" }, (sent) => Response.json({ part: sent.part }, { status: 202 })).with({ kind: "receipt" }, (sent) => Response.json(sent.receipt)).exhaustive();
46578
46777
  } catch (error) {
46579
46778
  const failure = toPeerError(error);
46580
46779
  return Response.json(encodePeerError(failure), { status: failure.status });
@@ -47287,9 +47486,9 @@ function printSweep(logger, result, orphans) {
47287
47486
  }
47288
47487
  logger.info("kept {length} orphans the database does not name; `impz gc --orphans --dry-run` lists what `impz gc --orphans` would retire", { length: String(result.kept.length) });
47289
47488
  }
47290
- var MIB4 = 1024 ** 2;
47489
+ var MIB7 = 1024 ** 2;
47291
47490
  function formatOrphan(orphan) {
47292
- const size = `${(orphan.bytes / MIB4).toFixed(1)} MiB`;
47491
+ const size = `${(orphan.bytes / MIB7).toFixed(1)} MiB`;
47293
47492
  const created = orphan.createdAt?.toISOString() ?? "unknown";
47294
47493
  const snapshots = orphan.snapshots.length === 0 ? "none" : orphan.snapshots.join(", ");
47295
47494
  return `orphan ${orphan.kind} ${orphan.id} (${orphan.location}): ${size}, created ${created}, snapshots: ${snapshots}`;
@@ -47420,7 +47619,7 @@ function createImpdServices(config, deps, parts) {
47420
47619
 
47421
47620
  // packages/daemon/src/imps/images/build/create-image-builds.ts
47422
47621
  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";
47622
+ import { join as join28, posix as posix3 } from "path";
47424
47623
 
47425
47624
  // packages/local-tar/src/extract-local.ts
47426
47625
  import tar from "tar-stream";
@@ -47885,6 +48084,9 @@ function formatFamiliarRepository(reference) {
47885
48084
  }
47886
48085
 
47887
48086
  // packages/daemon/src/imps/docker-contract/check-registry.ts
48087
+ function checkReferenceRegistry(reference) {
48088
+ return checkRegistry(parseImageReference(reference).registry);
48089
+ }
47888
48090
  function checkRegistry(registry) {
47889
48091
  const host = readRegistryHost(registry).toLowerCase();
47890
48092
  if (host === "localhost" || host.endsWith(".localhost")) {
@@ -47895,9 +48097,6 @@ function checkRegistry(registry) {
47895
48097
  }
47896
48098
  return null;
47897
48099
  }
47898
- function checkReferenceRegistry(reference) {
47899
- return checkRegistry(parseImageReference(reference).registry);
47900
- }
47901
48100
  function readRegistryHost(registry) {
47902
48101
  if (registry.startsWith("[")) {
47903
48102
  return registry.slice(1, registry.indexOf("]"));
@@ -48761,64 +48960,22 @@ class DockerBuildError extends Error {
48761
48960
  // packages/daemon/src/imps/images/build/guest-build.ts
48762
48961
  import * as z86 from "zod";
48763
48962
 
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;
48963
+ // packages/daemon/src/imps/images/build/image-limit-error.ts
48964
+ class ImageLimitError extends ImpdError {
48965
+ name = "ImageLimitError";
48966
+ constructor(limit, max) {
48967
+ 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)`;
48968
+ super("BAD_REQUEST", { message: `the built image's filesystem is ${over}` });
48812
48969
  }
48813
48970
  }
48814
48971
 
48815
48972
  // packages/daemon/src/imps/images/rootfs/rootfs-plan.ts
48816
48973
  var GIB3 = 1024 ** 3;
48817
- var MIB5 = 1024 ** 2;
48974
+ var MIB8 = 1024 ** 2;
48818
48975
  var ROOTFS_MIN_BYTES = 4 * GIB3;
48819
48976
  var ROOTFS_SPARE_BYTES = 2 * GIB3;
48820
48977
  var BYTES_PER_INODE = 16384;
48821
- var ROOTFS_OVERHEAD_MIN_BYTES = 128 * MIB5;
48978
+ var ROOTFS_OVERHEAD_MIN_BYTES = 128 * MIB8;
48822
48979
  var ROOTFS_OVERHEAD_SHARE = 64;
48823
48980
  function planRootfs(tree) {
48824
48981
  const wanted = Math.ceil((tree.bytes * 1.2 + ROOTFS_SPARE_BYTES) / GIB3) * GIB3;
@@ -48844,73 +49001,9 @@ var UNPACK_TAR_ARGS = [
48844
49001
  "-xpf",
48845
49002
  "-"
48846
49003
  ];
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
49004
  function assertUnpacked(result) {
48912
49005
  if (result.exitCode !== 0) {
48913
- throw buildExitError(["docker", "export", "|", "tar"], result);
49006
+ throw buildExitError(["tar"], result);
48914
49007
  }
48915
49008
  const failure = findXattrFailure(result.stderr);
48916
49009
  if (failure?.missingSetfcap === true) {
@@ -48946,6 +49039,122 @@ function readSetfcapWarning(status = readFileSync23("/proc/self/status", "utf8")
48946
49039
  return `no CAP_SETFCAP, so an image add or build fails on an image with a file capability; ${SETFCAP_HELP}`;
48947
49040
  }
48948
49041
 
49042
+ // packages/daemon/src/imps/images/rootfs/write-streamed-tree.ts
49043
+ var EXPORT_IDLE_MS = 120000;
49044
+ var GROW_STEP_BYTES = 256 * 1024 ** 2;
49045
+ var TAR_STOPPED = new Error("tar stopped before the export ended");
49046
+ async function writeStreamedTree(options) {
49047
+ const root = options.root;
49048
+ const limits = options.limits;
49049
+ const grow = options.grow ?? (() => Promise.resolve());
49050
+ const startTimer = options.startTimer;
49051
+ const maxBytes = limits.maxBytes;
49052
+ const tar = options.startTar([...UNPACK_TAR_ARGS, "-vv", "--quoting-style=escape", "-C", root]);
49053
+ const sent = { bytes: 0 };
49054
+ const unpacked = {};
49055
+ const stopExport = new AbortController;
49056
+ const readMax = (limit) => limit === "bytes" ? maxBytes : limits.maxFiles;
49057
+ const listed = { bytes: 0 };
49058
+ const held = { bytes: 0 };
49059
+ const growHold = async () => {
49060
+ const tree = Math.max(sent.bytes, listed.bytes);
49061
+ const needed = 2 * tree + planRootfsOverhead(tree);
49062
+ if (needed > held.bytes) {
49063
+ held.bytes = (Math.floor(needed / GROW_STEP_BYTES) + 1) * GROW_STEP_BYTES;
49064
+ const writtenBytes = await options.countTreeBytes(root);
49065
+ await grow(held.bytes, writtenBytes);
49066
+ }
49067
+ };
49068
+ const onCounted = (bytes) => {
49069
+ listed.bytes = bytes;
49070
+ };
49071
+ const counting = countUnpacked(tar.stdout, limits, onCounted, (limit) => {
49072
+ unpacked.over = limit;
49073
+ const max = readMax(limit);
49074
+ tar.kill();
49075
+ stopExport.abort(new ImageLimitError(limit, max));
49076
+ });
49077
+ const waitForTar = async () => {
49078
+ const code = await tar.exited;
49079
+ if (code !== 0 && unpacked.ended !== true) {
49080
+ stopExport.abort(TAR_STOPPED);
49081
+ }
49082
+ };
49083
+ waitForTar();
49084
+ const assertWithinLimits = () => {
49085
+ const over = unpacked.over;
49086
+ if (over !== undefined) {
49087
+ throw new ImageLimitError(over, readMax(over));
49088
+ }
49089
+ };
49090
+ const stopIdle = () => {
49091
+ stopExport.abort(new Error(`${options.source} sent nothing in ${String(EXPORT_IDLE_MS / 1000)} s`));
49092
+ };
49093
+ const idle = { cancel: startTimer(stopIdle, EXPORT_IDLE_MS) };
49094
+ const stderrText = new Response(tar.stderr).text();
49095
+ try {
49096
+ const exported = await options.runExport(async (chunk) => {
49097
+ idle.cancel();
49098
+ idle.cancel = startTimer(stopIdle, EXPORT_IDLE_MS);
49099
+ sent.bytes += chunk.byteLength;
49100
+ if (sent.bytes > maxBytes) {
49101
+ throw new ImageLimitError("bytes", maxBytes);
49102
+ }
49103
+ assertWithinLimits();
49104
+ await growHold();
49105
+ await tar.stdin.write(chunk);
49106
+ await tar.stdin.flush();
49107
+ }, AbortSignal.any([options.signal, stopExport.signal]));
49108
+ if (exported.exitCode !== 0) {
49109
+ throw new Error(`${options.source}: ${exported.stderr.trim()}`);
49110
+ }
49111
+ unpacked.ended = true;
49112
+ await tar.stdin.end();
49113
+ const [exitCode, stderr] = await Promise.all([tar.exited, stderrText, counting]);
49114
+ assertWithinLimits();
49115
+ assertUnpacked({ exitCode, stdout: "", stderr });
49116
+ await growHold();
49117
+ } catch (error) {
49118
+ tar.kill();
49119
+ assertWithinLimits();
49120
+ if (stopExport.signal.reason === TAR_STOPPED) {
49121
+ const [exitCode, stderr] = await Promise.all([tar.exited, stderrText]);
49122
+ assertUnpacked({ exitCode, stdout: "", stderr });
49123
+ }
49124
+ throw error;
49125
+ } finally {
49126
+ idle.cancel();
49127
+ }
49128
+ }
49129
+ async function countUnpacked(stdout, limits, onCounted, onOver) {
49130
+ const decoder = new TextDecoder;
49131
+ const counted = { lines: 0, bytes: 0, rest: "" };
49132
+ for await (const chunk of stdout) {
49133
+ const lines = `${counted.rest}${decoder.decode(chunk, { stream: true })}`.split(`
49134
+ `);
49135
+ counted.rest = lines.pop() ?? "";
49136
+ for (const line of lines) {
49137
+ counted.lines += 1;
49138
+ counted.bytes += readAllocatedBytes(line);
49139
+ }
49140
+ onCounted(counted.bytes);
49141
+ if (counted.bytes > limits.maxBytes) {
49142
+ onOver("bytes");
49143
+ return;
49144
+ }
49145
+ if (counted.lines > limits.maxFiles) {
49146
+ onOver("files");
49147
+ return;
49148
+ }
49149
+ }
49150
+ }
49151
+ var BLOCK_BYTES = 4096;
49152
+ function readAllocatedBytes(line) {
49153
+ const size = line.trim().split(/\s+/v)[2] ?? "";
49154
+ const bytes = /^\d+$/v.test(size) ? Number(size) : 0;
49155
+ return Math.max(1, Math.ceil(bytes / BLOCK_BYTES)) * BLOCK_BYTES;
49156
+ }
49157
+
48949
49158
  // packages/daemon/src/imps/images/build/create-build-steps.ts
48950
49159
  var STEP_PATTERN = /^\[(?:\S+ )?\d+\/\d+\] /v;
48951
49160
  function createBuildSteps(setStep) {
@@ -49018,15 +49227,6 @@ function createPlainLines(output) {
49018
49227
  };
49019
49228
  }
49020
49229
 
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
49230
  // packages/daemon/src/imps/images/build/guest-build.ts
49031
49231
  var GUEST_BUILD_TAG = "imp-build:latest";
49032
49232
  var LOG_MAX_CHARS = 8000;
@@ -49082,55 +49282,9 @@ async function loadGuestImage(exec, options) {
49082
49282
  var DIGEST_PREFIX = "imp-build-";
49083
49283
  var DIGEST_DOMAIN = `imp build image v1
49084
49284
  `;
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
49285
  async function writeGuestTree(options) {
49127
49286
  const exec = options.exec;
49128
- const root = options.root;
49129
- const limits = options.limits;
49130
49287
  const signal = options.signal;
49131
- const grow = options.grow ?? (() => Promise.resolve());
49132
- const startTimer = options.startTimer ?? startRealTimer;
49133
- const maxBytes = limits.maxBytes;
49134
49288
  const inspected = await exec(["docker", "image", "inspect", "--format", "{{json .Config}}", GUEST_BUILD_TAG], { signal });
49135
49289
  if (inspected.exitCode !== 0) {
49136
49290
  throw new Error(`docker image inspect in the builder: ${inspected.stderr.trim()}`);
@@ -49142,94 +49296,36 @@ async function writeGuestTree(options) {
49142
49296
  throw new Error(`docker create in the builder: ${created.stderr.trim()}`);
49143
49297
  }
49144
49298
  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
49299
  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]),
49300
+ await writeStreamedTree({
49301
+ root: options.root,
49302
+ limits: options.limits,
49303
+ signal,
49304
+ source: "docker export in the builder",
49305
+ grow: options.grow,
49306
+ startTimer: options.startTimer,
49307
+ startTar: options.startTar,
49308
+ countTreeBytes: options.countTreeBytes,
49309
+ runExport: (onStdout, exportSignal) => exec(["docker", "export", containerId], {
49310
+ signal: exportSignal,
49197
49311
  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();
49312
+ await onStdout(chunk);
49206
49313
  hash.update(chunk);
49207
- await tar.stdin.write(chunk);
49208
- await tar.stdin.flush();
49209
49314
  }
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
- }
49315
+ })
49316
+ });
49231
49317
  return { digest: `${DIGEST_PREFIX}${hash.digest("hex")}`, config };
49232
49318
  }
49319
+ function createImageHash(configText) {
49320
+ const config = new TextEncoder().encode(configText);
49321
+ const length = new Uint8Array(8);
49322
+ new DataView(length.buffer).setBigUint64(0, BigInt(config.byteLength));
49323
+ const hash = new Bun.CryptoHasher("sha256");
49324
+ hash.update(DIGEST_DOMAIN);
49325
+ hash.update(length);
49326
+ hash.update(config);
49327
+ return hash;
49328
+ }
49233
49329
 
49234
49330
  // packages/daemon/src/imps/images/build/write-context-tar.ts
49235
49331
  import { open as open9 } from "fs/promises";
@@ -49397,7 +49493,7 @@ function createImageBuilds(deps, writer) {
49397
49493
  });
49398
49494
  }
49399
49495
  const uploadsDir = buildUploadsDir(deps.config.dataDir);
49400
- const tarPath = join29(uploadsDir, `${Bun.randomUUIDv7()}.tar`);
49496
+ const tarPath = join28(uploadsDir, `${Bun.randomUUIDv7()}.tar`);
49401
49497
  mkdirSync20(uploadsDir, { recursive: true, mode: 448 });
49402
49498
  return deps.diskBudget.withRoom(tarBytes, async () => {
49403
49499
  try {
@@ -49516,102 +49612,39 @@ function buildImageRuntimeConfig(ociConfig) {
49516
49612
 
49517
49613
  // packages/daemon/src/imps/images/rootfs/create-image-writer.ts
49518
49614
  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
- }
49615
+ import { join as join30 } from "path";
49569
49616
 
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);
49617
+ // packages/daemon/src/imps/images/rootfs/read-oci-config.ts
49618
+ import * as z88 from "zod";
49619
+ var OciConfigSchema3 = z88.object({
49620
+ digest: z88.string().regex(/^sha256:[a-f0-9]{64}$/v),
49621
+ config: z88.record(z88.string(), z88.unknown()).nullable()
49622
+ });
49623
+ async function readOciConfig(ref, signal, run) {
49624
+ const argv = ["oci-fetch", "config", ref];
49625
+ const result = await run(argv, { signal });
49626
+ signal.throwIfAborted();
49594
49627
  if (result.exitCode !== 0) {
49595
- throw buildExitError(["docker", ...argv], result);
49628
+ throw buildExitError(argv, result);
49596
49629
  }
49597
- return result.stdout;
49630
+ return OciConfigSchema3.parse(JSON.parse(result.stdout));
49598
49631
  }
49599
49632
 
49600
49633
  // packages/daemon/src/imps/images/rootfs/write-image-config.ts
49601
- 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";
49634
+ import { closeSync as closeSync10, constants as constants5, lstatSync as lstatSync5, mkdirSync as mkdirSync21, openSync as openSync8, rmSync as rmSync25, writeSync as writeSync3 } from "fs";
49635
+ import { join as join29 } from "path";
49603
49636
  function writeImageConfig(root, contents) {
49604
- const etc = join30(root, "etc");
49605
- const directory = join30(etc, "imp");
49637
+ const etc = join29(root, "etc");
49638
+ const directory = join29(etc, "imp");
49606
49639
  createDirectory(etc, "/etc");
49607
49640
  createDirectory(directory, "/etc/imp");
49608
- const file = join30(directory, "image.json");
49641
+ const file = join29(directory, "image.json");
49609
49642
  rmSync25(file, { recursive: true, force: true });
49610
- const fd = openSync7(file, constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW, 420);
49643
+ const fd = openSync8(file, constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW, 420);
49611
49644
  try {
49612
49645
  writeSync3(fd, contents);
49613
49646
  } finally {
49614
- closeSync9(fd);
49647
+ closeSync10(fd);
49615
49648
  }
49616
49649
  }
49617
49650
  function createDirectory(path, name) {
@@ -49627,14 +49660,22 @@ function createDirectory(path, name) {
49627
49660
  }
49628
49661
  }
49629
49662
 
49663
+ // packages/daemon/src/imps/images/rootfs/write-oci-tree.ts
49664
+ function writeOciTree(options) {
49665
+ return writeStreamedTree({
49666
+ root: options.root,
49667
+ limits: options.limits,
49668
+ signal: options.signal,
49669
+ source: `oci-fetch export ${options.ref}`,
49670
+ grow: options.grow,
49671
+ startTimer: options.startTimer,
49672
+ startTar: options.startTar,
49673
+ countTreeBytes: options.countTreeBytes,
49674
+ runExport: (onStdout, signal) => options.runStreamed(["oci-fetch", "export", options.ref], onStdout, signal)
49675
+ });
49676
+ }
49677
+
49630
49678
  // 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
49679
  function createImageWriter(deps) {
49639
49680
  const storageGate = deps.storageGate;
49640
49681
  const logger = getImpdLogger("image");
@@ -49642,22 +49683,10 @@ function createImageWriter(deps) {
49642
49683
  const publishers = new Map;
49643
49684
  const freshRootfs = new Set;
49644
49685
  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
49686
  const makeWorkDir = () => {
49658
49687
  const images = buildImagesDir(deps.config.dataDir);
49659
- const work = join31(images, `.build-${Bun.randomUUIDv7()}`);
49660
- const root = join31(work, "root");
49688
+ const work = join30(images, `.build-${Bun.randomUUIDv7()}`);
49689
+ const root = join30(work, "root");
49661
49690
  mkdirSync22(images, { recursive: true });
49662
49691
  mkdirSync22(work, { mode: 448 });
49663
49692
  mkdirSync22(root, { mode: 493 });
@@ -49668,7 +49697,7 @@ function createImageWriter(deps) {
49668
49697
  const usage = await readTreeUsage(root);
49669
49698
  const plan = planRootfs(usage);
49670
49699
  await deps.storage.createImage(digest, async (directory) => {
49671
- const image = join31(directory, "rootfs.ext4");
49700
+ const image = join30(directory, "rootfs.ext4");
49672
49701
  await runChecked(["truncate", "-s", String(plan.bytes), image]);
49673
49702
  await runChecked([
49674
49703
  "mkfs.ext4",
@@ -49681,29 +49710,10 @@ function createImageWriter(deps) {
49681
49710
  root,
49682
49711
  image
49683
49712
  ]);
49684
- writeFileSync8(join31(directory, "config.json"), JSON.stringify(ociConfig ?? {}, null, 2));
49713
+ writeFileSync8(join30(directory, "config.json"), JSON.stringify(ociConfig ?? {}, null, 2));
49685
49714
  });
49686
49715
  return readDiskUsage(buildImagePaths(deps.config.dataDir, digest).rootfs);
49687
49716
  };
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
49717
  const createRootfsOnce = (digest, create) => building.run(digest, create);
49708
49718
  const writeDockerImageRow = async (imageName, ref, digest, sizeBytes) => {
49709
49719
  const at = deps.clock.now();
@@ -49757,22 +49767,6 @@ function createImageWriter(deps) {
49757
49767
  });
49758
49768
  }
49759
49769
  });
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
49770
  const writeGuestRootfsAndRow = async (guest) => {
49777
49771
  const digest = guest.digest;
49778
49772
  const rootfs = buildImagePaths(deps.config.dataDir, digest).rootfs;
@@ -49803,7 +49797,10 @@ function createImageWriter(deps) {
49803
49797
  root,
49804
49798
  limits: { maxBytes, maxFiles: deps.config.build.imageMaxFiles },
49805
49799
  signal,
49806
- grow
49800
+ grow,
49801
+ startTar: deps.startTar,
49802
+ countTreeBytes: deps.countTreeBytes,
49803
+ startTimer: deps.startTimer
49807
49804
  });
49808
49805
  return await writeGuestRootfsAndRow({
49809
49806
  root,
@@ -49818,6 +49815,41 @@ function createImageWriter(deps) {
49818
49815
  }
49819
49816
  }));
49820
49817
  };
49818
+ const createBuilderImage = async (imageName, options) => {
49819
+ const ref = deps.config.build.image;
49820
+ const signal = options.signal;
49821
+ const build = deps.config.build;
49822
+ const limits = { maxBytes: build.imageMaxBytes, maxFiles: build.imageMaxFiles };
49823
+ const image = await readOciConfig(ref, signal, deps.runCommand);
49824
+ return deps.diskBudget.withGrowingRoom((grow) => storageGate.join(async () => {
49825
+ const workDir = makeWorkDir();
49826
+ const root = workDir.root;
49827
+ try {
49828
+ await writeOciTree({
49829
+ ref,
49830
+ root,
49831
+ limits,
49832
+ signal,
49833
+ grow,
49834
+ runStreamed: deps.runStreamed,
49835
+ startTar: deps.startTar,
49836
+ countTreeBytes: deps.countTreeBytes,
49837
+ startTimer: deps.startTimer
49838
+ });
49839
+ options.onFetched();
49840
+ return await writeGuestRootfsAndRow({
49841
+ root,
49842
+ digest: image.digest,
49843
+ config: image.config,
49844
+ imageName,
49845
+ ref,
49846
+ signal: new AbortController().signal
49847
+ });
49848
+ } finally {
49849
+ rmSync26(workDir.work, { recursive: true, force: true });
49850
+ }
49851
+ }));
49852
+ };
49821
49853
  const withPublisher = async (digest, imageName, publish) => {
49822
49854
  await holdPublisher(digest);
49823
49855
  try {
@@ -49826,7 +49858,7 @@ function createImageWriter(deps) {
49826
49858
  await releasePublisher(digest, imageName);
49827
49859
  }
49828
49860
  };
49829
- return { createImageOnHost, writeGuestImage, removeIdleRootfs, removeIdleImage, withPublisher };
49861
+ return { createBuilderImage, writeGuestImage, removeIdleRootfs, removeIdleImage, withPublisher };
49830
49862
  }
49831
49863
  function requireDockerImage(existing) {
49832
49864
  if (existing?.source === "imp") {
@@ -49900,18 +49932,18 @@ function createImageAdds(deps, writer) {
49900
49932
  const createBuilderImage = async (ref) => {
49901
49933
  const pullMs = deps.builderImagePullMs ?? BUILDER_IMAGE_PULL_MS;
49902
49934
  const timeout = deps.startPullLimit?.(pullMs) ?? AbortSignal.timeout(pullMs);
49903
- const step = { pulled: false };
49935
+ const step = { fetched: false };
49904
49936
  try {
49905
- return await writer.createImageOnHost(ref, BUILDER_IMAGE, {
49937
+ return await writer.createBuilderImage(BUILDER_IMAGE, {
49906
49938
  signal: timeout,
49907
- onPulled: () => {
49908
- step.pulled = true;
49939
+ onFetched: () => {
49940
+ step.fetched = true;
49909
49941
  }
49910
49942
  });
49911
49943
  } 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);
49944
+ const reason = timeout.aborted && !step.fetched ? `the fetch did not finish in ${formatPullLimit(pullMs)}` : getErrorMessage(error);
49913
49945
  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}`
49946
+ message: `impd cannot add its builder image ${ref} (IMP_BUILD_IMAGE) from its registry, so no image add or build can run: ${reason}`
49915
49947
  });
49916
49948
  }
49917
49949
  };
@@ -50103,7 +50135,10 @@ function createImageEviction(deps) {
50103
50135
  // packages/daemon/src/imps/images/image-service.ts
50104
50136
  function createImageService(deps) {
50105
50137
  const clock = deps.clock ?? REAL_CLOCK;
50106
- const writer = createImageWriter({ ...deps, clock });
50138
+ const writer = createImageWriter({
50139
+ ...deps,
50140
+ clock
50141
+ });
50107
50142
  const builds = createImageBuilds({ ...deps, clock }, writer);
50108
50143
  const adds = createImageAdds({ ...deps, clock }, writer);
50109
50144
  const eviction = createImageEviction({
@@ -50190,6 +50225,91 @@ function createImageService(deps) {
50190
50225
  };
50191
50226
  }
50192
50227
 
50228
+ // packages/daemon/src/imps/images/rootfs/start-tar-process.ts
50229
+ function startTarProcess(argv) {
50230
+ return Bun.spawn([...argv], {
50231
+ stdin: "pipe",
50232
+ stdout: "pipe",
50233
+ stderr: "pipe",
50234
+ env: { ...process.env, LC_ALL: "C" }
50235
+ });
50236
+ }
50237
+
50238
+ // packages/daemon/src/platform/process/run-streamed.ts
50239
+ async function runStreamed(argv, onStdout, signal) {
50240
+ const child = Bun.spawn([...argv], {
50241
+ stdin: "ignore",
50242
+ stdout: "pipe",
50243
+ stderr: "pipe",
50244
+ env: process.env,
50245
+ signal
50246
+ });
50247
+ const stderr = new Response(child.stderr).text();
50248
+ try {
50249
+ for await (const chunk of child.stdout) {
50250
+ await onStdout(chunk);
50251
+ }
50252
+ } catch (error) {
50253
+ child.kill();
50254
+ await child.exited;
50255
+ throw error;
50256
+ }
50257
+ const exitCode = await child.exited;
50258
+ signal.throwIfAborted();
50259
+ return { exitCode, stdout: "", stderr: await stderr };
50260
+ }
50261
+
50262
+ // packages/daemon/src/vm/storage/count-tree-bytes.ts
50263
+ import { lstat as lstat3, readdir as readdir3 } from "fs/promises";
50264
+ import { join as join31 } from "path";
50265
+ async function countTreeBytes(root, deps = {}) {
50266
+ const readDir = deps.readDir ?? readDirEntries;
50267
+ const inodes = new Map;
50268
+ const dirs = [root];
50269
+ for (let directory = dirs.pop();directory !== undefined; directory = dirs.pop()) {
50270
+ const entries = await readEntries2(readDir, directory);
50271
+ for (const entry of entries) {
50272
+ if (entry.isDirectory()) {
50273
+ dirs.push(join31(directory, entry.name));
50274
+ }
50275
+ }
50276
+ const files = entries.filter((entry) => entry.isFile());
50277
+ const stats = await Promise.all(files.map((entry) => readFileStats(join31(directory, entry.name))));
50278
+ for (const stat of stats) {
50279
+ if (stat !== undefined) {
50280
+ inodes.set(`${String(stat.dev)}:${String(stat.ino)}`, Math.max(0, stat.blocks - 1) * 512);
50281
+ }
50282
+ }
50283
+ }
50284
+ return [...inodes.values()].reduce((sum, bytes) => sum + bytes, 0);
50285
+ }
50286
+ function readDirEntries(directory) {
50287
+ return readdir3(directory, { withFileTypes: true });
50288
+ }
50289
+ async function readEntries2(readDir, directory) {
50290
+ try {
50291
+ return await readDir(directory);
50292
+ } catch (error) {
50293
+ if (isMissing(error)) {
50294
+ return [];
50295
+ }
50296
+ throw error;
50297
+ }
50298
+ }
50299
+ function isMissing(error) {
50300
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
50301
+ }
50302
+ async function readFileStats(path) {
50303
+ try {
50304
+ return await lstat3(path);
50305
+ } catch (error) {
50306
+ if (isMissing(error)) {
50307
+ return;
50308
+ }
50309
+ throw error;
50310
+ }
50311
+ }
50312
+
50193
50313
  // packages/daemon/src/vm/storage/disk-usage-cache.ts
50194
50314
  var REFRESH_DELAY_MS = 1e4;
50195
50315
  var CHANGES_USAGE = new Set([
@@ -50301,6 +50421,11 @@ function createImpdStorage(config, deps) {
50301
50421
  storageGate,
50302
50422
  diskBudget,
50303
50423
  readBuilders: () => buildersHolder.builders,
50424
+ runCommand,
50425
+ runStreamed,
50426
+ startTar: startTarProcess,
50427
+ countTreeBytes,
50428
+ startTimer: startRealTimer,
50304
50429
  ...deps.now !== undefined && { clock: { now: deps.now } },
50305
50430
  ...deps.images
50306
50431
  });
@@ -50317,11 +50442,11 @@ function createImpdStorage(config, deps) {
50317
50442
  }
50318
50443
 
50319
50444
  // 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()
50445
+ import * as z89 from "zod";
50446
+ var IpListSchema = z89.array(z89.string()).nullable().optional();
50447
+ var StatusSchema = z89.object({
50448
+ BackendState: z89.string(),
50449
+ Self: z89.object({ HostName: z89.string(), DNSName: z89.string().optional(), TailscaleIPs: IpListSchema }).optional()
50325
50450
  });
50326
50451
  var UNKNOWN = { state: null, hostname: null, dnsName: null, ip: null, ips: [] };
50327
50452
  var STATUS_TTL_MS = 30000;
@@ -50599,18 +50724,18 @@ import { getOAuthProviderApi } from "@better-auth/oauth-provider";
50599
50724
  import { createAuthEndpoint } from "better-auth/api";
50600
50725
  import { setSessionCookie } from "better-auth/cookies";
50601
50726
  import { generateRandomString } from "better-auth/crypto";
50602
- import * as z91 from "zod";
50727
+ import * as z90 from "zod";
50603
50728
  var CLIENT_SCOPES = [...ScopeSchema.options, "offline_access"];
50604
50729
  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()
50730
+ var SignInBodySchema = z90.object({
50731
+ oauth_query: z90.string(),
50732
+ tokenID: z90.string().min(1),
50733
+ tokenName: z90.string().min(1),
50734
+ imps: z90.array(z90.string()).nullable()
50610
50735
  });
50611
- var FixedClientBodySchema = z91.object({
50612
- name: z91.string().min(1),
50613
- redirectURIs: z91.array(z91.string()).min(1)
50736
+ var FixedClientBodySchema = z90.object({
50737
+ name: z90.string().min(1),
50738
+ redirectURIs: z90.array(z90.string()).min(1)
50614
50739
  });
50615
50740
  function createApproverPlugin() {
50616
50741
  return {
@@ -50636,7 +50761,7 @@ function createApproverPlugin() {
50636
50761
  }),
50637
50762
  verifyMCPAccessToken: createAuthEndpoint("/imp/verify-access-token", {
50638
50763
  method: "POST",
50639
- body: z91.object({ token: z91.string() }),
50764
+ body: z90.object({ token: z90.string() }),
50640
50765
  metadata: { SERVER_ONLY: true }
50641
50766
  }, async (ctx) => {
50642
50767
  const provider = ctx.context.getPlugin("oauth-provider");
@@ -50680,7 +50805,7 @@ function createApproverPlugin() {
50680
50805
 
50681
50806
  // packages/daemon/src/identity/oauth/create-oauth-handlers.ts
50682
50807
  import { verifyOAuthQueryParams } from "@better-auth/oauth-provider";
50683
- import * as z92 from "zod";
50808
+ import * as z91 from "zod";
50684
50809
 
50685
50810
  // packages/daemon/src/identity/oauth/build-bad-token-request.ts
50686
50811
  function buildBadTokenRequest() {
@@ -50908,14 +51033,14 @@ function expandScopes(asked) {
50908
51033
  }
50909
51034
 
50910
51035
  // packages/daemon/src/platform/http/render-html.ts
50911
- import { P as P3, match as match7 } from "ts-pattern";
51036
+ import { P as P4, match as match9 } from "ts-pattern";
50912
51037
  var MARKUP = Symbol("markup");
50913
51038
  function renderHTML(strings, ...parts) {
50914
51039
  const html = parts.reduce((text, part, index) => `${text}${toHTML(part)}${strings[index + 1] ?? ""}`, strings[0] ?? "");
50915
51040
  return { [MARKUP]: true, html };
50916
51041
  }
50917
51042
  function toHTML(part) {
50918
- return match7(part).with(P3.string, (text) => escapeHTML(text)).with(P3.array(), (list) => list.map((each) => each.html).join("")).with({ html: P3.string }, (markup) => markup.html).exhaustive();
51043
+ return match9(part).with(P4.string, (text) => escapeHTML(text)).with(P4.array(), (list) => list.map((each) => each.html).join("")).with({ html: P4.string }, (markup) => markup.html).exhaustive();
50919
51044
  }
50920
51045
  function escapeHTML(text) {
50921
51046
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
@@ -51052,9 +51177,9 @@ var MINUTE_MS2 = 60000;
51052
51177
  var CONSENT_MS = 10 * MINUTE_MS2;
51053
51178
  var MAX_FORM_BYTES = 16 * 1024;
51054
51179
  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")
51180
+ var PKCEMissSchema = z91.object({
51181
+ error: z91.literal("invalid_request"),
51182
+ error_description: z91.literal("code verification failed")
51058
51183
  });
51059
51184
  function createOAuthHandlers(deps) {
51060
51185
  const host = new URL(deps.origin).host;
@@ -51746,8 +51871,8 @@ function loadOrCreateHostId(dataDir) {
51746
51871
 
51747
51872
  // packages/daemon/src/network/tailnet-names/oauth-credential.ts
51748
51873
  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) });
51874
+ import * as z92 from "zod";
51875
+ var CredentialSchema = z92.object({ clientId: z92.string().min(1), clientSecret: z92.string().min(1) });
51751
51876
  function readOAuthCredential(path) {
51752
51877
  const mode = statSync14(path).mode & 511;
51753
51878
  if ((mode & 63) !== 0) {
@@ -51768,17 +51893,17 @@ function readJson(path) {
51768
51893
  }
51769
51894
 
51770
51895
  // 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()
51896
+ import * as z93 from "zod";
51897
+ var HandlerSchema = z93.object({ Proxy: z93.string().optional() });
51898
+ var WebSchema = z93.object({
51899
+ Handlers: z93.record(z93.string(), HandlerSchema).nullable().optional()
51775
51900
  });
51776
- var ServiceConfigSchema = z94.object({
51777
- Web: z94.record(z94.string(), WebSchema).nullable().optional()
51901
+ var ServiceConfigSchema = z93.object({
51902
+ Web: z93.record(z93.string(), WebSchema).nullable().optional()
51778
51903
  });
51779
- var ServeStatusSchema = z94.object({
51780
- TCP: z94.record(z94.string(), z94.unknown()).optional(),
51781
- Services: z94.record(z94.string(), ServiceConfigSchema).nullable().optional()
51904
+ var ServeStatusSchema = z93.object({
51905
+ TCP: z93.record(z93.string(), z93.unknown()).optional(),
51906
+ Services: z93.record(z93.string(), ServiceConfigSchema).nullable().optional()
51782
51907
  });
51783
51908
  function parseServeStatus(json) {
51784
51909
  try {
@@ -51829,17 +51954,17 @@ function createServiceServe(run = runChecked) {
51829
51954
  }
51830
51955
 
51831
51956
  // packages/daemon/src/network/tailnet-names/services-api.ts
51832
- import * as z95 from "zod";
51957
+ import * as z94 from "zod";
51833
51958
  var TAILSCALE_API = "https://api.tailscale.com/api/v2";
51834
51959
  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()
51960
+ var ServiceSchema2 = z94.object({
51961
+ name: z94.string(),
51962
+ comment: z94.string().optional(),
51963
+ ports: z94.array(z94.string()).readonly().optional(),
51964
+ tags: z94.array(z94.string()).readonly().nullable().optional()
51840
51965
  }).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() });
51966
+ var ServiceListSchema2 = z94.object({ vipServices: z94.array(ServiceSchema2).nullable().optional() });
51967
+ var TokenSchema3 = z94.object({ access_token: z94.string(), expires_in: z94.number().positive() });
51843
51968
 
51844
51969
  class TailscaleApiError extends Error {
51845
51970
  status;
@@ -51928,7 +52053,7 @@ function createServicesApi(options) {
51928
52053
  }
51929
52054
  };
51930
52055
  }
51931
- var ErrorSchema = z95.object({ message: z95.string() });
52056
+ var ErrorSchema = z94.object({ message: z94.string() });
51932
52057
  function readErrorText(text) {
51933
52058
  try {
51934
52059
  return ErrorSchema.parse(JSON.parse(text)).message;
@@ -52186,7 +52311,6 @@ async function listPublicImpNames(db) {
52186
52311
 
52187
52312
  // packages/daemon/src/network/https/acme/acme-issuer.ts
52188
52313
  import { Agent } from "https";
52189
- import * as acme from "acme-client";
52190
52314
 
52191
52315
  // packages/daemon/src/network/https/acme/renewal-policy.ts
52192
52316
  var FIRST_BACKOFF_MS2 = 15 * 60000;
@@ -52228,10 +52352,12 @@ function findRenewalReason(info, domain, now) {
52228
52352
 
52229
52353
  // packages/daemon/src/network/https/acme/acme-issuer.ts
52230
52354
  function createAcmeIssuer(options) {
52231
- if (options.caPem !== null) {
52232
- acme.axios.defaults.httpsAgent = new Agent({ ca: options.caPem });
52233
- }
52234
- const openClient = async () => {
52355
+ const loading = { promise: null };
52356
+ const loadAcme = () => {
52357
+ loading.promise ??= readAcmeClient(options.caPem);
52358
+ return loading.promise;
52359
+ };
52360
+ const openClient = async (acme) => {
52235
52361
  const stored = options.store.readAccount(options.directoryUrl);
52236
52362
  if (stored !== null) {
52237
52363
  return new acme.Client({
@@ -52261,7 +52387,8 @@ function createAcmeIssuer(options) {
52261
52387
  };
52262
52388
  const runOrder = async (domain) => {
52263
52389
  await checkDirectory2(options.directoryUrl, options.caPem);
52264
- const client = await openClient();
52390
+ const acme = await loadAcme();
52391
+ const client = await openClient(acme);
52265
52392
  const names = listCertificateNames(domain);
52266
52393
  const order = await client.createOrder({
52267
52394
  identifiers: names.map((name) => ({ type: "dns", value: name }))
@@ -52310,24 +52437,12 @@ function createAcmeIssuer(options) {
52310
52437
  }
52311
52438
  };
52312
52439
  }
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
- }
52440
+ async function readAcmeClient(caPem) {
52441
+ const acme = await import("acme-client");
52442
+ if (caPem !== null) {
52443
+ acme.axios.defaults.httpsAgent = new Agent({ ca: caPem });
52330
52444
  }
52445
+ return acme;
52331
52446
  }
52332
52447
  async function checkDirectory2(directoryUrl, caPem) {
52333
52448
  try {
@@ -52344,6 +52459,19 @@ async function checkDirectory2(directoryUrl, caPem) {
52344
52459
  });
52345
52460
  }
52346
52461
  }
52462
+ async function removeRecords(options, records) {
52463
+ for (const record of records) {
52464
+ try {
52465
+ await options.dns.removeTxt(record);
52466
+ } catch (error) {
52467
+ const reason = getErrorMessage(error);
52468
+ getImpdLogger("https").warning("could not remove the TXT record {fqdn}: {reason}", {
52469
+ fqdn: record.fqdn,
52470
+ reason
52471
+ });
52472
+ }
52473
+ }
52474
+ }
52347
52475
  function formatAcmeError(error) {
52348
52476
  const message = getErrorMessage(error);
52349
52477
  if (error instanceof TypeError && message.includes("response.config")) {
@@ -52355,14 +52483,14 @@ function formatAcmeError(error) {
52355
52483
  // packages/daemon/src/network/https/acme/cert-store.ts
52356
52484
  import { existsSync as existsSync22, mkdirSync as mkdirSync24, readFileSync as readFileSync27, rmSync as rmSync28 } from "fs";
52357
52485
  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()
52486
+ import * as z95 from "zod";
52487
+ var AttemptStateSchema = z95.object({
52488
+ failures: z95.int().nonnegative(),
52489
+ lastAttemptAt: z95.number().nullable(),
52490
+ lastError: z95.string().nullable()
52363
52491
  });
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() });
52492
+ var AccountSchema = z95.object({ directoryUrl: z95.string(), url: z95.string(), keyPem: z95.string() });
52493
+ var LegacyAccountSchema = z95.object({ directoryUrl: z95.string(), url: z95.string() });
52366
52494
  var NO_ATTEMPTS = { failures: 0, lastAttemptAt: null, lastError: null };
52367
52495
  function createCertStore(dataDir) {
52368
52496
  const directory = join35(dataDir, "tls");
@@ -52500,7 +52628,7 @@ function createChalltestsrvProvider(apiUrl) {
52500
52628
  }
52501
52629
 
52502
52630
  // packages/daemon/src/network/https/dns/cloudflare-provider.ts
52503
- import * as z97 from "zod";
52631
+ import * as z96 from "zod";
52504
52632
 
52505
52633
  // packages/daemon/src/network/https/dns/wait-for-txt.ts
52506
52634
  import { Resolver as Resolver2 } from "dns/promises";
@@ -52562,30 +52690,30 @@ var CLOUDFLARE_API = "https://api.cloudflare.com/client/v4";
52562
52690
  var TXT_TTL_S = 60;
52563
52691
  var A_TTL_S = 300;
52564
52692
  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()
52693
+ var ApiErrorSchema = z96.object({ code: z96.number().optional(), message: z96.string() });
52694
+ var EnvelopeSchema = z96.object({
52695
+ success: z96.boolean(),
52696
+ errors: z96.array(ApiErrorSchema).default([]),
52697
+ result: z96.unknown().optional(),
52698
+ result_info: z96.object({ page: z96.number(), total_pages: z96.number() }).optional()
52571
52699
  });
52572
- var ZoneSchema = z97.object({
52573
- id: z97.string(),
52574
- name: z97.string(),
52575
- name_servers: z97.array(z97.string())
52700
+ var ZoneSchema = z96.object({
52701
+ id: z96.string(),
52702
+ name: z96.string(),
52703
+ name_servers: z96.array(z96.string())
52576
52704
  });
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()
52705
+ var RecordSchema = z96.object({
52706
+ id: z96.string(),
52707
+ name: z96.string().optional(),
52708
+ type: z96.string().optional(),
52709
+ content: z96.string(),
52710
+ proxied: z96.boolean().optional(),
52711
+ comment: z96.string().nullable().optional()
52584
52712
  });
52585
52713
  var MANAGED_COMMENT = "managed by impd";
52586
52714
  var ADDRESS_TYPES = new Set(["A", "AAAA", "CNAME"]);
52587
- var ZoneListSchema = z97.array(ZoneSchema);
52588
- var RecordListSchema = z97.array(RecordSchema);
52715
+ var ZoneListSchema = z96.array(ZoneSchema);
52716
+ var RecordListSchema = z96.array(RecordSchema);
52589
52717
  function createCloudflareProvider(options) {
52590
52718
  const base = options.apiUrl ?? CLOUDFLARE_API;
52591
52719
  const send = options.fetch ?? fetch;
@@ -52751,6 +52879,20 @@ function createDnsProvider(config, token) {
52751
52879
  });
52752
52880
  }
52753
52881
 
52882
+ // packages/daemon/src/network/https/acme/read-certificate-info.ts
52883
+ import"reflect-metadata";
52884
+ import * as x5092 from "@peculiar/x509";
52885
+ function readCertificateInfo(chainPem) {
52886
+ const certificate = new x5092.X509Certificate(x5092.PemConverter.decodeFirst(chainPem));
52887
+ const altNames = certificate.getExtension(x5092.SubjectAlternativeNameExtension);
52888
+ const names = new Set([
52889
+ ...certificate.subjectName.getField("CN"),
52890
+ ...altNames?.names.items.map((name) => name.value) ?? []
52891
+ ]);
52892
+ names.delete("");
52893
+ return { notBefore: certificate.notBefore, notAfter: certificate.notAfter, names: [...names] };
52894
+ }
52895
+
52754
52896
  // packages/daemon/src/network/https/acme/cert-manager.ts
52755
52897
  function createCertManager(options) {
52756
52898
  const domain = options.domain;
@@ -55104,8 +55246,8 @@ import {
55104
55246
  statSync as statSync15
55105
55247
  } from "fs";
55106
55248
  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() }));
55249
+ import * as z97 from "zod";
55250
+ var CopiesSchema = z97.record(z97.string(), z97.object({ ino: z97.string(), ctimeNs: z97.string(), size: z97.string() }));
55109
55251
  function createXfsBackups(ctx) {
55110
55252
  const backup = buildBackupPaths(ctx.dataDir);
55111
55253
  const freshCopies = new Set;
@@ -57082,6 +57224,7 @@ async function startImpdParts(config, boundaries, plan) {
57082
57224
  key: key.slice(0, 12)
57083
57225
  });
57084
57226
  }
57227
+ imps.bootTemplates?.lockKept();
57085
57228
  });
57086
57229
  await runStep("system-drives", async () => {
57087
57230
  const removed = await removeUnusedDrives(db, {