@impz/testing 6.7.0 → 6.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +809 -713
  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";
@@ -21105,166 +20670,603 @@ var authContract = {
21105
20670
  logout: contractBase.meta(ACCESS.noSession).output(EmptySchema)
21106
20671
  };
21107
20672
 
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";
20673
+ // packages/api/src/contract/session-contract.ts
20674
+ var sessionContract = { auth: authContract };
20675
+ // packages/api/src/contract/collect-procedure-access.ts
20676
+ import { isContractProcedure } from "@orpc/contract";
20677
+ import * as z45 from "zod";
20678
+
20679
+ // packages/api/src/schemas/procedure-access-schema.ts
20680
+ import * as z44 from "zod";
20681
+ var FieldsSchema = z44.array(z44.string()).readonly();
20682
+ var NoSessionAccessSchema = z44.object({ on: z44.literal("none") });
20683
+ var HostAccessSchema = z44.object({
20684
+ scope: ScopeSchema,
20685
+ on: z44.literal("host"),
20686
+ audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
20687
+ });
20688
+ var ImpAccessSchema = z44.object({
20689
+ scope: ScopeSchema,
20690
+ on: z44.literal("imp"),
20691
+ fields: FieldsSchema,
20692
+ noGrantable: z44.literal(true).optional(),
20693
+ audit: z44.literal(false).optional()
20694
+ });
20695
+ var GrantAccessSchema = z44.object({
20696
+ scope: ScopeSchema,
20697
+ on: z44.literal("grant"),
20698
+ fields: FieldsSchema,
20699
+ secretField: z44.string(),
20700
+ audit: z44.literal(false).optional()
20701
+ });
20702
+ var ImageAccessSchema = z44.object({
20703
+ scope: ScopeSchema,
20704
+ on: z44.literal("image"),
20705
+ held: z44.literal(true).optional(),
20706
+ audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
20707
+ });
20708
+ var AnyAccessSchema = z44.object({
20709
+ scope: ScopeSchema,
20710
+ on: z44.literal("any"),
20711
+ audit: z44.literal(false).optional()
20712
+ });
20713
+ var ProcedureAccessSchema = z44.discriminatedUnion("on", [
20714
+ NoSessionAccessSchema,
20715
+ HostAccessSchema,
20716
+ ImpAccessSchema,
20717
+ GrantAccessSchema,
20718
+ ImageAccessSchema,
20719
+ AnyAccessSchema
20720
+ ]);
20721
+
20722
+ // packages/api/src/contract/collect-procedure-access.ts
20723
+ var MetaSchema = z45.object({ access: ProcedureAccessSchema });
20724
+ function collectProcedureAccess(router, prefix = "") {
20725
+ return Object.entries(router).flatMap(([key, value]) => {
20726
+ const path = `${prefix}${key}`;
20727
+ if (isContractProcedure(value)) {
20728
+ const meta = value["~orpc"].meta;
20729
+ const parsed = MetaSchema.safeParse(meta);
20730
+ return [{ path, access: parsed.success ? parsed.data.access : null }];
20731
+ }
20732
+ return typeof value === "object" && value !== null ? collectProcedureAccess(value, `${path}.`) : [];
20733
+ });
20734
+ }
20735
+ // packages/api/src/rules/is-imp-allowed.ts
20736
+ function isImpAllowed(patterns, name) {
20737
+ return patterns === null || patterns.some((pattern) => isPatternMatch(pattern, name));
20738
+ }
20739
+ function isPatternMatch(pattern, name) {
20740
+ const [first = "", ...rest] = pattern.split("*");
20741
+ if (rest.length === 0) {
20742
+ return name === first;
20743
+ }
20744
+ const last = rest.pop() ?? "";
20745
+ if (!name.startsWith(first) || !name.endsWith(last) || name.length < first.length + last.length) {
20746
+ return false;
20747
+ }
20748
+ let at = first.length;
20749
+ const end = name.length - last.length;
20750
+ for (const part of rest) {
20751
+ const found = name.indexOf(part, at);
20752
+ if (found === -1 || found + part.length > end) {
20753
+ return false;
20754
+ }
20755
+ at = found + part.length;
20756
+ }
20757
+ return true;
20758
+ }
20759
+ // packages/api/src/protocols/login-shell.ts
20760
+ var CONSOLE_SHELL = buildConsoleShell("/etc/passwd", ["/bin/bash", "/bin/sh"]);
20761
+ function buildConsoleShell(passwdPath, fallbacks) {
20762
+ return [
20763
+ "uid=$(id -u 2>/dev/null) || uid=0",
20764
+ "shell=",
20765
+ "while IFS=: read -r _ _ id _ _ _ login; do",
20766
+ ' if [ "$id" = "$uid" ]; then shell=$login; break; fi',
20767
+ `done < ${passwdPath}`,
20768
+ ...fallbacks.map((fallback) => `[ -x "$shell" ] || shell=${fallback}`),
20769
+ 'exec "$shell" -l'
20770
+ ].join(`
20771
+ `);
20772
+ }
20773
+ // packages/api/src/protocols/mcp-path.ts
20774
+ var MCP_PATH = "/mcp";
20775
+ // packages/api/src/protocols/move-receive-path.ts
20776
+ var MOVE_RECEIVE_PATH = "/move/receive";
20777
+ // packages/api/src/contract/move-peer-contract.ts
20778
+ import { oc as oc2 } from "@orpc/contract";
20779
+
20780
+ // packages/api/src/schemas/move-peer-schema.ts
20781
+ import * as z46 from "zod";
20782
+ var SystemDriveSchema = z46.string().regex(/^[\da-f]{64}$/v);
20783
+ var MoveOfferSchema = z46.object({
20784
+ imageDigest: z46.string(),
20785
+ receivedImageDigest: z46.string().nullable().default(null),
20786
+ systemDrive: SystemDriveSchema.optional()
20787
+ });
20788
+ var MoveOfferReplySchema = z46.object({
20789
+ needsImage: z46.boolean(),
20790
+ needsSystemDrive: z46.boolean().default(false),
20791
+ storage: z46.enum(["xfs", "zfs"]),
20792
+ keepsMaxMemory: z46.boolean().default(false),
20793
+ keepsLeases: z46.boolean().default(false),
20794
+ keepsPublicEgress: z46.boolean().default(false),
20795
+ keepsBackupOff: z46.boolean().default(false)
20796
+ });
20797
+ var MoveCommitReplySchema = z46.object({ isCommitted: z46.boolean() });
20798
+
20799
+ // packages/api/src/contract/move-peer-contract.ts
20800
+ var movePeerContract = {
20801
+ offer: oc2.route({ method: "POST", path: "/move/offer" }).input(MoveOfferSchema).output(MoveOfferReplySchema),
20802
+ commit: oc2.route({ method: "POST", path: "/move/commit" }).output(MoveCommitReplySchema),
20803
+ abort: oc2.route({ method: "POST", path: "/move/abort" }).output(MoveCommitReplySchema)
20804
+ };
20805
+ // packages/api/src/schemas/secret-presets.ts
20806
+ var SECRET_PRESETS = {
20807
+ github: {
20808
+ kind: "static",
20809
+ rules: [
20810
+ { host: "github.com", header: "authorization", scheme: "basic", user: "x-access-token" },
20811
+ { host: "api.github.com", header: "authorization", scheme: "bearer" },
20812
+ { host: "uploads.github.com", header: "authorization", scheme: "bearer" }
20813
+ ],
20814
+ env: ["GH_TOKEN", "GITHUB_TOKEN"]
20815
+ },
20816
+ anthropic: {
20817
+ kind: "static",
20818
+ rules: [{ host: "api.anthropic.com", header: "x-api-key", scheme: "raw" }],
20819
+ env: ["ANTHROPIC_API_KEY"]
20820
+ },
20821
+ npm: {
20822
+ kind: "static",
20823
+ rules: [{ host: "registry.npmjs.org", header: "authorization", scheme: "bearer" }],
20824
+ env: ["NPM_TOKEN"]
20825
+ }
20826
+ };
20827
+ // packages/api/src/rules/has-scope.ts
20828
+ function hasScope(granted, needed) {
20829
+ const grantedRank = ScopeSchema.options.indexOf(granted);
20830
+ const neededRank = ScopeSchema.options.indexOf(needed);
20831
+ return grantedRank !== -1 && neededRank !== -1 && grantedRank >= neededRank;
20832
+ }
20833
+ // packages/daemon/src/config.ts
20834
+ import * as z53 from "zod";
20835
+ // packages/shared/src/format-bytes.ts
20836
+ var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"];
20837
+ function formatBytes(bytes) {
20838
+ let value = bytes;
20839
+ let unit = 0;
20840
+ while (value >= 1024 && unit < UNITS.length - 1) {
20841
+ value /= 1024;
20842
+ unit += 1;
20843
+ }
20844
+ const digits = unit === 0 || value >= 100 ? 0 : 1;
20845
+ return `${value.toFixed(digits)} ${UNITS[unit] ?? "B"}`;
20846
+ }
20847
+ // packages/shared/src/get-error-message.ts
20848
+ function getErrorMessage(error) {
20849
+ return error instanceof Error ? error.message : String(error);
20850
+ }
20851
+ // packages/shared/src/merge-bytes.ts
20852
+ function mergeBytes(chunks) {
20853
+ const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
20854
+ let offset = 0;
20855
+ for (const chunk of chunks) {
20856
+ merged.set(chunk, offset);
20857
+ offset += chunk.byteLength;
20858
+ }
20859
+ return merged;
20860
+ }
20861
+ // packages/shared/src/to-plain-octets.ts
20862
+ var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
20863
+ function toPlainOctets(entry) {
20864
+ const groups = DOTTED_ENTRY.exec(entry)?.groups;
20865
+ if (groups === undefined) {
20866
+ return entry;
20867
+ }
20868
+ const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
20869
+ return `${octets.join(".")}${groups["prefix"] ?? ""}`;
20870
+ }
20871
+ // packages/daemon/src/host-net/net/addressing.ts
20872
+ import { IPv4 as IPv43 } from "ipaddr.js";
20873
+
20874
+ // packages/daemon/src/host-net/net/addressing6.ts
20875
+ import { IPv6 as IPv62 } from "ipaddr.js";
20876
+
20877
+ // packages/daemon/src/host-net/net/parse-address.ts
20878
+ import { IPv4, IPv6 } from "ipaddr.js";
20879
+ function parseAddress(text) {
20880
+ if (IPv4.isValidFourPartDecimal(text)) {
20881
+ return IPv4.parse(text);
20882
+ }
20883
+ if (text.includes("%")) {
20884
+ return null;
20885
+ }
20886
+ const hex = toHexGroups(text);
20887
+ return hex !== null && IPv6.isValid(hex) ? IPv6.parse(hex) : null;
20888
+ }
20889
+ function toHexGroups(text) {
20890
+ const split = text.lastIndexOf(":");
20891
+ const tail = text.slice(split + 1);
20892
+ if (!tail.includes(".")) {
20893
+ return text;
20894
+ }
20895
+ if (!IPv4.isValidFourPartDecimal(tail)) {
20896
+ return null;
20897
+ }
20898
+ const [first = 0, second = 0, third = 0, fourth = 0] = IPv4.parse(tail).octets;
20899
+ return `${text.slice(0, split + 1)}${toGroup(first, second)}:${toGroup(third, fourth)}`;
20900
+ }
20901
+ function toGroup(high, low) {
20902
+ return (high * 256 + low).toString(16);
20903
+ }
20904
+
20905
+ // packages/daemon/src/host-net/net/parse-cidr.ts
20906
+ import { IPv4 as IPv42 } from "ipaddr.js";
20907
+ function parseCidr(text) {
20908
+ const [addressText = "", prefix, ...rest] = text.split("/");
20909
+ const address = parseAddress(addressText);
20910
+ if (address === null || rest.length > 0 || prefix !== undefined && !/^\d{1,3}$/v.test(prefix)) {
20911
+ return null;
20912
+ }
20913
+ const maxBits = address instanceof IPv42 ? 32 : 128;
20914
+ const bits = prefix === undefined ? maxBits : Number(prefix);
20915
+ return bits > maxBits ? null : { address, bits };
20916
+ }
20917
+
20918
+ // packages/daemon/src/host-net/net/addressing6.ts
20919
+ var GROUPS = 8;
20920
+ var GROUP_BITS = 16n;
20921
+ var PREFIX_LENGTH = 64;
20922
+ var GATEWAY_IP6 = "fe80::1";
20923
+ function parseIpv6(text) {
20924
+ const address = parseAddress(text);
20925
+ return address instanceof IPv62 ? toBigint(address) : null;
20926
+ }
20927
+ function formatIpv6(address) {
20928
+ const groups = Array.from({ length: GROUPS }, (_, index) => Number(address >> GROUP_BITS * BigInt(GROUPS - 1 - index) & 0xffffn));
20929
+ return new IPv62(groups).toRFC5952String();
20930
+ }
20931
+ function formatCidr6(text) {
20932
+ const parsed = parseCidr(text);
20933
+ if (parsed === null || !(parsed.address instanceof IPv62)) {
20934
+ return null;
20935
+ }
20936
+ const hostBits = BigInt(128 - parsed.bits);
20937
+ const network = toBigint(parsed.address) >> hostBits << hostBits;
20938
+ return `${formatIpv6(network)}/${String(parsed.bits)}`;
20939
+ }
20940
+ function parsePrefix64(cidr) {
20941
+ const parsed = parseCidr(cidr);
20942
+ const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
20943
+ if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
20944
+ return null;
20945
+ }
20946
+ return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
20947
+ }
20948
+ function buildUlaPrefix(random) {
20949
+ if (random.length < 5) {
20950
+ throw new Error("a ULA global ID needs 5 random bytes");
20951
+ }
20952
+ const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
20953
+ const network = (0xfdn << 40n) + globalId << 80n;
20954
+ return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
20955
+ }
20956
+ function findMappedIpv4(address) {
20957
+ const parsed = parseAddress(address);
20958
+ return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
20959
+ }
20960
+ function deriveGuestIp6(prefix, guestIpv4) {
20961
+ return formatIpv6(prefix.network + BigInt(guestIpv4));
20962
+ }
20963
+ function toBigint(address) {
20964
+ return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
20965
+ }
20966
+
20967
+ // packages/daemon/src/host-net/net/addressing.ts
20968
+ var SLOT_SIZE = 4;
20969
+ var SLOT_PREFIX_LENGTH = 30;
20970
+ function parseSubnet(cidr) {
20971
+ const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
20972
+ if (parsed === null || !(parsed.address instanceof IPv43)) {
20973
+ throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
20974
+ }
20975
+ if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
20976
+ throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
20977
+ }
20978
+ const network = toNumber(parsed.address);
20979
+ if (network % 2 ** (32 - parsed.bits) !== 0) {
20980
+ throw new Error(`subnet has host bits set: ${cidr}`);
20981
+ }
20982
+ return { network, prefixLength: parsed.bits };
20983
+ }
20984
+ var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
20985
+ var TAILNET_SIZE = 2 ** 22;
20986
+ function isTailnetOverlap(subnet) {
20987
+ const start = subnet.network;
20988
+ const end = start + 2 ** (32 - subnet.prefixLength);
20989
+ return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
20990
+ }
20991
+ function countSlots(subnet) {
20992
+ return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
20993
+ }
20994
+ function deriveSlotAddress(slot, plan) {
20995
+ if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
20996
+ throw new RangeError(`slot ${String(slot)} is outside the subnet`);
20997
+ }
20998
+ const base = plan.subnet.network + slot * SLOT_SIZE;
20999
+ const guest = base + 2;
21000
+ return {
21001
+ slot,
21002
+ tap: `imp${String(slot)}`,
21003
+ hostIp: formatIpv4(base + 1),
21004
+ guestIp: formatIpv4(guest),
21005
+ prefixLength: SLOT_PREFIX_LENGTH,
21006
+ netmask: "255.255.255.252",
21007
+ guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
21008
+ hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
21009
+ guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
21010
+ tailnetPort: plan.portBase + slot
21011
+ };
21012
+ }
21013
+ function parseIpv4(text, allowMapped = false) {
21014
+ const address = parseAddress(text);
21015
+ if (address instanceof IPv43) {
21016
+ return toNumber(address);
21017
+ }
21018
+ return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
21019
+ }
21020
+ function findPeerSlot(peer, local, subnet) {
21021
+ const peerIp = parseIpv4(peer, true);
21022
+ const localIp = parseIpv4(local, true);
21023
+ if (peerIp === null || localIp === null) {
21024
+ return null;
21025
+ }
21026
+ const offset = peerIp - subnet.network;
21027
+ if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
21028
+ return null;
21029
+ }
21030
+ return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
21031
+ }
21032
+ function findGuestSlot(address, subnet) {
21033
+ const ip = parseIpv4(address, true);
21034
+ const offset = ip === null ? -1 : ip - subnet.network;
21035
+ if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
21036
+ return null;
21037
+ }
21038
+ return (offset - 2) / SLOT_SIZE;
21039
+ }
21040
+ function formatSubnet(subnet) {
21041
+ return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
21042
+ }
21043
+ function formatCidr4(text) {
21044
+ const parsed = parseCidr(toPlainOctets(text));
21045
+ if (parsed === null || !(parsed.address instanceof IPv43)) {
21046
+ return null;
21047
+ }
21048
+ const ip = toNumber(parsed.address);
21049
+ return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
21050
+ }
21051
+ function toNumber(address) {
21052
+ return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
21053
+ }
21054
+ function formatIpv4(address) {
21055
+ return splitOctets(address).join(".");
21056
+ }
21057
+ function splitOctets(address) {
21058
+ return [
21059
+ Math.floor(address / 2 ** 24) % 256,
21060
+ Math.floor(address / 2 ** 16) % 256,
21061
+ Math.floor(address / 2 ** 8) % 256,
21062
+ address % 256
21063
+ ];
21064
+ }
21065
+ function toHexByte(octet) {
21066
+ return octet.toString(16).padStart(2, "0");
21067
+ }
21113
21068
 
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
- ]);
21069
+ // packages/daemon/src/host-net/net/ipv6-plan.ts
21070
+ import { randomBytes } from "crypto";
21071
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
21072
+ import { dirname, join as join2 } from "path";
21156
21073
 
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}.`) : [];
21074
+ // packages/daemon/src/platform/process/get-impd-logger.ts
21075
+ import { getLogger } from "@logtape/logtape";
21076
+ function getImpdLogger(area) {
21077
+ return getLogger(["impd", area]);
21078
+ }
21079
+
21080
+ // packages/daemon/src/platform/process/run-command.ts
21081
+ async function runCommand(argv, options = {}) {
21082
+ const child = Bun.spawn([...argv], {
21083
+ stdin: "ignore",
21084
+ stdout: "pipe",
21085
+ stderr: "pipe",
21086
+ env: options.env === undefined ? process.env : { ...options.env },
21087
+ ...options.signal !== undefined && { signal: options.signal }
21168
21088
  });
21089
+ const [stdout, stderr, exitCode] = await Promise.all([
21090
+ new Response(child.stdout).text(),
21091
+ new Response(child.stderr).text(),
21092
+ child.exited
21093
+ ]);
21094
+ return { exitCode, stdout, stderr };
21095
+ }
21096
+ async function runChecked(argv, options = {}, run = runCommand) {
21097
+ const result = await run(argv, options);
21098
+ if (result.exitCode !== 0) {
21099
+ throw buildExitError(argv, result);
21100
+ }
21101
+ return result.stdout;
21102
+ }
21103
+ function buildExitError(argv, result) {
21104
+ const output = result.stderr.trim() || (result.stdout ?? "").trim();
21105
+ return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
21106
+ }
21107
+
21108
+ // packages/daemon/src/host-net/net/nat66-ruleset.ts
21109
+ var NAT66_TABLE = "ip6 imp_nat66";
21110
+ function buildNat66Ruleset(prefix, uplink) {
21111
+ return [
21112
+ `table ${NAT66_TABLE} {}`,
21113
+ `delete table ${NAT66_TABLE}`,
21114
+ `table ${NAT66_TABLE} {`,
21115
+ " chain postrouting {",
21116
+ " type nat hook postrouting priority srcnat; policy accept;",
21117
+ ` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
21118
+ " }",
21119
+ "}",
21120
+ ""
21121
+ ].join(`
21122
+ `);
21123
+ }
21124
+ function buildNat66Removal() {
21125
+ return `table ${NAT66_TABLE} {}
21126
+ delete table ${NAT66_TABLE}
21127
+ `;
21169
21128
  }
