@forgezero/agent 0.1.63 → 0.1.65

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.
@@ -292,7 +292,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
292
292
  }
293
293
 
294
294
  // src/version.ts
295
- var VERSION = "0.1.63";
295
+ var VERSION = "0.1.65";
296
296
 
297
297
  // src/otel-collector.ts
298
298
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
@@ -25,6 +25,34 @@ export interface OperatorMetalBootstrapRequest {
25
25
  sshPublicKeyFiles: string[];
26
26
  };
27
27
  }
28
+ export interface OperatorPlatformBootstrapFleetRequest {
29
+ kind: 'platform-fleet-remote';
30
+ requests: readonly [string, string, string];
31
+ }
32
+ export interface OperatorPlatformBootstrapFleetPlan {
33
+ kind: 'platform-fleet-remote';
34
+ mode: OperatorPlatformBootstrapMode;
35
+ mutation: boolean;
36
+ environment: PlatformGenesisEnvironment;
37
+ nodes: readonly {
38
+ computeReference: string;
39
+ address: string;
40
+ via?: string;
41
+ }[];
42
+ secretInputs: readonly string[];
43
+ }
44
+ export interface OperatorPlatformBootstrapFleetOutcome {
45
+ computeReference: string;
46
+ address: string;
47
+ ok: boolean;
48
+ output?: string;
49
+ error?: string;
50
+ }
51
+ export interface OperatorPlatformBootstrapFleetResult {
52
+ plan: OperatorPlatformBootstrapFleetPlan;
53
+ ok: boolean;
54
+ outcomes: readonly OperatorPlatformBootstrapFleetOutcome[];
55
+ }
28
56
  export type OperatorPlatformBootstrapMode = 'apply' | 'status';
29
57
  export type OperatorMetalBootstrapMode = 'apply' | 'genesis' | 'rehearsal' | 'rehearsal-cleanup' | 'status';
