@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.
- package/README.md +8 -17
- package/dist/index.js +670 -527
- 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: "
|
|
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
|
-
|
|
37243
|
-
let
|
|
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
|
|
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
|
-
|
|
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 *
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
47489
|
+
var MIB7 = 1024 ** 2;
|
|
47291
47490
|
function formatOrphan(orphan) {
|
|
47292
|
-
const size = `${(orphan.bytes /
|
|
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
|
|
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/
|
|
48765
|
-
|
|
48766
|
-
|
|
48767
|
-
|
|
48768
|
-
|
|
48769
|
-
|
|
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
|
|
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 *
|
|
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(["
|
|
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
|
-
|
|
49153
|
-
|
|
49154
|
-
|
|
49155
|
-
|
|
49156
|
-
|
|
49157
|
-
|
|
49158
|
-
|
|
49159
|
-
|
|
49160
|
-
|
|
49161
|
-
|
|
49162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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/
|
|
49571
|
-
|
|
49572
|
-
|
|
49573
|
-
|
|
49574
|
-
|
|
49575
|
-
|
|
49576
|
-
|
|
49577
|
-
|
|
49578
|
-
|
|
49579
|
-
|
|
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(
|
|
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
|
|
49602
|
-
import { join as
|
|
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 =
|
|
49605
|
-
const directory =
|
|
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 =
|
|
49641
|
+
const file = join29(directory, "image.json");
|
|
49609
49642
|
rmSync25(file, { recursive: true, force: true });
|
|
49610
|
-
const fd =
|
|
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
|
-
|
|
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 =
|
|
49660
|
-
const 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 =
|
|
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(
|
|
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 {
|
|
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 = {
|
|
49935
|
+
const step = { fetched: false };
|
|
49904
49936
|
try {
|
|
49905
|
-
return await writer.
|
|
49937
|
+
return await writer.createBuilderImage(BUILDER_IMAGE, {
|
|
49906
49938
|
signal: timeout,
|
|
49907
|
-
|
|
49908
|
-
step.
|
|
49939
|
+
onFetched: () => {
|
|
49940
|
+
step.fetched = true;
|
|
49909
49941
|
}
|
|
49910
49942
|
});
|
|
49911
49943
|
} catch (error) {
|
|
49912
|
-
const reason = timeout.aborted && !step.
|
|
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
|
|
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({
|
|
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
|
|
50321
|
-
var IpListSchema =
|
|
50322
|
-
var StatusSchema =
|
|
50323
|
-
BackendState:
|
|
50324
|
-
Self:
|
|
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
|
|
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 =
|
|
50606
|
-
oauth_query:
|
|
50607
|
-
tokenID:
|
|
50608
|
-
tokenName:
|
|
50609
|
-
imps:
|
|
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 =
|
|
50612
|
-
name:
|
|
50613
|
-
redirectURIs:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -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 =
|
|
51056
|
-
error:
|
|
51057
|
-
error_description:
|
|
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
|
|
51750
|
-
var CredentialSchema =
|
|
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
|
|
51772
|
-
var HandlerSchema =
|
|
51773
|
-
var WebSchema =
|
|
51774
|
-
Handlers:
|
|
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 =
|
|
51777
|
-
Web:
|
|
51901
|
+
var ServiceConfigSchema = z93.object({
|
|
51902
|
+
Web: z93.record(z93.string(), WebSchema).nullable().optional()
|
|
51778
51903
|
});
|
|
51779
|
-
var ServeStatusSchema =
|
|
51780
|
-
TCP:
|
|
51781
|
-
Services:
|
|
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
|
|
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 =
|
|
51836
|
-
name:
|
|
51837
|
-
comment:
|
|
51838
|
-
ports:
|
|
51839
|
-
tags:
|
|
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 =
|
|
51842
|
-
var TokenSchema3 =
|
|
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 =
|
|
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
|
-
|
|
52232
|
-
|
|
52233
|
-
|
|
52234
|
-
|
|
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
|
|
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
|
|
52314
|
-
const
|
|
52315
|
-
|
|
52316
|
-
|
|
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
|
|
52359
|
-
var AttemptStateSchema =
|
|
52360
|
-
failures:
|
|
52361
|
-
lastAttemptAt:
|
|
52362
|
-
lastError:
|
|
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 =
|
|
52365
|
-
var LegacyAccountSchema =
|
|
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
|
|
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 =
|
|
52566
|
-
var EnvelopeSchema =
|
|
52567
|
-
success:
|
|
52568
|
-
errors:
|
|
52569
|
-
result:
|
|
52570
|
-
result_info:
|
|
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 =
|
|
52573
|
-
id:
|
|
52574
|
-
name:
|
|
52575
|
-
name_servers:
|
|
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 =
|
|
52578
|
-
id:
|
|
52579
|
-
name:
|
|
52580
|
-
type:
|
|
52581
|
-
content:
|
|
52582
|
-
proxied:
|
|
52583
|
-
comment:
|
|
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 =
|
|
52588
|
-
var RecordListSchema =
|
|
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
|
|
55108
|
-
var CopiesSchema =
|
|
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, {
|