@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-agent.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
-
import { randomBytes as
|
|
6
|
-
import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, existsSync as existsSync23, mkdirSync as mkdirSync18, chmodSync as
|
|
5
|
+
import { randomBytes as randomBytes8 } from "crypto";
|
|
6
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, existsSync as existsSync23, mkdirSync as mkdirSync18, chmodSync as chmodSync20, lstatSync as lstatSync10, statfsSync as statfsSync3 } from "fs";
|
|
7
7
|
import { cpus as cpus3, totalmem as totalmem3 } from "os";
|
|
8
8
|
import { dirname as dirname16, join as join12 } from "path";
|
|
9
9
|
|
|
@@ -6462,7 +6462,7 @@ function validateBuiltInProvider(requirement2, where) {
|
|
|
6462
6462
|
|
|
6463
6463
|
// src/deploy-plan.ts
|
|
6464
6464
|
var DEPLOY_PLAN_FORMAT = "forgezero-deployment-plan";
|
|
6465
|
-
var DEPLOY_PLAN_VERSION =
|
|
6465
|
+
var DEPLOY_PLAN_VERSION = 2;
|
|
6466
6466
|
|
|
6467
6467
|
class DeploymentPlanError extends Error {
|
|
6468
6468
|
constructor(message) {
|
|
@@ -6476,6 +6476,13 @@ var ACTION = /^[a-z][a-z0-9.-]{0,127}\/[a-z][a-z0-9.-]{0,127}@[1-9][0-9]{0,5}$/;
|
|
|
6476
6476
|
var VERSION = /^[A-Za-z0-9][A-Za-z0-9.+_-]{0,63}$/;
|
|
6477
6477
|
var ABSOLUTE_PATH = /^\/(?:[A-Za-z0-9._-]+\/?)*$/;
|
|
6478
6478
|
var HEALTH_PATH = /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{0,255}$/;
|
|
6479
|
+
function deploymentBranch(value, where = "deployment.spec.source.branch") {
|
|
6480
|
+
const branch = boundedString(value, where, 255);
|
|
6481
|
+
if (branch === "@" || branch.startsWith("/") || branch.endsWith("/") || branch.startsWith(".") || branch.endsWith(".") || branch.endsWith(".lock") || branch.includes("..") || branch.includes("//") || branch.includes("@{") || /[\x00-\x20~^:?*\[\\]/.test(branch)) {
|
|
6482
|
+
fail(where, "must be one exact valid Git branch name.");
|
|
6483
|
+
}
|
|
6484
|
+
return branch;
|
|
6485
|
+
}
|
|
6479
6486
|
var fail = (where, message) => {
|
|
6480
6487
|
throw new DeploymentPlanError(`${where} ${message}`);
|
|
6481
6488
|
};
|
|
@@ -7111,7 +7118,10 @@ function compileDeployment(sourceValue) {
|
|
|
7111
7118
|
if (metadata.description !== undefined)
|
|
7112
7119
|
boundedString(metadata.description, "deployment.metadata.description", 1024);
|
|
7113
7120
|
const spec = object(source.spec, "deployment.spec");
|
|
7114
|
-
exact3(spec, ["security", "inputs", "credentials", "targets", "requirements", "components", "workflows"], "deployment.spec");
|
|
7121
|
+
exact3(spec, ["source", "security", "inputs", "credentials", "targets", "requirements", "components", "workflows"], "deployment.spec");
|
|
7122
|
+
const sourceSelector = object(spec.source, "deployment.spec.source");
|
|
7123
|
+
exact3(sourceSelector, ["branch"], "deployment.spec.source");
|
|
7124
|
+
deploymentBranch(sourceSelector.branch);
|
|
7115
7125
|
if (spec.security !== undefined) {
|
|
7116
7126
|
const security = object(spec.security, "deployment.spec.security");
|
|
7117
7127
|
exact3(security, ["attestation"], "deployment.spec.security");
|
|
@@ -7208,11 +7218,13 @@ function compileDeployment(sourceValue) {
|
|
|
7208
7218
|
if (provisioning.metalHostname !== undefined && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(String(provisioning.metalHostname)))
|
|
7209
7219
|
fail(`deployment.spec.targets.${targetName}.provisioning.metalHostname`, "is invalid.");
|
|
7210
7220
|
const provisionedResources = object(provisioning.resources, `deployment.spec.targets.${targetName}.provisioning.resources`);
|
|
7211
|
-
exact3(provisionedResources, ["physicalCores", "vcpu", "memoryGib", "diskGib", "egressGuaranteedMbps", "egressBurstMbps", "confidential"], `deployment.spec.targets.${targetName}.provisioning.resources`);
|
|
7221
|
+
exact3(provisionedResources, ["physicalCores", "vcpu", "memoryGib", "diskGib", "diskEncryption", "egressGuaranteedMbps", "egressBurstMbps", "confidential"], `deployment.spec.targets.${targetName}.provisioning.resources`);
|
|
7212
7222
|
integer(provisionedResources.physicalCores, `deployment.spec.targets.${targetName}.provisioning.resources.physicalCores`, 1, 1024);
|
|
7213
7223
|
integer(provisionedResources.vcpu, `deployment.spec.targets.${targetName}.provisioning.resources.vcpu`, 1, 4096);
|
|
7214
7224
|
integer(provisionedResources.memoryGib, `deployment.spec.targets.${targetName}.provisioning.resources.memoryGib`, 1, 4194304);
|
|
7215
7225
|
integer(provisionedResources.diskGib, `deployment.spec.targets.${targetName}.provisioning.resources.diskGib`, 1, 1048576);
|
|
7226
|
+
if (provisionedResources.diskEncryption !== undefined && !["none", "luks2"].includes(String(provisionedResources.diskEncryption)))
|
|
7227
|
+
fail(`deployment.spec.targets.${targetName}.provisioning.resources.diskEncryption`, "must be none or luks2.");
|
|
7216
7228
|
integer(provisionedResources.egressGuaranteedMbps, `deployment.spec.targets.${targetName}.provisioning.resources.egressGuaranteedMbps`, 0, 1e7);
|
|
7217
7229
|
integer(provisionedResources.egressBurstMbps, `deployment.spec.targets.${targetName}.provisioning.resources.egressBurstMbps`, Number(provisionedResources.egressGuaranteedMbps), 1e7);
|
|
7218
7230
|
if (typeof provisionedResources.confidential !== "boolean")
|
|
@@ -7442,6 +7454,7 @@ function compileDeployment(sourceValue) {
|
|
|
7442
7454
|
...metadata.description ? { description: metadata.description } : {},
|
|
7443
7455
|
requiredProviders: [...requiredProviders.values()].sort((left, right) => `${left.provider}:${left.contract}:${left.capability}`.localeCompare(`${right.provider}:${right.contract}:${right.capability}`)),
|
|
7444
7456
|
spec: {
|
|
7457
|
+
source: sourceSelector,
|
|
7445
7458
|
...spec.security ? { security: spec.security } : {},
|
|
7446
7459
|
inputs,
|
|
7447
7460
|
credentials,
|
|
@@ -8568,7 +8581,7 @@ function createDeploymentManager(options) {
|
|
|
8568
8581
|
const credential = plan.spec.credentials?.[credentialName];
|
|
8569
8582
|
if (!credential || !options.cache)
|
|
8570
8583
|
throw new DeploymentActionError("refused", `credential ${credentialName} is unavailable`);
|
|
8571
|
-
environment[environmentName] = await options.cache.get(credential.source.name);
|
|
8584
|
+
environment[environmentName] = await options.cache.get(request.credentialBindings?.[credentialName] ?? credential.source.name);
|
|
8572
8585
|
credentialValues.push(environment[environmentName]);
|
|
8573
8586
|
}
|
|
8574
8587
|
}
|
|
@@ -9118,7 +9131,8 @@ async function deployClaim(options, claim) {
|
|
|
9118
9131
|
revision: claim.revision,
|
|
9119
9132
|
releaseExecutor: claim.releaseExecutor,
|
|
9120
9133
|
...claim.assignment ? { assignment: claim.assignment } : {},
|
|
9121
|
-
...claim.connectivityCapabilities ? { connectivityCapabilities: claim.connectivityCapabilities } : {}
|
|
9134
|
+
...claim.connectivityCapabilities ? { connectivityCapabilities: claim.connectivityCapabilities } : {},
|
|
9135
|
+
...claim.credentialBindings ? { credentialBindings: claim.credentialBindings } : {}
|
|
9122
9136
|
}).result;
|
|
9123
9137
|
result = options.telemetry ? await options.telemetry.observe("deployment.run", run2) : await run2();
|
|
9124
9138
|
} finally {
|
|
@@ -9669,7 +9683,7 @@ async function writeAndCloseProcessInput(input, value) {
|
|
|
9669
9683
|
}
|
|
9670
9684
|
|
|
9671
9685
|
// src/version.ts
|
|
9672
|
-
var VERSION2 = "0.1.
|
|
9686
|
+
var VERSION2 = "0.1.136";
|
|
9673
9687
|
|
|
9674
9688
|
// src/ssh-bootstrap.ts
|
|
9675
9689
|
function deriveMetalAdmissionProvisioning(lscpu) {
|
|
@@ -10479,12 +10493,13 @@ function startMetalAdmissionPull(options) {
|
|
|
10479
10493
|
}
|
|
10480
10494
|
|
|
10481
10495
|
// src/metal-helper-socket.ts
|
|
10482
|
-
import { chmodSync as
|
|
10496
|
+
import { chmodSync as chmodSync9, existsSync as existsSync8, unlinkSync as unlinkSync6 } from "fs";
|
|
10483
10497
|
import { connect as connect2, createServer as createServer3 } from "net";
|
|
10484
10498
|
|
|
10485
10499
|
// src/metal-provision.ts
|
|
10486
|
-
import { createHash as createHash7 } from "crypto";
|
|
10500
|
+
import { createHash as createHash7, randomBytes as randomBytes6 } from "crypto";
|
|
10487
10501
|
import {
|
|
10502
|
+
chmodSync as chmodSync8,
|
|
10488
10503
|
existsSync as existsSync7,
|
|
10489
10504
|
mkdirSync as mkdirSync5,
|
|
10490
10505
|
readFileSync as readFileSync6,
|
|
@@ -10548,6 +10563,10 @@ function guestUnit(spec) {
|
|
|
10548
10563
|
`ExecStopPost=-/usr/sbin/ip link del ${spec.tap}`
|
|
10549
10564
|
].join(`
|
|
10550
10565
|
`) : "";
|
|
10566
|
+
const encryptedDisk = spec.diskEncryption ? `LoadCredentialEncrypted=${spec.diskEncryption.credentialName}:${spec.diskEncryption.credentialPath}
|
|
10567
|
+
ExecStartPre=/usr/sbin/cryptsetup open --type luks2 --key-file %d/${spec.diskEncryption.credentialName} ${spec.diskEncryption.source} ${spec.diskEncryption.mapper}
|
|
10568
|
+
ExecStopPost=-/usr/sbin/cryptsetup close ${spec.diskEncryption.mapper}
|
|
10569
|
+
` : "";
|
|
10551
10570
|
return `[Unit]
|
|
10552
10571
|
Description=ForgeZero guest ${spec.name}
|
|
10553
10572
|
Documentation=https://www.forgezero.net
|
|
@@ -10557,7 +10576,7 @@ Wants=network-online.target
|
|
|
10557
10576
|
[Service]
|
|
10558
10577
|
Type=simple
|
|
10559
10578
|
Slice=forgezero-guests.slice
|
|
10560
|
-
${tapSetup}
|
|
10579
|
+
${encryptedDisk}${tapSetup}
|
|
10561
10580
|
ExecStart=${argv2}
|
|
10562
10581
|
Restart=always
|
|
10563
10582
|
RestartSec=5
|
|
@@ -10848,6 +10867,8 @@ function guestBootstrapOperations(profile, attested = Boolean(profile.confidenti
|
|
|
10848
10867
|
validateMetalProfile(profile);
|
|
10849
10868
|
const agentBun = "/usr/local/lib/forgezero/bun";
|
|
10850
10869
|
return [
|
|
10870
|
+
["/usr/sbin/swapoff", "--all"],
|
|
10871
|
+
["/usr/bin/systemctl", "mask", "--now", "swap.target"],
|
|
10851
10872
|
...attested ? [
|
|
10852
10873
|
["/usr/sbin/modprobe", "sev-guest"],
|
|
10853
10874
|
["/usr/bin/test", "-c", "/dev/sev-guest"]
|
|
@@ -10924,6 +10945,7 @@ ${access.sshPublicKeys.map((key) => ` - ${JSON.stringify(key)}`).join(`
|
|
|
10924
10945
|
return {
|
|
10925
10946
|
userData: `#cloud-config
|
|
10926
10947
|
package_update: true
|
|
10948
|
+
swap: { size: 0 }
|
|
10927
10949
|
${users}disable_root: true
|
|
10928
10950
|
# Git is part of the deployment transport, not a tenant-selected prerequisite:
|
|
10929
10951
|
# every dynamically claimed repository must be cloneable on a clean image.
|
|
@@ -10956,7 +10978,8 @@ var checked2 = async (exec, argv2) => {
|
|
|
10956
10978
|
async function provisionMetalGuest(profile, claim, exec) {
|
|
10957
10979
|
validateMetalProfile(profile);
|
|
10958
10980
|
assertSupportedGuestImage(claim.spec.imageKey, claim.spec.confidential);
|
|
10959
|
-
|
|
10981
|
+
const diskEncryption = claim.spec.diskEncryption ?? "none";
|
|
10982
|
+
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)
|
|
10960
10983
|
throw new MetalProvisionError("invalid compute claim");
|
|
10961
10984
|
const image = profile.images[claim.spec.imageKey];
|
|
10962
10985
|
if (!image || !isAbsolute3(image.path) || !SHA256.test(image.sha256)) {
|
|
@@ -10985,6 +11008,9 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
10985
11008
|
const prior = manifests.find((row2) => row2.computeKey === claim.computeKey);
|
|
10986
11009
|
if (prior && prior.reference !== claim.spec.reference)
|
|
10987
11010
|
throw new MetalProvisionError("compute identity conflicts with host inventory");
|
|
11011
|
+
if (prior && (prior.diskEncryption ?? "none") !== diskEncryption) {
|
|
11012
|
+
throw new MetalProvisionError("an allocated guest disk encryption mode is immutable");
|
|
11013
|
+
}
|
|
10988
11014
|
const address = requestedAddress(profile, claim, manifests);
|
|
10989
11015
|
const cpuPool = allocateCpuPool(profile, claim, manifests);
|
|
10990
11016
|
const manifest = prior ?? {
|
|
@@ -10996,6 +11022,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
10996
11022
|
cpuPoolKey: cpuPool.key,
|
|
10997
11023
|
allowedCpus: cpuPool.cpus,
|
|
10998
11024
|
allowedMemoryNodes: cpuPool.memoryNodes,
|
|
11025
|
+
diskEncryption,
|
|
10999
11026
|
phase: "allocating"
|
|
11000
11027
|
};
|
|
11001
11028
|
const save = () => writeFileSync6(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
@@ -11005,8 +11032,70 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
11005
11032
|
const exists = (await exec(["lvs", "--noheadings", lv])).exitCode === 0;
|
|
11006
11033
|
if (!exists)
|
|
11007
11034
|
await checked2(exec, ["lvcreate", "-y", "-n", name, "-L", `${claim.spec.diskGib}G`, profile.volumeGroup]);
|
|
11035
|
+
const encrypted = diskEncryption === "luks2";
|
|
11036
|
+
const mapper = `fz-${createHash7("sha256").update(claim.computeKey).digest("hex").slice(0, 20)}`;
|
|
11037
|
+
const mappedDisk = `/dev/mapper/${mapper}`;
|
|
11038
|
+
const credentialName = `guest-disk-${createHash7("sha256").update(claim.computeKey).digest("hex").slice(0, 20)}`;
|
|
11039
|
+
const credentialPath = `/etc/forgezero/creds/${credentialName}.cred`;
|
|
11040
|
+
const keyPath = `/run/forgezero/${credentialName}.key`;
|
|
11041
|
+
let imageTarget = lv;
|
|
11042
|
+
if (encrypted) {
|
|
11043
|
+
mkdirSync5(dirname5(credentialPath), { recursive: true, mode: 448 });
|
|
11044
|
+
mkdirSync5(dirname5(keyPath), { recursive: true, mode: 448 });
|
|
11045
|
+
if (existsSync7(keyPath))
|
|
11046
|
+
unlinkSync5(keyPath);
|
|
11047
|
+
if (!existsSync7(credentialPath)) {
|
|
11048
|
+
if (exists && (await exec(["cryptsetup", "isLuks", lv])).exitCode === 0) {
|
|
11049
|
+
throw new MetalProvisionError("encrypted guest disk exists without its sealed unlock credential");
|
|
11050
|
+
}
|
|
11051
|
+
writeFileSync6(keyPath, randomBytes6(64), { mode: 384, flag: "wx" });
|
|
11052
|
+
try {
|
|
11053
|
+
await checked2(exec, ["systemd-creds", "encrypt", `--name=${credentialName}`, keyPath, credentialPath]);
|
|
11054
|
+
chmodSync8(credentialPath, 384);
|
|
11055
|
+
} catch (cause) {
|
|
11056
|
+
if (existsSync7(credentialPath))
|
|
11057
|
+
unlinkSync5(credentialPath);
|
|
11058
|
+
throw cause;
|
|
11059
|
+
}
|
|
11060
|
+
} else {
|
|
11061
|
+
await checked2(exec, ["systemd-creds", "decrypt", `--name=${credentialName}`, credentialPath, keyPath]);
|
|
11062
|
+
}
|
|
11063
|
+
try {
|
|
11064
|
+
if ((await exec(["cryptsetup", "isLuks", lv])).exitCode !== 0) {
|
|
11065
|
+
await checked2(exec, [
|
|
11066
|
+
"cryptsetup",
|
|
11067
|
+
"luksFormat",
|
|
11068
|
+
"--batch-mode",
|
|
11069
|
+
"--type",
|
|
11070
|
+
"luks2",
|
|
11071
|
+
"--cipher",
|
|
11072
|
+
"aes-xts-plain64",
|
|
11073
|
+
"--key-size",
|
|
11074
|
+
"512",
|
|
11075
|
+
"--key-file",
|
|
11076
|
+
keyPath,
|
|
11077
|
+
lv
|
|
11078
|
+
]);
|
|
11079
|
+
}
|
|
11080
|
+
if ((await exec(["cryptsetup", "status", mapper])).exitCode !== 0) {
|
|
11081
|
+
await checked2(exec, ["cryptsetup", "open", "--type", "luks2", "--key-file", keyPath, lv, mapper]);
|
|
11082
|
+
}
|
|
11083
|
+
imageTarget = mappedDisk;
|
|
11084
|
+
if (manifest.phase === "allocating") {
|
|
11085
|
+
await checked2(exec, ["qemu-img", "convert", "-O", "raw", image.path, imageTarget]);
|
|
11086
|
+
manifest.phase = "image-ready";
|
|
11087
|
+
save();
|
|
11088
|
+
}
|
|
11089
|
+
} finally {
|
|
11090
|
+
if (existsSync7(keyPath))
|
|
11091
|
+
unlinkSync5(keyPath);
|
|
11092
|
+
if ((await exec(["cryptsetup", "status", mapper])).exitCode === 0) {
|
|
11093
|
+
await checked2(exec, ["cryptsetup", "close", mapper]);
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11008
11097
|
if (manifest.phase === "allocating") {
|
|
11009
|
-
await checked2(exec, ["qemu-img", "convert", "-O", "raw", image.path,
|
|
11098
|
+
await checked2(exec, ["qemu-img", "convert", "-O", "raw", image.path, imageTarget]);
|
|
11010
11099
|
manifest.phase = "image-ready";
|
|
11011
11100
|
save();
|
|
11012
11101
|
}
|
|
@@ -11030,7 +11119,8 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
11030
11119
|
memoryGib: claim.spec.memoryGib,
|
|
11031
11120
|
allowedCpus: manifest.allowedCpus,
|
|
11032
11121
|
allowedMemoryNodes: manifest.allowedMemoryNodes,
|
|
11033
|
-
disk: lv,
|
|
11122
|
+
disk: encrypted ? mappedDisk : lv,
|
|
11123
|
+
...encrypted ? { diskEncryption: { source: lv, mapper, credentialName, credentialPath } } : {},
|
|
11034
11124
|
seed,
|
|
11035
11125
|
bridge: profile.bridge,
|
|
11036
11126
|
mac: manifest.mac,
|
|
@@ -11088,6 +11178,7 @@ function legacyPlatformManifest(profile, claim, name) {
|
|
|
11088
11178
|
cpuPoolKey: claim.spec.cpuPoolKey ?? "",
|
|
11089
11179
|
allowedCpus: values.get("CPUSET") ?? "",
|
|
11090
11180
|
allowedMemoryNodes: values.get("NUMA"),
|
|
11181
|
+
diskEncryption: "none",
|
|
11091
11182
|
phase: "running",
|
|
11092
11183
|
disk,
|
|
11093
11184
|
unit: `forgezero-guest@${name}.service`
|
|
@@ -11102,6 +11193,9 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
11102
11193
|
const service = `forgezero-guest@${name}.service`;
|
|
11103
11194
|
const unitPath = join4(profile.unitDir, service);
|
|
11104
11195
|
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
11196
|
+
const mapper = `fz-${createHash7("sha256").update(claim.computeKey).digest("hex").slice(0, 20)}`;
|
|
11197
|
+
const credentialName = `guest-disk-${createHash7("sha256").update(claim.computeKey).digest("hex").slice(0, 20)}`;
|
|
11198
|
+
const credentialPath = `/etc/forgezero/creds/${credentialName}.cred`;
|
|
11105
11199
|
const manifest = existsSync7(manifestPath) ? JSON.parse(readFileSync6(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
11106
11200
|
if (!manifest) {
|
|
11107
11201
|
const seedBase = join4(profile.seedDir, name);
|
|
@@ -11136,9 +11230,14 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
11136
11230
|
else if ((await exec(["systemctl", "is-active", service])).exitCode === 0) {
|
|
11137
11231
|
throw new MetalProvisionError("guest unit is active but its owned unit file is missing");
|
|
11138
11232
|
}
|
|
11233
|
+
const encrypted = (manifest.diskEncryption ?? "none") === "luks2";
|
|
11234
|
+
if (encrypted && (await exec(["cryptsetup", "status", mapper])).exitCode === 0) {
|
|
11235
|
+
throw new MetalProvisionError("guest disk mapping remained active after the guest stopped");
|
|
11236
|
+
}
|
|
11139
11237
|
if ((await exec(["lvs", "--noheadings", lv])).exitCode === 0)
|
|
11140
11238
|
await checked2(exec, ["lvremove", "-fy", lv]);
|
|
11141
11239
|
for (const path2 of [
|
|
11240
|
+
...encrypted ? [credentialPath] : [],
|
|
11142
11241
|
unitPath,
|
|
11143
11242
|
join4(profile.seedDir, `${name}-seed.iso`),
|
|
11144
11243
|
join4(profile.seedDir, `${name}-user-data`),
|
|
@@ -11239,7 +11338,7 @@ function startMetalHelper(options) {
|
|
|
11239
11338
|
});
|
|
11240
11339
|
socket.on("error", () => socket.destroy());
|
|
11241
11340
|
});
|
|
11242
|
-
server.listen(socketPath, () =>
|
|
11341
|
+
server.listen(socketPath, () => chmodSync9(socketPath, 432));
|
|
11243
11342
|
return {
|
|
11244
11343
|
server,
|
|
11245
11344
|
async stop() {
|
|
@@ -11279,7 +11378,7 @@ function requestMetalProvision(claim, socketPath = DEFAULT_METAL_HELPER_SOCKET)
|
|
|
11279
11378
|
}
|
|
11280
11379
|
|
|
11281
11380
|
// src/deployment-runner.ts
|
|
11282
|
-
import { chmodSync as
|
|
11381
|
+
import { chmodSync as chmodSync10, existsSync as existsSync9, realpathSync, unlinkSync as unlinkSync7 } from "fs";
|
|
11283
11382
|
import { isAbsolute as isAbsolute4, resolve, sep } from "path";
|
|
11284
11383
|
import { connect as connect3, createServer as createServer4 } from "net";
|
|
11285
11384
|
var DEFAULT_DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
|
|
@@ -11441,7 +11540,7 @@ function startDeploymentRunner(options) {
|
|
|
11441
11540
|
const ready = new Promise((resolveReady, rejectReady) => {
|
|
11442
11541
|
server.once("error", rejectReady);
|
|
11443
11542
|
server.listen(socketPath, () => {
|
|
11444
|
-
|
|
11543
|
+
chmodSync10(socketPath, options.socketMode ?? 432);
|
|
11445
11544
|
server.off("error", rejectReady);
|
|
11446
11545
|
resolveReady();
|
|
11447
11546
|
});
|
|
@@ -11793,7 +11892,7 @@ async function closeServerWithin(server, timeoutMs) {
|
|
|
11793
11892
|
}
|
|
11794
11893
|
|
|
11795
11894
|
// src/lifecycle-helper.ts
|
|
11796
|
-
import { chmodSync as
|
|
11895
|
+
import { chmodSync as chmodSync11, existsSync as existsSync11, readFileSync as readFileSync7, unlinkSync as unlinkSync8 } from "fs";
|
|
11797
11896
|
import { connect as connect4, createConnection, createServer as createServer5, isIP as isIP3 } from "net";
|
|
11798
11897
|
var DEFAULT_LIFECYCLE_HELPER_SOCKET = "/run/forgezero-lifecycle/helper.sock";
|
|
11799
11898
|
var MAX_REQUEST_BYTES4 = 16 * 1024;
|
|
@@ -11975,7 +12074,7 @@ function startLifecycleHelper(options) {
|
|
|
11975
12074
|
});
|
|
11976
12075
|
socket.on("error", () => socket.destroy());
|
|
11977
12076
|
});
|
|
11978
|
-
server.listen(socketPath, () =>
|
|
12077
|
+
server.listen(socketPath, () => chmodSync11(socketPath, 432));
|
|
11979
12078
|
return {
|
|
11980
12079
|
server,
|
|
11981
12080
|
async stop() {
|
|
@@ -12015,7 +12114,7 @@ function requestLifecycleAction(claim, socketPath = DEFAULT_LIFECYCLE_HELPER_SOC
|
|
|
12015
12114
|
}
|
|
12016
12115
|
|
|
12017
12116
|
// src/warp-config.ts
|
|
12018
|
-
import { chmodSync as
|
|
12117
|
+
import { chmodSync as chmodSync12, mkdirSync as mkdirSync7, renameSync as renameSync6, symlinkSync as symlinkSync3, unlinkSync as unlinkSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
12019
12118
|
var xml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
12020
12119
|
function renderWarpMdm(options) {
|
|
12021
12120
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(options.organization))
|
|
@@ -12040,7 +12139,7 @@ function materializeWarpMdm(options) {
|
|
|
12040
12139
|
mkdirSync7(servicePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
|
|
12041
12140
|
const next = `${runtimePath}.next`;
|
|
12042
12141
|
writeFileSync8(next, renderWarpMdm(options), { mode: 384 });
|
|
12043
|
-
|
|
12142
|
+
chmodSync12(next, 384);
|
|
12044
12143
|
renameSync6(next, runtimePath);
|
|
12045
12144
|
try {
|
|
12046
12145
|
unlinkSync9(servicePath);
|
|
@@ -12098,7 +12197,7 @@ async function configureMeshConnector(input) {
|
|
|
12098
12197
|
// src/agent-update.ts
|
|
12099
12198
|
import { createHash as createHash8, timingSafeEqual, randomUUID as randomUUID2 } from "crypto";
|
|
12100
12199
|
import {
|
|
12101
|
-
chmodSync as
|
|
12200
|
+
chmodSync as chmodSync13,
|
|
12102
12201
|
closeSync as closeSync2,
|
|
12103
12202
|
existsSync as existsSync12,
|
|
12104
12203
|
fsyncSync,
|
|
@@ -12192,8 +12291,8 @@ var checked4 = async (run2, input, label) => {
|
|
|
12192
12291
|
return result;
|
|
12193
12292
|
};
|
|
12194
12293
|
async function validateReleaseDirectory(directory, release, run2) {
|
|
12195
|
-
|
|
12196
|
-
|
|
12294
|
+
chmodSync13(directory, 493);
|
|
12295
|
+
chmodSync13(join7(directory, "dist"), 493);
|
|
12197
12296
|
const manifest = JSON.parse(readFileSync9(join7(directory, "package.json"), "utf8"));
|
|
12198
12297
|
if (manifest.name !== release.package || manifest.version !== release.version) {
|
|
12199
12298
|
throw new Error("agent update manifest does not match the selected release");
|
|
@@ -12206,7 +12305,7 @@ async function validateReleaseDirectory(directory, release, run2) {
|
|
|
12206
12305
|
`)) {
|
|
12207
12306
|
throw new Error("agent update artifact is not a self-contained Bun executable");
|
|
12208
12307
|
}
|
|
12209
|
-
|
|
12308
|
+
chmodSync13(binary, 493);
|
|
12210
12309
|
}
|
|
12211
12310
|
const version = (await checked4(run2, { command: agent, args: ["--version"] }, "agent update smoke test")).output.trim();
|
|
12212
12311
|
if (version !== release.version)
|
|
@@ -12325,7 +12424,7 @@ function clearAgentCandidate(root = DEFAULT_AGENT_RELEASE_ROOT) {
|
|
|
12325
12424
|
// src/agent-update-helper.ts
|
|
12326
12425
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
12327
12426
|
import {
|
|
12328
|
-
chmodSync as
|
|
12427
|
+
chmodSync as chmodSync15,
|
|
12329
12428
|
closeSync as closeSync3,
|
|
12330
12429
|
existsSync as existsSync14,
|
|
12331
12430
|
fsyncSync as fsyncSync2,
|
|
@@ -12342,7 +12441,7 @@ import { dirname as dirname8, join as join8, resolve as resolve3 } from "path";
|
|
|
12342
12441
|
|
|
12343
12442
|
// src/agent-handover.ts
|
|
12344
12443
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12345
|
-
import { chmodSync as
|
|
12444
|
+
import { chmodSync as chmodSync14, existsSync as existsSync13, renameSync as renameSync8, rmSync as rmSync6, symlinkSync as symlinkSync5, unlinkSync as unlinkSync10 } from "fs";
|
|
12346
12445
|
import { basename as basename2, dirname as dirname7 } from "path";
|
|
12347
12446
|
import { connect as connect5, createServer as createServer6 } from "net";
|
|
12348
12447
|
var DEFAULT_AGENT_CANDIDATE_READY_SOCKET = "/run/forgezero/candidate-ready.sock";
|
|
@@ -12363,7 +12462,7 @@ function startAgentCandidateReady(ready, socketPath = DEFAULT_AGENT_CANDIDATE_RE
|
|
|
12363
12462
|
unlinkSync10(socketPath);
|
|
12364
12463
|
const server = createServer6((socket) => socket.end(`${JSON.stringify(ready)}
|
|
12365
12464
|
`));
|
|
12366
|
-
server.listen(socketPath, () =>
|
|
12465
|
+
server.listen(socketPath, () => chmodSync14(socketPath, 432));
|
|
12367
12466
|
return server;
|
|
12368
12467
|
}
|
|
12369
12468
|
function probeAgentCandidate(expected, socketPath = DEFAULT_AGENT_CANDIDATE_READY_SOCKET, timeoutMs = 5000) {
|
|
@@ -12916,7 +13015,7 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12916
13015
|
});
|
|
12917
13016
|
socket.on("error", () => socket.destroy());
|
|
12918
13017
|
});
|
|
12919
|
-
server.listen(socketPath, () =>
|
|
13018
|
+
server.listen(socketPath, () => chmodSync15(socketPath, 432));
|
|
12920
13019
|
return server;
|
|
12921
13020
|
}
|
|
12922
13021
|
function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
|
|
@@ -13133,7 +13232,7 @@ function startAgentHeartbeat(options) {
|
|
|
13133
13232
|
}
|
|
13134
13233
|
|
|
13135
13234
|
// src/software-helper.ts
|
|
13136
|
-
import { chmodSync as
|
|
13235
|
+
import { chmodSync as chmodSync16, existsSync as existsSync18, mkdirSync as mkdirSync13, unlinkSync as unlinkSync12 } from "fs";
|
|
13137
13236
|
import { connect as connect7, createServer as createServer8 } from "net";
|
|
13138
13237
|
import { dirname as dirname12 } from "path";
|
|
13139
13238
|
|
|
@@ -14121,7 +14220,7 @@ function startSoftwareHelper(options = {}) {
|
|
|
14121
14220
|
});
|
|
14122
14221
|
socket.on("error", () => socket.destroy());
|
|
14123
14222
|
});
|
|
14124
|
-
server.listen(socketPath, () =>
|
|
14223
|
+
server.listen(socketPath, () => chmodSync16(socketPath, 432));
|
|
14125
14224
|
return server;
|
|
14126
14225
|
}
|
|
14127
14226
|
function requestContainer(op, request, socketPath, timeoutMs) {
|
|
@@ -15206,7 +15305,7 @@ var METAL_SYSTEMD_CREDENTIALS = {
|
|
|
15206
15305
|
|
|
15207
15306
|
// src/platform-bootstrap-runtime.ts
|
|
15208
15307
|
import {
|
|
15209
|
-
chmodSync as
|
|
15308
|
+
chmodSync as chmodSync17,
|
|
15210
15309
|
chownSync,
|
|
15211
15310
|
copyFileSync as copyFileSync2,
|
|
15212
15311
|
existsSync as existsSync19,
|
|
@@ -15305,7 +15404,7 @@ var secureRelease = (path2, uid, gid) => {
|
|
|
15305
15404
|
if (metadata.isSymbolicLink())
|
|
15306
15405
|
return;
|
|
15307
15406
|
chownSync(current2, uid, gid);
|
|
15308
|
-
|
|
15407
|
+
chmodSync17(current2, metadata.isDirectory() ? 365 : 292);
|
|
15309
15408
|
if (metadata.isDirectory())
|
|
15310
15409
|
for (const name of readdirSync4(current2))
|
|
15311
15410
|
visit(join10(current2, name));
|
|
@@ -15546,7 +15645,7 @@ import { lstatSync as lstatSync7 } from "fs";
|
|
|
15546
15645
|
|
|
15547
15646
|
// src/bootstrap.ts
|
|
15548
15647
|
import {
|
|
15549
|
-
chmodSync as
|
|
15648
|
+
chmodSync as chmodSync19,
|
|
15550
15649
|
existsSync as existsSync22,
|
|
15551
15650
|
lstatSync as lstatSync6,
|
|
15552
15651
|
mkdirSync as mkdirSync16,
|
|
@@ -16547,9 +16646,9 @@ function planProvision(options) {
|
|
|
16547
16646
|
}
|
|
16548
16647
|
|
|
16549
16648
|
// src/cli/agent-install.ts
|
|
16550
|
-
import { randomBytes as
|
|
16649
|
+
import { randomBytes as randomBytes7 } from "crypto";
|
|
16551
16650
|
import {
|
|
16552
|
-
chmodSync as
|
|
16651
|
+
chmodSync as chmodSync18,
|
|
16553
16652
|
copyFileSync as copyFileSync3,
|
|
16554
16653
|
existsSync as existsSync21,
|
|
16555
16654
|
lstatSync as lstatSync5,
|
|
@@ -16640,12 +16739,12 @@ var runProvisionOperation = async (operation) => {
|
|
|
16640
16739
|
mkdirSync15(`${release}/dist`, { recursive: true, mode: 493 });
|
|
16641
16740
|
mkdirSync15(dirname13(operation.binary), { recursive: true, mode: 493 });
|
|
16642
16741
|
copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
|
|
16643
|
-
|
|
16742
|
+
chmodSync18(`${release}/dist/fz-agent.js`, 493);
|
|
16644
16743
|
const gitSshSource = `${dirname13(operation.source)}/fz-git-ssh.js`;
|
|
16645
16744
|
if (!existsSync21(gitSshSource))
|
|
16646
16745
|
return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
|
|
16647
16746
|
copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
|
|
16648
|
-
|
|
16747
|
+
chmodSync18(`${release}/dist/fz-git-ssh.js`, 493);
|
|
16649
16748
|
const pending = "/opt/forgezero/agent/current.next";
|
|
16650
16749
|
rmSync11(pending, { force: true });
|
|
16651
16750
|
symlinkSync7(`versions/${operation.version}`, pending);
|
|
@@ -16660,10 +16759,10 @@ var runProvisionOperation = async (operation) => {
|
|
|
16660
16759
|
if (operation.kind === "ensure-seed") {
|
|
16661
16760
|
if (existsSync21(operation.credential) && lstatSync5(operation.credential).size > 0)
|
|
16662
16761
|
return { stdout: "", exitCode: 0 };
|
|
16663
|
-
const seed =
|
|
16762
|
+
const seed = randomBytes7(32).toString("base64url");
|
|
16664
16763
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
|
|
16665
16764
|
if (result.exitCode === 0)
|
|
16666
|
-
|
|
16765
|
+
chmodSync18(operation.credential, 256);
|
|
16667
16766
|
return result;
|
|
16668
16767
|
}
|
|
16669
16768
|
if (operation.kind === "ensure-git-identity") {
|
|
@@ -16679,7 +16778,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
16679
16778
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=git-deploy-key", key, operation.credential]);
|
|
16680
16779
|
if (result.exitCode !== 0)
|
|
16681
16780
|
return result;
|
|
16682
|
-
|
|
16781
|
+
chmodSync18(operation.credential, 256);
|
|
16683
16782
|
}
|
|
16684
16783
|
if (!existsSync21(operation.publicKey) || lstatSync5(operation.publicKey).size < 1) {
|
|
16685
16784
|
if (!existsSync21(key)) {
|
|
@@ -16713,7 +16812,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
16713
16812
|
return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
|
|
16714
16813
|
}
|
|
16715
16814
|
copyFileSync3(operation.source, key);
|
|
16716
|
-
|
|
16815
|
+
chmodSync18(key, 384);
|
|
16717
16816
|
} else {
|
|
16718
16817
|
result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-bootstrap-runner", "-f", key]);
|
|
16719
16818
|
if (result.exitCode !== 0)
|
|
@@ -16726,14 +16825,14 @@ var runProvisionOperation = async (operation) => {
|
|
|
16726
16825
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
|
|
16727
16826
|
if (result.exitCode !== 0)
|
|
16728
16827
|
return result;
|
|
16729
|
-
|
|
16828
|
+
chmodSync18(operation.credential, 256);
|
|
16730
16829
|
}
|
|
16731
16830
|
if (!existsSync21(operation.publicKey) || lstatSync5(operation.publicKey).size < 1) {
|
|
16732
16831
|
if (!existsSync21(key)) {
|
|
16733
16832
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
|
|
16734
16833
|
if (decrypted.exitCode !== 0)
|
|
16735
16834
|
return decrypted;
|
|
16736
|
-
|
|
16835
|
+
chmodSync18(key, 384);
|
|
16737
16836
|
}
|
|
16738
16837
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
16739
16838
|
const publicKey = normalizeEd25519PublicKey(derived.stdout);
|
|
@@ -16743,7 +16842,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
16743
16842
|
mkdirSync15(dirname13(operation.publicKey), { recursive: true, mode: 493 });
|
|
16744
16843
|
writeFileSync15(operation.publicKey, `${publicKey} forgezero-bootstrap-runner
|
|
16745
16844
|
`, { mode: 292 });
|
|
16746
|
-
|
|
16845
|
+
chmodSync18(operation.publicKey, 292);
|
|
16747
16846
|
}
|
|
16748
16847
|
if (operation.source)
|
|
16749
16848
|
rmSync11(operation.source, { force: true });
|
|
@@ -16760,7 +16859,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
16760
16859
|
return { stdout: "enrolment source is missing", exitCode: 1 };
|
|
16761
16860
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
|
|
16762
16861
|
if (result.exitCode === 0) {
|
|
16763
|
-
|
|
16862
|
+
chmodSync18(operation.credential, 256);
|
|
16764
16863
|
rmSync11(operation.source, { force: true });
|
|
16765
16864
|
}
|
|
16766
16865
|
return result;
|
|
@@ -17146,7 +17245,7 @@ function localBootstrapHost() {
|
|
|
17146
17245
|
mkdirSync16(dirname14(path2), { recursive: true, mode: 493 });
|
|
17147
17246
|
const temporary = `${path2}.next.${process.pid}`;
|
|
17148
17247
|
writeFileSync16(temporary, content, { mode });
|
|
17149
|
-
|
|
17248
|
+
chmodSync19(temporary, mode);
|
|
17150
17249
|
renameSync15(temporary, path2);
|
|
17151
17250
|
},
|
|
17152
17251
|
mkdir: (path2, mode) => mkdirSync16(path2, { recursive: true, mode }),
|
|
@@ -18022,9 +18121,9 @@ function loadOrCreateSeed(path2) {
|
|
|
18022
18121
|
return seed2;
|
|
18023
18122
|
}
|
|
18024
18123
|
mkdirSync18(dirname16(path2), { recursive: true });
|
|
18025
|
-
const seed = new Uint8Array(
|
|
18124
|
+
const seed = new Uint8Array(randomBytes8(32));
|
|
18026
18125
|
writeFileSync18(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
|
|
18027
|
-
|
|
18126
|
+
chmodSync20(path2, 384);
|
|
18028
18127
|
return seed;
|
|
18029
18128
|
}
|
|
18030
18129
|
var DEFAULT_SOCKET_PATH = DEFAULT_SOCKET;
|