@forgezero/agent 0.1.63 → 0.1.64

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
@@ -4811,8 +4811,8 @@ async function spawnWith(command, env, report = () => {}, options = {}) {
4811
4811
 
4812
4812
  // src/cli/index.ts
4813
4813
  init_dist();
4814
- import { existsSync as existsSync12, lstatSync as lstatSync10, mkdirSync as mkdirSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
4815
- import { basename as basename3, dirname as dirname13, isAbsolute as isAbsolute6, resolve as resolve10 } from "path";
4814
+ import { existsSync as existsSync12, lstatSync as lstatSync10, mkdirSync as mkdirSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
4815
+ import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join12, resolve as resolve11 } from "path";
4816
4816
  import { fileURLToPath as fileURLToPath3 } from "url";
4817
4817
  import { hostname } from "os";
4818
4818
 
@@ -4829,7 +4829,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
4829
4829
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
4830
4830
 
4831
4831
  // src/version.ts
4832
- var VERSION2 = "0.1.63";
4832
+ var VERSION2 = "0.1.64";
4833
4833
 
4834
4834
  // src/software.ts
4835
4835
  var PINNED_BUN_VERSION = "1.3.14";
@@ -5838,17 +5838,16 @@ function agentUnit(options) {
5838
5838
  if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
5839
5839
  throw new Error("migration pull and lifecycle profile must be supplied together");
5840
5840
  }
5841
- const bootstrapEnabled = Boolean(options.pullBootstrap && options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath && options.bootstrapTargetTelemetryEndpoint);
5842
- if ([
5843
- options.pullBootstrap,
5844
- options.bootstrapSshCredentialPath,
5845
- options.bootstrapSshPublicKeyPath,
5846
- options.bootstrapTargetTelemetryEndpoint
5847
- ].some(Boolean) && !bootstrapEnabled) {
5848
- throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
5841
+ const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
5842
+ if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath)) {
5843
+ throw new Error("bootstrap SSH credential and public key paths must be supplied together");
5844
+ }
5845
+ const bootstrapEnabled = Boolean(options.pullBootstrap);
5846
+ if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
5847
+ throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
5849
5848
  }
5850
5849
  const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
5851
- const bootstrapSshCredentialPath = bootstrapEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
5850
+ const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
5852
5851
  const warpValues = [
5853
5852
  options.warpOrganization,
5854
5853
  options.warpClientIdCredentialPath,
@@ -6087,15 +6086,13 @@ function planProvision(options) {
6087
6086
  if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
6088
6087
  throw new Error("migration pull and lifecycle profile must be supplied together");
6089
6088
  }
6090
- const bootstrapEnabled = Boolean(options.pullBootstrap && options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath && options.bootstrapTargetTelemetryEndpoint);
6091
- if ([
6092
- options.pullBootstrap,
6093
- options.bootstrapSshCredentialPath,
6094
- options.bootstrapSshPublicKeyPath,
6095
- options.bootstrapSshSourcePath,
6096
- options.bootstrapTargetTelemetryEndpoint
6097
- ].some(Boolean) && !bootstrapEnabled) {
6098
- throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
6089
+ const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
6090
+ if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath) || options.bootstrapSshSourcePath && !bootstrapIdentityEnabled) {
6091
+ throw new Error("bootstrap SSH credential and public key paths must be supplied together");
6092
+ }
6093
+ const bootstrapEnabled = Boolean(options.pullBootstrap);
6094
+ if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
6095
+ throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
6099
6096
  }