21170
- // packages/api/src/rules/is-imp-allowed.ts
21171
- function isImpAllowed(patterns, name) {
21172
- return patterns === null || patterns.some((pattern) => isPatternMatch(pattern, name));
21129
+
21130
+ // packages/daemon/src/host-net/net/ipv6-plan.ts
21131
+ function parseIpv6Setting(text) {
21132
+ if (text === "auto" || text === "off") {
21133
+ return { kind: text };
21134
+ }
21135
+ const prefix = parsePrefix64(text);
21136
+ if (prefix === null) {
21137
+ throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
21138
+ }
21139
+ return { kind: "routed", prefix };
21173
21140
  }
21174
- function isPatternMatch(pattern, name) {
21175
- const [first = "", ...rest] = pattern.split("*");
21176
- if (rest.length === 0) {
21177
- return name === first;
21141
+ async function resolveIpv6Plan(setting, deps) {
21142
+ const stopIpv6 = async (reason) => {
21143
+ await removeNat66(deps);
21144
+ getImpdLogger("ipv6").info("off ({reason})", { reason });
21145
+ return null;
21146
+ };
21147
+ if (setting.kind === "off") {
21148
+ return stopIpv6("IMP_SUBNET6=off");
21178
21149
  }
21179
- const last = rest.pop() ?? "";
21180
- if (!name.startsWith(first) || !name.endsWith(last) || name.length < first.length + last.length) {
21181
- return false;
21150
+ const uplink = await deps.readDefaultRoute();
21151
+ if (setting.kind === "auto" && uplink === null) {
21152
+ return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
21182
21153
  }
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;
21154
+ const missing = await deps.checkHostRules();
21155
+ if (missing !== null) {
21156
+ return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
21157
+ }
21158
+ if (setting.kind === "routed") {
21159
+ await removeNat66(deps);
21160
+ getImpdLogger("ipv6").info("{text}, routed{route}", {
21161
+ text: setting.prefix.text,
21162
+ route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
21163
+ });
21164
+ return { prefix: setting.prefix, nat66: false, uplink };
21165
+ }
21166
+ const prefix = deps.readUlaPrefix();
21167
+ try {
21168
+ await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
21169
+ } catch (error) {
21170
+ return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
21171
+ }
21172
+ getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
21173
+ text: prefix.text,
21174
+ uplink: uplink ?? ""
21175
+ });
21176
+ return { prefix, nat66: true, uplink };
21177
+ }
21178
+ async function removeNat66(deps) {
21179
+ try {
21180
+ await deps.runNft(buildNat66Removal());
21181
+ } catch {}
21182
+ }
21183
+ var HOST_RULES6 = [
21184
+ ["filter", "-A INPUT -i imp+ -j DROP"],
21185
+ ["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
21186
+ ["filter", "-A FORWARD -o imp+ -j DROP"],
21187
+ ["filter", "-A FORWARD -i imp+ -j DROP"],
21188
+ ["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
21189
+ ];
21190
+ var HOST_SYSCTLS6 = [
21191
+ ["default/accept_ra", "0"],
21192
+ ["default/accept_redirects", "0"],
21193
+ ["all/forwarding", "1"]
21194
+ ];
21195
+ async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
21196
+ for (const table of ["filter", "raw"]) {
21197
+ const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
21198
+ const rules = new Set(result.stdout.split(`
21199
+ `).map((line) => line.trim()));
21200
+ const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
21201
+ if (result.exitCode !== 0) {
21202
+ return `ip6tables -t ${table}: ${result.stderr.trim()}`;
21203
+ }
21204
+ if (missing !== undefined) {
21205
+ return `no ip6tables -t ${table} ${missing[1]}`;
21189
21206
  }
21190
- at = found + part.length;
21191
21207
  }
21192
- return true;
21208
+ for (const [key, wanted] of HOST_SYSCTLS6) {
21209
+ const value = readSysctl(join2(procSys, key));
21210
+ if (value !== wanted) {
21211
+ return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
21212
+ }
21213
+ }
21214
+ return null;
21193
21215
  }
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(`
21216
+ function readSysctl(path) {
21217
+ try {
21218
+ return readFileSync2(path, "utf8").trim();
21219
+ } catch {
21220
+ return null;
21221
+ }
21222
+ }
21223
+ function readOrCreateUlaPrefix(path) {
21224
+ if (existsSync2(path)) {
21225
+ const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
21226
+ if (prefix !== null) {
21227
+ return prefix;
21228
+ }
21229
+ }
21230
+ const prefix = buildUlaPrefix(randomBytes(5));
21231
+ mkdirSync2(dirname(path), { recursive: true });
21232
+ writeFileSync(path, `${prefix.text}
21206
21233
  `);
21234
+ return prefix;
21207
21235
  }
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
21236
 
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"]
21237
+ // packages/daemon/src/host-net/net/range-matcher.ts
21238
+ import { IPv4 as IPv44 } from "ipaddr.js";
21239
+ function buildRangeMatcher(ranges) {
21240
+ const ranges4 = [];
21241
+ const ranges6 = [];
21242
+ for (const range of ranges) {
21243
+ const cidr = parseCidr(range);
21244
+ if (cidr === null) {
21245
+ throw new Error(`not an address range: ${range}`);
21246
+ }
21247
+ if (cidr.address instanceof IPv44) {
21248
+ ranges4.push([cidr.address, cidr.bits]);
21249
+ } else {
21250
+ ranges6.push([cidr.address, cidr.bits]);
21251
+ }
21260
21252
  }
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;
21253
+ const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
21254
+ const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
21255
+ return (address) => {
21256
+ const ip = parseAddress(address);
21257
+ if (ip === null) {
21258
+ return false;
21259
+ }
21260
+ if (ip instanceof IPv44) {
21261
+ return isIn4(ip);
21262
+ }
21263
+ return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
21264
+ };
21267
21265
  }
21266
+
21267
+ // packages/daemon/src/identity/auth/parse-tailnet-rules.ts
21268
+ import * as z48 from "zod";
21269
+
21268
21270
  // packages/daemon/src/identity/auth/tailnet-identity.ts
21269
21271
  import * as z47 from "zod";
21270
21272
 
@@ -21702,6 +21704,8 @@ var EnvSchema = z53.object({
21702
21704
  IMP_EGRESS_DENY: z53.string().default(""),
21703
21705
  IMP_HOST_ADDRESSES: z53.string().default(""),
21704
21706
  IMP_RAM_BUDGET_MIB: CountSchema3.default(16384),
21707
+ IMP_PRIORITY_RAM_MIB: CountSchema3.optional(),
21708
+ IMP_PRIORITY_IMPS: z53.string().transform(splitList).pipe(z53.array(ImpPatternSchema)).optional(),
21705
21709
  IMP_IDLE_TIMEOUT_S: CountSchema3.default(60),
21706
21710
  IMP_IDLE_CPU_PERCENT: z53.coerce.number().nonnegative().default(10),
21707
21711
  IMP_MEMORY_SHRINK_AFTER_S: CountSchema3.default(60),
@@ -21866,6 +21870,7 @@ function loadConfig(env) {
21866
21870
  hostAddresses,
21867
21871
  brokerTestUpstreams: parsed.IMP_BROKER_TEST_UPSTREAMS ?? null,
21868
21872
  ramBudgetMib: parsed.IMP_RAM_BUDGET_MIB,
21873
+ priorityRam: parsePriorityRam(parsed),
21869
21874
  idleTimeoutS: parsed.IMP_IDLE_TIMEOUT_S,
21870
21875
  idleCpuPercent: parsed.IMP_IDLE_CPU_PERCENT,
21871
21876
  memoryShrinkAfterMs: parsed.IMP_MEMORY_SHRINK_AFTER_S * 1000,
@@ -21922,6 +21927,20 @@ function loadConfig(env) {
21922
21927
  warnings: listHttpsWarnings(parsed)
21923
21928
  };
21924
21929
  }
21930
+ function parsePriorityRam(parsed) {
21931
+ const mib = parsed.IMP_PRIORITY_RAM_MIB;
21932
+ const patterns = parsed.IMP_PRIORITY_IMPS;
21933
+ if (mib === undefined && patterns === undefined) {
21934
+ return null;
21935
+ }
21936
+ if (mib === undefined || patterns === undefined) {
21937
+ throw new Error("IMP_PRIORITY_RAM_MIB and IMP_PRIORITY_IMPS go together; set both or neither");
21938
+ }
21939
+ if (mib >= parsed.IMP_RAM_BUDGET_MIB) {
21940
+ 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`);
21941
+ }
21942
+ return { mib, patterns };
21943
+ }
21925
21944
  function parseTestRegistry(parsed) {
21926
21945
  const registry = parsed.IMP_BUILD_TEST_REGISTRY;
21927
21946
  const caPath = parsed.IMP_BUILD_TEST_REGISTRY_CA;
@@ -21971,7 +21990,7 @@ import { join as join48 } from "path";
21971
21990
  // packages/daemon/package.json
21972
21991
  var package_default = {
21973
21992
  name: "@impz/daemon",
21974
- version: "6.7.0",
21993
+ version: "6.7.1",
21975
21994
  private: true,
21976
21995
  description: "impd: the imp host daemon",
21977
21996
  type: "module",
@@ -25003,7 +25022,7 @@ class RamBudgetError extends ImpdError {
25003
25022
  budgetMib: shortfall.budgetMib,
25004
25023
  usedMib: shortfall.usedMib,
25005
25024
  requestedMib: shortfall.requestedMib,
25006
- neededMib: buildNeededMib(shortfall.usedMib, shortfall.requestedMib, shortfall.budgetMib),
25025
+ neededMib: buildNeededMib(shortfall.usedMib + (shortfall.kept?.mib ?? 0), shortfall.requestedMib, shortfall.budgetMib),
25007
25026
  protected: [],
25008
25027
  protectedHidden: shortfall.protected.length
25009
25028
  }
@@ -25023,25 +25042,35 @@ function buildRamBudgetError(shortfall) {
25023
25042
  const used = String(shortfall.usedMib);
25024
25043
  const budget = String(shortfall.budgetMib);
25025
25044
  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");
25045
+ const kept = formatKept(shortfall.kept);
25046
+ 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
25047
  }
25028
25048
  function buildOrdinaryBudgetError(shortfall) {
25029
25049
  const used = String(shortfall.usedMib);
25030
25050
  const budget = String(shortfall.budgetMib);
25031
25051
  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");
25052
+ const kept = formatKept(shortfall.kept);
25053
+ 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
25054
  }
25034
25055
  function buildHugePoolError(shortfall) {
25035
25056
  const committed = String(shortfall.committedMib);
25036
25057
  const total = String(shortfall.totalMib);
25037
25058
  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`, {
25059
+ const kept = formatKept(shortfall.kept);
25060
+ 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
25061
  budgetMib: shortfall.totalMib,
25040
25062
  usedMib: shortfall.committedMib,
25041
25063
  requestedMib: shortfall.requestedMib,
25042
- protected: []
25064
+ protected: [],
25065
+ ...shortfall.kept !== undefined && { kept: shortfall.kept }
25043
25066
  }, "pool");
25044
25067
  }
25068
+ function formatKept(kept) {
25069
+ if (kept === undefined || kept.mib === 0) {
25070
+ return "";
25071
+ }
25072
+ return ` and ${String(kept.mib)} MiB kept for priority imps (${kept.patterns.join(", ")})`;
25073
+ }
25045
25074
  function buildNeededMib(usedMib, requestedMib, budgetMib) {
25046
25075
  return Math.max(0, usedMib + requestedMib - budgetMib);
25047
25076
  }
@@ -37697,6 +37726,61 @@ function buildBudgets(budgetMib, pool) {
37697
37726
  };
37698
37727
  }
37699
37728
 
37729
+ // packages/daemon/src/imp-ops/governor/build-usage-totals.ts
37730
+ function buildUsageTotals(input) {
37731
+ const reserved = new Map(input.reservations.map((reservation) => [reservation.id, reservation]));
37732
+ const byImp = new Map;
37733
+ let usedMib = input.lockedMib;
37734
+ let effectiveMib = input.lockedMib + input.headroomMib;
37735
+ let ordinaryUsedMib = input.lockedMib;
37736
+ let ordinaryEffectiveMib = input.lockedMib + input.headroomMib;
37737
+ const priority = { ramMib: 0, ordinaryMib: 0, poolMib: 0 };
37738
+ for (const [index, imp] of input.awake.entries()) {
37739
+ const reading = input.readings[index] ?? null;
37740
+ const measured = reading?.ramMib ?? 0;
37741
+ const effective = Math.max(measured, reserved.get(imp.id)?.mib ?? 0);
37742
+ const ordinary = imp.hugePagesMib > 0 ? Math.max(0, measured - (reading?.hugetlbMib ?? 0)) : effective;
37743
+ byImp.set(imp.id, effective);
37744
+ usedMib += measured;
37745
+ effectiveMib += effective;
37746
+ ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
37747
+ ordinaryEffectiveMib += ordinary;
37748
+ if (imp.isPriority) {
37749
+ priority.ramMib += effective;
37750
+ priority.ordinaryMib += ordinary;
37751
+ priority.poolMib += imp.hugePagesMib;
37752
+ }
37753
+ }
37754
+ for (const reservation of input.reservations) {
37755
+ if (!byImp.has(reservation.id)) {
37756
+ const ordinary = reservation.isOnPool ? 0 : reservation.mib;
37757
+ effectiveMib += reservation.mib;
37758
+ ordinaryEffectiveMib += ordinary;
37759
+ if (reservation.isPriority) {
37760
+ priority.ramMib += reservation.mib;
37761
+ priority.ordinaryMib += ordinary;
37762
+ }
37763
+ }
37764
+ }
37765
+ for (const pending of input.pendingPool) {
37766
+ if (pending.isPriority && !byImp.has(pending.id)) {
37767
+ priority.poolMib += pending.mib;
37768
+ }
37769
+ }
37770
+ return {
37771
+ byImp,
37772
+ usedMib,
37773
+ effectiveMib,
37774
+ ordinaryUsedMib,
37775
+ ordinaryEffectiveMib,
37776
+ kept: {
37777
+ ramMib: Math.max(0, input.priorityMib - priority.ramMib),
37778
+ ordinaryMib: Math.max(0, input.priorityMib - priority.ordinaryMib),
37779
+ poolMib: Math.max(0, input.priorityMib - priority.poolMib)
37780
+ }
37781
+ };
37782
+ }
37783
+
37700
37784
  // packages/daemon/src/imp-ops/governor/build-huge-pool-admission.ts
37701
37785
  function buildHugePoolAdmission(input) {
37702
37786
  const growthMib = input.awake.reduce((sum, guest) => sum + Math.max(0, guest.hugePagesMib - guest.heldMib), 0);
@@ -37704,21 +37788,22 @@ function buildHugePoolAdmission(input) {
37704
37788
  const totalMib = input.pool?.totalMib ?? 0;
37705
37789
  const freeMib = input.pool?.freeMib ?? 0;
37706
37790
  const promisedMib = growthMib + pendingMib;
37707
- if (promisedMib + input.requestedMib <= freeMib) {
37791
+ const wantedMib = promisedMib + input.keptMib + input.requestedMib;
37792
+ if (wantedMib <= freeMib) {
37708
37793
  return { admitted: true };
37709
37794
  }
37710
37795
  return {
37711
37796
  admitted: false,
37712
37797
  totalMib,
37713
37798
  committedMib: totalMib - freeMib + promisedMib,
37714
- neededMib: promisedMib + input.requestedMib - freeMib
37799
+ neededMib: wantedMib - freeMib
37715
37800
  };
37716
37801
  }
37717
37802
 
37718
37803
  // packages/daemon/src/imp-ops/governor/make-check-huge-pool.ts
37719
37804
  function makeCheckHugePool(deps) {
37720
37805
  const pendingHugePages = deps.pendingHugePages;
37721
- return async (request, hugePagesMib) => {
37806
+ return async (request, hugePagesMib, kept) => {
37722
37807
  const listed = await deps.listAwake();
37723
37808
  const awake = listed.filter((imp) => imp.id !== request.id);
37724
37809
  for (const imp of awake) {
@@ -37734,7 +37819,8 @@ function makeCheckHugePool(deps) {
37734
37819
  heldMib: held[index] ?? 0
37735
37820
  })),
37736
37821
  pendingMib: [...pendingHugePages].filter(([id]) => id !== request.id).map(([, mib]) => mib),
37737
- requestedMib: hugePagesMib
37822
+ requestedMib: hugePagesMib,
37823
+ keptMib: kept?.mib ?? 0
37738
37824
  });
37739
37825
  if (poolAdmission.admitted) {
37740
37826
  return;
@@ -37750,7 +37836,8 @@ function makeCheckHugePool(deps) {
37750
37836
  throw buildHugePoolError({
37751
37837
  totalMib: poolAdmission.totalMib,
37752
37838
  committedMib: poolAdmission.committedMib,
37753
- requestedMib: hugePagesMib
37839
+ requestedMib: hugePagesMib,
37840
+ ...kept !== null && { kept }
37754
37841
  });
37755
37842
  };
37756
37843
  }
@@ -37775,6 +37862,17 @@ function pickShortfall(used, budgets, requestedMib) {
37775
37862
  } : ram;
37776
37863
  }
37777
37864
 
37865
+ // packages/daemon/src/imp-ops/governor/pick-effective-shortfall.ts
37866
+ function pickEffectiveShortfall(usage, budgets, requestedMib, isPriority) {
37867
+ const kept = isPriority ? { ramMib: 0, ordinaryMib: 0 } : usage.kept;
37868
+ const shortfall = pickShortfall({
37869
+ ramMib: usage.effectiveMib + kept.ramMib,
37870
+ ordinaryMib: usage.ordinaryEffectiveMib + kept.ordinaryMib
37871
+ }, budgets, requestedMib);
37872
+ const keptMib = shortfall.budget === "ordinary" ? kept.ordinaryMib : kept.ramMib;
37873
+ return { ...shortfall, usedMib: shortfall.usedMib - keptMib, keptMib };
37874
+ }
37875
+
37778
37876
  // packages/daemon/src/imp-ops/governor/pick-sleep-victims.ts
37779
37877
  function pickSleepVictims(candidates, needMib) {
37780
37878
  if (needMib <= 0) {
@@ -37802,8 +37900,12 @@ function createRamGovernor(deps) {
37802
37900
  const admission = createPrioritySemaphore(1);
37803
37901
  const reservations = new Map;
37804
37902
  const now = deps.now ?? Date.now;
37903
+ const priority = deps.priority ?? null;
37904
+ const isPriority = (name) => priority !== null && name !== null && isImpAllowed(priority.patterns, name);
37905
+ const buildKept = (mib) => priority === null || mib === 0 ? null : { mib, patterns: priority.patterns };
37805
37906
  let stuckOver = false;
37806
37907
  const pendingHugePages = new Map;
37908
+ const admittedPriority = new Set;
37807
37909
  const emitDecision = (decision) => {
37808
37910
  deps.events?.publish({
37809
37911
  v: EVENT_VERSION,
@@ -37834,6 +37936,11 @@ function createRamGovernor(deps) {
37834
37936
  if ((request.hugePagesMib ?? 0) > 0) {
37835
37937
  pendingHugePages.set(request.id, request.hugePagesMib ?? 0);
37836
37938
  }
37939
+ if (isPriority(request.name)) {
37940
+ admittedPriority.add(request.id);
37941
+ } else {
37942
+ admittedPriority.delete(request.id);
37943
+ }
37837
37944
  emitAdmission(request, "admitted", usedMib);
37838
37945
  };
37839
37946
  const readReservation = (id) => {
@@ -37864,45 +37971,27 @@ function createRamGovernor(deps) {
37864
37971
  const readEffectiveUsage = async (excludeId, readVm = deps.readVmRam) => {
37865
37972
  const listed = await deps.listAwake();
37866
37973
  const awake = listed.filter((imp) => imp.id !== excludeId);
37867
- const byImp = new Map;
37868
37974
  const lockedMib = deps.readLockedMib?.() ?? 0;
37869
- let usedMib = lockedMib;
37870
- let effectiveMib = lockedMib;
37871
- let ordinaryUsedMib = lockedMib;
37872
- let ordinaryEffectiveMib = lockedMib;
37873
37975
  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
- }
37976
+ const live = [...reservations.keys()].filter((id) => id !== excludeId).map((id) => ({
37977
+ id,
37978
+ mib: readReservation(id),
37979
+ isPriority: admittedPriority.has(id),
37980
+ isOnPool: pendingHugePages.has(id)
37981
+ }));
37982
+ const pendingPool = [...pendingHugePages].filter(([id]) => id !== excludeId).map(([id, mib]) => ({ id, mib, isPriority: admittedPriority.has(id) }));
37893
37983
  const headroom = deps.readHeadroomMib?.(awake.map((imp) => imp.pid)) ?? Promise.resolve(0);
37894
37984
  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
- };
37985
+ const totals = buildUsageTotals({
37986
+ awake: awake.map((imp) => ({ ...imp, isPriority: isPriority(imp.name) })),
37987
+ readings,
37988
+ reservations: live,
37989
+ pendingPool,
37990
+ lockedMib,
37991
+ headroomMib,
37992
+ priorityMib: priority?.mib ?? 0
37993
+ });
37994
+ return { awake, headroomMib, ...totals };
37906
37995
  };
37907
37996
  const readFreeRoom = async (excludeId, findShortfall) => {
37908
37997
  const usage = await readEffectiveUsage(excludeId);
@@ -37930,7 +38019,7 @@ function createRamGovernor(deps) {
37930
38019
  const shortfall = options.findShortfall(usage);
37931
38020
  const missingMib = shortfall.missingMib;
37932
38021
  const isOrdinary = shortfall.budget === "ordinary";
37933
- const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0);
38022
+ const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0).filter((imp) => !options.sparesPriority || !isPriority(imp.name));
37934
38023
  if (missingMib <= 0) {
37935
38024
  return {
37936
38025
  fits: true,
@@ -38015,20 +38104,23 @@ function createRamGovernor(deps) {
38015
38104
  requestedMib: request.memoryMib
38016
38105
  });
38017
38106
  }
38107
+ const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
38108
+ const isPriorityRequest = isPriority(request.name);
38018
38109
  if (hugePagesMib > 0) {
38019
- await checkHugePool(request, hugePagesMib);
38110
+ const kept = isPriorityRequest ? null : buildKept(bound.kept.poolMib);
38111
+ await checkHugePool(request, hugePagesMib, kept);
38020
38112
  }
38021
- const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
38022
- if (pickEffectiveShortfall(bound, budgets, request.reserveMib).missingMib <= 0) {
38113
+ const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib, isPriorityRequest);
38114
+ if (findReserveShortfall(bound).missingMib <= 0) {
38023
38115
  recordAdmission(request, bound.effectiveMib);
38024
38116
  return;
38025
38117
  }
38026
- const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib);
38027
38118
  const room = request.maySleepImps === false ? await readFreeRoom(request.id, findReserveShortfall) : await makeRoom({
38028
38119
  excludeId: request.id,
38029
38120
  reason: `to make room for ${request.name ?? request.id}`,
38030
38121
  whenShort: "giveUp",
38031
- findShortfall: findReserveShortfall
38122
+ findShortfall: findReserveShortfall,
38123
+ sparesPriority: !isPriorityRequest
38032
38124
  });
38033
38125
  const diskFull = room.diskFull ? deps.readDiskFullError?.() ?? null : null;
38034
38126
  if (!room.fits && diskFull !== null) {
@@ -38038,8 +38130,9 @@ function createRamGovernor(deps) {
38038
38130
  const usage = await readEffectiveUsage(request.id);
38039
38131
  const shortfall = findReserveShortfall(usage);
38040
38132
  const isOrdinary = shortfall.budget === "ordinary";
38133
+ const kept = buildKept(shortfall.keptMib);
38041
38134
  emitAdmission(request, "refused", shortfall.usedMib, {
38042
- neededMib: buildNeededMib(shortfall.usedMib, request.reserveMib, shortfall.budgetMib),
38135
+ neededMib: Math.max(0, shortfall.missingMib),
38043
38136
  protectedCount: room.protected.length,
38044
38137
  ...isOrdinary && { trigger: ORDINARY_TRIGGER }
38045
38138
  });
@@ -38047,7 +38140,8 @@ function createRamGovernor(deps) {
38047
38140
  budgetMib: shortfall.budgetMib,
38048
38141
  usedMib: shortfall.usedMib,
38049
38142
  requestedMib: request.reserveMib,
38050
- protected: room.protected
38143
+ protected: room.protected,
38144
+ ...kept !== null && { kept }
38051
38145
  };
38052
38146
  throw isOrdinary ? buildOrdinaryBudgetError(refusal) : buildRamBudgetError(refusal);
38053
38147
  }
@@ -38056,6 +38150,7 @@ function createRamGovernor(deps) {
38056
38150
  release: (id) => {
38057
38151
  reservations.delete(id);
38058
38152
  pendingHugePages.delete(id);
38153
+ admittedPriority.delete(id);
38059
38154
  },
38060
38155
  admitGrow: (request) => admission.run("background", async () => {
38061
38156
  const listed = await deps.listAwake();
@@ -38063,15 +38158,17 @@ function createRamGovernor(deps) {
38063
38158
  return false;
38064
38159
  }
38065
38160
  const budgets = readBudgets();
38161
+ const findGrowShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.mib, isPriority(request.name));
38066
38162
  const room = await makeRoom({
38067
38163
  excludeId: null,
38068
38164
  reason: `to grow ${request.name}`,
38069
38165
  whenShort: "giveUp",
38070
- findShortfall: (usage) => pickEffectiveShortfall(usage, budgets, request.mib),
38071
- protectedId: request.id
38166
+ findShortfall: findGrowShortfall,
38167
+ protectedId: request.id,
38168
+ sparesPriority: !isPriority(request.name)
38072
38169
  });
38073
38170
  const usage = await readEffectiveUsage(null);
38074
- const shortfall = pickEffectiveShortfall(usage, budgets, request.mib);
38171
+ const shortfall = findGrowShortfall(usage);
38075
38172
  const fits = room.fits && usage.awake.some((imp) => imp.id === request.id);
38076
38173
  emitDecision({
38077
38174
  decision: fits ? "admitted" : "refused",
@@ -38120,7 +38217,8 @@ function createRamGovernor(deps) {
38120
38217
  excludeId: null,
38121
38218
  reason: ENFORCE_TRIGGER,
38122
38219
  whenShort: "sleepAll",
38123
- findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0)
38220
+ findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0),
38221
+ sparesPriority: false
38124
38222
  });
38125
38223
  const over = `over budget by ${String(room.missingMib)} MiB`;
38126
38224
  if (room.fits) {
@@ -38138,9 +38236,6 @@ function createRamGovernor(deps) {
38138
38236
  })
38139
38237
  };
38140
38238
  }
38141
- function pickEffectiveShortfall(usage, budgets, requestedMib) {
38142
- return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
38143
- }
38144
38239
  function toTaskPriority(request) {
38145
38240
  return request.isBackground === true ? "background" : "foreground";
38146
38241
  }
@@ -45638,6 +45733,7 @@ function createImpdImps(config, deps, parts) {
45638
45733
  });
45639
45734
  const governor = createRamGovernor({
45640
45735
  budgetMib: config.ramBudgetMib,
45736
+ priority: config.priorityRam,
45641
45737
  listAwake: async () => {
45642
45738
  const listed = await listImps(deps.db);
45643
45739
  return listed.flatMap((imp) => {