@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.
- package/README.md +1 -1
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +9 -1
- package/dist/compute.d.ts +7 -0
- package/dist/compute.js +5 -1
- package/dist/deploy-compiler.d.ts +2 -0
- package/dist/deploy-compiler.js +28 -5
- package/dist/deploy-plan-runner.js +16 -3
- package/dist/deploy-plan.d.ts +3 -1
- package/dist/deploy-plan.js +17 -3
- package/dist/deploy.d.ts +5 -0
- package/dist/deployment-pull.d.ts +1 -0
- package/dist/deployment.d.ts +3 -0
- package/dist/fz-agent.js +148 -49
- package/dist/fz.js +53 -8
- package/dist/metal-bootstrap.js +2 -1
- package/dist/metal-helper-socket.js +92 -7
- package/dist/metal-provision.d.ts +1 -0
- package/dist/metal-provision.js +90 -5
- package/dist/operator-bootstrap.js +13 -2
- package/dist/platform-bootstrap-runtime.d.ts +1 -0
- package/dist/platform-bootstrap-runtime.js +7 -0
- package/dist/platform-fleet-verification.js +8 -1
- package/dist/platform-genesis.d.ts +1 -0
- package/dist/platform-genesis.js +6 -0
- package/dist/provision.js +1 -1
- package/dist/provisioning-pull.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/fz.js
CHANGED
|
@@ -6007,7 +6007,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
6007
6007
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
6008
6008
|
|
|
6009
6009
|
// src/version.ts
|
|
6010
|
-
var VERSION2 = "0.1.
|
|
6010
|
+
var VERSION2 = "0.1.136";
|
|
6011
6011
|
|
|
6012
6012
|
// src/software.ts
|
|
6013
6013
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -10816,7 +10816,7 @@ function validateBuiltInProvider(requirement2, where) {
|
|
|
10816
10816
|
|
|
10817
10817
|
// src/deploy-plan.ts
|
|
10818
10818
|
var DEPLOY_PLAN_FORMAT = "forgezero-deployment-plan";
|
|
10819
|
-
var DEPLOY_PLAN_VERSION =
|
|
10819
|
+
var DEPLOY_PLAN_VERSION = 2;
|
|
10820
10820
|
|
|
10821
10821
|
class DeploymentPlanError extends Error {
|
|
10822
10822
|
constructor(message) {
|
|
@@ -10830,6 +10830,13 @@ var ACTION = /^[a-z][a-z0-9.-]{0,127}\/[a-z][a-z0-9.-]{0,127}@[1-9][0-9]{0,5}$/;
|
|
|
10830
10830
|
var VERSION3 = /^[A-Za-z0-9][A-Za-z0-9.+_-]{0,63}$/;
|
|
10831
10831
|
var ABSOLUTE_PATH = /^\/(?:[A-Za-z0-9._-]+\/?)*$/;
|
|
10832
10832
|
var HEALTH_PATH = /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{0,255}$/;
|
|
10833
|
+
function deploymentBranch(value, where = "deployment.spec.source.branch") {
|
|
10834
|
+
const branch = boundedString(value, where, 255);
|
|
10835
|
+
if (branch === "@" || branch.startsWith("/") || branch.endsWith("/") || branch.startsWith(".") || branch.endsWith(".") || branch.endsWith(".lock") || branch.includes("..") || branch.includes("//") || branch.includes("@{") || /[\x00-\x20~^:?*\[\\]/.test(branch)) {
|
|
10836
|
+
fail2(where, "must be one exact valid Git branch name.");
|
|
10837
|
+
}
|
|
10838
|
+
return branch;
|
|
10839
|
+
}
|
|
10833
10840
|
var fail2 = (where, message) => {
|
|
10834
10841
|
throw new DeploymentPlanError(`${where} ${message}`);
|
|
10835
10842
|
};
|
|
@@ -11465,7 +11472,10 @@ function compileDeployment(sourceValue) {
|
|
|
11465
11472
|
if (metadata.description !== undefined)
|
|
11466
11473
|
boundedString(metadata.description, "deployment.metadata.description", 1024);
|
|
11467
11474
|
const spec = object(source.spec, "deployment.spec");
|
|
11468
|
-
exact3(spec, ["security", "inputs", "credentials", "targets", "requirements", "components", "workflows"], "deployment.spec");
|
|
11475
|
+
exact3(spec, ["source", "security", "inputs", "credentials", "targets", "requirements", "components", "workflows"], "deployment.spec");
|
|
11476
|
+
const sourceSelector = object(spec.source, "deployment.spec.source");
|
|
11477
|
+
exact3(sourceSelector, ["branch"], "deployment.spec.source");
|
|
11478
|
+
deploymentBranch(sourceSelector.branch);
|
|
11469
11479
|
if (spec.security !== undefined) {
|
|
11470
11480
|
const security = object(spec.security, "deployment.spec.security");
|
|
11471
11481
|
exact3(security, ["attestation"], "deployment.spec.security");
|
|
@@ -11562,11 +11572,13 @@ function compileDeployment(sourceValue) {
|
|
|
11562
11572
|
if (provisioning.metalHostname !== undefined && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(String(provisioning.metalHostname)))
|
|
11563
11573
|
fail2(`deployment.spec.targets.${targetName}.provisioning.metalHostname`, "is invalid.");
|
|
11564
11574
|
const provisionedResources = object(provisioning.resources, `deployment.spec.targets.${targetName}.provisioning.resources`);
|
|
11565
|
-
exact3(provisionedResources, ["physicalCores", "vcpu", "memoryGib", "diskGib", "egressGuaranteedMbps", "egressBurstMbps", "confidential"], `deployment.spec.targets.${targetName}.provisioning.resources`);
|
|
11575
|
+
exact3(provisionedResources, ["physicalCores", "vcpu", "memoryGib", "diskGib", "diskEncryption", "egressGuaranteedMbps", "egressBurstMbps", "confidential"], `deployment.spec.targets.${targetName}.provisioning.resources`);
|
|
11566
11576
|
integer(provisionedResources.physicalCores, `deployment.spec.targets.${targetName}.provisioning.resources.physicalCores`, 1, 1024);
|
|
11567
11577
|
integer(provisionedResources.vcpu, `deployment.spec.targets.${targetName}.provisioning.resources.vcpu`, 1, 4096);
|
|
11568
11578
|
integer(provisionedResources.memoryGib, `deployment.spec.targets.${targetName}.provisioning.resources.memoryGib`, 1, 4194304);
|
|
11569
11579
|
integer(provisionedResources.diskGib, `deployment.spec.targets.${targetName}.provisioning.resources.diskGib`, 1, 1048576);
|
|
11580
|
+
if (provisionedResources.diskEncryption !== undefined && !["none", "luks2"].includes(String(provisionedResources.diskEncryption)))
|
|
11581
|
+
fail2(`deployment.spec.targets.${targetName}.provisioning.resources.diskEncryption`, "must be none or luks2.");
|
|
11570
11582
|
integer(provisionedResources.egressGuaranteedMbps, `deployment.spec.targets.${targetName}.provisioning.resources.egressGuaranteedMbps`, 0, 1e7);
|
|
11571
11583
|
integer(provisionedResources.egressBurstMbps, `deployment.spec.targets.${targetName}.provisioning.resources.egressBurstMbps`, Number(provisionedResources.egressGuaranteedMbps), 1e7);
|
|
11572
11584
|
if (typeof provisionedResources.confidential !== "boolean")
|
|
@@ -11796,6 +11808,7 @@ function compileDeployment(sourceValue) {
|
|
|
11796
11808
|
...metadata.description ? { description: metadata.description } : {},
|
|
11797
11809
|
requiredProviders: [...requiredProviders.values()].sort((left, right) => `${left.provider}:${left.contract}:${left.capability}`.localeCompare(`${right.provider}:${right.contract}:${right.capability}`)),
|
|
11798
11810
|
spec: {
|
|
11811
|
+
source: sourceSelector,
|
|
11799
11812
|
...spec.security ? { security: spec.security } : {},
|
|
11800
11813
|
inputs,
|
|
11801
11814
|
credentials,
|
|
@@ -11888,6 +11901,7 @@ function initCoordinates(options) {
|
|
|
11888
11901
|
const isolation = options.isolation ?? (runtime === "kata-snp" ? "sev-snp" : "standard");
|
|
11889
11902
|
const operatingSystem = options.operatingSystem ?? (isolation === "sev-snp" ? "ubuntu-26.04" : "ubuntu-24.04");
|
|
11890
11903
|
const result = {
|
|
11904
|
+
branch: options.branch ?? "main",
|
|
11891
11905
|
runtime,
|
|
11892
11906
|
isolation,
|
|
11893
11907
|
operatingSystem,
|
|
@@ -11901,6 +11915,9 @@ function initCoordinates(options) {
|
|
|
11901
11915
|
};
|
|
11902
11916
|
if (!/^[a-z][a-z0-9-]{0,62}$/.test(result.profile))
|
|
11903
11917
|
throw new Error("deployment profile must be a lowercase typed name");
|
|
11918
|
+
if (result.branch.length < 1 || result.branch.length > 255 || result.branch === "@" || result.branch.startsWith("/") || result.branch.endsWith("/") || result.branch.startsWith(".") || result.branch.endsWith(".") || result.branch.endsWith(".lock") || result.branch.includes("..") || result.branch.includes("//") || result.branch.includes("@{") || /[\x00-\x20~^:?*\[\\]/.test(result.branch)) {
|
|
11919
|
+
throw new Error("deployment branch must be one exact valid Git branch name");
|
|
11920
|
+
}
|
|
11904
11921
|
if (!Number.isSafeInteger(result.replicas) || result.replicas < 1 || result.replicas > 1024)
|
|
11905
11922
|
throw new Error("deployment replicas must be an integer from 1 to 1024");
|
|
11906
11923
|
if (!Number.isSafeInteger(result.cpuCores) || result.cpuCores < 1 || result.cpuCores > 1024)
|
|
@@ -11930,7 +11947,7 @@ function initCoordinates(options) {
|
|
|
11930
11947
|
if (!/^[a-z][a-z0-9-]{0,62}$/.test(value))
|
|
11931
11948
|
throw new Error(`deployment provisioning ${label} must be a lowercase typed name`);
|
|
11932
11949
|
const resources = options.provisioning.resources;
|
|
11933
|
-
for (const [label, value] of Object.entries(resources).filter(([key]) =>
|
|
11950
|
+
for (const [label, value] of Object.entries(resources).filter(([key]) => !["confidential", "diskEncryption"].includes(key))) {
|
|
11934
11951
|
if (!Number.isSafeInteger(value) || Number(value) < 0)
|
|
11935
11952
|
throw new Error(`deployment provisioning ${label} must be a non-negative integer`);
|
|
11936
11953
|
}
|
|
@@ -11939,6 +11956,8 @@ function initCoordinates(options) {
|
|
|
11939
11956
|
}
|
|
11940
11957
|
if (result.isolation === "sev-snp" && !resources.confidential)
|
|
11941
11958
|
throw new Error("deployment provisioning must provide confidential capacity for sev-snp");
|
|
11959
|
+
if (resources.diskEncryption !== undefined && !["none", "luks2"].includes(resources.diskEncryption))
|
|
11960
|
+
throw new Error("deployment provisioning disk encryption mode is unsupported");
|
|
11942
11961
|
if (!/^\d{1,18}$/.test(options.provisioning.monthlyAmountCents))
|
|
11943
11962
|
throw new Error("deployment provisioning monthly amount must be a bounded USD-cent amount");
|
|
11944
11963
|
if (!/^\d{1,18}$/.test(options.provisioning.maxMonthlySpendMinor))
|
|
@@ -11950,7 +11969,10 @@ function initCoordinates(options) {
|
|
|
11950
11969
|
if (options.provisioning.metalHostname && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(options.provisioning.metalHostname))
|
|
11951
11970
|
throw new Error("deployment provisioning metal hostname is invalid");
|
|
11952
11971
|
}
|
|
11953
|
-
return { ...result, ...options.provisioning ? { provisioning:
|
|
11972
|
+
return { ...result, ...options.provisioning ? { provisioning: {
|
|
11973
|
+
...options.provisioning,
|
|
11974
|
+
resources: { ...options.provisioning.resources, diskEncryption: options.provisioning.resources.diskEncryption ?? "luks2" }
|
|
11975
|
+
} } : {} };
|
|
11954
11976
|
}
|
|
11955
11977
|
function defaultTypeScriptDeployment(name, options = {}) {
|
|
11956
11978
|
const selected = initCoordinates(options);
|
|
@@ -12019,6 +12041,7 @@ export default defineDeployment({
|
|
|
12019
12041
|
kind: 'Deployment',
|
|
12020
12042
|
metadata: { name: '${safeName2(name)}' },
|
|
12021
12043
|
spec: {
|
|
12044
|
+
source: { branch: '${selected.branch}' },
|
|
12022
12045
|
security: { attestation: '${selected.isolation === "sev-snp" ? "required" : "preferred"}' },
|
|
12023
12046
|
targets: {
|
|
12024
12047
|
app: target.compute({
|
|
@@ -12414,6 +12437,10 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
|
|
|
12414
12437
|
throw new Error("platform genesis vCPU count cannot be smaller than physical cores");
|
|
12415
12438
|
const memoryGib = integer2(input.memoryGib, 1, 8192, "memory");
|
|
12416
12439
|
const diskGib = integer2(input.diskGib, 8, 65536, "disk");
|
|
12440
|
+
const diskEncryption = input.diskEncryption ?? "none";
|
|
12441
|
+
if (!["none", "luks2"].includes(diskEncryption)) {
|
|
12442
|
+
throw new Error("platform genesis disk encryption mode is unsupported");
|
|
12443
|
+
}
|
|
12417
12444
|
const egressGuaranteedMbps = integer2(input.egressGuaranteedMbps, 0, 1e6, "guaranteed egress");
|
|
12418
12445
|
const egressBurstMbps = integer2(input.egressBurstMbps, egressGuaranteedMbps, 1e6, "burst egress");
|
|
12419
12446
|
return {
|
|
@@ -12425,6 +12452,7 @@ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
|
|
|
12425
12452
|
vcpu,
|
|
12426
12453
|
memoryGib,
|
|
12427
12454
|
diskGib,
|
|
12455
|
+
diskEncryption,
|
|
12428
12456
|
egressGuaranteedMbps,
|
|
12429
12457
|
egressBurstMbps,
|
|
12430
12458
|
confidential: input.confidential
|
|
@@ -12455,6 +12483,7 @@ var claimFor = (guest, keys, action) => {
|
|
|
12455
12483
|
vcpu: guest.vcpu,
|
|
12456
12484
|
memoryGib: guest.memoryGib,
|
|
12457
12485
|
diskGib: guest.diskGib,
|
|
12486
|
+
diskEncryption: guest.diskEncryption,
|
|
12458
12487
|
egressGuaranteedMbps: guest.egressGuaranteedMbps,
|
|
12459
12488
|
egressBurstMbps: guest.egressBurstMbps,
|
|
12460
12489
|
confidential: guest.confidential
|
|
@@ -12544,6 +12573,7 @@ function validatePlatformInitialInventory(value) {
|
|
|
12544
12573
|
"vcpu",
|
|
12545
12574
|
"memoryGib",
|
|
12546
12575
|
"diskGib",
|
|
12576
|
+
"diskEncryption",
|
|
12547
12577
|
"egressGuaranteedMbps",
|
|
12548
12578
|
"egressBurstMbps",
|
|
12549
12579
|
"confidential",
|
|
@@ -15850,6 +15880,7 @@ function strictBootstrapDocument(value) {
|
|
|
15850
15880
|
"vcpu",
|
|
15851
15881
|
"memoryGib",
|
|
15852
15882
|
"diskGib",
|
|
15883
|
+
"diskEncryption",
|
|
15853
15884
|
"egressGuaranteedMbps",
|
|
15854
15885
|
"egressBurstMbps",
|
|
15855
15886
|
"confidential",
|
|
@@ -17084,6 +17115,7 @@ async function applyMetalBootstrap(config, options) {
|
|
|
17084
17115
|
"qemu-utils",
|
|
17085
17116
|
"cloud-image-utils",
|
|
17086
17117
|
"lvm2",
|
|
17118
|
+
"cryptsetup",
|
|
17087
17119
|
"nftables",
|
|
17088
17120
|
"curl",
|
|
17089
17121
|
"ca-certificates"
|
|
@@ -17518,11 +17550,12 @@ var validateMetalRequestValue = (value, options = {}) => {
|
|
|
17518
17550
|
"vcpu",
|
|
17519
17551
|
"memoryGib",
|
|
17520
17552
|
"diskGib",
|
|
17553
|
+
"diskEncryption",
|
|
17521
17554
|
"egressGuaranteedMbps",
|
|
17522
17555
|
"egressBurstMbps",
|
|
17523
17556
|
"confidential"
|
|
17524
17557
|
], label);
|
|
17525
|
-
if (typeof row2.name !== "string" || typeof row2.address !== "string" || typeof row2.imageKey !== "string" || row2.cpuPoolKey !== undefined && typeof row2.cpuPoolKey !== "string" || !["physicalCores", "vcpu", "memoryGib", "diskGib", "egressGuaranteedMbps", "egressBurstMbps"].every((key) => typeof row2[key] === "number") || typeof row2.confidential !== "boolean") {
|
|
17558
|
+
if (typeof row2.name !== "string" || typeof row2.address !== "string" || typeof row2.imageKey !== "string" || row2.cpuPoolKey !== undefined && typeof row2.cpuPoolKey !== "string" || !["physicalCores", "vcpu", "memoryGib", "diskGib", "egressGuaranteedMbps", "egressBurstMbps"].every((key) => typeof row2[key] === "number") || row2.diskEncryption !== undefined && !["none", "luks2"].includes(row2.diskEncryption) || typeof row2.confidential !== "boolean") {
|
|
17526
17559
|
throw new Error(`${label} has invalid resource coordinates`);
|
|
17527
17560
|
}
|
|
17528
17561
|
return {
|
|
@@ -17534,6 +17567,7 @@ var validateMetalRequestValue = (value, options = {}) => {
|
|
|
17534
17567
|
vcpu: row2.vcpu,
|
|
17535
17568
|
memoryGib: row2.memoryGib,
|
|
17536
17569
|
diskGib: row2.diskGib,
|
|
17570
|
+
diskEncryption: row2.diskEncryption ?? "none",
|
|
17537
17571
|
egressGuaranteedMbps: row2.egressGuaranteedMbps,
|
|
17538
17572
|
egressBurstMbps: row2.egressBurstMbps,
|
|
17539
17573
|
confidential: row2.confidential
|
|
@@ -19137,6 +19171,13 @@ function parseOptions(argv2) {
|
|
|
19137
19171
|
} else if (token === "--provision-metal") {
|
|
19138
19172
|
options.deployProvisionMetal = argv2[++index];
|
|
19139
19173
|
options.deployInitCustomized = true;
|
|
19174
|
+
} else if (token === "--provision-disk-encryption") {
|
|
19175
|
+
const value = argv2[++index];
|
|
19176
|
+
options.deployInitCustomized = true;
|
|
19177
|
+
if (value === "none" || value === "luks2")
|
|
19178
|
+
options.deployProvisionDiskEncryption = value;
|
|
19179
|
+
else
|
|
19180
|
+
options.optionError = "--provision-disk-encryption must be none or luks2.";
|
|
19140
19181
|
} else if (["--provision-physical-cores", "--provision-vcpu", "--provision-memory-gib", "--provision-disk-gib", "--provision-guaranteed-mbps", "--provision-burst-mbps"].includes(token)) {
|
|
19141
19182
|
const value = Number(argv2[++index] ?? "");
|
|
19142
19183
|
options.deployInitCustomized = true;
|
|
@@ -19985,6 +20026,7 @@ function interactiveGenesisGuests() {
|
|
|
19985
20026
|
vcpu: bootstrapNumber(`Guest ${index} vCPUs`),
|
|
19986
20027
|
memoryGib: bootstrapNumber(`Guest ${index} memory GiB`),
|
|
19987
20028
|
diskGib: bootstrapNumber(`Guest ${index} disk GiB`),
|
|
20029
|
+
diskEncryption: bootstrapBoolean(`Guest ${index} encrypts its persistent disk with LUKS2?`, "yes") ? "luks2" : "none",
|
|
19988
20030
|
egressGuaranteedMbps: bootstrapNumber(`Guest ${index} guaranteed egress Mbps`),
|
|
19989
20031
|
egressBurstMbps: bootstrapNumber(`Guest ${index} burst egress Mbps`),
|
|
19990
20032
|
confidential: bootstrapBoolean(`Guest ${index} requires confidential SEV-SNP compute?`)
|
|
@@ -20977,6 +21019,7 @@ async function cmdDeploy(options, args) {
|
|
|
20977
21019
|
initializeTypeScriptDeployment(options.projectRoot, {
|
|
20978
21020
|
name: options.projectName ?? basename3(resolve13(options.projectRoot)),
|
|
20979
21021
|
force: options.force,
|
|
21022
|
+
branch: options.branch,
|
|
20980
21023
|
runtime: options.deployRuntime,
|
|
20981
21024
|
operatingSystem: options.deployOperatingSystem,
|
|
20982
21025
|
isolation: options.deployIsolation,
|
|
@@ -20997,6 +21040,7 @@ async function cmdDeploy(options, args) {
|
|
|
20997
21040
|
options.deployProvisionMemoryGib,
|
|
20998
21041
|
options.deployProvisionDiskGib,
|
|
20999
21042
|
options.deployProvisionGuaranteedMbps,
|
|
21043
|
+
options.deployProvisionDiskEncryption,
|
|
21000
21044
|
options.deployProvisionBurstMbps,
|
|
21001
21045
|
options.deployProvisionMonthlyAmountCents,
|
|
21002
21046
|
options.deployMaxMonthlySpendMinor
|
|
@@ -21012,6 +21056,7 @@ async function cmdDeploy(options, args) {
|
|
|
21012
21056
|
vcpu: options.deployProvisionVcpu ?? 0,
|
|
21013
21057
|
memoryGib: options.deployProvisionMemoryGib ?? 0,
|
|
21014
21058
|
diskGib: options.deployProvisionDiskGib ?? 0,
|
|
21059
|
+
diskEncryption: options.deployProvisionDiskEncryption ?? "luks2",
|
|
21015
21060
|
egressGuaranteedMbps: options.deployProvisionGuaranteedMbps ?? 0,
|
|
21016
21061
|
egressBurstMbps: options.deployProvisionBurstMbps ?? 0,
|
|
21017
21062
|
confidential: options.deployIsolation === "sev-snp"
|
|
@@ -21359,7 +21404,7 @@ function usage() {
|
|
|
21359
21404
|
--provision-environment <key> --provision-ownership <platform|tenant-metal>
|
|
21360
21405
|
--provision-metal <host> Required only for tenant-metal ownership
|
|
21361
21406
|
--provision-physical-cores <n> --provision-vcpu <n>
|
|
21362
|
-
--provision-memory-gib <n> --provision-disk-gib <n>
|
|
21407
|
+
--provision-memory-gib <n> --provision-disk-gib <n> --provision-disk-encryption <none|luks2>
|
|
21363
21408
|
--provision-guaranteed-mbps <n> --provision-burst-mbps <n>
|
|
21364
21409
|
--provision-monthly-amount-cents <n> Exact approved USD cost per created compute
|
|
21365
21410
|
--max-monthly-spend-minor <n> Hard approved scale-out spend ceiling
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
// src/version.ts
|
|
369
|
-
var VERSION = "0.1.
|
|
369
|
+
var VERSION = "0.1.136";
|
|
370
370
|
|
|
371
371
|
// src/otel-collector.ts
|
|
372
372
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -1058,6 +1058,7 @@ async function applyMetalBootstrap(config, options) {
|
|
|
1058
1058
|
"qemu-utils",
|
|
1059
1059
|
"cloud-image-utils",
|
|
1060
1060
|
"lvm2",
|
|
1061
|
+
"cryptsetup",
|
|
1061
1062
|
"nftables",
|
|
1062
1063
|
"curl",
|
|
1063
1064
|
"ca-certificates"
|
|
@@ -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
|
-
|
|
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,
|
|
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`),
|
|
@@ -1302,7 +1387,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
1302
1387
|
}
|
|
1303
1388
|
|
|
1304
1389
|
// src/metal-helper-socket.ts
|
|
1305
|
-
import { chmodSync as
|
|
1390
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, unlinkSync as unlinkSync3 } from "node:fs";
|
|
1306
1391
|
import { connect, createServer } from "node:net";
|
|
1307
1392
|
var DEFAULT_METAL_HELPER_SOCKET = "/run/forgezero-metal/helper.sock";
|
|
1308
1393
|
var MAX_REQUEST_BYTES = 32 * 1024;
|
|
@@ -1382,7 +1467,7 @@ function startMetalHelper(options) {
|
|
|
1382
1467
|
});
|
|
1383
1468
|
socket.on("error", () => socket.destroy());
|
|
1384
1469
|
});
|
|
1385
|
-
server.listen(socketPath, () =>
|
|
1470
|
+
server.listen(socketPath, () => chmodSync3(socketPath, 432));
|
|
1386
1471
|
return {
|
|
1387
1472
|
server,
|
|
1388
1473
|
async stop() {
|
|
@@ -63,6 +63,7 @@ export interface GuestManifest {
|
|
|
63
63
|
cpuPoolKey: string;
|
|
64
64
|
allowedCpus: string;
|
|
65
65
|
allowedMemoryNodes?: string;
|
|
66
|
+
diskEncryption?: 'none' | 'luks2';
|
|
66
67
|
phase: 'allocating' | 'image-ready' | 'running';
|
|
67
68
|
}
|
|
68
69
|
export declare class MetalProvisionError extends Error {
|