6100
6097
  const warpValues = [
6101
6098
  options.warpOrganization,
@@ -6105,13 +6102,13 @@ function planProvision(options) {
6105
6102
  const warpEnabled = warpValues.every(Boolean);
6106
6103
  if (warpValues.some(Boolean) && !warpEnabled)
6107
6104
  throw new Error("WARP configuration must be supplied together");
6108
- const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath && options.enrolStatePath);
6109
- if (Boolean(options.enrolTokenCredentialPath) !== Boolean(options.enrolStatePath) || options.enrolTokenSourcePath && !enrolmentEnabled) {
6110
- throw new Error("direct enrolment credential and state paths must be supplied together");
6105
+ const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath);
6106
+ if (options.enrolTokenCredentialPath && !options.enrolStatePath || options.enrolTokenSourcePath && (!options.enrolTokenCredentialPath || !options.enrolStatePath)) {
6107
+ throw new Error("direct enrolment credential requires its durable state path");
6111
6108
  }
6112
6109
  const enrolTokenSourcePath = options.enrolTokenSourcePath ? systemdPath(options.enrolTokenSourcePath, "enrolment source") : undefined;
6113
6110
  const enrolTokenCredentialPath = enrolmentEnabled ? systemdPath(options.enrolTokenCredentialPath, "enrolment credential") : undefined;
6114
- const enrolStatePath = enrolmentEnabled ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
6111
+ const enrolStatePath = options.enrolStatePath ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
6115
6112
  const enrolStateDir = enrolStatePath?.replace(/\/[^/]+$/, "");
6116
6113
  const sourceBinPath = options.sourceBinPath ? systemdPath(options.sourceBinPath, "agent source binary") : undefined;
6117
6114
  const binPath = options.binPath ? systemdPath(options.binPath, "agent binary") : undefined;
@@ -6123,8 +6120,8 @@ function planProvision(options) {
6123
6120
  const gitPublicKeyDir = gitPublicKeyPath?.replace(/\/[^/]+$/, "");
6124
6121
  const lifecycleProfilePath = lifecycleEnabled ? systemdPath(options.lifecycleProfilePath, "lifecycle profile") : undefined;
6125
6122
  const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
6126
- const bootstrapSshCredentialPath = bootstrapEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
6127
- const bootstrapSshPublicKeyPath = bootstrapEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
6123
+ const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
6124
+ const bootstrapSshPublicKeyPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
6128
6125
  const bootstrapSshSourcePath = options.bootstrapSshSourcePath ? systemdPath(options.bootstrapSshSourcePath, "bootstrap SSH private-key source") : undefined;
6129
6126
  const bootstrapSshPublicKeyDir = bootstrapSshPublicKeyPath?.replace(/\/[^/]+$/, "");
6130
6127
  const warpClientIdCredentialPath = warpEnabled ? systemdPath(options.warpClientIdCredentialPath, "WARP client-id credential") : undefined;
@@ -6224,7 +6221,7 @@ function planProvision(options) {
6224
6221
  step("Git deploy identity directory", { kind: "directories", directories: [{ path: gitPublicKeyDir, mode: 493, owner: "root", group: "root" }] }),
6225
6222
  step("unique encrypted Git deploy identity", { kind: "ensure-git-identity", credential: gitCredentialPath, publicKey: gitPublicKeyPath })
6226
6223
  ] : [],
6227
- ...bootstrapEnabled ? [
6224
+ ...bootstrapIdentityEnabled ? [
6228
6225
  step("bootstrap SSH public identity directory", { kind: "directories", directories: [
6229
6226
  { path: bootstrapSshPublicKeyDir, mode: 493, owner: "root", group: "root" }
6230
6227
  ] }),
@@ -6260,6 +6257,10 @@ function planProvision(options) {
6260
6257
  { argv: ["/usr/bin/rm", "-f", "/etc/systemd/system/forgezero-deploy-runner.socket"] },
6261
6258
  { argv: ["/usr/bin/systemctl", "daemon-reload"] }
6262
6259
  ] })] : [],
6260
+ ...enrolStatePath && !enrolmentEnabled ? [step("retire consumed enrolment unit", { kind: "commands", commands: [
6261
+ { argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-agent-enrol.service"], acceptedExitCodes: [0, 1, 5] },
6262
+ { argv: ["/usr/bin/rm", "-f", ENROLMENT_UNIT_PATH] }
6263
+ ] })] : [],
6263
6264
  step("enable and converge services", { kind: "commands", commands: [
6264
6265
  { argv: ["/usr/bin/systemctl", "enable", ...enabledUnits] },
6265
6266
  { argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
@@ -14188,7 +14189,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14188
14189
  if (connectorCapabilities?.meshConnectorToken && !host.exists(WARP_CONNECTOR_CREDENTIAL)) {
14189
14190
  await seal(host, "CF_WARP_CONNECTOR_TOKEN", WARP_CONNECTOR_CREDENTIAL, connectorCapabilities.meshConnectorToken);
14190
14191
  }
14191
- await host.installAgent(config);
14192
+ if (alreadyEnrolled) {
14193
+ await host.installAgent(config, "bound");
14194
+ } else if (config.kind === "platform") {
14195
+ if (bootstrapManifest)
14196
+ await host.installAgent(config, "bootstrap");
14197
+ } else {
14198
+ await host.installAgent(config, "enrol");
14199
+ await host.installAgent(config, "bound");
14200
+ }
14192
14201
  if (config.kind === "platform" && config.firewall.enabled) {
14193
14202
  await host.ensureSoftware(plan.software.filter(({ id: id2 }) => id2 === "ufw"));
14194
14203
  } else
@@ -14338,7 +14347,19 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14338
14347
  `, 384);
14339
14348
  host.remove(config.bootstrapBundle.bundleFile);
14340
14349
  host.remove(config.bootstrapBundle.manifestFile);
14341
- await host.installAgent(config);
14350
+ }
14351
+ if (!alreadyEnrolled) {
14352
+ await checked3(host, [
14353
+ "curl",
14354
+ "--fail",
14355
+ "--silent",
14356
+ "--show-error",
14357
+ "--max-time",
14358
+ "10",
14359
+ `http://127.0.0.1:3000${runtime.healthPath}`
14360
+ ], "release-one API health");
14361
+ await host.installAgent(config, "enrol");
14362
+ await host.installAgent(config, "bound");
14342
14363
  }
14343
14364
  }
14344
14365
  if (config.cloudflareHandoff) {
@@ -14507,6 +14528,65 @@ function readBootstrapConfig(path) {
14507
14528
  }
14508
14529
  return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync9(path, "utf8"))));
14509
14530
  }
