@forgezero/agent 0.1.103 → 0.1.107

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.js CHANGED
@@ -4634,7 +4634,7 @@ var VaultError, runtimeEnvironment = () => typeof process !== "undefined" && pro
4634
4634
  } catch {
4635
4635
  return;
4636
4636
  }
4637
- }, VERSION = "0.1.19";
4637
+ }, VERSION = "0.1.20";
4638
4638
  var init_dist = __esm(() => {
4639
4639
  init_identity();
4640
4640
  VaultError = class VaultError extends Error {
@@ -4810,9 +4810,9 @@ async function spawnWith(command, env, report = () => {}, options = {}) {
4810
4810
  }
4811
4811
 
4812
4812
  // src/cli/index.ts
4813
- import { existsSync as existsSync13, lstatSync as lstatSync10, mkdirSync as mkdirSync13, readFileSync as readFileSync16, renameSync as renameSync10, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
4813
+ import { existsSync as existsSync13, lstatSync as lstatSync11, mkdirSync as mkdirSync13, readFileSync as readFileSync17, renameSync as renameSync10, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
4814
4814
  import { randomBytes as randomBytes10 } from "crypto";
4815
- import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join13, resolve as resolve12 } from "path";
4815
+ import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join13, resolve as resolve13 } from "path";
4816
4816
 
4817
4817
  // src/process-input.ts
4818
4818
  async function writeAndCloseProcessInput(input, value) {
@@ -4841,7 +4841,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
4841
4841
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
4842
4842
 
4843
4843
  // src/version.ts
4844
- var VERSION2 = "0.1.103";
4844
+ var VERSION2 = "0.1.107";
4845
4845
 
4846
4846
  // src/software.ts
4847
4847
  var PINNED_BUN_VERSION = "1.3.14";
@@ -12496,6 +12496,7 @@ function platformApiCredentialSpecs(options) {
12496
12496
  ];
12497
12497
  return [
12498
12498
  { name: "arangodb-jwt", encryptedPath: "/etc/forgezero/creds/arangodb-jwt.cred", required: true },
12499
+ { name: "arangodb-root-password", encryptedPath: "/etc/forgezero/creds/arangodb-root-password.cred", required: true },
12499
12500
  { name: "seed-sync-root", encryptedPath: "/etc/forgezero/creds/seed-sync-root.cred", required: true },
12500
12501
  ...optional.filter(([, present]) => present).map(([name]) => ({
12501
12502
  name,
@@ -14079,6 +14080,46 @@ async function buildBootstrapBundle(input, exec = run) {
14079
14080
  throw cause;
14080
14081
  }
14081
14082
  }
14083
+ async function refreshBootstrapBundle(input, exec = run) {
14084
+ const outputPath = resolve6(input.outputPath);
14085
+ const manifestPath = `${outputPath}.json`;
14086
+ if (existsSync8(outputPath) !== existsSync8(manifestPath)) {
14087
+ throw new Error("bootstrap bundle and manifest must either both exist or both be absent");
14088
+ }
14089
+ const candidatePath = `${outputPath}.candidate.${process.pid}.${randomBytes6(6).toString("hex")}`;
14090
+ const candidate = await buildBootstrapBundle({ ...input, outputPath: candidatePath }, exec);
14091
+ try {
14092
+ if (!existsSync8(outputPath)) {
14093
+ renameSync7(candidate.bundlePath, outputPath);
14094
+ renameSync7(candidate.manifestPath, manifestPath);
14095
+ return readBootstrapBundle(outputPath);
14096
+ }
14097
+ const current2 = await readBootstrapBundle(outputPath);
14098
+ if (current2.manifest.branch === candidate.manifest.branch && current2.manifest.revision === candidate.manifest.revision) {
14099
+ rmSync5(candidate.bundlePath, { force: true });
14100
+ rmSync5(candidate.manifestPath, { force: true });
14101
+ return current2;
14102
+ }
14103
+ const archivePath = `${outputPath}.before-${current2.manifest.revision.slice(0, 7)}-${Date.now()}`;
14104
+ const archiveManifestPath = `${archivePath}.json`;
14105
+ renameSync7(outputPath, archivePath);
14106
+ renameSync7(manifestPath, archiveManifestPath);
14107
+ try {
14108
+ renameSync7(candidate.bundlePath, outputPath);
14109
+ renameSync7(candidate.manifestPath, manifestPath);
14110
+ return readBootstrapBundle(outputPath);
14111
+ } catch (cause) {
14112
+ rmSync5(outputPath, { force: true });
14113
+ rmSync5(manifestPath, { force: true });
14114
+ renameSync7(archivePath, outputPath);
14115
+ renameSync7(archiveManifestPath, manifestPath);
14116
+ throw cause;
14117
+ }
14118
+ } finally {
14119
+ rmSync5(candidate.bundlePath, { force: true });
14120
+ rmSync5(candidate.manifestPath, { force: true });
14121
+ }
14122
+ }
14082
14123
 
14083
14124
  // src/bootstrap.ts
14084
14125
  var PLATFORM_BOOTSTRAP_PROFILES = [
@@ -14199,6 +14240,7 @@ var STATE_PATH = BOOTSTRAP_STATE_PATH;
14199
14240
  var INTENT_PATH = "/var/lib/forgezero/bootstrap.intent.json";
14200
14241
  var CREDS = "/etc/forgezero/creds";
14201
14242
  var JWT_CREDENTIAL = `${CREDS}/arangodb-jwt.cred`;
14243
+ var ARANGO_ROOT_CREDENTIAL = `${CREDS}/arangodb-root-password.cred`;
14202
14244
  var ENROL_CREDENTIAL = `${CREDS}/enrol-token.cred`;
14203
14245
  var TUNNEL_CREDENTIAL = `${CREDS}/CF_TUNNEL_CONNECTOR_TOKEN.cred`;
14204
14246
  var CF_API_CREDENTIAL = `${CREDS}/CF_API_TOKEN.cred`;
@@ -14484,7 +14526,7 @@ WantedBy=multi-user.target
14484
14526
  function databaseVerifyUnit(config) {
14485
14527
  const { address } = config.database;
14486
14528
  return `[Unit]
14487
- Description=Verify ForgeZero ArangoDB Community 3.11.14 writable Coordinator
14529
+ Description=Secure and verify ForgeZero ArangoDB Community 3.11.14 writable Coordinator
14488
14530
  Requires=forgezero-db.service
14489
14531
  After=forgezero-db.service
14490
14532
  PartOf=forgezero-db.service
@@ -14494,7 +14536,9 @@ Type=oneshot
14494
14536
  User=arangodb
14495
14537
  Group=arangodb
14496
14538
  LoadCredentialEncrypted=arangodb-jwt:${JWT_CREDENTIAL}
14497
- ExecStart=/usr/bin/arangosh --server.endpoint tcp://${unitEscape(address)}:8529 --server.jwt-secret-keyfile %d/arangodb-jwt --javascript.execute-string 'const c=require("@arangodb").db._connection;let last;let ok=false;for(let i=0;i<90;i++){try{const v=c.GET("/_api/version?details=true");const s=c.GET("/_admin/status");const m=c.GET("/_admin/server/mode");if(!v.error&&v.version==="3.11.14"&&v.details&&v.details.license==="community"&&!s.error&&s.serverInfo&&s.serverInfo.role==="COORDINATOR"&&!m.error&&m.mode==="default"){ok=true;break;}last={v,s,m};}catch(e){last=String(e);}require("internal").wait(2);}if(!ok)throw new Error("writable Community Coordinator verification failed: "+JSON.stringify(last));'
14539
+ LoadCredentialEncrypted=arangodb-root-password:${ARANGO_ROOT_CREDENTIAL}
14540
+ ExecStart=/usr/bin/arangosh --server.endpoint tcp://${unitEscape(address)}:8529 --server.jwt-secret-keyfile %d/arangodb-jwt --javascript.execute-string 'const c=require("@arangodb").db._connection;let last;let ok=false;for(let i=0;i<90;i++){try{const v=c.GET("/_api/version?details=true");const s=c.GET("/_admin/status");const m=c.GET("/_admin/server/mode");if(!v.error&&v.version==="3.11.14"&&v.details&&v.details.license==="community"&&!s.error&&s.serverInfo&&s.serverInfo.role==="COORDINATOR"&&!m.error&&m.mode==="default"){ok=true;break;}last={v,s,m};}catch(e){last=String(e);}require("internal").wait(2);}if(!ok)throw new Error("writable Community Coordinator verification failed: "+JSON.stringify(last));const password=require("fs").read("%d/arangodb-root-password").trim();if(password.length<48)throw new Error("ArangoDB root credential is malformed");require("@arangodb/users").update("root",password,true);'
14541
+ ExecStart=/usr/local/bin/fz-agent database-auth-verify --endpoint=http://${unitEscape(address)}:8529
14498
14542
  RemainAfterExit=yes
14499
14543
  TimeoutStartSec=200
14500
14544
  NoNewPrivileges=true
@@ -14961,6 +15005,11 @@ async function bootstrapStatus(host = localBootstrapHost()) {
14961
15005
  if (nginx.exitCode !== 0)
14962
15006
  problems.push("nginx configuration is invalid");
14963
15007
  if (state.databaseRole !== "none") {
15008
+ for (const credential of [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL]) {
15009
+ services[credential] = host.exists(credential);
15010
+ if (!services[credential])
15011
+ problems.push(`${credential} is missing`);
15012
+ }
14964
15013
  const unitPath = "/etc/systemd/system/forgezero-db.service";
14965
15014
  const expectsNoAgency = state.databaseAgency === "none";
14966
15015
  const unitHasNoAgency = host.exists(unitPath) && host.read(unitPath).includes("--cluster.start-agent=false");
@@ -15195,6 +15244,9 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15195
15244
  if (!host.exists(JWT_CREDENTIAL)) {
15196
15245
  await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
15197
15246
  }
15247
+ if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
15248
+ await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
15249
+ }
15198
15250
  await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
15199
15251
  await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
15200
15252
  const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
@@ -15478,7 +15530,11 @@ function strictBootstrapDocument(value) {
15478
15530
  ], "runtime environment");
15479
15531
  const environment = runtime.environment;
15480
15532
  if (environment.initialInventory !== undefined) {
15481
- const inventory = exactKeys2(environment.initialInventory, ["metalHostname", "region", "computes", "attestation", "deployment"], "initial inventory");
15533
+ const inventory = exactKeys2(environment.initialInventory, ["metalHostname", "metalIdentity", "region", "computes", "attestation", "deployment"], "initial inventory");
15534
+ if (inventory.metalIdentity !== undefined) {
15535
+ const identity = exactKeys2(inventory.metalIdentity, ["nodeKey", "publicKeys"], "initial Metal identity");
15536
+ exactKeys2(identity.publicKeys, ["ed25519", "mlDsa"], "initial Metal public keys");
15537
+ }
15482
15538
  exactKeys2(inventory.region, ["key", "label", "country", "city", "confidentialCapable"], "initial inventory region");
15483
15539
  if (inventory.attestation !== undefined) {
15484
15540
  const attestation = exactKeys2(inventory.attestation, ["measurement", "tcbFloor"], "initial attestation evidence");
@@ -18535,6 +18591,92 @@ function planAppBuild(input) {
18535
18591
  };
18536
18592
  }
18537
18593
 
18594
+ // src/platform-launch-env.ts
18595
+ import { lstatSync as lstatSync10, readFileSync as readFileSync16 } from "fs";
18596
+ import { resolve as resolve12 } from "path";
18597
+ var booleanValue = (values, name, fallback) => {
18598
+ const raw = values.get(name)?.trim().toLowerCase();
18599
+ if (raw === undefined || raw === "")
18600
+ return fallback;
18601
+ if (raw === "true")
18602
+ return true;
18603
+ if (raw === "false")
18604
+ return false;
18605
+ throw new Error(`${name} must be true or false`);
18606
+ };
18607
+ function readPlatformLaunchEnvironment(path) {
18608
+ const absolute2 = resolve12(path);
18609
+ const stat2 = lstatSync10(absolute2);
18610
+ if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.nlink !== 1 || typeof process.getuid === "function" && stat2.uid !== process.getuid() || (stat2.mode & 63) !== 0) {
18611
+ throw new Error(`bootstrap secret environment must be one owner-only regular file: ${absolute2}`);
18612
+ }
18613
+ const values = new Map;
18614
+ for (const [index, raw] of readFileSync16(absolute2, "utf8").split(/\r?\n/).entries()) {
18615
+ const line = raw.trim();
18616
+ if (!line || line.startsWith("#"))
18617
+ continue;
18618
+ const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
18619
+ if (!match)
18620
+ throw new Error(`invalid environment assignment at ${absolute2}:${index + 1}`);
18621
+ if (values.has(match[1]))
18622
+ throw new Error(`duplicate environment assignment ${match[1]} at ${absolute2}:${index + 1}`);
18623
+ let value = match[2].trim();
18624
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
18625
+ value = value.slice(1, -1);
18626
+ }
18627
+ values.set(match[1], value);
18628
+ }
18629
+ const requiredValue = (name) => {
18630
+ const value = values.get(name)?.trim();
18631
+ if (!value)
18632
+ throw new Error(`bootstrap secret environment is missing ${name}`);
18633
+ return value;
18634
+ };
18635
+ const provider = requiredValue("FZ_EMAIL_PROVIDER");
18636
+ if (provider !== "smtp" && provider !== "jetemail")
18637
+ throw new Error("FZ_EMAIL_PROVIDER must be smtp or jetemail");
18638
+ const smtpPort = provider === "smtp" ? Number(requiredValue("FZ_SMTP_PORT")) : undefined;
18639
+ if (smtpPort !== undefined && (!Number.isInteger(smtpPort) || smtpPort < 1 || smtpPort > 65535)) {
18640
+ throw new Error("FZ_SMTP_PORT must be an integer from 1 through 65535");
18641
+ }
18642
+ const githubEnabled = booleanValue(values, "FZ_GITHUB_ENABLED", false);
18643
+ const githubApp = githubEnabled ? {
18644
+ clientId: requiredValue("FZ_GITHUB_CLIENT_ID"),
18645
+ appId: requiredValue("FZ_GITHUB_APP_ID"),
18646
+ slug: requiredValue("FZ_GITHUB_APP_SLUG")
18647
+ } : undefined;
18648
+ const owner = provider === "jetemail" ? {
18649
+ custodianEmail: requiredValue("FZ_BOOTSTRAP_CUSTODIAN_EMAIL"),
18650
+ email: {
18651
+ provider,
18652
+ from: requiredValue("FZ_EMAIL_FROM"),
18653
+ eu: booleanValue(values, "FZ_JETEMAIL_EU", false)
18654
+ },
18655
+ ...githubApp ? { githubApp } : {}
18656
+ } : {
18657
+ custodianEmail: requiredValue("FZ_BOOTSTRAP_CUSTODIAN_EMAIL"),
18658
+ email: {
18659
+ provider,
18660
+ from: requiredValue("FZ_EMAIL_FROM"),
18661
+ host: requiredValue("FZ_SMTP_HOST"),
18662
+ port: smtpPort,
18663
+ user: requiredValue("FZ_SMTP_USER")
18664
+ },
18665
+ ...githubApp ? { githubApp } : {}
18666
+ };
18667
+ return {
18668
+ owner,
18669
+ emailSecret: requiredValue(provider === "jetemail" ? "FZ_JETEMAIL_API_KEY" : "FZ_SMTP_PASSWORD"),
18670
+ cloudflareTunnelToken: requiredValue("CF_TUNNEL_TOKEN"),
18671
+ cloudflareApiToken: requiredValue("CF_API_TOKEN"),
18672
+ ...githubEnabled ? {
18673
+ githubClientSecret: requiredValue("FZ_GITHUB_CLIENT_SECRET"),
18674
+ githubPrivateKeyBase64: requiredValue("FZ_GITHUB_PRIVATE_KEY_BASE64"),
18675
+ githubWebhookSecret: requiredValue("FZ_GITHUB_WEBHOOK_SECRET")
18676
+ } : {}
18677
+ };
18678
+ }
18679
+
18538
18680
  // src/cli/index.ts
18539
18681
  var DEFAULT_MODE = (THRESHOLD_MODES.find((mode) => mode.threshold === 1 && mode.total === 1) ?? THRESHOLD_MODES[0]).id;
18540
18682
  var PACKAGED_AGENT_BIN2 = operatorPackagedAgentPath();
@@ -18847,7 +18989,7 @@ async function api(options, path, init) {
18847
18989
  return { status: response.status, body };
18848
18990
  }
18849
18991
  function sleep(ms) {
18850
- return new Promise((resolve13) => setTimeout(resolve13, ms));
18992
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
18851
18993
  }
18852
18994
  function browserCommand(url) {
18853
18995
  if (process.platform === "darwin")
@@ -18932,7 +19074,7 @@ function requireSession() {
18932
19074
  function readOwnerOnlySecret(path, label) {
18933
19075
  let metadata;
18934
19076
  try {
18935
- metadata = lstatSync10(path);
19077
+ metadata = lstatSync11(path);
18936
19078
  } catch {
18937
19079
  throw new Error(`The ${label} file ${path} is unreadable.`);
18938
19080
  }
@@ -18946,7 +19088,7 @@ function readOwnerOnlySecret(path, label) {
18946
19088
  if (uid !== undefined && uid !== 0 && metadata.uid !== uid) {
18947
19089
  throw new Error(`The ${label} file ${path} is not owned by the current user.`);
18948
19090
  }
18949
- const value = readFileSync16(path, "utf8").trim();
19091
+ const value = readFileSync17(path, "utf8").trim();
18950
19092
  if (!value)
18951
19093
  throw new Error(`The ${label} file ${path} is empty.`);
18952
19094
  return value;
@@ -19127,7 +19269,7 @@ async function cmdApi(options, args) {
19127
19269
  let body = undefined;
19128
19270
  if (options.data !== undefined && options.dataFile)
19129
19271
  throw new Error("Use only one of --data or --data-file.");
19130
- const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync16(options.dataFile, "utf8") : options.data;
19272
+ const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync17(options.dataFile, "utf8") : options.data;
19131
19273
  if (encoded !== undefined)
19132
19274
  body = JSON.parse(encoded);
19133
19275
  const result = await api(options, `${url.pathname}${url.search}`, { method, body });
@@ -19306,7 +19448,7 @@ async function cmdAgent(options, args) {
19306
19448
  out.line();
19307
19449
  out.line(" Compatibility SSH deploy public key:");
19308
19450
  out.line();
19309
- out.line(` ${readFileSync16(gitIdentity.gitPublicKeyPath, "utf8").trim()}`);
19451
+ out.line(` ${readFileSync17(gitIdentity.gitPublicKeyPath, "utf8").trim()}`);
19310
19452
  out.line();
19311
19453
  }
19312
19454
  return 0;
@@ -19346,78 +19488,6 @@ async function bootstrapSecret(question) {
19346
19488
  throw new Error(`${question} was not provided`);
19347
19489
  return value;
19348
19490
  }
19349
- function readPlatformLaunchEnvironment(path) {
19350
- const absolute2 = resolve12(path);
19351
- const stat2 = lstatSync10(absolute2);
19352
- if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.nlink !== 1 || typeof process.getuid === "function" && stat2.uid !== process.getuid() || (stat2.mode & 63) !== 0) {
19353
- throw new Error(`bootstrap secret environment must be one owner-only regular file: ${absolute2}`);
19354
- }
19355
- const values = new Map;
19356
- for (const [index, raw] of readFileSync16(absolute2, "utf8").split(/\r?\n/).entries()) {
19357
- const line = raw.trim();
19358
- if (!line || line.startsWith("#"))
19359
- continue;
19360
- const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
19361
- if (!match)
19362
- throw new Error(`invalid environment assignment at ${absolute2}:${index + 1}`);
19363
- if (values.has(match[1]))
19364
- throw new Error(`duplicate environment assignment ${match[1]} at ${absolute2}:${index + 1}`);
19365
- let value = match[2].trim();
19366
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
19367
- value = value.slice(1, -1);
19368
- }
19369
- values.set(match[1], value);
19370
- }
19371
- const requiredValue = (name) => {
19372
- const value = values.get(name)?.trim();
19373
- if (!value)
19374
- throw new Error(`bootstrap secret environment is missing ${name}`);
19375
- return value;
19376
- };
19377
- const provider = requiredValue("FZ_EMAIL_PROVIDER");
19378
- if (provider !== "smtp" && provider !== "jetemail")
19379
- throw new Error("FZ_EMAIL_PROVIDER must be smtp or jetemail");
19380
- const smtpPort = provider === "smtp" ? Number(requiredValue("FZ_SMTP_PORT")) : undefined;
19381
- if (smtpPort !== undefined && (!Number.isInteger(smtpPort) || smtpPort < 1 || smtpPort > 65535)) {
19382
- throw new Error("FZ_SMTP_PORT must be an integer from 1 through 65535");
19383
- }
19384
- const githubEnabled = (values.get("FZ_GITHUB_ENABLED") ?? "false").trim().toLowerCase() === "true";
19385
- const githubApp = githubEnabled ? {
19386
- clientId: requiredValue("FZ_GITHUB_CLIENT_ID"),
19387
- appId: requiredValue("FZ_GITHUB_APP_ID"),
19388
- slug: requiredValue("FZ_GITHUB_APP_SLUG")
19389
- } : undefined;
19390
- const owner = provider === "jetemail" ? {
19391
- custodianEmail: requiredValue("FZ_BOOTSTRAP_CUSTODIAN_EMAIL"),
19392
- email: {
19393
- provider,
19394
- from: requiredValue("FZ_EMAIL_FROM"),
19395
- eu: (values.get("FZ_JETEMAIL_EU") ?? "false").trim().toLowerCase() === "true"
19396
- },
19397
- ...githubApp ? { githubApp } : {}
19398
- } : {
19399
- custodianEmail: requiredValue("FZ_BOOTSTRAP_CUSTODIAN_EMAIL"),
19400
- email: {
19401
- provider,
19402
- from: requiredValue("FZ_EMAIL_FROM"),
19403
- host: requiredValue("FZ_SMTP_HOST"),
19404
- port: smtpPort,
19405
- user: requiredValue("FZ_SMTP_USER")
19406
- },
19407
- ...githubApp ? { githubApp } : {}
19408
- };
19409
- return {
19410
- owner,
19411
- emailSecret: requiredValue(provider === "jetemail" ? "FZ_JETEMAIL_API_KEY" : "FZ_SMTP_PASSWORD"),
19412
- cloudflareTunnelToken: requiredValue("CF_TUNNEL_TOKEN"),
19413
- cloudflareApiToken: requiredValue("CF_API_TOKEN"),
19414
- ...githubEnabled ? {
19415
- githubClientSecret: requiredValue("FZ_GITHUB_CLIENT_SECRET"),
19416
- githubPrivateKeyBase64: requiredValue("FZ_GITHUB_PRIVATE_KEY_BASE64"),
19417
- githubWebhookSecret: requiredValue("FZ_GITHUB_WEBHOOK_SECRET")
19418
- } : {}
19419
- };
19420
- }
19421
19491
  async function promptPlatformBootstrapSecrets(config, generatedClusterBootstrapCode) {
19422
19492
  return validatePlatformBootstrapSecrets(config, {
19423
19493
  clusterBootstrapCode: generatedClusterBootstrapCode ?? await bootstrapSecret("Shared 64-hex cluster bootstrap code"),
@@ -19493,7 +19563,7 @@ function interactiveMetalBootstrap() {
19493
19563
  });
19494
19564
  }
19495
19565
  function writeBootstrapConfig(path, config) {
19496
- if (!isAbsolute7(path) || resolve12(path) !== path)
19566
+ if (!isAbsolute7(path) || resolve13(path) !== path)
19497
19567
  throw new Error("--output must be a canonical absolute path");
19498
19568
  mkdirSync13(dirname14(path), { recursive: true, mode: 448 });
19499
19569
  writeFileSync13(path, `${JSON.stringify(config, null, 2)}
@@ -19557,11 +19627,11 @@ async function interactiveCloudflareBootstrap() {
19557
19627
  });
19558
19628
  }
19559
19629
  function genesisOutputDirectory(path) {
19560
- if (!isAbsolute7(path) || resolve12(path) !== path)
19630
+ if (!isAbsolute7(path) || resolve13(path) !== path)
19561
19631
  throw new Error("--output must be a canonical absolute directory");
19562
19632
  if (!existsSync13(path))
19563
19633
  mkdirSync13(path, { recursive: true, mode: 448 });
19564
- const metadata = lstatSync10(path);
19634
+ const metadata = lstatSync11(path);
19565
19635
  const uid = process.getuid?.();
19566
19636
  if (!metadata.isDirectory() || metadata.isSymbolicLink() || (metadata.mode & 63) !== 0 || uid !== undefined && uid !== 0 && metadata.uid !== uid) {
19567
19637
  throw new Error("platform genesis output must be an owner-only directory owned by the current user");
@@ -19772,14 +19842,11 @@ async function prepareForgeZeroPlatformLaunch(environment, outputDirectory, proj
19772
19842
  if (!existsSync13(path))
19773
19843
  throw new Error(`${label} is missing at ${path}`);
19774
19844
  }
19775
- const root = resolve12(projectRoot);
19845
+ const root = resolve13(projectRoot);
19776
19846
  const nestedApi = join13(root, "api");
19777
19847
  const repositoryRoot = existsSync13(join13(nestedApi, "package.json")) ? nestedApi : root;
19778
19848
  const bundlePath = join13(base, "api.bundle");
19779
- const bundle = existsSync13(bundlePath) || existsSync13(`${bundlePath}.json`) ? await readBootstrapBundle(bundlePath) : await buildBootstrapBundle({ repositoryRoot, outputPath: bundlePath, branch: profile.branch });
19780
- if (bundle.manifest.branch !== profile.branch) {
19781
- throw new Error(`existing bootstrap bundle is for ${bundle.manifest.branch}, expected ${profile.branch}`);
19782
- }
19849
+ const bundle = await refreshBootstrapBundle({ repositoryRoot, outputPath: bundlePath, branch: profile.branch });
19783
19850
  return writePlatformGenesisFleet(outputDirectory, {
19784
19851
  profile,
19785
19852
  metalRequestFile,
@@ -20261,7 +20328,7 @@ async function cmdUnlock(options) {
20261
20328
  if (options.key || options.userExplicit) {
20262
20329
  throw new Error("SSH/user unlock coordinates are not accepted. SSH keys cannot open a WebAuthn-PRF custody envelope; use --phrase-file or --phrase-stdin.");
20263
20330
  }
20264
- const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync16(0, "utf8").trim() : "";
20331
+ const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync17(0, "utf8").trim() : "";
20265
20332
  const phrase = phraseText ? phraseText.split(/\s+/) : [];
20266
20333
  if (phrase.length !== 24) {
20267
20334
  throw new Error(`Recovery phrase must contain exactly 24 words; received ${phrase.length}. Use --phrase-file or --phrase-stdin.`);
@@ -20494,7 +20561,7 @@ function projectFromCheckout(options) {
20494
20561
  try {
20495
20562
  return loadConfig({
20496
20563
  cwd: options.projectRoot,
20497
- readFile: (path) => existsSync13(path) ? readFileSync16(path, "utf8") : undefined
20564
+ readFile: (path) => existsSync13(path) ? readFileSync17(path, "utf8") : undefined
20498
20565
  }).config.project;
20499
20566
  } catch (cause) {
20500
20567
  throw new Error(`No --project was given and the checkout has no usable .fz/config.json: ${cause instanceof Error ? cause.message : String(cause)}`);
@@ -20561,7 +20628,7 @@ async function cmdDeploy(options, args) {
20561
20628
  throw new Error("typed runtime initialization selects its tested software and attestation from --runtime/--isolation; do not combine --software or --attestation");
20562
20629
  }
20563
20630
  initializeTypeScriptDeployment(options.projectRoot, {
20564
- name: options.projectName ?? basename3(resolve12(options.projectRoot)),
20631
+ name: options.projectName ?? basename3(resolve13(options.projectRoot)),
20565
20632
  force: options.force,
20566
20633
  runtime: options.deployRuntime,
20567
20634
  operatingSystem: options.deployOperatingSystem,
@@ -20616,7 +20683,7 @@ async function cmdDeploy(options, args) {
20616
20683
  return problems.length === 0 ? 0 : 1;
20617
20684
  }
20618
20685
  if (operation === "check" || operation === "sync") {
20619
- if (existsSync13(resolve12(options.projectRoot, DEPLOY_SOURCE_FILE))) {
20686
+ if (existsSync13(resolve13(options.projectRoot, DEPLOY_SOURCE_FILE))) {
20620
20687
  const expected = await compileDeploymentProject(options.projectRoot, { write: false });
20621
20688
  const actual = inspectCompiledDeployment(options.projectRoot);
20622
20689
  const current2 = canonicalJson(expected.plan) === canonicalJson(actual.plan);
@@ -20692,7 +20759,7 @@ async function cmdDeploy(options, args) {
20692
20759
  branch: options.branch,
20693
20760
  cloneUrl: required(options.cloneUrl, "--clone-url"),
20694
20761
  sourceAuth,
20695
- ...options.knownHostsFile ? { knownHosts: readFileSync16(options.knownHostsFile, "utf8") } : {},
20762
+ ...options.knownHostsFile ? { knownHosts: readFileSync17(options.knownHostsFile, "utf8") } : {},
20696
20763
  projectKey
20697
20764
  } });
20698
20765
  const pipelineKey2 = String(created.pipelineKey);
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
366
366
  }
367
367
 
368
368
  // src/version.ts
369
- var VERSION = "0.1.103";
369
+ var VERSION = "0.1.107";
370
370
 
371
371
  // src/otel-collector.ts
372
372
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
@@ -1384,6 +1384,46 @@ async function buildBootstrapBundle(input, exec = run) {
1384
1384
  throw cause;
1385
1385
  }
1386
1386
  }
1387
+ async function refreshBootstrapBundle(input, exec = run) {
1388
+ const outputPath = resolve2(input.outputPath);
1389
+ const manifestPath = `${outputPath}.json`;
1390
+ if (existsSync(outputPath) !== existsSync(manifestPath)) {
1391
+ throw new Error("bootstrap bundle and manifest must either both exist or both be absent");
1392
+ }
1393
+ const candidatePath = `${outputPath}.candidate.${process.pid}.${randomBytes(6).toString("hex")}`;
1394
+ const candidate = await buildBootstrapBundle({ ...input, outputPath: candidatePath }, exec);
1395
+ try {
1396
+ if (!existsSync(outputPath)) {
1397
+ renameSync(candidate.bundlePath, outputPath);
1398
+ renameSync(candidate.manifestPath, manifestPath);
1399
+ return readBootstrapBundle(outputPath);
1400
+ }
1401
+ const current = await readBootstrapBundle(outputPath);
1402
+ if (current.manifest.branch === candidate.manifest.branch && current.manifest.revision === candidate.manifest.revision) {
1403
+ rmSync(candidate.bundlePath, { force: true });
1404
+ rmSync(candidate.manifestPath, { force: true });
1405
+ return current;
1406
+ }
1407
+ const archivePath = `${outputPath}.before-${current.manifest.revision.slice(0, 7)}-${Date.now()}`;
1408
+ const archiveManifestPath = `${archivePath}.json`;
1409
+ renameSync(outputPath, archivePath);
1410
+ renameSync(manifestPath, archiveManifestPath);
1411
+ try {
1412
+ renameSync(candidate.bundlePath, outputPath);
1413
+ renameSync(candidate.manifestPath, manifestPath);
1414
+ return readBootstrapBundle(outputPath);
1415
+ } catch (cause) {
1416
+ rmSync(outputPath, { force: true });
1417
+ rmSync(manifestPath, { force: true });
1418
+ renameSync(archivePath, outputPath);
1419
+ renameSync(archiveManifestPath, manifestPath);
1420
+ throw cause;
1421
+ }
1422
+ } finally {
1423
+ rmSync(candidate.bundlePath, { force: true });
1424
+ rmSync(candidate.manifestPath, { force: true });
1425
+ }
1426
+ }
1387
1427
 
1388
1428
  // src/bootstrap.ts
1389
1429
  import { createHash as createHash2, createHmac as createHmac2, createPrivateKey, randomBytes as randomBytes3 } from "crypto";
@@ -1420,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1420
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1421
1461
 
1422
1462
  // src/version.ts
1423
- var VERSION = "0.1.103";
1463
+ var VERSION = "0.1.107";
1424
1464
 
1425
1465
  // src/software.ts
1426
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3347,6 +3387,7 @@ function platformApiCredentialSpecs(options) {
3347
3387
  ];
3348
3388
  return [
3349
3389
  { name: "arangodb-jwt", encryptedPath: "/etc/forgezero/creds/arangodb-jwt.cred", required: true },
3390
+ { name: "arangodb-root-password", encryptedPath: "/etc/forgezero/creds/arangodb-root-password.cred", required: true },
3350
3391
  { name: "seed-sync-root", encryptedPath: "/etc/forgezero/creds/seed-sync-root.cred", required: true },
3351
3392
  ...optional.filter(([, present]) => present).map(([name]) => ({
3352
3393
  name,
@@ -3814,6 +3855,7 @@ var STATE_PATH = BOOTSTRAP_STATE_PATH;
3814
3855
  var INTENT_PATH = "/var/lib/forgezero/bootstrap.intent.json";
3815
3856
  var CREDS = "/etc/forgezero/creds";
3816
3857
  var JWT_CREDENTIAL = `${CREDS}/arangodb-jwt.cred`;
3858
+ var ARANGO_ROOT_CREDENTIAL = `${CREDS}/arangodb-root-password.cred`;
3817
3859
  var ENROL_CREDENTIAL = `${CREDS}/enrol-token.cred`;
3818
3860
  var TUNNEL_CREDENTIAL = `${CREDS}/CF_TUNNEL_CONNECTOR_TOKEN.cred`;
3819
3861
  var CF_API_CREDENTIAL = `${CREDS}/CF_API_TOKEN.cred`;
@@ -4099,7 +4141,7 @@ WantedBy=multi-user.target
4099
4141
  function databaseVerifyUnit(config) {
4100
4142
  const { address } = config.database;
4101
4143
  return `[Unit]
4102
- Description=Verify ForgeZero ArangoDB Community 3.11.14 writable Coordinator
4144
+ Description=Secure and verify ForgeZero ArangoDB Community 3.11.14 writable Coordinator
4103
4145
  Requires=forgezero-db.service
4104
4146
  After=forgezero-db.service
4105
4147
  PartOf=forgezero-db.service
@@ -4109,7 +4151,9 @@ Type=oneshot
4109
4151
  User=arangodb
4110
4152
  Group=arangodb
4111
4153
  LoadCredentialEncrypted=arangodb-jwt:${JWT_CREDENTIAL}
4112
- ExecStart=/usr/bin/arangosh --server.endpoint tcp://${unitEscape(address)}:8529 --server.jwt-secret-keyfile %d/arangodb-jwt --javascript.execute-string 'const c=require("@arangodb").db._connection;let last;let ok=false;for(let i=0;i<90;i++){try{const v=c.GET("/_api/version?details=true");const s=c.GET("/_admin/status");const m=c.GET("/_admin/server/mode");if(!v.error&&v.version==="3.11.14"&&v.details&&v.details.license==="community"&&!s.error&&s.serverInfo&&s.serverInfo.role==="COORDINATOR"&&!m.error&&m.mode==="default"){ok=true;break;}last={v,s,m};}catch(e){last=String(e);}require("internal").wait(2);}if(!ok)throw new Error("writable Community Coordinator verification failed: "+JSON.stringify(last));'
4154
+ LoadCredentialEncrypted=arangodb-root-password:${ARANGO_ROOT_CREDENTIAL}
4155
+ ExecStart=/usr/bin/arangosh --server.endpoint tcp://${unitEscape(address)}:8529 --server.jwt-secret-keyfile %d/arangodb-jwt --javascript.execute-string 'const c=require("@arangodb").db._connection;let last;let ok=false;for(let i=0;i<90;i++){try{const v=c.GET("/_api/version?details=true");const s=c.GET("/_admin/status");const m=c.GET("/_admin/server/mode");if(!v.error&&v.version==="3.11.14"&&v.details&&v.details.license==="community"&&!s.error&&s.serverInfo&&s.serverInfo.role==="COORDINATOR"&&!m.error&&m.mode==="default"){ok=true;break;}last={v,s,m};}catch(e){last=String(e);}require("internal").wait(2);}if(!ok)throw new Error("writable Community Coordinator verification failed: "+JSON.stringify(last));const password=require("fs").read("%d/arangodb-root-password").trim();if(password.length<48)throw new Error("ArangoDB root credential is malformed");require("@arangodb/users").update("root",password,true);'
4156
+ ExecStart=/usr/local/bin/fz-agent database-auth-verify --endpoint=http://${unitEscape(address)}:8529
4113
4157
  RemainAfterExit=yes
4114
4158
  TimeoutStartSec=200
4115
4159
  NoNewPrivileges=true
@@ -4576,6 +4620,11 @@ async function bootstrapStatus(host = localBootstrapHost()) {
4576
4620
  if (nginx.exitCode !== 0)
4577
4621
  problems.push("nginx configuration is invalid");
4578
4622
  if (state.databaseRole !== "none") {
4623
+ for (const credential of [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL]) {
4624
+ services[credential] = host.exists(credential);
4625
+ if (!services[credential])
4626
+ problems.push(`${credential} is missing`);
4627
+ }
4579
4628
  const unitPath = "/etc/systemd/system/forgezero-db.service";
4580
4629
  const expectsNoAgency = state.databaseAgency === "none";
4581
4630
  const unitHasNoAgency = host.exists(unitPath) && host.read(unitPath).includes("--cluster.start-agent=false");
@@ -4810,6 +4859,9 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4810
4859
  if (!host.exists(JWT_CREDENTIAL)) {
4811
4860
  await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
4812
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
+ }
4813
4865
  await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
4814
4866
  await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
4815
4867
  const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
@@ -5093,7 +5145,11 @@ function strictBootstrapDocument(value) {
5093
5145
  ], "runtime environment");
5094
5146
  const environment = runtime.environment;
5095
5147
  if (environment.initialInventory !== undefined) {
5096
- const inventory = exactKeys(environment.initialInventory, ["metalHostname", "region", "computes", "attestation", "deployment"], "initial inventory");
5148
+ const inventory = exactKeys(environment.initialInventory, ["metalHostname", "metalIdentity", "region", "computes", "attestation", "deployment"], "initial inventory");
5149
+ if (inventory.metalIdentity !== undefined) {
5150
+ const identity = exactKeys(inventory.metalIdentity, ["nodeKey", "publicKeys"], "initial Metal identity");
5151
+ exactKeys(identity.publicKeys, ["ed25519", "mlDsa"], "initial Metal public keys");
5152
+ }
5097
5153
  exactKeys(inventory.region, ["key", "label", "country", "city", "confidentialCapable"], "initial inventory region");
5098
5154
  if (inventory.attestation !== undefined) {
5099
5155
  const attestation = exactKeys(inventory.attestation, ["measurement", "tcbFloor"], "initial attestation evidence");
@@ -137,7 +137,7 @@ export declare function validatePlatformSharedEnvironment(input: PlatformSharedE
137
137
  /** Render only non-secret runtime coordinates. Passwords/tokens have no field in this contract. */
138
138
  export declare function renderPlatformSharedEnvironment(input: PlatformSharedEnvironment): string;
139
139
  export interface SystemdCredentialSpec {
140
- name: 'arangodb-jwt' | 'seed-sync-root' | 'fz_smtp.password' | 'fz_jetemail.apiKey' | 'fz_github.clientSecret' | 'fz_github.privateKey' | 'fz_github.webhookSecret' | 'CF_API_TOKEN' | 'CF_TUNNEL_TOKEN' | 'REALTIME_PUBLISH_SECRET' | 'REALTIME_TICKET_SECRET';
140
+ name: 'arangodb-jwt' | 'arangodb-root-password' | 'seed-sync-root' | 'fz_smtp.password' | 'fz_jetemail.apiKey' | 'fz_github.clientSecret' | 'fz_github.privateKey' | 'fz_github.webhookSecret' | 'CF_API_TOKEN' | 'CF_TUNNEL_TOKEN' | 'REALTIME_PUBLISH_SECRET' | 'REALTIME_TICKET_SECRET';
141
141
  encryptedPath: string;
142
142
  required: boolean;
143
143
  }
@@ -987,6 +987,7 @@ function platformApiCredentialSpecs(options) {
987
987
  ];
988
988
  return [
989
989
  { name: "arangodb-jwt", encryptedPath: "/etc/forgezero/creds/arangodb-jwt.cred", required: true },
990
+ { name: "arangodb-root-password", encryptedPath: "/etc/forgezero/creds/arangodb-root-password.cred", required: true },
990
991
  { name: "seed-sync-root", encryptedPath: "/etc/forgezero/creds/seed-sync-root.cred", required: true },
991
992
  ...optional.filter(([, present]) => present).map(([name]) => ({
992
993
  name,