@forgezero/agent 0.1.134 → 0.1.136

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.
@@ -56,6 +56,10 @@ function guestUnit(spec) {
56
56
  `ExecStopPost=-/usr/sbin/ip link del ${spec.tap}`
57
57
  ].join(`
58
58
  `) : "";
59
+ const encryptedDisk = spec.diskEncryption ? `LoadCredentialEncrypted=${spec.diskEncryption.credentialName}:${spec.diskEncryption.credentialPath}
60
+ ExecStartPre=/usr/sbin/cryptsetup open --type luks2 --key-file %d/${spec.diskEncryption.credentialName} ${spec.diskEncryption.source} ${spec.diskEncryption.mapper}
61
+ ExecStopPost=-/usr/sbin/cryptsetup close ${spec.diskEncryption.mapper}
62
+ ` : "";
59
63
  return `[Unit]
60
64
  Description=ForgeZero guest ${spec.name}
61
65
  Documentation=https://www.forgezero.net
@@ -65,7 +69,7 @@ Wants=network-online.target
65
69
  [Service]
66
70
  Type=simple
67
71
  Slice=forgezero-guests.slice
68
- ${tapSetup}
72
+ ${encryptedDisk}${tapSetup}
69
73
  ExecStart=${argv}
70
74
  Restart=always
71
75
  RestartSec=5
@@ -754,8 +758,9 @@ function assertSupportedGuestImage(imageKey, confidential = false) {
754
758
  }
755
759
 
756
760
  // src/metal-provision.ts
757
- import { createHash as createHash2 } from "node:crypto";
761
+ import { createHash as createHash2, randomBytes } from "node:crypto";
758
762
  import {
763
+ chmodSync as chmodSync2,
759
764
  existsSync as existsSync2,
760
765
  mkdirSync as mkdirSync2,
761
766
  readFileSync as readFileSync2,
@@ -989,6 +994,8 @@ function guestBootstrapOperations(profile, attested = Boolean(profile.confidenti
989
994
  validateMetalProfile(profile);
990
995
  const agentBun = "/usr/local/lib/forgezero/bun";
991
996
  return [
997
+ ["/usr/sbin/swapoff", "--all"],
998
+ ["/usr/bin/systemctl", "mask", "--now", "swap.target"],
992
999
  ...attested ? [
993
1000
  ["/usr/sbin/modprobe", "sev-guest"],
994
1001
  ["/usr/bin/test", "-c", "/dev/sev-guest"]
@@ -1065,6 +1072,7 @@ ${access.sshPublicKeys.map((key) => ` - ${JSON.stringify(key)}`).join(`
1065
1072
  return {
1066
1073
  userData: `#cloud-config
1067
1074
  package_update: true
1075
+ swap: { size: 0 }
1068
1076
  ${users}disable_root: true
1069
1077
  # Git is part of the deployment transport, not a tenant-selected prerequisite:
1070
1078
  # every dynamically claimed repository must be cloneable on a clean image.
@@ -1097,7 +1105,8 @@ var checked = async (exec, argv) => {
1097
1105
  async function provisionMetalGuest(profile, claim, exec) {
1098
1106
  validateMetalProfile(profile);
1099
1107
  assertSupportedGuestImage(claim.spec.imageKey, claim.spec.confidential);
1100
- if (!claim.computeKey || !claim.spec.reference || !SAFE_NAME.test(claim.spec.imageKey) || claim.spec.guestName !== undefined && !SAFE_NAME.test(claim.spec.guestName) || claim.spec.cpuPoolKey !== undefined && !SAFE_NAME.test(claim.spec.cpuPoolKey) || !Number.isInteger(claim.spec.physicalCores) || claim.spec.physicalCores < 1 || claim.spec.physicalCores > 256 || !Number.isInteger(claim.spec.vcpu) || claim.spec.vcpu < 1 || claim.spec.vcpu > 512 || !Number.isInteger(claim.spec.memoryGib) || claim.spec.memoryGib < 1 || claim.spec.memoryGib > 8192 || !Number.isInteger(claim.spec.diskGib) || claim.spec.diskGib < 8 || claim.spec.diskGib > 65536 || !Number.isInteger(claim.spec.egressGuaranteedMbps) || claim.spec.egressGuaranteedMbps < 0 || !Number.isInteger(claim.spec.egressBurstMbps) || claim.spec.egressBurstMbps < claim.spec.egressGuaranteedMbps)
1108
+ const diskEncryption = claim.spec.diskEncryption ?? "none";
1109
+ if (!claim.computeKey || !claim.spec.reference || !SAFE_NAME.test(claim.spec.imageKey) || claim.spec.guestName !== undefined && !SAFE_NAME.test(claim.spec.guestName) || claim.spec.cpuPoolKey !== undefined && !SAFE_NAME.test(claim.spec.cpuPoolKey) || !Number.isInteger(claim.spec.physicalCores) || claim.spec.physicalCores < 1 || claim.spec.physicalCores > 256 || !Number.isInteger(claim.spec.vcpu) || claim.spec.vcpu < 1 || claim.spec.vcpu > 512 || !Number.isInteger(claim.spec.memoryGib) || claim.spec.memoryGib < 1 || claim.spec.memoryGib > 8192 || !Number.isInteger(claim.spec.diskGib) || claim.spec.diskGib < 8 || claim.spec.diskGib > 65536 || !["none", "luks2"].includes(diskEncryption) || !Number.isInteger(claim.spec.egressGuaranteedMbps) || claim.spec.egressGuaranteedMbps < 0 || !Number.isInteger(claim.spec.egressBurstMbps) || claim.spec.egressBurstMbps < claim.spec.egressGuaranteedMbps)
1101
1110
  throw new MetalProvisionError("invalid compute claim");
1102
1111
  const image = profile.images[claim.spec.imageKey];
1103
1112
  if (!image || !isAbsolute(image.path) || !SHA256.test(image.sha256)) {
@@ -1126,6 +1135,9 @@ async function provisionMetalGuest(profile, claim, exec) {
1126
1135
  const prior = manifests.find((row) => row.computeKey === claim.computeKey);
1127
1136
  if (prior && prior.reference !== claim.spec.reference)
1128
1137
  throw new MetalProvisionError("compute identity conflicts with host inventory");
1138
+ if (prior && (prior.diskEncryption ?? "none") !== diskEncryption) {
1139
+ throw new MetalProvisionError("an allocated guest disk encryption mode is immutable");
1140
+ }
1129
1141
  const address = requestedAddress(profile, claim, manifests);
1130
1142
  const cpuPool = allocateCpuPool(profile, claim, manifests);
1131
1143
  const manifest = prior ?? {
@@ -1137,6 +1149,7 @@ async function provisionMetalGuest(profile, claim, exec) {
1137
1149
  cpuPoolKey: cpuPool.key,
1138
1150
  allowedCpus: cpuPool.cpus,
1139
1151
  allowedMemoryNodes: cpuPool.memoryNodes,
1152
+ diskEncryption,
1140
1153
  phase: "allocating"
1141
1154
  };
1142
1155
  const save = () => writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
@@ -1146,8 +1159,70 @@ async function provisionMetalGuest(profile, claim, exec) {
1146
1159
  const exists = (await exec(["lvs", "--noheadings", lv])).exitCode === 0;
1147
1160
  if (!exists)
1148
1161
  await checked(exec, ["lvcreate", "-y", "-n", name, "-L", `${claim.spec.diskGib}G`, profile.volumeGroup]);
1162
+ const encrypted = diskEncryption === "luks2";
1163
+ const mapper = `fz-${createHash2("sha256").update(claim.computeKey).digest("hex").slice(0, 20)}`;
1164
+ const mappedDisk = `/dev/mapper/${mapper}`;
1165
+ const credentialName = `guest-disk-${createHash2("sha256").update(claim.computeKey).digest("hex").slice(0, 20)}`;
1166
+ const credentialPath = `/etc/forgezero/creds/${credentialName}.cred`;
1167
+ const keyPath = `/run/forgezero/${credentialName}.key`;
1168
+ let imageTarget = lv;
1169
+ if (encrypted) {
1170
+ mkdirSync2(dirname2(credentialPath), { recursive: true, mode: 448 });
1171
+ mkdirSync2(dirname2(keyPath), { recursive: true, mode: 448 });
1172
+ if (existsSync2(keyPath))
1173
+ unlinkSync2(keyPath);
1174
+ if (!existsSync2(credentialPath)) {
1175
+ if (exists && (await exec(["cryptsetup", "isLuks", lv])).exitCode === 0) {
1176
+ throw new MetalProvisionError("encrypted guest disk exists without its sealed unlock credential");
1177
+ }
1178
+ writeFileSync2(keyPath, randomBytes(64), { mode: 384, flag: "wx" });
1179
+ try {
1180
+ await checked(exec, ["systemd-creds", "encrypt", `--name=${credentialName}`, keyPath, credentialPath]);
1181
+ chmodSync2(credentialPath, 384);
1182
+ } catch (cause) {
1183
+ if (existsSync2(credentialPath))
1184
+ unlinkSync2(credentialPath);
1185
+ throw cause;
1186
+ }
1187
+ } else {
1188
+ await checked(exec, ["systemd-creds", "decrypt", `--name=${credentialName}`, credentialPath, keyPath]);
1189
+ }
1190
+ try {
1191
+ if ((await exec(["cryptsetup", "isLuks", lv])).exitCode !== 0) {
1192
+ await checked(exec, [
1193
+ "cryptsetup",
1194
+ "luksFormat",
1195
+ "--batch-mode",
1196
+ "--type",
1197
+ "luks2",
1198
+ "--cipher",
1199
+ "aes-xts-plain64",
1200
+ "--key-size",
1201
+ "512",
1202
+ "--key-file",
1203
+ keyPath,
1204
+ lv
1205
+ ]);
1206
+ }
1207
+ if ((await exec(["cryptsetup", "status", mapper])).exitCode !== 0) {
1208
+ await checked(exec, ["cryptsetup", "open", "--type", "luks2", "--key-file", keyPath, lv, mapper]);
1209
+ }
1210
+ imageTarget = mappedDisk;
1211
+ if (manifest.phase === "allocating") {
1212
+ await checked(exec, ["qemu-img", "convert", "-O", "raw", image.path, imageTarget]);
1213
+ manifest.phase = "image-ready";
1214
+ save();
1215
+ }
1216
+ } finally {
1217
+ if (existsSync2(keyPath))
1218
+ unlinkSync2(keyPath);
1219
+ if ((await exec(["cryptsetup", "status", mapper])).exitCode === 0) {
1220
+ await checked(exec, ["cryptsetup", "close", mapper]);
1221
+ }
1222
+ }
1223
+ }
1149
1224
  if (manifest.phase === "allocating") {
1150
- await checked(exec, ["qemu-img", "convert", "-O", "raw", image.path, lv]);
1225
+ await checked(exec, ["qemu-img", "convert", "-O", "raw", image.path, imageTarget]);
1151
1226
  manifest.phase = "image-ready";
1152
1227
  save();
1153
1228
  }
@@ -1171,7 +1246,8 @@ async function provisionMetalGuest(profile, claim, exec) {
1171
1246
  memoryGib: claim.spec.memoryGib,
1172
1247
  allowedCpus: manifest.allowedCpus,
1173
1248
  allowedMemoryNodes: manifest.allowedMemoryNodes,
1174
- disk: lv,
1249
+ disk: encrypted ? mappedDisk : lv,
1250
+ ...encrypted ? { diskEncryption: { source: lv, mapper, credentialName, credentialPath } } : {},
1175
1251
  seed,
1176
1252
  bridge: profile.bridge,
1177
1253
  mac: manifest.mac,
@@ -1229,6 +1305,7 @@ function legacyPlatformManifest(profile, claim, name) {
1229
1305
  cpuPoolKey: claim.spec.cpuPoolKey ?? "",
1230
1306
  allowedCpus: values.get("CPUSET") ?? "",
1231
1307
  allowedMemoryNodes: values.get("NUMA"),
1308
+ diskEncryption: "none",
1232
1309
  phase: "running",
1233
1310
  disk,
1234
1311
  unit: `forgezero-guest@${name}.service`
@@ -1243,6 +1320,9 @@ async function removeMetalGuest(profile, claim, exec) {
1243
1320
  const service = `forgezero-guest@${name}.service`;
1244
1321
  const unitPath = join2(profile.unitDir, service);
1245
1322
  const lv = `/dev/${profile.volumeGroup}/${name}`;
1323
+ const mapper = `fz-${createHash2("sha256").update(claim.computeKey).digest("hex").slice(0, 20)}`;
1324
+ const credentialName = `guest-disk-${createHash2("sha256").update(claim.computeKey).digest("hex").slice(0, 20)}`;
1325
+ const credentialPath = `/etc/forgezero/creds/${credentialName}.cred`;
1246
1326
  const manifest = existsSync2(manifestPath) ? JSON.parse(readFileSync2(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
1247
1327
  if (!manifest) {
1248
1328
  const seedBase = join2(profile.seedDir, name);
@@ -1277,9 +1357,14 @@ async function removeMetalGuest(profile, claim, exec) {
1277
1357
  else if ((await exec(["systemctl", "is-active", service])).exitCode === 0) {
1278
1358
  throw new MetalProvisionError("guest unit is active but its owned unit file is missing");
1279
1359
  }
1360
+ const encrypted = (manifest.diskEncryption ?? "none") === "luks2";
1361
+ if (encrypted && (await exec(["cryptsetup", "status", mapper])).exitCode === 0) {
1362
+ throw new MetalProvisionError("guest disk mapping remained active after the guest stopped");
1363
+ }
1280
1364
  if ((await exec(["lvs", "--noheadings", lv])).exitCode === 0)
1281
1365
  await checked(exec, ["lvremove", "-fy", lv]);
1282
1366
  for (const path2 of [
1367
+ ...encrypted ? [credentialPath] : [],
1283
1368
  unitPath,
1284
1369
  join2(profile.seedDir, `${name}-seed.iso`),
1285
1370
  join2(profile.seedDir, `${name}-user-data`),
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.134";
1463
+ var VERSION = "0.1.136";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3070,6 +3070,10 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
3070
3070
  throw new Error("platform genesis vCPU count cannot be smaller than physical cores");
3071
3071
  const memoryGib = integer(input.memoryGib, 1, 8192, "memory");
3072
3072
  const diskGib = integer(input.diskGib, 8, 65536, "disk");
3073
+ const diskEncryption = input.diskEncryption ?? "none";
3074
+ if (!["none", "luks2"].includes(diskEncryption)) {
3075
+ throw new Error("platform genesis disk encryption mode is unsupported");
3076
+ }
3073
3077
  const egressGuaranteedMbps = integer(input.egressGuaranteedMbps, 0, 1e6, "guaranteed egress");
3074
3078
  const egressBurstMbps = integer(input.egressBurstMbps, egressGuaranteedMbps, 1e6, "burst egress");
3075
3079
  return {
@@ -3081,6 +3085,7 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
3081
3085
  vcpu,
3082
3086
  memoryGib,
3083
3087
  diskGib,
3088
+ diskEncryption,
3084
3089
  egressGuaranteedMbps,
3085
3090
  egressBurstMbps,
3086
3091
  confidential: input.confidential
@@ -3111,6 +3116,7 @@ var claimFor = (guest, keys, action) => {
3111
3116
  vcpu: guest.vcpu,
3112
3117
  memoryGib: guest.memoryGib,
3113
3118
  diskGib: guest.diskGib,
3119
+ diskEncryption: guest.diskEncryption,
3114
3120
  egressGuaranteedMbps: guest.egressGuaranteedMbps,
3115
3121
  egressBurstMbps: guest.egressBurstMbps,
3116
3122
  confidential: guest.confidential
@@ -3200,6 +3206,7 @@ function validatePlatformInitialInventory(value) {
3200
3206
  "vcpu",
3201
3207
  "memoryGib",
3202
3208
  "diskGib",
3209
+ "diskEncryption",
3203
3210
  "egressGuaranteedMbps",
3204
3211
  "egressBurstMbps",
3205
3212
  "confidential",
@@ -5230,6 +5237,7 @@ function strictBootstrapDocument(value) {
5230
5237
  "vcpu",
5231
5238
  "memoryGib",
5232
5239
  "diskGib",
5240
+ "diskEncryption",
5233
5241
  "egressGuaranteedMbps",
5234
5242
  "egressBurstMbps",
5235
5243
  "confidential",
@@ -6194,6 +6202,7 @@ async function applyMetalBootstrap(config, options) {
6194
6202
  "qemu-utils",
6195
6203
  "cloud-image-utils",
6196
6204
  "lvm2",
6205
+ "cryptsetup",
6197
6206
  "nftables",
6198
6207
  "curl",
6199
6208
  "ca-certificates"
@@ -6634,11 +6643,12 @@ var validateMetalRequestValue = (value, options = {}) => {
6634
6643
  "vcpu",
6635
6644
  "memoryGib",
6636
6645
  "diskGib",
6646
+ "diskEncryption",
6637
6647
  "egressGuaranteedMbps",
6638
6648
  "egressBurstMbps",
6639
6649
  "confidential"
6640
6650
  ], label);
6641
- if (typeof row.name !== "string" || typeof row.address !== "string" || typeof row.imageKey !== "string" || row.cpuPoolKey !== undefined && typeof row.cpuPoolKey !== "string" || !["physicalCores", "vcpu", "memoryGib", "diskGib", "egressGuaranteedMbps", "egressBurstMbps"].every((key) => typeof row[key] === "number") || typeof row.confidential !== "boolean") {
6651
+ if (typeof row.name !== "string" || typeof row.address !== "string" || typeof row.imageKey !== "string" || row.cpuPoolKey !== undefined && typeof row.cpuPoolKey !== "string" || !["physicalCores", "vcpu", "memoryGib", "diskGib", "egressGuaranteedMbps", "egressBurstMbps"].every((key) => typeof row[key] === "number") || row.diskEncryption !== undefined && !["none", "luks2"].includes(row.diskEncryption) || typeof row.confidential !== "boolean") {
6642
6652
  throw new Error(`${label} has invalid resource coordinates`);
6643
6653
  }
6644
6654
  return {
@@ -6650,6 +6660,7 @@ var validateMetalRequestValue = (value, options = {}) => {
6650
6660
  vcpu: row.vcpu,
6651
6661
  memoryGib: row.memoryGib,
6652
6662
  diskGib: row.diskGib,
6663
+ diskEncryption: row.diskEncryption ?? "none",
6653
6664
  egressGuaranteedMbps: row.egressGuaranteedMbps,
6654
6665
  egressBurstMbps: row.egressBurstMbps,
6655
6666
  confidential: row.confidential
@@ -11,6 +11,7 @@ export interface PlatformInitialInventoryCompute {
11
11
  vcpu: number;
12
12
  memoryGib: number;
13
13
  diskGib: number;
14
+ diskEncryption?: 'none' | 'luks2';
14
15
  egressGuaranteedMbps: number;
15
16
  egressBurstMbps: number;
16
17
  confidential: true;
@@ -652,6 +652,10 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
652
652
  throw new Error("platform genesis vCPU count cannot be smaller than physical cores");
653
653
  const memoryGib = integer(input.memoryGib, 1, 8192, "memory");
654
654
  const diskGib = integer(input.diskGib, 8, 65536, "disk");
655
+ const diskEncryption = input.diskEncryption ?? "none";
656
+ if (!["none", "luks2"].includes(diskEncryption)) {
657
+ throw new Error("platform genesis disk encryption mode is unsupported");
658
+ }
655
659
  const egressGuaranteedMbps = integer(input.egressGuaranteedMbps, 0, 1e6, "guaranteed egress");
656
660
  const egressBurstMbps = integer(input.egressBurstMbps, egressGuaranteedMbps, 1e6, "burst egress");
657
661
  return {
@@ -663,6 +667,7 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
663
667
  vcpu,
664
668
  memoryGib,
665
669
  diskGib,
670
+ diskEncryption,
666
671
  egressGuaranteedMbps,
667
672
  egressBurstMbps,
668
673
  confidential: input.confidential
@@ -693,6 +698,7 @@ var claimFor = (guest, keys, action) => {
693
698
  vcpu: guest.vcpu,
694
699
  memoryGib: guest.memoryGib,
695
700
  diskGib: guest.diskGib,
701
+ diskEncryption: guest.diskEncryption,
696
702
  egressGuaranteedMbps: guest.egressGuaranteedMbps,
697
703
  egressBurstMbps: guest.egressBurstMbps,
698
704
  confidential: guest.confidential
@@ -799,6 +805,7 @@ function validatePlatformInitialInventory(value) {
799
805
  "vcpu",
800
806
  "memoryGib",
801
807
  "diskGib",
808
+ "diskEncryption",
802
809
  "egressGuaranteedMbps",
803
810
  "egressBurstMbps",
804
811
  "confidential",
@@ -3049,7 +3049,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3049
3049
  }
3050
3050
 
3051
3051
  // src/version.ts
3052
- var VERSION3 = "0.1.134";
3052
+ var VERSION3 = "0.1.136";
3053
3053
 
3054
3054
  // src/egress-policy.ts
3055
3055
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -4168,6 +4168,10 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
4168
4168
  throw new Error("platform genesis vCPU count cannot be smaller than physical cores");
4169
4169
  const memoryGib = integer(input.memoryGib, 1, 8192, "memory");
4170
4170
  const diskGib = integer(input.diskGib, 8, 65536, "disk");
4171
+ const diskEncryption = input.diskEncryption ?? "none";
4172
+ if (!["none", "luks2"].includes(diskEncryption)) {
4173
+ throw new Error("platform genesis disk encryption mode is unsupported");
4174
+ }
4171
4175
  const egressGuaranteedMbps = integer(input.egressGuaranteedMbps, 0, 1e6, "guaranteed egress");
4172
4176
  const egressBurstMbps = integer(input.egressBurstMbps, egressGuaranteedMbps, 1e6, "burst egress");
4173
4177
  return {
@@ -4179,6 +4183,7 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
4179
4183
  vcpu,
4180
4184
  memoryGib,
4181
4185
  diskGib,
4186
+ diskEncryption,
4182
4187
  egressGuaranteedMbps,
4183
4188
  egressBurstMbps,
4184
4189
  confidential: input.confidential
@@ -4209,6 +4214,7 @@ var claimFor = (guest, keys, action) => {
4209
4214
  vcpu: guest.vcpu,
4210
4215
  memoryGib: guest.memoryGib,
4211
4216
  diskGib: guest.diskGib,
4217
+ diskEncryption: guest.diskEncryption,
4212
4218
  egressGuaranteedMbps: guest.egressGuaranteedMbps,
4213
4219
  egressBurstMbps: guest.egressBurstMbps,
4214
4220
  confidential: guest.confidential
@@ -4315,6 +4321,7 @@ function validatePlatformInitialInventory(value) {
4315
4321
  "vcpu",
4316
4322
  "memoryGib",
4317
4323
  "diskGib",
4324
+ "diskEncryption",
4318
4325
  "egressGuaranteedMbps",
4319
4326
  "egressBurstMbps",
4320
4327
  "confidential",
@@ -14,6 +14,7 @@ export interface PlatformGenesisGuest {
14
14
  vcpu: number;
15
15
  memoryGib: number;
16
16
  diskGib: number;
17
+ diskEncryption?: 'none' | 'luks2';
17
18
  egressGuaranteedMbps: number;
18
19
  egressBurstMbps: number;
19
20
  confidential: boolean;
@@ -652,6 +652,10 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
652
652
  throw new Error("platform genesis vCPU count cannot be smaller than physical cores");
653
653
  const memoryGib = integer(input.memoryGib, 1, 8192, "memory");
654
654
  const diskGib = integer(input.diskGib, 8, 65536, "disk");
655
+ const diskEncryption = input.diskEncryption ?? "none";
656
+ if (!["none", "luks2"].includes(diskEncryption)) {
657
+ throw new Error("platform genesis disk encryption mode is unsupported");
658
+ }
655
659
  const egressGuaranteedMbps = integer(input.egressGuaranteedMbps, 0, 1e6, "guaranteed egress");
656
660
  const egressBurstMbps = integer(input.egressBurstMbps, egressGuaranteedMbps, 1e6, "burst egress");
657
661
  return {
@@ -663,6 +667,7 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
663
667
  vcpu,
664
668
  memoryGib,
665
669
  diskGib,
670
+ diskEncryption,
666
671
  egressGuaranteedMbps,
667
672
  egressBurstMbps,
668
673
  confidential: input.confidential
@@ -693,6 +698,7 @@ var claimFor = (guest, keys, action) => {
693
698
  vcpu: guest.vcpu,
694
699
  memoryGib: guest.memoryGib,
695
700
  diskGib: guest.diskGib,
701
+ diskEncryption: guest.diskEncryption,
696
702
  egressGuaranteedMbps: guest.egressGuaranteedMbps,
697
703
  egressBurstMbps: guest.egressBurstMbps,
698
704
  confidential: guest.confidential
package/dist/provision.js CHANGED
@@ -3049,7 +3049,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3049
3049
  }
3050
3050
 
3051
3051
  // src/version.ts
3052
- var VERSION3 = "0.1.134";
3052
+ var VERSION3 = "0.1.136";
3053
3053
 
3054
3054
  // src/egress-policy.ts
3055
3055
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -17,6 +17,8 @@ interface RemoteProvisionClaimBase {
17
17
  vcpu: number;
18
18
  memoryGib: number;
19
19
  diskGib: number;
20
+ /** Absent only on pre-encryption claims; normalized to `none` by the Metal Agent. */
21
+ diskEncryption?: 'none' | 'luks2';
20
22
  egressGuaranteedMbps: number;
21
23
  egressBurstMbps: number;
22
24
  confidential: boolean;
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** One package version shared by both public binaries. Pinned to package.json by tests. */
2
- export declare const VERSION = "0.1.134";
2
+ export declare const VERSION = "0.1.136";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.134",
3
+ "version": "0.1.136",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",