14531
+ function planBootstrapAgentInstall(config, phase, context) {
14532
+ const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
14533
+ if (phase === "bootstrap") {
14534
+ if (config.kind !== "platform" || hasBinding || !initialBundle) {
14535
+ throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
14536
+ }
14537
+ } else if (phase === "enrol") {
14538
+ if (hasBinding || !hasEnrolCredential) {
14539
+ throw new Error("Agent enrolment requires one unbound host and its sealed one-use credential");
14540
+ }
14541
+ } else if (!hasBinding) {
14542
+ throw new Error("bound Agent convergence requires durable enrolment state");
14543
+ }
14544
+ const bound = phase === "bound";
14545
+ const enrolling = phase === "enrol";
14546
+ const authenticated = enrolling || bound;
14547
+ const bootstrapRunner = platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner);
14548
+ const options = {
14549
+ capabilities,
14550
+ socketPath: DEFAULT_SOCKET,
14551
+ seedPath: "/var/lib/forgezero/node.seed",
14552
+ controlSocketPath: CONTROL_SOCKET,
14553
+ repository: phase === "bootstrap" ? initialBundle.path : bound && config.kind === "enrolled-compute" ? config.repository : undefined,
14554
+ branch: phase === "bootstrap" ? initialBundle.manifest.branch : bound && config.kind === "enrolled-compute" ? config.branch : undefined,
14555
+ bootstrapBundlePath: phase === "bootstrap" ? initialBundle.path : undefined,
14556
+ bootstrapBundleManifestPath: phase === "bootstrap" ? initialBundle.manifestPath : undefined,
14557
+ profile: config.profile,
14558
+ deployRoot: config.deployRoot ?? "/opt/forgezero",
14559
+ deploymentCredentials: config.deploymentCredentials,
14560
+ publicApiUrl: config.apiUrl,
14561
+ gitCredentialPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
14562
+ gitPublicKeyPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
14563
+ generateGitIdentity: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey === true,
14564
+ pullDeployments: bound,
14565
+ pullMigrations: bound && config.kind === "platform",
14566
+ pullBootstrap: bound && bootstrapRunner,
14567
+ bootstrapSshCredentialPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
14568
+ bootstrapSshSourcePath: enrolling && config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
14569
+ bootstrapSshPublicKeyPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
14570
+ bootstrapTargetTelemetryEndpoint: bound && bootstrapRunner ? platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined : undefined,
14571
+ lifecycleProfilePath: bound && config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
14572
+ enforceEgress: true,
14573
+ nodeHostname: config.nodeHostname,
14574
+ telemetryEndpoint: config.telemetryEndpoint,
14575
+ binPath: "/usr/local/lib/forgezero/agent/fz-agent",
14576
+ sourceBinPath: PACKAGED_AGENT_BIN,
14577
+ ...enrolling ? {
14578
+ enrolTokenCredentialPath: ENROL_CREDENTIAL,
14579
+ enrolStatePath: "/var/lib/forgezero/enrolment.json"
14580
+ } : bound ? { enrolStatePath: "/var/lib/forgezero/enrolment.json" } : {},
14581
+ ...authenticated ? {
14582
+ apiUrl: config.apiUrl,
14583
+ project: config.kind === "enrolled-compute" ? config.realm : "platform",
14584
+ environment: config.kind === "enrolled-compute" ? undefined : config.environment,
14585
+ nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
14586
+ } : {}
14587
+ };
14588
+ return planInstall(options);
14589
+ }
14510
14590
  function localBootstrapHost() {
14511
14591
  const execute = async (argv2, options = {}) => {
14512
14592
  const child = Bun.spawn([...argv2], { stdin: options.stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
@@ -14554,10 +14634,10 @@ function localBootstrapHost() {
14554
14634
  throw new Error(`Agent software requirements failed: ${result.output.trim()}`);
14555
14635
  return result;
14556
14636
  },
14557
- async installAgent(config) {
14637
+ async installAgent(config, phase) {
14558
14638
  const capabilities = await readCapabilities(localRunner);
14559
- const deployRoot = config.deployRoot ?? "/opt/forgezero";
14560
14639
  const hasBinding = existsSync9("/var/lib/forgezero/enrolment.json");
14640
+ const hasEnrolCredential = existsSync9(ENROL_CREDENTIAL);
14561
14641
  const initialBundle = config.kind === "platform" && !existsSync9(BOOTSTRAP_RELEASE_EVIDENCE) ? {
14562
14642
  path: config.bootstrapBundle.bundleFile,
14563
14643
  manifestPath: config.bootstrapBundle.manifestFile,
@@ -14578,45 +14658,11 @@ function localBootstrapHost() {
14578
14658
  writeFileSync9(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
14579
14659
  `, { mode: 256 });
14580
14660
  }
14581
- const plan = planInstall({
14661
+ const plan = planBootstrapAgentInstall(config, phase, {
14582
14662
  capabilities,
14583
- socketPath: DEFAULT_SOCKET,
14584
- seedPath: "/var/lib/forgezero/node.seed",
14585
- controlSocketPath: "/run/forgezero/control.sock",
14586
- repository: initialBundle?.path ?? (config.kind === "enrolled-compute" ? config.repository : undefined),
14587
- branch: initialBundle?.manifest.branch ?? (config.kind === "enrolled-compute" ? config.branch : undefined),
14588
- bootstrapBundlePath: initialBundle?.path,
14589
- bootstrapBundleManifestPath: initialBundle?.manifestPath,
14590
- profile: config.kind === "platform" ? config.profile : config.profile,
14591
- deployRoot,
14592
- deploymentCredentials: config.deploymentCredentials,
14593
- publicApiUrl: config.apiUrl,
14594
- gitCredentialPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
14595
- gitPublicKeyPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
14596
- generateGitIdentity: config.kind === "enrolled-compute" && config.gitDeployKey === true,
14597
- pullDeployments: hasBinding,
14598
- pullMigrations: config.kind === "platform" && hasBinding,
14599
- pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
14600
- bootstrapSshCredentialPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
14601
- bootstrapSshSourcePath: config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
14602
- bootstrapSshPublicKeyPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
14603
- bootstrapTargetTelemetryEndpoint: platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined,
14604
- lifecycleProfilePath: config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
14605
- enforceEgress: true,
14606
- nodeHostname: config.nodeHostname,
14607
- telemetryEndpoint: config.telemetryEndpoint,
14608
- binPath: "/usr/local/lib/forgezero/agent/fz-agent",
14609
- sourceBinPath: PACKAGED_AGENT_BIN,
14610
- ...existsSync9(ENROL_CREDENTIAL) || hasBinding ? {
14611
- enrolTokenCredentialPath: existsSync9(ENROL_CREDENTIAL) ? ENROL_CREDENTIAL : undefined,
14612
- enrolStatePath: "/var/lib/forgezero/enrolment.json"
14613
- } : {},
14614
- ...hasBinding ? {
14615
- apiUrl: config.apiUrl,
14616
- project: config.kind === "enrolled-compute" ? config.realm : "platform",
14617
- environment: config.kind === "enrolled-compute" ? undefined : config.environment,
14618
- nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
14619
- } : {}
14663
+ hasBinding,
14664
+ hasEnrolCredential,
14665
+ initialBundle
14620
14666
  });
14621
14667
  for (const unit of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
14622
14668
  mkdirSync9(dirname9(unit.path), { recursive: true, mode: 493 });
@@ -15671,10 +15717,10 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15671
15717
 
15672
15718
  // src/operator-bootstrap.ts
15673
15719
  import { createHash as createHash6, randomBytes as randomBytes9 } from "crypto";
15674
- import { lstatSync as lstatSync7, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
15720
+ import { chmodSync as chmodSync6, lstatSync as lstatSync7, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
15675
15721
  import { isIP as isIP6 } from "net";
15676
15722
  import { tmpdir } from "os";
15677
- import { basename as basename2, join as join10 } from "path";
15723
+ import { basename as basename2, dirname as dirname13, isAbsolute as isAbsolute5, join as join10, resolve as resolve9 } from "path";
15678
15724
  import { fileURLToPath as fileURLToPath2 } from "url";
15679
15725
 
15680
15726
  // src/platform-genesis.ts
@@ -15809,6 +15855,25 @@ var exactKeys5 = (value, keys, label) => {
15809
15855
  throw new Error(`${label} contains unknown fields: ${unknown.join(", ")}`);
15810
15856
  return record3;
15811
15857
  };
15858
+ var canonicalOutputPath = (path, label) => {
15859
+ if (!isAbsolute5(path) || resolve9(path) !== path || /[\r\n\0]/.test(path)) {
15860
+ throw new Error(`${label} must be a canonical absolute path`);
15861
+ }
15862
+ return path;
15863
+ };
15864
+ var writeOwnerJson2 = (path, value, label) => {
15865
+ canonicalOutputPath(path, label);
15866
+ mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
15867
+ const parent = lstatSync7(dirname13(path));
15868
+ const uid = process.getuid?.() ?? parent.uid;
15869
+ if (!parent.isDirectory() || parent.isSymbolicLink() || parent.uid !== uid || (parent.mode & 63) !== 0) {
15870
+ throw new Error(`${label} parent must be an owner-only directory`);
15871
+ }
15872
+ writeFileSync12(path, `${JSON.stringify(value, null, 2)}
15873
+ `, { mode: 384, flag: "wx" });
15874
+ chmodSync6(path, 384);
15875
+ return path;
15876
+ };
15812
15877
  var ownerFile = (path, limit, label) => {
15813
15878
  if (!path.startsWith("/") || /[\r\n]/.test(path))
15814
15879
  throw new Error(`${label} path must be absolute`);
@@ -15867,8 +15932,10 @@ var validateHop = (value, label) => {
15867
15932
  }
15868
15933
  return hop;
15869
15934
  };
15870
- function readOperatorPlatformBootstrapRequest(path) {
15871
- const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator bootstrap request")));
15935
+ function createOperatorSshHop(input) {
15936
+ return validateHop({ ...input, hostKeySha256: fingerprint(input.hostKey) }, "operator SSH hop");
15937
+ }
15938
+ var validatePlatformRequestValue = (value) => {
15872
15939
  const root = exactKeys5(value, ["kind", "target", "platformConfigFile"], "operator bootstrap request");
15873
15940
  if (root.kind !== "platform-remote")
15874
15941
  throw new Error("operator bootstrap kind must be platform-remote");
@@ -15887,6 +15954,7 @@ function readOperatorPlatformBootstrapRequest(path) {
15887
15954
  socketPath(targetValue.agentSocket);
15888
15955
  if (typeof root.platformConfigFile !== "string")
15889
15956
  throw new Error("platformConfigFile must be an absolute path");
15957
+ canonicalOutputPath(root.platformConfigFile, "platformConfigFile");
15890
15958
  const config = readBootstrapConfig(root.platformConfigFile);
15891
15959
  if (config.kind !== "platform")
15892
15960
  throw new Error("operator bootstrap requires a platform config");
@@ -15900,9 +15968,39 @@ function readOperatorPlatformBootstrapRequest(path) {
15900
15968
  ...targetValue.jump === undefined ? {} : { jump: validateHop(targetValue.jump, "operator bootstrap jump") }
15901
15969
  }
15902
15970
  };
15903
- }
15904
- function readOperatorMetalBootstrapRequest(path) {
15905
- const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal request")));
15971
+ };
15972
+ function readOperatorPlatformBootstrapRequest(path) {
15973
+ const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator bootstrap request")));
15974
+ return validatePlatformRequestValue(value);
15975
+ }
15976
+ function writeOperatorPlatformBootstrapRequest(path, request) {
15977
+ validatePlatformRequestValue(request);
15978
+ return writeOwnerJson2(path, request, "operator bootstrap request");
15979
+ }
15980
+ function readOperatorPlatformBootstrapFleetRequest(path) {
15981
+ const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator fleet request")));
15982
+ const root = exactKeys5(value, ["kind", "requests"], "operator fleet request");
15983
+ if (root.kind !== "platform-fleet-remote")
15984
+ throw new Error("operator fleet kind must be platform-fleet-remote");
15985
+ if (!Array.isArray(root.requests) || root.requests.length !== 3 || root.requests.some((entry) => typeof entry !== "string")) {
15986
+ throw new Error("operator fleet requires exactly three request files");
15987
+ }
15988
+ const requests = root.requests.map((entry) => canonicalOutputPath(entry, "operator fleet request file"));
15989
+ if (new Set(requests).size !== 3)
15990
+ throw new Error("operator fleet request files must be unique");
15991
+ for (const request of requests)
15992
+ readOperatorPlatformBootstrapRequest(request);
15993
+ return { kind: "platform-fleet-remote", requests };
15994
+ }
15995
+ function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles) {
15996
+ const request = { kind: "platform-fleet-remote", requests: requestFiles };
15997
+ if (new Set(requestFiles).size !== 3)
15998
+ throw new Error("operator fleet request files must be unique");
15999
+ for (const file of requestFiles)
16000
+ readOperatorPlatformBootstrapRequest(file);
16001
+ return writeOwnerJson2(path, request, "operator fleet request");
16002
+ }
16003
+ var validateMetalRequestValue = (value) => {
15906
16004
  const root = exactKeys5(value, ["kind", "target", "metalConfigFile", "genesis"], "operator metal request");
15907
16005
  if (root.kind !== "metal-remote")
15908
16006
  throw new Error("operator metal bootstrap kind must be metal-remote");
@@ -15921,6 +16019,7 @@ function readOperatorMetalBootstrapRequest(path) {
15921
16019
  socketPath(targetValue.agentSocket);
15922
16020
  if (typeof root.metalConfigFile !== "string")
15923
16021
  throw new Error("metalConfigFile must be an absolute path");
16022
+ canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
15924
16023
  readMetalBootstrapConfig(root.metalConfigFile);
15925
16024
  const genesis = exactKeys5(root.genesis, ["environment", "sshPublicKeyFiles"], "operator metal genesis");
15926
16025
  if (typeof genesis.environment !== "string" || !PLATFORM_GENESIS_ENVIRONMENTS.includes(genesis.environment)) {
@@ -15944,6 +16043,14 @@ function readOperatorMetalBootstrapRequest(path) {
15944
16043
  sshPublicKeyFiles: genesis.sshPublicKeyFiles
15945
16044
  }
15946
16045
  };
16046
+ };
16047
+ function readOperatorMetalBootstrapRequest(path) {
16048
+ const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal request")));
16049
+ return validateMetalRequestValue(value);
16050
+ }
16051
+ function writeOperatorMetalBootstrapRequest(path, request) {
16052
+ validateMetalRequestValue(request);
16053
+ return writeOwnerJson2(path, request, "operator metal request");
15947
16054
  }
15948
16055
  function planOperatorMetalBootstrap(request, mode) {
15949
16056
  const config = readMetalBootstrapConfig(request.metalConfigFile);
@@ -16003,6 +16110,87 @@ function planOperatorPlatformBootstrap(request, mode) {
16003
16110
  secretInputs: mode === "apply" ? [...attendedSecretNames(config), ...secretSources(config).map(([name]) => name)] : []
16004
16111
  };
16005
16112
  }
16113
+ var fleetConfigIdentity = (config) => {
16114
+ const normalized = structuredClone(config);
16115
+ delete normalized.computeReference;
16116
+ delete normalized.nodeHostname;
16117
+ delete normalized.cloudflareHandoff;
16118
+ delete normalized.database.role;
16119
+ delete normalized.database.address;
16120
+ delete normalized.database.master;
16121
+ const environment = normalized.runtime.environment;
16122
+ delete environment.databaseRole;
16123
+ delete environment.databaseAddress;
16124
+ delete environment.databaseMaster;
16125
+ delete environment.nodeHostname;
16126
+ delete environment.seedSyncPeers;
16127
+ delete environment.custodianEmail;
16128
+ return JSON.stringify(normalized);
16129
+ };
16130
+ function validatedPlatformFleet(fleet) {
16131
+ const requests = fleet.requests.map(readOperatorPlatformBootstrapRequest);
16132
+ const configs = requests.map((request) => {
16133
+ const config = readBootstrapConfig(request.platformConfigFile);
16134
+ if (config.kind !== "platform")
16135
+ throw new Error("operator fleet requires platform configs");
16136
+ return config;
16137
+ });
16138
+ const environment = configs[0].environment;
16139
+ const expected = PLATFORM_GENESIS_FLEETS[environment];
16140
+ if (!expected || expected.length !== 3)
16141
+ throw new Error("operator fleet environment is invalid");
16142
+ const identity = fleetConfigIdentity(configs[0]);
16143
+ const operatorIdentity = `${requests[0].target.identityPublicKeyFile}\x00${requests[0].target.agentSocket}`;
16144
+ const jumpIdentity = JSON.stringify(requests[0].target.jump ?? null);
16145
+ const coordinators = expected.map((guest) => `http://${guest.address}:8529`);
16146
+ const nodeHostnames = new Set;
16147
+ for (let index = 0;index < 3; index += 1) {
16148
+ const config = configs[index];
16149
+ const request = requests[index];
16150
+ const guest = expected[index];
16151
+ const role = index === 0 ? "master" : "joiner";
16152
+ if (config.environment !== environment || fleetConfigIdentity(config) !== identity) {
16153
+ throw new Error("operator fleet platform configs do not share one immutable identity");
16154
+ }
16155
+ if (config.computeReference !== guest.name || config.database.address !== guest.address || config.database.role !== role || config.database.agency !== "member" || (role === "master" ? config.database.master !== undefined : config.database.master !== expected[0].address) || config.database.coordinators.join(",") !== coordinators.join(",") || config.enrolment.source !== "genesis-derived" || config.cloudflareHandoff?.nodeName !== guest.name) {
16156
+ throw new Error(`operator fleet ${guest.name} does not match the exact genesis topology`);
16157
+ }
16158
+ if (request.target.address !== guest.address || request.target.port !== 22 || request.target.user !== "forgezero") {
16159
+ throw new Error(`operator fleet ${guest.name} SSH target must be forgezero@${guest.address}:22`);
16160
+ }
16161
+ if (`${request.target.identityPublicKeyFile}\x00${request.target.agentSocket}` !== operatorIdentity) {
16162
+ throw new Error("operator fleet must use one caller-approved SSH identity and agent socket");
16163
+ }
16164
+ if (JSON.stringify(request.target.jump ?? null) !== jumpIdentity) {
16165
+ throw new Error("operator fleet must use one pinned Metal jump or no jump on every node");
16166
+ }
16167
+ if (nodeHostnames.has(config.nodeHostname))
16168
+ throw new Error("operator fleet node hostnames must be unique");
16169
+ nodeHostnames.add(config.nodeHostname);
16170
+ }
16171
+ return { requests, configs, environment };
16172
+ }
16173
+ function planOperatorPlatformFleetBootstrap(fleet, mode) {
16174
+ const { requests, configs, environment } = validatedPlatformFleet(fleet);
16175
+ const secretInputs = mode === "apply" ? attendedSecretNames(configs[0]) : [];
16176
+ for (const config of configs.slice(1)) {
16177
+ if (JSON.stringify(attendedSecretNames(config)) !== JSON.stringify(secretInputs)) {
16178
+ throw new Error("operator fleet configs do not share one attended secret schema");
16179
+ }
16180
+ }
16181
+ return {
16182
+ kind: "platform-fleet-remote",
16183
+ mode,
16184
+ mutation: mode !== "status",
16185
+ environment,
16186
+ nodes: requests.map((request, index) => ({
16187
+ computeReference: configs[index].computeReference,
16188
+ address: request.target.address,
16189
+ ...request.target.jump ? { via: request.target.jump.address } : {}
16190
+ })),
16191
+ secretInputs
16192
+ };
16193
+ }
16006
16194
  var defaultExec3 = async (argv2, options = {}) => {
16007
16195
  const child = Bun.spawn([...argv2], {
16008
16196
  stdin: options.stdin === undefined ? "ignore" : "pipe",
@@ -16245,6 +16433,30 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
16245
16433
  rmSync7(directory, { recursive: true, force: true });
16246
16434
  }
16247
16435
  }
16436
+ async function applyOperatorPlatformFleetBootstrap(fleet, mode, options = {}) {
16437
+ const plan = planOperatorPlatformFleetBootstrap(fleet, mode);
16438
+ const { requests, configs } = validatedPlatformFleet(fleet);
16439
+ const secrets = mode === "apply" ? validatePlatformBootstrapSecrets(configs[0], options.secrets) : undefined;
16440
+ if (secrets) {
16441
+ for (const config of configs.slice(1))
16442
+ validatePlatformBootstrapSecrets(config, secrets);
16443
+ }
16444
+ const settled = await Promise.allSettled(requests.map((request) => applyOperatorPlatformBootstrap(request, mode, { ...options, secrets })));
16445
+ const outcomes = settled.map((result, index) => {
16446
+ const config = configs[index];
16447
+ const address = requests[index].target.address;
16448
+ if (result.status === "fulfilled")
16449
+ return {
16450
+ computeReference: config.computeReference,
16451
+ address,
16452
+ ok: true,
16453
+ output: result.value.output.slice(0, 65536)
16454
+ };
16455
+ const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
16456
+ return { computeReference: config.computeReference, address, ok: false, error: message.slice(0, 4096) };
16457
+ });
16458
+ return { plan, ok: outcomes.every(({ ok }) => ok), outcomes };
16459
+ }
16248
16460
  async function applyOperatorMetalBootstrap(request, mode, options = {}) {
16249
16461
  const plan = planOperatorMetalBootstrap(request, mode);
16250
16462
  publicIdentity(request.target.identityPublicKeyFile);
@@ -16478,7 +16690,7 @@ async function applyHostMaintenance(request, runtime = localRuntime()) {
16478
16690
 
16479
16691
  // src/cli/maintenance.ts
16480
16692
  import { existsSync as existsSync11, lstatSync as lstatSync9, readFileSync as readFileSync14, realpathSync as realpathSync5 } from "fs";
16481
- import { isAbsolute as isAbsolute5, join as join11, relative as relative2, resolve as resolve9 } from "path";
16693
+ import { isAbsolute as isAbsolute6, join as join11, relative as relative2, resolve as resolve10 } from "path";
16482
16694
  var API_OPERATION_ENTRYPOINTS = {
16483
16695
  "dev-reset": ["src", "server", "maintenance", "dev-reset.ts"],
16484
16696
  "db-backup": ["src", "server", "maintenance", "snapshot-backup.ts"],
@@ -16572,13 +16784,13 @@ function checkedEntrypoint(root, parts) {
16572
16784
  }
16573
16785
  const actual = realpathSync5(candidate);
16574
16786
  const within = relative2(root, actual);
16575
- if (within.startsWith("..") || isAbsolute5(within)) {
16787
+ if (within.startsWith("..") || isAbsolute6(within)) {
16576
16788
  throw new Error(`The reviewed operation entrypoint escapes repository root ${root}.`);
16577
16789
  }
16578
16790
  return actual;
16579
16791
  }
16580
16792
  function resolveRepositoryOperation(operation, requestedRoot) {
16581
- const root = realpathSync5(resolve9(requestedRoot));
16793
+ const root = realpathSync5(resolve10(requestedRoot));
16582
16794
  const name = manifestName(root);
16583
16795
  if (operation in API_OPERATION_ENTRYPOINTS) {
16584
16796
  const parts = API_OPERATION_ENTRYPOINTS[operation];
@@ -16879,7 +17091,7 @@ async function api(options, path, init) {
16879
17091
  return { status: response.status, body };
16880
17092
  }
16881
17093
  function sleep(ms) {
16882
- return new Promise((resolve11) => setTimeout(resolve11, ms));
17094
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
16883
17095
  }
16884
17096
  function browserCommand(url) {
16885
17097
  if (process.platform === "darwin")
@@ -17313,7 +17525,7 @@ async function cmdAgent(options, args) {
17313
17525
  writeFileSync13(plan.unitPath, plan.unit, { mode: 420 });
17314
17526
  out.ok(`Wrote ${plan.unitPath}`);
17315
17527
  for (const auxiliary of plan.auxiliaryUnits) {
17316
- mkdirSync12(dirname13(auxiliary.path), { recursive: true, mode: 493 });
17528
+ mkdirSync13(dirname14(auxiliary.path), { recursive: true, mode: 493 });
17317
17529
  writeFileSync13(auxiliary.path, auxiliary.unit, { mode: 420 });
17318
17530
  out.ok(`Wrote ${auxiliary.path}`);
17319
17531
  }
@@ -17322,7 +17534,7 @@ async function cmdAgent(options, args) {
17322
17534
  const token = await bootstrapSecret("ForgeZero one-time enrolment token");
17323
17535
  if (!/^fze_[A-Za-z0-9_-]{40,100}$/.test(token))
17324
17536
  throw new Error("A valid fze_ enrolment token was not provided.");
17325
- mkdirSync12(dirname13(enrolTokenCredentialPath), { recursive: true, mode: 448 });
17537
+ mkdirSync13(dirname14(enrolTokenCredentialPath), { recursive: true, mode: 448 });
17326
17538
  const sealing = Bun.spawn(["systemd-creds", "encrypt", "--name=enrol-token", "-", enrolTokenCredentialPath], { stdin: "pipe", stdout: "ignore", stderr: "pipe" });
17327
17539
  if (sealing.stdin && typeof sealing.stdin !== "number") {
17328
17540
  sealing.stdin.write(`${token}
@@ -17448,9 +17660,9 @@ function interactiveMetalBootstrap() {
17448
17660
  });
17449
17661
  }
17450
17662
  function writeBootstrapConfig(path, config) {
17451
- if (!isAbsolute6(path) || resolve10(path) !== path)
17663
+ if (!isAbsolute7(path) || resolve11(path) !== path)
17452
17664
  throw new Error("--output must be a canonical absolute path");
17453
- mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
17665
+ mkdirSync13(dirname14(path), { recursive: true, mode: 448 });
17454
17666
  writeFileSync13(path, `${JSON.stringify(config, null, 2)}
17455
17667
  `, { mode: 384, flag: "wx" });
17456
17668
  return path;
@@ -17512,10 +17724,10 @@ async function interactiveCloudflareBootstrap() {
17512
17724
  });
17513
17725
  }
17514
17726
  function genesisOutputDirectory(path) {
17515
- if (!isAbsolute6(path) || resolve10(path) !== path)
17727
+ if (!isAbsolute7(path) || resolve11(path) !== path)
17516
17728
  throw new Error("--output must be a canonical absolute directory");
17517
17729
  if (!existsSync12(path))
17518
- mkdirSync12(path, { recursive: true, mode: 448 });
17730
+ mkdirSync13(path, { recursive: true, mode: 448 });
17519
17731
  const metadata = lstatSync10(path);
17520
17732
  const uid = process.getuid?.();
17521
17733
  if (!metadata.isDirectory() || metadata.isSymbolicLink() || (metadata.mode & 63) !== 0 || uid !== undefined && uid !== 0 && metadata.uid !== uid) {
@@ -17523,6 +17735,37 @@ function genesisOutputDirectory(path) {
17523
17735
  }
17524
17736
  return path;
17525
17737
  }
17738
+ function interactiveOperatorHop(label, defaults) {
17739
+ return createOperatorSshHop({
17740
+ address: bootstrapAnswer(`${label} explicit SSH IP`, defaults.address),
17741
+ port: bootstrapNumber(`${label} SSH port`, "22"),
17742
+ user: bootstrapAnswer(`${label} SSH user`, defaults.user),
17743
+ hostKey: bootstrapAnswer(`${label} trusted ssh-ed25519 host public key`)
17744
+ });
17745
+ }
17746
+ function operatorIdentityCoordinates() {
17747
+ return {
17748
+ identityPublicKeyFile: bootstrapAnswer("Operator SSH public-key file (private key remains in Bitwarden SSH agent)"),
17749
+ agentSocket: bootstrapAnswer("Bitwarden SSH agent socket", process.env.SSH_AUTH_SOCK)
17750
+ };
17751
+ }
17752
+ function writeMetalOperatorFiles(directory) {
17753
+ const output = genesisOutputDirectory(directory);
17754
+ const config = interactiveMetalBootstrap();
17755
+ const metalConfig = writeBootstrapConfig(join12(output, "metal.json"), config);
17756
+ const identity = operatorIdentityCoordinates();
17757
+ const target = interactiveOperatorHop("Metal", { user: "root" });
17758
+ const remoteRequest = writeOperatorMetalBootstrapRequest(join12(output, "metal-remote.json"), {
17759
+ kind: "metal-remote",
17760
+ metalConfigFile: metalConfig,
17761
+ target: { ...target, ...identity },
17762
+ genesis: {
17763
+ environment: bootstrapAnswer("Genesis environment (production/development)", "development"),
17764
+ sshPublicKeyFiles: [identity.identityPublicKeyFile]
17765
+ }
17766
+ });
17767
+ return { metalConfig, remoteRequest };
17768
+ }
17526
17769
  function writePlatformGenesisFleet(directory) {
17527
17770
  const output = genesisOutputDirectory(directory);
17528
17771
  const template = interactiveBootstrap("platform", true);
@@ -17539,7 +17782,20 @@ function writePlatformGenesisFleet(directory) {
17539
17782
  writeBootstrapConfig(path, config);
17540
17783
  return path;
17541
17784
  });
17542
- return { configs };
17785
+ const identity = operatorIdentityCoordinates();
17786
+ const useJump = bootstrapAnswer("Reach genesis computes through the Metal SSH jump? (yes/no)", "yes") === "yes";
17787
+ const jump = useJump ? interactiveOperatorHop("Metal jump", { user: "root" }) : undefined;
17788
+ const requests = configs.map((platformConfigFile, index) => {
17789
+ const guest = fleet[index];
17790
+ const target = interactiveOperatorHop(`${guest.name} compute`, { address: guest.address, user: "forgezero" });
17791
+ return writeOperatorPlatformBootstrapRequest(join12(output, `${guest.name}-remote.json`), {
17792
+ kind: "platform-remote",
17793
+ platformConfigFile,
17794
+ target: { ...target, ...identity, ...jump ? { jump } : {} }
17795
+ });
17796
+ });
17797
+ const fleetRequest = writeOperatorPlatformBootstrapFleetRequest(join12(output, "platform-fleet-remote.json"), requests);
17798
+ return { configs, requests, fleetRequest };
17543
17799
  }
17544
17800
  function interactiveBootstrap(kind, genesis = false) {
17545
17801
  if (!process.stdin.isTTY)
@@ -17661,7 +17917,7 @@ async function cmdBootstrap(options, args) {
17661
17917
  if (operation === "config") {
17662
17918
  const kind = args[1];
17663
17919
  if (kind !== "metal" && kind !== "platform" && kind !== "cloudflare" || args[2] !== undefined || !options.outputPath) {
17664
- throw new Error("Usage: fz bootstrap config metal|cloudflare --output <absolute-file> | fz bootstrap config platform --output <absolute-owner-only-directory>");
17920
+ throw new Error("Usage: fz bootstrap config cloudflare --output <absolute-file> | fz bootstrap config metal|platform --output <absolute-owner-only-directory>");
17665
17921
  }
17666
17922
  if (kind === "platform") {
17667
17923
  out.line(JSON.stringify({ kind, ...writePlatformGenesisFleet(options.outputPath), mode: "0600" }, null, 2));
@@ -17673,10 +17929,32 @@ async function cmdBootstrap(options, args) {
17673
17929
  out.line(JSON.stringify({ kind, path, mode: "0600" }, null, 2));
17674
17930
  return 0;
17675
17931
  }
17676
- const config2 = interactiveMetalBootstrap();
17677
- out.line(JSON.stringify({ kind, path: writeBootstrapConfig(options.outputPath, config2), mode: "0600" }, null, 2));
17932
+ out.line(JSON.stringify({ kind, ...writeMetalOperatorFiles(options.outputPath), mode: "0600" }, null, 2));
17678
17933
  return 0;
17679
17934
  }
17935
+ if (operation === "platform" && args[1] === "fleet") {
17936
+ const mode = args[2];
17937
+ if (!mode || !["apply", "status"].includes(mode) || args[3] !== undefined) {
17938
+ throw new Error("Usage: fz bootstrap platform fleet <apply|status> --bootstrap-config <owner-only-platform-fleet-remote.json> [--apply]");
17939
+ }
17940
+ if (!options.bootstrapConfigPath)
17941
+ throw new Error("remote platform fleet bootstrap requires --bootstrap-config");
17942
+ const fleet = readOperatorPlatformBootstrapFleetRequest(options.bootstrapConfigPath);
17943
+ const plan2 = planOperatorPlatformFleetBootstrap(fleet, mode);
17944
+ if (!options.apply) {
17945
+ out.line(JSON.stringify(plan2, null, 2));
17946
+ out.step("Review all three pinned targets and shared secret names, then repeat with --apply.");
17947
+ return 0;
17948
+ }
17949
+ const first = readOperatorPlatformBootstrapRequest(fleet.requests[0]);
17950
+ const firstConfig = readBootstrapConfig(first.platformConfigFile);
17951
+ if (firstConfig.kind !== "platform")
17952
+ throw new Error("remote platform fleet requires platform configs");
17953
+ const secrets2 = mode === "apply" ? await promptPlatformBootstrapSecrets(firstConfig) : undefined;
17954
+ const result2 = await applyOperatorPlatformFleetBootstrap(fleet, mode, { secrets: secrets2 });
17955
+ out.line(JSON.stringify(result2, null, 2));
17956
+ return result2.ok ? 0 : 1;
17957
+ }
17680
17958
  if (operation === "platform" && args[1] === "remote") {
17681
17959
  const mode = args[2];
17682
17960
  if (!mode || !["apply", "status"].includes(mode) || args[3] !== undefined) {
@@ -17812,7 +18090,7 @@ async function cmdBootstrap(options, args) {
17812
18090
  return 0;
17813
18091
  }
17814
18092
  if (!["platform", "repair"].includes(operation)) {
17815
- throw new Error("Usage: fz bootstrap config <metal|platform|cloudflare>|platform [bundle|remote <apply|status>|cloudflare [verify|finalize]]|metal [remote <apply|genesis|rehearsal|rehearsal-cleanup|status>]|status|repair [--bootstrap-config <path>] [--apply]");
18093
+ throw new Error("Usage: fz bootstrap config <metal|platform|cloudflare>|platform [bundle|fleet <apply|status>|remote <apply|status>|cloudflare [verify|finalize]]|metal [remote <apply|genesis|rehearsal|rehearsal-cleanup|status>]|status|repair [--bootstrap-config <path>] [--apply]");
17816
18094
  }
17817
18095
  const config = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : operation === "repair" ? (() => {
17818
18096
  throw new Error("repair requires --bootstrap-config so immutable coordinates are revalidated");
@@ -18188,7 +18466,7 @@ async function cmdDeploy(options, args) {
18188
18466
  out.ok(`Initialized legacy .fz/deploy.json (${created.summary.digest}).`);
18189
18467
  return 0;
18190
18468
  }
18191
- initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(resolve10(options.projectRoot)), force: options.force });
18469
+ initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(resolve11(options.projectRoot)), force: options.force });
18192
18470
  const compiled = await compileDeploymentProject(options.projectRoot);
18193
18471
  if (options.json)
18194
18472
  out.line(JSON.stringify({ source: compiled.source, output: compiled.output, digest: compiled.digest }, null, 2));
@@ -18213,7 +18491,7 @@ async function cmdDeploy(options, args) {
18213
18491
  return problems.length === 0 ? 0 : 1;
18214
18492
  }
18215
18493
  if (operation === "check" || operation === "sync") {
18216
- if (existsSync12(resolve10(options.projectRoot, DEPLOY_SOURCE_FILE))) {
18494
+ if (existsSync12(resolve11(options.projectRoot, DEPLOY_SOURCE_FILE))) {
18217
18495
  const expected = await compileDeploymentProject(options.projectRoot, { write: false });
18218
18496
  const actual = inspectCompiledDeployment(options.projectRoot);
18219
18497
  const current = canonicalJson(expected.plan) === canonicalJson(actual.plan);
@@ -18390,10 +18668,13 @@ function usage() {
18390
18668
  fz bootstrap platform remote <apply|status>
18391
18669
  Run typed bootstrap from the operator laptop through
18392
18670
  a pinned host; apply copies and verifies that bundle
18671
+ fz bootstrap platform fleet <apply|status>
18672
+ Validate the exact three-node genesis identity, prompt
18673
+ shared secrets once and run all pinned nodes concurrently
18393
18674
  fz bootstrap platform Install/repair a typed elastic platform compute
18394
18675
  fz bootstrap config <metal|platform|cloudflare>
18395
- Write a validated owner-only metal/Cloudflare file or
18396
- strict three-node platform genesis directory; never apply locally
18676
+ Write validated owner-only metal config+remote request,
18677
+ Cloudflare file, or complete three-node platform request set
18397
18678
  fz bootstrap platform cloudflare
18398
18679
  Plan/apply attended Tunnel and DNS reconciliation
18399
18680
  using hidden management and KV/Worker runtime token prompts