@forgezero/agent 0.1.125 → 0.1.129

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 CHANGED
@@ -214,7 +214,7 @@ fz unlock --phrase-file /secure/offline-phrase.txt
214
214
 
215
215
  ## Use website operations from a project or an AI agent
216
216
 
217
- Every UI operation is an API operation. `fz ui routes --json` discovers the exact actions allowed by the signed-in account’s current realm, lifecycle stage and grants from the API’s enforced matrix, so the CLI and AI agents carry no copied route list. `fz api` (also `fz ui`) executes authenticated JSON GET, POST, PUT, PATCH and DELETE without requiring a browser visit for ordinary work. Paths are forced onto the signed-in origin, query values are repeatable, bodies can be inline, stdin or a file, and server grants plus fresh-proof rules remain authoritative. `fz project init|sync|check` separately gives AI tools one Git-persisted project memory.
217
+ Every UI operation is an API operation. `fz ui routes --json` discovers the exact actions allowed by the signed-in account’s current workspace, lifecycle stage and permissions from the API’s enforced matrix, so the CLI and AI agents carry no copied route list. `fz api` (also `fz ui`) executes authenticated JSON GET, POST, PUT, PATCH and DELETE without requiring a browser visit for ordinary work. Paths are forced onto the signed-in origin, query values are repeatable, bodies can be inline, stdin or a file, and server permissions plus fresh-proof rules remain authoritative. `fz project init|sync|check` separately gives AI tools one Git-persisted project memory.
218
218
 
