@forgezero/agent 0.1.62 → 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 existsSync11, lstatSync as lstatSync9, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
4815
- import { basename as basename3, dirname as dirname12, isAbsolute as isAbsolute5, resolve as resolve9 } 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.62";
4832
+ var VERSION2 = "0.1.64";
4833
4833
 
4834
4834
  // src/software.ts
4835
4835
  var PINNED_BUN_VERSION = "1.3.14";
@@ -4845,7 +4845,8 @@ var SOFTWARE_CATALOG = [
4845
4845
  { id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4846
4846
  { id: "cloudflare-warp", version: "2026.6.822.0-min", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4847
4847
  { id: "ufw", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4848
- { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
4848
+ { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4849
+ { id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
4849
4850
  ];
4850
4851
  var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
4851
4852
  "data-root": "/var/lib/docker",
@@ -4866,7 +4867,7 @@ function validateSoftwareRequirements(value, _options = {}) {
4866
4867
  if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
4867
4868
  throw new Error("software requirement contains an unknown field");
4868
4869
  }
4869
- if (!["bun", "docker", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
4870
+ if (!["bun", "docker", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client", "git"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
4870
4871
  throw new Error("software requirement coordinate is invalid");
4871
4872
  }
4872
4873
  const requirement = { id: row.id, version: row.version };
@@ -5427,7 +5428,11 @@ function agentEgressUnit(options) {
5427
5428
  const user = options.user ?? "forgezero";
5428
5429
  if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(user))
5429
5430
  throw new Error("invalid Agent service user");
5430
- const deploymentEnabled = Boolean(options.repository || options.pullDeployments);
5431
+ const bootstrapBundleEnabled = Boolean(options.bootstrapBundlePath && options.bootstrapBundleManifestPath);
5432
+ if (Boolean(options.bootstrapBundlePath) !== Boolean(options.bootstrapBundleManifestPath) || bootstrapBundleEnabled && ![options.bootstrapBundlePath, options.bootstrapBundleManifestPath].every((path) => path.startsWith("/") && !/[\r\n\0:]/.test(path))) {
5433
+ throw new Error("bootstrap bundle and manifest must be supplied together as absolute paths");
5434
+ }
5435
+ const deploymentEnabled = Boolean(options.repository || bootstrapBundleEnabled || options.pullDeployments);
5431
5436
  const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
5432
5437
  const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
5433
5438
  if (deploymentEnabled && runnerPublicTcpPorts.length < 1) {
@@ -5833,17 +5838,16 @@ function agentUnit(options) {
5833
5838
  if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
5834
5839
  throw new Error("migration pull and lifecycle profile must be supplied together");
5835
5840
  }
5836
- const bootstrapEnabled = Boolean(options.pullBootstrap && options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath && options.bootstrapTargetTelemetryEndpoint);
5837
- if ([
5838
- options.pullBootstrap,
5839
- options.bootstrapSshCredentialPath,
5840
- options.bootstrapSshPublicKeyPath,
5841
- options.bootstrapTargetTelemetryEndpoint
5842
- ].some(Boolean) && !bootstrapEnabled) {
5843
- 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");
5844
5848
  }
5845
5849
  const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
5846
- const bootstrapSshCredentialPath = bootstrapEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
5850
+ const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
5847
5851
  const warpValues = [
5848
5852
  options.warpOrganization,
5849
5853
  options.warpClientIdCredentialPath,
@@ -5897,6 +5901,8 @@ function agentUnit(options) {
5897
5901
  options.gitPublicKeyPath ? `FZ_GIT_PUBLIC_KEY_FILE=${options.gitPublicKeyPath}` : null,
5898
5902
  options.repository ? `FZ_DEPLOY_REPO=${options.repository}` : null,
5899
5903
  options.branch ? `FZ_DEPLOY_BRANCH=${options.branch}` : null,
5904
+ options.bootstrapBundlePath ? `FZ_BOOTSTRAP_BUNDLE=${options.bootstrapBundlePath}` : null,
5905
+ options.bootstrapBundleManifestPath ? `FZ_BOOTSTRAP_BUNDLE_MANIFEST=${options.bootstrapBundleManifestPath}` : null,
5900
5906
  options.profile ? `FZ_DEPLOY_PROFILE=${options.profile}` : null,
5901
5907
  options.repository && options.branch ? `FZ_DEPLOY_KEY=${options.project ?? "platform"}:${options.environment ?? "production"}` : null,
5902
5908
  deploymentEnabled ? `FZ_DEPLOY_ROOT=${deployRoot}` : null,
@@ -6080,15 +6086,13 @@ function planProvision(options) {
6080
6086
  if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
6081
6087
  throw new Error("migration pull and lifecycle profile must be supplied together");
6082
6088
  }
6083
- const bootstrapEnabled = Boolean(options.pullBootstrap && options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath && options.bootstrapTargetTelemetryEndpoint);
6084
- if ([
6085
- options.pullBootstrap,
6086
- options.bootstrapSshCredentialPath,
6087
- options.bootstrapSshPublicKeyPath,
6088
- options.bootstrapSshSourcePath,
6089
- options.bootstrapTargetTelemetryEndpoint
6090
- ].some(Boolean) && !bootstrapEnabled) {
6091
- 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");
6092
6096
  }
6093
6097
  const warpValues = [
6094
6098
  options.warpOrganization,
@@ -6098,13 +6102,13 @@ function planProvision(options) {
6098
6102
  const warpEnabled = warpValues.every(Boolean);
6099
6103
  if (warpValues.some(Boolean) && !warpEnabled)
6100
6104
  throw new Error("WARP configuration must be supplied together");
6101
- const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath && options.enrolStatePath);
6102
- if (Boolean(options.enrolTokenCredentialPath) !== Boolean(options.enrolStatePath) || options.enrolTokenSourcePath && !enrolmentEnabled) {
6103
- 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");
6104
6108
  }
6105
6109
  const enrolTokenSourcePath = options.enrolTokenSourcePath ? systemdPath(options.enrolTokenSourcePath, "enrolment source") : undefined;
6106
6110
  const enrolTokenCredentialPath = enrolmentEnabled ? systemdPath(options.enrolTokenCredentialPath, "enrolment credential") : undefined;
6107
- const enrolStatePath = enrolmentEnabled ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
6111
+ const enrolStatePath = options.enrolStatePath ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
6108
6112
  const enrolStateDir = enrolStatePath?.replace(/\/[^/]+$/, "");
6109
6113
  const sourceBinPath = options.sourceBinPath ? systemdPath(options.sourceBinPath, "agent source binary") : undefined;
6110
6114
  const binPath = options.binPath ? systemdPath(options.binPath, "agent binary") : undefined;
@@ -6116,8 +6120,8 @@ function planProvision(options) {
6116
6120
  const gitPublicKeyDir = gitPublicKeyPath?.replace(/\/[^/]+$/, "");
6117
6121
  const lifecycleProfilePath = lifecycleEnabled ? systemdPath(options.lifecycleProfilePath, "lifecycle profile") : undefined;
6118
6122
  const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
6119
- const bootstrapSshCredentialPath = bootstrapEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
6120
- 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;
6121
6125
  const bootstrapSshSourcePath = options.bootstrapSshSourcePath ? systemdPath(options.bootstrapSshSourcePath, "bootstrap SSH private-key source") : undefined;
6122
6126
  const bootstrapSshPublicKeyDir = bootstrapSshPublicKeyPath?.replace(/\/[^/]+$/, "");
6123
6127
  const warpClientIdCredentialPath = warpEnabled ? systemdPath(options.warpClientIdCredentialPath, "WARP client-id credential") : undefined;
@@ -6217,7 +6221,7 @@ function planProvision(options) {
6217
6221
  step("Git deploy identity directory", { kind: "directories", directories: [{ path: gitPublicKeyDir, mode: 493, owner: "root", group: "root" }] }),
6218
6222
  step("unique encrypted Git deploy identity", { kind: "ensure-git-identity", credential: gitCredentialPath, publicKey: gitPublicKeyPath })
6219
6223
  ] : [],
6220
- ...bootstrapEnabled ? [
6224
+ ...bootstrapIdentityEnabled ? [
6221
6225
  step("bootstrap SSH public identity directory", { kind: "directories", directories: [
6222
6226
  { path: bootstrapSshPublicKeyDir, mode: 493, owner: "root", group: "root" }
6223
6227
  ] }),
@@ -6253,6 +6257,10 @@ function planProvision(options) {
6253
6257
  { argv: ["/usr/bin/rm", "-f", "/etc/systemd/system/forgezero-deploy-runner.socket"] },
6254
6258
  { argv: ["/usr/bin/systemctl", "daemon-reload"] }
6255
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
+ ] })] : [],
6256
6264
  step("enable and converge services", { kind: "commands", commands: [
6257
6265
  { argv: ["/usr/bin/systemctl", "enable", ...enabledUnits] },
6258
6266
  { argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
@@ -11555,18 +11563,18 @@ function removeSession(api, realm, path = defaultSessionPath()) {
11555
11563
  }
11556
11564
 
11557
11565
  // src/bootstrap.ts
11558
- import { createHash as createHash3, createHmac as createHmac2, randomBytes as randomBytes6 } from "crypto";
11566
+ import { createHash as createHash4, createHmac as createHmac2, randomBytes as randomBytes7 } from "crypto";
11559
11567
  import {
11560
- chmodSync as chmodSync3,
11561
- existsSync as existsSync8,
11562
- lstatSync as lstatSync4,
11563
- mkdirSync as mkdirSync8,
11564
- readFileSync as readFileSync8,
11565
- renameSync as renameSync7,
11566
- rmSync as rmSync5,
11567
- writeFileSync as writeFileSync8
11568
+ chmodSync as chmodSync4,
11569
+ existsSync as existsSync9,
11570
+ lstatSync as lstatSync5,
11571
+ mkdirSync as mkdirSync9,
11572
+ readFileSync as readFileSync9,
11573
+ renameSync as renameSync8,
11574
+ rmSync as rmSync6,
11575
+ writeFileSync as writeFileSync9
11568
11576
  } from "fs";
11569
- import { dirname as dirname8 } from "path";
11577
+ import { dirname as dirname9 } from "path";
11570
11578
  import { fileURLToPath } from "url";
11571
11579
 
11572
11580
  // src/platform-bootstrap-runtime.ts
@@ -11654,8 +11662,6 @@ function validatePlatformSharedEnvironment(input) {
11654
11662
  databaseUser: input.databaseUser,
11655
11663
  sharedDirectory: input.sharedDirectory,
11656
11664
  seedSyncEpoch: input.seedSyncEpoch,
11657
- repository: input.repository,
11658
- branch: input.branch,
11659
11665
  deployProfile: input.deployProfile
11660
11666
  }))
11661
11667
  safeAtom(name, value);
@@ -11738,8 +11744,6 @@ function renderPlatformSharedEnvironment(input) {
11738
11744
  FZ_AGENT_OTLP_ENDPOINT: value.agentOtlpEndpoint,
11739
11745
  FZ_CUSTODIAN_EMAIL: value.custodianEmail ?? "",
11740
11746
  FZ_PROFILE: value.deployProfile,
11741
- FZ_REPO: value.repository,
11742
- FZ_BRANCH: value.branch,
11743
11747
  FZ_EMAIL_PROVIDER: value.email?.provider ?? "",
11744
11748
  FZ_SMTP_HOST: value.email?.provider === "smtp" ? value.email.host : "",
11745
11749
  FZ_SMTP_PORT: value.email?.provider === "smtp" ? String(value.email.port) : "",
@@ -13158,6 +13162,164 @@ async function ensureForgeZeroOtelCollector(host, exportEndpoint) {
13158
13162
 
13159
13163
  // src/bootstrap.ts
13160
13164
  init_dist();
13165
+
13166
+ // src/bootstrap-bundle.ts
13167
+ import { createHash as createHash3, randomBytes as randomBytes6 } from "crypto";
13168
+ import {
13169
+ chmodSync as chmodSync3,
13170
+ createReadStream,
13171
+ existsSync as existsSync8,
13172
+ lstatSync as lstatSync4,
13173
+ mkdirSync as mkdirSync8,
13174
+ readFileSync as readFileSync8,
13175
+ renameSync as renameSync7,
13176
+ rmSync as rmSync5,
13177
+ writeFileSync as writeFileSync8
13178
+ } from "fs";
13179
+ import { dirname as dirname8, isAbsolute as isAbsolute2, resolve as resolve6 } from "path";
13180
+ var BOOTSTRAP_BUNDLE_FORMAT = 1;
13181
+ var BOOTSTRAP_BUNDLE_KIND = "forgezero-api-git-bundle";
13182
+ var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
13183
+ var run = async (argv2) => {
13184
+ const child = Bun.spawn([...argv2], { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
13185
+ const [stdout, stderr, exitCode] = await Promise.all([
13186
+ new Response(child.stdout).text(),
13187
+ new Response(child.stderr).text(),
13188
+ child.exited
13189
+ ]);
13190
+ return { exitCode, output: `${stdout}${stderr}`.slice(0, 65536) };
13191
+ };
13192
+ var checked2 = async (exec, argv2, label) => {
13193
+ const result = await exec(argv2);
13194
+ if (result.exitCode !== 0)
13195
+ throw new Error(`${label} failed${result.output.trim() ? `: ${result.output.trim()}` : ""}`);
13196
+ return result.output.trim();
13197
+ };
13198
+ async function sha256File(path) {
13199
+ const hash = createHash3("sha256");
13200
+ await new Promise((resolveDone, reject) => {
13201
+ const stream = createReadStream(path);
13202
+ stream.on("data", (chunk) => hash.update(chunk));
13203
+ stream.on("error", reject);
13204
+ stream.on("end", resolveDone);
13205
+ });
13206
+ return hash.digest("hex");
13207
+ }
13208
+ var branchName = (value) => {
13209
+ if (typeof value !== "string" || !/^[A-Za-z0-9](?:[A-Za-z0-9._/-]{0,126}[A-Za-z0-9])?$/.test(value) || value.includes("..") || value.includes("//") || value.startsWith("-")) {
13210
+ throw new Error("bootstrap bundle branch is malformed");
13211
+ }
13212
+ return value;
13213
+ };
13214
+ function parseBootstrapBundleManifest(value) {
13215
+ if (!value || typeof value !== "object" || Array.isArray(value))
13216
+ throw new Error("bootstrap bundle manifest must be an object");
13217
+ const source = value;
13218
+ const allowed = ["format", "kind", "branch", "revision", "sha256", "bytes", "createdAt"];
13219
+ const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
13220
+ if (unknown.length)
13221
+ throw new Error(`bootstrap bundle manifest contains unknown field ${unknown[0]}`);
13222
+ if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
13223
+ throw new Error("bootstrap bundle manifest format is unsupported");
13224
+ }
13225
+ branchName(source.branch);
13226
+ if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
13227
+ throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
13228
+ }
13229
+ if (typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256)) {
13230
+ throw new Error("bootstrap bundle digest is malformed");
13231
+ }
13232
+ if (!Number.isSafeInteger(source.bytes) || Number(source.bytes) < 1 || Number(source.bytes) > MAX_BOOTSTRAP_BUNDLE_BYTES) {
13233
+ throw new Error("bootstrap bundle size is outside the supported boundary");
13234
+ }
13235
+ if (typeof source.createdAt !== "string" || Number.isNaN(Date.parse(source.createdAt))) {
13236
+ throw new Error("bootstrap bundle creation time is malformed");
13237
+ }
13238
+ return source;
13239
+ }
13240
+ function ownerRegularFile(path, maximum, label) {
13241
+ if (!isAbsolute2(path) || resolve6(path) !== path || /[\r\n\0]/.test(path)) {
13242
+ throw new Error(`${label} path must be canonical and absolute`);
13243
+ }
13244
+ const value = lstatSync4(path);
13245
+ const uid = process.getuid?.() ?? value.uid;
13246
+ if (!value.isFile() || value.isSymbolicLink() || value.uid !== uid || value.nlink !== 1 || (value.mode & 63) !== 0 || value.size < 1 || value.size > maximum) {
13247
+ throw new Error(`${label} must be an owner-only regular file with one link and at most ${maximum} bytes`);
13248
+ }
13249
+ }
13250
+ async function readBootstrapBundle(bundlePath, manifestPath = `${bundlePath}.json`) {
13251
+ ownerRegularFile(bundlePath, MAX_BOOTSTRAP_BUNDLE_BYTES, "bootstrap bundle");
13252
+ ownerRegularFile(manifestPath, 16 * 1024, "bootstrap bundle manifest");
13253
+ let parsed;
13254
+ try {
13255
+ parsed = JSON.parse(readFileSync8(manifestPath, "utf8"));
13256
+ } catch {
13257
+ throw new Error("bootstrap bundle manifest is not valid JSON");
13258
+ }
13259
+ const manifest = parseBootstrapBundleManifest(parsed);
13260
+ const size = lstatSync4(bundlePath).size;
13261
+ if (size !== manifest.bytes || await sha256File(bundlePath) !== manifest.sha256) {
13262
+ throw new Error("bootstrap bundle bytes do not match their manifest");
13263
+ }
13264
+ return { bundlePath, manifestPath, manifest };
13265
+ }
13266
+ async function buildBootstrapBundle(input, exec = run) {
13267
+ const repositoryRoot = resolve6(input.repositoryRoot);
13268
+ const outputPath = resolve6(input.outputPath);
13269
+ const manifestPath = `${outputPath}.json`;
13270
+ const branch = branchName(input.branch);
13271
+ if (!isAbsolute2(input.outputPath) || outputPath !== input.outputPath) {
13272
+ throw new Error("bootstrap bundle output must be a canonical absolute path");
13273
+ }
13274
+ if (existsSync8(outputPath) || existsSync8(manifestPath)) {
13275
+ throw new Error("bootstrap bundle output already exists");
13276
+ }
13277
+ const root = lstatSync4(repositoryRoot);
13278
+ if (!root.isDirectory() || root.isSymbolicLink())
13279
+ throw new Error("bootstrap bundle source must be a real directory");
13280
+ mkdirSync8(dirname8(outputPath), { recursive: true, mode: 448 });
13281
+ const parent = lstatSync4(dirname8(outputPath));
13282
+ const uid = process.getuid?.() ?? parent.uid;
13283
+ if (!parent.isDirectory() || parent.isSymbolicLink() || parent.uid !== uid || (parent.mode & 63) !== 0) {
13284
+ throw new Error("bootstrap bundle output directory must be caller-owned and owner-only");
13285
+ }
13286
+ const dirty = await checked2(exec, ["git", "-C", repositoryRoot, "status", "--porcelain=v1", "--untracked-files=no"], "Git worktree check");
13287
+ if (dirty)
13288
+ throw new Error("bootstrap bundle source has tracked changes; commit the reviewed API release first");
13289
+ const revision = (await checked2(exec, ["git", "-C", repositoryRoot, "rev-parse", "--verify", `refs/heads/${branch}^{commit}`], "Git branch resolution")).toLowerCase();
13290
+ if (!/^[a-f0-9]{40}$/.test(revision))
13291
+ throw new Error("bootstrap bundle branch did not resolve to one exact commit");
13292
+ const temporary = `${outputPath}.next.${process.pid}.${randomBytes6(6).toString("hex")}`;
13293
+ try {
13294
+ await checked2(exec, ["git", "-C", repositoryRoot, "bundle", "create", temporary, `refs/heads/${branch}`], "Git bundle creation");
13295
+ chmodSync3(temporary, 384);
13296
+ const file = lstatSync4(temporary);
13297
+ if (!file.isFile() || file.isSymbolicLink() || file.size < 1 || file.size > MAX_BOOTSTRAP_BUNDLE_BYTES) {
13298
+ throw new Error("created bootstrap bundle is outside the supported boundary");
13299
+ }
13300
+ await checked2(exec, ["git", "bundle", "verify", temporary], "Git bundle verification");
13301
+ const manifest = {
13302
+ format: BOOTSTRAP_BUNDLE_FORMAT,
13303
+ kind: BOOTSTRAP_BUNDLE_KIND,
13304
+ branch,
13305
+ revision,
13306
+ sha256: await sha256File(temporary),
13307
+ bytes: file.size,
13308
+ createdAt: new Date().toISOString()
13309
+ };
13310
+ const manifestTemporary = `${manifestPath}.next.${process.pid}.${randomBytes6(6).toString("hex")}`;
13311
+ writeFileSync8(manifestTemporary, `${JSON.stringify(manifest, null, 2)}
13312
+ `, { mode: 384, flag: "wx" });
13313
+ renameSync7(temporary, outputPath);
13314
+ renameSync7(manifestTemporary, manifestPath);
13315
+ return { bundlePath: outputPath, manifestPath, manifest };
13316
+ } catch (cause) {
13317
+ rmSync5(temporary, { force: true });
13318
+ throw cause;
13319
+ }
13320
+ }
13321
+
13322
+ // src/bootstrap.ts
13161
13323
  var PLATFORM_BOOTSTRAP_PROFILES = [
13162
13324
  "platform-db-api",
13163
13325
  "platform-api"
@@ -13257,7 +13419,7 @@ var SEED_CREDENTIAL = `${CREDS}/seed-sync-root.cred`;
13257
13419
  var BACKUP_RECOVERY_CREDENTIAL = `${CREDS}/backup-recovery-root.cred`;
13258
13420
  var BOOTSTRAP_SSH_CREDENTIAL = `${CREDS}/bootstrap-ssh-key.cred`;
13259
13421
  var BOOTSTRAP_SSH_PUBLIC_KEY = "/etc/forgezero/bootstrap/runner.pub";
13260
- var GIT_PUBLIC_KEY = "/etc/forgezero/git/deploy.pub";
13422
+ var BOOTSTRAP_RELEASE_EVIDENCE = "/var/lib/forgezero/bootstrap-release.json";
13261
13423
  var DB_MODE_EVIDENCE = "/var/lib/forgezero-cluster/server-mode.json";
13262
13424
  var LIFECYCLE_PROFILE = "/etc/forgezero/lifecycle.json";
13263
13425
  var CONTROL_SOCKET = "/run/forgezero/control.sock";
@@ -13330,6 +13492,9 @@ function validateBootstrapConfig(value) {
13330
13492
  throw new Error("Cloudflare Mesh/WARP requires a node-specific Cloudflare handoff");
13331
13493
  }
13332
13494
  if (value.kind === "enrolled-compute") {
13495
+ if (value.gitDeployKey !== undefined && typeof value.gitDeployKey !== "boolean") {
13496
+ throw new Error("gitDeployKey must be boolean");
13497
+ }
13333
13498
  if (!/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(value.realm))
13334
13499
  throw new Error("tenant realm is malformed");
13335
13500
  if (!/^https:\/\//.test(value.apiUrl) && !/^http:\/\/(?:127\.0\.0\.1|localhost)(?::\d+)?$/.test(value.apiUrl)) {
@@ -13355,6 +13520,9 @@ function validateBootstrapConfig(value) {
13355
13520
  throw new Error("unsupported platform software profile");
13356
13521
  if (!["production", "development"].includes(value.environment))
13357
13522
  throw new Error("platform environment must be production or development");
13523
+ if (!value.bootstrapBundle || ![value.bootstrapBundle.bundleFile, value.bootstrapBundle.manifestFile].every((path) => typeof path === "string" && path.startsWith("/") && !/[\r\n\0:]/.test(path))) {
13524
+ throw new Error("platform bootstrap requires absolute bundle and manifest paths");
13525
+ }
13358
13526
  let api;
13359
13527
  try {
13360
13528
  api = new URL(value.apiUrl);
@@ -13387,7 +13555,7 @@ function validateBootstrapConfig(value) {
13387
13555
  if (runtime.otlpCollectorUnit !== FORGEZERO_OTEL_COLLECTOR_UNIT) {
13388
13556
  throw new Error(`platform bootstrap requires ${FORGEZERO_OTEL_COLLECTOR_UNIT}`);
13389
13557
  }
13390
- if (runtime.softwareProfile !== value.profile || runtime.databaseRole !== value.database.role || runtime.nodeHostname !== value.nodeHostname || runtime.apiOrigin !== api.origin || runtime.repository !== value.repository || runtime.branch !== value.branch || runtime.databaseCoordinators.join(",") !== write.join(",")) {
13558
+ if (runtime.softwareProfile !== value.profile || runtime.databaseRole !== value.database.role || runtime.nodeHostname !== value.nodeHostname || runtime.apiOrigin !== api.origin || runtime.databaseCoordinators.join(",") !== write.join(",")) {
13391
13559
  throw new Error("platform runtime coordinates disagree with immutable bootstrap coordinates");
13392
13560
  }
13393
13561
  if (runtime.deployProfile !== value.environment)
@@ -13417,6 +13585,7 @@ function planBootstrap(input, initialized = false) {
13417
13585
  ] : [
13418
13586
  ...config.firewall.enabled ? [{ id: "ufw", version: "ubuntu-26.04" }] : [],
13419
13587
  { id: "bun", version: "1.3.14" },
13588
+ { id: "git", version: "ubuntu-26.04" },
13420
13589
  { id: "nginx", version: "ubuntu-26.04" },
13421
13590
  ...platformBootstrapRunner(config) ? [{ id: "openssh-client", version: "ubuntu-26.04" }] : [],
13422
13591
  ...config.profile === "platform-api" ? [] : [{ id: "arangodb", version: "3.11.14" }],
@@ -13445,7 +13614,7 @@ function planBootstrap(input, initialized = false) {
13445
13614
  ]
13446
13615
  };
13447
13616
  }
13448
- var checked2 = async (host, argv2, label, options) => {
13617
+ var checked3 = async (host, argv2, label, options) => {
13449
13618
  const result = await host.exec(argv2, options);
13450
13619
  if (result.exitCode !== 0)
13451
13620
  throw new Error(`${label} failed: ${result.output.trim()}`);
@@ -13626,6 +13795,37 @@ async function seal(host, name, destination2, value) {
13626
13795
  if (result.exitCode !== 0)
13627
13796
  throw new Error(`could not seal ${name}: ${result.output.trim()}`);
13628
13797
  }
13798
+ async function verifyBootstrapBundleOnHost(host, config, verifyGit = false) {
13799
+ const manifestMetadata = host.inspect?.(config.bootstrapBundle.manifestFile);
13800
+ const bundleMetadata = host.inspect?.(config.bootstrapBundle.bundleFile);
13801
+ if (manifestMetadata && (!manifestMetadata.regular || manifestMetadata.symbolic || manifestMetadata.uid !== 0 || manifestMetadata.links !== 1 || (manifestMetadata.mode & 63) !== 0 || manifestMetadata.size > 16 * 1024)) {
13802
+ throw new Error("bootstrap bundle manifest must be a root-owned owner-only regular file");
13803
+ }
13804
+ if (bundleMetadata && (!bundleMetadata.regular || bundleMetadata.symbolic || bundleMetadata.uid !== 0 || bundleMetadata.links !== 1 || (bundleMetadata.mode & 63) !== 0 || bundleMetadata.size < 1 || bundleMetadata.size > 512 * 1024 * 1024)) {
13805
+ throw new Error("bootstrap bundle must be a root-owned owner-only regular file within 512 MiB");
13806
+ }
13807
+ if (!host.exists(config.bootstrapBundle.bundleFile) || !host.exists(config.bootstrapBundle.manifestFile)) {
13808
+ throw new Error("attended bootstrap bundle and manifest are required for release generation one");
13809
+ }
13810
+ let parsed;
13811
+ try {
13812
+ parsed = JSON.parse(host.read(config.bootstrapBundle.manifestFile));
13813
+ } catch {
13814
+ throw new Error("bootstrap bundle manifest is not valid JSON");
13815
+ }
13816
+ const manifest = parseBootstrapBundleManifest(parsed);
13817
+ const expectedBranch = config.environment === "production" ? "main" : "dev";
13818
+ if (manifest.branch !== expectedBranch || bundleMetadata && bundleMetadata.size !== manifest.bytes) {
13819
+ throw new Error("bootstrap bundle manifest disagrees with the selected platform environment");
13820
+ }
13821
+ const digest = (await checked3(host, ["/usr/bin/sha256sum", config.bootstrapBundle.bundleFile], "bootstrap bundle digest")).trim().split(/\s+/)[0];
13822
+ if (digest !== manifest.sha256)
13823
+ throw new Error("bootstrap bundle digest does not match its manifest");
13824
+ if (verifyGit) {
13825
+ await checked3(host, ["/usr/bin/git", "bundle", "verify", config.bootstrapBundle.bundleFile], "bootstrap Git bundle verification");
13826
+ }
13827
+ return manifest;
13828
+ }
13629
13829
  function bootstrapIdentity(config) {
13630
13830
  if (config.kind === "enrolled-compute")
13631
13831
  return {
@@ -13652,8 +13852,7 @@ function bootstrapIdentity(config) {
13652
13852
  computeReference: config.computeReference,
13653
13853
  nodeHostname: config.nodeHostname,
13654
13854
  apiUrl: config.apiUrl,
13655
- repository: config.repository,
13656
- branch: config.branch,
13855
+ bootstrapBundle: config.bootstrapBundle,
13657
13856
  deployRoot: config.deployRoot ?? "/opt/forgezero",
13658
13857
  telemetryEndpoint: config.telemetryEndpoint,
13659
13858
  database: {
@@ -13683,7 +13882,7 @@ function bootstrapIdentity(config) {
13683
13882
  };
13684
13883
  }
13685
13884
  function bootstrapIdentityDigest(config) {
13686
- return createHash3("sha256").update(JSON.stringify(bootstrapIdentity(config))).digest("hex");
13885
+ return createHash4("sha256").update(JSON.stringify(bootstrapIdentity(config))).digest("hex");
13687
13886
  }
13688
13887
  function parseStoredState(raw) {
13689
13888
  let value;
@@ -13733,34 +13932,6 @@ function bindBootstrapIntent(host, config) {
13733
13932
  }
13734
13933
  return identityDigest;
13735
13934
  }
13736
- async function preparePlatformBootstrap(input, host = localBootstrapHost()) {
13737
- const config = validateBootstrapConfig(structuredClone(input));
13738
- if (config.kind !== "platform")
13739
- throw new Error("platform preparation requires a platform bootstrap config");
13740
- if (host.uid() !== 0)
13741
- throw new Error("fz bootstrap platform prepare --apply must run as root");
13742
- if (host.exists(STATE_PATH)) {
13743
- const installed = parseStoredState(host.read(STATE_PATH));
13744
- if (installed.kind !== "platform" || installed.identityDigest !== bootstrapIdentityDigest(config)) {
13745
- throw new Error("platform preparation coordinates do not match the installed host identity");
13746
- }
13747
- }
13748
- const identityDigest = bindBootstrapIntent(host, config);
13749
- await host.installAgent(config);
13750
- if (!host.exists(GIT_PUBLIC_KEY))
13751
- throw new Error("Agent installation did not produce its public Git deploy key");
13752
- const gitPublicKey = host.read(GIT_PUBLIC_KEY).trim();
13753
- if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(gitPublicKey)) {
13754
- throw new Error("Agent public Git deploy key is malformed");
13755
- }
13756
- return {
13757
- kind: "platform",
13758
- prepared: true,
13759
- identityDigest,
13760
- gitPublicKey,
13761
- next: "register this read-only deploy key, then run --apply concurrently on all three genesis Agency members"
13762
- };
13763
- }
13764
13935
  function stateFor(config, cloudflare, previousCloudflareTunnelId) {
13765
13936
  return `${JSON.stringify({
13766
13937
  format: 2,
@@ -13914,6 +14085,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
13914
14085
  let installed;
13915
14086
  if (host.exists(STATE_PATH))
13916
14087
  installed = parseStoredState(host.read(STATE_PATH));
14088
+ const bootstrapManifest = config.kind === "platform" && !host.exists(BOOTSTRAP_RELEASE_EVIDENCE) ? await verifyBootstrapBundleOnHost(host, config) : undefined;
13917
14089
  let cloudflare;
13918
14090
  if (config.cloudflareHandoff) {
13919
14091
  if (host.exists(config.cloudflareHandoff.handoffFile)) {
@@ -13971,14 +14143,14 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
13971
14143
  const platformPrivate = config.kind === "platform" ? (() => {
13972
14144
  if (!secrets)
13973
14145
  throw new Error("platform apply requires attended credentials on stdin");
13974
- const checked3 = validatePlatformBootstrapSecrets(config, secrets);
14146
+ const checked4 = validatePlatformBootstrapSecrets(config, secrets);
13975
14147
  return {
13976
- root: checked3.clusterBootstrapCode,
13977
- email: checked3.emailSecret,
13978
- enrolmentToken: checked3.enrolmentToken,
13979
- backup: checked3.backupS3Secret,
13980
- cloudflareTunnelToken: checked3.cloudflareTunnelToken,
13981
- cloudflareApiToken: checked3.cloudflareApiToken
14148
+ root: checked4.clusterBootstrapCode,
14149
+ email: checked4.emailSecret,
14150
+ enrolmentToken: checked4.enrolmentToken,
14151
+ backup: checked4.backupS3Secret,
14152
+ cloudflareTunnelToken: checked4.cloudflareTunnelToken,
14153
+ cloudflareApiToken: checked4.cloudflareApiToken
13982
14154
  };
13983
14155
  })() : undefined;
13984
14156
  const enrolledPrivate = config.kind === "enrolled-compute" ? validateEnrolledComputeBootstrapSecrets(config, secrets) : undefined;
@@ -14017,24 +14189,35 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14017
14189
  if (connectorCapabilities?.meshConnectorToken && !host.exists(WARP_CONNECTOR_CREDENTIAL)) {
14018
14190
  await seal(host, "CF_WARP_CONNECTOR_TOKEN", WARP_CONNECTOR_CREDENTIAL, connectorCapabilities.meshConnectorToken);
14019
14191
  }
14020
- 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
+ }
14021
14201
  if (config.kind === "platform" && config.firewall.enabled) {
14022
14202
  await host.ensureSoftware(plan.software.filter(({ id: id2 }) => id2 === "ufw"));
14023
14203
  } else
14024
14204
  await host.ensureSoftware(plan.software);
14025
14205
  if (config.kind === "platform" && config.firewall.enabled) {
14026
- await checked2(host, ["ufw", "--force", "default", "deny", "incoming"], "firewall inbound policy");
14027
- await checked2(host, ["ufw", "--force", "default", "allow", "outgoing"], "firewall outbound policy");
14028
- await checked2(host, ["ufw", "allow", `${config.firewall.sshPort}/tcp`], "firewall SSH rule");
14206
+ await checked3(host, ["ufw", "--force", "default", "deny", "incoming"], "firewall inbound policy");
14207
+ await checked3(host, ["ufw", "--force", "default", "allow", "outgoing"], "firewall outbound policy");
14208
+ await checked3(host, ["ufw", "allow", `${config.firewall.sshPort}/tcp`], "firewall SSH rule");
14029
14209
  for (const cidr of config.firewall.privateCidrs) {
14030
14210
  if (config.database.role !== "none")
14031
- await checked2(host, ["ufw", "allow", "from", cidr, "to", "any", "port", "8528:8539", "proto", "tcp"], "database firewall rule");
14211
+ await checked3(host, ["ufw", "allow", "from", cidr, "to", "any", "port", "8528:8539", "proto", "tcp"], "database firewall rule");
14032
14212
  for (const port of [config.runtime.bluePort, config.runtime.greenPort])
14033
- await checked2(host, ["ufw", "allow", "from", cidr, "to", "any", "port", String(port), "proto", "tcp"], "seed-mesh firewall rule");
14213
+ await checked3(host, ["ufw", "allow", "from", cidr, "to", "any", "port", String(port), "proto", "tcp"], "seed-mesh firewall rule");
14034
14214
  }
14035
- await checked2(host, ["ufw", "--force", "enable"], "firewall activation");
14215
+ await checked3(host, ["ufw", "--force", "enable"], "firewall activation");
14036
14216
  await host.ensureSoftware(plan.software.filter(({ id: id2 }) => id2 !== "ufw"));
14037
14217
  }
14218
+ if (config.kind === "platform" && bootstrapManifest) {
14219
+ await verifyBootstrapBundleOnHost(host, config, true);
14220
+ }
14038
14221
  if (config.kind === "platform") {
14039
14222
  const root = platformPrivate.root;
14040
14223
  if (!host.exists(JWT_CREDENTIAL)) {
@@ -14089,8 +14272,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14089
14272
  keepReleases: runtime.keepReleases,
14090
14273
  drainDeadlineMs: runtime.environment.drainDeadlineMs
14091
14274
  });
14092
- await checked2(host, ["useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", runtime.serviceUser], "API service account").catch(async () => {
14093
- await checked2(host, ["id", runtime.serviceUser], "existing API service account");
14275
+ await checked3(host, ["useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", runtime.serviceUser], "API service account").catch(async () => {
14276
+ await checked3(host, ["id", runtime.serviceUser], "existing API service account");
14094
14277
  });
14095
14278
  host.mkdir(runtime.environment.sharedDirectory, 488);
14096
14279
  host.mkdir(runtime.slotsDirectory, 493);
@@ -14099,7 +14282,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14099
14282
  host.write("/etc/forgezero/capacity.env", `FZ_CONCURRENCY_LIMIT=${runtime.environment.concurrencyLimit}
14100
14283
  `, 420);
14101
14284
  }
14102
- await checked2(host, ["chown", `root:${runtime.serviceUser}`, runtime.environment.sharedDirectory, envPath], "runtime ownership");
14285
+ await checked3(host, ["chown", `root:${runtime.serviceUser}`, runtime.environment.sharedDirectory, envPath], "runtime ownership");
14103
14286
  host.write("/etc/systemd/system/forgezero@.service", units.template, 420);
14104
14287
  host.write("/etc/systemd/system/forgezero@blue.service.d/port.conf", units.dropIns.blue, 420);
14105
14288
  host.write("/etc/systemd/system/forgezero@green.service.d/port.conf", units.dropIns.green, 420);
@@ -14108,30 +14291,30 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14108
14291
  host.write("/etc/forgezero/deploy.env", activation.environment, 420);
14109
14292
  host.write("/etc/forgezero/deploy-activation.json", activation.helper, 384);
14110
14293
  host.write("/etc/sudoers.d/forgezero-runner", activation.sudoers, 288);
14111
- await checked2(host, ["visudo", "-cf", "/etc/sudoers.d/forgezero-runner"], "activation sudo policy");
14294
+ await checked3(host, ["visudo", "-cf", "/etc/sudoers.d/forgezero-runner"], "activation sudo policy");
14112
14295
  const telemetry = planLocalOtlpProof(runtime.environment.otlpEndpoint, runtime.environment.otlpCollectorUnit);
14113
- await checked2(host, telemetry.unitCheck.argv, "OTLP collector supervision");
14114
- const otlpStatus = (await checked2(host, [telemetry.receiverCheck.command, ...telemetry.receiverCheck.argv], "OTLP receiver")).trim();
14296
+ await checked3(host, telemetry.unitCheck.argv, "OTLP collector supervision");
14297
+ const otlpStatus = (await checked3(host, [telemetry.receiverCheck.command, ...telemetry.receiverCheck.argv], "OTLP receiver")).trim();
14115
14298
  if (!/^2\d\d$/.test(otlpStatus))
14116
14299
  throw new Error(`OTLP receiver returned HTTP ${otlpStatus || "unknown"}`);
14117
- await checked2(host, ["nginx", "-t"], "nginx configuration");
14118
- await checked2(host, ["systemctl", "daemon-reload"], "systemd reload");
14119
- await checked2(host, ["systemctl", "enable", "--now", "nginx.service"], "nginx supervision");
14300
+ await checked3(host, ["nginx", "-t"], "nginx configuration");
14301
+ await checked3(host, ["systemctl", "daemon-reload"], "systemd reload");
14302
+ await checked3(host, ["systemctl", "enable", "--now", "nginx.service"], "nginx supervision");
14120
14303
  if (config.database.role === "master") {
14121
14304
  const invite = `${runtime.environment.sharedDirectory}/platform-invite.token`;
14122
14305
  if (!host.exists(invite)) {
14123
- host.write(invite, `plt_${randomBytes6(24).toString("hex")}
14306
+ host.write(invite, `plt_${randomBytes7(24).toString("hex")}
14124
14307
  `, 384);
14125
- await checked2(host, ["chown", `${runtime.serviceUser}:${runtime.serviceUser}`, invite], "platform invite ownership");
14308
+ await checked3(host, ["chown", `${runtime.serviceUser}:${runtime.serviceUser}`, invite], "platform invite ownership");
14126
14309
  }
14127
14310
  }
14128
14311
  if (config.database.role !== "none") {
14129
14312
  host.mkdir("/var/lib/forgezero-cluster", 448);
14130
- await checked2(host, ["chown", "arangodb:arangodb", "/var/lib/forgezero-cluster"], "database state ownership");
14313
+ await checked3(host, ["chown", "arangodb:arangodb", "/var/lib/forgezero-cluster"], "database state ownership");
14131
14314
  host.write("/etc/systemd/system/forgezero-db.service", databaseUnit(config), 420);
14132
14315
  host.write("/etc/systemd/system/forgezero-db-verify.service", databaseVerifyUnit(config), 420);
14133
- await checked2(host, ["systemctl", "daemon-reload"], "database unit reload");
14134
- await checked2(host, ["systemctl", "enable", "--now", "forgezero-db.service", "forgezero-db-verify.service"], "database supervision");
14316
+ await checked3(host, ["systemctl", "daemon-reload"], "database unit reload");
14317
+ await checked3(host, ["systemctl", "enable", "--now", "forgezero-db.service", "forgezero-db-verify.service"], "database supervision");
14135
14318
  const evidence = {
14136
14319
  expectedMode: "default",
14137
14320
  role: "COORDINATOR",
@@ -14142,15 +14325,42 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14142
14325
  host.write(DB_MODE_EVIDENCE, `${JSON.stringify(evidence, null, 2)}
14143
14326
  `, 384);
14144
14327
  }
14145
- await checked2(host, [
14146
- "runuser",
14147
- "-u",
14148
- "forgezero-agent",
14149
- "--",
14150
- "/usr/local/bin/fz-agent",
14151
- "deploy",
14152
- ...config.database.role === "master" ? ["--release-executor"] : []
14153
- ], "initial Agent deployment");
14328
+ if (bootstrapManifest) {
14329
+ await checked3(host, [
14330
+ "runuser",
14331
+ "-u",
14332
+ "forgezero-agent",
14333
+ "--",
14334
+ "/usr/local/bin/fz-agent",
14335
+ "deploy",
14336
+ `--revision=${bootstrapManifest.revision}`,
14337
+ ...config.database.role === "master" ? ["--release-executor"] : []
14338
+ ], "initial Agent deployment");
14339
+ host.write(BOOTSTRAP_RELEASE_EVIDENCE, `${JSON.stringify({
14340
+ format: 1,
14341
+ kind: "forgezero-bootstrap-release",
14342
+ revision: bootstrapManifest.revision,
14343
+ sha256: bootstrapManifest.sha256,
14344
+ branch: bootstrapManifest.branch,
14345
+ deployedAt: new Date().toISOString()
14346
+ }, null, 2)}
14347
+ `, 384);
14348
+ host.remove(config.bootstrapBundle.bundleFile);
14349
+ host.remove(config.bootstrapBundle.manifestFile);
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");
14363
+ }
14154
14364
  }
14155
14365
  if (config.cloudflareHandoff) {
14156
14366
  if (!host.exists(TUNNEL_CREDENTIAL) && connectorCapabilities) {
@@ -14159,8 +14369,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14159
14369
  if (!host.exists(TUNNEL_CREDENTIAL))
14160
14370
  throw new Error("sealed cloudflared connector credential is missing");
14161
14371
  host.write("/etc/systemd/system/cloudflared.service", tunnelUnit(), 420);
14162
- await checked2(host, ["systemctl", "daemon-reload"], "cloudflared unit reload");
14163
- await checked2(host, ["systemctl", "enable", "--now", "cloudflared.service"], "cloudflared connector supervision");
14372
+ await checked3(host, ["systemctl", "daemon-reload"], "cloudflared unit reload");
14373
+ await checked3(host, ["systemctl", "enable", "--now", "cloudflared.service"], "cloudflared connector supervision");
14164
14374
  const tunnelId = cloudflare?.tunnelId ?? installed?.cloudflareTunnelId ?? (config.kind === "platform" ? config.runtime.environment.cloudflare?.tunnelId : undefined);
14165
14375
  if (!tunnelId)
14166
14376
  throw new Error("Cloudflare tunnel identity is missing after handoff validation");
@@ -14170,8 +14380,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14170
14380
  if (!host.exists(WARP_CONNECTOR_CREDENTIAL))
14171
14381
  throw new Error("sealed Cloudflare Mesh connector credential is missing");
14172
14382
  host.write("/etc/systemd/system/forgezero-mesh-config.service", meshConnectorUnit(), 420);
14173
- await checked2(host, ["systemctl", "daemon-reload"], "Cloudflare Mesh unit reload");
14174
- await checked2(host, ["systemctl", "enable", "--now", "warp-svc.service", "forgezero-mesh-config.service"], "Cloudflare Mesh connector supervision");
14383
+ await checked3(host, ["systemctl", "daemon-reload"], "Cloudflare Mesh unit reload");
14384
+ await checked3(host, ["systemctl", "enable", "--now", "warp-svc.service", "forgezero-mesh-config.service"], "Cloudflare Mesh connector supervision");
14175
14385
  }
14176
14386
  host.write(STATE_PATH, stateFor(config, cloudflare, installed?.cloudflareTunnelId), 384);
14177
14387
  const status = await bootstrapStatus(host);
@@ -14216,8 +14426,7 @@ function strictBootstrapDocument(value) {
14216
14426
  "computeReference",
14217
14427
  "nodeHostname",
14218
14428
  "apiUrl",
14219
- "repository",
14220
- "branch",
14429
+ "bootstrapBundle",
14221
14430
  "deployRoot",
14222
14431
  "telemetryEndpoint",
14223
14432
  "database",
@@ -14230,9 +14439,11 @@ function strictBootstrapDocument(value) {
14230
14439
  "realm",
14231
14440
  "software",
14232
14441
  "deploymentCredentials",
14442
+ "gitDeployKey",
14233
14443
  "bootstrapRunner"
14234
14444
  ], "bootstrap config");
14235
14445
  if (root.kind === "platform") {
14446
+ exactKeys2(root.bootstrapBundle, ["bundleFile", "manifestFile"], "bootstrap bundle config");
14236
14447
  exactKeys2(root.firewall, ["enabled", "sshPort", "privateCidrs"], "firewall config");
14237
14448
  if (root.cloudflareHandoff !== undefined)
14238
14449
  exactKeys2(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
@@ -14274,8 +14485,6 @@ function strictBootstrapDocument(value) {
14274
14485
  "agentOtlpEndpoint",
14275
14486
  "custodianEmail",
14276
14487
  "email",
14277
- "repository",
14278
- "branch",
14279
14488
  "deployProfile",
14280
14489
  "otlpFlushIntervalMs",
14281
14490
  "otlpTraceSampleRatio",
@@ -14313,11 +14522,70 @@ function strictBootstrapDocument(value) {
14313
14522
  return value;
14314
14523
  }
14315
14524
  function readBootstrapConfig(path) {
14316
- const metadata = lstatSync4(path);
14525
+ const metadata = lstatSync5(path);
14317
14526
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.uid !== (process.getuid?.() ?? metadata.uid) || metadata.nlink !== 1 || (metadata.mode & 63) !== 0 || metadata.size > 64 * 1024) {
14318
14527
  throw new Error("bootstrap config must be an owner-only regular file with one link and at most 64 KiB");
14319
14528
  }
14320
- return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync8(path, "utf8"))));
14529
+ return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync9(path, "utf8"))));
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);
14321
14589
  }
14322
14590
  function localBootstrapHost() {
14323
14591
  const execute = async (argv2, options = {}) => {
@@ -14335,19 +14603,19 @@ function localBootstrapHost() {
14335
14603
  };
14336
14604
  return {
14337
14605
  uid: () => process.getuid?.() ?? -1,
14338
- exists: existsSync8,
14339
- read: (path) => readFileSync8(path, "utf8"),
14606
+ exists: existsSync9,
14607
+ read: (path) => readFileSync9(path, "utf8"),
14340
14608
  write(path, content, mode) {
14341
- mkdirSync8(dirname8(path), { recursive: true, mode: 493 });
14609
+ mkdirSync9(dirname9(path), { recursive: true, mode: 493 });
14342
14610
  const temporary = `${path}.next.${process.pid}`;
14343
- writeFileSync8(temporary, content, { mode });
14344
- chmodSync3(temporary, mode);
14345
- renameSync7(temporary, path);
14611
+ writeFileSync9(temporary, content, { mode });
14612
+ chmodSync4(temporary, mode);
14613
+ renameSync8(temporary, path);
14346
14614
  },
14347
- mkdir: (path, mode) => mkdirSync8(path, { recursive: true, mode }),
14348
- remove: (path) => rmSync5(path, { force: true }),
14615
+ mkdir: (path, mode) => mkdirSync9(path, { recursive: true, mode }),
14616
+ remove: (path) => rmSync6(path, { force: true }),
14349
14617
  inspect(path) {
14350
- const value = lstatSync4(path);
14618
+ const value = lstatSync5(path);
14351
14619
  return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
14352
14620
  },
14353
14621
  exec: execute,
@@ -14366,10 +14634,15 @@ function localBootstrapHost() {
14366
14634
  throw new Error(`Agent software requirements failed: ${result.output.trim()}`);
14367
14635
  return result;
14368
14636
  },
14369
- async installAgent(config) {
14637
+ async installAgent(config, phase) {
14370
14638
  const capabilities = await readCapabilities(localRunner);
14371
- const deployRoot = config.deployRoot ?? "/opt/forgezero";
14372
- const hasBinding = config.kind === "enrolled-compute" || existsSync8(ENROL_CREDENTIAL) || existsSync8("/var/lib/forgezero/enrolment.json");
14639
+ const hasBinding = existsSync9("/var/lib/forgezero/enrolment.json");
14640
+ const hasEnrolCredential = existsSync9(ENROL_CREDENTIAL);
14641
+ const initialBundle = config.kind === "platform" && !existsSync9(BOOTSTRAP_RELEASE_EVIDENCE) ? {
14642
+ path: config.bootstrapBundle.bundleFile,
14643
+ manifestPath: config.bootstrapBundle.manifestFile,
14644
+ manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync9(config.bootstrapBundle.manifestFile, "utf8")))
14645
+ } : undefined;
14373
14646
  if (config.kind === "platform") {
14374
14647
  const lifecycle = config.database.role === "none" ? {
14375
14648
  apiUnits: ["forgezero@blue.service", "forgezero@green.service"],
@@ -14381,49 +14654,19 @@ function localBootstrapHost() {
14381
14654
  databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
14382
14655
  databasePorts: [8529]
14383
14656
  };
14384
- mkdirSync8(dirname8(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
14385
- writeFileSync8(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
14657
+ mkdirSync9(dirname9(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
14658
+ writeFileSync9(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
14386
14659
  `, { mode: 256 });
14387
14660
  }
14388
- const plan = planInstall({
14661
+ const plan = planBootstrapAgentInstall(config, phase, {
14389
14662
  capabilities,
14390
- socketPath: DEFAULT_SOCKET,
14391
- seedPath: "/var/lib/forgezero/node.seed",
14392
- controlSocketPath: "/run/forgezero/control.sock",
14393
- repository: config.repository,
14394
- branch: config.branch,
14395
- profile: config.kind === "platform" ? config.profile : config.profile,
14396
- deployRoot,
14397
- deploymentCredentials: config.deploymentCredentials,
14398
- publicApiUrl: config.apiUrl,
14399
- gitCredentialPath: "/etc/forgezero/creds/git-deploy-key.cred",
14400
- gitPublicKeyPath: "/etc/forgezero/git/deploy.pub",
14401
- generateGitIdentity: true,
14402
- pullDeployments: hasBinding,
14403
- pullMigrations: config.kind === "platform" && hasBinding,
14404
- pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
14405
- bootstrapSshCredentialPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
14406
- bootstrapSshSourcePath: config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
14407
- bootstrapSshPublicKeyPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
14408
- bootstrapTargetTelemetryEndpoint: platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined,
14409
- lifecycleProfilePath: config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
14410
- enforceEgress: true,
14411
- nodeHostname: config.nodeHostname,
14412
- telemetryEndpoint: config.telemetryEndpoint,
14413
- binPath: "/usr/local/lib/forgezero/agent/fz-agent",
14414
- sourceBinPath: PACKAGED_AGENT_BIN,
14415
- ...hasBinding ? {
14416
- enrolTokenCredentialPath: ENROL_CREDENTIAL,
14417
- enrolStatePath: "/var/lib/forgezero/enrolment.json",
14418
- apiUrl: config.apiUrl,
14419
- project: config.kind === "enrolled-compute" ? config.realm : "platform",
14420
- environment: config.kind === "enrolled-compute" ? undefined : config.environment,
14421
- nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
14422
- } : {}
14663
+ hasBinding,
14664
+ hasEnrolCredential,
14665
+ initialBundle
14423
14666
  });
14424
14667
  for (const unit of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
14425
- mkdirSync8(dirname8(unit.path), { recursive: true, mode: 493 });
14426
- writeFileSync8(unit.path, unit.unit, { mode: 420 });
14668
+ mkdirSync9(dirname9(unit.path), { recursive: true, mode: 493 });
14669
+ writeFileSync9(unit.path, unit.unit, { mode: 420 });
14427
14670
  }
14428
14671
  await applyPlan(plan, localRunner);
14429
14672
  return plan;
@@ -14432,8 +14675,8 @@ function localBootstrapHost() {
14432
14675
  }
14433
14676
 
14434
14677
  // src/cli/cloudflare-bootstrap.ts
14435
- import { constants as constants2, closeSync, fstatSync, openSync, readFileSync as readFileSync9 } from "fs";
14436
- import { dirname as dirname9, resolve as resolve6 } from "path";
14678
+ import { constants as constants2, closeSync, fstatSync, openSync, readFileSync as readFileSync10 } from "fs";
14679
+ import { dirname as dirname10, resolve as resolve7 } from "path";
14437
14680
  async function discoverCloudflareBootstrapCommandResources(input, dependencies = {}) {
14438
14681
  return (dependencies.discover ?? discoverCloudflareBootstrapResources)({
14439
14682
  zoneName: input.zoneName,
@@ -14464,7 +14707,7 @@ function createCloudflareBootstrapCommandConfig(input) {
14464
14707
  };
14465
14708
  }
14466
14709
  function readOwnerConfig(path) {
14467
- const absolute2 = resolve6(path);
14710
+ const absolute2 = resolve7(path);
14468
14711
  let descriptor;
14469
14712
  try {
14470
14713
  descriptor = openSync(absolute2, constants2.O_RDONLY | constants2.O_NOFOLLOW);
@@ -14473,7 +14716,7 @@ function readOwnerConfig(path) {
14473
14716
  if (!metadata.isFile() || metadata.nlink !== 1 || metadata.size < 2 || metadata.size > 131072 || uid !== undefined && metadata.uid !== uid || (metadata.mode & 63) !== 0) {
14474
14717
  throw new Error(`${absolute2} must be one operator-owned 0600 regular file`);
14475
14718
  }
14476
- return JSON.parse(readFileSync9(descriptor, "utf8"));
14719
+ return JSON.parse(readFileSync10(descriptor, "utf8"));
14477
14720
  } catch (cause) {
14478
14721
  if (cause instanceof SyntaxError)
14479
14722
  throw new Error(`${absolute2} is not valid Cloudflare bootstrap JSON`);
@@ -14487,7 +14730,7 @@ function readOwnerConfig(path) {
14487
14730
  }
14488
14731
  function readCloudflareBootstrapCommandConfig(path, mode) {
14489
14732
  const input = record2(readOwnerConfig(path), "Cloudflare bootstrap config");
14490
- const baseDirectory = dirname9(resolve6(path));
14733
+ const baseDirectory = dirname10(resolve7(path));
14491
14734
  exactKeys3(input, ["format", "kind", "checkpointPath", "coordinates"], "Cloudflare bootstrap config");
14492
14735
  if (input.format !== 1 || input.kind !== "forgezero-cloudflare-bootstrap-request") {
14493
14736
  throw new Error("Cloudflare bootstrap config format/kind is invalid");
@@ -14527,7 +14770,7 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
14527
14770
  return {
14528
14771
  mode,
14529
14772
  coordinates,
14530
- checkpointPath: resolve6(baseDirectory, input.checkpointPath)
14773
+ checkpointPath: resolve7(baseDirectory, input.checkpointPath)
14531
14774
  };
14532
14775
  }
14533
14776
  async function runCloudflareBootstrapCommand(configPath, apply, tokens, dependencies = {}) {
@@ -14540,14 +14783,14 @@ async function runCloudflareBootstrapCommand(configPath, apply, tokens, dependen
14540
14783
  return evidence;
14541
14784
  }
14542
14785
  async function runCloudflareBootstrapVerificationCommand(checkpointPath, dependencies = {}) {
14543
- const evidence = await (dependencies.verify ?? verifyCloudflareBootstrapAcceptance)(resolve6(checkpointPath));
14786
+ const evidence = await (dependencies.verify ?? verifyCloudflareBootstrapAcceptance)(resolve7(checkpointPath));
14544
14787
  (dependencies.write ?? ((text3) => process.stdout.write(text3)))(`${JSON.stringify(evidence, null, 2)}
14545
14788
  `);
14546
14789
  return evidence;
14547
14790
  }
14548
14791
  function readCloudflareBootstrapFinalizeConfig(path) {
14549
14792
  const input = record2(readOwnerConfig(path), "Cloudflare bootstrap finalize config");
14550
- const baseDirectory = dirname9(resolve6(path));
14793
+ const baseDirectory = dirname10(resolve7(path));
14551
14794
  exactKeys3(input, ["format", "kind", "checkpointPath", "acceptancePath"], "Cloudflare bootstrap finalize config");
14552
14795
  if (input.format !== 1 || input.kind !== "forgezero-cloudflare-bootstrap-finalize") {
14553
14796
  throw new Error("Cloudflare bootstrap finalize config format/kind is invalid");
@@ -14558,8 +14801,8 @@ function readCloudflareBootstrapFinalizeConfig(path) {
14558
14801
  }
14559
14802
  }
14560
14803
  return {
14561
- checkpointPath: resolve6(baseDirectory, input.checkpointPath),
14562
- acceptancePath: resolve6(baseDirectory, input.acceptancePath)
14804
+ checkpointPath: resolve7(baseDirectory, input.checkpointPath),
14805
+ acceptancePath: resolve7(baseDirectory, input.acceptancePath)
14563
14806
  };
14564
14807
  }
14565
14808
  async function runCloudflareBootstrapFinalizeCommand(configPath, dependencies = {}) {
@@ -14571,31 +14814,31 @@ async function runCloudflareBootstrapFinalizeCommand(configPath, dependencies =
14571
14814
  }
14572
14815
 
14573
14816
  // src/metal-bootstrap.ts
14574
- import { createHash as createHash4, randomBytes as randomBytes7 } from "crypto";
14817
+ import { createHash as createHash5, randomBytes as randomBytes8 } from "crypto";
14575
14818
  import {
14576
- chmodSync as chmodSync4,
14819
+ chmodSync as chmodSync5,
14577
14820
  chownSync,
14578
14821
  copyFileSync as copyFileSync2,
14579
- existsSync as existsSync9,
14580
- lstatSync as lstatSync5,
14581
- mkdirSync as mkdirSync10,
14582
- readFileSync as readFileSync10,
14822
+ existsSync as existsSync10,
14823
+ lstatSync as lstatSync6,
14824
+ mkdirSync as mkdirSync11,
14825
+ readFileSync as readFileSync11,
14583
14826
  realpathSync as realpathSync4,
14584
- renameSync as renameSync8,
14827
+ renameSync as renameSync9,
14585
14828
  statSync,
14586
14829
  symlinkSync as symlinkSync2,
14587
14830
  unlinkSync as unlinkSync2,
14588
- writeFileSync as writeFileSync10
14831
+ writeFileSync as writeFileSync11
14589
14832
  } from "fs";
14590
- import { dirname as dirname11, isAbsolute as isAbsolute3, join as join9, resolve as resolve7 } from "path";
14833
+ import { dirname as dirname12, isAbsolute as isAbsolute4, join as join9, resolve as resolve8 } from "path";
14591
14834
  import { isIP as isIP5 } from "net";
14592
14835
 
14593
14836
  // src/metal-isolation.ts
14594
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
14837
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
14595
14838
  import { join as join8 } from "path";
14596
14839
 
14597
14840
  // src/metal-provision.ts
14598
- import { dirname as dirname10, isAbsolute as isAbsolute2, join as join7 } from "path";
14841
+ import { dirname as dirname11, isAbsolute as isAbsolute3, join as join7 } from "path";
14599
14842
  import { isIP as isIP4 } from "net";
14600
14843
 
14601
14844
  // src/ubuntu.ts
@@ -14644,7 +14887,7 @@ function validateMetalProfile(profile) {
14644
14887
  if (!Number.isInteger(profile.addressStart) || !Number.isInteger(profile.addressEnd) || profile.addressStart < 2 || profile.addressEnd > 254 || profile.addressStart > profile.addressEnd)
14645
14888
  throw new MetalProvisionError("invalid guest address range");
14646
14889
  for (const path of [profile.stateDir, profile.seedDir, profile.unitDir]) {
14647
- if (!isAbsolute2(path))
14890
+ if (!isAbsolute3(path))
14648
14891
  throw new MetalProvisionError("metal paths must be absolute");
14649
14892
  }
14650
14893
  new URL(profile.apiUrl);
@@ -14752,14 +14995,14 @@ var defaultExec = async (argv2) => {
14752
14995
  ]);
14753
14996
  return { exitCode, stdout, stderr };
14754
14997
  };
14755
- var checked3 = async (exec, argv2) => {
14998
+ var checked4 = async (exec, argv2) => {
14756
14999
  const result = await exec(argv2);
14757
15000
  if (result.exitCode !== 0)
14758
15001
  throw new Error(`${argv2[0]} failed: ${(result.stderr || result.stdout).trim()}`);
14759
15002
  return result;
14760
15003
  };
14761
15004
  var requireGuestsInSlice = async (exec) => {
14762
- const active = await checked3(exec, [
15005
+ const active = await checked4(exec, [
14763
15006
  "systemctl",
14764
15007
  "list-units",
14765
15008
  "--type=service",
@@ -14773,7 +15016,7 @@ var requireGuestsInSlice = async (exec) => {
14773
15016
  const service = line.trim().split(/\s+/)[0];
14774
15017
  if (!service)
14775
15018
  continue;
14776
- const cgroup = await checked3(exec, ["systemctl", "show", "-p", "ControlGroup", "--value", service]);
15019
+ const cgroup = await checked4(exec, ["systemctl", "show", "-p", "ControlGroup", "--value", service]);
14777
15020
  if (!cgroup.stdout.trim().includes("/forgezero-guests.slice/")) {
14778
15021
  throw new Error(`${service} must be drained and restarted into forgezero-guests.slice`);
14779
15022
  }
@@ -14783,23 +15026,23 @@ async function applyMetalIsolation(profile, exec = defaultExec) {
14783
15026
  validateMetalProfile(profile);
14784
15027
  await requireGuestsInSlice(exec);
14785
15028
  const unitDir = profile.unitDir;
14786
- mkdirSync9(unitDir, { recursive: true });
14787
- writeFileSync9(join8(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
15029
+ mkdirSync10(unitDir, { recursive: true });
15030
+ writeFileSync10(join8(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
14788
15031
  for (const unit of ["system.slice", "user.slice"]) {
14789
15032
  const directory = join8(unitDir, `${unit}.d`);
14790
- mkdirSync9(directory, { recursive: true });
14791
- writeFileSync9(join8(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
15033
+ mkdirSync10(directory, { recursive: true });
15034
+ writeFileSync10(join8(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
14792
15035
  }
14793
15036
  const initDirectory = join8(unitDir, "init.scope.d");
14794
- mkdirSync9(initDirectory, { recursive: true });
14795
- writeFileSync9(join8(initDirectory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "scope"), { mode: 420 });
14796
- await checked3(exec, ["systemctl", "daemon-reload"]);
15037
+ mkdirSync10(initDirectory, { recursive: true });
15038
+ writeFileSync10(join8(initDirectory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "scope"), { mode: 420 });
15039
+ await checked4(exec, ["systemctl", "daemon-reload"]);
14797
15040
  await requireGuestsInSlice(exec);
14798
15041
  const properties = [`AllowedCPUs=${profile.housekeepingCpus}`];
14799
15042
  if (profile.housekeepingMemoryNodes)
14800
15043
  properties.push(`AllowedMemoryNodes=${profile.housekeepingMemoryNodes}`);
14801
15044
  for (const unit of ["system.slice", "user.slice", "init.scope"]) {
14802
- await checked3(exec, ["systemctl", "set-property", "--runtime", unit, ...properties]);
15045
+ await checked4(exec, ["systemctl", "set-property", "--runtime", unit, ...properties]);
14803
15046
  }
14804
15047
  }
14805
15048
 
@@ -14911,7 +15154,7 @@ function validateMetalBootstrapConfig(config) {
14911
15154
  throw new MetalBootstrapError("metal bootstrap uses fixed state, seed, and systemd unit directories");
14912
15155
  }
14913
15156
  const image = config.profile.images[Object.keys(config.profile.images)[0]];
14914
- if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve7(image.path) !== image.path || /[\0\r\n]/.test(image.path)) {
15157
+ if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve8(image.path) !== image.path || /[\0\r\n]/.test(image.path)) {
14915
15158
  throw new MetalBootstrapError("the pinned guest image must use the fixed image directory");
14916
15159
  }
14917
15160
  if (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION2) {
@@ -14946,10 +15189,10 @@ function validateMetalBootstrapConfig(config) {
14946
15189
  return config;
14947
15190
  }
14948
15191
  function validateOwnerOnlyPath(path, requireRootOwner) {
14949
- if (!isAbsolute3(path) || resolve7(path) !== path || path.includes("/../")) {
15192
+ if (!isAbsolute4(path) || resolve8(path) !== path || path.includes("/../")) {
14950
15193
  throw new MetalBootstrapError("private bootstrap paths must be canonical absolute paths");
14951
15194
  }
14952
- const metadata = lstatSync5(path);
15195
+ const metadata = lstatSync6(path);
14953
15196
  if (!metadata.isFile() || metadata.isSymbolicLink() || realpathSync4(path) !== path) {
14954
15197
  throw new MetalBootstrapError("private bootstrap path must be a regular non-symlink file");
14955
15198
  }
@@ -14967,7 +15210,7 @@ function readMetalBootstrapConfig(path) {
14967
15210
  throw new MetalBootstrapError("metal bootstrap config size is invalid");
14968
15211
  let parsed;
14969
15212
  try {
14970
- parsed = JSON.parse(readFileSync10(path, "utf8"));
15213
+ parsed = JSON.parse(readFileSync11(path, "utf8"));
14971
15214
  } catch {
14972
15215
  throw new MetalBootstrapError("metal bootstrap config is not valid JSON");
14973
15216
  }
@@ -15000,15 +15243,15 @@ function planMetalBootstrap(config) {
15000
15243
  };
15001
15244
  }
15002
15245
  var atomicWrite2 = (path, body, mode) => {
15003
- mkdirSync10(dirname11(path), { recursive: true, mode: 493 });
15246
+ mkdirSync11(dirname12(path), { recursive: true, mode: 493 });
15004
15247
  const temporary = `${path}.next-${process.pid}`;
15005
- writeFileSync10(temporary, body, { mode, flag: "wx" });
15006
- chmodSync4(temporary, mode);
15248
+ writeFileSync11(temporary, body, { mode, flag: "wx" });
15249
+ chmodSync5(temporary, mode);
15007
15250
  chownSync(temporary, 0, 0);
15008
- renameSync8(temporary, path);
15251
+ renameSync9(temporary, path);
15009
15252
  };
15010
15253
  var validateAgentSourcePath = (source) => {
15011
- if (!isAbsolute3(source) || !lstatSync5(source).isFile() || lstatSync5(source).isSymbolicLink()) {
15254
+ if (!isAbsolute4(source) || !lstatSync6(source).isFile() || lstatSync6(source).isSymbolicLink()) {
15012
15255
  throw new MetalBootstrapError("published Agent source path must be an absolute regular non-symlink file");
15013
15256
  }
15014
15257
  };
@@ -15172,11 +15415,11 @@ WantedBy=multi-user.target
15172
15415
  var installAgentBinary = (source, version) => {
15173
15416
  validateAgentSourcePath(source);
15174
15417
  const release = `/opt/forgezero/agent/versions/${version}/dist`;
15175
- mkdirSync10(release, { recursive: true, mode: 493 });
15418
+ mkdirSync11(release, { recursive: true, mode: 493 });
15176
15419
  copyFileSync2(source, join9(release, "fz-agent.js"));
15177
- chmodSync4(join9(release, "fz-agent.js"), 493);
15420
+ chmodSync5(join9(release, "fz-agent.js"), 493);
15178
15421
  chownSync(join9(release, "fz-agent.js"), 0, 0);
15179
- mkdirSync10("/opt/forgezero/agent", { recursive: true, mode: 493 });
15422
+ mkdirSync11("/opt/forgezero/agent", { recursive: true, mode: 493 });
15180
15423
  for (const [link, target] of [
15181
15424
  ["/opt/forgezero/agent/current.next", `versions/${version}`],
15182
15425
  [AGENT_PATH, "/opt/forgezero/agent/current/dist/fz-agent.js"]
@@ -15186,7 +15429,7 @@ var installAgentBinary = (source, version) => {
15186
15429
  } catch {}
15187
15430
  symlinkSync2(target, link);
15188
15431
  if (link.endsWith("current.next"))
15189
- renameSync8(link, "/opt/forgezero/agent/current");
15432
+ renameSync9(link, "/opt/forgezero/agent/current");
15190
15433
  }
15191
15434
  };
15192
15435
  var preflight = async (config, exec) => {
@@ -15208,10 +15451,10 @@ var preflight = async (config, exec) => {
15208
15451
  ]);
15209
15452
  await runChecked(exec, ["/usr/sbin/vgs", config.profile.volumeGroup]);
15210
15453
  await runChecked(exec, ["/usr/sbin/ip", "link", "show", config.profile.bridge]);
15211
- if (!existsSync9("/dev/kvm"))
15454
+ if (!existsSync10("/dev/kvm"))
15212
15455
  throw new MetalBootstrapError("/dev/kvm is required");
15213
15456
  if (config.profile.confidential) {
15214
- if (!existsSync9("/dev/sev"))
15457
+ if (!existsSync10("/dev/sev"))
15215
15458
  throw new MetalBootstrapError("/dev/sev is required by the confidential profile");
15216
15459
  await runChecked(exec, ["/usr/bin/qemu-system-x86_64", "-object", "sev-snp-guest,help"]);
15217
15460
  }
@@ -15223,16 +15466,16 @@ var assertSupportedMetalHost = () => {
15223
15466
  if (process.platform !== "linux" || process.arch !== "x64") {
15224
15467
  throw new MetalBootstrapError("metal bootstrap supports only Ubuntu 26.04 x86_64 hosts");
15225
15468
  }
15226
- const release = readFileSync10("/etc/os-release", "utf8");
15469
+ const release = readFileSync11("/etc/os-release", "utf8");
15227
15470
  if (!/^ID=ubuntu$/m.test(release) || !/^VERSION_ID="?26\.04"?$/m.test(release)) {
15228
15471
  throw new MetalBootstrapError("metal bootstrap supports only Ubuntu 26.04 x86_64 hosts");
15229
15472
  }
15230
15473
  };
15231
15474
  var ensurePinnedGuestImage = async (config, exec) => {
15232
15475
  const image = config.profile.images[SUPPORTED_GUEST_IMAGE.key];
15233
- if (existsSync9(image.path))
15476
+ if (existsSync10(image.path))
15234
15477
  return;
15235
- mkdirSync10(dirname11(image.path), { recursive: true, mode: 493 });
15478
+ mkdirSync11(dirname12(image.path), { recursive: true, mode: 493 });
15236
15479
  const temporary = `${image.path}.next-${process.pid}`;
15237
15480
  try {
15238
15481
  await runChecked(exec, [
@@ -15249,9 +15492,9 @@ var ensurePinnedGuestImage = async (config, exec) => {
15249
15492
  const digest = (await runChecked(exec, ["/usr/bin/sha256sum", temporary])).stdout.split(/\s+/)[0];
15250
15493
  if (digest !== image.sha256)
15251
15494
  throw new MetalBootstrapError("downloaded guest image digest mismatch");
15252
- chmodSync4(temporary, 292);
15495
+ chmodSync5(temporary, 292);
15253
15496
  chownSync(temporary, 0, 0);
15254
- renameSync8(temporary, image.path);
15497
+ renameSync9(temporary, image.path);
15255
15498
  } catch (cause) {
15256
15499
  try {
15257
15500
  unlinkSync2(temporary);
@@ -15264,11 +15507,11 @@ async function applyMetalBootstrap(config, options) {
15264
15507
  if ((options.getuid ?? process.getuid)?.() !== 0)
15265
15508
  throw new MetalBootstrapError("fz bootstrap metal --apply must run as root");
15266
15509
  assertSupportedMetalHost();
15267
- if (existsSync9(STATE_PATH2) && !options.repair)
15510
+ if (existsSync10(STATE_PATH2) && !options.repair)
15268
15511
  throw new MetalBootstrapError("metal host is already initialized; use explicit repair");
15269
15512
  if (config.agentSeedFile)
15270
15513
  validateOwnerOnlyPath(config.agentSeedFile, true);
15271
- if (config.agentSeedFile && existsSync9(SEED_CREDENTIAL_PATH)) {
15514
+ if (config.agentSeedFile && existsSync10(SEED_CREDENTIAL_PATH)) {
15272
15515
  throw new MetalBootstrapError("repair refuses replacement seed material while the sealed metal identity exists");
15273
15516
  }
15274
15517
  validateAgentSourcePath(options.agentSourcePath);
@@ -15295,9 +15538,9 @@ async function applyMetalBootstrap(config, options) {
15295
15538
  await preflight(config, exec);
15296
15539
  await ensureAccount(exec);
15297
15540
  installAgentBinary(options.agentSourcePath, config.profile.agentVersion);
15298
- mkdirSync10("/etc/forgezero/creds", { recursive: true, mode: 448 });
15299
- mkdirSync10(config.profile.stateDir, { recursive: true, mode: 448 });
15300
- mkdirSync10(config.profile.seedDir, { recursive: true, mode: 448 });
15541
+ mkdirSync11("/etc/forgezero/creds", { recursive: true, mode: 448 });
15542
+ mkdirSync11(config.profile.stateDir, { recursive: true, mode: 448 });
15543
+ mkdirSync11(config.profile.seedDir, { recursive: true, mode: 448 });
15301
15544
  const persistedProfile = {
15302
15545
  ...config.profile,
15303
15546
  metalHostname: config.metalHostname,
@@ -15306,13 +15549,13 @@ async function applyMetalBootstrap(config, options) {
15306
15549
  };
15307
15550
  atomicWrite2(PROFILE_PATH, `${JSON.stringify(persistedProfile, null, 2)}
15308
15551
  `, 384);
15309
- if (!existsSync9(SEED_CREDENTIAL_PATH)) {
15310
- const seed = config.agentSeedFile ? readFileSync10(config.agentSeedFile, "utf8").trim() : randomBytes7(32).toString("base64url");
15552
+ if (!existsSync10(SEED_CREDENTIAL_PATH)) {
15553
+ const seed = config.agentSeedFile ? readFileSync11(config.agentSeedFile, "utf8").trim() : randomBytes8(32).toString("base64url");
15311
15554
  if (seed.length < 32 || /[\0\r\n]/.test(seed))
15312
15555
  throw new MetalBootstrapError("metal Agent seed is invalid");
15313
15556
  await runChecked(exec, ["/usr/bin/systemd-creds", "encrypt", "--name=metal-agent-seed", "-", SEED_CREDENTIAL_PATH], `${seed}
15314
15557
  `);
15315
- chmodSync4(SEED_CREDENTIAL_PATH, 256);
15558
+ chmodSync5(SEED_CREDENTIAL_PATH, 256);
15316
15559
  chownSync(SEED_CREDENTIAL_PATH, 0, 0);
15317
15560
  if (config.agentSeedFile)
15318
15561
  unlinkSync2(config.agentSeedFile);
@@ -15361,7 +15604,7 @@ async function applyMetalBootstrap(config, options) {
15361
15604
  initializedAt: new Date().toISOString(),
15362
15605
  role: "metal",
15363
15606
  metalHostname: config.metalHostname,
15364
- profileSha256: createHash4("sha256").update(JSON.stringify(config.profile)).digest("hex")
15607
+ profileSha256: createHash5("sha256").update(JSON.stringify(config.profile)).digest("hex")
15365
15608
  };
15366
15609
  atomicWrite2(STATE_PATH2, `${JSON.stringify(state, null, 2)}
15367
15610
  `, 384);
@@ -15369,7 +15612,7 @@ async function applyMetalBootstrap(config, options) {
15369
15612
  }
15370
15613
  var socketReady = (path) => {
15371
15614
  try {
15372
- return lstatSync5(path).isSocket();
15615
+ return lstatSync6(path).isSocket();
15373
15616
  } catch {
15374
15617
  return false;
15375
15618
  }
@@ -15378,17 +15621,17 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15378
15621
  const problems = [];
15379
15622
  let profileValid = false, imageVerified = false, metalHostname, profileSha256;
15380
15623
  let profileMode = null;
15381
- if (existsSync9(PROFILE_PATH)) {
15624
+ if (existsSync10(PROFILE_PATH)) {
15382
15625
  try {
15383
15626
  const metadata = statSync(PROFILE_PATH);
15384
15627
  profileMode = metadata.mode & 511;
15385
15628
  if (profileMode !== 384 || metadata.uid !== 0)
15386
15629
  problems.push("metal profile is not root-owned mode 0600");
15387
- const persisted = JSON.parse(readFileSync10(PROFILE_PATH, "utf8"));
15630
+ const persisted = JSON.parse(readFileSync11(PROFILE_PATH, "utf8"));
15388
15631
  const { metalHostname: profileHostname, hostTelemetryEndpoint, hostTelemetryUnit, ...profile } = persisted;
15389
15632
  validateMetalProfile(profile);
15390
15633
  profileValid = true;
15391
- profileSha256 = createHash4("sha256").update(JSON.stringify(profile)).digest("hex");
15634
+ profileSha256 = createHash5("sha256").update(JSON.stringify(profile)).digest("hex");
15392
15635
  if (!/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(profileHostname) || hostTelemetryEndpoint !== "http://127.0.0.1:4318") {
15393
15636
  problems.push("persisted metal host coordinates are invalid");
15394
15637
  }
@@ -15417,7 +15660,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15417
15660
  problems.push("local OTLP metrics receiver did not accept a proof request");
15418
15661
  }
15419
15662
  const image = profile.images[Object.keys(profile.images)[0]];
15420
- if (existsSync9(image.path)) {
15663
+ if (existsSync10(image.path)) {
15421
15664
  const digest = (await exec(["/usr/bin/sha256sum", image.path])).stdout.split(/\s+/)[0];
15422
15665
  imageVerified = digest === image.sha256;
15423
15666
  }
@@ -15428,9 +15671,9 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15428
15671
  }
15429
15672
  } else
15430
15673
  problems.push("metal profile is missing");
15431
- if (existsSync9(STATE_PATH2)) {
15674
+ if (existsSync10(STATE_PATH2)) {
15432
15675
  try {
15433
- const state = JSON.parse(readFileSync10(STATE_PATH2, "utf8"));
15676
+ const state = JSON.parse(readFileSync11(STATE_PATH2, "utf8"));
15434
15677
  metalHostname = state.metalHostname;
15435
15678
  if (state.role !== "metal" || !metalHostname || state.profileSha256 !== profileSha256) {
15436
15679
  problems.push("metal initialized state does not bind the current profile");
@@ -15446,7 +15689,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15446
15689
  "forgezero-metal-agent-egress.service",
15447
15690
  "forgezero-metal-agent.service"
15448
15691
  ]) {
15449
- if (!existsSync9(join9(UNIT_DIRECTORY, unit)))
15692
+ if (!existsSync10(join9(UNIT_DIRECTORY, unit)))
15450
15693
  units[unit] = "missing";
15451
15694
  else
15452
15695
  units[unit] = (await exec(["/usr/bin/systemctl", "is-active", "--quiet", unit])).exitCode === 0 ? "active" : "inactive";
@@ -15460,7 +15703,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15460
15703
  if (!updateSocketReady)
15461
15704
  problems.push("Agent update helper socket is not ready");
15462
15705
  return {
15463
- initialized: existsSync9(STATE_PATH2),
15706
+ initialized: existsSync10(STATE_PATH2),
15464
15707
  profileValid,
15465
15708
  profileMode,
15466
15709
  imageVerified,
@@ -15473,11 +15716,11 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15473
15716
  }
15474
15717
 
15475
15718
  // src/operator-bootstrap.ts
15476
- import { createHash as createHash5, randomBytes as randomBytes8 } from "crypto";
15477
- import { lstatSync as lstatSync6, mkdtempSync, readFileSync as readFileSync11, rmSync as rmSync6, writeFileSync as writeFileSync11 } from "fs";
15719
+ import { createHash as createHash6, randomBytes as randomBytes9 } from "crypto";
15720
+ import { chmodSync as chmodSync6, lstatSync as lstatSync7, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
15478
15721
  import { isIP as isIP6 } from "net";
15479
15722
  import { tmpdir } from "os";
15480
- 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";
15481
15724
  import { fileURLToPath as fileURLToPath2 } from "url";
15482
15725
 
15483
15726
  // src/platform-genesis.ts
@@ -15612,25 +15855,44 @@ var exactKeys5 = (value, keys, label) => {
15612
15855
  throw new Error(`${label} contains unknown fields: ${unknown.join(", ")}`);
15613
15856
  return record3;
15614
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
+ };
15615
15877
  var ownerFile = (path, limit, label) => {
15616
15878
  if (!path.startsWith("/") || /[\r\n]/.test(path))
15617
15879
  throw new Error(`${label} path must be absolute`);
15618
- const metadata = lstatSync6(path);
15880
+ const metadata = lstatSync7(path);
15619
15881
  const uid = process.getuid?.() ?? metadata.uid;
15620
15882
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.uid !== uid || metadata.nlink !== 1 || (metadata.mode & 63) !== 0 || metadata.size < 1 || metadata.size > limit) {
15621
15883
  throw new Error(`${label} must be an owner-only regular file with one link and at most ${limit} bytes`);
15622
15884
  }
15623
- return readFileSync11(path);
15885
+ return readFileSync12(path);
15624
15886
  };
15625
15887
  var publicIdentity = (path) => {
15626
15888
  if (!path.startsWith("/") || /[\r\n]/.test(path))
15627
15889
  throw new Error("SSH public-key path must be absolute");
15628
- const metadata = lstatSync6(path);
15890
+ const metadata = lstatSync7(path);
15629
15891
  const uid = process.getuid?.() ?? metadata.uid;
15630
15892
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.uid !== uid || metadata.nlink !== 1 || metadata.size > 16384) {
15631
15893
  throw new Error("SSH public key must be a caller-owned regular file with one link");
15632
15894
  }
15633
- const value = readFileSync11(path, "utf8").trim();
15895
+ const value = readFileSync12(path, "utf8").trim();
15634
15896
  if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(value)) {
15635
15897
  throw new Error("SSH public key is malformed");
15636
15898
  }
@@ -15639,7 +15901,7 @@ var publicIdentity = (path) => {
15639
15901
  var socketPath = (path) => {
15640
15902
  if (!path.startsWith("/") || /[\r\n]/.test(path))
15641
15903
  throw new Error("SSH agent socket path must be absolute");
15642
- const metadata = lstatSync6(path);
15904
+ const metadata = lstatSync7(path);
15643
15905
  const uid = process.getuid?.() ?? metadata.uid;
15644
15906
  if (!metadata.isSocket() || metadata.isSymbolicLink() || metadata.uid !== uid) {
15645
15907
  throw new Error("SSH agent socket must be a caller-owned Unix socket");
@@ -15653,7 +15915,7 @@ var fingerprint = (key) => {
15653
15915
  if (!bytes.length || bytes.toString("base64").replace(/=+$/, "") !== encoded.replace(/=+$/, "")) {
15654
15916
  throw new Error("SSH host key is malformed");
15655
15917
  }
15656
- return `SHA256:${createHash5("sha256").update(bytes).digest("base64").replace(/=+$/, "")}`;
15918
+ return `SHA256:${createHash6("sha256").update(bytes).digest("base64").replace(/=+$/, "")}`;
15657
15919
  };
15658
15920
  var validateHop = (value, label) => {
15659
15921
  const hop = exactKeys5(value, ["address", "port", "user", "hostKey", "hostKeySha256"], label);
@@ -15670,8 +15932,10 @@ var validateHop = (value, label) => {
15670
15932
  }
15671
15933
  return hop;
15672
15934
  };
15673
- function readOperatorPlatformBootstrapRequest(path) {
15674
- 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) => {
15675
15939
  const root = exactKeys5(value, ["kind", "target", "platformConfigFile"], "operator bootstrap request");
15676
15940
  if (root.kind !== "platform-remote")
15677
15941
  throw new Error("operator bootstrap kind must be platform-remote");
@@ -15690,6 +15954,7 @@ function readOperatorPlatformBootstrapRequest(path) {
15690
15954
  socketPath(targetValue.agentSocket);
15691
15955
  if (typeof root.platformConfigFile !== "string")
15692
15956
  throw new Error("platformConfigFile must be an absolute path");
15957
+ canonicalOutputPath(root.platformConfigFile, "platformConfigFile");
15693
15958
  const config = readBootstrapConfig(root.platformConfigFile);
15694
15959
  if (config.kind !== "platform")
15695
15960
  throw new Error("operator bootstrap requires a platform config");
@@ -15703,9 +15968,39 @@ function readOperatorPlatformBootstrapRequest(path) {
15703
15968
  ...targetValue.jump === undefined ? {} : { jump: validateHop(targetValue.jump, "operator bootstrap jump") }
15704
15969
  }
15705
15970
  };
15706
- }
15707
- function readOperatorMetalBootstrapRequest(path) {
15708
- 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) => {
15709
16004
  const root = exactKeys5(value, ["kind", "target", "metalConfigFile", "genesis"], "operator metal request");
15710
16005
  if (root.kind !== "metal-remote")
15711
16006
  throw new Error("operator metal bootstrap kind must be metal-remote");
@@ -15724,6 +16019,7 @@ function readOperatorMetalBootstrapRequest(path) {
15724
16019
  socketPath(targetValue.agentSocket);
15725
16020
  if (typeof root.metalConfigFile !== "string")
15726
16021
  throw new Error("metalConfigFile must be an absolute path");
16022
+ canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
15727
16023
  readMetalBootstrapConfig(root.metalConfigFile);
15728
16024
  const genesis = exactKeys5(root.genesis, ["environment", "sshPublicKeyFiles"], "operator metal genesis");
15729
16025
  if (typeof genesis.environment !== "string" || !PLATFORM_GENESIS_ENVIRONMENTS.includes(genesis.environment)) {
@@ -15747,6 +16043,14 @@ function readOperatorMetalBootstrapRequest(path) {
15747
16043
  sshPublicKeyFiles: genesis.sshPublicKeyFiles
15748
16044
  }
15749
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");
15750
16054
  }
15751
16055
  function planOperatorMetalBootstrap(request, mode) {
15752
16056
  const config = readMetalBootstrapConfig(request.metalConfigFile);
@@ -15798,13 +16102,95 @@ function planOperatorPlatformBootstrap(request, mode) {
15798
16102
  steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : [
15799
16103
  "verify pinned SSH transport and caller-approved SSH agent",
15800
16104
  "copy pinned Bun and packaged fz artifacts to a private staging directory",
16105
+ "verify and copy the one immutable API Git bundle plus its manifest",
15801
16106
  "stage the rewritten owner-only platform config and its named credential handoffs",
15802
- `run typed fz bootstrap platform${mode === "prepare" ? " prepare" : ""} --apply`,
16107
+ "run typed fz bootstrap platform --apply from the local bundle",
15803
16108
  "remove transient local and remote staging data"
15804
16109
  ],
15805
16110
  secretInputs: mode === "apply" ? [...attendedSecretNames(config), ...secretSources(config).map(([name]) => name)] : []
15806
16111
  };
15807
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
+ }
15808
16194
  var defaultExec3 = async (argv2, options = {}) => {
15809
16195
  const child = Bun.spawn([...argv2], {
15810
16196
  stdin: options.stdin === undefined ? "ignore" : "pipe",
@@ -15828,7 +16214,7 @@ var defaultExec3 = async (argv2, options = {}) => {
15828
16214
  ]);
15829
16215
  return { exitCode, output: options.secret ? "" : `${stdout}${stderr}`.slice(0, 65536) };
15830
16216
  };
15831
- var checked4 = async (exec, argv2, label, options) => {
16217
+ var checked5 = async (exec, argv2, label, options) => {
15832
16218
  const result = await exec(argv2, options);
15833
16219
  if (result.exitCode !== 0)
15834
16220
  throw new Error(`${label} failed${result.output ? `: ${result.output.trim()}` : ""}`);
@@ -15841,7 +16227,7 @@ function writeKnownHosts(request, directory) {
15841
16227
  if (request.target.jump)
15842
16228
  lines.push(`${hostLabel(request.target.jump.address, request.target.jump.port)} ${request.target.jump.hostKey}`);
15843
16229
  const path = join10(directory, "known_hosts");
15844
- writeFileSync11(path, `${lines.join(`
16230
+ writeFileSync12(path, `${lines.join(`
15845
16231
  `)}
15846
16232
  `, { mode: 384, flag: "wx" });
15847
16233
  return path;
@@ -15882,7 +16268,7 @@ var safeRemoteArg = (value) => {
15882
16268
  };
15883
16269
  var remote = async (exec, request, knownHosts, argv2, label, secret = false, stdin) => {
15884
16270
  argv2.forEach(safeRemoteArg);
15885
- return checked4(exec, ["ssh", ...sshOptions(request, knownHosts), destination2(request.target), "--", ...argv2], label, { secret, stdin });
16271
+ return checked5(exec, ["ssh", ...sshOptions(request, knownHosts), destination2(request.target), "--", ...argv2], label, { secret, stdin });
15886
16272
  };
15887
16273
  var remoteRegularFileExists = async (exec, request, knownHosts, path) => {
15888
16274
  safeRemoteArg(path);
@@ -15903,36 +16289,45 @@ var remoteRegularFileExists = async (exec, request, knownHosts, path) => {
15903
16289
  };
15904
16290
  var copy = async (exec, request, knownHosts, local, remotePath, secret = false) => {
15905
16291
  safeRemoteArg(remotePath);
15906
- await checked4(exec, [
16292
+ await checked5(exec, [
15907
16293
  "scp",
15908
16294
  ...sshOptions(request, knownHosts, true),
15909
16295
  local,
15910
16296
  `${destination2(request.target)}:${remotePath}`
15911
16297
  ], "secure copy", { secret });
15912
16298
  };
15913
- function stageConfig(config, directory) {
16299
+ async function stageConfig(config, directory) {
15914
16300
  const rewritten = structuredClone(config);
15915
16301
  const staged = [];
15916
16302
  for (const [name, source] of secretSources(config)) {
15917
16303
  const bytes = ownerFile(source, SECRET_LIMIT, name);
15918
16304
  const local = join10(directory, name);
15919
- writeFileSync11(local, bytes, { mode: 384, flag: "wx" });
16305
+ writeFileSync12(local, bytes, { mode: 384, flag: "wx" });
15920
16306
  staged.push(name);
15921
16307
  const remotePath = `${REMOTE_STAGE}/${name}`;
15922
16308
  if (name === "cloudflare-handoff")
15923
16309
  rewritten.cloudflareHandoff.handoffFile = remotePath;
15924
16310
  }
16311
+ const bundle = await readBootstrapBundle(config.bootstrapBundle.bundleFile, config.bootstrapBundle.manifestFile);
16312
+ const bundleFiles = [
16313
+ { source: bundle.bundlePath, name: "bootstrap-api.bundle" },
16314
+ { source: bundle.manifestPath, name: "bootstrap-api.bundle.json" }
16315
+ ];
16316
+ rewritten.bootstrapBundle = {
16317
+ bundleFile: `${REMOTE_STAGE}/bootstrap-api.bundle`,
16318
+ manifestFile: `${REMOTE_STAGE}/bootstrap-api.bundle.json`
16319
+ };
15925
16320
  const path = join10(directory, "platform-config.json");
15926
- writeFileSync11(path, `${JSON.stringify(rewritten, null, 2)}
16321
+ writeFileSync12(path, `${JSON.stringify(rewritten, null, 2)}
15927
16322
  `, { mode: 384, flag: "wx" });
15928
- return { path, files: staged };
16323
+ return { path, files: staged, bundleFiles };
15929
16324
  }
15930
16325
  function stageMetalConfig(config, directory) {
15931
16326
  const rewritten = structuredClone(config);
15932
16327
  const files = [];
15933
16328
  if (config.agentSeedFile) {
15934
16329
  const name = "metal-agent-seed";
15935
- writeFileSync11(join10(directory, name), ownerFile(config.agentSeedFile, SECRET_LIMIT, name), {
16330
+ writeFileSync12(join10(directory, name), ownerFile(config.agentSeedFile, SECRET_LIMIT, name), {
15936
16331
  mode: 384,
15937
16332
  flag: "wx"
15938
16333
  });
@@ -15940,7 +16335,7 @@ function stageMetalConfig(config, directory) {
15940
16335
  files.push(name);
15941
16336
  }
15942
16337
  const path = join10(directory, "metal-config.json");
15943
- writeFileSync11(path, `${JSON.stringify(rewritten, null, 2)}
16338
+ writeFileSync12(path, `${JSON.stringify(rewritten, null, 2)}
15944
16339
  `, { mode: 384, flag: "wx" });
15945
16340
  return { path, files };
15946
16341
  }
@@ -15949,10 +16344,10 @@ async function verifiedBunArchive(directory, fetcher) {
15949
16344
  if (!response.ok)
15950
16345
  throw new Error("pinned Bun download failed");
15951
16346
  const bytes = new Uint8Array(await response.arrayBuffer());
15952
- if (createHash5("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256)
16347
+ if (createHash6("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256)
15953
16348
  throw new Error("pinned Bun checksum mismatch");
15954
16349
  const path = join10(directory, "bun.zip");
15955
- writeFileSync11(path, bytes, { mode: 384, flag: "wx" });
16350
+ writeFileSync12(path, bytes, { mode: 384, flag: "wx" });
15956
16351
  return path;
15957
16352
  }
15958
16353
  async function installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options) {
@@ -15962,7 +16357,7 @@ async function installPackagedAgent(request, knownHosts, exec, directory, remote
15962
16357
  [options.fzGitSshPath ?? fileURLToPath2(new URL("./fz-git-ssh.js", import.meta.url)), "fz-git-ssh.js"]
15963
16358
  ];
15964
16359
  for (const [artifact] of artifacts) {
15965
- if (!readFileSync11(artifact).length)
16360
+ if (!readFileSync12(artifact).length)
15966
16361
  throw new Error(`packaged artifact is empty: ${basename2(artifact)}`);
15967
16362
  }
15968
16363
  await remote(exec, request, knownHosts, ["/usr/bin/mkdir", "-m", "0700", remoteTemp], "remote staging");
@@ -16003,7 +16398,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
16003
16398
  socketPath(request.target.agentSocket);
16004
16399
  const exec = options.exec ?? defaultExec3;
16005
16400
  const directory = mkdtempSync(join10(tmpdir(), "forgezero-operator-bootstrap-"));
16006
- const remoteTemp = `/tmp/forgezero-operator-${randomBytes8(12).toString("hex")}`;
16401
+ const remoteTemp = `/tmp/forgezero-operator-${randomBytes9(12).toString("hex")}`;
16007
16402
  let knownHosts = "";
16008
16403
  try {
16009
16404
  knownHosts = writeKnownHosts(request, directory);
@@ -16015,18 +16410,16 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
16015
16410
  if (config.kind !== "platform")
16016
16411
  throw new Error("operator bootstrap requires a platform config");
16017
16412
  const secrets = mode === "apply" ? validatePlatformBootstrapSecrets(config, options.secrets) : undefined;
16018
- const staged = stageConfig(config, directory);
16413
+ const staged = await stageConfig(config, directory);
16019
16414
  await installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options);
16020
16415
  await copy(exec, request, knownHosts, staged.path, `${remoteTemp}/platform-config.json`, true);
16021
16416
  for (const name of staged.files)
16022
16417
  await copy(exec, request, knownHosts, join10(directory, name), `${remoteTemp}/${name}`, true);
16023
- for (const name of ["platform-config.json", ...staged.files])
16418
+ for (const bundle of staged.bundleFiles)
16419
+ await copy(exec, request, knownHosts, bundle.source, `${remoteTemp}/${bundle.name}`, true);
16420
+ for (const name of ["platform-config.json", ...staged.files, ...staged.bundleFiles.map(({ name: name2 }) => name2)])
16024
16421
  await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/bin/install", "-m", "0600", `${remoteTemp}/${name}`, `${REMOTE_STAGE}/${name}`], "remote bootstrap handoff", true);
16025
- const command = ["/usr/bin/sudo", "-n", "/usr/local/bin/fz", "bootstrap", "platform"];
16026
- if (mode === "prepare")
16027
- command.push("prepare");
16028
- else
16029
- command.push("credentials-stdin");
16422
+ const command = ["/usr/bin/sudo", "-n", "/usr/local/bin/fz", "bootstrap", "platform", "credentials-stdin"];
16030
16423
  command.push("--bootstrap-config", `${REMOTE_STAGE}/platform-config.json`, "--apply");
16031
16424
  const output = await remote(exec, request, knownHosts, command, "remote typed bootstrap", mode === "apply", secrets ? `${JSON.stringify(secrets)}
16032
16425
  ` : undefined);
@@ -16037,16 +16430,40 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
16037
16430
  return;
16038
16431
  });
16039
16432
  }
16040
- rmSync6(directory, { recursive: true, force: true });
16433
+ rmSync7(directory, { recursive: true, force: true });
16041
16434
  }
16042
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
+ }
16043
16460
  async function applyOperatorMetalBootstrap(request, mode, options = {}) {
16044
16461
  const plan = planOperatorMetalBootstrap(request, mode);
16045
16462
  publicIdentity(request.target.identityPublicKeyFile);
16046
16463
  socketPath(request.target.agentSocket);
16047
16464
  const exec = options.exec ?? defaultExec3;
16048
16465
  const directory = mkdtempSync(join10(tmpdir(), "forgezero-operator-metal-"));
16049
- const remoteTemp = `/tmp/forgezero-operator-${randomBytes8(12).toString("hex")}`;
16466
+ const remoteTemp = `/tmp/forgezero-operator-${randomBytes9(12).toString("hex")}`;
16050
16467
  let knownHosts = "";
16051
16468
  try {
16052
16469
  knownHosts = writeKnownHosts(request, directory);
@@ -16090,7 +16507,7 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
16090
16507
  return;
16091
16508
  });
16092
16509
  }
16093
- rmSync6(directory, { recursive: true, force: true });
16510
+ rmSync7(directory, { recursive: true, force: true });
16094
16511
  }
16095
16512
  }
16096
16513
 
@@ -16103,9 +16520,6 @@ function platformGenesisBootstrapConfigs(template, nodes) {
16103
16520
  if (nodes.some((node) => !node.nodeHostname || !node.cloudflareHandoffFile)) {
16104
16521
  throw new Error("every platform genesis node requires its public hostname and node-specific Cloudflare handoff");
16105
16522
  }
16106
- if (template.branch !== (environment === "production" ? "main" : "dev")) {
16107
- throw new Error("platform genesis branch must match the selected environment");
16108
- }
16109
16523
  const coordinators = fleet.map((guest) => `http://${guest.address}:8529`);
16110
16524
  return fleet.map((guest, index) => {
16111
16525
  const node = nodes[index];
@@ -16144,7 +16558,7 @@ function platformGenesisBootstrapConfigs(template, nodes) {
16144
16558
  }
16145
16559
 
16146
16560
  // src/host-maintenance.ts
16147
- import { lstatSync as lstatSync7, readFileSync as readFileSync12 } from "fs";
16561
+ import { lstatSync as lstatSync8, readFileSync as readFileSync13 } from "fs";
16148
16562
  var ROOT = "/opt/forgezero";
16149
16563
  var SLOT_FILE = `${ROOT}/.forge-slot`;
16150
16564
  var SHARED_ENV = `${ROOT}/shared/.env`;
@@ -16152,9 +16566,9 @@ var JWT = "/etc/forgezero/creds/arangodb-jwt.cred";
16152
16566
  var FZ = "/usr/local/bin/fz";
16153
16567
  var localRuntime = () => ({
16154
16568
  uid: () => process.getuid?.() ?? -1,
16155
- read: (path) => readFileSync12(path, "utf8"),
16569
+ read: (path) => readFileSync13(path, "utf8"),
16156
16570
  inspect(path) {
16157
- const value = lstatSync7(path);
16571
+ const value = lstatSync8(path);
16158
16572
  return {
16159
16573
  regular: value.isFile(),
16160
16574
  symbolic: value.isSymbolicLink(),
@@ -16275,8 +16689,8 @@ async function applyHostMaintenance(request, runtime = localRuntime()) {
16275
16689
  }
16276
16690
 
16277
16691
  // src/cli/maintenance.ts
16278
- import { existsSync as existsSync10, lstatSync as lstatSync8, readFileSync as readFileSync13, realpathSync as realpathSync5 } from "fs";
16279
- import { isAbsolute as isAbsolute4, join as join11, relative as relative2, resolve as resolve8 } from "path";
16692
+ import { existsSync as existsSync11, lstatSync as lstatSync9, readFileSync as readFileSync14, realpathSync as realpathSync5 } from "fs";
16693
+ import { isAbsolute as isAbsolute6, join as join11, relative as relative2, resolve as resolve10 } from "path";
16280
16694
  var API_OPERATION_ENTRYPOINTS = {
16281
16695
  "dev-reset": ["src", "server", "maintenance", "dev-reset.ts"],
16282
16696
  "db-backup": ["src", "server", "maintenance", "snapshot-backup.ts"],
@@ -16346,12 +16760,12 @@ function unsupportedRepositoryCliOption(argv2) {
16346
16760
  }
16347
16761
  function manifestName(root) {
16348
16762
  const manifestPath = join11(root, "package.json");
16349
- if (!existsSync10(manifestPath)) {
16763
+ if (!existsSync11(manifestPath)) {
16350
16764
  throw new Error(`No package.json exists at repository root ${root}.`);
16351
16765
  }
16352
16766
  let parsed;
16353
16767
  try {
16354
- parsed = JSON.parse(readFileSync13(manifestPath, "utf8"));
16768
+ parsed = JSON.parse(readFileSync14(manifestPath, "utf8"));
16355
16769
  } catch (cause) {
16356
16770
  throw new Error(`Cannot read ${manifestPath}: ${cause instanceof Error ? cause.message : String(cause)}`);
16357
16771
  }
@@ -16362,21 +16776,21 @@ function manifestName(root) {
16362
16776
  }
16363
16777
  function checkedEntrypoint(root, parts) {
16364
16778
  const candidate = join11(root, ...parts);
16365
- if (!existsSync10(candidate) || !lstatSync8(candidate).isFile()) {
16779
+ if (!existsSync11(candidate) || !lstatSync9(candidate).isFile()) {
16366
16780
  throw new Error(`The reviewed operation entrypoint is missing: ${candidate}`);
16367
16781
  }
16368
- if (lstatSync8(candidate).isSymbolicLink()) {
16782
+ if (lstatSync9(candidate).isSymbolicLink()) {
16369
16783
  throw new Error(`The reviewed operation entrypoint must not be a symbolic link: ${candidate}`);
16370
16784
  }
16371
16785
  const actual = realpathSync5(candidate);
16372
16786
  const within = relative2(root, actual);
16373
- if (within.startsWith("..") || isAbsolute4(within)) {
16787
+ if (within.startsWith("..") || isAbsolute6(within)) {
16374
16788
  throw new Error(`The reviewed operation entrypoint escapes repository root ${root}.`);
16375
16789
  }
16376
16790
  return actual;
16377
16791
  }
16378
16792
  function resolveRepositoryOperation(operation, requestedRoot) {
16379
- const root = realpathSync5(resolve8(requestedRoot));
16793
+ const root = realpathSync5(resolve10(requestedRoot));
16380
16794
  const name = manifestName(root);
16381
16795
  if (operation in API_OPERATION_ENTRYPOINTS) {
16382
16796
  const parts = API_OPERATION_ENTRYPOINTS[operation];
@@ -16677,7 +17091,7 @@ async function api(options, path, init) {
16677
17091
  return { status: response.status, body };
16678
17092
  }
16679
17093
  function sleep(ms) {
16680
- return new Promise((resolve10) => setTimeout(resolve10, ms));
17094
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
16681
17095
  }
16682
17096
  function browserCommand(url) {
16683
17097
  if (process.platform === "darwin")
@@ -16762,7 +17176,7 @@ function requireSession() {
16762
17176
  function readOwnerOnlySecret(path, label) {
16763
17177
  let metadata;
16764
17178
  try {
16765
- metadata = lstatSync9(path);
17179
+ metadata = lstatSync10(path);
16766
17180
  } catch {
16767
17181
  throw new Error(`The ${label} file ${path} is unreadable.`);
16768
17182
  }
@@ -16776,7 +17190,7 @@ function readOwnerOnlySecret(path, label) {
16776
17190
  if (uid !== undefined && uid !== 0 && metadata.uid !== uid) {
16777
17191
  throw new Error(`The ${label} file ${path} is not owned by the current user.`);
16778
17192
  }
16779
- const value = readFileSync14(path, "utf8").trim();
17193
+ const value = readFileSync15(path, "utf8").trim();
16780
17194
  if (!value)
16781
17195
  throw new Error(`The ${label} file ${path} is empty.`);
16782
17196
  return value;
@@ -16957,7 +17371,7 @@ async function cmdApi(options, args) {
16957
17371
  let body = undefined;
16958
17372
  if (options.data !== undefined && options.dataFile)
16959
17373
  throw new Error("Use only one of --data or --data-file.");
16960
- const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync14(options.dataFile, "utf8") : options.data;
17374
+ const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync15(options.dataFile, "utf8") : options.data;
16961
17375
  if (encoded !== undefined)
16962
17376
  body = JSON.parse(encoded);
16963
17377
  const result = await api(options, `${url.pathname}${url.search}`, { method, body });
@@ -17051,7 +17465,7 @@ async function cmdAgent(options, args) {
17051
17465
  seedCredentialPath: process.env.FZ_SEED_CREDENTIAL_PATH,
17052
17466
  gitCredentialPath,
17053
17467
  gitPublicKeyPath,
17054
- generateGitIdentity: true,
17468
+ generateGitIdentity: process.env.FZ_GENERATE_GIT_DEPLOY_KEY === "true",
17055
17469
  controlSocketPath: process.env.FZ_CONTROL_SOCKET,
17056
17470
  repository: process.env.FZ_DEPLOY_REPO,
17057
17471
  branch: process.env.FZ_DEPLOY_BRANCH,
@@ -17108,19 +17522,19 @@ async function cmdAgent(options, args) {
17108
17522
  return 0;
17109
17523
  }
17110
17524
  try {
17111
- writeFileSync12(plan.unitPath, plan.unit, { mode: 420 });
17525
+ writeFileSync13(plan.unitPath, plan.unit, { mode: 420 });
17112
17526
  out.ok(`Wrote ${plan.unitPath}`);
17113
17527
  for (const auxiliary of plan.auxiliaryUnits) {
17114
- mkdirSync11(dirname12(auxiliary.path), { recursive: true, mode: 493 });
17115
- writeFileSync12(auxiliary.path, auxiliary.unit, { mode: 420 });
17528
+ mkdirSync13(dirname14(auxiliary.path), { recursive: true, mode: 493 });
17529
+ writeFileSync13(auxiliary.path, auxiliary.unit, { mode: 420 });
17116
17530
  out.ok(`Wrote ${auxiliary.path}`);
17117
17531
  }
17118
17532
  if (options.enrol) {
17119
- if (!existsSync11(enrolStatePath) && !existsSync11(enrolTokenCredentialPath)) {
17533
+ if (!existsSync12(enrolStatePath) && !existsSync12(enrolTokenCredentialPath)) {
17120
17534
  const token = await bootstrapSecret("ForgeZero one-time enrolment token");
17121
17535
  if (!/^fze_[A-Za-z0-9_-]{40,100}$/.test(token))
17122
17536
  throw new Error("A valid fze_ enrolment token was not provided.");
17123
- mkdirSync11(dirname12(enrolTokenCredentialPath), { recursive: true, mode: 448 });
17537
+ mkdirSync13(dirname14(enrolTokenCredentialPath), { recursive: true, mode: 448 });
17124
17538
  const sealing = Bun.spawn(["systemd-creds", "encrypt", "--name=enrol-token", "-", enrolTokenCredentialPath], { stdin: "pipe", stdout: "ignore", stderr: "pipe" });
17125
17539
  if (sealing.stdin && typeof sealing.stdin !== "number") {
17126
17540
  sealing.stdin.write(`${token}
@@ -17136,11 +17550,13 @@ async function cmdAgent(options, args) {
17136
17550
  for (const step3 of transcript)
17137
17551
  out.ok(step3.label);
17138
17552
  out.ok("Agent service and socket verified");
17139
- out.line();
17140
- out.line(" Add this machine-specific PUBLIC key as a read-only deploy key:");
17141
- out.line();
17142
- out.line(` ${readFileSync14(gitPublicKeyPath, "utf8").trim()}`);
17143
- out.line();
17553
+ if (existsSync12(gitPublicKeyPath)) {
17554
+ out.line();
17555
+ out.line(" Compatibility SSH deploy public key:");
17556
+ out.line();
17557
+ out.line(` ${readFileSync15(gitPublicKeyPath, "utf8").trim()}`);
17558
+ out.line();
17559
+ }
17144
17560
  return 0;
17145
17561
  } catch (cause) {
17146
17562
  out.fail(`Could not write ${plan.unitPath}: ${cause.message}`);
@@ -17244,10 +17660,10 @@ function interactiveMetalBootstrap() {
17244
17660
  });
17245
17661
  }
17246
17662
  function writeBootstrapConfig(path, config) {
17247
- if (!isAbsolute5(path) || resolve9(path) !== path)
17663
+ if (!isAbsolute7(path) || resolve11(path) !== path)
17248
17664
  throw new Error("--output must be a canonical absolute path");
17249
- mkdirSync11(dirname12(path), { recursive: true, mode: 448 });
17250
- writeFileSync12(path, `${JSON.stringify(config, null, 2)}
17665
+ mkdirSync13(dirname14(path), { recursive: true, mode: 448 });
17666
+ writeFileSync13(path, `${JSON.stringify(config, null, 2)}
17251
17667
  `, { mode: 384, flag: "wx" });
17252
17668
  return path;
17253
17669
  }
@@ -17308,17 +17724,48 @@ async function interactiveCloudflareBootstrap() {
17308
17724
  });
17309
17725
  }
17310
17726
  function genesisOutputDirectory(path) {
17311
- if (!isAbsolute5(path) || resolve9(path) !== path)
17727
+ if (!isAbsolute7(path) || resolve11(path) !== path)
17312
17728
  throw new Error("--output must be a canonical absolute directory");
17313
- if (!existsSync11(path))
17314
- mkdirSync11(path, { recursive: true, mode: 448 });
17315
- const metadata = lstatSync9(path);
17729
+ if (!existsSync12(path))
17730
+ mkdirSync13(path, { recursive: true, mode: 448 });
17731
+ const metadata = lstatSync10(path);
17316
17732
  const uid = process.getuid?.();
17317
17733
  if (!metadata.isDirectory() || metadata.isSymbolicLink() || (metadata.mode & 63) !== 0 || uid !== undefined && uid !== 0 && metadata.uid !== uid) {
17318
17734
  throw new Error("platform genesis output must be an owner-only directory owned by the current user");
17319
17735
  }
17320
17736
  return path;
17321
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
+ }
17322
17769
  function writePlatformGenesisFleet(directory) {
17323
17770
  const output = genesisOutputDirectory(directory);
17324
17771
  const template = interactiveBootstrap("platform", true);
@@ -17335,7 +17782,20 @@ function writePlatformGenesisFleet(directory) {
17335
17782
  writeBootstrapConfig(path, config);
17336
17783
  return path;
17337
17784
  });
17338
- 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 };
17339
17799
  }
17340
17800
  function interactiveBootstrap(kind, genesis = false) {
17341
17801
  if (!process.stdin.isTTY)
@@ -17356,8 +17816,8 @@ function interactiveBootstrap(kind, genesis = false) {
17356
17816
  const nodeHostname = bootstrapAnswer(genesis ? `${computeReference} public node hostname` : "Public node hostname");
17357
17817
  const apiUrl = bootstrapAnswer("Platform API URL");
17358
17818
  const appOrigin = bootstrapAnswer("Public App origin");
17359
- const repository = bootstrapAnswer("API Git repository");
17360
- const branch = bootstrapAnswer("Deployment branch", environment === "production" ? "main" : "dev");
17819
+ const bundleFile = bootstrapAnswer("Bootstrap API Git bundle (absolute path)");
17820
+ const manifestFile = bootstrapAnswer("Bootstrap bundle manifest (absolute path)", `${bundleFile}.json`);
17361
17821
  const telemetryEndpoint = bootstrapAnswer("Public HTTPS Agent OTLP endpoint");
17362
17822
  const collectorUnit = FORGEZERO_OTEL_COLLECTOR_UNIT;
17363
17823
  const replicationFactor = Number(bootstrapAnswer("Database replication factor", "3"));
@@ -17386,8 +17846,7 @@ function interactiveBootstrap(kind, genesis = false) {
17386
17846
  computeReference,
17387
17847
  nodeHostname,
17388
17848
  apiUrl,
17389
- repository,
17390
- branch,
17849
+ bootstrapBundle: { bundleFile, manifestFile },
17391
17850
  telemetryEndpoint,
17392
17851
  database: {
17393
17852
  role,
@@ -17431,8 +17890,6 @@ function interactiveBootstrap(kind, genesis = false) {
17431
17890
  ...backupEndpoint && backupRegion && backupBucket && backupAccessKeyId ? {
17432
17891
  backup: { endpoint: backupEndpoint, region: backupRegion, bucket: backupBucket, accessKeyId: backupAccessKeyId }
17433
17892
  } : {},
17434
- repository,
17435
- branch,
17436
17893
  deployProfile: environment
17437
17894
  },
17438
17895
  serviceUser: "forgezero-api",
@@ -17460,7 +17917,7 @@ async function cmdBootstrap(options, args) {
17460
17917
  if (operation === "config") {
17461
17918
  const kind = args[1];
17462
17919
  if (kind !== "metal" && kind !== "platform" && kind !== "cloudflare" || args[2] !== undefined || !options.outputPath) {
17463
- 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>");
17464
17921
  }
17465
17922
  if (kind === "platform") {
17466
17923
  out.line(JSON.stringify({ kind, ...writePlatformGenesisFleet(options.outputPath), mode: "0600" }, null, 2));
@@ -17472,14 +17929,36 @@ async function cmdBootstrap(options, args) {
17472
17929
  out.line(JSON.stringify({ kind, path, mode: "0600" }, null, 2));
17473
17930
  return 0;
17474
17931
  }
17475
- const config2 = interactiveMetalBootstrap();
17476
- 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));
17477
17933
  return 0;
17478
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
+ }
17479
17958
  if (operation === "platform" && args[1] === "remote") {
17480
17959
  const mode = args[2];
17481
- if (!mode || !["prepare", "apply", "status"].includes(mode) || args[3] !== undefined) {
17482
- throw new Error("Usage: fz bootstrap platform remote <prepare|apply|status> --bootstrap-config <owner-only-request.json> [--apply]");
17960
+ if (!mode || !["apply", "status"].includes(mode) || args[3] !== undefined) {
17961
+ throw new Error("Usage: fz bootstrap platform remote <apply|status> --bootstrap-config <owner-only-request.json> [--apply]");
17483
17962
  }
17484
17963
  if (!options.bootstrapConfigPath)
17485
17964
  throw new Error("remote platform bootstrap requires --bootstrap-config");
@@ -17497,6 +17976,18 @@ async function cmdBootstrap(options, args) {
17497
17976
  out.line(JSON.stringify(await applyOperatorPlatformBootstrap(request, mode, { secrets: secrets2 }), null, 2));
17498
17977
  return 0;
17499
17978
  }
17979
+ if (operation === "platform" && args[1] === "bundle") {
17980
+ if (args[2] !== undefined || !options.outputPath) {
17981
+ throw new Error("Usage: fz bootstrap platform bundle --root <clean-api-checkout> --branch <main|dev> --output <absolute.bundle>");
17982
+ }
17983
+ const result2 = await buildBootstrapBundle({
17984
+ repositoryRoot: options.projectRoot,
17985
+ outputPath: options.outputPath,
17986
+ branch: options.branch
17987
+ });
17988
+ out.line(JSON.stringify(result2, null, 2));
17989
+ return 0;
17990
+ }
17500
17991
  if (operation === "metal" && args[1] === "remote") {
17501
17992
  const mode = args[2];
17502
17993
  if (!mode || !["apply", "genesis", "rehearsal", "rehearsal-cleanup", "status"].includes(mode) || args[3] !== undefined) {
@@ -17553,31 +18044,12 @@ async function cmdBootstrap(options, args) {
17553
18044
  await runCloudflareBootstrapCommand(options.bootstrapConfigPath, options.apply, tokens);
17554
18045
  return 0;
17555
18046
  }
17556
- if (operation === "platform" && args[1] === "prepare") {
17557
- if (args[2] !== undefined)
17558
- throw new Error("platform prepare accepts no positional arguments");
17559
- const config2 = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : interactiveBootstrap("platform");
17560
- if (config2.kind !== "platform")
17561
- throw new Error("platform prepare requires a platform bootstrap config");
17562
- if (!options.apply) {
17563
- out.line(JSON.stringify({
17564
- kind: "platform",
17565
- mode: "prepare",
17566
- mutation: false,
17567
- steps: ["bind immutable host intent", "install common Agent", "print machine public deploy key"]
17568
- }, null, 2));
17569
- out.step("Review the plan, then repeat with --apply as root. No database or API starts in prepare.");
17570
- return 0;
17571
- }
17572
- out.line(JSON.stringify(await preparePlatformBootstrap(config2), null, 2));
17573
- return 0;
17574
- }
17575
18047
  const credentialStdin = operation === "platform" && args[1] === "credentials-stdin";
17576
18048
  if (credentialStdin && args[2] !== undefined)
17577
18049
  throw new Error("platform credential stdin accepts no additional positional arguments");
17578
18050
  const installedBootstrapKind = () => resolveInstalledBootstrapKind({
17579
- metal: existsSync11(METAL_BOOTSTRAP_STATE_PATH),
17580
- compute: existsSync11(BOOTSTRAP_STATE_PATH)
18051
+ metal: existsSync12(METAL_BOOTSTRAP_STATE_PATH),
18052
+ compute: existsSync12(BOOTSTRAP_STATE_PATH)
17581
18053
  });
17582
18054
  if (operation === "status") {
17583
18055
  if (installedBootstrapKind() === "metal") {
@@ -17618,7 +18090,7 @@ async function cmdBootstrap(options, args) {
17618
18090
  return 0;
17619
18091
  }
17620
18092
  if (!["platform", "repair"].includes(operation)) {
17621
- throw new Error("Usage: fz bootstrap config <metal|platform|cloudflare>|platform [prepare|remote <prepare|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]");
17622
18094
  }
17623
18095
  const config = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : operation === "repair" ? (() => {
17624
18096
  throw new Error("repair requires --bootstrap-config so immutable coordinates are revalidated");
@@ -17715,7 +18187,7 @@ async function cmdUnlock(options) {
17715
18187
  if (options.key || options.userExplicit) {
17716
18188
  throw new Error("SSH/user unlock coordinates are not accepted. SSH keys cannot open a WebAuthn-PRF custody envelope; use --phrase-file or --phrase-stdin.");
17717
18189
  }
17718
- const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync14(0, "utf8").trim() : "";
18190
+ const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync15(0, "utf8").trim() : "";
17719
18191
  const phrase = phraseText ? phraseText.split(/\s+/) : [];
17720
18192
  if (phrase.length !== 24) {
17721
18193
  throw new Error(`Recovery phrase must contain exactly 24 words; received ${phrase.length}. Use --phrase-file or --phrase-stdin.`);
@@ -17931,7 +18403,7 @@ function projectFromCheckout(options) {
17931
18403
  try {
17932
18404
  return loadConfig({
17933
18405
  cwd: options.projectRoot,
17934
- readFile: (path) => existsSync11(path) ? readFileSync14(path, "utf8") : undefined
18406
+ readFile: (path) => existsSync12(path) ? readFileSync15(path, "utf8") : undefined
17935
18407
  }).config.project;
17936
18408
  } catch (cause) {
17937
18409
  throw new Error(`No --project was given and the checkout has no usable .fz/config.json: ${cause instanceof Error ? cause.message : String(cause)}`);
@@ -17964,10 +18436,10 @@ async function waitForRuns(options, pipelineKey, keys) {
17964
18436
  while (Date.now() < deadline) {
17965
18437
  const payload = await deploymentRequest(options, `/runs?pipelineKey=${encodeURIComponent(pipelineKey)}`);
17966
18438
  const runs = payload.runs ?? [];
17967
- const selected = runs.filter((run) => run._key && keys.includes(run._key));
17968
- if (selected.length === keys.length && selected.every((run) => terminal.has(run.outcome ?? ""))) {
17969
- out.line(JSON.stringify({ ok: selected.every((run) => run.outcome === "deployed"), runs: selected }, null, 2));
17970
- return selected.every((run) => run.outcome === "deployed");
18439
+ const selected = runs.filter((run2) => run2._key && keys.includes(run2._key));
18440
+ if (selected.length === keys.length && selected.every((run2) => terminal.has(run2.outcome ?? ""))) {
18441
+ out.line(JSON.stringify({ ok: selected.every((run2) => run2.outcome === "deployed"), runs: selected }, null, 2));
18442
+ return selected.every((run2) => run2.outcome === "deployed");
17971
18443
  }
17972
18444
  await sleep(2000);
17973
18445
  }
@@ -17994,7 +18466,7 @@ async function cmdDeploy(options, args) {
17994
18466
  out.ok(`Initialized legacy .fz/deploy.json (${created.summary.digest}).`);
17995
18467
  return 0;
17996
18468
  }
17997
- initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(resolve9(options.projectRoot)), force: options.force });
18469
+ initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(resolve11(options.projectRoot)), force: options.force });
17998
18470
  const compiled = await compileDeploymentProject(options.projectRoot);
17999
18471
  if (options.json)
18000
18472
  out.line(JSON.stringify({ source: compiled.source, output: compiled.output, digest: compiled.digest }, null, 2));
@@ -18019,7 +18491,7 @@ async function cmdDeploy(options, args) {
18019
18491
  return problems.length === 0 ? 0 : 1;
18020
18492
  }
18021
18493
  if (operation === "check" || operation === "sync") {
18022
- if (existsSync11(resolve9(options.projectRoot, DEPLOY_SOURCE_FILE))) {
18494
+ if (existsSync12(resolve11(options.projectRoot, DEPLOY_SOURCE_FILE))) {
18023
18495
  const expected = await compileDeploymentProject(options.projectRoot, { write: false });
18024
18496
  const actual = inspectCompiledDeployment(options.projectRoot);
18025
18497
  const current = canonicalJson(expected.plan) === canonicalJson(actual.plan);
@@ -18095,7 +18567,7 @@ async function cmdDeploy(options, args) {
18095
18567
  branch: options.branch,
18096
18568
  cloneUrl: required(options.cloneUrl, "--clone-url"),
18097
18569
  sourceAuth,
18098
- ...options.knownHostsFile ? { knownHosts: readFileSync14(options.knownHostsFile, "utf8") } : {},
18570
+ ...options.knownHostsFile ? { knownHosts: readFileSync15(options.knownHostsFile, "utf8") } : {},
18099
18571
  projectKey
18100
18572
  } });
18101
18573
  const pipelineKey2 = String(created.pipelineKey);
@@ -18135,7 +18607,7 @@ async function cmdDeploy(options, args) {
18135
18607
  out.line(JSON.stringify(released, null, 2));
18136
18608
  return 0;
18137
18609
  }
18138
- const keys = (released.runs ?? []).flatMap((run) => run._key ? [run._key] : []);
18610
+ const keys = (released.runs ?? []).flatMap((run2) => run2._key ? [run2._key] : []);
18139
18611
  if (keys.length === 0)
18140
18612
  throw new Error("The API accepted the release but returned no deployment runs.");
18141
18613
  return await waitForRuns(options, pipelineKey, keys) ? 0 : 1;
@@ -18191,15 +18663,18 @@ function usage() {
18191
18663
  fz agent install Install the node agent as a systemd service, so
18192
18664
  applications on this box read secrets through a
18193
18665
  local socket instead of holding an API key
18194
- fz bootstrap platform prepare
18195
- Generate the machine deploy key before private Git deployment
18196
- fz bootstrap platform remote <prepare|apply|status>
18666
+ fz bootstrap platform bundle
18667
+ Build one clean exact-revision API Git bundle for genesis
18668
+ fz bootstrap platform remote <apply|status>
18197
18669
  Run typed bootstrap from the operator laptop through
18198
- a pinned host and caller-approved SSH agent
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
18199
18674
  fz bootstrap platform Install/repair a typed elastic platform compute
18200
18675
  fz bootstrap config <metal|platform|cloudflare>
18201
- Write a validated owner-only metal/Cloudflare file or
18202
- 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
18203
18678
  fz bootstrap platform cloudflare
18204
18679
  Plan/apply attended Tunnel and DNS reconciliation
18205
18680
  using hidden management and KV/Worker runtime token prompts