@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 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 exact running-kernel module before the agent is allowed to start.
1736
- DEBIAN_FRONTEND=noninteractive apt-get install -y linux-image-generic "linux-modules-extra-$(uname -r)"
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
- gateway4: ${profile.gateway}
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
- if (!existsSync5(manifestPath))
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 = `/etc/forgezero/guests/${name}.conf`;
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.12";
2716
+ var VERSION = "0.1.14";
2683
2717
 
2684
2718
  // src/index.ts
2685
2719
  function loadOrCreateSeed(path) {
package/dist/fz.js CHANGED
@@ -781,7 +781,7 @@ async function resolveIdentity(selector, socketPath) {
781
781
  }
782
782
 
783
783
  // src/version.ts
784
- var VERSION = "0.1.12";
784
+ var VERSION = "0.1.14";
785
785
 
786
786
  // src/cli/index.ts
787
787
  var DEFAULT_MODE = THRESHOLD_MODES[0].id;
@@ -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 exact running-kernel module before the agent is allowed to start.
732
- DEBIAN_FRONTEND=noninteractive apt-get install -y linux-image-generic "linux-modules-extra-$(uname -r)"
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
- gateway4: ${profile.gateway}
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
- if (!existsSync(manifestPath))
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 = `/etc/forgezero/guests/${name}.conf`;
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>;
@@ -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 exact running-kernel module before the agent is allowed to start.
732
- DEBIAN_FRONTEND=noninteractive apt-get install -y linux-image-generic "linux-modules-extra-$(uname -r)"
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
- gateway4: ${profile.gateway}
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
- if (!existsSync(manifestPath))
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 = `/etc/forgezero/guests/${name}.conf`;
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.12";
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.12",
4
+ "version": "0.1.14",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "check": "tsc --noEmit",