219
219
  ```text
220
220
  fz ui routes --json
@@ -917,7 +917,7 @@ async function postSignedNode(options, path, body) {
917
917
  }
918
918
 
919
919
  // src/version.ts
920
- var VERSION3 = "0.1.125";
920
+ var VERSION3 = "0.1.129";
921
921
 
922
922
  // src/agent-heartbeat.ts
923
923
  function readAgentHostMetrics() {
@@ -123,7 +123,9 @@ export interface EnrolledComputeBootstrapSecrets {
123
123
  }
124
124
  export type BootstrapSecrets = PlatformBootstrapSecrets | EnrolledComputeBootstrapSecrets;
125
125
  export declare function validateEnrolledComputeBootstrapSecrets(config: EnrolledComputeActivationConfig, input: unknown): EnrolledComputeBootstrapSecrets;
126
- export declare function validatePlatformBootstrapSecrets(config: PlatformBootstrapConfig, input: unknown): PlatformBootstrapSecrets;
126
+ export declare function validatePlatformBootstrapSecrets(config: PlatformBootstrapConfig, input: unknown, options?: {
127
+ allowStoredCloudflareCredentials?: boolean;
128
+ }): PlatformBootstrapSecrets;
127
129
  export interface BootstrapStep {
128
130
  id: string;
129
131
  label: string;
@@ -208,6 +210,7 @@ export declare function interruptedPlatformReleaseIdentityDigests(config: Platfo
208
210
  export declare function bootstrapStatus(host?: BootstrapHost): Promise<BootstrapStatus>;
209
211
  export declare function applyBootstrap(input: BootstrapConfig, host?: BootstrapHost, secrets?: BootstrapSecrets, dependencies?: {
210
212
  fetcher?: typeof fetch;
213
+ reuseBoundPlatformCredentials?: boolean;
211
214
  }): Promise<BootstrapResult>;
212
215
  export declare function readBootstrapConfig(path: string): BootstrapConfig;
213
216
  interface BootstrapAgentInitialBundle {
package/dist/bootstrap.js CHANGED
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.125";
1463
+ var VERSION = "0.1.129";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3805,7 +3805,7 @@ function validateEnrolledComputeBootstrapSecrets(config, input) {
3805
3805
  ...cloudflareConfigured ? validateCloudflareBootstrapSecretPair(source) : {}
3806
3806
  };
3807
3807
  }
3808
- function validatePlatformBootstrapSecrets(config, input) {
3808
+ function validatePlatformBootstrapSecrets(config, input, options = {}) {
3809
3809
  if (!input || typeof input !== "object" || Array.isArray(input))
3810
3810
  throw new Error("bootstrap credential input must be an object");
3811
3811
  const source = input;
@@ -3833,7 +3833,8 @@ function validatePlatformBootstrapSecrets(config, input) {
3833
3833
  throw new Error("backup credential is malformed");
3834
3834
  }
3835
3835
  const cloudflareConfigured = Boolean(config.cloudflareHandoff || config.runtime.environment.cloudflare);
3836
- if (cloudflareConfigured !== Boolean(cloudflareTunnelToken && cloudflareApiToken)) {
3836
+ const cloudflareCredentialsSupplied = Boolean(cloudflareTunnelToken || cloudflareApiToken);
3837
+ if (!cloudflareConfigured && cloudflareCredentialsSupplied || cloudflareConfigured && !cloudflareCredentialsSupplied && !options.allowStoredCloudflareCredentials || Boolean(cloudflareTunnelToken) !== Boolean(cloudflareApiToken)) {
3837
3838
  throw new Error("Cloudflare configuration requires attended CF_TUNNEL_TOKEN and CF_API_TOKEN together");
3838
3839
  }
3839
3840
  if (cloudflareTunnelToken)
@@ -4756,10 +4757,16 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4756
4757
  }
4757
4758
  allowInterruptedReleaseRebind = !exactIdentity && (boundedReleaseResume || approvedIdentityMigration);
4758
4759
  }
4759
- const platformPrivate = config.kind === "platform" ? (() => {
4760
+ const reuseBoundPlatformCredentials = config.kind === "platform" && dependencies.reuseBoundPlatformCredentials === true;
4761
+ if (reuseBoundPlatformCredentials && (!installed || !host.exists("/var/lib/forgezero/enrolment.json"))) {
4762
+ throw new Error("stored-credential promotion requires an installed enrolled platform compute");
4763
+ }
4764
+ const platformPrivate = config.kind === "platform" && !reuseBoundPlatformCredentials ? (() => {
4760
4765
  if (!secrets)
4761
4766
  throw new Error("platform apply requires attended credentials on stdin");
4762
- const checked4 = validatePlatformBootstrapSecrets(config, secrets);
4767
+ const checked4 = validatePlatformBootstrapSecrets(config, secrets, {
4768
+ allowStoredCloudflareCredentials: Boolean(installed?.cloudflare && config.cloudflareHandoff && !cloudflare)
4769
+ });
4763
4770
  return {
4764
4771
  root: checked4.clusterBootstrapCode,
4765
4772
  email: checked4.emailSecret,
@@ -4797,7 +4804,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4797
4804
  if (cloudflarePrivate && !host.exists(CF_API_CREDENTIAL)) {
4798
4805
  await seal(host, "CF_API_TOKEN", CF_API_CREDENTIAL, cloudflarePrivate.cloudflareApiToken);
4799
4806
  }
4800
- if (config.kind === "platform" && platformPrivate.cloudflareTunnelToken && !host.exists(CF_TUNNEL_API_CREDENTIAL)) {
4807
+ if (config.kind === "platform" && platformPrivate?.cloudflareTunnelToken && !host.exists(CF_TUNNEL_API_CREDENTIAL)) {
4801
4808
  await seal(host, "CF_TUNNEL_TOKEN", CF_TUNNEL_API_CREDENTIAL, platformPrivate.cloudflareTunnelToken);
4802
4809
  }
4803
4810
  const realtimeSecrets = cloudflare?.realtime && cloudflarePrivate ? deriveCloudflareRealtimeSecrets(cloudflarePrivate.cloudflareApiToken) : undefined;
@@ -4855,24 +4862,38 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4855
4862
  await verifyBootstrapBundleOnHost(host, config, true);
4856
4863
  }
4857
4864
  if (config.kind === "platform") {
4858
- const root = platformPrivate.root;
4859
- if (!host.exists(JWT_CREDENTIAL)) {
4860
- await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
4861
- }
4862
- if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
4863
- await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
4864
- }
4865
- await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
4866
- await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
4867
4865
  const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
4868
- for (const [name, source] of Object.entries({
4869
- ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
4870
- "backup.s3.secretAccessKey": platformPrivate.backup
4871
- })) {
4872
- if (source) {
4873
- const destination2 = `${CREDS}/${name}.cred`;
4874
- if (!host.exists(destination2)) {
4875
- await seal(host, name, destination2, source);
4866
+ if (reuseBoundPlatformCredentials) {
4867
+ const required = [
4868
+ ...config.database.role !== "none" ? [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL] : [],
4869
+ SEED_CREDENTIAL,
4870
+ BACKUP_RECOVERY_CREDENTIAL,
4871
+ ...emailCredentialName ? [`${CREDS}/${emailCredentialName}.cred`] : [],
4872
+ ...config.runtime.environment.backup ? [`${CREDS}/backup.s3.secretAccessKey.cred`] : [],
4873
+ ...installed?.cloudflare ? [CF_API_CREDENTIAL, CF_TUNNEL_API_CREDENTIAL, TUNNEL_CREDENTIAL] : [],
4874
+ ...installed?.realtime ? [REALTIME_PUBLISH_CREDENTIAL, REALTIME_TICKET_CREDENTIAL] : []
4875
+ ];
4876
+ const missing = required.find((path) => !host.exists(path));
4877
+ if (missing)
4878
+ throw new Error(`stored platform credential is missing: ${missing}`);
4879
+ } else {
4880
+ const root = platformPrivate.root;
4881
+ if (!host.exists(JWT_CREDENTIAL)) {
4882
+ await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
4883
+ }
4884
+ if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
4885
+ await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
4886
+ }
4887
+ await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
4888
+ await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
4889
+ for (const [name, source] of Object.entries({
4890
+ ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
4891
+ "backup.s3.secretAccessKey": platformPrivate.backup
4892
+ })) {
4893
+ if (source) {
4894
+ const destination2 = `${CREDS}/${name}.cred`;
4895
+ if (!host.exists(destination2))
4896
+ await seal(host, name, destination2, source);
4876
4897
  }
4877
4898
  }
4878
4899
  }
@@ -5037,7 +5058,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
5037
5058
  host.remove(config.cloudflareHandoff.handoffFile);
5038
5059
  }
5039
5060
  let launch;
5040
- if (config.kind === "platform" && config.database.role === "master") {
5061
+ if (config.kind === "platform" && config.database.role === "master" && !reuseBoundPlatformCredentials) {
5041
5062
  const invitePath = `${config.runtime.environment.sharedDirectory}/platform-invite.token`;
5042
5063
  if (!host.exists(invitePath))
5043
5064
  throw new Error("platform invite is missing after deployment");
package/dist/fz-agent.js CHANGED
@@ -4049,7 +4049,7 @@ function combineKEMS(realSeedLen, realMsgLen, expandSeed, combiner, ...kems) {
4049
4049
  var x25519kem = /* @__PURE__ */ ecdhKem(x25519);
4050
4050
  var ml_kem768_x25519 = /* @__PURE__ */ (() => combineKEMS(32, 32, expandSeedXof(shake256), (pk, ct, ss) => sha3_256(concatBytes2(ss[0], ss[1], ct[1], pk[1], asciiToBytes("\\.//^\\"))), ml_kem768, x25519kem))();
4051
4051
 
4052
- // ../../node_modules/.bun/@forgezero+runtime@0.1.17+cd212ec15d36da0a/node_modules/@forgezero/runtime/dist/identity.js
4052
+ // ../runtime/dist/identity.js
4053
4053
  var ENCODER = new TextEncoder;
4054
4054
  var b64 = toBase64Url;
4055
4055
  var un64 = fromBase64Url;
@@ -4196,7 +4196,7 @@ function signRequest(keys, nodeKey, args) {
4196
4196
  };
4197
4197
  }
4198
4198
 
4199
- // ../../node_modules/.bun/@forgezero+vault@0.1.22+3f759277d50bc01a/node_modules/@forgezero/vault/dist/index.js
4199
+ // ../vault/dist/index.js
4200
4200
  var DEFAULT_SOCKET = "/run/forgezero/vault.sock";
4201
4201
 
4202
4202
  // src/socket.ts
@@ -4692,7 +4692,7 @@ import { chmodSync as chmodSync4, existsSync as existsSync4, lstatSync as lstatS
4692
4692
  import { createHash as createHash5, randomUUID } from "crypto";
4693
4693
  import { dirname as dirname3, isAbsolute as isAbsolute2, join as join2 } from "path";
4694
4694
 
4695
- // ../../node_modules/.bun/@forgezero+runtime@0.1.17+cd212ec15d36da0a/node_modules/@forgezero/runtime/dist/queue.js
4695
+ // ../runtime/dist/queue.js
4696
4696
  class QueueStoppedError extends Error {
4697
4697
  constructor() {
4698
4698
  super("queue: stopped before this task could run");
@@ -9640,9 +9640,47 @@ async function writeAndCloseProcessInput(input, value) {
9640
9640
  }
9641
9641
 
9642
9642
  // src/version.ts
9643
- var VERSION2 = "0.1.125";
9643
+ var VERSION2 = "0.1.129";
9644
9644
 
9645
9645
  // src/ssh-bootstrap.ts
9646
+ function deriveMetalAdmissionProvisioning(lscpu) {
9647
+ const rows = lscpu.split(`
9648
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
9649
+ const fields = line.split(",");
9650
+ if (fields.length !== 4 || fields.some((field) => !/^\d+$/.test(field))) {
9651
+ throw new SshBootstrapError("METAL_CPU_TOPOLOGY_INVALID");
9652
+ }
9653
+ return { cpu: Number(fields[0]), core: `${fields[2]}:${fields[1]}` };
9654
+ });
9655
+ if (rows.length < 2 || new Set(rows.map(({ cpu }) => cpu)).size !== rows.length) {
9656
+ throw new SshBootstrapError("METAL_CPU_TOPOLOGY_INVALID");
9657
+ }
9658
+ const cores = new Map;
9659
+ for (const row2 of rows)
9660
+ cores.set(row2.core, [...cores.get(row2.core) ?? [], row2.cpu]);
9661
+ if (cores.size < 2)
9662
+ throw new SshBootstrapError("METAL_CPU_CAPACITY_INSUFFICIENT");
9663
+ const ordered = [...cores.values()].map((cpus2) => cpus2.sort((a, b) => a - b)).sort((left, right) => left[0] - right[0]);
9664
+ const threadsPerCore = ordered[0].length;
9665
+ if (threadsPerCore < 1 || threadsPerCore > 8 || ordered.some((cpus2) => cpus2.length !== threadsPerCore)) {
9666
+ throw new SshBootstrapError("METAL_CPU_TOPOLOGY_INVALID");
9667
+ }
9668
+ const linuxList = (values) => [...values].sort((a, b) => a - b).join(",");
9669
+ const housekeeping = ordered.shift();
9670
+ const guestCpus = Array.from({ length: threadsPerCore }, (_, thread) => ordered.map((cpus2) => cpus2[thread])).flat();
9671
+ return {
9672
+ volumeGroup: "fzvg",
9673
+ bridge: "fzbr0",
9674
+ subnetPrefix: "10.42.0",
9675
+ addressStart: 20,
9676
+ addressEnd: 200,
9677
+ gateway: "10.42.0.1",
9678
+ nameservers: ["1.1.1.1", "1.0.0.1"],
9679
+ cpuPools: [{ key: "host-capacity", cpus: linuxList(guestCpus), physicalCores: ordered.length }],
9680
+ housekeepingCpus: linuxList(housekeeping)
9681
+ };
9682
+ }
9683
+
9646
9684
  class SshBootstrapError extends Error {
9647
9685
  code;
9648
9686
  constructor(code, message = code) {
@@ -9655,7 +9693,7 @@ var defaultExec = async (argv2, options = {}) => {
9655
9693
  stdin: options.stdin === undefined ? "ignore" : "pipe",
9656
9694
  stdout: "pipe",
9657
9695
  stderr: "pipe",
9658
- env: { PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin", LANG: "C", LC_ALL: "C" }
9696
+ env: { PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin", LANG: "C", LC_ALL: "C", ...options.env }
9659
9697
  });
9660
9698
  if (options.stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
9661
9699
  await writeAndCloseProcessInput(child.stdin, options.stdin);
@@ -9677,12 +9715,12 @@ var validateClaim = (claim) => {
9677
9715
  if (!/^[A-Za-z0-9_-]{1,64}$/.test(claim.computeKey) || !/^[A-Za-z0-9_-]{16,128}$/.test(claim.claimToken) || !/^fze_[A-Za-z0-9_-]{32,128}$/.test(claim.enrolmentToken) || !/^[a-z0-9][a-z0-9_-]{0,62}$/.test(claim.realm) || !/^[A-Za-z0-9_-]{1,64}$/.test(claim.projectKey) || !/^[A-Za-z0-9_-]{1,64}$/.test(claim.environmentKey))
9678
9716
  throw new SshBootstrapError("CLAIM_INVALID");
9679
9717
  const { host, port, user, hostKeySha256, nodeHostname, operatingSystem } = claim.target;
9680
- if (!host || host.length > 253 || /[\s/@]/.test(host) || !Number.isSafeInteger(port) || port < 1 || port > 65535 || !/^[a-z_][a-z0-9_-]{0,31}$/.test(user) || !/^SHA256:[A-Za-z0-9+/]{43}$/.test(hostKeySha256) || !/^[a-z0-9](?:[a-z0-9.-]{1,251}[a-z0-9])$/.test(nodeHostname) || !nodeHostname.includes(".")) {
9718
+ if (!host || host.length > 253 || /[\s/@]/.test(host) || !Number.isSafeInteger(port) || port < 1 || port > 65535 || !/^[a-z_][a-z0-9_-]{0,31}$/.test(user) || hostKeySha256 !== "" && !/^SHA256:[A-Za-z0-9+/]{43}$/.test(hostKeySha256) || !/^[a-z0-9](?:[a-z0-9.-]{1,251}[a-z0-9])$/.test(nodeHostname) || !nodeHostname.includes(".")) {
9681
9719
  throw new SshBootstrapError("CLAIM_INVALID");
9682
9720
  }
9683
9721
  if (operatingSystem?.id !== "ubuntu" || !["24.04", "26.04"].includes(operatingSystem.version) || operatingSystem.architecture !== "x64")
9684
9722
  throw new SshBootstrapError("CLAIM_INVALID");
9685
- if (!claim.target.credential || claim.target.credential.source !== "runner" && claim.target.credential.source !== "vault" || claim.target.credential.source === "vault" && (typeof claim.target.credential.privateKey !== "string" || claim.target.credential.privateKey.length > 32768 || !claim.target.credential.privateKey.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----") || !claim.target.credential.privateKey.trimEnd().endsWith("-----END OPENSSH PRIVATE KEY-----"))) {
9723
+ if (!/^ssh-(?:ed25519|rsa|ecdsa-[^\s]+) [A-Za-z0-9+/]+={0,3}(?:\s+.*)?$/.test(claim.runnerSshPublicKey) || !claim.target.credential || claim.target.credential.source !== "runner" && claim.target.credential.source !== "vault" || claim.target.credential.source === "vault" && (claim.target.credential.kind === "private-key" && (typeof claim.target.credential.privateKey !== "string" || claim.target.credential.privateKey.length > 32768 || !claim.target.credential.privateKey.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----") || !claim.target.credential.privateKey.trimEnd().endsWith("-----END OPENSSH PRIVATE KEY-----")) || claim.target.credential.kind === "password" && (typeof claim.target.credential.password !== "string" || claim.target.credential.password.length < 1 || claim.target.credential.password.length > 4096))) {
9686
9724
  throw new SshBootstrapError("CLAIM_INVALID");
9687
9725
  }
9688
9726
  };
@@ -9693,14 +9731,7 @@ var target = (claim, pinned) => {
9693
9731
  var sshOptions = (claim, pinned, knownHosts, keyPath) => [
9694
9732
  "-F",
9695
9733
  "/dev/null",
9696
- "-o",
9697
- "BatchMode=yes",
9698
- "-o",
9699
- "IdentitiesOnly=yes",
9700
- "-o",
9701
- `IdentityFile=${keyPath}`,
9702
- "-o",
9703
- "IdentityAgent=none",
9734
+ ...claim.target.credential.source === "vault" && claim.target.credential.kind === "password" ? ["-o", "BatchMode=no", "-o", "IdentitiesOnly=yes", "-o", "PubkeyAuthentication=no", "-o", "PreferredAuthentications=password,keyboard-interactive", "-o", "NumberOfPasswordPrompts=1"] : ["-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", "-o", `IdentityFile=${keyPath}`, "-o", "IdentityAgent=none"],
9704
9735
  "-o",
9705
9736
  `UserKnownHostsFile=${knownHosts}`,
9706
9737
  "-o",
@@ -9745,11 +9776,14 @@ async function pinnedKnownHost(claim, pinned, directory, exec) {
9745
9776
  const candidates = scanned.split(`
9746
9777
  `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
9747
9778
  let matched;
9779
+ let observedFingerprint = "";
9748
9780
  for (const line of candidates) {
9749
- const fingerprint = await checked(exec, ["ssh-keygen", "-E", "sha256", "-lf", "-"], "HOST_KEY_INVALID", { stdin: `${line}
9781
+ const result = await checked(exec, ["ssh-keygen", "-E", "sha256", "-lf", "-"], "HOST_KEY_INVALID", { stdin: `${line}
9750
9782
  ` });
9751
- if (fingerprint.split(/\s+/).includes(claim.target.hostKeySha256)) {
9783
+ const fingerprint = result.split(/\s+/).find((part) => /^SHA256:[A-Za-z0-9+/]{43}$/.test(part)) ?? "";
9784
+ if (fingerprint && (!claim.target.hostKeySha256 || fingerprint === claim.target.hostKeySha256)) {
9752
9785
  matched = line;
9786
+ observedFingerprint = fingerprint;
9753
9787
  break;
9754
9788
  }
9755
9789
  }
@@ -9761,7 +9795,7 @@ async function pinnedKnownHost(claim, pinned, directory, exec) {
9761
9795
  const path2 = join3(directory, "known_hosts");
9762
9796
  writeFileSync5(path2, `${pinned.hostKeyAlias} ${key}
9763
9797
  `, { mode: 384, flag: "wx" });
9764
- return path2;
9798
+ return { path: path2, fingerprint: observedFingerprint };
9765
9799
  }
9766
9800
  var BUN_RELEASE_URL = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-linux-x64.zip";
9767
9801
  var verifiedBunArchive = async (directory) => {
@@ -9787,28 +9821,57 @@ async function executeSshBootstrap(claim, options) {
9787
9821
  const api = new URL(options.platformApiUrl);
9788
9822
  if (api.protocol !== "https:" || api.username || api.password || api.search || api.hash)
9789
9823
  throw new SshBootstrapError("TARGET_API_INVALID");
9790
- const exec = options.exec ?? defaultExec;
9824
+ const baseExec = options.exec ?? defaultExec;
9791
9825
  const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-ssh-bootstrap-"));
9792
9826
  chmodSync7(directory, 448);
9827
+ let exec = baseExec;
9793
9828
  let sshKeyPath = options.sshKeyPath;
9794
9829
  let remoteDirectory = "";
9795
9830
  let pinned;
9796
9831
  try {
9797
- if (claim.target.credential.source === "vault") {
9832
+ if (claim.target.credential.source === "vault" && claim.target.credential.kind === "private-key") {
9798
9833
  sshKeyPath = join3(directory, "identity");
9799
9834
  writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
9800
9835
  `, { mode: 384, flag: "wx" });
9801
9836
  }
9837
+ if (claim.target.credential.source === "vault" && claim.target.credential.kind === "password") {
9838
+ const askpass = join3(directory, "askpass");
9839
+ writeFileSync5(askpass, `#!/usr/bin/env bun
9840
+ process.stdout.write(process.env.FZ_SSH_PASSWORD ?? "")
9841
+ `, { mode: 448, flag: "wx" });
9842
+ const password = claim.target.credential.password;
9843
+ exec = (argv2, command = {}) => baseExec(argv2, {
9844
+ ...command,
9845
+ env: { ...command.env, DISPLAY: "forgezero:0", SSH_ASKPASS: askpass, SSH_ASKPASS_REQUIRE: "force", FZ_SSH_PASSWORD: password }
9846
+ });
9847
+ }
9802
9848
  const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
9803
9849
  const resolved = await resolvePinnedGitTarget(`ssh://${claim.target.user}@${sourceHost}:${claim.target.port}/`, options.resolveHost);
9804
9850
  if (!resolved || resolved.protocol !== "ssh")
9805
9851
  throw new SshBootstrapError("TARGET_ADDRESS_REFUSED");
9806
9852
  pinned = resolved;
9807
- const knownHosts = await pinnedKnownHost(claim, pinned, directory, exec);
9853
+ const observedHost = await pinnedKnownHost(claim, pinned, directory, exec);
9854
+ claim.target.hostKeySha256 = observedHost.fingerprint;
9855
+ const knownHosts = observedHost.path;
9808
9856
  const ssh = sshOptions(claim, pinned, knownHosts, sshKeyPath);
9809
9857
  remoteDirectory = await checked(exec, ["ssh", ...ssh, target(claim, pinned), "--", "mktemp", "-d", "/tmp/forgezero-bootstrap.XXXXXXXX"], "SSH_UNREACHABLE");
9810
9858
  if (!/^\/tmp\/forgezero-bootstrap\.[A-Za-z0-9]{8}$/.test(remoteDirectory))
9811
9859
  throw new SshBootstrapError("REMOTE_PATH_INVALID");
9860
+ const destination = target(claim, pinned);
9861
+ const passwd = await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/getent", "passwd", claim.target.user]);
9862
+ const home = passwd.split(":")[5] ?? "";
9863
+ if (!home.startsWith("/") || /[\r\n]/.test(home))
9864
+ throw new SshBootstrapError("SSH_IDENTITY_INSTALL_FAILED");
9865
+ await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-d", "-m", "0700", `${home}/.ssh`]);
9866
+ const existing = await remote(exec, ssh, destination, "SSH_IDENTITY_READ_FAILED", ["/usr/bin/cat", `${home}/.ssh/authorized_keys`]).catch(() => "");
9867
+ const authorized = [...new Set([...existing.split(`
9868
+ `).map((line) => line.trim()).filter(Boolean), claim.runnerSshPublicKey])].join(`
9869
+ `) + `
9870
+ `;
9871
+ const authorizedPath = join3(directory, "authorized_keys");
9872
+ writeFileSync5(authorizedPath, authorized, { mode: 384, flag: "wx" });
9873
+ await checked(exec, ["scp", ...scpOptions(claim, pinned, knownHosts, sshKeyPath), authorizedPath, `${destination}:${remoteDirectory}/authorized_keys`], "SSH_IDENTITY_INSTALL_FAILED");
9874
+ await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-m", "0600", `${remoteDirectory}/authorized_keys`, `${home}/.ssh/authorized_keys`]);
9812
9875
  const config = {
9813
9876
  kind: "enrolled-compute",
9814
9877
  operatingSystem: claim.target.operatingSystem,
@@ -9844,7 +9907,6 @@ async function executeSshBootstrap(claim, options) {
9844
9907
  join3(directory, name),
9845
9908
  `${target(claim, pinned)}:${remoteDirectory}/${name}`
9846
9909
  ], "SCP_FAILED", { secret: name === "enrol.token" });
9847
- const destination = target(claim, pinned);
9848
9910
  const version = await remote(exec, ssh, destination, "REMOTE_BUN_CHECK_FAILED", ["/usr/local/bin/bun", "--version"]).catch(() => "");
9849
9911
  if (version !== PINNED_BUN_VERSION) {
9850
9912
  await remote(exec, ssh, destination, "REMOTE_PREREQUISITES_FAILED", [
@@ -10093,6 +10155,7 @@ var validateMetalClaim = (claim) => {
10093
10155
  realm: claim.tenantKey,
10094
10156
  projectKey: claim.projectKey,
10095
10157
  environmentKey: claim.environmentKey,
10158
+ runnerSshPublicKey: claim.runnerSshPublicKey,
10096
10159
  target: claim.target,
10097
10160
  enrolmentToken: `fze_${"A".repeat(43)}`
10098
10161
  });
@@ -10104,29 +10167,64 @@ async function executeMetalAdmission(claim, options) {
10104
10167
  if (api.protocol !== "https:" || api.username || api.password || api.search || api.hash || telemetry.protocol !== "https:" || telemetry.username || telemetry.password || telemetry.search || telemetry.hash) {
10105
10168
  throw new SshBootstrapError("TARGET_COORDINATE_INVALID");
10106
10169
  }
10107
- const exec = options.exec ?? defaultExec;
10170
+ const baseExec = options.exec ?? defaultExec;
10108
10171
  const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-metal-admission-"));
10109
10172
  chmodSync7(directory, 448);
10173
+ let exec = baseExec;
10110
10174
  let sshKeyPath = options.sshKeyPath;
10111
10175
  let remoteDirectory = "";
10112
10176
  let pinned;
10113
10177
  try {
10114
- if (claim.target.credential.source === "vault") {
10178
+ if (claim.target.credential.source === "vault" && claim.target.credential.kind === "private-key") {
10115
10179
  sshKeyPath = join3(directory, "identity");
10116
10180
  writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
10117
10181
  `, { mode: 384, flag: "wx" });
10118
10182
  }
10183
+ if (claim.target.credential.source === "vault" && claim.target.credential.kind === "password") {
10184
+ const askpass = join3(directory, "askpass");
10185
+ writeFileSync5(askpass, `#!/usr/bin/env bun
10186
+ process.stdout.write(process.env.FZ_SSH_PASSWORD ?? "")
10187
+ `, { mode: 448, flag: "wx" });
10188
+ const password = claim.target.credential.password;
10189
+ exec = (argv2, command = {}) => baseExec(argv2, {
10190
+ ...command,
10191
+ env: { ...command.env, DISPLAY: "forgezero:0", SSH_ASKPASS: askpass, SSH_ASKPASS_REQUIRE: "force", FZ_SSH_PASSWORD: password }
10192
+ });
10193
+ }
10119
10194
  const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
10120
10195
  const resolved = await resolvePinnedGitTarget(`ssh://${claim.target.user}@${sourceHost}:${claim.target.port}/`, options.resolveHost);
10121
10196
  if (!resolved || resolved.protocol !== "ssh")
10122
10197
  throw new SshBootstrapError("TARGET_ADDRESS_REFUSED");
10123
10198
  pinned = resolved;
10124
- const knownHosts = await pinnedKnownHost(claim, pinned, directory, exec);
10199
+ const observedHost = await pinnedKnownHost(claim, pinned, directory, exec);
10200
+ claim.target.hostKeySha256 = observedHost.fingerprint;
10201
+ const knownHosts = observedHost.path;
10125
10202
  const ssh = sshOptions(claim, pinned, knownHosts, sshKeyPath);
10126
10203
  const destination = target(claim, pinned);
10127
10204
  remoteDirectory = await checked(exec, ["ssh", ...ssh, destination, "--", "mktemp", "-d", "/tmp/forgezero-metal-admission.XXXXXXXX"], "SSH_UNREACHABLE");
10128
10205
  if (!/^\/tmp\/forgezero-metal-admission\.[A-Za-z0-9]{8}$/.test(remoteDirectory))
10129
10206
  throw new SshBootstrapError("REMOTE_PATH_INVALID");
10207
+ const passwd = await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/getent", "passwd", claim.target.user]);
10208
+ const home = passwd.split(":")[5] ?? "";
10209
+ if (!home.startsWith("/") || /[\r\n]/.test(home))
10210
+ throw new SshBootstrapError("SSH_IDENTITY_INSTALL_FAILED");
10211
+ await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-d", "-m", "0700", `${home}/.ssh`]);
10212
+ const existing = await remote(exec, ssh, destination, "SSH_IDENTITY_READ_FAILED", ["/usr/bin/cat", `${home}/.ssh/authorized_keys`]).catch(() => "");
10213
+ const authorized = [...new Set([...existing.split(`
10214
+ `).map((line) => line.trim()).filter(Boolean), claim.runnerSshPublicKey])].join(`
10215
+ `) + `
10216
+ `;
10217
+ const authorizedPath = join3(directory, "authorized_keys");
10218
+ writeFileSync5(authorizedPath, authorized, { mode: 384, flag: "wx" });
10219
+ await checked(exec, ["scp", ...scpOptions(claim, pinned, knownHosts, sshKeyPath), authorizedPath, `${destination}:${remoteDirectory}/authorized_keys`], "SSH_IDENTITY_INSTALL_FAILED");
10220
+ await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-m", "0600", `${remoteDirectory}/authorized_keys`, `${home}/.ssh/authorized_keys`]);
10221
+ await remote(exec, ssh, destination, "METAL_STORAGE_UNAVAILABLE", ["/usr/bin/sudo", "-n", "/usr/sbin/vgs", "fzvg"]);
10222
+ await remote(exec, ssh, destination, "METAL_NETWORK_UNAVAILABLE", ["/usr/bin/sudo", "-n", "/usr/sbin/ip", "link", "show", "fzbr0"]);
10223
+ const topology = await remote(exec, ssh, destination, "METAL_CPU_TOPOLOGY_INVALID", [
10224
+ "/usr/bin/lscpu",
10225
+ "--parse=CPU,CORE,SOCKET,NODE"
10226
+ ]);
10227
+ const provisioning = deriveMetalAdmissionProvisioning(topology);
10130
10228
  const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
10131
10229
  const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
10132
10230
  for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
@@ -10145,7 +10243,7 @@ async function executeMetalAdmission(claim, options) {
10145
10243
  hostTelemetryEndpoint: "http://127.0.0.1:4318",
10146
10244
  hostTelemetryUnit: "forgezero-otel-collector.service",
10147
10245
  profile: {
10148
- ...claim.provisioning,
10246
+ ...provisioning,
10149
10247
  stateDir: "/etc/forgezero/metal-guests",
10150
10248
  seedDir: "/var/lib/forgezero/seed",
10151
10249
  unitDir: "/etc/systemd/system",
@@ -11363,7 +11461,7 @@ function requestDeploymentCommand(input, socketPath = DEFAULT_DEPLOYMENT_RUNNER_
11363
11461
  // src/snp-attestation.ts
11364
11462
  import { existsSync as existsSync10 } from "fs";
11365
11463
 
11366
- // ../../node_modules/.bun/@forgezero+runtime@0.1.17+cd212ec15d36da0a/node_modules/@forgezero/runtime/dist/snp.js
11464
+ // ../runtime/dist/snp.js
11367
11465
  var REPORT_BYTES = 1184;
11368
11466
 
11369
11467
  // src/snp-attestation.ts
@@ -15302,6 +15400,26 @@ var nonEmpty = (path2) => {
15302
15400
  if (!existsSync20(path2) || !statSync5(path2).isFile() || statSync5(path2).size < 1)
15303
15401
  throw new Error(`required file missing: ${path2}`);
15304
15402
  };
15403
+ function validateRecoveryBootstrapState(value, expected) {
15404
+ if (!value || typeof value !== "object" || Array.isArray(value))
15405
+ throw new Error("bootstrap state is malformed");
15406
+ const state = value;
15407
+ if (state.format !== 2 || state.kind !== "platform" || state.environment !== expected.profile || state.databaseRole !== expected.role || state.profile !== expected.software || state.apiUrl !== expected.apiOrigin || state.nodeHostname !== expected.edgeHostname || expected.databaseAddress !== undefined && state.databaseAddress !== expected.databaseAddress)
15408
+ throw new Error("bootstrap state does not match reviewed recovery coordinates");
15409
+ }
15410
+ var exactBootstrapState = (expected) => {
15411
+ const path2 = "/var/lib/forgezero/bootstrap.json";
15412
+ nonEmpty(path2);
15413
+ if (statSync5(path2).size > 64 * 1024)
15414
+ throw new Error("bootstrap state is oversized");
15415
+ let value;
15416
+ try {
15417
+ value = JSON.parse(readFileSync15(path2, "utf8"));
15418
+ } catch {
15419
+ throw new Error("bootstrap state is malformed");
15420
+ }
15421
+ validateRecoveryBootstrapState(value, expected);
15422
+ };
15305
15423
  var option = (args, name) => {
15306
15424
  const value = args.find((arg) => arg.startsWith(`--${name}=`))?.slice(name.length + 3);
15307
15425
  if (!value || /[\0\r\n]/.test(value))
@@ -15325,15 +15443,11 @@ async function runRecoveryHost(args, run2 = execute2) {
15325
15443
  const apiOrigin = option(args, "api-origin");
15326
15444
  const edgeHostname = option(args, "edge-hostname");
15327
15445
  const databaseAddress = args.find((arg) => arg.startsWith("--db-address="))?.slice(13);
15328
- exactState("/opt/forgezero/.forge-state", `profile=${profile}`);
15329
- exactState("/opt/forgezero/.forge-state", `db_role=${role}`);
15330
- exactState("/opt/forgezero/.forge-state", `software_profile=${software}`);
15331
- if (databaseAddress)
15332
- exactState("/opt/forgezero/.forge-state", `db_address=${databaseAddress}`);
15333
- exactState("/opt/forgezero/shared/.env", "ARANGO_DB=fz");
15334
- exactState("/opt/forgezero/shared/.env", "FZ_DATABASE_MODE=platform");
15335
- exactState("/opt/forgezero/shared/.env", `API_ORIGIN=${apiOrigin}`);
15336
- exactState("/opt/forgezero/shared/.env", `FZ_NODE_HOSTNAME=${edgeHostname}`);
15446
+ exactBootstrapState({ profile, role, software, apiOrigin, edgeHostname, ...databaseAddress ? { databaseAddress } : {} });
15447
+ exactState("/opt/forgezero/shared/.env", 'ARANGO_DB="fz"');
15448
+ exactState("/opt/forgezero/shared/.env", 'FZ_DATABASE_MODE="platform"');
15449
+ exactState("/opt/forgezero/shared/.env", `API_ORIGIN="${apiOrigin}"`);
15450
+ exactState("/opt/forgezero/shared/.env", `FZ_NODE_HOSTNAME="${edgeHostname}"`);
15337
15451
  nonEmpty("/etc/forgezero/creds/arangodb-jwt.cred");
15338
15452
  const hasDatabase = role !== "none";
15339
15453
  if (hasDatabase) {
@@ -15377,7 +15491,7 @@ async function runRecoveryHost(args, run2 = execute2) {
15377
15491
  throw new Error("maintenance accepts only fz db argv");
15378
15492
  const slot = activeSlot();
15379
15493
  const properties = [
15380
- "--property=User=forgezero",
15494
+ "--property=User=forgezero-api",
15381
15495
  `--property=WorkingDirectory=/opt/forgezero/slots/${slot}`,
15382
15496
  "--property=Environment=NODE_ENV=production",
15383
15497
  "--property=EnvironmentFile=/opt/forgezero/shared/.env",