@impz/testing 6.7.0 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +813 -714
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -18998,517 +18998,82 @@ import invariant7 from "tiny-invariant";
18998
18998
 
18999
18999
  // packages/daemon/src/config.ts
19000
19000
  import { join as join4 } from "path";
19001
- import * as z53 from "zod";
19002
- // packages/shared/src/format-bytes.ts
19003
- var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"];
19004
- function formatBytes(bytes) {
19005
- let value = bytes;
19006
- let unit = 0;
19007
- while (value >= 1024 && unit < UNITS.length - 1) {
19008
- value /= 1024;
19009
- unit += 1;
19010
- }
19011
- const digits = unit === 0 || value >= 100 ? 0 : 1;
19012
- return `${value.toFixed(digits)} ${UNITS[unit] ?? "B"}`;
19013
- }
19014
- // packages/shared/src/get-error-message.ts
19015
- function getErrorMessage(error) {
19016
- return error instanceof Error ? error.message : String(error);
19017
- }
19018
- // packages/shared/src/merge-bytes.ts
19019
- function mergeBytes(chunks) {
19020
- const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
19021
- let offset = 0;
19022
- for (const chunk of chunks) {
19023
- merged.set(chunk, offset);
19024
- offset += chunk.byteLength;
19025
- }
19026
- return merged;
19027
- }
19028
- // packages/shared/src/to-plain-octets.ts
19029
- var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
19030
- function toPlainOctets(entry) {
19031
- const groups = DOTTED_ENTRY.exec(entry)?.groups;
19032
- if (groups === undefined) {
19033
- return entry;
19034
- }
19035
- const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
19036
- return `${octets.join(".")}${groups["prefix"] ?? ""}`;
19037
- }
19038
- // packages/daemon/src/host-net/net/addressing.ts
19039
- import { IPv4 as IPv43 } from "ipaddr.js";
19001
+ // packages/api/src/schemas/lease-schema.ts
19002
+ import * as z2 from "zod";
19040
19003
 
19041
- // packages/daemon/src/host-net/net/addressing6.ts
19042
- import { IPv6 as IPv62 } from "ipaddr.js";
19004
+ // packages/api/src/schemas/name-schema.ts
19005
+ import * as z from "zod";
19006
+ var NameSchema = z.string().regex(/^[a-z][a-z0-9-]{0,30}$/u, "must be a lowercase letter followed by up to 30 lowercase letters, digits or hyphens");
19043
19007
 
19044
- // packages/daemon/src/host-net/net/parse-address.ts
19045
- import { IPv4, IPv6 } from "ipaddr.js";
19046
- function parseAddress(text) {
19047
- if (IPv4.isValidFourPartDecimal(text)) {
19048
- return IPv4.parse(text);
19049
- }
19050
- if (text.includes("%")) {
19051
- return null;
19052
- }
19053
- const hex = toHexGroups(text);
19054
- return hex !== null && IPv6.isValid(hex) ? IPv6.parse(hex) : null;
19055
- }
19056
- function toHexGroups(text) {
19057
- const split = text.lastIndexOf(":");
19058
- const tail = text.slice(split + 1);
19059
- if (!tail.includes(".")) {
19060
- return text;
19061
- }
19062
- if (!IPv4.isValidFourPartDecimal(tail)) {
19063
- return null;
19064
- }
19065
- const [first = 0, second = 0, third = 0, fourth = 0] = IPv4.parse(tail).octets;
19066
- return `${text.slice(0, split + 1)}${toGroup(first, second)}:${toGroup(third, fourth)}`;
19067
- }
19068
- function toGroup(high, low) {
19069
- return (high * 256 + low).toString(16);
19070
- }
19008
+ // packages/api/src/schemas/lease-schema.ts
19009
+ var LeaseLabelSchema = z2.string().regex(/^[\w.:-]{1,64}$/u, "must be 1\u201364 letters, digits, dots, underscores, colons or hyphens");
19010
+ var LeaseTtlSchema = z2.int().min(10).max(3600);
19011
+ var LeaseOwnerSchema = z2.object({
19012
+ principal: z2.string(),
19013
+ display: z2.string(),
19014
+ label: z2.string()
19015
+ });
19016
+ var LeaseSchema = z2.object({
19017
+ name: NameSchema,
19018
+ owner: LeaseOwnerSchema,
19019
+ until: z2.date().nullable()
19020
+ });
19021
+ var LeaseSummarySchema = z2.object({
19022
+ leases: z2.array(LeaseSchema).readonly(),
19023
+ otherCount: z2.int().nonnegative()
19024
+ });
19025
+ // packages/api/src/protocols/build-context-protocol.ts
19026
+ import * as z3 from "zod";
19027
+ var BUILD_CONTEXT_PATH = "/images/build-context";
19028
+ var DockerfilePathSchema = z3.string().min(1).refine((path) => !path.startsWith("/") && !path.split(/[/\\]/u).includes(".."), {
19029
+ message: "the Dockerfile path must be relative and stay inside the build context"
19030
+ });
19031
+ var BuildContextQuerySchema = z3.object({
19032
+ name: NameSchema,
19033
+ dockerfile: DockerfilePathSchema.optional()
19034
+ });
19035
+ var BuildContextUploadSchema = z3.object({
19036
+ id: z3.string().min(1),
19037
+ expiresAt: z3.coerce.date()
19038
+ });
19039
+ var BuildContextErrorSchema = z3.object({ code: z3.string(), message: z3.string() });
19040
+ // packages/api/src/schemas/image-op-event-schema.ts
19041
+ import * as z5 from "zod";
19071
19042
 
19072
- // packages/daemon/src/host-net/net/parse-cidr.ts
19073
- import { IPv4 as IPv42 } from "ipaddr.js";
19074
- function parseCidr(text) {
19075
- const [addressText = "", prefix, ...rest] = text.split("/");
19076
- const address = parseAddress(addressText);
19077
- if (address === null || rest.length > 0 || prefix !== undefined && !/^\d{1,3}$/v.test(prefix)) {
19078
- return null;
19079
- }
19080
- const maxBits = address instanceof IPv42 ? 32 : 128;
19081
- const bits = prefix === undefined ? maxBits : Number(prefix);
19082
- return bits > maxBits ? null : { address, bits };
19083
- }
19043
+ // packages/api/src/schemas/image-schema.ts
19044
+ import * as z4 from "zod";
19045
+ var ImageSourceSchema = z4.enum(["oci", "imp"]);
19046
+ var ImageSchema = z4.object({
19047
+ id: z4.string(),
19048
+ name: NameSchema,
19049
+ ref: z4.string(),
19050
+ digest: z4.string(),
19051
+ source: ImageSourceSchema,
19052
+ createdAt: z4.date(),
19053
+ sizeBytes: z4.int().nonnegative(),
19054
+ pinned: z4.boolean().default(false)
19055
+ });
19084
19056
 
