@forgezero/agent 0.1.12 → 0.1.14
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/dist/fz-agent.js +43 -9
- package/dist/fz.js +1 -1
- package/dist/metal-helper-socket.js +42 -8
- package/dist/metal-provision.d.ts +2 -1
- package/dist/metal-provision.js +42 -8
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/fz-agent.js
CHANGED
|
@@ -1732,12 +1732,13 @@ function guestBootstrapScript(profile, attested = Boolean(profile.confidential),
|
|
|
1732
1732
|
const agentBun = "/usr/local/lib/forgezero/bun";
|
|
1733
1733
|
const attestationSetup = attested ? `# The report device is not part of the encryption path, so a guest can appear
|
|
1734
1734
|
# healthy and encrypted while attestation is silently impossible. Install and
|
|
1735
|
-
# load the
|
|
1736
|
-
|
|
1735
|
+
# load the driver shipped by the one pinned image before the agent starts. Do
|
|
1736
|
+
# not ask an evolving apt repository for an exact old kernel package: the image
|
|
1737
|
+
# can keep booting after Canonical removes that package from the mirror.
|
|
1738
|
+
if [[ ! -c /dev/sev-guest ]]; then modprobe sev-guest; fi
|
|
1739
|
+
test -c /dev/sev-guest
|
|
1737
1740
|
printf 'sev-guest
|
|
1738
1741
|
' >/etc/modules-load.d/sev-guest.conf
|
|
1739
|
-
modprobe sev-guest
|
|
1740
|
-
test -c /dev/sev-guest
|
|
1741
1742
|
` : "";
|
|
1742
1743
|
return `#!/usr/bin/env bash
|
|
1743
1744
|
set -Eeuo pipefail
|
|
@@ -1926,7 +1927,7 @@ ethernets:
|
|
|
1926
1927
|
primary:
|
|
1927
1928
|
match: { name: "en*" }
|
|
1928
1929
|
addresses: [ ${manifest.address}/24 ]
|
|
1929
|
-
|
|
1930
|
+
routes: [ { to: default, via: ${profile.gateway} } ]
|
|
1930
1931
|
nameservers: { addresses: [${(profile.nameservers ?? ["1.1.1.1", "9.9.9.9"]).join(", ")}] }
|
|
1931
1932
|
`
|
|
1932
1933
|
};
|
|
@@ -2045,15 +2046,48 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
2045
2046
|
save();
|
|
2046
2047
|
return { guestAddress: address };
|
|
2047
2048
|
}
|
|
2049
|
+
function legacyPlatformManifest(profile, claim, name) {
|
|
2050
|
+
if (claim.bootstrap?.kind !== "platform-genesis" || claim.computeKey !== `platform:${name}` || claim.spec.reference !== `platform:${name}` || claim.spec.guestAddress === undefined)
|
|
2051
|
+
return;
|
|
2052
|
+
const legacyDir = profile.legacyStateDir ?? "/etc/forgezero/guests";
|
|
2053
|
+
const legacyPath = join2(legacyDir, `${name}.conf`);
|
|
2054
|
+
if (!existsSync5(legacyPath))
|
|
2055
|
+
return;
|
|
2056
|
+
const values = new Map;
|
|
2057
|
+
for (const line of readFileSync3(legacyPath, "utf8").split(/\r?\n/)) {
|
|
2058
|
+
if (!line || line.startsWith("#"))
|
|
2059
|
+
continue;
|
|
2060
|
+
const match = /^([A-Z][A-Z0-9_]*)=([^\s'"`$;|&<>]+)$/.exec(line);
|
|
2061
|
+
if (!match)
|
|
2062
|
+
throw new MetalProvisionError("legacy guest inventory contains unsafe syntax");
|
|
2063
|
+
values.set(match[1], match[2]);
|
|
2064
|
+
}
|
|
2065
|
+
const disk = `/dev/${profile.volumeGroup}/${name}`;
|
|
2066
|
+
if (values.get("NAME") !== name || values.get("DISK") !== disk || values.get("IP") !== claim.spec.guestAddress)
|
|
2067
|
+
throw new MetalProvisionError("legacy guest inventory does not match the platform claim");
|
|
2068
|
+
return {
|
|
2069
|
+
computeKey: claim.computeKey,
|
|
2070
|
+
reference: claim.spec.reference,
|
|
2071
|
+
name,
|
|
2072
|
+
address: claim.spec.guestAddress,
|
|
2073
|
+
mac: values.get("MAC") ?? "",
|
|
2074
|
+
cpuPoolKey: claim.spec.cpuPoolKey ?? "",
|
|
2075
|
+
allowedCpus: values.get("CPUSET") ?? "",
|
|
2076
|
+
allowedMemoryNodes: values.get("NUMA"),
|
|
2077
|
+
phase: "running",
|
|
2078
|
+
disk,
|
|
2079
|
+
unit: `forgezero-guest@${name}.service`
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2048
2082
|
async function removeMetalGuest(profile, claim, exec) {
|
|
2049
2083
|
validateMetalProfile(profile);
|
|
2050
2084
|
if (claim.action !== "delete")
|
|
2051
2085
|
throw new MetalProvisionError("create claim cannot remove a guest");
|
|
2052
2086
|
const name = claim.spec.guestName ?? guestNameFor(claim.computeKey);
|
|
2053
2087
|
const manifestPath = join2(profile.stateDir, `${name}.json`);
|
|
2054
|
-
|
|
2088
|
+
const manifest = existsSync5(manifestPath) ? JSON.parse(readFileSync3(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
2089
|
+
if (!manifest)
|
|
2055
2090
|
return {};
|
|
2056
|
-
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
2057
2091
|
const currentIdentity = manifest.computeKey === claim.computeKey && manifest.reference === claim.spec.reference && manifest.name === name;
|
|
2058
2092
|
const legacyPlatformIdentity = claim.bootstrap?.kind === "platform-genesis" && claim.computeKey === `platform:${name}` && claim.spec.reference === `platform:${name}` && manifest.name === name && manifest.disk === `/dev/${profile.volumeGroup}/${name}` && manifest.address === claim.spec.guestAddress && (manifest.unit === `fz-guest@${name}.service` || manifest.unit === `forgezero-guest@${name}.service`);
|
|
2059
2093
|
if (!currentIdentity && !legacyPlatformIdentity) {
|
|
@@ -2082,7 +2116,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
2082
2116
|
if (existsSync5(path))
|
|
2083
2117
|
unlinkSync4(path);
|
|
2084
2118
|
if (legacyPlatformIdentity) {
|
|
2085
|
-
const legacyConfig =
|
|
2119
|
+
const legacyConfig = join2(profile.legacyStateDir ?? "/etc/forgezero/guests", `${name}.conf`);
|
|
2086
2120
|
const legacyDropIn = join2(profile.unitDir, `${service}.d`);
|
|
2087
2121
|
if (existsSync5(legacyConfig))
|
|
2088
2122
|
unlinkSync4(legacyConfig);
|
|
@@ -2679,7 +2713,7 @@ async function applyMetalIsolation(profile, exec = defaultExec) {
|
|
|
2679
2713
|
}
|
|
2680
2714
|
|
|
2681
2715
|
// src/version.ts
|
|
2682
|
-
var VERSION = "0.1.
|
|
2716
|
+
var VERSION = "0.1.14";
|
|
2683
2717
|
|
|
2684
2718
|
// src/index.ts
|
|
2685
2719
|
function loadOrCreateSeed(path) {
|
package/dist/fz.js
CHANGED
|
@@ -728,12 +728,13 @@ function guestBootstrapScript(profile, attested = Boolean(profile.confidential),
|
|
|
728
728
|
const agentBun = "/usr/local/lib/forgezero/bun";
|
|
729
729
|
const attestationSetup = attested ? `# The report device is not part of the encryption path, so a guest can appear
|
|
730
730
|
# healthy and encrypted while attestation is silently impossible. Install and
|
|
731
|
-
# load the
|
|
732
|
-
|
|
731
|
+
# load the driver shipped by the one pinned image before the agent starts. Do
|
|
732
|
+
# not ask an evolving apt repository for an exact old kernel package: the image
|
|
733
|
+
# can keep booting after Canonical removes that package from the mirror.
|
|
734
|
+
if [[ ! -c /dev/sev-guest ]]; then modprobe sev-guest; fi
|
|
735
|
+
test -c /dev/sev-guest
|
|
733
736
|
printf 'sev-guest
|
|
734
737
|
' >/etc/modules-load.d/sev-guest.conf
|
|
735
|
-
modprobe sev-guest
|
|
736
|
-
test -c /dev/sev-guest
|
|
737
738
|
` : "";
|
|
738
739
|
return `#!/usr/bin/env bash
|
|
739
740
|
set -Eeuo pipefail
|
|
@@ -922,7 +923,7 @@ ethernets:
|
|
|
922
923
|
primary:
|
|
923
924
|
match: { name: "en*" }
|
|
924
925
|
addresses: [ ${manifest.address}/24 ]
|
|
925
|
-
|
|
926
|
+
routes: [ { to: default, via: ${profile.gateway} } ]
|
|
926
927
|
nameservers: { addresses: [${(profile.nameservers ?? ["1.1.1.1", "9.9.9.9"]).join(", ")}] }
|
|
927
928
|
`
|
|
928
929
|
};
|
|
@@ -1041,15 +1042,48 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
1041
1042
|
save();
|
|
1042
1043
|
return { guestAddress: address };
|
|
1043
1044
|
}
|
|
1045
|
+
function legacyPlatformManifest(profile, claim, name) {
|
|
1046
|
+
if (claim.bootstrap?.kind !== "platform-genesis" || claim.computeKey !== `platform:${name}` || claim.spec.reference !== `platform:${name}` || claim.spec.guestAddress === undefined)
|
|
1047
|
+
return;
|
|
1048
|
+
const legacyDir = profile.legacyStateDir ?? "/etc/forgezero/guests";
|
|
1049
|
+
const legacyPath = join(legacyDir, `${name}.conf`);
|
|
1050
|
+
if (!existsSync(legacyPath))
|
|
1051
|
+
return;
|
|
1052
|
+
const values = new Map;
|
|
1053
|
+
for (const line of readFileSync(legacyPath, "utf8").split(/\r?\n/)) {
|
|
1054
|
+
if (!line || line.startsWith("#"))
|
|
1055
|
+
continue;
|
|
1056
|
+
const match = /^([A-Z][A-Z0-9_]*)=([^\s'"`$;|&<>]+)$/.exec(line);
|
|
1057
|
+
if (!match)
|
|
1058
|
+
throw new MetalProvisionError("legacy guest inventory contains unsafe syntax");
|
|
1059
|
+
values.set(match[1], match[2]);
|
|
1060
|
+
}
|
|
1061
|
+
const disk = `/dev/${profile.volumeGroup}/${name}`;
|
|
1062
|
+
if (values.get("NAME") !== name || values.get("DISK") !== disk || values.get("IP") !== claim.spec.guestAddress)
|
|
1063
|
+
throw new MetalProvisionError("legacy guest inventory does not match the platform claim");
|
|
1064
|
+
return {
|
|
1065
|
+
computeKey: claim.computeKey,
|
|
1066
|
+
reference: claim.spec.reference,
|
|
1067
|
+
name,
|
|
1068
|
+
address: claim.spec.guestAddress,
|
|
1069
|
+
mac: values.get("MAC") ?? "",
|
|
1070
|
+
cpuPoolKey: claim.spec.cpuPoolKey ?? "",
|
|
1071
|
+
allowedCpus: values.get("CPUSET") ?? "",
|
|
1072
|
+
allowedMemoryNodes: values.get("NUMA"),
|
|
1073
|
+
phase: "running",
|
|
1074
|
+
disk,
|
|
1075
|
+
unit: `forgezero-guest@${name}.service`
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1044
1078
|
async function removeMetalGuest(profile, claim, exec) {
|
|
1045
1079
|
validateMetalProfile(profile);
|
|
1046
1080
|
if (claim.action !== "delete")
|
|
1047
1081
|
throw new MetalProvisionError("create claim cannot remove a guest");
|
|
1048
1082
|
const name = claim.spec.guestName ?? guestNameFor(claim.computeKey);
|
|
1049
1083
|
const manifestPath = join(profile.stateDir, `${name}.json`);
|
|
1050
|
-
|
|
1084
|
+
const manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
1085
|
+
if (!manifest)
|
|
1051
1086
|
return {};
|
|
1052
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
1053
1087
|
const currentIdentity = manifest.computeKey === claim.computeKey && manifest.reference === claim.spec.reference && manifest.name === name;
|
|
1054
1088
|
const legacyPlatformIdentity = claim.bootstrap?.kind === "platform-genesis" && claim.computeKey === `platform:${name}` && claim.spec.reference === `platform:${name}` && manifest.name === name && manifest.disk === `/dev/${profile.volumeGroup}/${name}` && manifest.address === claim.spec.guestAddress && (manifest.unit === `fz-guest@${name}.service` || manifest.unit === `forgezero-guest@${name}.service`);
|
|
1055
1089
|
if (!currentIdentity && !legacyPlatformIdentity) {
|
|
@@ -1078,7 +1112,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
1078
1112
|
if (existsSync(path))
|
|
1079
1113
|
unlinkSync(path);
|
|
1080
1114
|
if (legacyPlatformIdentity) {
|
|
1081
|
-
const legacyConfig =
|
|
1115
|
+
const legacyConfig = join(profile.legacyStateDir ?? "/etc/forgezero/guests", `${name}.conf`);
|
|
1082
1116
|
const legacyDropIn = join(profile.unitDir, `${service}.d`);
|
|
1083
1117
|
if (existsSync(legacyConfig))
|
|
1084
1118
|
unlinkSync(legacyConfig);
|
|
@@ -24,6 +24,8 @@ export interface MetalProvisionProfile {
|
|
|
24
24
|
stateDir: string;
|
|
25
25
|
seedDir: string;
|
|
26
26
|
unitDir: string;
|
|
27
|
+
/** One-time compatibility source for pre-Agent platform guests. */
|
|
28
|
+
legacyStateDir?: string;
|
|
27
29
|
apiUrl: string;
|
|
28
30
|
images: Record<string, MetalImage>;
|
|
29
31
|
/** Explicit host-observed pools. No pool means no dynamic guest authority. */
|
|
@@ -81,5 +83,4 @@ export declare function cloudInit(profile: MetalProvisionProfile, claim: CreateR
|
|
|
81
83
|
};
|
|
82
84
|
/** The only root operation the metal agent may request. */
|
|
83
85
|
export declare function provisionMetalGuest(profile: MetalProvisionProfile, claim: CreateRemoteProvisionClaim, exec: MetalExec): Promise<ProvisionResult>;
|
|
84
|
-
/** Idempotently remove every host-owned artifact before capacity is returned. */
|
|
85
86
|
export declare function removeMetalGuest(profile: MetalProvisionProfile, claim: RemoteProvisionClaim, exec: MetalExec): Promise<ProvisionResult>;
|
package/dist/metal-provision.js
CHANGED
|
@@ -728,12 +728,13 @@ function guestBootstrapScript(profile, attested = Boolean(profile.confidential),
|
|
|
728
728
|
const agentBun = "/usr/local/lib/forgezero/bun";
|
|
729
729
|
const attestationSetup = attested ? `# The report device is not part of the encryption path, so a guest can appear
|
|
730
730
|
# healthy and encrypted while attestation is silently impossible. Install and
|
|
731
|
-
# load the
|
|
732
|
-
|
|
731
|
+
# load the driver shipped by the one pinned image before the agent starts. Do
|
|
732
|
+
# not ask an evolving apt repository for an exact old kernel package: the image
|
|
733
|
+
# can keep booting after Canonical removes that package from the mirror.
|
|
734
|
+
if [[ ! -c /dev/sev-guest ]]; then modprobe sev-guest; fi
|
|
735
|
+
test -c /dev/sev-guest
|
|
733
736
|
printf 'sev-guest
|
|
734
737
|
' >/etc/modules-load.d/sev-guest.conf
|
|
735
|
-
modprobe sev-guest
|
|
736
|
-
test -c /dev/sev-guest
|
|
737
738
|
` : "";
|
|
738
739
|
return `#!/usr/bin/env bash
|
|
739
740
|
set -Eeuo pipefail
|
|
@@ -922,7 +923,7 @@ ethernets:
|
|
|
922
923
|
primary:
|
|
923
924
|
match: { name: "en*" }
|
|
924
925
|
addresses: [ ${manifest.address}/24 ]
|
|
925
|
-
|
|
926
|
+
routes: [ { to: default, via: ${profile.gateway} } ]
|
|
926
927
|
nameservers: { addresses: [${(profile.nameservers ?? ["1.1.1.1", "9.9.9.9"]).join(", ")}] }
|
|
927
928
|
`
|
|
928
929
|
};
|
|
@@ -1041,15 +1042,48 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
1041
1042
|
save();
|
|
1042
1043
|
return { guestAddress: address };
|
|
1043
1044
|
}
|
|
1045
|
+
function legacyPlatformManifest(profile, claim, name) {
|
|
1046
|
+
if (claim.bootstrap?.kind !== "platform-genesis" || claim.computeKey !== `platform:${name}` || claim.spec.reference !== `platform:${name}` || claim.spec.guestAddress === undefined)
|
|
1047
|
+
return;
|
|
1048
|
+
const legacyDir = profile.legacyStateDir ?? "/etc/forgezero/guests";
|
|
1049
|
+
const legacyPath = join(legacyDir, `${name}.conf`);
|
|
1050
|
+
if (!existsSync(legacyPath))
|
|
1051
|
+
return;
|
|
1052
|
+
const values = new Map;
|
|
1053
|
+
for (const line of readFileSync(legacyPath, "utf8").split(/\r?\n/)) {
|
|
1054
|
+
if (!line || line.startsWith("#"))
|
|
1055
|
+
continue;
|
|
1056
|
+
const match = /^([A-Z][A-Z0-9_]*)=([^\s'"`$;|&<>]+)$/.exec(line);
|
|
1057
|
+
if (!match)
|
|
1058
|
+
throw new MetalProvisionError("legacy guest inventory contains unsafe syntax");
|
|
1059
|
+
values.set(match[1], match[2]);
|
|
1060
|
+
}
|
|
1061
|
+
const disk = `/dev/${profile.volumeGroup}/${name}`;
|
|
1062
|
+
if (values.get("NAME") !== name || values.get("DISK") !== disk || values.get("IP") !== claim.spec.guestAddress)
|
|
1063
|
+
throw new MetalProvisionError("legacy guest inventory does not match the platform claim");
|
|
1064
|
+
return {
|
|
1065
|
+
computeKey: claim.computeKey,
|
|
1066
|
+
reference: claim.spec.reference,
|
|
1067
|
+
name,
|
|
1068
|
+
address: claim.spec.guestAddress,
|
|
1069
|
+
mac: values.get("MAC") ?? "",
|
|
1070
|
+
cpuPoolKey: claim.spec.cpuPoolKey ?? "",
|
|
1071
|
+
allowedCpus: values.get("CPUSET") ?? "",
|
|
1072
|
+
allowedMemoryNodes: values.get("NUMA"),
|
|
1073
|
+
phase: "running",
|
|
1074
|
+
disk,
|
|
1075
|
+
unit: `forgezero-guest@${name}.service`
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1044
1078
|
async function removeMetalGuest(profile, claim, exec) {
|
|
1045
1079
|
validateMetalProfile(profile);
|
|
1046
1080
|
if (claim.action !== "delete")
|
|
1047
1081
|
throw new MetalProvisionError("create claim cannot remove a guest");
|
|
1048
1082
|
const name = claim.spec.guestName ?? guestNameFor(claim.computeKey);
|
|
1049
1083
|
const manifestPath = join(profile.stateDir, `${name}.json`);
|
|
1050
|
-
|
|
1084
|
+
const manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
1085
|
+
if (!manifest)
|
|
1051
1086
|
return {};
|
|
1052
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
1053
1087
|
const currentIdentity = manifest.computeKey === claim.computeKey && manifest.reference === claim.spec.reference && manifest.name === name;
|
|
1054
1088
|
const legacyPlatformIdentity = claim.bootstrap?.kind === "platform-genesis" && claim.computeKey === `platform:${name}` && claim.spec.reference === `platform:${name}` && manifest.name === name && manifest.disk === `/dev/${profile.volumeGroup}/${name}` && manifest.address === claim.spec.guestAddress && (manifest.unit === `fz-guest@${name}.service` || manifest.unit === `forgezero-guest@${name}.service`);
|
|
1055
1089
|
if (!currentIdentity && !legacyPlatformIdentity) {
|
|
@@ -1078,7 +1112,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
1078
1112
|
if (existsSync(path))
|
|
1079
1113
|
unlinkSync(path);
|
|
1080
1114
|
if (legacyPlatformIdentity) {
|
|
1081
|
-
const legacyConfig =
|
|
1115
|
+
const legacyConfig = join(profile.legacyStateDir ?? "/etc/forgezero/guests", `${name}.conf`);
|
|
1082
1116
|
const legacyDropIn = join(profile.unitDir, `${service}.d`);
|
|
1083
1117
|
if (existsSync(legacyConfig))
|
|
1084
1118
|
unlinkSync(legacyConfig);
|
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.
|
|
2
|
+
export declare const VERSION = "0.1.14";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
|
|
3
3
|
"name": "@forgezero/agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.14",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"check": "tsc --noEmit",
|