30
58
  export interface OperatorPlatformBootstrapPlan {
@@ -68,10 +96,16 @@ export interface OperatorPlatformBootstrapOptions {
68
96
  fzAgentPath?: string;
69
97
  fzGitSshPath?: string;
70
98
  }
99
+ export declare function createOperatorSshHop(input: Omit<OperatorSshHop, 'hostKeySha256'>): OperatorSshHop;
71
100
  export declare function readOperatorPlatformBootstrapRequest(path: string): OperatorPlatformBootstrapRequest;
101
+ export declare function writeOperatorPlatformBootstrapRequest(path: string, request: OperatorPlatformBootstrapRequest): string;
102
+ export declare function readOperatorPlatformBootstrapFleetRequest(path: string): OperatorPlatformBootstrapFleetRequest;
103
+ export declare function writeOperatorPlatformBootstrapFleetRequest(path: string, requestFiles: readonly [string, string, string]): string;
72
104
  export declare function readOperatorMetalBootstrapRequest(path: string): OperatorMetalBootstrapRequest;
105
+ export declare function writeOperatorMetalBootstrapRequest(path: string, request: OperatorMetalBootstrapRequest): string;
73
106
  export declare function planOperatorMetalBootstrap(request: OperatorMetalBootstrapRequest, mode: OperatorMetalBootstrapMode): OperatorMetalBootstrapPlan;
74
107
  export declare function planOperatorPlatformBootstrap(request: OperatorPlatformBootstrapRequest, mode: OperatorPlatformBootstrapMode): OperatorPlatformBootstrapPlan;
108
+ export declare function planOperatorPlatformFleetBootstrap(fleet: OperatorPlatformBootstrapFleetRequest, mode: OperatorPlatformBootstrapMode): OperatorPlatformBootstrapFleetPlan;
75
109
  /**
76
110
  * Run one reviewed operator action. No private SSH key, credential value or
77
111
  * management token is placed in argv; OpenSSH selects the matching private key
@@ -83,6 +117,14 @@ export declare function applyOperatorPlatformBootstrap(request: OperatorPlatform
83
117
  plan: OperatorPlatformBootstrapPlan;
84
118
  output: string;
85
119
  }>;
120
+ /**
121
+ * Run the exact three-node genesis fleet from one attended secret collection.
122
+ * Every request is fully validated before the first SSH connection. Nodes run
123
+ * concurrently because the three-member Agency cannot form sequentially.
124
+ */
125
+ export declare function applyOperatorPlatformFleetBootstrap(fleet: OperatorPlatformBootstrapFleetRequest, mode: OperatorPlatformBootstrapMode, options?: OperatorPlatformBootstrapOptions & {
126
+ secrets?: PlatformBootstrapSecrets;
127
+ }): Promise<OperatorPlatformBootstrapFleetResult>;
86
128
  /**
87
129
  * Install/bootstrap one blank metal host or provision its exact three-node
88
130
  * platform seed through the same pinned SSH transport used for guest bootstrap.
@@ -1396,7 +1396,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1396
1396
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1397
1397
 
1398
1398
  // src/version.ts
1399
- var VERSION = "0.1.63";
1399
+ var VERSION = "0.1.65";
1400
1400
 
1401
1401
  // src/software.ts
1402
1402
  var PINNED_BUN_VERSION = "1.3.14";
@@ -2045,17 +2045,16 @@ function agentUnit(options) {
2045
2045
  if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
2046
2046
  throw new Error("migration pull and lifecycle profile must be supplied together");
2047
2047
  }
2048
- const bootstrapEnabled = Boolean(options.pullBootstrap && options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath && options.bootstrapTargetTelemetryEndpoint);
2049
- if ([
2050
- options.pullBootstrap,
2051
- options.bootstrapSshCredentialPath,
2052
- options.bootstrapSshPublicKeyPath,
2053
- options.bootstrapTargetTelemetryEndpoint
2054
- ].some(Boolean) && !bootstrapEnabled) {
2055
- throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
2048
+ const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
2049
+ if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath)) {
2050
+ throw new Error("bootstrap SSH credential and public key paths must be supplied together");
2051
+ }
2052
+ const bootstrapEnabled = Boolean(options.pullBootstrap);
2053
+ if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
2054
+ throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
2056
2055
  }
2057
2056
  const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
2058
- const bootstrapSshCredentialPath = bootstrapEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
2057
+ const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
2059
2058
  const warpValues = [
2060
2059
  options.warpOrganization,
2061
2060
  options.warpClientIdCredentialPath,
@@ -2294,15 +2293,13 @@ function planProvision(options) {
2294
2293
  if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
2295
2294
  throw new Error("migration pull and lifecycle profile must be supplied together");
2296
2295
  }
2297
- const bootstrapEnabled = Boolean(options.pullBootstrap && options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath && options.bootstrapTargetTelemetryEndpoint);
2298
- if ([
2299
- options.pullBootstrap,
2300
- options.bootstrapSshCredentialPath,
2301
- options.bootstrapSshPublicKeyPath,
2302
- options.bootstrapSshSourcePath,
2303
- options.bootstrapTargetTelemetryEndpoint
2304
- ].some(Boolean) && !bootstrapEnabled) {
2305
- throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
2296
+ const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
2297
+ if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath) || options.bootstrapSshSourcePath && !bootstrapIdentityEnabled) {
2298
+ throw new Error("bootstrap SSH credential and public key paths must be supplied together");
2299
+ }
2300
+ const bootstrapEnabled = Boolean(options.pullBootstrap);
2301
+ if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
2302
+ throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
2306
2303
  }
2307
2304
  const warpValues = [
2308
2305
  options.warpOrganization,
@@ -2312,13 +2309,13 @@ function planProvision(options) {
2312
2309
  const warpEnabled = warpValues.every(Boolean);
2313
2310
  if (warpValues.some(Boolean) && !warpEnabled)
2314
2311
  throw new Error("WARP configuration must be supplied together");
2315
- const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath && options.enrolStatePath);
2316
- if (Boolean(options.enrolTokenCredentialPath) !== Boolean(options.enrolStatePath) || options.enrolTokenSourcePath && !enrolmentEnabled) {
2317
- throw new Error("direct enrolment credential and state paths must be supplied together");
2312
+ const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath);
2313
+ if (options.enrolTokenCredentialPath && !options.enrolStatePath || options.enrolTokenSourcePath && (!options.enrolTokenCredentialPath || !options.enrolStatePath)) {
2314
+ throw new Error("direct enrolment credential requires its durable state path");
2318
2315
  }
2319
2316
  const enrolTokenSourcePath = options.enrolTokenSourcePath ? systemdPath(options.enrolTokenSourcePath, "enrolment source") : undefined;
2320
2317
  const enrolTokenCredentialPath = enrolmentEnabled ? systemdPath(options.enrolTokenCredentialPath, "enrolment credential") : undefined;
2321
- const enrolStatePath = enrolmentEnabled ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
2318
+ const enrolStatePath = options.enrolStatePath ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
2322
2319
  const enrolStateDir = enrolStatePath?.replace(/\/[^/]+$/, "");
2323
2320
  const sourceBinPath = options.sourceBinPath ? systemdPath(options.sourceBinPath, "agent source binary") : undefined;
2324
2321
  const binPath = options.binPath ? systemdPath(options.binPath, "agent binary") : undefined;
@@ -2330,8 +2327,8 @@ function planProvision(options) {
2330
2327
  const gitPublicKeyDir = gitPublicKeyPath?.replace(/\/[^/]+$/, "");
2331
2328
  const lifecycleProfilePath = lifecycleEnabled ? systemdPath(options.lifecycleProfilePath, "lifecycle profile") : undefined;
2332
2329
  const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
2333
- const bootstrapSshCredentialPath = bootstrapEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
2334
- const bootstrapSshPublicKeyPath = bootstrapEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
2330
+ const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
2331
+ const bootstrapSshPublicKeyPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
2335
2332
  const bootstrapSshSourcePath = options.bootstrapSshSourcePath ? systemdPath(options.bootstrapSshSourcePath, "bootstrap SSH private-key source") : undefined;
2336
2333
  const bootstrapSshPublicKeyDir = bootstrapSshPublicKeyPath?.replace(/\/[^/]+$/, "");
2337
2334
  const warpClientIdCredentialPath = warpEnabled ? systemdPath(options.warpClientIdCredentialPath, "WARP client-id credential") : undefined;
@@ -2431,7 +2428,7 @@ function planProvision(options) {
2431
2428
  step("Git deploy identity directory", { kind: "directories", directories: [{ path: gitPublicKeyDir, mode: 493, owner: "root", group: "root" }] }),
2432
2429
  step("unique encrypted Git deploy identity", { kind: "ensure-git-identity", credential: gitCredentialPath, publicKey: gitPublicKeyPath })
2433
2430
  ] : [],
2434
- ...bootstrapEnabled ? [
2431
+ ...bootstrapIdentityEnabled ? [
2435
2432
  step("bootstrap SSH public identity directory", { kind: "directories", directories: [
2436
2433
  { path: bootstrapSshPublicKeyDir, mode: 493, owner: "root", group: "root" }
2437
2434
  ] }),
@@ -2467,6 +2464,10 @@ function planProvision(options) {
2467
2464
  { argv: ["/usr/bin/rm", "-f", "/etc/systemd/system/forgezero-deploy-runner.socket"] },
2468
2465
  { argv: ["/usr/bin/systemctl", "daemon-reload"] }
2469
2466
  ] })] : [],
2467
+ ...enrolStatePath && !enrolmentEnabled ? [step("retire consumed enrolment unit", { kind: "commands", commands: [
2468
+ { argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-agent-enrol.service"], acceptedExitCodes: [0, 1, 5] },
2469
+ { argv: ["/usr/bin/rm", "-f", ENROLMENT_UNIT_PATH] }
2470
+ ] })] : [],
2470
2471
  step("enable and converge services", { kind: "commands", commands: [
2471
2472
  { argv: ["/usr/bin/systemctl", "enable", ...enabledUnits] },
2472
2473
  { argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
@@ -4181,7 +4182,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4181
4182
  if (connectorCapabilities?.meshConnectorToken && !host.exists(WARP_CONNECTOR_CREDENTIAL)) {
4182
4183
  await seal(host, "CF_WARP_CONNECTOR_TOKEN", WARP_CONNECTOR_CREDENTIAL, connectorCapabilities.meshConnectorToken);
4183
4184
  }
4184
- await host.installAgent(config);
4185
+ if (alreadyEnrolled) {
4186
+ await host.installAgent(config, "bound");
4187
+ } else if (config.kind === "platform") {
4188
+ if (bootstrapManifest)
4189
+ await host.installAgent(config, "bootstrap");
4190
+ } else {
4191
+ await host.installAgent(config, "enrol");
4192
+ await host.installAgent(config, "bound");
4193
+ }
4185
4194
  if (config.kind === "platform" && config.firewall.enabled) {
4186
4195
  await host.ensureSoftware(plan.software.filter(({ id }) => id === "ufw"));
4187
4196
  } else
@@ -4331,7 +4340,19 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4331
4340
  `, 384);
4332
4341
  host.remove(config.bootstrapBundle.bundleFile);
4333
4342
  host.remove(config.bootstrapBundle.manifestFile);
4334
- await host.installAgent(config);
4343
+ }
4344
+ if (!alreadyEnrolled) {
4345
+ await checked3(host, [
4346
+ "curl",
4347
+ "--fail",
4348
+ "--silent",
4349
+ "--show-error",
4350
+ "--max-time",
4351
+ "10",
4352
+ `http://127.0.0.1:3000${runtime.healthPath}`
4353
+ ], "release-one API health");
4354
+ await host.installAgent(config, "enrol");
4355
+ await host.installAgent(config, "bound");
4335
4356
  }
4336
4357
  }
4337
4358
  if (config.cloudflareHandoff) {
@@ -4500,6 +4521,65 @@ function readBootstrapConfig(path) {
4500
4521
  }
4501
4522
  return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync5(path, "utf8"))));
4502
4523
  }
4524
+ function planBootstrapAgentInstall(config, phase, context) {
4525
+ const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
4526
+ if (phase === "bootstrap") {
4527
+ if (config.kind !== "platform" || hasBinding || !initialBundle) {
4528
+ throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
4529
+ }
4530
+ } else if (phase === "enrol") {
4531
+ if (hasBinding || !hasEnrolCredential) {
4532
+ throw new Error("Agent enrolment requires one unbound host and its sealed one-use credential");
4533
+ }
4534
+ } else if (!hasBinding) {
4535
+ throw new Error("bound Agent convergence requires durable enrolment state");
4536
+ }
4537
+ const bound = phase === "bound";
4538
+ const enrolling = phase === "enrol";
4539
+ const authenticated = enrolling || bound;
4540
+ const bootstrapRunner = platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner);
4541
+ const options = {
4542
+ capabilities,
4543
+ socketPath: DEFAULT_SOCKET2,
4544
+ seedPath: "/var/lib/forgezero/node.seed",
4545
+ controlSocketPath: CONTROL_SOCKET,
4546
+ repository: phase === "bootstrap" ? initialBundle.path : bound && config.kind === "enrolled-compute" ? config.repository : undefined,
4547
+ branch: phase === "bootstrap" ? initialBundle.manifest.branch : bound && config.kind === "enrolled-compute" ? config.branch : undefined,
4548
+ bootstrapBundlePath: phase === "bootstrap" ? initialBundle.path : undefined,
4549
+ bootstrapBundleManifestPath: phase === "bootstrap" ? initialBundle.manifestPath : undefined,
4550
+ profile: config.profile,
4551
+ deployRoot: config.deployRoot ?? "/opt/forgezero",
4552
+ deploymentCredentials: config.deploymentCredentials,
4553
+ publicApiUrl: config.apiUrl,
4554
+ gitCredentialPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
4555
+ gitPublicKeyPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
4556
+ generateGitIdentity: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey === true,
4557
+ pullDeployments: bound,
4558
+ pullMigrations: bound && config.kind === "platform",
4559
+ pullBootstrap: bound && bootstrapRunner,
4560
+ bootstrapSshCredentialPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
4561
+ bootstrapSshSourcePath: enrolling && config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
4562
+ bootstrapSshPublicKeyPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
4563
+ bootstrapTargetTelemetryEndpoint: bound && bootstrapRunner ? platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined : undefined,
4564
+ lifecycleProfilePath: bound && config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
4565
+ enforceEgress: true,
4566
+ nodeHostname: config.nodeHostname,
4567
+ telemetryEndpoint: config.telemetryEndpoint,
4568
+ binPath: "/usr/local/lib/forgezero/agent/fz-agent",
4569
+ sourceBinPath: PACKAGED_AGENT_BIN,
4570
+ ...enrolling ? {
4571
+ enrolTokenCredentialPath: ENROL_CREDENTIAL,
4572
+ enrolStatePath: "/var/lib/forgezero/enrolment.json"
4573
+ } : bound ? { enrolStatePath: "/var/lib/forgezero/enrolment.json" } : {},
4574
+ ...authenticated ? {
4575
+ apiUrl: config.apiUrl,
4576
+ project: config.kind === "enrolled-compute" ? config.realm : "platform",
4577
+ environment: config.kind === "enrolled-compute" ? undefined : config.environment,
4578
+ nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
4579
+ } : {}
4580
+ };
4581
+ return planInstall(options);
4582
+ }
4503
4583
  function localBootstrapHost() {
4504
4584
  const execute = async (argv, options = {}) => {
4505
4585
  const child = Bun.spawn([...argv], { stdin: options.stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
@@ -4547,10 +4627,10 @@ function localBootstrapHost() {
4547
4627
  throw new Error(`Agent software requirements failed: ${result.output.trim()}`);
4548
4628
  return result;
4549
4629
  },
4550
- async installAgent(config) {
4630
+ async installAgent(config, phase) {
4551
4631
  const capabilities = await readCapabilities(localRunner);
4552
- const deployRoot = config.deployRoot ?? "/opt/forgezero";
4553
4632
  const hasBinding = existsSync5("/var/lib/forgezero/enrolment.json");
4633
+ const hasEnrolCredential = existsSync5(ENROL_CREDENTIAL);
4554
4634
  const initialBundle = config.kind === "platform" && !existsSync5(BOOTSTRAP_RELEASE_EVIDENCE) ? {
4555
4635
  path: config.bootstrapBundle.bundleFile,
4556
4636
  manifestPath: config.bootstrapBundle.manifestFile,
@@ -4571,45 +4651,11 @@ function localBootstrapHost() {
4571
4651
  writeFileSync5(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
4572
4652
  `, { mode: 256 });
4573
4653
  }
4574
- const plan = planInstall({
4654
+ const plan = planBootstrapAgentInstall(config, phase, {
4575
4655
  capabilities,
4576
- socketPath: DEFAULT_SOCKET2,
4577
- seedPath: "/var/lib/forgezero/node.seed",
4578
- controlSocketPath: "/run/forgezero/control.sock",
4579
- repository: initialBundle?.path ?? (config.kind === "enrolled-compute" ? config.repository : undefined),
4580
- branch: initialBundle?.manifest.branch ?? (config.kind === "enrolled-compute" ? config.branch : undefined),
4581
- bootstrapBundlePath: initialBundle?.path,
4582
- bootstrapBundleManifestPath: initialBundle?.manifestPath,
4583
- profile: config.kind === "platform" ? config.profile : config.profile,
4584
- deployRoot,
4585
- deploymentCredentials: config.deploymentCredentials,
4586
- publicApiUrl: config.apiUrl,
4587
- gitCredentialPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
4588
- gitPublicKeyPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
4589
- generateGitIdentity: config.kind === "enrolled-compute" && config.gitDeployKey === true,
4590
- pullDeployments: hasBinding,
4591
- pullMigrations: config.kind === "platform" && hasBinding,
4592
- pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
4593
- bootstrapSshCredentialPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
4594
- bootstrapSshSourcePath: config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
4595
- bootstrapSshPublicKeyPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
4596
- bootstrapTargetTelemetryEndpoint: platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined,
4597
- lifecycleProfilePath: config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
4598
- enforceEgress: true,
4599
- nodeHostname: config.nodeHostname,
4600
- telemetryEndpoint: config.telemetryEndpoint,
4601
- binPath: "/usr/local/lib/forgezero/agent/fz-agent",
4602
- sourceBinPath: PACKAGED_AGENT_BIN,
4603
- ...existsSync5(ENROL_CREDENTIAL) || hasBinding ? {
4604
- enrolTokenCredentialPath: existsSync5(ENROL_CREDENTIAL) ? ENROL_CREDENTIAL : undefined,
4605
- enrolStatePath: "/var/lib/forgezero/enrolment.json"
4606
- } : {},
4607
- ...hasBinding ? {
4608
- apiUrl: config.apiUrl,
4609
- project: config.kind === "enrolled-compute" ? config.realm : "platform",
4610
- environment: config.kind === "enrolled-compute" ? undefined : config.environment,
4611
- nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
4612
- } : {}
4656
+ hasBinding,
4657
+ hasEnrolCredential,
4658
+ initialBundle
4613
4659
  });
4614
4660
  for (const unit of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
4615
4661
  mkdirSync5(dirname6(unit.path), { recursive: true, mode: 493 });
@@ -5525,10 +5571,10 @@ async function metalBootstrapStatus(exec = defaultExec2) {
5525
5571
 
5526
5572
  // src/operator-bootstrap.ts
5527
5573
  import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
5528
- import { lstatSync as lstatSync5, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
5574
+ import { chmodSync as chmodSync5, lstatSync as lstatSync5, mkdirSync as mkdirSync8, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
5529
5575
  import { isIP as isIP6 } from "net";
5530
5576
  import { tmpdir } from "os";
5531
- import { basename, join as join6 } from "path";
5577
+ import { basename, dirname as dirname9, isAbsolute as isAbsolute4, join as join6, resolve as resolve6 } from "path";
5532
5578
  import { fileURLToPath as fileURLToPath2 } from "url";
5533
5579
 
5534
5580
  // src/platform-genesis.ts
@@ -5663,6 +5709,25 @@ var exactKeys3 = (value, keys, label) => {
5663
5709
  throw new Error(`${label} contains unknown fields: ${unknown.join(", ")}`);
5664
5710
  return record;
5665
5711
  };
5712
+ var canonicalOutputPath = (path, label) => {
5713
+ if (!isAbsolute4(path) || resolve6(path) !== path || /[\r\n\0]/.test(path)) {
5714
+ throw new Error(`${label} must be a canonical absolute path`);
5715
+ }
5716
+ return path;
5717
+ };
5718
+ var writeOwnerJson2 = (path, value, label) => {
5719
+ canonicalOutputPath(path, label);
5720
+ mkdirSync8(dirname9(path), { recursive: true, mode: 448 });
5721
+ const parent = lstatSync5(dirname9(path));
5722
+ const uid = process.getuid?.() ?? parent.uid;
5723
+ if (!parent.isDirectory() || parent.isSymbolicLink() || parent.uid !== uid || (parent.mode & 63) !== 0) {
5724
+ throw new Error(`${label} parent must be an owner-only directory`);
5725
+ }
5726
+ writeFileSync8(path, `${JSON.stringify(value, null, 2)}
5727
+ `, { mode: 384, flag: "wx" });
5728
+ chmodSync5(path, 384);
5729
+ return path;
5730
+ };
5666
5731
  var ownerFile = (path, limit, label) => {
5667
5732
  if (!path.startsWith("/") || /[\r\n]/.test(path))
5668
5733
  throw new Error(`${label} path must be absolute`);
@@ -5721,8 +5786,10 @@ var validateHop = (value, label) => {
5721
5786
  }
5722
5787
  return hop;
5723
5788
  };
5724
- function readOperatorPlatformBootstrapRequest(path) {
5725
- const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator bootstrap request")));
5789
+ function createOperatorSshHop(input) {
5790
+ return validateHop({ ...input, hostKeySha256: fingerprint(input.hostKey) }, "operator SSH hop");
5791
+ }
5792
+ var validatePlatformRequestValue = (value) => {
5726
5793
  const root = exactKeys3(value, ["kind", "target", "platformConfigFile"], "operator bootstrap request");
5727
5794
  if (root.kind !== "platform-remote")
5728
5795
  throw new Error("operator bootstrap kind must be platform-remote");
@@ -5741,6 +5808,7 @@ function readOperatorPlatformBootstrapRequest(path) {
5741
5808
  socketPath(targetValue.agentSocket);
5742
5809
  if (typeof root.platformConfigFile !== "string")
5743
5810
  throw new Error("platformConfigFile must be an absolute path");
5811
+ canonicalOutputPath(root.platformConfigFile, "platformConfigFile");
5744
5812
  const config = readBootstrapConfig(root.platformConfigFile);
5745
5813
  if (config.kind !== "platform")
5746
5814
  throw new Error("operator bootstrap requires a platform config");
@@ -5754,9 +5822,39 @@ function readOperatorPlatformBootstrapRequest(path) {
5754
5822
  ...targetValue.jump === undefined ? {} : { jump: validateHop(targetValue.jump, "operator bootstrap jump") }
5755
5823
  }
5756
5824
  };
5757
- }
5758
- function readOperatorMetalBootstrapRequest(path) {
5759
- const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal request")));
5825
+ };
5826
+ function readOperatorPlatformBootstrapRequest(path) {
5827
+ const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator bootstrap request")));
5828
+ return validatePlatformRequestValue(value);
5829
+ }
5830
+ function writeOperatorPlatformBootstrapRequest(path, request) {
5831
+ validatePlatformRequestValue(request);
5832
+ return writeOwnerJson2(path, request, "operator bootstrap request");
5833
+ }
5834
+ function readOperatorPlatformBootstrapFleetRequest(path) {
5835
+ const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator fleet request")));
5836
+ const root = exactKeys3(value, ["kind", "requests"], "operator fleet request");
5837
+ if (root.kind !== "platform-fleet-remote")
5838
+ throw new Error("operator fleet kind must be platform-fleet-remote");
5839
+ if (!Array.isArray(root.requests) || root.requests.length !== 3 || root.requests.some((entry) => typeof entry !== "string")) {
5840
+ throw new Error("operator fleet requires exactly three request files");
5841
+ }
5842
+ const requests = root.requests.map((entry) => canonicalOutputPath(entry, "operator fleet request file"));
5843
+ if (new Set(requests).size !== 3)
5844
+ throw new Error("operator fleet request files must be unique");
5845
+ for (const request of requests)
5846
+ readOperatorPlatformBootstrapRequest(request);
5847
+ return { kind: "platform-fleet-remote", requests };
5848
+ }
5849
+ function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles) {
5850
+ const request = { kind: "platform-fleet-remote", requests: requestFiles };
5851
+ if (new Set(requestFiles).size !== 3)
5852
+ throw new Error("operator fleet request files must be unique");
5853
+ for (const file of requestFiles)
5854
+ readOperatorPlatformBootstrapRequest(file);
5855
+ return writeOwnerJson2(path, request, "operator fleet request");
5856
+ }
5857
+ var validateMetalRequestValue = (value) => {
5760
5858
  const root = exactKeys3(value, ["kind", "target", "metalConfigFile", "genesis"], "operator metal request");
5761
5859
  if (root.kind !== "metal-remote")
5762
5860
  throw new Error("operator metal bootstrap kind must be metal-remote");
@@ -5775,6 +5873,7 @@ function readOperatorMetalBootstrapRequest(path) {
5775
5873
  socketPath(targetValue.agentSocket);
5776
5874
  if (typeof root.metalConfigFile !== "string")
5777
5875
  throw new Error("metalConfigFile must be an absolute path");
5876
+ canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
5778
5877
  readMetalBootstrapConfig(root.metalConfigFile);
5779
5878
  const genesis = exactKeys3(root.genesis, ["environment", "sshPublicKeyFiles"], "operator metal genesis");
5780
5879
  if (typeof genesis.environment !== "string" || !PLATFORM_GENESIS_ENVIRONMENTS.includes(genesis.environment)) {
@@ -5798,6 +5897,14 @@ function readOperatorMetalBootstrapRequest(path) {
5798
5897
  sshPublicKeyFiles: genesis.sshPublicKeyFiles
5799
5898
  }
5800
5899
  };
5900
+ };
5901
+ function readOperatorMetalBootstrapRequest(path) {
5902
+ const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal request")));
5903
+ return validateMetalRequestValue(value);
5904
+ }
5905
+ function writeOperatorMetalBootstrapRequest(path, request) {
5906
+ validateMetalRequestValue(request);
5907
+ return writeOwnerJson2(path, request, "operator metal request");
5801
5908
  }
5802
5909
  function planOperatorMetalBootstrap(request, mode) {
5803
5910
  const config = readMetalBootstrapConfig(request.metalConfigFile);
@@ -5857,6 +5964,87 @@ function planOperatorPlatformBootstrap(request, mode) {
5857
5964
  secretInputs: mode === "apply" ? [...attendedSecretNames(config), ...secretSources(config).map(([name]) => name)] : []
5858
5965
  };
5859
5966
  }
5967
+ var fleetConfigIdentity = (config) => {
5968
+ const normalized = structuredClone(config);
5969
+ delete normalized.computeReference;
5970
+ delete normalized.nodeHostname;
5971
+ delete normalized.cloudflareHandoff;
5972
+ delete normalized.database.role;
5973
+ delete normalized.database.address;
5974
+ delete normalized.database.master;
5975
+ const environment = normalized.runtime.environment;
5976
+ delete environment.databaseRole;
5977
+ delete environment.databaseAddress;
5978
+ delete environment.databaseMaster;
5979
+ delete environment.nodeHostname;
5980
+ delete environment.seedSyncPeers;
5981
+ delete environment.custodianEmail;
5982
+ return JSON.stringify(normalized);
5983
+ };
5984
+ function validatedPlatformFleet(fleet) {
5985
+ const requests = fleet.requests.map(readOperatorPlatformBootstrapRequest);
5986
+ const configs = requests.map((request) => {
5987
+ const config = readBootstrapConfig(request.platformConfigFile);
5988
+ if (config.kind !== "platform")
5989
+ throw new Error("operator fleet requires platform configs");
5990
+ return config;
5991
+ });
5992
+ const environment = configs[0].environment;
5993
+ const expected = PLATFORM_GENESIS_FLEETS[environment];
5994
+ if (!expected || expected.length !== 3)
5995
+ throw new Error("operator fleet environment is invalid");
5996
+ const identity = fleetConfigIdentity(configs[0]);
5997
+ const operatorIdentity = `${requests[0].target.identityPublicKeyFile}\x00${requests[0].target.agentSocket}`;
5998
+ const jumpIdentity = JSON.stringify(requests[0].target.jump ?? null);
5999
+ const coordinators = expected.map((guest) => `http://${guest.address}:8529`);
6000
+ const nodeHostnames = new Set;
6001
+ for (let index = 0;index < 3; index += 1) {
6002
+ const config = configs[index];
6003
+ const request = requests[index];
6004
+ const guest = expected[index];
6005
+ const role = index === 0 ? "master" : "joiner";
6006
+ if (config.environment !== environment || fleetConfigIdentity(config) !== identity) {
6007
+ throw new Error("operator fleet platform configs do not share one immutable identity");
6008
+ }
6009
+ 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) {
6010
+ throw new Error(`operator fleet ${guest.name} does not match the exact genesis topology`);
6011
+ }
6012
+ if (request.target.address !== guest.address || request.target.port !== 22 || request.target.user !== "forgezero") {
6013
+ throw new Error(`operator fleet ${guest.name} SSH target must be forgezero@${guest.address}:22`);
6014
+ }
6015
+ if (`${request.target.identityPublicKeyFile}\x00${request.target.agentSocket}` !== operatorIdentity) {
6016
+ throw new Error("operator fleet must use one caller-approved SSH identity and agent socket");
6017
+ }
6018
+ if (JSON.stringify(request.target.jump ?? null) !== jumpIdentity) {
6019
+ throw new Error("operator fleet must use one pinned Metal jump or no jump on every node");
6020
+ }
6021
+ if (nodeHostnames.has(config.nodeHostname))
6022
+ throw new Error("operator fleet node hostnames must be unique");
6023
+ nodeHostnames.add(config.nodeHostname);
6024
+ }
6025
+ return { requests, configs, environment };
6026
+ }
6027
+ function planOperatorPlatformFleetBootstrap(fleet, mode) {
6028
+ const { requests, configs, environment } = validatedPlatformFleet(fleet);
6029
+ const secretInputs = mode === "apply" ? attendedSecretNames(configs[0]) : [];
6030
+ for (const config of configs.slice(1)) {
6031
+ if (JSON.stringify(attendedSecretNames(config)) !== JSON.stringify(secretInputs)) {
6032
+ throw new Error("operator fleet configs do not share one attended secret schema");
6033
+ }
6034
+ }
6035
+ return {
6036
+ kind: "platform-fleet-remote",
6037
+ mode,
6038
+ mutation: mode !== "status",
6039
+ environment,
6040
+ nodes: requests.map((request, index) => ({
6041
+ computeReference: configs[index].computeReference,
6042
+ address: request.target.address,
6043
+ ...request.target.jump ? { via: request.target.jump.address } : {}
6044
+ })),
6045
+ secretInputs
6046
+ };
6047
+ }
5860
6048
  var defaultExec3 = async (argv, options = {}) => {
5861
6049
  const child = Bun.spawn([...argv], {
5862
6050
  stdin: options.stdin === undefined ? "ignore" : "pipe",
@@ -6099,6 +6287,30 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
6099
6287
  rmSync6(directory, { recursive: true, force: true });
6100
6288
  }
6101
6289
  }
6290
+ async function applyOperatorPlatformFleetBootstrap(fleet, mode, options = {}) {
6291
+ const plan = planOperatorPlatformFleetBootstrap(fleet, mode);
6292
+ const { requests, configs } = validatedPlatformFleet(fleet);
6293
+ const secrets = mode === "apply" ? validatePlatformBootstrapSecrets(configs[0], options.secrets) : undefined;
6294
+ if (secrets) {
6295
+ for (const config of configs.slice(1))
6296
+ validatePlatformBootstrapSecrets(config, secrets);
6297
+ }
6298
+ const settled = await Promise.allSettled(requests.map((request) => applyOperatorPlatformBootstrap(request, mode, { ...options, secrets })));
6299
+ const outcomes = settled.map((result, index) => {
6300
+ const config = configs[index];
6301
+ const address = requests[index].target.address;
6302
+ if (result.status === "fulfilled")
6303
+ return {
6304
+ computeReference: config.computeReference,
6305
+ address,
6306
+ ok: true,
6307
+ output: result.value.output.slice(0, 65536)
6308
+ };
6309
+ const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
6310
+ return { computeReference: config.computeReference, address, ok: false, error: message.slice(0, 4096) };
6311
+ });
6312
+ return { plan, ok: outcomes.every(({ ok }) => ok), outcomes };
6313
+ }
6102
6314
  async function applyOperatorMetalBootstrap(request, mode, options = {}) {
6103
6315
  const plan = planOperatorMetalBootstrap(request, mode);
6104
6316
  publicIdentity(request.target.identityPublicKeyFile);
@@ -6153,10 +6365,17 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
6153
6365
  }
6154
6366
  }
6155
6367
  export {
6368
+ writeOperatorPlatformBootstrapRequest,
6369
+ writeOperatorPlatformBootstrapFleetRequest,
6370
+ writeOperatorMetalBootstrapRequest,
6156
6371
  readOperatorPlatformBootstrapRequest,
6372
+ readOperatorPlatformBootstrapFleetRequest,
6157
6373
  readOperatorMetalBootstrapRequest,
6374
+ planOperatorPlatformFleetBootstrap,
6158
6375
  planOperatorPlatformBootstrap,
6159
6376
  planOperatorMetalBootstrap,
6377
+ createOperatorSshHop,
6378
+ applyOperatorPlatformFleetBootstrap,
6160
6379
  applyOperatorPlatformBootstrap,
6161
6380
  applyOperatorMetalBootstrap
6162
6381
  };