19085
- // packages/daemon/src/host-net/net/addressing6.ts
19086
- var GROUPS = 8;
19087
- var GROUP_BITS = 16n;
19088
- var PREFIX_LENGTH = 64;
19089
- var GATEWAY_IP6 = "fe80::1";
19090
- function parseIpv6(text) {
19091
- const address = parseAddress(text);
19092
- return address instanceof IPv62 ? toBigint(address) : null;
19093
- }
19094
- function formatIpv6(address) {
19095
- const groups = Array.from({ length: GROUPS }, (_, index) => Number(address >> GROUP_BITS * BigInt(GROUPS - 1 - index) & 0xffffn));
19096
- return new IPv62(groups).toRFC5952String();
19097
- }
19098
- function formatCidr6(text) {
19099
- const parsed = parseCidr(text);
19100
- if (parsed === null || !(parsed.address instanceof IPv62)) {
19101
- return null;
19102
- }
19103
- const hostBits = BigInt(128 - parsed.bits);
19104
- const network = toBigint(parsed.address) >> hostBits << hostBits;
19105
- return `${formatIpv6(network)}/${String(parsed.bits)}`;
19106
- }
19107
- function parsePrefix64(cidr) {
19108
- const parsed = parseCidr(cidr);
19109
- const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
19110
- if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
19111
- return null;
19112
- }
19113
- return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
19114
- }
19115
- function buildUlaPrefix(random) {
19116
- if (random.length < 5) {
19117
- throw new Error("a ULA global ID needs 5 random bytes");
19118
- }
19119
- const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
19120
- const network = (0xfdn << 40n) + globalId << 80n;
19121
- return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
19122
- }
19123
- function findMappedIpv4(address) {
19124
- const parsed = parseAddress(address);
19125
- return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
19126
- }
19127
- function deriveGuestIp6(prefix, guestIpv4) {
19128
- return formatIpv6(prefix.network + BigInt(guestIpv4));
19129
- }
19130
- function toBigint(address) {
19131
- return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
19132
- }
19133
-
19134
- // packages/daemon/src/host-net/net/addressing.ts
19135
- var SLOT_SIZE = 4;
19136
- var SLOT_PREFIX_LENGTH = 30;
19137
- function parseSubnet(cidr) {
19138
- const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
19139
- if (parsed === null || !(parsed.address instanceof IPv43)) {
19140
- throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
19141
- }
19142
- if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
19143
- throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
19144
- }
19145
- const network = toNumber(parsed.address);
19146
- if (network % 2 ** (32 - parsed.bits) !== 0) {
19147
- throw new Error(`subnet has host bits set: ${cidr}`);
19148
- }
19149
- return { network, prefixLength: parsed.bits };
19150
- }
19151
- var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
19152
- var TAILNET_SIZE = 2 ** 22;
19153
- function isTailnetOverlap(subnet) {
19154
- const start = subnet.network;
19155
- const end = start + 2 ** (32 - subnet.prefixLength);
19156
- return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
19157
- }
19158
- function countSlots(subnet) {
19159
- return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
19160
- }
19161
- function deriveSlotAddress(slot, plan) {
19162
- if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
19163
- throw new RangeError(`slot ${String(slot)} is outside the subnet`);
19164
- }
19165
- const base = plan.subnet.network + slot * SLOT_SIZE;
19166
- const guest = base + 2;
19167
- return {
19168
- slot,
19169
- tap: `imp${String(slot)}`,
19170
- hostIp: formatIpv4(base + 1),
19171
- guestIp: formatIpv4(guest),
19172
- prefixLength: SLOT_PREFIX_LENGTH,
19173
- netmask: "255.255.255.252",
19174
- guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
19175
- hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
19176
- guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
19177
- tailnetPort: plan.portBase + slot
19178
- };
19179
- }
19180
- function parseIpv4(text, allowMapped = false) {
19181
- const address = parseAddress(text);
19182
- if (address instanceof IPv43) {
19183
- return toNumber(address);
19184
- }
19185
- return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
19186
- }
19187
- function findPeerSlot(peer, local, subnet) {
19188
- const peerIp = parseIpv4(peer, true);
19189
- const localIp = parseIpv4(local, true);
19190
- if (peerIp === null || localIp === null) {
19191
- return null;
19192
- }
19193
- const offset = peerIp - subnet.network;
19194
- if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
19195
- return null;
19196
- }
19197
- return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
19198
- }
19199
- function findGuestSlot(address, subnet) {
19200
- const ip = parseIpv4(address, true);
19201
- const offset = ip === null ? -1 : ip - subnet.network;
19202
- if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
19203
- return null;
19204
- }
19205
- return (offset - 2) / SLOT_SIZE;
19206
- }
19207
- function formatSubnet(subnet) {
19208
- return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
19209
- }
19210
- function formatCidr4(text) {
19211
- const parsed = parseCidr(toPlainOctets(text));
19212
- if (parsed === null || !(parsed.address instanceof IPv43)) {
19213
- return null;
19214
- }
19215
- const ip = toNumber(parsed.address);
19216
- return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
19217
- }
19218
- function toNumber(address) {
19219
- return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
19220
- }
19221
- function formatIpv4(address) {
19222
- return splitOctets(address).join(".");
19223
- }
19224
- function splitOctets(address) {
19225
- return [
19226
- Math.floor(address / 2 ** 24) % 256,
19227
- Math.floor(address / 2 ** 16) % 256,
19228
- Math.floor(address / 2 ** 8) % 256,
19229
- address % 256
19230
- ];
19231
- }
19232
- function toHexByte(octet) {
19233
- return octet.toString(16).padStart(2, "0");
19234
- }
19235
-
19236
- // packages/daemon/src/host-net/net/ipv6-plan.ts
19237
- import { randomBytes } from "crypto";
19238
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
19239
- import { dirname, join as join2 } from "path";
19240
-
19241
- // packages/daemon/src/platform/process/get-impd-logger.ts
19242
- import { getLogger } from "@logtape/logtape";
19243
- function getImpdLogger(area) {
19244
- return getLogger(["impd", area]);
19245
- }
19246
-
19247
- // packages/daemon/src/platform/process/run-command.ts
19248
- async function runCommand(argv, options = {}) {
19249
- const child = Bun.spawn([...argv], {
19250
- stdin: "ignore",
19251
- stdout: "pipe",
19252
- stderr: "pipe",
19253
- env: options.env === undefined ? process.env : { ...options.env },
19254
- ...options.signal !== undefined && { signal: options.signal }
19255
- });
19256
- const [stdout, stderr, exitCode] = await Promise.all([
19257
- new Response(child.stdout).text(),
19258
- new Response(child.stderr).text(),
19259
- child.exited
19260
- ]);
19261
- return { exitCode, stdout, stderr };
19262
- }
19263
- async function runChecked(argv, options = {}, run = runCommand) {
19264
- const result = await run(argv, options);
19265
- if (result.exitCode !== 0) {
19266
- throw buildExitError(argv, result);
19267
- }
19268
- return result.stdout;
19269
- }
19270
- function buildExitError(argv, result) {
19271
- const output = result.stderr.trim() || (result.stdout ?? "").trim();
19272
- return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
19273
- }
19274
-
19275
- // packages/daemon/src/host-net/net/nat66-ruleset.ts
19276
- var NAT66_TABLE = "ip6 imp_nat66";
19277
- function buildNat66Ruleset(prefix, uplink) {
19278
- return [
19279
- `table ${NAT66_TABLE} {}`,
19280
- `delete table ${NAT66_TABLE}`,
19281
- `table ${NAT66_TABLE} {`,
19282
- " chain postrouting {",
19283
- " type nat hook postrouting priority srcnat; policy accept;",
19284
- ` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
19285
- " }",
19286
- "}",
19287
- ""
19288
- ].join(`
19289
- `);
19290
- }
19291
- function buildNat66Removal() {
19292
- return `table ${NAT66_TABLE} {}
19293
- delete table ${NAT66_TABLE}
19294
- `;
19295
- }
19296
-
19297
- // packages/daemon/src/host-net/net/ipv6-plan.ts
19298
- function parseIpv6Setting(text) {
19299
- if (text === "auto" || text === "off") {
19300
- return { kind: text };
19301
- }
19302
- const prefix = parsePrefix64(text);
19303
- if (prefix === null) {
19304
- throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
19305
- }
19306
- return { kind: "routed", prefix };
19307
- }
19308
- async function resolveIpv6Plan(setting, deps) {
19309
- const stopIpv6 = async (reason) => {
19310
- await removeNat66(deps);
19311
- getImpdLogger("ipv6").info("off ({reason})", { reason });
19312
- return null;
19313
- };
19314
- if (setting.kind === "off") {
19315
- return stopIpv6("IMP_SUBNET6=off");
19316
- }
19317
- const uplink = await deps.readDefaultRoute();
19318
- if (setting.kind === "auto" && uplink === null) {
19319
- return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
19320
- }
19321
- const missing = await deps.checkHostRules();
19322
- if (missing !== null) {
19323
- return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
19324
- }
19325
- if (setting.kind === "routed") {
19326
- await removeNat66(deps);
19327
- getImpdLogger("ipv6").info("{text}, routed{route}", {
19328
- text: setting.prefix.text,
19329
- route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
19330
- });
19331
- return { prefix: setting.prefix, nat66: false, uplink };
19332
- }
19333
- const prefix = deps.readUlaPrefix();
19334
- try {
19335
- await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
19336
- } catch (error) {
19337
- return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
19338
- }
19339
- getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
19340
- text: prefix.text,
19341
- uplink: uplink ?? ""
19342
- });
19343
- return { prefix, nat66: true, uplink };
19344
- }
19345
- async function removeNat66(deps) {
19346
- try {
19347
- await deps.runNft(buildNat66Removal());
19348
- } catch {}
19349
- }
19350
- var HOST_RULES6 = [
19351
- ["filter", "-A INPUT -i imp+ -j DROP"],
19352
- ["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
19353
- ["filter", "-A FORWARD -o imp+ -j DROP"],
19354
- ["filter", "-A FORWARD -i imp+ -j DROP"],
19355
- ["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
19356
- ];
19357
- var HOST_SYSCTLS6 = [
19358
- ["default/accept_ra", "0"],
19359
- ["default/accept_redirects", "0"],
19360
- ["all/forwarding", "1"]
19361
- ];
19362
- async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
19363
- for (const table of ["filter", "raw"]) {
19364
- const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
19365
- const rules = new Set(result.stdout.split(`
19366
- `).map((line) => line.trim()));
19367
- const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
19368
- if (result.exitCode !== 0) {
19369
- return `ip6tables -t ${table}: ${result.stderr.trim()}`;
19370
- }
19371
- if (missing !== undefined) {
19372
- return `no ip6tables -t ${table} ${missing[1]}`;
19373
- }
19374
- }
19375
- for (const [key, wanted] of HOST_SYSCTLS6) {
19376
- const value = readSysctl(join2(procSys, key));
19377
- if (value !== wanted) {
19378
- return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
19379
- }
19380
- }
19381
- return null;
19382
- }
19383
- function readSysctl(path) {
19384
- try {
19385
- return readFileSync2(path, "utf8").trim();
19386
- } catch {
19387
- return null;
19388
- }
19389
- }
19390
- function readOrCreateUlaPrefix(path) {
19391
- if (existsSync2(path)) {
19392
- const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
19393
- if (prefix !== null) {
19394
- return prefix;
19395
- }
19396
- }
19397
- const prefix = buildUlaPrefix(randomBytes(5));
19398
- mkdirSync2(dirname(path), { recursive: true });
19399
- writeFileSync(path, `${prefix.text}
19400
- `);
19401
- return prefix;
19402
- }
19403
-
19404
- // packages/daemon/src/host-net/net/range-matcher.ts
19405
- import { IPv4 as IPv44 } from "ipaddr.js";
19406
- function buildRangeMatcher(ranges) {
19407
- const ranges4 = [];
19408
- const ranges6 = [];
19409
- for (const range of ranges) {
19410
- const cidr = parseCidr(range);
19411
- if (cidr === null) {
19412
- throw new Error(`not an address range: ${range}`);
19413
- }
19414
- if (cidr.address instanceof IPv44) {
19415
- ranges4.push([cidr.address, cidr.bits]);
19416
- } else {
19417
- ranges6.push([cidr.address, cidr.bits]);
19418
- }
19419
- }
19420
- const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
19421
- const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
19422
- return (address) => {
19423
- const ip = parseAddress(address);
19424
- if (ip === null) {
19425
- return false;
19426
- }
19427
- if (ip instanceof IPv44) {
19428
- return isIn4(ip);
19429
- }
19430
- return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
19431
- };
19432
- }
19433
-
19434
- // packages/daemon/src/identity/auth/parse-tailnet-rules.ts
19435
- import * as z48 from "zod";
19436
- // packages/api/src/schemas/lease-schema.ts
19437
- import * as z2 from "zod";
19438
-
19439
- // packages/api/src/schemas/name-schema.ts
19440
- import * as z from "zod";
19441
- var NameSchema = z.string().regex(/^[a-z][a-z0-9-]{0,30}$/u, "must be a lowercase letter followed by up to 30 lowercase letters, digits or hyphens");
19442
-
19443
- // packages/api/src/schemas/lease-schema.ts
19444
- var LeaseLabelSchema = z2.string().regex(/^[\w.:-]{1,64}$/u, "must be 1\u201364 letters, digits, dots, underscores, colons or hyphens");
19445
- var LeaseTtlSchema = z2.int().min(10).max(3600);
19446
- var LeaseOwnerSchema = z2.object({
19447
- principal: z2.string(),
19448
- display: z2.string(),
19449
- label: z2.string()
19450
- });
19451
- var LeaseSchema = z2.object({
19452
- name: NameSchema,
19453
- owner: LeaseOwnerSchema,
19454
- until: z2.date().nullable()
19455
- });
19456
- var LeaseSummarySchema = z2.object({
19457
- leases: z2.array(LeaseSchema).readonly(),
19458
- otherCount: z2.int().nonnegative()
19459
- });
19460
- // packages/api/src/protocols/build-context-protocol.ts
19461
- import * as z3 from "zod";
19462
- var BUILD_CONTEXT_PATH = "/images/build-context";
19463
- var DockerfilePathSchema = z3.string().min(1).refine((path) => !path.startsWith("/") && !path.split(/[/\\]/u).includes(".."), {
19464
- message: "the Dockerfile path must be relative and stay inside the build context"
19465
- });
19466
- var BuildContextQuerySchema = z3.object({
19467
- name: NameSchema,
19468
- dockerfile: DockerfilePathSchema.optional()
19469
- });
19470
- var BuildContextUploadSchema = z3.object({
19471
- id: z3.string().min(1),
19472
- expiresAt: z3.coerce.date()
19473
- });
19474
- var BuildContextErrorSchema = z3.object({ code: z3.string(), message: z3.string() });
19475
- // packages/api/src/schemas/image-op-event-schema.ts
19476
- import * as z5 from "zod";
19477
-
19478
- // packages/api/src/schemas/image-schema.ts
19479
- import * as z4 from "zod";
19480
- var ImageSourceSchema = z4.enum(["oci", "imp"]);
19481
- var ImageSchema = z4.object({
19482
- id: z4.string(),
19483
- name: NameSchema,
19484
- ref: z4.string(),
19485
- digest: z4.string(),
19486
- source: ImageSourceSchema,
19487
- createdAt: z4.date(),
19488
- sizeBytes: z4.int().nonnegative(),
19489
- pinned: z4.boolean().default(false)
19490
- });
19491
-
19492
- // packages/api/src/schemas/image-op-event-schema.ts
19493
- var ImageBuildPhaseSchema = z5.enum(["upload", "pack", "build", "pull", "unpack", "copy"]);
19494
- var IMAGE_BUILD_LOG_MAX_CHARS = 8192;
19495
- var ImageBuildProgressSchema = z5.object({
19496
- type: z5.literal("progress"),
19497
- phase: ImageBuildPhaseSchema,
19498
- elapsedMs: z5.number().nonnegative(),
19499
- step: z5.string().optional()
19500
- });
19501
- var ImageBuildLogSchema = z5.object({
19502
- type: z5.literal("log"),
19503
- text: z5.string().max(IMAGE_BUILD_LOG_MAX_CHARS)
19504
- });
19505
- var ImageOpEventSchema = z5.discriminatedUnion("type", [
19506
- ImageBuildProgressSchema,
19507
- ImageBuildLogSchema,
19508
- z5.object({ type: z5.literal("image"), image: ImageSchema })
19509
- ]);
19510
- // packages/api/src/protocols/exec-protocol.ts
19511
- import * as z9 from "zod";
19057
+ // packages/api/src/schemas/image-op-event-schema.ts
19058
+ var ImageBuildPhaseSchema = z5.enum(["upload", "pack", "build", "pull", "unpack", "copy"]);
19059
+ var IMAGE_BUILD_LOG_MAX_CHARS = 8192;
19060
+ var ImageBuildProgressSchema = z5.object({
19061
+ type: z5.literal("progress"),
19062
+ phase: ImageBuildPhaseSchema,
19063
+ elapsedMs: z5.number().nonnegative(),
19064
+ step: z5.string().optional()
19065
+ });
19066
+ var ImageBuildLogSchema = z5.object({
19067
+ type: z5.literal("log"),
19068
+ text: z5.string().max(IMAGE_BUILD_LOG_MAX_CHARS)
19069
+ });
19070
+ var ImageOpEventSchema = z5.discriminatedUnion("type", [
19071
+ ImageBuildProgressSchema,
19072
+ ImageBuildLogSchema,
19073
+ z5.object({ type: z5.literal("image"), image: ImageSchema })
19074
+ ]);
19075
+ // packages/api/src/protocols/exec-protocol.ts
19076
+ import * as z9 from "zod";
19512
19077
 
19513
19078
  // packages/api/src/schemas/session-output-schema.ts
19514
19079
  import * as z7 from "zod";
@@ -20643,6 +20208,7 @@ var SystemInfoSchema = z33.object({
20643
20208
  ramUsedMib: z33.int().nonnegative(),
20644
20209
  ramReservedMib: z33.int().nonnegative(),
20645
20210
  ramCommittedMib: z33.int().nonnegative(),
20211
+ ramPriorityKeptMib: z33.int().nonnegative().optional(),
20646
20212
  ramSleepingMib: z33.int().nonnegative().optional(),
20647
20213
  awakeCount: z33.int().nonnegative(),
20648
20214
  impCount: z33.int().nonnegative(),
@@ -21105,166 +20671,603 @@ var authContract = {
21105
20671
  logout: contractBase.meta(ACCESS.noSession).output(EmptySchema)
21106
20672
  };
21107
20673
 
21108
- // packages/api/src/contract/session-contract.ts
21109
- var sessionContract = { auth: authContract };
21110
- // packages/api/src/contract/collect-procedure-access.ts
21111
- import { isContractProcedure } from "@orpc/contract";
21112
- import * as z45 from "zod";
20674
+ // packages/api/src/contract/session-contract.ts
20675
+ var sessionContract = { auth: authContract };
20676
+ // packages/api/src/contract/collect-procedure-access.ts
20677
+ import { isContractProcedure } from "@orpc/contract";
20678
+ import * as z45 from "zod";
20679
+
20680
+ // packages/api/src/schemas/procedure-access-schema.ts
20681
+ import * as z44 from "zod";
20682
+ var FieldsSchema = z44.array(z44.string()).readonly();
20683
+ var NoSessionAccessSchema = z44.object({ on: z44.literal("none") });
20684
+ var HostAccessSchema = z44.object({
20685
+ scope: ScopeSchema,
20686
+ on: z44.literal("host"),
20687
+ audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
20688
+ });
20689
+ var ImpAccessSchema = z44.object({
20690
+ scope: ScopeSchema,
20691
+ on: z44.literal("imp"),
20692
+ fields: FieldsSchema,
20693
+ noGrantable: z44.literal(true).optional(),
20694
+ audit: z44.literal(false).optional()
20695
+ });
20696
+ var GrantAccessSchema = z44.object({
20697
+ scope: ScopeSchema,
20698
+ on: z44.literal("grant"),
20699
+ fields: FieldsSchema,
20700
+ secretField: z44.string(),
20701
+ audit: z44.literal(false).optional()
20702
+ });
20703
+ var ImageAccessSchema = z44.object({
20704
+ scope: ScopeSchema,
20705
+ on: z44.literal("image"),
20706
+ held: z44.literal(true).optional(),
20707
+ audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
20708
+ });
20709
+ var AnyAccessSchema = z44.object({
20710
+ scope: ScopeSchema,
20711
+ on: z44.literal("any"),
20712
+ audit: z44.literal(false).optional()
20713
+ });
20714
+ var ProcedureAccessSchema = z44.discriminatedUnion("on", [
20715
+ NoSessionAccessSchema,
20716
+ HostAccessSchema,
20717
+ ImpAccessSchema,
20718
+ GrantAccessSchema,
20719
+ ImageAccessSchema,
20720
+ AnyAccessSchema
20721
+ ]);
20722
+
20723
+ // packages/api/src/contract/collect-procedure-access.ts
20724
+ var MetaSchema = z45.object({ access: ProcedureAccessSchema });
20725
+ function collectProcedureAccess(router, prefix = "") {
20726
+ return Object.entries(router).flatMap(([key, value]) => {
20727
+ const path = `${prefix}${key}`;
20728
+ if (isContractProcedure(value)) {
20729
+ const meta = value["~orpc"].meta;
20730
+ const parsed = MetaSchema.safeParse(meta);
20731
+ return [{ path, access: parsed.success ? parsed.data.access : null }];
20732
+ }
20733
+ return typeof value === "object" && value !== null ? collectProcedureAccess(value, `${path}.`) : [];
20734
+ });
20735
+ }
20736
+ // packages/api/src/rules/is-imp-allowed.ts
20737
+ function isImpAllowed(patterns, name) {
20738
+ return patterns === null || patterns.some((pattern) => isPatternMatch(pattern, name));
20739
+ }
20740
+ function isPatternMatch(pattern, name) {
20741
+ const [first = "", ...rest] = pattern.split("*");
20742
+ if (rest.length === 0) {
20743
+ return name === first;
20744
+ }
20745
+ const last = rest.pop() ?? "";
20746
+ if (!name.startsWith(first) || !name.endsWith(last) || name.length < first.length + last.length) {
20747
+ return false;
20748
+ }
20749
+ let at = first.length;
20750
+ const end = name.length - last.length;
20751
+ for (const part of rest) {
20752
+ const found = name.indexOf(part, at);
20753
+ if (found === -1 || found + part.length > end) {
20754
+ return false;
20755
+ }
20756
+ at = found + part.length;
20757
+ }
20758
+ return true;
20759
+ }
20760
+ // packages/api/src/protocols/login-shell.ts
20761
+ var CONSOLE_SHELL = buildConsoleShell("/etc/passwd", ["/bin/bash", "/bin/sh"]);
20762
+ function buildConsoleShell(passwdPath, fallbacks) {
20763
+ return [
20764
+ "uid=$(id -u 2>/dev/null) || uid=0",
20765
+ "shell=",
20766
+ "while IFS=: read -r _ _ id _ _ _ login; do",
20767
+ ' if [ "$id" = "$uid" ]; then shell=$login; break; fi',
20768
+ `done < ${passwdPath}`,
20769
+ ...fallbacks.map((fallback) => `[ -x "$shell" ] || shell=${fallback}`),
20770
+ 'exec "$shell" -l'
20771
+ ].join(`
20772
+ `);
20773
+ }
20774
+ // packages/api/src/protocols/mcp-path.ts
20775
+ var MCP_PATH = "/mcp";
20776
+ // packages/api/src/protocols/move-receive-path.ts
20777
+ var MOVE_RECEIVE_PATH = "/move/receive";
20778
+ // packages/api/src/contract/move-peer-contract.ts
20779
+ import { oc as oc2 } from "@orpc/contract";
20780
+
20781
+ // packages/api/src/schemas/move-peer-schema.ts
20782
+ import * as z46 from "zod";
20783
+ var SystemDriveSchema = z46.string().regex(/^[\da-f]{64}$/v);
20784
+ var MoveOfferSchema = z46.object({
20785
+ imageDigest: z46.string(),
20786
+ receivedImageDigest: z46.string().nullable().default(null),
20787
+ systemDrive: SystemDriveSchema.optional()
20788
+ });
20789
+ var MoveOfferReplySchema = z46.object({
20790
+ needsImage: z46.boolean(),
20791
+ needsSystemDrive: z46.boolean().default(false),
20792
+ storage: z46.enum(["xfs", "zfs"]),
20793
+ keepsMaxMemory: z46.boolean().default(false),
20794
+ keepsLeases: z46.boolean().default(false),
20795
+ keepsPublicEgress: z46.boolean().default(false),
20796
+ keepsBackupOff: z46.boolean().default(false)
20797
+ });
20798
+ var MoveCommitReplySchema = z46.object({ isCommitted: z46.boolean() });
20799
+
20800
+ // packages/api/src/contract/move-peer-contract.ts
20801
+ var movePeerContract = {
20802
+ offer: oc2.route({ method: "POST", path: "/move/offer" }).input(MoveOfferSchema).output(MoveOfferReplySchema),
20803
+ commit: oc2.route({ method: "POST", path: "/move/commit" }).output(MoveCommitReplySchema),
20804
+ abort: oc2.route({ method: "POST", path: "/move/abort" }).output(MoveCommitReplySchema)
20805
+ };
20806
+ // packages/api/src/schemas/secret-presets.ts
20807
+ var SECRET_PRESETS = {
20808
+ github: {
20809
+ kind: "static",
20810
+ rules: [
20811
+ { host: "github.com", header: "authorization", scheme: "basic", user: "x-access-token" },
20812
+ { host: "api.github.com", header: "authorization", scheme: "bearer" },
20813
+ { host: "uploads.github.com", header: "authorization", scheme: "bearer" }
20814
+ ],
20815
+ env: ["GH_TOKEN", "GITHUB_TOKEN"]
20816
+ },
20817
+ anthropic: {
20818
+ kind: "static",
20819
+ rules: [{ host: "api.anthropic.com", header: "x-api-key", scheme: "raw" }],
20820
+ env: ["ANTHROPIC_API_KEY"]
20821
+ },
20822
+ npm: {
20823
+ kind: "static",
20824
+ rules: [{ host: "registry.npmjs.org", header: "authorization", scheme: "bearer" }],
20825
+ env: ["NPM_TOKEN"]
20826
+ }
20827
+ };
20828
+ // packages/api/src/rules/has-scope.ts
20829
+ function hasScope(granted, needed) {
20830
+ const grantedRank = ScopeSchema.options.indexOf(granted);
20831
+ const neededRank = ScopeSchema.options.indexOf(needed);
20832
+ return grantedRank !== -1 && neededRank !== -1 && grantedRank >= neededRank;
20833
+ }
20834
+ // packages/daemon/src/config.ts
20835
+ import * as z53 from "zod";
20836
+ // packages/shared/src/format-bytes.ts
20837
+ var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"];
20838
+ function formatBytes(bytes) {
20839
+ let value = bytes;
20840
+ let unit = 0;
20841
+ while (value >= 1024 && unit < UNITS.length - 1) {
20842
+ value /= 1024;
20843
+ unit += 1;
20844
+ }
20845
+ const digits = unit === 0 || value >= 100 ? 0 : 1;
20846
+ return `${value.toFixed(digits)} ${UNITS[unit] ?? "B"}`;
20847
+ }
20848
+ // packages/shared/src/get-error-message.ts
20849
+ function getErrorMessage(error) {
20850
+ return error instanceof Error ? error.message : String(error);
20851
+ }
20852
+ // packages/shared/src/merge-bytes.ts
20853
+ function mergeBytes(chunks) {
20854
+ const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
20855
+ let offset = 0;
20856
+ for (const chunk of chunks) {
20857
+ merged.set(chunk, offset);
20858
+ offset += chunk.byteLength;
20859
+ }
20860
+ return merged;
20861
+ }
20862
+ // packages/shared/src/to-plain-octets.ts
20863
+ var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
20864
+ function toPlainOctets(entry) {
20865
+ const groups = DOTTED_ENTRY.exec(entry)?.groups;
20866
+ if (groups === undefined) {
20867
+ return entry;
20868
+ }
20869
+ const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
20870
+ return `${octets.join(".")}${groups["prefix"] ?? ""}`;
20871
+ }
20872
+ // packages/daemon/src/host-net/net/addressing.ts
20873
+ import { IPv4 as IPv43 } from "ipaddr.js";
20874
+
20875
+ // packages/daemon/src/host-net/net/addressing6.ts
20876
+ import { IPv6 as IPv62 } from "ipaddr.js";
20877
+
20878
+ // packages/daemon/src/host-net/net/parse-address.ts
20879
+ import { IPv4, IPv6 } from "ipaddr.js";
20880
+ function parseAddress(text) {
20881
+ if (IPv4.isValidFourPartDecimal(text)) {
20882
+ return IPv4.parse(text);
20883
+ }
20884
+ if (text.includes("%")) {
20885
+ return null;
20886
+ }
20887
+ const hex = toHexGroups(text);
20888
+ return hex !== null && IPv6.isValid(hex) ? IPv6.parse(hex) : null;
20889
+ }
20890
+ function toHexGroups(text) {
20891
+ const split = text.lastIndexOf(":");
20892
+ const tail = text.slice(split + 1);
20893
+ if (!tail.includes(".")) {
20894
+ return text;
20895
+ }
20896
+ if (!IPv4.isValidFourPartDecimal(tail)) {
20897
+ return null;
20898
+ }
20899
+ const [first = 0, second = 0, third = 0, fourth = 0] = IPv4.parse(tail).octets;
20900
+ return `${text.slice(0, split + 1)}${toGroup(first, second)}:${toGroup(third, fourth)}`;
20901
+ }
20902
+ function toGroup(high, low) {
20903
+ return (high * 256 + low).toString(16);
20904
+ }
20905
+
20906
+ // packages/daemon/src/host-net/net/parse-cidr.ts
20907
+ import { IPv4 as IPv42 } from "ipaddr.js";
20908
+ function parseCidr(text) {
20909
+ const [addressText = "", prefix, ...rest] = text.split("/");
20910
+ const address = parseAddress(addressText);
20911
+ if (address === null || rest.length > 0 || prefix !== undefined && !/^\d{1,3}$/v.test(prefix)) {
20912
+ return null;
20913
+ }
20914
+ const maxBits = address instanceof IPv42 ? 32 : 128;
20915
+ const bits = prefix === undefined ? maxBits : Number(prefix);
20916
+ return bits > maxBits ? null : { address, bits };
20917
+ }
20918
+
20919
+ // packages/daemon/src/host-net/net/addressing6.ts
20920
+ var GROUPS = 8;
20921
+ var GROUP_BITS = 16n;
20922
+ var PREFIX_LENGTH = 64;
20923
+ var GATEWAY_IP6 = "fe80::1";
20924
+ function parseIpv6(text) {
20925
+ const address = parseAddress(text);
20926
+ return address instanceof IPv62 ? toBigint(address) : null;
20927
+ }
20928
+ function formatIpv6(address) {
20929
+ const groups = Array.from({ length: GROUPS }, (_, index) => Number(address >> GROUP_BITS * BigInt(GROUPS - 1 - index) & 0xffffn));
20930
+ return new IPv62(groups).toRFC5952String();
20931
+ }
20932
+ function formatCidr6(text) {
20933
+ const parsed = parseCidr(text);
20934
+ if (parsed === null || !(parsed.address instanceof IPv62)) {
20935
+ return null;
20936
+ }
20937
+ const hostBits = BigInt(128 - parsed.bits);
20938
+ const network = toBigint(parsed.address) >> hostBits << hostBits;
20939
+ return `${formatIpv6(network)}/${String(parsed.bits)}`;
20940
+ }
20941
+ function parsePrefix64(cidr) {
20942
+ const parsed = parseCidr(cidr);
20943
+ const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
20944
+ if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
20945
+ return null;
20946
+ }
20947
+ return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
20948
+ }
20949
+ function buildUlaPrefix(random) {
20950
+ if (random.length < 5) {
20951
+ throw new Error("a ULA global ID needs 5 random bytes");
20952
+ }
20953
+ const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
20954
+ const network = (0xfdn << 40n) + globalId << 80n;
20955
+ return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
20956
+ }
20957
+ function findMappedIpv4(address) {
20958
+ const parsed = parseAddress(address);
20959
+ return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
20960
+ }
20961
+ function deriveGuestIp6(prefix, guestIpv4) {
20962
+ return formatIpv6(prefix.network + BigInt(guestIpv4));
20963
+ }
20964
+ function toBigint(address) {
20965
+ return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
20966
+ }
20967
+
20968
+ // packages/daemon/src/host-net/net/addressing.ts
20969
+ var SLOT_SIZE = 4;
20970
+ var SLOT_PREFIX_LENGTH = 30;
20971
+ function parseSubnet(cidr) {
20972
+ const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
20973
+ if (parsed === null || !(parsed.address instanceof IPv43)) {
20974
+ throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
20975
+ }
20976
+ if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
20977
+ throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
20978
+ }
20979
+ const network = toNumber(parsed.address);
20980
+ if (network % 2 ** (32 - parsed.bits) !== 0) {
20981
+ throw new Error(`subnet has host bits set: ${cidr}`);
20982
+ }
20983
+ return { network, prefixLength: parsed.bits };
20984
+ }
20985
+ var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
20986
+ var TAILNET_SIZE = 2 ** 22;
20987
+ function isTailnetOverlap(subnet) {
20988
+ const start = subnet.network;
20989
+ const end = start + 2 ** (32 - subnet.prefixLength);
20990
+ return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
20991
+ }
20992
+ function countSlots(subnet) {
20993
+ return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
20994
+ }
20995
+ function deriveSlotAddress(slot, plan) {
20996
+ if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
20997
+ throw new RangeError(`slot ${String(slot)} is outside the subnet`);
20998
+ }
20999
+ const base = plan.subnet.network + slot * SLOT_SIZE;
21000
+ const guest = base + 2;
21001
+ return {
21002
+ slot,
21003
+ tap: `imp${String(slot)}`,
21004
+ hostIp: formatIpv4(base + 1),
21005
+ guestIp: formatIpv4(guest),
21006
+ prefixLength: SLOT_PREFIX_LENGTH,
21007
+ netmask: "255.255.255.252",
21008
+ guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
21009
+ hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
21010
+ guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
21011
+ tailnetPort: plan.portBase + slot
21012
+ };
21013
+ }
21014
+ function parseIpv4(text, allowMapped = false) {
21015
+ const address = parseAddress(text);
21016
+ if (address instanceof IPv43) {
21017
+ return toNumber(address);
21018
+ }
21019
+ return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
21020
+ }
21021
+ function findPeerSlot(peer, local, subnet) {
21022
+ const peerIp = parseIpv4(peer, true);
21023
+ const localIp = parseIpv4(local, true);
21024
+ if (peerIp === null || localIp === null) {
21025
+ return null;
21026
+ }
21027
+ const offset = peerIp - subnet.network;
21028
+ if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
21029
+ return null;
21030
+ }
21031
+ return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
21032
+ }
21033
+ function findGuestSlot(address, subnet) {
21034
+ const ip = parseIpv4(address, true);
21035
+ const offset = ip === null ? -1 : ip - subnet.network;
21036
+ if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
21037
+ return null;
21038
+ }
21039
+ return (offset - 2) / SLOT_SIZE;
21040
+ }
21041
+ function formatSubnet(subnet) {
21042
+ return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
21043
+ }
21044
+ function formatCidr4(text) {
21045
+ const parsed = parseCidr(toPlainOctets(text));
21046
+ if (parsed === null || !(parsed.address instanceof IPv43)) {
21047
+ return null;
21048
+ }
21049
+ const ip = toNumber(parsed.address);
21050
+ return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
21051
+ }
21052
+ function toNumber(address) {
21053
+ return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
21054
+ }
21055
+ function formatIpv4(address) {
21056
+ return splitOctets(address).join(".");
21057
+ }
21058
+ function splitOctets(address) {
21059
+ return [
21060
+ Math.floor(address / 2 ** 24) % 256,
21061
+ Math.floor(address / 2 ** 16) % 256,
21062
+ Math.floor(address / 2 ** 8) % 256,
21063
+ address % 256
21064
+ ];
21065
+ }
21066
+ function toHexByte(octet) {
21067
+ return octet.toString(16).padStart(2, "0");
21068
+ }
21113
21069
 
21114
- // packages/api/src/schemas/procedure-access-schema.ts
21115
- import * as z44 from "zod";
21116
- var FieldsSchema = z44.array(z44.string()).readonly();
21117
- var NoSessionAccessSchema = z44.object({ on: z44.literal("none") });
21118
- var HostAccessSchema = z44.object({
21119
- scope: ScopeSchema,
21120
- on: z44.literal("host"),
21121
- audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
21122
- });
21123
- var ImpAccessSchema = z44.object({
21124
- scope: ScopeSchema,
21125
- on: z44.literal("imp"),
21126
- fields: FieldsSchema,
21127
- noGrantable: z44.literal(true).optional(),
21128
- audit: z44.literal(false).optional()
21129
- });
21130
- var GrantAccessSchema = z44.object({
21131
- scope: ScopeSchema,
21132
- on: z44.literal("grant"),
21133
- fields: FieldsSchema,
21134
- secretField: z44.string(),
21135
- audit: z44.literal(false).optional()
21136
- });
21137
- var ImageAccessSchema = z44.object({
21138
- scope: ScopeSchema,
21139
- on: z44.literal("image"),
21140
- held: z44.literal(true).optional(),
21141
- audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
21142
- });
21143
- var AnyAccessSchema = z44.object({
21144
- scope: ScopeSchema,
21145
- on: z44.literal("any"),
21146
- audit: z44.literal(false).optional()
21147
- });
21148
- var ProcedureAccessSchema = z44.discriminatedUnion("on", [
21149
- NoSessionAccessSchema,
21150
- HostAccessSchema,
21151
- ImpAccessSchema,
21152
- GrantAccessSchema,
21153
- ImageAccessSchema,
21154
- AnyAccessSchema
21155
- ]);
21070
+ // packages/daemon/src/host-net/net/ipv6-plan.ts
21071
+ import { randomBytes } from "crypto";
21072
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
21073
+ import { dirname, join as join2 } from "path";
21156
21074
 
21157
- // packages/api/src/contract/collect-procedure-access.ts
21158
- var MetaSchema = z45.object({ access: ProcedureAccessSchema });
21159
- function collectProcedureAccess(router, prefix = "") {
21160
- return Object.entries(router).flatMap(([key, value]) => {
21161
- const path = `${prefix}${key}`;
21162
- if (isContractProcedure(value)) {
21163
- const meta = value["~orpc"].meta;
21164
- const parsed = MetaSchema.safeParse(meta);
21165
- return [{ path, access: parsed.success ? parsed.data.access : null }];
21166
- }
21167
- return typeof value === "object" && value !== null ? collectProcedureAccess(value, `${path}.`) : [];
21075
+ // packages/daemon/src/platform/process/get-impd-logger.ts
21076
+ import { getLogger } from "@logtape/logtape";
21077
+ function getImpdLogger(area) {
21078
+ return getLogger(["impd", area]);
21079
+ }
21080
+
21081
+ // packages/daemon/src/platform/process/run-command.ts
21082
+ async function runCommand(argv, options = {}) {
21083
+ const child = Bun.spawn([...argv], {
21084
+ stdin: "ignore",
21085
+ stdout: "pipe",
21086
+ stderr: "pipe",
21087
+ env: options.env === undefined ? process.env : { ...options.env },
21088
+ ...options.signal !== undefined && { signal: options.signal }
21168
21089
  });
21090
+ const [stdout, stderr, exitCode] = await Promise.all([
21091
+ new Response(child.stdout).text(),
21092
+ new Response(child.stderr).text(),
21093
+ child.exited
21094
+ ]);
21095
+ return { exitCode, stdout, stderr };
21096
+ }
21097
+ async function runChecked(argv, options = {}, run = runCommand) {
21098
+ const result = await run(argv, options);
21099
+ if (result.exitCode !== 0) {
21100
+ throw buildExitError(argv, result);
21101
+ }
21102
+ return result.stdout;
21103
+ }
21104
+ function buildExitError(argv, result) {
21105
+ const output = result.stderr.trim() || (result.stdout ?? "").trim();
21106
+ return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
21107
+ }
21108
+
21109
+ // packages/daemon/src/host-net/net/nat66-ruleset.ts
21110
+ var NAT66_TABLE = "ip6 imp_nat66";
21111
+ function buildNat66Ruleset(prefix, uplink) {
21112
+ return [
21113
+ `table ${NAT66_TABLE} {}`,
21114
+ `delete table ${NAT66_TABLE}`,
21115
+ `table ${NAT66_TABLE} {`,
21116
+ " chain postrouting {",
21117
+ " type nat hook postrouting priority srcnat; policy accept;",
21118
+ ` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
21119
+ " }",
21120
+ "}",
21121
+ ""
21122
+ ].join(`
21123
+ `);
21124
+ }
21125
+ function buildNat66Removal() {
21126
+ return `table ${NAT66_TABLE} {}
21127
+ delete table ${NAT66_TABLE}
21128
+ `;
21169
21129
  }
21170
- // packages/api/src/rules/is-imp-allowed.ts
21171
- function isImpAllowed(patterns, name) {
21172
- return patterns === null || patterns.some((pattern) => isPatternMatch(pattern, name));
21130
+
21131
+ // packages/daemon/src/host-net/net/ipv6-plan.ts
21132
+ function parseIpv6Setting(text) {
21133
+ if (text === "auto" || text === "off") {
21134
+ return { kind: text };
21135
+ }
21136
+ const prefix = parsePrefix64(text);
21137
+ if (prefix === null) {
21138
+ throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
21139
+ }
21140
+ return { kind: "routed", prefix };
21173
21141
  }
21174
- function isPatternMatch(pattern, name) {
21175
- const [first = "", ...rest] = pattern.split("*");
21176
- if (rest.length === 0) {
21177
- return name === first;
21142
+ async function resolveIpv6Plan(setting, deps) {
21143
+ const stopIpv6 = async (reason) => {
21144
+ await removeNat66(deps);
21145
+ getImpdLogger("ipv6").info("off ({reason})", { reason });
21146
+ return null;
21147
+ };
21148
+ if (setting.kind === "off") {
21149
+ return stopIpv6("IMP_SUBNET6=off");
21178
21150
  }
21179
- const last = rest.pop() ?? "";
21180
- if (!name.startsWith(first) || !name.endsWith(last) || name.length < first.length + last.length) {
21181
- return false;
21151
+ const uplink = await deps.readDefaultRoute();
21152
+ if (setting.kind === "auto" && uplink === null) {
21153
+ return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
21182
21154
  }
21183
- let at = first.length;
21184
- const end = name.length - last.length;
21185
- for (const part of rest) {
21186
- const found = name.indexOf(part, at);
21187
- if (found === -1 || found + part.length > end) {
21188
- return false;
21155
+ const missing = await deps.checkHostRules();
21156
+ if (missing !== null) {
21157
+ return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
21158
+ }
21159
+ if (setting.kind === "routed") {
21160
+ await removeNat66(deps);
21161
+ getImpdLogger("ipv6").info("{text}, routed{route}", {
21162
+ text: setting.prefix.text,
21163
+ route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
21164
+ });
21165
+ return { prefix: setting.prefix, nat66: false, uplink };
21166
+ }
21167
+ const prefix = deps.readUlaPrefix();
21168
+ try {
21169
+ await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
21170
+ } catch (error) {
21171
+ return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
21172
+ }
21173
+ getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
21174
+ text: prefix.text,
21175
+ uplink: uplink ?? ""
21176
+ });
21177
+ return { prefix, nat66: true, uplink };
21178
+ }
21179
+ async function removeNat66(deps) {
21180
+ try {
21181
+ await deps.runNft(buildNat66Removal());
21182
+ } catch {}
21183
+ }
21184
+ var HOST_RULES6 = [
21185
+ ["filter", "-A INPUT -i imp+ -j DROP"],
21186
+ ["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
21187
+ ["filter", "-A FORWARD -o imp+ -j DROP"],
21188
+ ["filter", "-A FORWARD -i imp+ -j DROP"],
21189
+ ["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
21190
+ ];
21191
+ var HOST_SYSCTLS6 = [
21192
+ ["default/accept_ra", "0"],
21193
+ ["default/accept_redirects", "0"],
21194
+ ["all/forwarding", "1"]
21195
+ ];
21196
+ async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
21197
+ for (const table of ["filter", "raw"]) {
21198
+ const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
21199
+ const rules = new Set(result.stdout.split(`
21200
+ `).map((line) => line.trim()));
21201
+ const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
21202
+ if (result.exitCode !== 0) {
21203
+ return `ip6tables -t ${table}: ${result.stderr.trim()}`;
21204
+ }
21205
+ if (missing !== undefined) {
21206
+ return `no ip6tables -t ${table} ${missing[1]}`;
21189
21207
  }
21190
- at = found + part.length;
21191
21208
  }
21192
- return true;
21209
+ for (const [key, wanted] of HOST_SYSCTLS6) {
21210
+ const value = readSysctl(join2(procSys, key));
21211
+ if (value !== wanted) {
21212
+ return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
21213
+ }
21214
+ }
21215
+ return null;
21193
21216
  }
21194
- // packages/api/src/protocols/login-shell.ts
21195
- var CONSOLE_SHELL = buildConsoleShell("/etc/passwd", ["/bin/bash", "/bin/sh"]);
21196
- function buildConsoleShell(passwdPath, fallbacks) {
21197
- return [
21198
- "uid=$(id -u 2>/dev/null) || uid=0",
21199
- "shell=",
21200
- "while IFS=: read -r _ _ id _ _ _ login; do",
21201
- ' if [ "$id" = "$uid" ]; then shell=$login; break; fi',
21202
- `done < ${passwdPath}`,
21203
- ...fallbacks.map((fallback) => `[ -x "$shell" ] || shell=${fallback}`),
21204
- 'exec "$shell" -l'
21205
- ].join(`
21217
+ function readSysctl(path) {
21218
+ try {
21219
+ return readFileSync2(path, "utf8").trim();
21220
+ } catch {
21221
+ return null;
21222
+ }
21223
+ }
21224
+ function readOrCreateUlaPrefix(path) {
21225
+ if (existsSync2(path)) {
21226
+ const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
21227
+ if (prefix !== null) {
21228
+ return prefix;
21229
+ }
21230
+ }
21231
+ const prefix = buildUlaPrefix(randomBytes(5));
21232
+ mkdirSync2(dirname(path), { recursive: true });
21233
+ writeFileSync(path, `${prefix.text}
21206
21234
  `);
21235
+ return prefix;
21207
21236
  }
21208
- // packages/api/src/protocols/mcp-path.ts
21209
- var MCP_PATH = "/mcp";
21210
- // packages/api/src/protocols/move-receive-path.ts
21211
- var MOVE_RECEIVE_PATH = "/move/receive";
21212
- // packages/api/src/contract/move-peer-contract.ts
21213
- import { oc as oc2 } from "@orpc/contract";
21214
-
21215
- // packages/api/src/schemas/move-peer-schema.ts
21216
- import * as z46 from "zod";
21217
- var SystemDriveSchema = z46.string().regex(/^[\da-f]{64}$/v);
21218
- var MoveOfferSchema = z46.object({
21219
- imageDigest: z46.string(),
21220
- receivedImageDigest: z46.string().nullable().default(null),
21221
- systemDrive: SystemDriveSchema.optional()
21222
- });
21223
- var MoveOfferReplySchema = z46.object({
21224
- needsImage: z46.boolean(),
21225
- needsSystemDrive: z46.boolean().default(false),
21226
- storage: z46.enum(["xfs", "zfs"]),
21227
- keepsMaxMemory: z46.boolean().default(false),
21228
- keepsLeases: z46.boolean().default(false),
21229
- keepsPublicEgress: z46.boolean().default(false),
21230
- keepsBackupOff: z46.boolean().default(false)
21231
- });
21232
- var MoveCommitReplySchema = z46.object({ isCommitted: z46.boolean() });
21233
21237
 
21234
- // packages/api/src/contract/move-peer-contract.ts
21235
- var movePeerContract = {
21236
- offer: oc2.route({ method: "POST", path: "/move/offer" }).input(MoveOfferSchema).output(MoveOfferReplySchema),
21237
- commit: oc2.route({ method: "POST", path: "/move/commit" }).output(MoveCommitReplySchema),
21238
- abort: oc2.route({ method: "POST", path: "/move/abort" }).output(MoveCommitReplySchema)
21239
- };
21240
- // packages/api/src/schemas/secret-presets.ts
21241
- var SECRET_PRESETS = {
21242
- github: {
21243
- kind: "static",
21244
- rules: [
21245
- { host: "github.com", header: "authorization", scheme: "basic", user: "x-access-token" },
21246
- { host: "api.github.com", header: "authorization", scheme: "bearer" },
21247
- { host: "uploads.github.com", header: "authorization", scheme: "bearer" }
21248
- ],
21249
- env: ["GH_TOKEN", "GITHUB_TOKEN"]
21250
- },
21251
- anthropic: {
21252
- kind: "static",
21253
- rules: [{ host: "api.anthropic.com", header: "x-api-key", scheme: "raw" }],
21254
- env: ["ANTHROPIC_API_KEY"]
21255
- },
21256
- npm: {
21257
- kind: "static",
21258
- rules: [{ host: "registry.npmjs.org", header: "authorization", scheme: "bearer" }],
21259
- env: ["NPM_TOKEN"]
21238
+ // packages/daemon/src/host-net/net/range-matcher.ts
21239
+ import { IPv4 as IPv44 } from "ipaddr.js";
21240
+ function buildRangeMatcher(ranges) {
21241
+ const ranges4 = [];
21242
+ const ranges6 = [];
21243
+ for (const range of ranges) {
21244
+ const cidr = parseCidr(range);
21245
+ if (cidr === null) {
21246
+ throw new Error(`not an address range: ${range}`);
21247
+ }
21248
+ if (cidr.address instanceof IPv44) {
21249
+ ranges4.push([cidr.address, cidr.bits]);
21250
+ } else {
21251
+ ranges6.push([cidr.address, cidr.bits]);
21252
+ }
21260
21253
  }
21261
- };
21262
- // packages/api/src/rules/has-scope.ts
21263
- function hasScope(granted, needed) {
21264
- const grantedRank = ScopeSchema.options.indexOf(granted);
21265
- const neededRank = ScopeSchema.options.indexOf(needed);
21266
- return grantedRank !== -1 && neededRank !== -1 && grantedRank >= neededRank;
21254
+ const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
21255
+ const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
21256
+ return (address) => {
21257
+ const ip = parseAddress(address);
21258
+ if (ip === null) {
21259
+ return false;
21260
+ }
21261
+ if (ip instanceof IPv44) {
21262
+ return isIn4(ip);
21263
+ }
21264
+ return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
21265
+ };
21267
21266
  }
21267
+
21268
+ // packages/daemon/src/identity/auth/parse-tailnet-rules.ts
21269
+ import * as z48 from "zod";
21270
+
21268
21271
  // packages/daemon/src/identity/auth/tailnet-identity.ts
21269
21272
  import * as z47 from "zod";
21270
21273
 
@@ -21702,6 +21705,8 @@ var EnvSchema = z53.object({
21702
21705
  IMP_EGRESS_DENY: z53.string().default(""),
21703
21706
  IMP_HOST_ADDRESSES: z53.string().default(""),
21704
21707
  IMP_RAM_BUDGET_MIB: CountSchema3.default(16384),
21708
+ IMP_PRIORITY_RAM_MIB: CountSchema3.optional(),
21709
+ IMP_PRIORITY_IMPS: z53.string().transform(splitList).pipe(z53.array(ImpPatternSchema)).optional(),
21705
21710
  IMP_IDLE_TIMEOUT_S: CountSchema3.default(60),
21706
21711
  IMP_IDLE_CPU_PERCENT: z53.coerce.number().nonnegative().default(10),
21707
21712
  IMP_MEMORY_SHRINK_AFTER_S: CountSchema3.default(60),
@@ -21866,6 +21871,7 @@ function loadConfig(env) {
21866
21871
  hostAddresses,
21867
21872
  brokerTestUpstreams: parsed.IMP_BROKER_TEST_UPSTREAMS ?? null,
21868
21873
  ramBudgetMib: parsed.IMP_RAM_BUDGET_MIB,
21874
+ priorityRam: parsePriorityRam(parsed),
21869
21875
  idleTimeoutS: parsed.IMP_IDLE_TIMEOUT_S,
21870
21876
  idleCpuPercent: parsed.IMP_IDLE_CPU_PERCENT,
21871
21877
  memoryShrinkAfterMs: parsed.IMP_MEMORY_SHRINK_AFTER_S * 1000,
@@ -21922,6 +21928,20 @@ function loadConfig(env) {
21922
21928
  warnings: listHttpsWarnings(parsed)
21923
21929
  };
21924
21930
  }
21931
+ function parsePriorityRam(parsed) {
21932
+ const mib = parsed.IMP_PRIORITY_RAM_MIB;
21933
+ const patterns = parsed.IMP_PRIORITY_IMPS;
21934
+ if (mib === undefined && patterns === undefined) {
21935
+ return null;
21936
+ }
21937
+ if (mib === undefined || patterns === undefined) {
21938
+ throw new Error("IMP_PRIORITY_RAM_MIB and IMP_PRIORITY_IMPS go together; set both or neither");
21939
+ }
21940
+ if (mib >= parsed.IMP_RAM_BUDGET_MIB) {
21941
+ throw new Error(`IMP_PRIORITY_RAM_MIB ${String(mib)} leaves no room for other imps in the ${String(parsed.IMP_RAM_BUDGET_MIB)} MiB IMP_RAM_BUDGET_MIB`);
21942
+ }
21943
+ return { mib, patterns };
21944
+ }
21925
21945
  function parseTestRegistry(parsed) {
21926
21946
  const registry = parsed.IMP_BUILD_TEST_REGISTRY;
21927
21947
  const caPath = parsed.IMP_BUILD_TEST_REGISTRY_CA;
@@ -21971,7 +21991,7 @@ import { join as join48 } from "path";
21971
21991
  // packages/daemon/package.json
21972
21992
  var package_default = {
21973
21993
  name: "@impz/daemon",
21974
- version: "6.7.0",
21994
+ version: "6.8.0",
21975
21995
  private: true,
21976
21996
  description: "impd: the imp host daemon",
21977
21997
  type: "module",
@@ -25003,7 +25023,7 @@ class RamBudgetError extends ImpdError {
25003
25023
  budgetMib: shortfall.budgetMib,
25004
25024
  usedMib: shortfall.usedMib,
25005
25025
  requestedMib: shortfall.requestedMib,
25006
- neededMib: buildNeededMib(shortfall.usedMib, shortfall.requestedMib, shortfall.budgetMib),
25026
+ neededMib: buildNeededMib(shortfall.usedMib + (shortfall.kept?.mib ?? 0), shortfall.requestedMib, shortfall.budgetMib),
25007
25027
  protected: [],
25008
25028
  protectedHidden: shortfall.protected.length
25009
25029
  }
@@ -25023,25 +25043,35 @@ function buildRamBudgetError(shortfall) {
25023
25043
  const used = String(shortfall.usedMib);
25024
25044
  const budget = String(shortfall.budgetMib);
25025
25045
  const requested = String(shortfall.requestedMib);
25026
- return new RamBudgetError(`not enough RAM: ${used} of ${budget} MiB in use, ${requested} MiB requested, and no idle imp left to sleep`, shortfall, "ram");
25046
+ const kept = formatKept(shortfall.kept);
25047
+ return new RamBudgetError(`not enough RAM: ${used} of ${budget} MiB in use${kept}, ${requested} MiB requested, and no idle imp left to sleep`, shortfall, "ram");
25027
25048
  }
25028
25049
  function buildOrdinaryBudgetError(shortfall) {
25029
25050
  const used = String(shortfall.usedMib);
25030
25051
  const budget = String(shortfall.budgetMib);
25031
25052
  const requested = String(shortfall.requestedMib);
25032
- return new RamBudgetError(`not enough RAM outside the huge page pool: ${used} of ${budget} MiB in use, ${requested} MiB requested on 4 KiB pages, and no idle imp left to sleep`, shortfall, "ordinary");
25053
+ const kept = formatKept(shortfall.kept);
25054
+ return new RamBudgetError(`not enough RAM outside the huge page pool: ${used} of ${budget} MiB in use${kept}, ${requested} MiB requested on 4 KiB pages, and no idle imp left to sleep`, shortfall, "ordinary");
25033
25055
  }
25034
25056
  function buildHugePoolError(shortfall) {
25035
25057
  const committed = String(shortfall.committedMib);
25036
25058
  const total = String(shortfall.totalMib);
25037
25059
  const requested = String(shortfall.requestedMib);
25038
- return new RamBudgetError(`not enough 2 MiB pages: ${committed} of the host's ${total} MiB pool in use or promised to awake imps, ${requested} MiB requested`, {
25060
+ const kept = formatKept(shortfall.kept);
25061
+ return new RamBudgetError(`not enough 2 MiB pages: ${committed} of the host's ${total} MiB pool in use or promised to awake imps${kept}, ${requested} MiB requested`, {
25039
25062
  budgetMib: shortfall.totalMib,
25040
25063
  usedMib: shortfall.committedMib,
25041
25064
  requestedMib: shortfall.requestedMib,
25042
- protected: []
25065
+ protected: [],
25066
+ ...shortfall.kept !== undefined && { kept: shortfall.kept }
25043
25067
  }, "pool");
25044
25068
  }
25069
+ function formatKept(kept) {
25070
+ if (kept === undefined || kept.mib === 0) {
25071
+ return "";
25072
+ }
25073
+ return ` and ${String(kept.mib)} MiB kept for priority imps (${kept.patterns.join(", ")})`;
25074
+ }
25045
25075
  function buildNeededMib(usedMib, requestedMib, budgetMib) {
25046
25076
  return Math.max(0, usedMib + requestedMib - budgetMib);
25047
25077
  }
@@ -30535,6 +30565,7 @@ async function readSystemInfo(deps) {
30535
30565
  ramUsedMib: usage.usedMib,
30536
30566
  ramReservedMib: usage.reservedMib,
30537
30567
  ramCommittedMib: running.reduce((sum, imp) => sum + imp.maxMemoryMib, 0),
30568
+ ramPriorityKeptMib: usage.priorityKeptMib,
30538
30569
  ramSleepingMib: sleeping.reduce((sum, imp) => sum + imp.memoryMib, 0),
30539
30570
  awakeCount: running.length,
30540
30571
  impCount: imps.length,
@@ -37697,6 +37728,61 @@ function buildBudgets(budgetMib, pool) {
37697
37728
  };
37698
37729
  }
37699
37730
 
37731
+ // packages/daemon/src/imp-ops/governor/build-usage-totals.ts
37732
+ function buildUsageTotals(input) {
37733
+ const reserved = new Map(input.reservations.map((reservation) => [reservation.id, reservation]));
37734
+ const byImp = new Map;
37735
+ let usedMib = input.lockedMib;
37736
+ let effectiveMib = input.lockedMib + input.headroomMib;
37737
+ let ordinaryUsedMib = input.lockedMib;
37738
+ let ordinaryEffectiveMib = input.lockedMib + input.headroomMib;
37739
+ const priority = { ramMib: 0, ordinaryMib: 0, poolMib: 0 };
37740
+ for (const [index, imp] of input.awake.entries()) {
37741
+ const reading = input.readings[index] ?? null;
37742
+ const measured = reading?.ramMib ?? 0;
37743
+ const effective = Math.max(measured, reserved.get(imp.id)?.mib ?? 0);
37744
+ const ordinary = imp.hugePagesMib > 0 ? Math.max(0, measured - (reading?.hugetlbMib ?? 0)) : effective;
37745
+ byImp.set(imp.id, effective);
37746
+ usedMib += measured;
37747
+ effectiveMib += effective;
37748
+ ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
37749
+ ordinaryEffectiveMib += ordinary;
37750
+ if (imp.isPriority) {
37751
+ priority.ramMib += effective;
37752
+ priority.ordinaryMib += ordinary;
37753
+ priority.poolMib += imp.hugePagesMib;
37754
+ }
37755
+ }
37756
+ for (const reservation of input.reservations) {
37757
+ if (!byImp.has(reservation.id)) {
37758
+ const ordinary = reservation.isOnPool ? 0 : reservation.mib;
37759
+ effectiveMib += reservation.mib;
37760
+ ordinaryEffectiveMib += ordinary;
37761
+ if (reservation.isPriority) {
37762
+ priority.ramMib += reservation.mib;
37763
+ priority.ordinaryMib += ordinary;
37764
+ }
37765
+ }
37766
+ }
37767
+ for (const pending of input.pendingPool) {
37768
+ if (pending.isPriority && !byImp.has(pending.id)) {
37769
+ priority.poolMib += pending.mib;
37770
+ }
37771
+ }
37772
+ return {
37773
+ byImp,
37774
+ usedMib,
37775
+ effectiveMib,
37776
+ ordinaryUsedMib,
37777
+ ordinaryEffectiveMib,
37778
+ kept: {
37779
+ ramMib: Math.max(0, input.priorityMib - priority.ramMib),
37780
+ ordinaryMib: Math.max(0, input.priorityMib - priority.ordinaryMib),
37781
+ poolMib: Math.max(0, input.priorityMib - priority.poolMib)
37782
+ }
37783
+ };
37784
+ }
37785
+
37700
37786
  // packages/daemon/src/imp-ops/governor/build-huge-pool-admission.ts
37701
37787
  function buildHugePoolAdmission(input) {
37702
37788
  const growthMib = input.awake.reduce((sum, guest) => sum + Math.max(0, guest.hugePagesMib - guest.heldMib), 0);
@@ -37704,21 +37790,22 @@ function buildHugePoolAdmission(input) {
37704
37790
  const totalMib = input.pool?.totalMib ?? 0;
37705
37791
  const freeMib = input.pool?.freeMib ?? 0;
37706
37792
  const promisedMib = growthMib + pendingMib;
37707
- if (promisedMib + input.requestedMib <= freeMib) {
37793
+ const wantedMib = promisedMib + input.keptMib + input.requestedMib;
37794
+ if (wantedMib <= freeMib) {
37708
37795
  return { admitted: true };
37709
37796
  }
37710
37797
  return {
37711
37798
  admitted: false,
37712
37799
  totalMib,
37713
37800
  committedMib: totalMib - freeMib + promisedMib,
37714
- neededMib: promisedMib + input.requestedMib - freeMib
37801
+ neededMib: wantedMib - freeMib
37715
37802
  };
37716
37803
  }
37717
37804
 
37718
37805
  // packages/daemon/src/imp-ops/governor/make-check-huge-pool.ts
37719
37806
  function makeCheckHugePool(deps) {
37720
37807
  const pendingHugePages = deps.pendingHugePages;
37721
- return async (request, hugePagesMib) => {
37808
+ return async (request, hugePagesMib, kept) => {
37722
37809
  const listed = await deps.listAwake();
37723
37810
  const awake = listed.filter((imp) => imp.id !== request.id);
37724
37811
  for (const imp of awake) {
@@ -37734,7 +37821,8 @@ function makeCheckHugePool(deps) {
37734
37821
  heldMib: held[index] ?? 0
37735
37822
  })),
37736
37823
  pendingMib: [...pendingHugePages].filter(([id]) => id !== request.id).map(([, mib]) => mib),
37737
- requestedMib: hugePagesMib
37824
+ requestedMib: hugePagesMib,
37825
+ keptMib: kept?.mib ?? 0
37738
37826
  });
37739
37827
  if (poolAdmission.admitted) {
37740
37828
  return;
@@ -37750,7 +37838,8 @@ function makeCheckHugePool(deps) {
37750
37838
  throw buildHugePoolError({
37751
37839
  totalMib: poolAdmission.totalMib,
37752
37840
  committedMib: poolAdmission.committedMib,
37753
- requestedMib: hugePagesMib
37841
+ requestedMib: hugePagesMib,
37842
+ ...kept !== null && { kept }
37754
37843
  });
37755
37844
  };
37756
37845
  }
@@ -37775,6 +37864,17 @@ function pickShortfall(used, budgets, requestedMib) {
37775
37864
  } : ram;
37776
37865
  }
37777
37866
 
37867
+ // packages/daemon/src/imp-ops/governor/pick-effective-shortfall.ts
37868
+ function pickEffectiveShortfall(usage, budgets, requestedMib, isPriority) {
37869
+ const kept = isPriority ? { ramMib: 0, ordinaryMib: 0 } : usage.kept;
37870
+ const shortfall = pickShortfall({
37871
+ ramMib: usage.effectiveMib + kept.ramMib,
37872
+ ordinaryMib: usage.ordinaryEffectiveMib + kept.ordinaryMib
37873
+ }, budgets, requestedMib);
37874
+ const keptMib = shortfall.budget === "ordinary" ? kept.ordinaryMib : kept.ramMib;
37875
+ return { ...shortfall, usedMib: shortfall.usedMib - keptMib, keptMib };
37876
+ }
37877
+
37778
37878
  // packages/daemon/src/imp-ops/governor/pick-sleep-victims.ts
37779
37879
  function pickSleepVictims(candidates, needMib) {
37780
37880
  if (needMib <= 0) {
@@ -37802,8 +37902,12 @@ function createRamGovernor(deps) {
37802
37902
  const admission = createPrioritySemaphore(1);
37803
37903
  const reservations = new Map;
37804
37904
  const now = deps.now ?? Date.now;
37905
+ const priority = deps.priority ?? null;
37906
+ const isPriority = (name) => priority !== null && name !== null && isImpAllowed(priority.patterns, name);
37907
+ const buildKept = (mib) => priority === null || mib === 0 ? null : { mib, patterns: priority.patterns };
37805
37908
  let stuckOver = false;
37806
37909
  const pendingHugePages = new Map;
37910
+ const admittedPriority = new Set;
37807
37911
  const emitDecision = (decision) => {
37808
37912
  deps.events?.publish({
37809
37913
  v: EVENT_VERSION,
@@ -37834,6 +37938,11 @@ function createRamGovernor(deps) {
37834
37938
  if ((request.hugePagesMib ?? 0) > 0) {
37835
37939
  pendingHugePages.set(request.id, request.hugePagesMib ?? 0);
37836
37940
  }
37941
+ if (isPriority(request.name)) {
37942
+ admittedPriority.add(request.id);
37943
+ } else {
37944
+ admittedPriority.delete(request.id);
37945
+ }
37837
37946
  emitAdmission(request, "admitted", usedMib);
37838
37947
  };
37839
37948
  const readReservation = (id) => {
@@ -37864,45 +37973,27 @@ function createRamGovernor(deps) {
37864
37973
  const readEffectiveUsage = async (excludeId, readVm = deps.readVmRam) => {
37865
37974
  const listed = await deps.listAwake();
37866
37975
  const awake = listed.filter((imp) => imp.id !== excludeId);
37867
- const byImp = new Map;
37868
37976
  const lockedMib = deps.readLockedMib?.() ?? 0;
37869
- let usedMib = lockedMib;
37870
- let effectiveMib = lockedMib;
37871
- let ordinaryUsedMib = lockedMib;
37872
- let ordinaryEffectiveMib = lockedMib;
37873
37977
  const readings = await Promise.all(awake.map((imp) => readVm(imp.pid, imp.apiSocket)));
37874
- for (const [index, imp] of awake.entries()) {
37875
- const reading = readings[index] ?? null;
37876
- const measured = reading?.ramMib ?? 0;
37877
- const effective = Math.max(measured, readReservation(imp.id));
37878
- const ordinary = imp.hugePagesMib > 0 ? Math.max(0, measured - (reading?.hugetlbMib ?? 0)) : effective;
37879
- byImp.set(imp.id, effective);
37880
- usedMib += measured;
37881
- effectiveMib += effective;
37882
- ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
37883
- ordinaryEffectiveMib += ordinary;
37884
- }
37885
- for (const id of reservations.keys()) {
37886
- if (id !== excludeId && !byImp.has(id)) {
37887
- effectiveMib += readReservation(id);
37888
- if (!pendingHugePages.has(id)) {
37889
- ordinaryEffectiveMib += readReservation(id);
37890
- }
37891
- }
37892
- }
37978
+ const live = [...reservations.keys()].filter((id) => id !== excludeId).map((id) => ({
37979
+ id,
37980
+ mib: readReservation(id),
37981
+ isPriority: admittedPriority.has(id),
37982
+ isOnPool: pendingHugePages.has(id)
37983
+ }));
37984
+ const pendingPool = [...pendingHugePages].filter(([id]) => id !== excludeId).map(([id, mib]) => ({ id, mib, isPriority: admittedPriority.has(id) }));
37893
37985
  const headroom = deps.readHeadroomMib?.(awake.map((imp) => imp.pid)) ?? Promise.resolve(0);
37894
37986
  const headroomMib = await headroom;
37895
- effectiveMib += headroomMib;
37896
- ordinaryEffectiveMib += headroomMib;
37897
- return {
37898
- awake,
37899
- byImp,
37900
- usedMib,
37901
- effectiveMib,
37902
- ordinaryUsedMib,
37903
- ordinaryEffectiveMib,
37904
- headroomMib
37905
- };
37987
+ const totals = buildUsageTotals({
37988
+ awake: awake.map((imp) => ({ ...imp, isPriority: isPriority(imp.name) })),
37989
+ readings,
37990
+ reservations: live,
37991
+ pendingPool,
37992
+ lockedMib,
37993
+ headroomMib,
37994
+ priorityMib: priority?.mib ?? 0
37995
+ });
37996
+ return { awake, headroomMib, ...totals };
37906
37997
  };
37907
37998
  const readFreeRoom = async (excludeId, findShortfall) => {
37908
37999
  const usage = await readEffectiveUsage(excludeId);
@@ -37930,7 +38021,7 @@ function createRamGovernor(deps) {
37930
38021
  const shortfall = options.findShortfall(usage);
37931
38022
  const missingMib = shortfall.missingMib;
37932
38023
  const isOrdinary = shortfall.budget === "ordinary";
37933
- const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0);
38024
+ const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0).filter((imp) => !options.sparesPriority || !isPriority(imp.name));
37934
38025
  if (missingMib <= 0) {
37935
38026
  return {
37936
38027
  fits: true,
@@ -38015,20 +38106,23 @@ function createRamGovernor(deps) {
38015
38106
  requestedMib: request.memoryMib
38016
38107
  });
38017
38108
  }
38109
+ const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
38110
+ const isPriorityRequest = isPriority(request.name);
38018
38111
  if (hugePagesMib > 0) {
38019
- await checkHugePool(request, hugePagesMib);
38112
+ const kept = isPriorityRequest ? null : buildKept(bound.kept.poolMib);
38113
+ await checkHugePool(request, hugePagesMib, kept);
38020
38114
  }
38021
- const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
38022
- if (pickEffectiveShortfall(bound, budgets, request.reserveMib).missingMib <= 0) {
38115
+ const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib, isPriorityRequest);
38116
+ if (findReserveShortfall(bound).missingMib <= 0) {
38023
38117
  recordAdmission(request, bound.effectiveMib);
38024
38118
  return;
38025
38119
  }
38026
- const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib);
38027
38120
  const room = request.maySleepImps === false ? await readFreeRoom(request.id, findReserveShortfall) : await makeRoom({
38028
38121
  excludeId: request.id,
38029
38122
  reason: `to make room for ${request.name ?? request.id}`,
38030
38123
  whenShort: "giveUp",
38031
- findShortfall: findReserveShortfall
38124
+ findShortfall: findReserveShortfall,
38125
+ sparesPriority: !isPriorityRequest
38032
38126
  });
38033
38127
  const diskFull = room.diskFull ? deps.readDiskFullError?.() ?? null : null;
38034
38128
  if (!room.fits && diskFull !== null) {
@@ -38038,8 +38132,9 @@ function createRamGovernor(deps) {
38038
38132
  const usage = await readEffectiveUsage(request.id);
38039
38133
  const shortfall = findReserveShortfall(usage);
38040
38134
  const isOrdinary = shortfall.budget === "ordinary";
38135
+ const kept = buildKept(shortfall.keptMib);
38041
38136
  emitAdmission(request, "refused", shortfall.usedMib, {
38042
- neededMib: buildNeededMib(shortfall.usedMib, request.reserveMib, shortfall.budgetMib),
38137
+ neededMib: Math.max(0, shortfall.missingMib),
38043
38138
  protectedCount: room.protected.length,
38044
38139
  ...isOrdinary && { trigger: ORDINARY_TRIGGER }
38045
38140
  });
@@ -38047,7 +38142,8 @@ function createRamGovernor(deps) {
38047
38142
  budgetMib: shortfall.budgetMib,
38048
38143
  usedMib: shortfall.usedMib,
38049
38144
  requestedMib: request.reserveMib,
38050
- protected: room.protected
38145
+ protected: room.protected,
38146
+ ...kept !== null && { kept }
38051
38147
  };
38052
38148
  throw isOrdinary ? buildOrdinaryBudgetError(refusal) : buildRamBudgetError(refusal);
38053
38149
  }
@@ -38056,6 +38152,7 @@ function createRamGovernor(deps) {
38056
38152
  release: (id) => {
38057
38153
  reservations.delete(id);
38058
38154
  pendingHugePages.delete(id);
38155
+ admittedPriority.delete(id);
38059
38156
  },
38060
38157
  admitGrow: (request) => admission.run("background", async () => {
38061
38158
  const listed = await deps.listAwake();
@@ -38063,15 +38160,17 @@ function createRamGovernor(deps) {
38063
38160
  return false;
38064
38161
  }
38065
38162
  const budgets = readBudgets();
38163
+ const findGrowShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.mib, isPriority(request.name));
38066
38164
  const room = await makeRoom({
38067
38165
  excludeId: null,
38068
38166
  reason: `to grow ${request.name}`,
38069
38167
  whenShort: "giveUp",
38070
- findShortfall: (usage) => pickEffectiveShortfall(usage, budgets, request.mib),
38071
- protectedId: request.id
38168
+ findShortfall: findGrowShortfall,
38169
+ protectedId: request.id,
38170
+ sparesPriority: !isPriority(request.name)
38072
38171
  });
38073
38172
  const usage = await readEffectiveUsage(null);
38074
- const shortfall = pickEffectiveShortfall(usage, budgets, request.mib);
38173
+ const shortfall = findGrowShortfall(usage);
38075
38174
  const fits = room.fits && usage.awake.some((imp) => imp.id === request.id);
38076
38175
  emitDecision({
38077
38176
  decision: fits ? "admitted" : "refused",
@@ -38111,7 +38210,8 @@ function createRamGovernor(deps) {
38111
38210
  return {
38112
38211
  usedMib: usage.usedMib,
38113
38212
  reservedMib: usage.effectiveMib - usage.usedMib - usage.headroomMib,
38114
- headroomMib: usage.headroomMib
38213
+ headroomMib: usage.headroomMib,
38214
+ priorityKeptMib: usage.kept.ramMib
38115
38215
  };
38116
38216
  },
38117
38217
  enforce: () => admission.run("background", async () => {
@@ -38120,7 +38220,8 @@ function createRamGovernor(deps) {
38120
38220
  excludeId: null,
38121
38221
  reason: ENFORCE_TRIGGER,
38122
38222
  whenShort: "sleepAll",
38123
- findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0)
38223
+ findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0),
38224
+ sparesPriority: false
38124
38225
  });
38125
38226
  const over = `over budget by ${String(room.missingMib)} MiB`;
38126
38227
  if (room.fits) {
@@ -38138,9 +38239,6 @@ function createRamGovernor(deps) {
38138
38239
  })
38139
38240
  };
38140
38241
  }
38141
- function pickEffectiveShortfall(usage, budgets, requestedMib) {
38142
- return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
38143
- }
38144
38242
  function toTaskPriority(request) {
38145
38243
  return request.isBackground === true ? "background" : "foreground";
38146
38244
  }
@@ -45638,6 +45736,7 @@ function createImpdImps(config, deps, parts) {
45638
45736
  });
45639
45737
  const governor = createRamGovernor({
45640
45738
  budgetMib: config.ramBudgetMib,
45739
+ priority: config.priorityRam,
45641
45740
  listAwake: async () => {
45642
45741
  const listed = await listImps(deps.db);
45643
45742
  return listed.flatMap((imp) => {