@forgezero/agent 0.1.68 → 0.1.70

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.
@@ -749,7 +749,7 @@ async function postSignedNode(options, path, body) {
749
749
  }
750
750
 
751
751
  // src/version.ts
752
- var VERSION3 = "0.1.68";
752
+ var VERSION3 = "0.1.70";
753
753
 
754
754
  // src/agent-heartbeat.ts
755
755
  var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
@@ -0,0 +1,22 @@
1
+ import { finalizeCloudflareBootstrapAcceptance, runAttendedCloudflareBootstrap, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapEvidence } from './cloudflare-bootstrap';
2
+ import { applyOperatorPlatformFleetBootstrap, type OperatorPlatformBootstrapFleetRequest, type OperatorPlatformBootstrapFleetResult } from './operator-bootstrap';
3
+ import type { PlatformBootstrapSecrets } from './bootstrap';
4
+ export interface AttendedPlatformBootstrapResult {
5
+ kind: 'forgezero-attended-platform-bootstrap';
6
+ ok: boolean;
7
+ edge: CloudflareBootstrapEvidence;
8
+ fleet: OperatorPlatformBootstrapFleetResult;
9
+ acceptance?: CloudflareBootstrapAcceptanceEvidence;
10
+ inviteUrl?: string;
11
+ }
12
+ export interface AttendedPlatformBootstrapDependencies {
13
+ cloudflare?: typeof runAttendedCloudflareBootstrap;
14
+ fleet?: typeof applyOperatorPlatformFleetBootstrap;
15
+ finalize?: typeof finalizeCloudflareBootstrapAcceptance;
16
+ }
17
+ /**
18
+ * The sole attended genesis composition boundary. All non-secret coordinates
19
+ * are checked before the first Cloudflare or SSH mutation. Secret values exist
20
+ * only in this call and the authenticated SSH stdin used by the fleet runner.
21
+ */
22
+ export declare function applyAttendedPlatformBootstrap(fleetRequest: OperatorPlatformBootstrapFleetRequest, cloudflareRequest: AttendedCloudflareBootstrapRequest, acceptancePath: string, secrets: PlatformBootstrapSecrets, dependencies?: AttendedPlatformBootstrapDependencies): Promise<AttendedPlatformBootstrapResult>;
package/dist/bootstrap.js CHANGED
@@ -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.68";
1399
+ var VERSION = "0.1.70";
1400
1400
 
1401
1401
  // src/software.ts
1402
1402
  var PINNED_BUN_VERSION = "1.3.14";
@@ -4380,6 +4380,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4380
4380
  }
4381
4381
  bindBootstrapIntent(host, config);
4382
4382
  const plan = planBootstrap(config, host.exists(STATE_PATH));
4383
+ await checked3(host, ["hostnamectl", "set-hostname", "--static", config.nodeHostname], "compute hostname");
4383
4384
  const alreadyEnrolled = host.exists("/var/lib/forgezero/enrolment.json");
4384
4385
  host.mkdir(CREDS, 448);
4385
4386
  host.mkdir("/var/lib/forgezero", 448);
package/dist/fz-agent.js CHANGED
@@ -8588,7 +8588,7 @@ import { join as join3 } from "path";
8588
8588
  import { fileURLToPath } from "url";
8589
8589
 
8590
8590
  // src/version.ts
8591
- var VERSION2 = "0.1.68";
8591
+ var VERSION2 = "0.1.70";
8592
8592
 
8593
8593
  // src/ssh-bootstrap.ts
8594
8594
  class SshBootstrapError extends Error {
package/dist/fz.js CHANGED
@@ -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.68";
4832
+ var VERSION2 = "0.1.70";
4833
4833
 
4834
4834
  // src/software.ts
4835
4835
  var PINNED_BUN_VERSION = "1.3.14";
@@ -14399,6 +14399,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14399
14399
  }
14400
14400
  bindBootstrapIntent(host, config);
14401
14401
  const plan = planBootstrap(config, host.exists(STATE_PATH));
14402
+ await checked3(host, ["hostnamectl", "set-hostname", "--static", config.nodeHostname], "compute hostname");
14402
14403
  const alreadyEnrolled = host.exists("/var/lib/forgezero/enrolment.json");
14403
14404
  host.mkdir(CREDS, 448);
14404
14405
  host.mkdir("/var/lib/forgezero", 448);
@@ -15076,6 +15077,14 @@ async function runCloudflareBootstrapFinalizeCommand(configPath, dependencies =
15076
15077
  return evidence;
15077
15078
  }
15078
15079
 
15080
+ // src/operator-bootstrap.ts
15081
+ import { createHash as createHash6, randomBytes as randomBytes9 } from "crypto";
15082
+ import { chmodSync as chmodSync6, lstatSync as lstatSync7, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
15083
+ import { isIP as isIP6 } from "net";
15084
+ import { tmpdir } from "os";
15085
+ import { basename as basename2, dirname as dirname13, isAbsolute as isAbsolute5, join as join10, resolve as resolve9 } from "path";
15086
+ import { fileURLToPath as fileURLToPath2 } from "url";
15087
+
15079
15088
  // src/metal-bootstrap.ts
15080
15089
  import { createHash as createHash5, randomBytes as randomBytes8 } from "crypto";
15081
15090
  import {
@@ -15986,12 +15995,6 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15986
15995
  }
15987
15996
 
15988
15997
  // src/operator-bootstrap.ts
15989
- import { createHash as createHash6, randomBytes as randomBytes9 } from "crypto";
15990
- import { chmodSync as chmodSync6, lstatSync as lstatSync7, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
15991
- import { isIP as isIP6 } from "net";
15992
- import { tmpdir } from "os";
15993
- import { basename as basename2, dirname as dirname13, isAbsolute as isAbsolute5, join as join10, resolve as resolve9 } from "path";
15994
- import { fileURLToPath as fileURLToPath2 } from "url";
15995
15998
  var REQUEST_LIMIT = 64 * 1024;
15996
15999
  var SECRET_LIMIT = 256 * 1024;
15997
16000
  var REMOTE_STAGE = "/run/forgezero-operator-bootstrap";
@@ -16129,7 +16132,12 @@ function writeOperatorPlatformBootstrapRequest(path, request) {
16129
16132
  }
16130
16133
  function readOperatorPlatformBootstrapFleetRequest(path) {
16131
16134
  const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator fleet request")));
16132
- const root = exactKeys5(value, ["kind", "requests"], "operator fleet request");
16135
+ const root = exactKeys5(value, [
16136
+ "kind",
16137
+ "requests",
16138
+ "cloudflareConfigFile",
16139
+ "cloudflareAcceptanceFile"
16140
+ ], "operator fleet request");
16133
16141
  if (root.kind !== "platform-fleet-remote")
16134
16142
  throw new Error("operator fleet kind must be platform-fleet-remote");
16135
16143
  if (!Array.isArray(root.requests) || root.requests.length !== 3 || root.requests.some((entry) => typeof entry !== "string")) {
@@ -16140,10 +16148,25 @@ function readOperatorPlatformBootstrapFleetRequest(path) {
16140
16148
  throw new Error("operator fleet request files must be unique");
16141
16149
  for (const request of requests)
16142
16150
  readOperatorPlatformBootstrapRequest(request);
16143
- return { kind: "platform-fleet-remote", requests };
16151
+ if (typeof root.cloudflareConfigFile !== "string" || typeof root.cloudflareAcceptanceFile !== "string") {
16152
+ throw new Error("operator fleet requires Cloudflare coordinates and an acceptance evidence path");
16153
+ }
16154
+ const cloudflareConfigFile = canonicalOutputPath(root.cloudflareConfigFile, "operator fleet Cloudflare config");
16155
+ const cloudflareAcceptanceFile = canonicalOutputPath(root.cloudflareAcceptanceFile, "operator fleet Cloudflare acceptance evidence");
16156
+ return {
16157
+ kind: "platform-fleet-remote",
16158
+ requests,
16159
+ cloudflareConfigFile,
16160
+ cloudflareAcceptanceFile
16161
+ };
16144
16162
  }
16145
- function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles) {
16146
- const request = { kind: "platform-fleet-remote", requests: requestFiles };
16163
+ function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles, cloudflare) {
16164
+ const request = {
16165
+ kind: "platform-fleet-remote",
16166
+ requests: requestFiles,
16167
+ cloudflareConfigFile: canonicalOutputPath(cloudflare.configFile, "operator fleet Cloudflare config"),
16168
+ cloudflareAcceptanceFile: canonicalOutputPath(cloudflare.acceptanceFile, "operator fleet Cloudflare acceptance evidence")
16169
+ };
16147
16170
  if (new Set(requestFiles).size !== 3)
16148
16171
  throw new Error("operator fleet request files must be unique");
16149
16172
  for (const file of requestFiles)
@@ -16396,6 +16419,20 @@ function validatedPlatformFleet(fleet) {
16396
16419
  }
16397
16420
  return { requests, configs, environment };
16398
16421
  }
16422
+ function operatorPlatformFleetCoordinates(fleet) {
16423
+ const { requests, configs, environment } = validatedPlatformFleet(fleet);
16424
+ return {
16425
+ environment,
16426
+ appOrigin: configs[0].runtime.environment.appOrigin,
16427
+ apiOrigin: configs[0].runtime.environment.apiOrigin,
16428
+ nodes: configs.map((config, index) => ({
16429
+ computeReference: config.computeReference,
16430
+ address: requests[index].target.address,
16431
+ nodeHostname: config.nodeHostname,
16432
+ databaseRole: config.database.role
16433
+ }))
16434
+ };
16435
+ }
16399
16436
  function planOperatorPlatformFleetBootstrap(fleet, mode) {
16400
16437
  const { requests, configs, environment } = validatedPlatformFleet(fleet);
16401
16438
  const secretInputs = mode === "apply" ? attendedSecretNames(configs[0]) : [];
@@ -16797,6 +16834,47 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
16797
16834
  }
16798
16835
  }
16799
16836
 
16837
+ // src/attended-platform-bootstrap.ts
16838
+ var invitationFrom = (fleet, master) => {
16839
+ const output = fleet.outcomes.find(({ computeReference }) => computeReference === master)?.output;
16840
+ try {
16841
+ const parsed = JSON.parse(output ?? "");
16842
+ if (typeof parsed.launch?.inviteUrl === "string")
16843
+ return parsed.launch.inviteUrl;
16844
+ } catch {}
16845
+ throw new Error("platform bootstrap completed without the initial custodian invitation");
16846
+ };
16847
+ async function applyAttendedPlatformBootstrap(fleetRequest, cloudflareRequest, acceptancePath, secrets, dependencies = {}) {
16848
+ if (cloudflareRequest.mode !== "apply")
16849
+ throw new Error("attended platform bootstrap requires Cloudflare apply mode");
16850
+ if (!secrets.cloudflareTunnelToken || !secrets.cloudflareApiToken) {
16851
+ throw new Error("attended platform bootstrap requires both Cloudflare tokens");
16852
+ }
16853
+ const fleetCoordinates = operatorPlatformFleetCoordinates(fleetRequest);
16854
+ if (cloudflareRequest.coordinates.nodes.length !== fleetCoordinates.nodes.length || fleetCoordinates.nodes.some((node, index) => {
16855
+ const edge2 = cloudflareRequest.coordinates.nodes[index];
16856
+ return edge2?.nodeName !== node.computeReference || edge2.hostname !== node.nodeHostname || edge2.service !== "http://127.0.0.1:3000";
16857
+ })) {
16858
+ throw new Error("Cloudflare coordinates do not match the exact reviewed platform fleet");
16859
+ }
16860
+ const edge = await (dependencies.cloudflare ?? runAttendedCloudflareBootstrap)({
16861
+ ...cloudflareRequest,
16862
+ tokens: {
16863
+ tunnelToken: secrets.cloudflareTunnelToken,
16864
+ apiToken: secrets.cloudflareApiToken
16865
+ }
16866
+ });
16867
+ const fleet = await (dependencies.fleet ?? applyOperatorPlatformFleetBootstrap)(fleetRequest, "apply", { secrets });
16868
+ if (!fleet.ok)
16869
+ return { kind: "forgezero-attended-platform-bootstrap", ok: false, edge, fleet };
16870
+ const inviteUrl = invitationFrom(fleet, fleetCoordinates.nodes[0].computeReference);
16871
+ const acceptance = await (dependencies.finalize ?? finalizeCloudflareBootstrapAcceptance)({
16872
+ checkpointPath: edge.checkpointFile,
16873
+ acceptancePath
16874
+ });
16875
+ return { kind: "forgezero-attended-platform-bootstrap", ok: true, edge, fleet, acceptance, inviteUrl };
16876
+ }
16877
+
16800
16878
  // src/platform-genesis-config.ts
16801
16879
  function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname, region) {
16802
16880
  const fleet = validatePlatformGenesisGuests(guests);
@@ -18102,7 +18180,8 @@ function writePlatformGenesisFleet(directory) {
18102
18180
  const guestHostKeys = readOperatorGuestHostKeyEvidence(guestHostKeysFile, metalRequest);
18103
18181
  const fleet = metalRequest.genesis.nodes;
18104
18182
  const metalHostname = readMetalBootstrapConfig(metalRequest.metalConfigFile).metalHostname;
18105
- const template = interactiveBootstrap("platform", fleet);
18183
+ const checkpointPath = join12(output, "cloudflare-handoff.json");
18184
+ const template = interactiveBootstrap("platform", fleet, checkpointPath);
18106
18185
  const region = {
18107
18186
  label: bootstrapAnswer(`Region ${template.runtime.environment.nodeRegion} display label`),
18108
18187
  country: bootstrapAnswer("Region ISO 3166-1 alpha-2 country code").toUpperCase(),
@@ -18110,11 +18189,32 @@ function writePlatformGenesisFleet(directory) {
18110
18189
  };
18111
18190
  const nodes = fleet.map((guest, index) => index === 0 ? {
18112
18191
  nodeHostname: template.nodeHostname,
18113
- cloudflareHandoffFile: template.cloudflareHandoff.handoffFile
18192
+ cloudflareHandoffFile: cloudflareHostHandoffPath(checkpointPath, guest.name)
18114
18193
  } : {
18115
18194
  nodeHostname: bootstrapAnswer(`${guest.name} public node hostname`),
18116
- cloudflareHandoffFile: bootstrapAnswer(`${guest.name} node-specific Cloudflare host handoff`)
18195
+ cloudflareHandoffFile: cloudflareHostHandoffPath(checkpointPath, guest.name)
18117
18196
  });
18197
+ const realtimeEnabled = bootstrapBoolean("Configure existing Worker realtime fan-out?", "yes");
18198
+ const cloudflareConfig = writeBootstrapConfig(join12(output, "cloudflare.json"), createCloudflareBootstrapCommandConfig({
18199
+ checkpointPath,
18200
+ coordinates: {
18201
+ accountId: bootstrapAnswer("Cloudflare account id"),
18202
+ zoneId: bootstrapAnswer("Cloudflare zone id"),
18203
+ kvNamespaceId: bootstrapAnswer("Existing Worker-bound KV namespace id"),
18204
+ ...realtimeEnabled ? { realtime: {
18205
+ workerScriptName: bootstrapAnswer("Existing Worker script name"),
18206
+ endpoint: bootstrapAnswer("Stable public Worker HTTPS endpoint"),
18207
+ producer: bootstrapAnswer("Realtime producer identity", "platform-api")
18208
+ } } : {},
18209
+ nodes: fleet.map((guest, index) => ({
18210
+ nodeName: guest.name,
18211
+ hostname: nodes[index].nodeHostname,
18212
+ service: "http://127.0.0.1:3000",
18213
+ tunnelName: bootstrapAnswer(`${guest.name} Tunnel name`, guest.name)
18214
+ }))
18215
+ }
18216
+ }));
18217
+ const cloudflareAcceptance = join12(output, "cloudflare-acceptance.json");
18118
18218
  const configs = platformGenesisBootstrapConfigs(template, fleet, nodes, metalHostname, region).map((config) => {
18119
18219
  const path = `${output}/${config.computeReference}-platform.json`;
18120
18220
  writeBootstrapConfig(path, config);
@@ -18144,10 +18244,10 @@ function writePlatformGenesisFleet(directory) {
18144
18244
  }
18145
18245
  });
18146
18246
  });
18147
- const fleetRequest = writeOperatorPlatformBootstrapFleetRequest(join12(output, "platform-fleet-remote.json"), requests);
18148
- return { configs, requests, fleetRequest };
18247
+ const fleetRequest = writeOperatorPlatformBootstrapFleetRequest(join12(output, "platform-fleet-remote.json"), requests, { configFile: cloudflareConfig, acceptanceFile: cloudflareAcceptance });
18248
+ return { configs, requests, fleetRequest, cloudflareConfig, cloudflareAcceptance };
18149
18249
  }
18150
- function interactiveBootstrap(kind, genesisGuests) {
18250
+ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint) {
18151
18251
  if (!process.stdin.isTTY)
18152
18252
  throw new Error("non-interactive bootstrap requires --bootstrap-config <private-json-file>");
18153
18253
  const environment = bootstrapAnswer("Environment (production/development)", "production");
@@ -18187,7 +18287,7 @@ function interactiveBootstrap(kind, genesisGuests) {
18187
18287
  const backupRegion = backupEndpoint ? bootstrapAnswer("Backup S3 region") : undefined;
18188
18288
  const backupBucket = backupEndpoint ? bootstrapAnswer("Backup S3 bucket") : undefined;
18189
18289
  const backupAccessKeyId = backupEndpoint ? bootstrapAnswer("Backup S3 access-key id") : undefined;
18190
- const cloudflareHandoffFile = bootstrapAnswer(genesis ? `${computeReference} node-specific Cloudflare host handoff` : "Node-specific Cloudflare host handoff (blank for no public edge)", genesis ? undefined : "");
18290
+ const cloudflareHandoffFile = genesis ? cloudflareHostHandoffPath(genesisCloudflareCheckpoint, computeReference) : bootstrapAnswer("Node-specific Cloudflare host handoff (blank for no public edge)", "");
18191
18291
  const cloudflareNodeName = cloudflareHandoffFile ? bootstrapAnswer("Cloudflare handoff node name", computeReference) : undefined;
18192
18292
  const databaseNetworkMode = "private-lan";
18193
18293
  const enrolmentSource = genesis ? "genesis-derived" : bootstrapAnswer("Enrolment source (genesis-derived/api-token)", "genesis-derived");
@@ -18263,6 +18363,45 @@ function interactiveBootstrap(kind, genesisGuests) {
18263
18363
  } : {}
18264
18364
  };
18265
18365
  }
18366
+ async function runAttendedPlatformFleet(fleet, apply) {
18367
+ const plan = planOperatorPlatformFleetBootstrap(fleet, "apply");
18368
+ const cloudflare = readCloudflareBootstrapCommandConfig(fleet.cloudflareConfigFile, apply ? "apply" : "plan");
18369
+ const coordinates = operatorPlatformFleetCoordinates(fleet);
18370
+ if (cloudflare.coordinates.nodes.length !== coordinates.nodes.length || coordinates.nodes.some((node, index) => {
18371
+ const edge = cloudflare.coordinates.nodes[index];
18372
+ return edge?.nodeName !== node.computeReference || edge.hostname !== node.nodeHostname || edge.service !== "http://127.0.0.1:3000";
18373
+ })) {
18374
+ throw new Error("Cloudflare coordinates do not match the exact reviewed platform fleet");
18375
+ }
18376
+ const attendedPlan = {
18377
+ kind: "forgezero-attended-platform-bootstrap-plan",
18378
+ mutation: true,
18379
+ cloudflare: {
18380
+ checkpointFile: cloudflare.checkpointPath,
18381
+ nodes: cloudflare.coordinates.nodes.map(({ nodeName, hostname: hostname2 }) => ({ nodeName, hostname: hostname2 }))
18382
+ },
18383
+ fleet: plan,
18384
+ acceptanceFile: fleet.cloudflareAcceptanceFile,
18385
+ completion: ["public ingress verified", "initial custodian invitation printed"]
18386
+ };
18387
+ out.line(JSON.stringify(attendedPlan, null, 2));
18388
+ if (!apply) {
18389
+ out.step("Review the complete Tunnel, hostname, bundle, credential and three-node plan, then repeat with --apply.");
18390
+ return 0;
18391
+ }
18392
+ const first = readOperatorPlatformBootstrapRequest(fleet.requests[0]);
18393
+ const firstConfig = readBootstrapConfig(first.platformConfigFile);
18394
+ if (firstConfig.kind !== "platform")
18395
+ throw new Error("remote platform fleet requires platform configs");
18396
+ const secrets = await promptPlatformBootstrapSecrets(firstConfig);
18397
+ const result = await applyAttendedPlatformBootstrap(fleet, cloudflare, fleet.cloudflareAcceptanceFile, secrets);
18398
+ out.line(JSON.stringify(result, null, 2));
18399
+ if (!result.ok)
18400
+ return 1;
18401
+ out.line();
18402
+ out.step(`Initial custodian invitation: ${result.inviteUrl}`);
18403
+ return 0;
18404
+ }
18266
18405
  async function cmdBootstrap(options, args) {
18267
18406
  try {
18268
18407
  const operation = args[0] ?? "status";
@@ -18284,6 +18423,17 @@ async function cmdBootstrap(options, args) {
18284
18423
  out.line(JSON.stringify({ kind, ...writeMetalOperatorFiles(options.outputPath), mode: "0600" }, null, 2));
18285
18424
  return 0;
18286
18425
  }
18426
+ if (operation === "platform" && args[1] === "launch") {
18427
+ if (args[2] !== undefined || !options.outputPath || options.bootstrapConfigPath) {
18428
+ throw new Error("Usage: fz bootstrap platform launch --output <absolute-owner-only-directory> [--apply]");
18429
+ }
18430
+ const generated = writePlatformGenesisFleet(options.outputPath);
18431
+ const fleet = readOperatorPlatformBootstrapFleetRequest(generated.fleetRequest);
18432
+ if (options.apply && bootstrapAnswer("Type APPLY to run the reviewed Cloudflare and three-compute plan") !== "APPLY") {
18433
+ throw new Error("attended platform bootstrap was not confirmed");
18434
+ }
18435
+ return await runAttendedPlatformFleet(fleet, options.apply);
18436
+ }
18287
18437
  if (operation === "platform" && args[1] === "fleet") {
18288
18438
  const mode = args[2];
18289
18439
  if (!mode || !["apply", "status"].includes(mode) || args[3] !== undefined) {
@@ -18292,20 +18442,12 @@ async function cmdBootstrap(options, args) {
18292
18442
  if (!options.bootstrapConfigPath)
18293
18443
  throw new Error("remote platform fleet bootstrap requires --bootstrap-config");
18294
18444
  const fleet = readOperatorPlatformBootstrapFleetRequest(options.bootstrapConfigPath);
18295
- const plan2 = planOperatorPlatformFleetBootstrap(fleet, mode);
18296
- if (!options.apply) {
18297
- out.line(JSON.stringify(plan2, null, 2));
18298
- out.step("Review all three pinned targets and shared secret names, then repeat with --apply.");
18299
- return 0;
18445
+ if (mode === "status") {
18446
+ const result2 = await applyOperatorPlatformFleetBootstrap(fleet, mode);
18447
+ out.line(JSON.stringify(result2, null, 2));
18448
+ return result2.ok ? 0 : 1;
18300
18449
  }
18301
- const first = readOperatorPlatformBootstrapRequest(fleet.requests[0]);
18302
- const firstConfig = readBootstrapConfig(first.platformConfigFile);
18303
- if (firstConfig.kind !== "platform")
18304
- throw new Error("remote platform fleet requires platform configs");
18305
- const secrets2 = mode === "apply" ? await promptPlatformBootstrapSecrets(firstConfig) : undefined;
18306
- const result2 = await applyOperatorPlatformFleetBootstrap(fleet, mode, { secrets: secrets2 });
18307
- out.line(JSON.stringify(result2, null, 2));
18308
- return result2.ok ? 0 : 1;
18450
+ return await runAttendedPlatformFleet(fleet, options.apply);
18309
18451
  }
18310
18452
  if (operation === "platform" && args[1] === "remote") {
18311
18453
  const mode = args[2];
@@ -18453,7 +18595,7 @@ async function cmdBootstrap(options, args) {
18453
18595
  return 0;
18454
18596
  }
18455
18597
  if (!["platform", "repair"].includes(operation)) {
18456
- 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|host-keys|rehearsal-host-keys|status>]|status|repair [--bootstrap-config <path>] [--apply]");
18598
+ throw new Error("Usage: fz bootstrap config <metal|platform|cloudflare>|platform [launch|bundle|fleet <apply|status>|remote <apply|status>|cloudflare [verify|finalize]]|metal [remote <apply|genesis|rehearsal|rehearsal-cleanup|host-keys|rehearsal-host-keys|status>]|status|repair [--bootstrap-config <path>] [--apply]");
18457
18599
  }
18458
18600
  const config = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : operation === "repair" ? (() => {
18459
18601
  throw new Error("repair requires --bootstrap-config so immutable coordinates are revalidated");
@@ -19028,18 +19170,23 @@ function usage() {
19028
19170
  local socket instead of holding an API key
19029
19171
  fz bootstrap platform bundle
19030
19172
  Build one clean exact-revision API Git bundle for genesis
19173
+ fz bootstrap platform launch
19174
+ Prompt, review and run the complete attended platform
19175
+ bootstrap; --output is the owner-only working directory
19031
19176
  fz bootstrap platform remote <apply|status>
19032
19177
  Run typed bootstrap from the operator laptop through
19033
19178
  a pinned host; apply copies and verifies that bundle
19034
19179
  fz bootstrap platform fleet <apply|status>
19035
- Validate the exact three-node genesis identity, prompt
19036
- shared secrets once and run all pinned nodes concurrently
19180
+ One attended genesis operation: reconcile public
19181
+ hostnames/Tunnels, seal credentials, deploy the bundle,
19182
+ run all three nodes, verify ingress and print the invite
19037
19183
  fz bootstrap platform Install/repair a typed elastic platform compute
19038
19184
  fz bootstrap config <metal|platform|cloudflare>
19039
19185
  Write validated owner-only metal config+remote request,
19040
- Cloudflare file, or complete three-node platform request set
19186
+ low-level Cloudflare file, or the complete three-node
19187
+ platform + Cloudflare request set
19041
19188
  fz bootstrap platform cloudflare
19042
- Plan/apply attended Tunnel and DNS reconciliation
19189
+ Low-level resume/diagnostic Tunnel and DNS reconciliation
19043
19190
  using hidden management and KV/Worker runtime token prompts
19044
19191
  fz bootstrap platform cloudflare verify
19045
19192
  From the operator laptop, prove every ordinary public
@@ -354,7 +354,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
354
354
  }
355
355
 
356
356
  // src/version.ts
357
- var VERSION = "0.1.68";
357
+ var VERSION = "0.1.70";
358
358
 
359
359
  // src/otel-collector.ts
360
360
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
@@ -40,6 +40,10 @@ export interface OperatorGuestHostKeyEvidence {
40
40
  export interface OperatorPlatformBootstrapFleetRequest {
41
41
  kind: 'platform-fleet-remote';
42
42
  requests: readonly [string, string, string];
43
+ /** Reviewed non-secret coordinates for the attended Tunnel/DNS/KV/DO phase. */
44
+ cloudflareConfigFile: string;
45
+ /** Secret-free evidence retained after checkpoint and host handoffs are consumed. */
46
+ cloudflareAcceptanceFile: string;
43
47
  }
44
48
  export interface OperatorPlatformBootstrapFleetPlan {
45
49
  kind: 'platform-fleet-remote';
@@ -123,7 +127,10 @@ export declare function createOperatorSshHop(input: Omit<OperatorSshHop, 'hostKe
123
127
  export declare function readOperatorPlatformBootstrapRequest(path: string): OperatorPlatformBootstrapRequest;
124
128
  export declare function writeOperatorPlatformBootstrapRequest(path: string, request: OperatorPlatformBootstrapRequest): string;
125
129
  export declare function readOperatorPlatformBootstrapFleetRequest(path: string): OperatorPlatformBootstrapFleetRequest;
126
- export declare function writeOperatorPlatformBootstrapFleetRequest(path: string, requestFiles: readonly [string, string, string]): string;
130
+ export declare function writeOperatorPlatformBootstrapFleetRequest(path: string, requestFiles: readonly [string, string, string], cloudflare: {
131
+ configFile: string;
132
+ acceptanceFile: string;
133
+ }): string;
127
134
  export declare function readOperatorMetalBootstrapRequest(path: string): OperatorMetalBootstrapRequest;
128
135
  export declare function writeOperatorMetalBootstrapRequest(path: string, request: OperatorMetalBootstrapRequest): string;
129
136
  export declare function readOperatorGuestHostKeyEvidence(path: string, request?: OperatorMetalBootstrapRequest): OperatorGuestHostKeyEvidence;
@@ -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.68";
1399
+ var VERSION = "0.1.70";
1400
1400
 
1401
1401
  // src/software.ts
1402
1402
  var PINNED_BUN_VERSION = "1.3.14";
@@ -4380,6 +4380,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4380
4380
  }
4381
4381
  bindBootstrapIntent(host, config);
4382
4382
  const plan = planBootstrap(config, host.exists(STATE_PATH));
4383
+ await checked3(host, ["hostnamectl", "set-hostname", "--static", config.nodeHostname], "compute hostname");
4383
4384
  const alreadyEnrolled = host.exists("/var/lib/forgezero/enrolment.json");
4384
4385
  host.mkdir(CREDS, 448);
4385
4386
  host.mkdir("/var/lib/forgezero", 448);
@@ -5971,7 +5972,12 @@ function writeOperatorPlatformBootstrapRequest(path, request) {
5971
5972
  }
5972
5973
  function readOperatorPlatformBootstrapFleetRequest(path) {
5973
5974
  const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator fleet request")));
5974
- const root = exactKeys3(value, ["kind", "requests"], "operator fleet request");
5975
+ const root = exactKeys3(value, [
5976
+ "kind",
5977
+ "requests",
5978
+ "cloudflareConfigFile",
5979
+ "cloudflareAcceptanceFile"
5980
+ ], "operator fleet request");
5975
5981
  if (root.kind !== "platform-fleet-remote")
5976
5982
  throw new Error("operator fleet kind must be platform-fleet-remote");
5977
5983
  if (!Array.isArray(root.requests) || root.requests.length !== 3 || root.requests.some((entry) => typeof entry !== "string")) {
@@ -5982,10 +5988,25 @@ function readOperatorPlatformBootstrapFleetRequest(path) {
5982
5988
  throw new Error("operator fleet request files must be unique");
5983
5989
  for (const request of requests)
5984
5990
  readOperatorPlatformBootstrapRequest(request);
5985
- return { kind: "platform-fleet-remote", requests };
5991
+ if (typeof root.cloudflareConfigFile !== "string" || typeof root.cloudflareAcceptanceFile !== "string") {
5992
+ throw new Error("operator fleet requires Cloudflare coordinates and an acceptance evidence path");
5993
+ }
5994
+ const cloudflareConfigFile = canonicalOutputPath(root.cloudflareConfigFile, "operator fleet Cloudflare config");
5995
+ const cloudflareAcceptanceFile = canonicalOutputPath(root.cloudflareAcceptanceFile, "operator fleet Cloudflare acceptance evidence");
5996
+ return {
5997
+ kind: "platform-fleet-remote",
5998
+ requests,
5999
+ cloudflareConfigFile,
6000
+ cloudflareAcceptanceFile
6001
+ };
5986
6002
  }
5987
- function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles) {
5988
- const request = { kind: "platform-fleet-remote", requests: requestFiles };
6003
+ function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles, cloudflare) {
6004
+ const request = {
6005
+ kind: "platform-fleet-remote",
6006
+ requests: requestFiles,
6007
+ cloudflareConfigFile: canonicalOutputPath(cloudflare.configFile, "operator fleet Cloudflare config"),
6008
+ cloudflareAcceptanceFile: canonicalOutputPath(cloudflare.acceptanceFile, "operator fleet Cloudflare acceptance evidence")
6009
+ };
5989
6010
  if (new Set(requestFiles).size !== 3)
5990
6011
  throw new Error("operator fleet request files must be unique");
5991
6012
  for (const file of requestFiles)
@@ -2125,7 +2125,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2125
2125
  }
2126
2126
 
2127
2127
  // src/version.ts
2128
- var VERSION3 = "0.1.68";
2128
+ var VERSION3 = "0.1.70";
2129
2129
 
2130
2130
  // src/egress-policy.ts
2131
2131
  import { realpathSync as realpathSync3 } from "node:fs";
package/dist/provision.js CHANGED
@@ -2125,7 +2125,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2125
2125
  }
2126
2126
 
2127
2127
  // src/version.ts
2128
- var VERSION3 = "0.1.68";
2128
+ var VERSION3 = "0.1.70";
2129
2129
 
2130
2130
  // src/egress-policy.ts
2131
2131
  import { realpathSync as realpathSync3 } from "node:fs";
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** One package version shared by both public binaries. Pinned to package.json by tests. */
2
- export declare const VERSION = "0.1.68";
2
+ export declare const VERSION = "0.1.70";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",