@forgezero/agent 0.1.61 → 0.1.63

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 mkdirSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
4815
+ import { basename as basename3, dirname as dirname13, isAbsolute as isAbsolute6, resolve as resolve10 } from "path";
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.61";
4832
+ var VERSION2 = "0.1.63";
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) {
@@ -5897,6 +5902,8 @@ function agentUnit(options) {
5897
5902
  options.gitPublicKeyPath ? `FZ_GIT_PUBLIC_KEY_FILE=${options.gitPublicKeyPath}` : null,
5898
5903
  options.repository ? `FZ_DEPLOY_REPO=${options.repository}` : null,
5899
5904
  options.branch ? `FZ_DEPLOY_BRANCH=${options.branch}` : null,
5905
+ options.bootstrapBundlePath ? `FZ_BOOTSTRAP_BUNDLE=${options.bootstrapBundlePath}` : null,
5906
+ options.bootstrapBundleManifestPath ? `FZ_BOOTSTRAP_BUNDLE_MANIFEST=${options.bootstrapBundleManifestPath}` : null,
5900
5907
  options.profile ? `FZ_DEPLOY_PROFILE=${options.profile}` : null,
5901
5908
  options.repository && options.branch ? `FZ_DEPLOY_KEY=${options.project ?? "platform"}:${options.environment ?? "production"}` : null,
5902
5909
  deploymentEnabled ? `FZ_DEPLOY_ROOT=${deployRoot}` : null,
@@ -11555,18 +11562,18 @@ function removeSession(api, realm, path = defaultSessionPath()) {
11555
11562
  }
11556
11563
 
11557
11564
  // src/bootstrap.ts
11558
- import { createHash as createHash3, createHmac as createHmac2, randomBytes as randomBytes6 } from "crypto";
11565
+ import { createHash as createHash4, createHmac as createHmac2, randomBytes as randomBytes7 } from "crypto";
11559
11566
  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
11567
+ chmodSync as chmodSync4,
11568
+ existsSync as existsSync9,
11569
+ lstatSync as lstatSync5,
11570
+ mkdirSync as mkdirSync9,
11571
+ readFileSync as readFileSync9,
11572
+ renameSync as renameSync8,
11573
+ rmSync as rmSync6,
11574
+ writeFileSync as writeFileSync9
11568
11575
  } from "fs";
11569
- import { dirname as dirname8 } from "path";
11576
+ import { dirname as dirname9 } from "path";
11570
11577
  import { fileURLToPath } from "url";
11571
11578
 
11572
11579
  // src/platform-bootstrap-runtime.ts
@@ -11654,8 +11661,6 @@ function validatePlatformSharedEnvironment(input) {
11654
11661
  databaseUser: input.databaseUser,
11655
11662
  sharedDirectory: input.sharedDirectory,
11656
11663
  seedSyncEpoch: input.seedSyncEpoch,
11657
- repository: input.repository,
11658
- branch: input.branch,
11659
11664
  deployProfile: input.deployProfile
11660
11665
  }))
11661
11666
  safeAtom(name, value);
@@ -11738,8 +11743,6 @@ function renderPlatformSharedEnvironment(input) {
11738
11743
  FZ_AGENT_OTLP_ENDPOINT: value.agentOtlpEndpoint,
11739
11744
  FZ_CUSTODIAN_EMAIL: value.custodianEmail ?? "",
11740
11745
  FZ_PROFILE: value.deployProfile,
11741
- FZ_REPO: value.repository,
11742
- FZ_BRANCH: value.branch,
11743
11746
  FZ_EMAIL_PROVIDER: value.email?.provider ?? "",
11744
11747
  FZ_SMTP_HOST: value.email?.provider === "smtp" ? value.email.host : "",
11745
11748
  FZ_SMTP_PORT: value.email?.provider === "smtp" ? String(value.email.port) : "",
@@ -13158,6 +13161,164 @@ async function ensureForgeZeroOtelCollector(host, exportEndpoint) {
13158
13161
 
13159
13162
  // src/bootstrap.ts
13160
13163
  init_dist();
13164
+
13165
+ // src/bootstrap-bundle.ts
13166
+ import { createHash as createHash3, randomBytes as randomBytes6 } from "crypto";
13167
+ import {
13168
+ chmodSync as chmodSync3,
13169
+ createReadStream,
13170
+ existsSync as existsSync8,
13171
+ lstatSync as lstatSync4,
13172
+ mkdirSync as mkdirSync8,
13173
+ readFileSync as readFileSync8,
13174
+ renameSync as renameSync7,
13175
+ rmSync as rmSync5,
13176
+ writeFileSync as writeFileSync8
13177
+ } from "fs";
13178
+ import { dirname as dirname8, isAbsolute as isAbsolute2, resolve as resolve6 } from "path";
13179
+ var BOOTSTRAP_BUNDLE_FORMAT = 1;
13180
+ var BOOTSTRAP_BUNDLE_KIND = "forgezero-api-git-bundle";
13181
+ var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
13182
+ var run = async (argv2) => {
13183
+ const child = Bun.spawn([...argv2], { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
13184
+ const [stdout, stderr, exitCode] = await Promise.all([
13185
+ new Response(child.stdout).text(),
13186
+ new Response(child.stderr).text(),
13187
+ child.exited
13188
+ ]);
13189
+ return { exitCode, output: `${stdout}${stderr}`.slice(0, 65536) };
13190
+ };
13191
+ var checked2 = async (exec, argv2, label) => {
13192
+ const result = await exec(argv2);
13193
+ if (result.exitCode !== 0)
13194
+ throw new Error(`${label} failed${result.output.trim() ? `: ${result.output.trim()}` : ""}`);
13195
+ return result.output.trim();
13196
+ };
13197
+ async function sha256File(path) {
13198
+ const hash = createHash3("sha256");
13199
+ await new Promise((resolveDone, reject) => {
13200
+ const stream = createReadStream(path);
13201
+ stream.on("data", (chunk) => hash.update(chunk));
13202
+ stream.on("error", reject);
13203
+ stream.on("end", resolveDone);
13204
+ });
13205
+ return hash.digest("hex");
13206
+ }
13207
+ var branchName = (value) => {
13208
+ 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("-")) {
13209
+ throw new Error("bootstrap bundle branch is malformed");
13210
+ }
13211
+ return value;
13212
+ };
13213
+ function parseBootstrapBundleManifest(value) {
13214
+ if (!value || typeof value !== "object" || Array.isArray(value))
13215
+ throw new Error("bootstrap bundle manifest must be an object");
13216
+ const source = value;
13217
+ const allowed = ["format", "kind", "branch", "revision", "sha256", "bytes", "createdAt"];
13218
+ const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
13219
+ if (unknown.length)
13220
+ throw new Error(`bootstrap bundle manifest contains unknown field ${unknown[0]}`);
13221
+ if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
13222
+ throw new Error("bootstrap bundle manifest format is unsupported");
13223
+ }
13224
+ branchName(source.branch);
13225
+ if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
13226
+ throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
13227
+ }
13228
+ if (typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256)) {
13229
+ throw new Error("bootstrap bundle digest is malformed");
13230
+ }
13231
+ if (!Number.isSafeInteger(source.bytes) || Number(source.bytes) < 1 || Number(source.bytes) > MAX_BOOTSTRAP_BUNDLE_BYTES) {
13232
+ throw new Error("bootstrap bundle size is outside the supported boundary");
13233
+ }
13234
+ if (typeof source.createdAt !== "string" || Number.isNaN(Date.parse(source.createdAt))) {
13235
+ throw new Error("bootstrap bundle creation time is malformed");
13236
+ }
13237
+ return source;
13238
+ }
13239
+ function ownerRegularFile(path, maximum, label) {
13240
+ if (!isAbsolute2(path) || resolve6(path) !== path || /[\r\n\0]/.test(path)) {
13241
+ throw new Error(`${label} path must be canonical and absolute`);
13242
+ }
13243
+ const value = lstatSync4(path);
13244
+ const uid = process.getuid?.() ?? value.uid;
13245
+ if (!value.isFile() || value.isSymbolicLink() || value.uid !== uid || value.nlink !== 1 || (value.mode & 63) !== 0 || value.size < 1 || value.size > maximum) {
13246
+ throw new Error(`${label} must be an owner-only regular file with one link and at most ${maximum} bytes`);
13247
+ }
13248
+ }
13249
+ async function readBootstrapBundle(bundlePath, manifestPath = `${bundlePath}.json`) {
13250
+ ownerRegularFile(bundlePath, MAX_BOOTSTRAP_BUNDLE_BYTES, "bootstrap bundle");
13251
+ ownerRegularFile(manifestPath, 16 * 1024, "bootstrap bundle manifest");
13252
+ let parsed;
13253
+ try {
13254
+ parsed = JSON.parse(readFileSync8(manifestPath, "utf8"));
13255
+ } catch {
13256
+ throw new Error("bootstrap bundle manifest is not valid JSON");
13257
+ }
13258
+ const manifest = parseBootstrapBundleManifest(parsed);
13259
+ const size = lstatSync4(bundlePath).size;
13260
+ if (size !== manifest.bytes || await sha256File(bundlePath) !== manifest.sha256) {
13261
+ throw new Error("bootstrap bundle bytes do not match their manifest");
13262
+ }
13263
+ return { bundlePath, manifestPath, manifest };
13264
+ }
13265
+ async function buildBootstrapBundle(input, exec = run) {
13266
+ const repositoryRoot = resolve6(input.repositoryRoot);
13267
+ const outputPath = resolve6(input.outputPath);
13268
+ const manifestPath = `${outputPath}.json`;
13269
+ const branch = branchName(input.branch);
13270
+ if (!isAbsolute2(input.outputPath) || outputPath !== input.outputPath) {
13271
+ throw new Error("bootstrap bundle output must be a canonical absolute path");
13272
+ }
13273
+ if (existsSync8(outputPath) || existsSync8(manifestPath)) {
13274
+ throw new Error("bootstrap bundle output already exists");
13275
+ }
13276
+ const root = lstatSync4(repositoryRoot);
13277
+ if (!root.isDirectory() || root.isSymbolicLink())
13278
+ throw new Error("bootstrap bundle source must be a real directory");
13279
+ mkdirSync8(dirname8(outputPath), { recursive: true, mode: 448 });
13280
+ const parent = lstatSync4(dirname8(outputPath));
13281
+ const uid = process.getuid?.() ?? parent.uid;
13282
+ if (!parent.isDirectory() || parent.isSymbolicLink() || parent.uid !== uid || (parent.mode & 63) !== 0) {
13283
+ throw new Error("bootstrap bundle output directory must be caller-owned and owner-only");
13284
+ }
13285
+ const dirty = await checked2(exec, ["git", "-C", repositoryRoot, "status", "--porcelain=v1", "--untracked-files=no"], "Git worktree check");
13286
+ if (dirty)
13287
+ throw new Error("bootstrap bundle source has tracked changes; commit the reviewed API release first");
13288
+ const revision = (await checked2(exec, ["git", "-C", repositoryRoot, "rev-parse", "--verify", `refs/heads/${branch}^{commit}`], "Git branch resolution")).toLowerCase();
13289
+ if (!/^[a-f0-9]{40}$/.test(revision))
13290
+ throw new Error("bootstrap bundle branch did not resolve to one exact commit");
13291
+ const temporary = `${outputPath}.next.${process.pid}.${randomBytes6(6).toString("hex")}`;
13292
+ try {
13293
+ await checked2(exec, ["git", "-C", repositoryRoot, "bundle", "create", temporary, `refs/heads/${branch}`], "Git bundle creation");
13294
+ chmodSync3(temporary, 384);
13295
+ const file = lstatSync4(temporary);
13296
+ if (!file.isFile() || file.isSymbolicLink() || file.size < 1 || file.size > MAX_BOOTSTRAP_BUNDLE_BYTES) {
13297
+ throw new Error("created bootstrap bundle is outside the supported boundary");
13298
+ }
13299
+ await checked2(exec, ["git", "bundle", "verify", temporary], "Git bundle verification");
13300
+ const manifest = {
13301
+ format: BOOTSTRAP_BUNDLE_FORMAT,
13302
+ kind: BOOTSTRAP_BUNDLE_KIND,
13303
+ branch,
13304
+ revision,
13305
+ sha256: await sha256File(temporary),
13306
+ bytes: file.size,
13307
+ createdAt: new Date().toISOString()
13308
+ };
13309
+ const manifestTemporary = `${manifestPath}.next.${process.pid}.${randomBytes6(6).toString("hex")}`;
13310
+ writeFileSync8(manifestTemporary, `${JSON.stringify(manifest, null, 2)}
13311
+ `, { mode: 384, flag: "wx" });
13312
+ renameSync7(temporary, outputPath);
13313
+ renameSync7(manifestTemporary, manifestPath);
13314
+ return { bundlePath: outputPath, manifestPath, manifest };
13315
+ } catch (cause) {
13316
+ rmSync5(temporary, { force: true });
13317
+ throw cause;
13318
+ }
13319
+ }
13320
+
13321
+ // src/bootstrap.ts
13161
13322
  var PLATFORM_BOOTSTRAP_PROFILES = [
13162
13323
  "platform-db-api",
13163
13324
  "platform-api"
@@ -13257,7 +13418,7 @@ var SEED_CREDENTIAL = `${CREDS}/seed-sync-root.cred`;
13257
13418
  var BACKUP_RECOVERY_CREDENTIAL = `${CREDS}/backup-recovery-root.cred`;
13258
13419
  var BOOTSTRAP_SSH_CREDENTIAL = `${CREDS}/bootstrap-ssh-key.cred`;
13259
13420
  var BOOTSTRAP_SSH_PUBLIC_KEY = "/etc/forgezero/bootstrap/runner.pub";
13260
- var GIT_PUBLIC_KEY = "/etc/forgezero/git/deploy.pub";
13421
+ var BOOTSTRAP_RELEASE_EVIDENCE = "/var/lib/forgezero/bootstrap-release.json";
13261
13422
  var DB_MODE_EVIDENCE = "/var/lib/forgezero-cluster/server-mode.json";
13262
13423
  var LIFECYCLE_PROFILE = "/etc/forgezero/lifecycle.json";
13263
13424
  var CONTROL_SOCKET = "/run/forgezero/control.sock";
@@ -13330,6 +13491,9 @@ function validateBootstrapConfig(value) {
13330
13491
  throw new Error("Cloudflare Mesh/WARP requires a node-specific Cloudflare handoff");
13331
13492
  }
13332
13493
  if (value.kind === "enrolled-compute") {
13494
+ if (value.gitDeployKey !== undefined && typeof value.gitDeployKey !== "boolean") {
13495
+ throw new Error("gitDeployKey must be boolean");
13496
+ }
13333
13497
  if (!/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(value.realm))
13334
13498
  throw new Error("tenant realm is malformed");
13335
13499
  if (!/^https:\/\//.test(value.apiUrl) && !/^http:\/\/(?:127\.0\.0\.1|localhost)(?::\d+)?$/.test(value.apiUrl)) {
@@ -13355,6 +13519,9 @@ function validateBootstrapConfig(value) {
13355
13519
  throw new Error("unsupported platform software profile");
13356
13520
  if (!["production", "development"].includes(value.environment))
13357
13521
  throw new Error("platform environment must be production or development");
13522
+ if (!value.bootstrapBundle || ![value.bootstrapBundle.bundleFile, value.bootstrapBundle.manifestFile].every((path) => typeof path === "string" && path.startsWith("/") && !/[\r\n\0:]/.test(path))) {
13523
+ throw new Error("platform bootstrap requires absolute bundle and manifest paths");
13524
+ }
13358
13525
  let api;
13359
13526
  try {
13360
13527
  api = new URL(value.apiUrl);
@@ -13387,7 +13554,7 @@ function validateBootstrapConfig(value) {
13387
13554
  if (runtime.otlpCollectorUnit !== FORGEZERO_OTEL_COLLECTOR_UNIT) {
13388
13555
  throw new Error(`platform bootstrap requires ${FORGEZERO_OTEL_COLLECTOR_UNIT}`);
13389
13556
  }
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(",")) {
13557
+ if (runtime.softwareProfile !== value.profile || runtime.databaseRole !== value.database.role || runtime.nodeHostname !== value.nodeHostname || runtime.apiOrigin !== api.origin || runtime.databaseCoordinators.join(",") !== write.join(",")) {
13391
13558
  throw new Error("platform runtime coordinates disagree with immutable bootstrap coordinates");
13392
13559
  }
13393
13560
  if (runtime.deployProfile !== value.environment)
@@ -13417,6 +13584,7 @@ function planBootstrap(input, initialized = false) {
13417
13584
  ] : [
13418
13585
  ...config.firewall.enabled ? [{ id: "ufw", version: "ubuntu-26.04" }] : [],
13419
13586
  { id: "bun", version: "1.3.14" },
13587
+ { id: "git", version: "ubuntu-26.04" },
13420
13588
  { id: "nginx", version: "ubuntu-26.04" },
13421
13589
  ...platformBootstrapRunner(config) ? [{ id: "openssh-client", version: "ubuntu-26.04" }] : [],
13422
13590
  ...config.profile === "platform-api" ? [] : [{ id: "arangodb", version: "3.11.14" }],
@@ -13445,7 +13613,7 @@ function planBootstrap(input, initialized = false) {
13445
13613
  ]
13446
13614
  };
13447
13615
  }
13448
- var checked2 = async (host, argv2, label, options) => {
13616
+ var checked3 = async (host, argv2, label, options) => {
13449
13617
  const result = await host.exec(argv2, options);
13450
13618
  if (result.exitCode !== 0)
13451
13619
  throw new Error(`${label} failed: ${result.output.trim()}`);
@@ -13626,6 +13794,37 @@ async function seal(host, name, destination2, value) {
13626
13794
  if (result.exitCode !== 0)
13627
13795
  throw new Error(`could not seal ${name}: ${result.output.trim()}`);
13628
13796
  }
13797
+ async function verifyBootstrapBundleOnHost(host, config, verifyGit = false) {
13798
+ const manifestMetadata = host.inspect?.(config.bootstrapBundle.manifestFile);
13799
+ const bundleMetadata = host.inspect?.(config.bootstrapBundle.bundleFile);
13800
+ if (manifestMetadata && (!manifestMetadata.regular || manifestMetadata.symbolic || manifestMetadata.uid !== 0 || manifestMetadata.links !== 1 || (manifestMetadata.mode & 63) !== 0 || manifestMetadata.size > 16 * 1024)) {
13801
+ throw new Error("bootstrap bundle manifest must be a root-owned owner-only regular file");
13802
+ }
13803
+ if (bundleMetadata && (!bundleMetadata.regular || bundleMetadata.symbolic || bundleMetadata.uid !== 0 || bundleMetadata.links !== 1 || (bundleMetadata.mode & 63) !== 0 || bundleMetadata.size < 1 || bundleMetadata.size > 512 * 1024 * 1024)) {
13804
+ throw new Error("bootstrap bundle must be a root-owned owner-only regular file within 512 MiB");
13805
+ }
13806
+ if (!host.exists(config.bootstrapBundle.bundleFile) || !host.exists(config.bootstrapBundle.manifestFile)) {
13807
+ throw new Error("attended bootstrap bundle and manifest are required for release generation one");
13808
+ }
13809
+ let parsed;
13810
+ try {
13811
+ parsed = JSON.parse(host.read(config.bootstrapBundle.manifestFile));
13812
+ } catch {
13813
+ throw new Error("bootstrap bundle manifest is not valid JSON");
13814
+ }
13815
+ const manifest = parseBootstrapBundleManifest(parsed);
13816
+ const expectedBranch = config.environment === "production" ? "main" : "dev";
13817
+ if (manifest.branch !== expectedBranch || bundleMetadata && bundleMetadata.size !== manifest.bytes) {
13818
+ throw new Error("bootstrap bundle manifest disagrees with the selected platform environment");
13819
+ }
13820
+ const digest = (await checked3(host, ["/usr/bin/sha256sum", config.bootstrapBundle.bundleFile], "bootstrap bundle digest")).trim().split(/\s+/)[0];
13821
+ if (digest !== manifest.sha256)
13822
+ throw new Error("bootstrap bundle digest does not match its manifest");
13823
+ if (verifyGit) {
13824
+ await checked3(host, ["/usr/bin/git", "bundle", "verify", config.bootstrapBundle.bundleFile], "bootstrap Git bundle verification");
13825
+ }
13826
+ return manifest;
13827
+ }
13629
13828
  function bootstrapIdentity(config) {
13630
13829
  if (config.kind === "enrolled-compute")
13631
13830
  return {
@@ -13652,8 +13851,7 @@ function bootstrapIdentity(config) {
13652
13851
  computeReference: config.computeReference,
13653
13852
  nodeHostname: config.nodeHostname,
13654
13853
  apiUrl: config.apiUrl,
13655
- repository: config.repository,
13656
- branch: config.branch,
13854
+ bootstrapBundle: config.bootstrapBundle,
13657
13855
  deployRoot: config.deployRoot ?? "/opt/forgezero",
13658
13856
  telemetryEndpoint: config.telemetryEndpoint,
13659
13857
  database: {
@@ -13683,7 +13881,7 @@ function bootstrapIdentity(config) {
13683
13881
  };
13684
13882
  }
13685
13883
  function bootstrapIdentityDigest(config) {
13686
- return createHash3("sha256").update(JSON.stringify(bootstrapIdentity(config))).digest("hex");
13884
+ return createHash4("sha256").update(JSON.stringify(bootstrapIdentity(config))).digest("hex");
13687
13885
  }
13688
13886
  function parseStoredState(raw) {
13689
13887
  let value;
@@ -13733,34 +13931,6 @@ function bindBootstrapIntent(host, config) {
13733
13931
  }
13734
13932
  return identityDigest;
13735
13933
  }
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
13934
  function stateFor(config, cloudflare, previousCloudflareTunnelId) {
13765
13935
  return `${JSON.stringify({
13766
13936
  format: 2,
@@ -13914,6 +14084,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
13914
14084
  let installed;
13915
14085
  if (host.exists(STATE_PATH))
13916
14086
  installed = parseStoredState(host.read(STATE_PATH));
14087
+ const bootstrapManifest = config.kind === "platform" && !host.exists(BOOTSTRAP_RELEASE_EVIDENCE) ? await verifyBootstrapBundleOnHost(host, config) : undefined;
13917
14088
  let cloudflare;
13918
14089
  if (config.cloudflareHandoff) {
13919
14090
  if (host.exists(config.cloudflareHandoff.handoffFile)) {
@@ -13971,14 +14142,14 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
13971
14142
  const platformPrivate = config.kind === "platform" ? (() => {
13972
14143
  if (!secrets)
13973
14144
  throw new Error("platform apply requires attended credentials on stdin");
13974
- const checked3 = validatePlatformBootstrapSecrets(config, secrets);
14145
+ const checked4 = validatePlatformBootstrapSecrets(config, secrets);
13975
14146
  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
14147
+ root: checked4.clusterBootstrapCode,
14148
+ email: checked4.emailSecret,
14149
+ enrolmentToken: checked4.enrolmentToken,
14150
+ backup: checked4.backupS3Secret,
14151
+ cloudflareTunnelToken: checked4.cloudflareTunnelToken,
14152
+ cloudflareApiToken: checked4.cloudflareApiToken
13982
14153
  };
13983
14154
  })() : undefined;
13984
14155
  const enrolledPrivate = config.kind === "enrolled-compute" ? validateEnrolledComputeBootstrapSecrets(config, secrets) : undefined;
@@ -14023,18 +14194,21 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14023
14194
  } else
14024
14195
  await host.ensureSoftware(plan.software);
14025
14196
  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");
14197
+ await checked3(host, ["ufw", "--force", "default", "deny", "incoming"], "firewall inbound policy");
14198
+ await checked3(host, ["ufw", "--force", "default", "allow", "outgoing"], "firewall outbound policy");
14199
+ await checked3(host, ["ufw", "allow", `${config.firewall.sshPort}/tcp`], "firewall SSH rule");
14029
14200
  for (const cidr of config.firewall.privateCidrs) {
14030
14201
  if (config.database.role !== "none")
14031
- await checked2(host, ["ufw", "allow", "from", cidr, "to", "any", "port", "8528:8539", "proto", "tcp"], "database firewall rule");
14202
+ await checked3(host, ["ufw", "allow", "from", cidr, "to", "any", "port", "8528:8539", "proto", "tcp"], "database firewall rule");
14032
14203
  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");
14204
+ await checked3(host, ["ufw", "allow", "from", cidr, "to", "any", "port", String(port), "proto", "tcp"], "seed-mesh firewall rule");
14034
14205
  }
14035
- await checked2(host, ["ufw", "--force", "enable"], "firewall activation");
14206
+ await checked3(host, ["ufw", "--force", "enable"], "firewall activation");
14036
14207
  await host.ensureSoftware(plan.software.filter(({ id: id2 }) => id2 !== "ufw"));
14037
14208
  }
14209
+ if (config.kind === "platform" && bootstrapManifest) {
14210
+ await verifyBootstrapBundleOnHost(host, config, true);
14211
+ }
14038
14212
  if (config.kind === "platform") {
14039
14213
  const root = platformPrivate.root;
14040
14214
  if (!host.exists(JWT_CREDENTIAL)) {
@@ -14089,8 +14263,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14089
14263
  keepReleases: runtime.keepReleases,
14090
14264
  drainDeadlineMs: runtime.environment.drainDeadlineMs
14091
14265
  });
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");
14266
+ await checked3(host, ["useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", runtime.serviceUser], "API service account").catch(async () => {
14267
+ await checked3(host, ["id", runtime.serviceUser], "existing API service account");
14094
14268
  });
14095
14269
  host.mkdir(runtime.environment.sharedDirectory, 488);
14096
14270
  host.mkdir(runtime.slotsDirectory, 493);
@@ -14099,7 +14273,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14099
14273
  host.write("/etc/forgezero/capacity.env", `FZ_CONCURRENCY_LIMIT=${runtime.environment.concurrencyLimit}
14100
14274
  `, 420);
14101
14275
  }
14102
- await checked2(host, ["chown", `root:${runtime.serviceUser}`, runtime.environment.sharedDirectory, envPath], "runtime ownership");
14276
+ await checked3(host, ["chown", `root:${runtime.serviceUser}`, runtime.environment.sharedDirectory, envPath], "runtime ownership");
14103
14277
  host.write("/etc/systemd/system/forgezero@.service", units.template, 420);
14104
14278
  host.write("/etc/systemd/system/forgezero@blue.service.d/port.conf", units.dropIns.blue, 420);
14105
14279
  host.write("/etc/systemd/system/forgezero@green.service.d/port.conf", units.dropIns.green, 420);
@@ -14108,30 +14282,30 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14108
14282
  host.write("/etc/forgezero/deploy.env", activation.environment, 420);
14109
14283
  host.write("/etc/forgezero/deploy-activation.json", activation.helper, 384);
14110
14284
  host.write("/etc/sudoers.d/forgezero-runner", activation.sudoers, 288);
14111
- await checked2(host, ["visudo", "-cf", "/etc/sudoers.d/forgezero-runner"], "activation sudo policy");
14285
+ await checked3(host, ["visudo", "-cf", "/etc/sudoers.d/forgezero-runner"], "activation sudo policy");
14112
14286
  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();
14287
+ await checked3(host, telemetry.unitCheck.argv, "OTLP collector supervision");
14288
+ const otlpStatus = (await checked3(host, [telemetry.receiverCheck.command, ...telemetry.receiverCheck.argv], "OTLP receiver")).trim();
14115
14289
  if (!/^2\d\d$/.test(otlpStatus))
14116
14290
  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");
14291
+ await checked3(host, ["nginx", "-t"], "nginx configuration");
14292
+ await checked3(host, ["systemctl", "daemon-reload"], "systemd reload");
14293
+ await checked3(host, ["systemctl", "enable", "--now", "nginx.service"], "nginx supervision");
14120
14294
  if (config.database.role === "master") {
14121
14295
  const invite = `${runtime.environment.sharedDirectory}/platform-invite.token`;
14122
14296
  if (!host.exists(invite)) {
14123
- host.write(invite, `plt_${randomBytes6(24).toString("hex")}
14297
+ host.write(invite, `plt_${randomBytes7(24).toString("hex")}
14124
14298
  `, 384);
14125
- await checked2(host, ["chown", `${runtime.serviceUser}:${runtime.serviceUser}`, invite], "platform invite ownership");
14299
+ await checked3(host, ["chown", `${runtime.serviceUser}:${runtime.serviceUser}`, invite], "platform invite ownership");
14126
14300
  }
14127
14301
  }
14128
14302
  if (config.database.role !== "none") {
14129
14303
  host.mkdir("/var/lib/forgezero-cluster", 448);
14130
- await checked2(host, ["chown", "arangodb:arangodb", "/var/lib/forgezero-cluster"], "database state ownership");
14304
+ await checked3(host, ["chown", "arangodb:arangodb", "/var/lib/forgezero-cluster"], "database state ownership");
14131
14305
  host.write("/etc/systemd/system/forgezero-db.service", databaseUnit(config), 420);
14132
14306
  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");
14307
+ await checked3(host, ["systemctl", "daemon-reload"], "database unit reload");
14308
+ await checked3(host, ["systemctl", "enable", "--now", "forgezero-db.service", "forgezero-db-verify.service"], "database supervision");
14135
14309
  const evidence = {
14136
14310
  expectedMode: "default",
14137
14311
  role: "COORDINATOR",
@@ -14142,15 +14316,30 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14142
14316
  host.write(DB_MODE_EVIDENCE, `${JSON.stringify(evidence, null, 2)}
14143
14317
  `, 384);
14144
14318
  }
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");
14319
+ if (bootstrapManifest) {
14320
+ await checked3(host, [
14321
+ "runuser",
14322
+ "-u",
14323
+ "forgezero-agent",
14324
+ "--",
14325
+ "/usr/local/bin/fz-agent",
14326
+ "deploy",
14327
+ `--revision=${bootstrapManifest.revision}`,
14328
+ ...config.database.role === "master" ? ["--release-executor"] : []
14329
+ ], "initial Agent deployment");
14330
+ host.write(BOOTSTRAP_RELEASE_EVIDENCE, `${JSON.stringify({
14331
+ format: 1,
14332
+ kind: "forgezero-bootstrap-release",
14333
+ revision: bootstrapManifest.revision,
14334
+ sha256: bootstrapManifest.sha256,
14335
+ branch: bootstrapManifest.branch,
14336
+ deployedAt: new Date().toISOString()
14337
+ }, null, 2)}
14338
+ `, 384);
14339
+ host.remove(config.bootstrapBundle.bundleFile);
14340
+ host.remove(config.bootstrapBundle.manifestFile);
14341
+ await host.installAgent(config);
14342
+ }
14154
14343
  }
14155
14344
  if (config.cloudflareHandoff) {
14156
14345
  if (!host.exists(TUNNEL_CREDENTIAL) && connectorCapabilities) {
@@ -14159,8 +14348,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14159
14348
  if (!host.exists(TUNNEL_CREDENTIAL))
14160
14349
  throw new Error("sealed cloudflared connector credential is missing");
14161
14350
  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");
14351
+ await checked3(host, ["systemctl", "daemon-reload"], "cloudflared unit reload");
14352
+ await checked3(host, ["systemctl", "enable", "--now", "cloudflared.service"], "cloudflared connector supervision");
14164
14353
  const tunnelId = cloudflare?.tunnelId ?? installed?.cloudflareTunnelId ?? (config.kind === "platform" ? config.runtime.environment.cloudflare?.tunnelId : undefined);
14165
14354
  if (!tunnelId)
14166
14355
  throw new Error("Cloudflare tunnel identity is missing after handoff validation");
@@ -14170,8 +14359,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
14170
14359
  if (!host.exists(WARP_CONNECTOR_CREDENTIAL))
14171
14360
  throw new Error("sealed Cloudflare Mesh connector credential is missing");
14172
14361
  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");
14362
+ await checked3(host, ["systemctl", "daemon-reload"], "Cloudflare Mesh unit reload");
14363
+ await checked3(host, ["systemctl", "enable", "--now", "warp-svc.service", "forgezero-mesh-config.service"], "Cloudflare Mesh connector supervision");
14175
14364
  }
14176
14365
  host.write(STATE_PATH, stateFor(config, cloudflare, installed?.cloudflareTunnelId), 384);
14177
14366
  const status = await bootstrapStatus(host);
@@ -14216,8 +14405,7 @@ function strictBootstrapDocument(value) {
14216
14405
  "computeReference",
14217
14406
  "nodeHostname",
14218
14407
  "apiUrl",
14219
- "repository",
14220
- "branch",
14408
+ "bootstrapBundle",
14221
14409
  "deployRoot",
14222
14410
  "telemetryEndpoint",
14223
14411
  "database",
@@ -14230,9 +14418,11 @@ function strictBootstrapDocument(value) {
14230
14418
  "realm",
14231
14419
  "software",
14232
14420
  "deploymentCredentials",
14421
+ "gitDeployKey",
14233
14422
  "bootstrapRunner"
14234
14423
  ], "bootstrap config");
14235
14424
  if (root.kind === "platform") {
14425
+ exactKeys2(root.bootstrapBundle, ["bundleFile", "manifestFile"], "bootstrap bundle config");
14236
14426
  exactKeys2(root.firewall, ["enabled", "sshPort", "privateCidrs"], "firewall config");
14237
14427
  if (root.cloudflareHandoff !== undefined)
14238
14428
  exactKeys2(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
@@ -14274,8 +14464,6 @@ function strictBootstrapDocument(value) {
14274
14464
  "agentOtlpEndpoint",
14275
14465
  "custodianEmail",
14276
14466
  "email",
14277
- "repository",
14278
- "branch",
14279
14467
  "deployProfile",
14280
14468
  "otlpFlushIntervalMs",
14281
14469
  "otlpTraceSampleRatio",
@@ -14313,11 +14501,11 @@ function strictBootstrapDocument(value) {
14313
14501
  return value;
14314
14502
  }
14315
14503
  function readBootstrapConfig(path) {
14316
- const metadata = lstatSync4(path);
14504
+ const metadata = lstatSync5(path);
14317
14505
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.uid !== (process.getuid?.() ?? metadata.uid) || metadata.nlink !== 1 || (metadata.mode & 63) !== 0 || metadata.size > 64 * 1024) {
14318
14506
  throw new Error("bootstrap config must be an owner-only regular file with one link and at most 64 KiB");
14319
14507
  }
14320
- return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync8(path, "utf8"))));
14508
+ return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync9(path, "utf8"))));
14321
14509
  }
14322
14510
  function localBootstrapHost() {
14323
14511
  const execute = async (argv2, options = {}) => {
@@ -14335,19 +14523,19 @@ function localBootstrapHost() {
14335
14523
  };
14336
14524
  return {
14337
14525
  uid: () => process.getuid?.() ?? -1,
14338
- exists: existsSync8,
14339
- read: (path) => readFileSync8(path, "utf8"),
14526
+ exists: existsSync9,
14527
+ read: (path) => readFileSync9(path, "utf8"),
14340
14528
  write(path, content, mode) {
14341
- mkdirSync8(dirname8(path), { recursive: true, mode: 493 });
14529
+ mkdirSync9(dirname9(path), { recursive: true, mode: 493 });
14342
14530
  const temporary = `${path}.next.${process.pid}`;
14343
- writeFileSync8(temporary, content, { mode });
14344
- chmodSync3(temporary, mode);
14345
- renameSync7(temporary, path);
14531
+ writeFileSync9(temporary, content, { mode });
14532
+ chmodSync4(temporary, mode);
14533
+ renameSync8(temporary, path);
14346
14534
  },
14347
- mkdir: (path, mode) => mkdirSync8(path, { recursive: true, mode }),
14348
- remove: (path) => rmSync5(path, { force: true }),
14535
+ mkdir: (path, mode) => mkdirSync9(path, { recursive: true, mode }),
14536
+ remove: (path) => rmSync6(path, { force: true }),
14349
14537
  inspect(path) {
14350
- const value = lstatSync4(path);
14538
+ const value = lstatSync5(path);
14351
14539
  return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
14352
14540
  },
14353
14541
  exec: execute,
@@ -14369,7 +14557,12 @@ function localBootstrapHost() {
14369
14557
  async installAgent(config) {
14370
14558
  const capabilities = await readCapabilities(localRunner);
14371
14559
  const deployRoot = config.deployRoot ?? "/opt/forgezero";
14372
- const hasBinding = config.kind === "enrolled-compute" || existsSync8(ENROL_CREDENTIAL) || existsSync8("/var/lib/forgezero/enrolment.json");
14560
+ const hasBinding = existsSync9("/var/lib/forgezero/enrolment.json");
14561
+ const initialBundle = config.kind === "platform" && !existsSync9(BOOTSTRAP_RELEASE_EVIDENCE) ? {
14562
+ path: config.bootstrapBundle.bundleFile,
14563
+ manifestPath: config.bootstrapBundle.manifestFile,
14564
+ manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync9(config.bootstrapBundle.manifestFile, "utf8")))
14565
+ } : undefined;
14373
14566
  if (config.kind === "platform") {
14374
14567
  const lifecycle = config.database.role === "none" ? {
14375
14568
  apiUnits: ["forgezero@blue.service", "forgezero@green.service"],
@@ -14381,8 +14574,8 @@ function localBootstrapHost() {
14381
14574
  databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
14382
14575
  databasePorts: [8529]
14383
14576
  };
14384
- mkdirSync8(dirname8(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
14385
- writeFileSync8(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
14577
+ mkdirSync9(dirname9(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
14578
+ writeFileSync9(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
14386
14579
  `, { mode: 256 });
14387
14580
  }
14388
14581
  const plan = planInstall({
@@ -14390,15 +14583,17 @@ function localBootstrapHost() {
14390
14583
  socketPath: DEFAULT_SOCKET,
14391
14584
  seedPath: "/var/lib/forgezero/node.seed",
14392
14585
  controlSocketPath: "/run/forgezero/control.sock",
14393
- repository: config.repository,
14394
- branch: config.branch,
14586
+ repository: initialBundle?.path ?? (config.kind === "enrolled-compute" ? config.repository : undefined),
14587
+ branch: initialBundle?.manifest.branch ?? (config.kind === "enrolled-compute" ? config.branch : undefined),
14588
+ bootstrapBundlePath: initialBundle?.path,
14589
+ bootstrapBundleManifestPath: initialBundle?.manifestPath,
14395
14590
  profile: config.kind === "platform" ? config.profile : config.profile,
14396
14591
  deployRoot,
14397
14592
  deploymentCredentials: config.deploymentCredentials,
14398
14593
  publicApiUrl: config.apiUrl,
14399
- gitCredentialPath: "/etc/forgezero/creds/git-deploy-key.cred",
14400
- gitPublicKeyPath: "/etc/forgezero/git/deploy.pub",
14401
- generateGitIdentity: true,
14594
+ gitCredentialPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
14595
+ gitPublicKeyPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
14596
+ generateGitIdentity: config.kind === "enrolled-compute" && config.gitDeployKey === true,
14402
14597
  pullDeployments: hasBinding,
14403
14598
  pullMigrations: config.kind === "platform" && hasBinding,
14404
14599
  pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
@@ -14412,9 +14607,11 @@ function localBootstrapHost() {
14412
14607
  telemetryEndpoint: config.telemetryEndpoint,
14413
14608
  binPath: "/usr/local/lib/forgezero/agent/fz-agent",
14414
14609
  sourceBinPath: PACKAGED_AGENT_BIN,
14610
+ ...existsSync9(ENROL_CREDENTIAL) || hasBinding ? {
14611
+ enrolTokenCredentialPath: existsSync9(ENROL_CREDENTIAL) ? ENROL_CREDENTIAL : undefined,
14612
+ enrolStatePath: "/var/lib/forgezero/enrolment.json"
14613
+ } : {},
14415
14614
  ...hasBinding ? {
14416
- enrolTokenCredentialPath: ENROL_CREDENTIAL,
14417
- enrolStatePath: "/var/lib/forgezero/enrolment.json",
14418
14615
  apiUrl: config.apiUrl,
14419
14616
  project: config.kind === "enrolled-compute" ? config.realm : "platform",
14420
14617
  environment: config.kind === "enrolled-compute" ? undefined : config.environment,
@@ -14422,8 +14619,8 @@ function localBootstrapHost() {
14422
14619
  } : {}
14423
14620
  });
14424
14621
  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 });
14622
+ mkdirSync9(dirname9(unit.path), { recursive: true, mode: 493 });
14623
+ writeFileSync9(unit.path, unit.unit, { mode: 420 });
14427
14624
  }
14428
14625
  await applyPlan(plan, localRunner);
14429
14626
  return plan;
@@ -14432,8 +14629,8 @@ function localBootstrapHost() {
14432
14629
  }
14433
14630
 
14434
14631
  // 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";
14632
+ import { constants as constants2, closeSync, fstatSync, openSync, readFileSync as readFileSync10 } from "fs";
14633
+ import { dirname as dirname10, resolve as resolve7 } from "path";
14437
14634
  async function discoverCloudflareBootstrapCommandResources(input, dependencies = {}) {
14438
14635
  return (dependencies.discover ?? discoverCloudflareBootstrapResources)({
14439
14636
  zoneName: input.zoneName,
@@ -14464,7 +14661,7 @@ function createCloudflareBootstrapCommandConfig(input) {
14464
14661
  };
14465
14662
  }
14466
14663
  function readOwnerConfig(path) {
14467
- const absolute2 = resolve6(path);
14664
+ const absolute2 = resolve7(path);
14468
14665
  let descriptor;
14469
14666
  try {
14470
14667
  descriptor = openSync(absolute2, constants2.O_RDONLY | constants2.O_NOFOLLOW);
@@ -14473,7 +14670,7 @@ function readOwnerConfig(path) {
14473
14670
  if (!metadata.isFile() || metadata.nlink !== 1 || metadata.size < 2 || metadata.size > 131072 || uid !== undefined && metadata.uid !== uid || (metadata.mode & 63) !== 0) {
14474
14671
  throw new Error(`${absolute2} must be one operator-owned 0600 regular file`);
14475
14672
  }
14476
- return JSON.parse(readFileSync9(descriptor, "utf8"));
14673
+ return JSON.parse(readFileSync10(descriptor, "utf8"));
14477
14674
  } catch (cause) {
14478
14675
  if (cause instanceof SyntaxError)
14479
14676
  throw new Error(`${absolute2} is not valid Cloudflare bootstrap JSON`);
@@ -14487,7 +14684,7 @@ function readOwnerConfig(path) {
14487
14684
  }
14488
14685
  function readCloudflareBootstrapCommandConfig(path, mode) {
14489
14686
  const input = record2(readOwnerConfig(path), "Cloudflare bootstrap config");
14490
- const baseDirectory = dirname9(resolve6(path));
14687
+ const baseDirectory = dirname10(resolve7(path));
14491
14688
  exactKeys3(input, ["format", "kind", "checkpointPath", "coordinates"], "Cloudflare bootstrap config");
14492
14689
  if (input.format !== 1 || input.kind !== "forgezero-cloudflare-bootstrap-request") {
14493
14690
  throw new Error("Cloudflare bootstrap config format/kind is invalid");
@@ -14527,7 +14724,7 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
14527
14724
  return {
14528
14725
  mode,
14529
14726
  coordinates,
14530
- checkpointPath: resolve6(baseDirectory, input.checkpointPath)
14727
+ checkpointPath: resolve7(baseDirectory, input.checkpointPath)
14531
14728
  };
14532
14729
  }
14533
14730
  async function runCloudflareBootstrapCommand(configPath, apply, tokens, dependencies = {}) {
@@ -14540,14 +14737,14 @@ async function runCloudflareBootstrapCommand(configPath, apply, tokens, dependen
14540
14737
  return evidence;
14541
14738
  }
14542
14739
  async function runCloudflareBootstrapVerificationCommand(checkpointPath, dependencies = {}) {
14543
- const evidence = await (dependencies.verify ?? verifyCloudflareBootstrapAcceptance)(resolve6(checkpointPath));
14740
+ const evidence = await (dependencies.verify ?? verifyCloudflareBootstrapAcceptance)(resolve7(checkpointPath));
14544
14741
  (dependencies.write ?? ((text3) => process.stdout.write(text3)))(`${JSON.stringify(evidence, null, 2)}
14545
14742
  `);
14546
14743
  return evidence;
14547
14744
  }
14548
14745
  function readCloudflareBootstrapFinalizeConfig(path) {
14549
14746
  const input = record2(readOwnerConfig(path), "Cloudflare bootstrap finalize config");
14550
- const baseDirectory = dirname9(resolve6(path));
14747
+ const baseDirectory = dirname10(resolve7(path));
14551
14748
  exactKeys3(input, ["format", "kind", "checkpointPath", "acceptancePath"], "Cloudflare bootstrap finalize config");
14552
14749
  if (input.format !== 1 || input.kind !== "forgezero-cloudflare-bootstrap-finalize") {
14553
14750
  throw new Error("Cloudflare bootstrap finalize config format/kind is invalid");
@@ -14558,8 +14755,8 @@ function readCloudflareBootstrapFinalizeConfig(path) {
14558
14755
  }
14559
14756
  }
14560
14757
  return {
14561
- checkpointPath: resolve6(baseDirectory, input.checkpointPath),
14562
- acceptancePath: resolve6(baseDirectory, input.acceptancePath)
14758
+ checkpointPath: resolve7(baseDirectory, input.checkpointPath),
14759
+ acceptancePath: resolve7(baseDirectory, input.acceptancePath)
14563
14760
  };
14564
14761
  }
14565
14762
  async function runCloudflareBootstrapFinalizeCommand(configPath, dependencies = {}) {
@@ -14571,31 +14768,31 @@ async function runCloudflareBootstrapFinalizeCommand(configPath, dependencies =
14571
14768
  }
14572
14769
 
14573
14770
  // src/metal-bootstrap.ts
14574
- import { createHash as createHash4, randomBytes as randomBytes7 } from "crypto";
14771
+ import { createHash as createHash5, randomBytes as randomBytes8 } from "crypto";
14575
14772
  import {
14576
- chmodSync as chmodSync4,
14773
+ chmodSync as chmodSync5,
14577
14774
  chownSync,
14578
14775
  copyFileSync as copyFileSync2,
14579
- existsSync as existsSync9,
14580
- lstatSync as lstatSync5,
14581
- mkdirSync as mkdirSync10,
14582
- readFileSync as readFileSync10,
14776
+ existsSync as existsSync10,
14777
+ lstatSync as lstatSync6,
14778
+ mkdirSync as mkdirSync11,
14779
+ readFileSync as readFileSync11,
14583
14780
  realpathSync as realpathSync4,
14584
- renameSync as renameSync8,
14781
+ renameSync as renameSync9,
14585
14782
  statSync,
14586
14783
  symlinkSync as symlinkSync2,
14587
14784
  unlinkSync as unlinkSync2,
14588
- writeFileSync as writeFileSync10
14785
+ writeFileSync as writeFileSync11
14589
14786
  } from "fs";
14590
- import { dirname as dirname11, isAbsolute as isAbsolute3, join as join9, resolve as resolve7 } from "path";
14787
+ import { dirname as dirname12, isAbsolute as isAbsolute4, join as join9, resolve as resolve8 } from "path";
14591
14788
  import { isIP as isIP5 } from "net";
14592
14789
 
14593
14790
  // src/metal-isolation.ts
14594
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
14791
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
14595
14792
  import { join as join8 } from "path";
14596
14793
 
14597
14794
  // src/metal-provision.ts
14598
- import { dirname as dirname10, isAbsolute as isAbsolute2, join as join7 } from "path";
14795
+ import { dirname as dirname11, isAbsolute as isAbsolute3, join as join7 } from "path";
14599
14796
  import { isIP as isIP4 } from "net";
14600
14797
 
14601
14798
  // src/ubuntu.ts
@@ -14644,7 +14841,7 @@ function validateMetalProfile(profile) {
14644
14841
  if (!Number.isInteger(profile.addressStart) || !Number.isInteger(profile.addressEnd) || profile.addressStart < 2 || profile.addressEnd > 254 || profile.addressStart > profile.addressEnd)
14645
14842
  throw new MetalProvisionError("invalid guest address range");
14646
14843
  for (const path of [profile.stateDir, profile.seedDir, profile.unitDir]) {
14647
- if (!isAbsolute2(path))
14844
+ if (!isAbsolute3(path))
14648
14845
  throw new MetalProvisionError("metal paths must be absolute");
14649
14846
  }
14650
14847
  new URL(profile.apiUrl);
@@ -14752,14 +14949,14 @@ var defaultExec = async (argv2) => {
14752
14949
  ]);
14753
14950
  return { exitCode, stdout, stderr };
14754
14951
  };
14755
- var checked3 = async (exec, argv2) => {
14952
+ var checked4 = async (exec, argv2) => {
14756
14953
  const result = await exec(argv2);
14757
14954
  if (result.exitCode !== 0)
14758
14955
  throw new Error(`${argv2[0]} failed: ${(result.stderr || result.stdout).trim()}`);
14759
14956
  return result;
14760
14957
  };
14761
14958
  var requireGuestsInSlice = async (exec) => {
14762
- const active = await checked3(exec, [
14959
+ const active = await checked4(exec, [
14763
14960
  "systemctl",
14764
14961
  "list-units",
14765
14962
  "--type=service",
@@ -14773,7 +14970,7 @@ var requireGuestsInSlice = async (exec) => {
14773
14970
  const service = line.trim().split(/\s+/)[0];
14774
14971
  if (!service)
14775
14972
  continue;
14776
- const cgroup = await checked3(exec, ["systemctl", "show", "-p", "ControlGroup", "--value", service]);
14973
+ const cgroup = await checked4(exec, ["systemctl", "show", "-p", "ControlGroup", "--value", service]);
14777
14974
  if (!cgroup.stdout.trim().includes("/forgezero-guests.slice/")) {
14778
14975
  throw new Error(`${service} must be drained and restarted into forgezero-guests.slice`);
14779
14976
  }
@@ -14783,23 +14980,23 @@ async function applyMetalIsolation(profile, exec = defaultExec) {
14783
14980
  validateMetalProfile(profile);
14784
14981
  await requireGuestsInSlice(exec);
14785
14982
  const unitDir = profile.unitDir;
14786
- mkdirSync9(unitDir, { recursive: true });
14787
- writeFileSync9(join8(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
14983
+ mkdirSync10(unitDir, { recursive: true });
14984
+ writeFileSync10(join8(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
14788
14985
  for (const unit of ["system.slice", "user.slice"]) {
14789
14986
  const directory = join8(unitDir, `${unit}.d`);
14790
- mkdirSync9(directory, { recursive: true });
14791
- writeFileSync9(join8(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
14987
+ mkdirSync10(directory, { recursive: true });
14988
+ writeFileSync10(join8(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
14792
14989
  }
14793
14990
  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"]);
14991
+ mkdirSync10(initDirectory, { recursive: true });
14992
+ writeFileSync10(join8(initDirectory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "scope"), { mode: 420 });
14993
+ await checked4(exec, ["systemctl", "daemon-reload"]);
14797
14994
  await requireGuestsInSlice(exec);
14798
14995
  const properties = [`AllowedCPUs=${profile.housekeepingCpus}`];
14799
14996
  if (profile.housekeepingMemoryNodes)
14800
14997
  properties.push(`AllowedMemoryNodes=${profile.housekeepingMemoryNodes}`);
14801
14998
  for (const unit of ["system.slice", "user.slice", "init.scope"]) {
14802
- await checked3(exec, ["systemctl", "set-property", "--runtime", unit, ...properties]);
14999
+ await checked4(exec, ["systemctl", "set-property", "--runtime", unit, ...properties]);
14803
15000
  }
14804
15001
  }
14805
15002
 
@@ -14911,7 +15108,7 @@ function validateMetalBootstrapConfig(config) {
14911
15108
  throw new MetalBootstrapError("metal bootstrap uses fixed state, seed, and systemd unit directories");
14912
15109
  }
14913
15110
  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)) {
15111
+ if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve8(image.path) !== image.path || /[\0\r\n]/.test(image.path)) {
14915
15112
  throw new MetalBootstrapError("the pinned guest image must use the fixed image directory");
14916
15113
  }
14917
15114
  if (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION2) {
@@ -14946,10 +15143,10 @@ function validateMetalBootstrapConfig(config) {
14946
15143
  return config;
14947
15144
  }
14948
15145
  function validateOwnerOnlyPath(path, requireRootOwner) {
14949
- if (!isAbsolute3(path) || resolve7(path) !== path || path.includes("/../")) {
15146
+ if (!isAbsolute4(path) || resolve8(path) !== path || path.includes("/../")) {
14950
15147
  throw new MetalBootstrapError("private bootstrap paths must be canonical absolute paths");
14951
15148
  }
14952
- const metadata = lstatSync5(path);
15149
+ const metadata = lstatSync6(path);
14953
15150
  if (!metadata.isFile() || metadata.isSymbolicLink() || realpathSync4(path) !== path) {
14954
15151
  throw new MetalBootstrapError("private bootstrap path must be a regular non-symlink file");
14955
15152
  }
@@ -14967,7 +15164,7 @@ function readMetalBootstrapConfig(path) {
14967
15164
  throw new MetalBootstrapError("metal bootstrap config size is invalid");
14968
15165
  let parsed;
14969
15166
  try {
14970
- parsed = JSON.parse(readFileSync10(path, "utf8"));
15167
+ parsed = JSON.parse(readFileSync11(path, "utf8"));
14971
15168
  } catch {
14972
15169
  throw new MetalBootstrapError("metal bootstrap config is not valid JSON");
14973
15170
  }
@@ -15000,15 +15197,15 @@ function planMetalBootstrap(config) {
15000
15197
  };
15001
15198
  }
15002
15199
  var atomicWrite2 = (path, body, mode) => {
15003
- mkdirSync10(dirname11(path), { recursive: true, mode: 493 });
15200
+ mkdirSync11(dirname12(path), { recursive: true, mode: 493 });
15004
15201
  const temporary = `${path}.next-${process.pid}`;
15005
- writeFileSync10(temporary, body, { mode, flag: "wx" });
15006
- chmodSync4(temporary, mode);
15202
+ writeFileSync11(temporary, body, { mode, flag: "wx" });
15203
+ chmodSync5(temporary, mode);
15007
15204
  chownSync(temporary, 0, 0);
15008
- renameSync8(temporary, path);
15205
+ renameSync9(temporary, path);
15009
15206
  };
15010
15207
  var validateAgentSourcePath = (source) => {
15011
- if (!isAbsolute3(source) || !lstatSync5(source).isFile() || lstatSync5(source).isSymbolicLink()) {
15208
+ if (!isAbsolute4(source) || !lstatSync6(source).isFile() || lstatSync6(source).isSymbolicLink()) {
15012
15209
  throw new MetalBootstrapError("published Agent source path must be an absolute regular non-symlink file");
15013
15210
  }
15014
15211
  };
@@ -15172,11 +15369,11 @@ WantedBy=multi-user.target
15172
15369
  var installAgentBinary = (source, version) => {
15173
15370
  validateAgentSourcePath(source);
15174
15371
  const release = `/opt/forgezero/agent/versions/${version}/dist`;
15175
- mkdirSync10(release, { recursive: true, mode: 493 });
15372
+ mkdirSync11(release, { recursive: true, mode: 493 });
15176
15373
  copyFileSync2(source, join9(release, "fz-agent.js"));
15177
- chmodSync4(join9(release, "fz-agent.js"), 493);
15374
+ chmodSync5(join9(release, "fz-agent.js"), 493);
15178
15375
  chownSync(join9(release, "fz-agent.js"), 0, 0);
15179
- mkdirSync10("/opt/forgezero/agent", { recursive: true, mode: 493 });
15376
+ mkdirSync11("/opt/forgezero/agent", { recursive: true, mode: 493 });
15180
15377
  for (const [link, target] of [
15181
15378
  ["/opt/forgezero/agent/current.next", `versions/${version}`],
15182
15379
  [AGENT_PATH, "/opt/forgezero/agent/current/dist/fz-agent.js"]
@@ -15186,7 +15383,7 @@ var installAgentBinary = (source, version) => {
15186
15383
  } catch {}
15187
15384
  symlinkSync2(target, link);
15188
15385
  if (link.endsWith("current.next"))
15189
- renameSync8(link, "/opt/forgezero/agent/current");
15386
+ renameSync9(link, "/opt/forgezero/agent/current");
15190
15387
  }
15191
15388
  };
15192
15389
  var preflight = async (config, exec) => {
@@ -15208,10 +15405,10 @@ var preflight = async (config, exec) => {
15208
15405
  ]);
15209
15406
  await runChecked(exec, ["/usr/sbin/vgs", config.profile.volumeGroup]);
15210
15407
  await runChecked(exec, ["/usr/sbin/ip", "link", "show", config.profile.bridge]);
15211
- if (!existsSync9("/dev/kvm"))
15408
+ if (!existsSync10("/dev/kvm"))
15212
15409
  throw new MetalBootstrapError("/dev/kvm is required");
15213
15410
  if (config.profile.confidential) {
15214
- if (!existsSync9("/dev/sev"))
15411
+ if (!existsSync10("/dev/sev"))
15215
15412
  throw new MetalBootstrapError("/dev/sev is required by the confidential profile");
15216
15413
  await runChecked(exec, ["/usr/bin/qemu-system-x86_64", "-object", "sev-snp-guest,help"]);
15217
15414
  }
@@ -15223,16 +15420,16 @@ var assertSupportedMetalHost = () => {
15223
15420
  if (process.platform !== "linux" || process.arch !== "x64") {
15224
15421
  throw new MetalBootstrapError("metal bootstrap supports only Ubuntu 26.04 x86_64 hosts");
15225
15422
  }
15226
- const release = readFileSync10("/etc/os-release", "utf8");
15423
+ const release = readFileSync11("/etc/os-release", "utf8");
15227
15424
  if (!/^ID=ubuntu$/m.test(release) || !/^VERSION_ID="?26\.04"?$/m.test(release)) {
15228
15425
  throw new MetalBootstrapError("metal bootstrap supports only Ubuntu 26.04 x86_64 hosts");
15229
15426
  }
15230
15427
  };
15231
15428
  var ensurePinnedGuestImage = async (config, exec) => {
15232
15429
  const image = config.profile.images[SUPPORTED_GUEST_IMAGE.key];
15233
- if (existsSync9(image.path))
15430
+ if (existsSync10(image.path))
15234
15431
  return;
15235
- mkdirSync10(dirname11(image.path), { recursive: true, mode: 493 });
15432
+ mkdirSync11(dirname12(image.path), { recursive: true, mode: 493 });
15236
15433
  const temporary = `${image.path}.next-${process.pid}`;
15237
15434
  try {
15238
15435
  await runChecked(exec, [
@@ -15249,9 +15446,9 @@ var ensurePinnedGuestImage = async (config, exec) => {
15249
15446
  const digest = (await runChecked(exec, ["/usr/bin/sha256sum", temporary])).stdout.split(/\s+/)[0];
15250
15447
  if (digest !== image.sha256)
15251
15448
  throw new MetalBootstrapError("downloaded guest image digest mismatch");
15252
- chmodSync4(temporary, 292);
15449
+ chmodSync5(temporary, 292);
15253
15450
  chownSync(temporary, 0, 0);
15254
- renameSync8(temporary, image.path);
15451
+ renameSync9(temporary, image.path);
15255
15452
  } catch (cause) {
15256
15453
  try {
15257
15454
  unlinkSync2(temporary);
@@ -15264,11 +15461,11 @@ async function applyMetalBootstrap(config, options) {
15264
15461
  if ((options.getuid ?? process.getuid)?.() !== 0)
15265
15462
  throw new MetalBootstrapError("fz bootstrap metal --apply must run as root");
15266
15463
  assertSupportedMetalHost();
15267
- if (existsSync9(STATE_PATH2) && !options.repair)
15464
+ if (existsSync10(STATE_PATH2) && !options.repair)
15268
15465
  throw new MetalBootstrapError("metal host is already initialized; use explicit repair");
15269
15466
  if (config.agentSeedFile)
15270
15467
  validateOwnerOnlyPath(config.agentSeedFile, true);
15271
- if (config.agentSeedFile && existsSync9(SEED_CREDENTIAL_PATH)) {
15468
+ if (config.agentSeedFile && existsSync10(SEED_CREDENTIAL_PATH)) {
15272
15469
  throw new MetalBootstrapError("repair refuses replacement seed material while the sealed metal identity exists");
15273
15470
  }
15274
15471
  validateAgentSourcePath(options.agentSourcePath);
@@ -15295,9 +15492,9 @@ async function applyMetalBootstrap(config, options) {
15295
15492
  await preflight(config, exec);
15296
15493
  await ensureAccount(exec);
15297
15494
  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 });
15495
+ mkdirSync11("/etc/forgezero/creds", { recursive: true, mode: 448 });
15496
+ mkdirSync11(config.profile.stateDir, { recursive: true, mode: 448 });
15497
+ mkdirSync11(config.profile.seedDir, { recursive: true, mode: 448 });
15301
15498
  const persistedProfile = {
15302
15499
  ...config.profile,
15303
15500
  metalHostname: config.metalHostname,
@@ -15306,13 +15503,13 @@ async function applyMetalBootstrap(config, options) {
15306
15503
  };
15307
15504
  atomicWrite2(PROFILE_PATH, `${JSON.stringify(persistedProfile, null, 2)}
15308
15505
  `, 384);
15309
- if (!existsSync9(SEED_CREDENTIAL_PATH)) {
15310
- const seed = config.agentSeedFile ? readFileSync10(config.agentSeedFile, "utf8").trim() : randomBytes7(32).toString("base64url");
15506
+ if (!existsSync10(SEED_CREDENTIAL_PATH)) {
15507
+ const seed = config.agentSeedFile ? readFileSync11(config.agentSeedFile, "utf8").trim() : randomBytes8(32).toString("base64url");
15311
15508
  if (seed.length < 32 || /[\0\r\n]/.test(seed))
15312
15509
  throw new MetalBootstrapError("metal Agent seed is invalid");
15313
15510
  await runChecked(exec, ["/usr/bin/systemd-creds", "encrypt", "--name=metal-agent-seed", "-", SEED_CREDENTIAL_PATH], `${seed}
15314
15511
  `);
15315
- chmodSync4(SEED_CREDENTIAL_PATH, 256);
15512
+ chmodSync5(SEED_CREDENTIAL_PATH, 256);
15316
15513
  chownSync(SEED_CREDENTIAL_PATH, 0, 0);
15317
15514
  if (config.agentSeedFile)
15318
15515
  unlinkSync2(config.agentSeedFile);
@@ -15361,7 +15558,7 @@ async function applyMetalBootstrap(config, options) {
15361
15558
  initializedAt: new Date().toISOString(),
15362
15559
  role: "metal",
15363
15560
  metalHostname: config.metalHostname,
15364
- profileSha256: createHash4("sha256").update(JSON.stringify(config.profile)).digest("hex")
15561
+ profileSha256: createHash5("sha256").update(JSON.stringify(config.profile)).digest("hex")
15365
15562
  };
15366
15563
  atomicWrite2(STATE_PATH2, `${JSON.stringify(state, null, 2)}
15367
15564
  `, 384);
@@ -15369,7 +15566,7 @@ async function applyMetalBootstrap(config, options) {
15369
15566
  }
15370
15567
  var socketReady = (path) => {
15371
15568
  try {
15372
- return lstatSync5(path).isSocket();
15569
+ return lstatSync6(path).isSocket();
15373
15570
  } catch {
15374
15571
  return false;
15375
15572
  }
@@ -15378,17 +15575,17 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15378
15575
  const problems = [];
15379
15576
  let profileValid = false, imageVerified = false, metalHostname, profileSha256;
15380
15577
  let profileMode = null;
15381
- if (existsSync9(PROFILE_PATH)) {
15578
+ if (existsSync10(PROFILE_PATH)) {
15382
15579
  try {
15383
15580
  const metadata = statSync(PROFILE_PATH);
15384
15581
  profileMode = metadata.mode & 511;
15385
15582
  if (profileMode !== 384 || metadata.uid !== 0)
15386
15583
  problems.push("metal profile is not root-owned mode 0600");
15387
- const persisted = JSON.parse(readFileSync10(PROFILE_PATH, "utf8"));
15584
+ const persisted = JSON.parse(readFileSync11(PROFILE_PATH, "utf8"));
15388
15585
  const { metalHostname: profileHostname, hostTelemetryEndpoint, hostTelemetryUnit, ...profile } = persisted;
15389
15586
  validateMetalProfile(profile);
15390
15587
  profileValid = true;
15391
- profileSha256 = createHash4("sha256").update(JSON.stringify(profile)).digest("hex");
15588
+ profileSha256 = createHash5("sha256").update(JSON.stringify(profile)).digest("hex");
15392
15589
  if (!/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(profileHostname) || hostTelemetryEndpoint !== "http://127.0.0.1:4318") {
15393
15590
  problems.push("persisted metal host coordinates are invalid");
15394
15591
  }
@@ -15417,7 +15614,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15417
15614
  problems.push("local OTLP metrics receiver did not accept a proof request");
15418
15615
  }
15419
15616
  const image = profile.images[Object.keys(profile.images)[0]];
15420
- if (existsSync9(image.path)) {
15617
+ if (existsSync10(image.path)) {
15421
15618
  const digest = (await exec(["/usr/bin/sha256sum", image.path])).stdout.split(/\s+/)[0];
15422
15619
  imageVerified = digest === image.sha256;
15423
15620
  }
@@ -15428,9 +15625,9 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15428
15625
  }
15429
15626
  } else
15430
15627
  problems.push("metal profile is missing");
15431
- if (existsSync9(STATE_PATH2)) {
15628
+ if (existsSync10(STATE_PATH2)) {
15432
15629
  try {
15433
- const state = JSON.parse(readFileSync10(STATE_PATH2, "utf8"));
15630
+ const state = JSON.parse(readFileSync11(STATE_PATH2, "utf8"));
15434
15631
  metalHostname = state.metalHostname;
15435
15632
  if (state.role !== "metal" || !metalHostname || state.profileSha256 !== profileSha256) {
15436
15633
  problems.push("metal initialized state does not bind the current profile");
@@ -15446,7 +15643,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15446
15643
  "forgezero-metal-agent-egress.service",
15447
15644
  "forgezero-metal-agent.service"
15448
15645
  ]) {
15449
- if (!existsSync9(join9(UNIT_DIRECTORY, unit)))
15646
+ if (!existsSync10(join9(UNIT_DIRECTORY, unit)))
15450
15647
  units[unit] = "missing";
15451
15648
  else
15452
15649
  units[unit] = (await exec(["/usr/bin/systemctl", "is-active", "--quiet", unit])).exitCode === 0 ? "active" : "inactive";
@@ -15460,7 +15657,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15460
15657
  if (!updateSocketReady)
15461
15658
  problems.push("Agent update helper socket is not ready");
15462
15659
  return {
15463
- initialized: existsSync9(STATE_PATH2),
15660
+ initialized: existsSync10(STATE_PATH2),
15464
15661
  profileValid,
15465
15662
  profileMode,
15466
15663
  imageVerified,
@@ -15473,8 +15670,8 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15473
15670
  }
15474
15671
 
15475
15672
  // 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";
15673
+ import { createHash as createHash6, randomBytes as randomBytes9 } from "crypto";
15674
+ import { lstatSync as lstatSync7, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
15478
15675
  import { isIP as isIP6 } from "net";
15479
15676
  import { tmpdir } from "os";
15480
15677
  import { basename as basename2, join as join10 } from "path";
@@ -15615,22 +15812,22 @@ var exactKeys5 = (value, keys, label) => {
15615
15812
  var ownerFile = (path, limit, label) => {
15616
15813
  if (!path.startsWith("/") || /[\r\n]/.test(path))
15617
15814
  throw new Error(`${label} path must be absolute`);
15618
- const metadata = lstatSync6(path);
15815
+ const metadata = lstatSync7(path);
15619
15816
  const uid = process.getuid?.() ?? metadata.uid;
15620
15817
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.uid !== uid || metadata.nlink !== 1 || (metadata.mode & 63) !== 0 || metadata.size < 1 || metadata.size > limit) {
15621
15818
  throw new Error(`${label} must be an owner-only regular file with one link and at most ${limit} bytes`);
15622
15819
  }
15623
- return readFileSync11(path);
15820
+ return readFileSync12(path);
15624
15821
  };
15625
15822
  var publicIdentity = (path) => {
15626
15823
  if (!path.startsWith("/") || /[\r\n]/.test(path))
15627
15824
  throw new Error("SSH public-key path must be absolute");
15628
- const metadata = lstatSync6(path);
15825
+ const metadata = lstatSync7(path);
15629
15826
  const uid = process.getuid?.() ?? metadata.uid;
15630
15827
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.uid !== uid || metadata.nlink !== 1 || metadata.size > 16384) {
15631
15828
  throw new Error("SSH public key must be a caller-owned regular file with one link");
15632
15829
  }
15633
- const value = readFileSync11(path, "utf8").trim();
15830
+ const value = readFileSync12(path, "utf8").trim();
15634
15831
  if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(value)) {
15635
15832
  throw new Error("SSH public key is malformed");
15636
15833
  }
@@ -15639,7 +15836,7 @@ var publicIdentity = (path) => {
15639
15836
  var socketPath = (path) => {
15640
15837
  if (!path.startsWith("/") || /[\r\n]/.test(path))
15641
15838
  throw new Error("SSH agent socket path must be absolute");
15642
- const metadata = lstatSync6(path);
15839
+ const metadata = lstatSync7(path);
15643
15840
  const uid = process.getuid?.() ?? metadata.uid;
15644
15841
  if (!metadata.isSocket() || metadata.isSymbolicLink() || metadata.uid !== uid) {
15645
15842
  throw new Error("SSH agent socket must be a caller-owned Unix socket");
@@ -15653,7 +15850,7 @@ var fingerprint = (key) => {
15653
15850
  if (!bytes.length || bytes.toString("base64").replace(/=+$/, "") !== encoded.replace(/=+$/, "")) {
15654
15851
  throw new Error("SSH host key is malformed");
15655
15852
  }
15656
- return `SHA256:${createHash5("sha256").update(bytes).digest("base64").replace(/=+$/, "")}`;
15853
+ return `SHA256:${createHash6("sha256").update(bytes).digest("base64").replace(/=+$/, "")}`;
15657
15854
  };
15658
15855
  var validateHop = (value, label) => {
15659
15856
  const hop = exactKeys5(value, ["address", "port", "user", "hostKey", "hostKeySha256"], label);
@@ -15798,8 +15995,9 @@ function planOperatorPlatformBootstrap(request, mode) {
15798
15995
  steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : [
15799
15996
  "verify pinned SSH transport and caller-approved SSH agent",
15800
15997
  "copy pinned Bun and packaged fz artifacts to a private staging directory",
15998
+ "verify and copy the one immutable API Git bundle plus its manifest",
15801
15999
  "stage the rewritten owner-only platform config and its named credential handoffs",
15802
- `run typed fz bootstrap platform${mode === "prepare" ? " prepare" : ""} --apply`,
16000
+ "run typed fz bootstrap platform --apply from the local bundle",
15803
16001
  "remove transient local and remote staging data"
15804
16002
  ],
15805
16003
  secretInputs: mode === "apply" ? [...attendedSecretNames(config), ...secretSources(config).map(([name]) => name)] : []
@@ -15828,7 +16026,7 @@ var defaultExec3 = async (argv2, options = {}) => {
15828
16026
  ]);
15829
16027
  return { exitCode, output: options.secret ? "" : `${stdout}${stderr}`.slice(0, 65536) };
15830
16028
  };
15831
- var checked4 = async (exec, argv2, label, options) => {
16029
+ var checked5 = async (exec, argv2, label, options) => {
15832
16030
  const result = await exec(argv2, options);
15833
16031
  if (result.exitCode !== 0)
15834
16032
  throw new Error(`${label} failed${result.output ? `: ${result.output.trim()}` : ""}`);
@@ -15841,7 +16039,7 @@ function writeKnownHosts(request, directory) {
15841
16039
  if (request.target.jump)
15842
16040
  lines.push(`${hostLabel(request.target.jump.address, request.target.jump.port)} ${request.target.jump.hostKey}`);
15843
16041
  const path = join10(directory, "known_hosts");
15844
- writeFileSync11(path, `${lines.join(`
16042
+ writeFileSync12(path, `${lines.join(`
15845
16043
  `)}
15846
16044
  `, { mode: 384, flag: "wx" });
15847
16045
  return path;
@@ -15882,7 +16080,7 @@ var safeRemoteArg = (value) => {
15882
16080
  };
15883
16081
  var remote = async (exec, request, knownHosts, argv2, label, secret = false, stdin) => {
15884
16082
  argv2.forEach(safeRemoteArg);
15885
- return checked4(exec, ["ssh", ...sshOptions(request, knownHosts), destination2(request.target), "--", ...argv2], label, { secret, stdin });
16083
+ return checked5(exec, ["ssh", ...sshOptions(request, knownHosts), destination2(request.target), "--", ...argv2], label, { secret, stdin });
15886
16084
  };
15887
16085
  var remoteRegularFileExists = async (exec, request, knownHosts, path) => {
15888
16086
  safeRemoteArg(path);
@@ -15903,36 +16101,45 @@ var remoteRegularFileExists = async (exec, request, knownHosts, path) => {
15903
16101
  };
15904
16102
  var copy = async (exec, request, knownHosts, local, remotePath, secret = false) => {
15905
16103
  safeRemoteArg(remotePath);
15906
- await checked4(exec, [
16104
+ await checked5(exec, [
15907
16105
  "scp",
15908
16106
  ...sshOptions(request, knownHosts, true),
15909
16107
  local,
15910
16108
  `${destination2(request.target)}:${remotePath}`
15911
16109
  ], "secure copy", { secret });
15912
16110
  };
15913
- function stageConfig(config, directory) {
16111
+ async function stageConfig(config, directory) {
15914
16112
  const rewritten = structuredClone(config);
15915
16113
  const staged = [];
15916
16114
  for (const [name, source] of secretSources(config)) {
15917
16115
  const bytes = ownerFile(source, SECRET_LIMIT, name);
15918
16116
  const local = join10(directory, name);
15919
- writeFileSync11(local, bytes, { mode: 384, flag: "wx" });
16117
+ writeFileSync12(local, bytes, { mode: 384, flag: "wx" });
15920
16118
  staged.push(name);
15921
16119
  const remotePath = `${REMOTE_STAGE}/${name}`;
15922
16120
  if (name === "cloudflare-handoff")
15923
16121
  rewritten.cloudflareHandoff.handoffFile = remotePath;
15924
16122
  }
16123
+ const bundle = await readBootstrapBundle(config.bootstrapBundle.bundleFile, config.bootstrapBundle.manifestFile);
16124
+ const bundleFiles = [
16125
+ { source: bundle.bundlePath, name: "bootstrap-api.bundle" },
16126
+ { source: bundle.manifestPath, name: "bootstrap-api.bundle.json" }
16127
+ ];
16128
+ rewritten.bootstrapBundle = {
16129
+ bundleFile: `${REMOTE_STAGE}/bootstrap-api.bundle`,
16130
+ manifestFile: `${REMOTE_STAGE}/bootstrap-api.bundle.json`
16131
+ };
15925
16132
  const path = join10(directory, "platform-config.json");
15926
- writeFileSync11(path, `${JSON.stringify(rewritten, null, 2)}
16133
+ writeFileSync12(path, `${JSON.stringify(rewritten, null, 2)}
15927
16134
  `, { mode: 384, flag: "wx" });
15928
- return { path, files: staged };
16135
+ return { path, files: staged, bundleFiles };
15929
16136
  }
15930
16137
  function stageMetalConfig(config, directory) {
15931
16138
  const rewritten = structuredClone(config);
15932
16139
  const files = [];
15933
16140
  if (config.agentSeedFile) {
15934
16141
  const name = "metal-agent-seed";
15935
- writeFileSync11(join10(directory, name), ownerFile(config.agentSeedFile, SECRET_LIMIT, name), {
16142
+ writeFileSync12(join10(directory, name), ownerFile(config.agentSeedFile, SECRET_LIMIT, name), {
15936
16143
  mode: 384,
15937
16144
  flag: "wx"
15938
16145
  });
@@ -15940,7 +16147,7 @@ function stageMetalConfig(config, directory) {
15940
16147
  files.push(name);
15941
16148
  }
15942
16149
  const path = join10(directory, "metal-config.json");
15943
- writeFileSync11(path, `${JSON.stringify(rewritten, null, 2)}
16150
+ writeFileSync12(path, `${JSON.stringify(rewritten, null, 2)}
15944
16151
  `, { mode: 384, flag: "wx" });
15945
16152
  return { path, files };
15946
16153
  }
@@ -15949,10 +16156,10 @@ async function verifiedBunArchive(directory, fetcher) {
15949
16156
  if (!response.ok)
15950
16157
  throw new Error("pinned Bun download failed");
15951
16158
  const bytes = new Uint8Array(await response.arrayBuffer());
15952
- if (createHash5("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256)
16159
+ if (createHash6("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256)
15953
16160
  throw new Error("pinned Bun checksum mismatch");
15954
16161
  const path = join10(directory, "bun.zip");
15955
- writeFileSync11(path, bytes, { mode: 384, flag: "wx" });
16162
+ writeFileSync12(path, bytes, { mode: 384, flag: "wx" });
15956
16163
  return path;
15957
16164
  }
15958
16165
  async function installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options) {
@@ -15962,7 +16169,7 @@ async function installPackagedAgent(request, knownHosts, exec, directory, remote
15962
16169
  [options.fzGitSshPath ?? fileURLToPath2(new URL("./fz-git-ssh.js", import.meta.url)), "fz-git-ssh.js"]
15963
16170
  ];
15964
16171
  for (const [artifact] of artifacts) {
15965
- if (!readFileSync11(artifact).length)
16172
+ if (!readFileSync12(artifact).length)
15966
16173
  throw new Error(`packaged artifact is empty: ${basename2(artifact)}`);
15967
16174
  }
15968
16175
  await remote(exec, request, knownHosts, ["/usr/bin/mkdir", "-m", "0700", remoteTemp], "remote staging");
@@ -16003,7 +16210,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
16003
16210
  socketPath(request.target.agentSocket);
16004
16211
  const exec = options.exec ?? defaultExec3;
16005
16212
  const directory = mkdtempSync(join10(tmpdir(), "forgezero-operator-bootstrap-"));
16006
- const remoteTemp = `/tmp/forgezero-operator-${randomBytes8(12).toString("hex")}`;
16213
+ const remoteTemp = `/tmp/forgezero-operator-${randomBytes9(12).toString("hex")}`;
16007
16214
  let knownHosts = "";
16008
16215
  try {
16009
16216
  knownHosts = writeKnownHosts(request, directory);
@@ -16015,18 +16222,16 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
16015
16222
  if (config.kind !== "platform")
16016
16223
  throw new Error("operator bootstrap requires a platform config");
16017
16224
  const secrets = mode === "apply" ? validatePlatformBootstrapSecrets(config, options.secrets) : undefined;
16018
- const staged = stageConfig(config, directory);
16225
+ const staged = await stageConfig(config, directory);
16019
16226
  await installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options);
16020
16227
  await copy(exec, request, knownHosts, staged.path, `${remoteTemp}/platform-config.json`, true);
16021
16228
  for (const name of staged.files)
16022
16229
  await copy(exec, request, knownHosts, join10(directory, name), `${remoteTemp}/${name}`, true);
16023
- for (const name of ["platform-config.json", ...staged.files])
16230
+ for (const bundle of staged.bundleFiles)
16231
+ await copy(exec, request, knownHosts, bundle.source, `${remoteTemp}/${bundle.name}`, true);
16232
+ for (const name of ["platform-config.json", ...staged.files, ...staged.bundleFiles.map(({ name: name2 }) => name2)])
16024
16233
  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");
16234
+ const command = ["/usr/bin/sudo", "-n", "/usr/local/bin/fz", "bootstrap", "platform", "credentials-stdin"];
16030
16235
  command.push("--bootstrap-config", `${REMOTE_STAGE}/platform-config.json`, "--apply");
16031
16236
  const output = await remote(exec, request, knownHosts, command, "remote typed bootstrap", mode === "apply", secrets ? `${JSON.stringify(secrets)}
16032
16237
  ` : undefined);
@@ -16037,7 +16242,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
16037
16242
  return;
16038
16243
  });
16039
16244
  }
16040
- rmSync6(directory, { recursive: true, force: true });
16245
+ rmSync7(directory, { recursive: true, force: true });
16041
16246
  }
16042
16247
  }
16043
16248
  async function applyOperatorMetalBootstrap(request, mode, options = {}) {
@@ -16046,7 +16251,7 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
16046
16251
  socketPath(request.target.agentSocket);
16047
16252
  const exec = options.exec ?? defaultExec3;
16048
16253
  const directory = mkdtempSync(join10(tmpdir(), "forgezero-operator-metal-"));
16049
- const remoteTemp = `/tmp/forgezero-operator-${randomBytes8(12).toString("hex")}`;
16254
+ const remoteTemp = `/tmp/forgezero-operator-${randomBytes9(12).toString("hex")}`;
16050
16255
  let knownHosts = "";
16051
16256
  try {
16052
16257
  knownHosts = writeKnownHosts(request, directory);
@@ -16090,7 +16295,7 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
16090
16295
  return;
16091
16296
  });
16092
16297
  }
16093
- rmSync6(directory, { recursive: true, force: true });
16298
+ rmSync7(directory, { recursive: true, force: true });
16094
16299
  }
16095
16300
  }
16096
16301
 
@@ -16103,9 +16308,6 @@ function platformGenesisBootstrapConfigs(template, nodes) {
16103
16308
  if (nodes.some((node) => !node.nodeHostname || !node.cloudflareHandoffFile)) {
16104
16309
  throw new Error("every platform genesis node requires its public hostname and node-specific Cloudflare handoff");
16105
16310
  }
16106
- if (template.branch !== (environment === "production" ? "main" : "dev")) {
16107
- throw new Error("platform genesis branch must match the selected environment");
16108
- }
16109
16311
  const coordinators = fleet.map((guest) => `http://${guest.address}:8529`);
16110
16312
  return fleet.map((guest, index) => {
16111
16313
  const node = nodes[index];
@@ -16144,7 +16346,7 @@ function platformGenesisBootstrapConfigs(template, nodes) {
16144
16346
  }
16145
16347
 
16146
16348
  // src/host-maintenance.ts
16147
- import { lstatSync as lstatSync7, readFileSync as readFileSync12 } from "fs";
16349
+ import { lstatSync as lstatSync8, readFileSync as readFileSync13 } from "fs";
16148
16350
  var ROOT = "/opt/forgezero";
16149
16351
  var SLOT_FILE = `${ROOT}/.forge-slot`;
16150
16352
  var SHARED_ENV = `${ROOT}/shared/.env`;
@@ -16152,9 +16354,9 @@ var JWT = "/etc/forgezero/creds/arangodb-jwt.cred";
16152
16354
  var FZ = "/usr/local/bin/fz";
16153
16355
  var localRuntime = () => ({
16154
16356
  uid: () => process.getuid?.() ?? -1,
16155
- read: (path) => readFileSync12(path, "utf8"),
16357
+ read: (path) => readFileSync13(path, "utf8"),
16156
16358
  inspect(path) {
16157
- const value = lstatSync7(path);
16359
+ const value = lstatSync8(path);
16158
16360
  return {
16159
16361
  regular: value.isFile(),
16160
16362
  symbolic: value.isSymbolicLink(),
@@ -16275,8 +16477,8 @@ async function applyHostMaintenance(request, runtime = localRuntime()) {
16275
16477
  }
16276
16478
 
16277
16479
  // 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";
16480
+ import { existsSync as existsSync11, lstatSync as lstatSync9, readFileSync as readFileSync14, realpathSync as realpathSync5 } from "fs";
16481
+ import { isAbsolute as isAbsolute5, join as join11, relative as relative2, resolve as resolve9 } from "path";
16280
16482
  var API_OPERATION_ENTRYPOINTS = {
16281
16483
  "dev-reset": ["src", "server", "maintenance", "dev-reset.ts"],
16282
16484
  "db-backup": ["src", "server", "maintenance", "snapshot-backup.ts"],
@@ -16346,12 +16548,12 @@ function unsupportedRepositoryCliOption(argv2) {
16346
16548
  }
16347
16549
  function manifestName(root) {
16348
16550
  const manifestPath = join11(root, "package.json");
16349
- if (!existsSync10(manifestPath)) {
16551
+ if (!existsSync11(manifestPath)) {
16350
16552
  throw new Error(`No package.json exists at repository root ${root}.`);
16351
16553
  }
16352
16554
  let parsed;
16353
16555
  try {
16354
- parsed = JSON.parse(readFileSync13(manifestPath, "utf8"));
16556
+ parsed = JSON.parse(readFileSync14(manifestPath, "utf8"));
16355
16557
  } catch (cause) {
16356
16558
  throw new Error(`Cannot read ${manifestPath}: ${cause instanceof Error ? cause.message : String(cause)}`);
16357
16559
  }
@@ -16362,21 +16564,21 @@ function manifestName(root) {
16362
16564
  }
16363
16565
  function checkedEntrypoint(root, parts) {
16364
16566
  const candidate = join11(root, ...parts);
16365
- if (!existsSync10(candidate) || !lstatSync8(candidate).isFile()) {
16567
+ if (!existsSync11(candidate) || !lstatSync9(candidate).isFile()) {
16366
16568
  throw new Error(`The reviewed operation entrypoint is missing: ${candidate}`);
16367
16569
  }
16368
- if (lstatSync8(candidate).isSymbolicLink()) {
16570
+ if (lstatSync9(candidate).isSymbolicLink()) {
16369
16571
  throw new Error(`The reviewed operation entrypoint must not be a symbolic link: ${candidate}`);
16370
16572
  }
16371
16573
  const actual = realpathSync5(candidate);
16372
16574
  const within = relative2(root, actual);
16373
- if (within.startsWith("..") || isAbsolute4(within)) {
16575
+ if (within.startsWith("..") || isAbsolute5(within)) {
16374
16576
  throw new Error(`The reviewed operation entrypoint escapes repository root ${root}.`);
16375
16577
  }
16376
16578
  return actual;
16377
16579
  }
16378
16580
  function resolveRepositoryOperation(operation, requestedRoot) {
16379
- const root = realpathSync5(resolve8(requestedRoot));
16581
+ const root = realpathSync5(resolve9(requestedRoot));
16380
16582
  const name = manifestName(root);
16381
16583
  if (operation in API_OPERATION_ENTRYPOINTS) {
16382
16584
  const parts = API_OPERATION_ENTRYPOINTS[operation];
@@ -16677,7 +16879,7 @@ async function api(options, path, init) {
16677
16879
  return { status: response.status, body };
16678
16880
  }
16679
16881
  function sleep(ms) {
16680
- return new Promise((resolve10) => setTimeout(resolve10, ms));
16882
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
16681
16883
  }
16682
16884
  function browserCommand(url) {
16683
16885
  if (process.platform === "darwin")
@@ -16762,7 +16964,7 @@ function requireSession() {
16762
16964
  function readOwnerOnlySecret(path, label) {
16763
16965
  let metadata;
16764
16966
  try {
16765
- metadata = lstatSync9(path);
16967
+ metadata = lstatSync10(path);
16766
16968
  } catch {
16767
16969
  throw new Error(`The ${label} file ${path} is unreadable.`);
16768
16970
  }
@@ -16776,7 +16978,7 @@ function readOwnerOnlySecret(path, label) {
16776
16978
  if (uid !== undefined && uid !== 0 && metadata.uid !== uid) {
16777
16979
  throw new Error(`The ${label} file ${path} is not owned by the current user.`);
16778
16980
  }
16779
- const value = readFileSync14(path, "utf8").trim();
16981
+ const value = readFileSync15(path, "utf8").trim();
16780
16982
  if (!value)
16781
16983
  throw new Error(`The ${label} file ${path} is empty.`);
16782
16984
  return value;
@@ -16957,7 +17159,7 @@ async function cmdApi(options, args) {
16957
17159
  let body = undefined;
16958
17160
  if (options.data !== undefined && options.dataFile)
16959
17161
  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;
17162
+ const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync15(options.dataFile, "utf8") : options.data;
16961
17163
  if (encoded !== undefined)
16962
17164
  body = JSON.parse(encoded);
16963
17165
  const result = await api(options, `${url.pathname}${url.search}`, { method, body });
@@ -17051,7 +17253,7 @@ async function cmdAgent(options, args) {
17051
17253
  seedCredentialPath: process.env.FZ_SEED_CREDENTIAL_PATH,
17052
17254
  gitCredentialPath,
17053
17255
  gitPublicKeyPath,
17054
- generateGitIdentity: true,
17256
+ generateGitIdentity: process.env.FZ_GENERATE_GIT_DEPLOY_KEY === "true",
17055
17257
  controlSocketPath: process.env.FZ_CONTROL_SOCKET,
17056
17258
  repository: process.env.FZ_DEPLOY_REPO,
17057
17259
  branch: process.env.FZ_DEPLOY_BRANCH,
@@ -17108,19 +17310,19 @@ async function cmdAgent(options, args) {
17108
17310
  return 0;
17109
17311
  }
17110
17312
  try {
17111
- writeFileSync12(plan.unitPath, plan.unit, { mode: 420 });
17313
+ writeFileSync13(plan.unitPath, plan.unit, { mode: 420 });
17112
17314
  out.ok(`Wrote ${plan.unitPath}`);
17113
17315
  for (const auxiliary of plan.auxiliaryUnits) {
17114
- mkdirSync11(dirname12(auxiliary.path), { recursive: true, mode: 493 });
17115
- writeFileSync12(auxiliary.path, auxiliary.unit, { mode: 420 });
17316
+ mkdirSync12(dirname13(auxiliary.path), { recursive: true, mode: 493 });
17317
+ writeFileSync13(auxiliary.path, auxiliary.unit, { mode: 420 });
17116
17318
  out.ok(`Wrote ${auxiliary.path}`);
17117
17319
  }
17118
17320
  if (options.enrol) {
17119
- if (!existsSync11(enrolStatePath) && !existsSync11(enrolTokenCredentialPath)) {
17321
+ if (!existsSync12(enrolStatePath) && !existsSync12(enrolTokenCredentialPath)) {
17120
17322
  const token = await bootstrapSecret("ForgeZero one-time enrolment token");
17121
17323
  if (!/^fze_[A-Za-z0-9_-]{40,100}$/.test(token))
17122
17324
  throw new Error("A valid fze_ enrolment token was not provided.");
17123
- mkdirSync11(dirname12(enrolTokenCredentialPath), { recursive: true, mode: 448 });
17325
+ mkdirSync12(dirname13(enrolTokenCredentialPath), { recursive: true, mode: 448 });
17124
17326
  const sealing = Bun.spawn(["systemd-creds", "encrypt", "--name=enrol-token", "-", enrolTokenCredentialPath], { stdin: "pipe", stdout: "ignore", stderr: "pipe" });
17125
17327
  if (sealing.stdin && typeof sealing.stdin !== "number") {
17126
17328
  sealing.stdin.write(`${token}
@@ -17136,11 +17338,13 @@ async function cmdAgent(options, args) {
17136
17338
  for (const step3 of transcript)
17137
17339
  out.ok(step3.label);
17138
17340
  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();
17341
+ if (existsSync12(gitPublicKeyPath)) {
17342
+ out.line();
17343
+ out.line(" Compatibility SSH deploy public key:");
17344
+ out.line();
17345
+ out.line(` ${readFileSync15(gitPublicKeyPath, "utf8").trim()}`);
17346
+ out.line();
17347
+ }
17144
17348
  return 0;
17145
17349
  } catch (cause) {
17146
17350
  out.fail(`Could not write ${plan.unitPath}: ${cause.message}`);
@@ -17244,10 +17448,10 @@ function interactiveMetalBootstrap() {
17244
17448
  });
17245
17449
  }
17246
17450
  function writeBootstrapConfig(path, config) {
17247
- if (!isAbsolute5(path) || resolve9(path) !== path)
17451
+ if (!isAbsolute6(path) || resolve10(path) !== path)
17248
17452
  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)}
17453
+ mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
17454
+ writeFileSync13(path, `${JSON.stringify(config, null, 2)}
17251
17455
  `, { mode: 384, flag: "wx" });
17252
17456
  return path;
17253
17457
  }
@@ -17308,11 +17512,11 @@ async function interactiveCloudflareBootstrap() {
17308
17512
  });
17309
17513
  }
17310
17514
  function genesisOutputDirectory(path) {
17311
- if (!isAbsolute5(path) || resolve9(path) !== path)
17515
+ if (!isAbsolute6(path) || resolve10(path) !== path)
17312
17516
  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);
17517
+ if (!existsSync12(path))
17518
+ mkdirSync12(path, { recursive: true, mode: 448 });
17519
+ const metadata = lstatSync10(path);
17316
17520
  const uid = process.getuid?.();
17317
17521
  if (!metadata.isDirectory() || metadata.isSymbolicLink() || (metadata.mode & 63) !== 0 || uid !== undefined && uid !== 0 && metadata.uid !== uid) {
17318
17522
  throw new Error("platform genesis output must be an owner-only directory owned by the current user");
@@ -17356,8 +17560,8 @@ function interactiveBootstrap(kind, genesis = false) {
17356
17560
  const nodeHostname = bootstrapAnswer(genesis ? `${computeReference} public node hostname` : "Public node hostname");
17357
17561
  const apiUrl = bootstrapAnswer("Platform API URL");
17358
17562
  const appOrigin = bootstrapAnswer("Public App origin");
17359
- const repository = bootstrapAnswer("API Git repository");
17360
- const branch = bootstrapAnswer("Deployment branch", environment === "production" ? "main" : "dev");
17563
+ const bundleFile = bootstrapAnswer("Bootstrap API Git bundle (absolute path)");
17564
+ const manifestFile = bootstrapAnswer("Bootstrap bundle manifest (absolute path)", `${bundleFile}.json`);
17361
17565
  const telemetryEndpoint = bootstrapAnswer("Public HTTPS Agent OTLP endpoint");
17362
17566
  const collectorUnit = FORGEZERO_OTEL_COLLECTOR_UNIT;
17363
17567
  const replicationFactor = Number(bootstrapAnswer("Database replication factor", "3"));
@@ -17386,8 +17590,7 @@ function interactiveBootstrap(kind, genesis = false) {
17386
17590
  computeReference,
17387
17591
  nodeHostname,
17388
17592
  apiUrl,
17389
- repository,
17390
- branch,
17593
+ bootstrapBundle: { bundleFile, manifestFile },
17391
17594
  telemetryEndpoint,
17392
17595
  database: {
17393
17596
  role,
@@ -17431,8 +17634,6 @@ function interactiveBootstrap(kind, genesis = false) {
17431
17634
  ...backupEndpoint && backupRegion && backupBucket && backupAccessKeyId ? {
17432
17635
  backup: { endpoint: backupEndpoint, region: backupRegion, bucket: backupBucket, accessKeyId: backupAccessKeyId }
17433
17636
  } : {},
17434
- repository,
17435
- branch,
17436
17637
  deployProfile: environment
17437
17638
  },
17438
17639
  serviceUser: "forgezero-api",
@@ -17478,8 +17679,8 @@ async function cmdBootstrap(options, args) {
17478
17679
  }
17479
17680
  if (operation === "platform" && args[1] === "remote") {
17480
17681
  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]");
17682
+ if (!mode || !["apply", "status"].includes(mode) || args[3] !== undefined) {
17683
+ throw new Error("Usage: fz bootstrap platform remote <apply|status> --bootstrap-config <owner-only-request.json> [--apply]");
17483
17684
  }
17484
17685
  if (!options.bootstrapConfigPath)
17485
17686
  throw new Error("remote platform bootstrap requires --bootstrap-config");
@@ -17497,6 +17698,18 @@ async function cmdBootstrap(options, args) {
17497
17698
  out.line(JSON.stringify(await applyOperatorPlatformBootstrap(request, mode, { secrets: secrets2 }), null, 2));
17498
17699
  return 0;
17499
17700
  }
17701
+ if (operation === "platform" && args[1] === "bundle") {
17702
+ if (args[2] !== undefined || !options.outputPath) {
17703
+ throw new Error("Usage: fz bootstrap platform bundle --root <clean-api-checkout> --branch <main|dev> --output <absolute.bundle>");
17704
+ }
17705
+ const result2 = await buildBootstrapBundle({
17706
+ repositoryRoot: options.projectRoot,
17707
+ outputPath: options.outputPath,
17708
+ branch: options.branch
17709
+ });
17710
+ out.line(JSON.stringify(result2, null, 2));
17711
+ return 0;
17712
+ }
17500
17713
  if (operation === "metal" && args[1] === "remote") {
17501
17714
  const mode = args[2];
17502
17715
  if (!mode || !["apply", "genesis", "rehearsal", "rehearsal-cleanup", "status"].includes(mode) || args[3] !== undefined) {
@@ -17553,31 +17766,12 @@ async function cmdBootstrap(options, args) {
17553
17766
  await runCloudflareBootstrapCommand(options.bootstrapConfigPath, options.apply, tokens);
17554
17767
  return 0;
17555
17768
  }
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
17769
  const credentialStdin = operation === "platform" && args[1] === "credentials-stdin";
17576
17770
  if (credentialStdin && args[2] !== undefined)
17577
17771
  throw new Error("platform credential stdin accepts no additional positional arguments");
17578
17772
  const installedBootstrapKind = () => resolveInstalledBootstrapKind({
17579
- metal: existsSync11(METAL_BOOTSTRAP_STATE_PATH),
17580
- compute: existsSync11(BOOTSTRAP_STATE_PATH)
17773
+ metal: existsSync12(METAL_BOOTSTRAP_STATE_PATH),
17774
+ compute: existsSync12(BOOTSTRAP_STATE_PATH)
17581
17775
  });
17582
17776
  if (operation === "status") {
17583
17777
  if (installedBootstrapKind() === "metal") {
@@ -17618,7 +17812,7 @@ async function cmdBootstrap(options, args) {
17618
17812
  return 0;
17619
17813
  }
17620
17814
  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]");
17815
+ throw new Error("Usage: fz bootstrap config <metal|platform|cloudflare>|platform [bundle|remote <apply|status>|cloudflare [verify|finalize]]|metal [remote <apply|genesis|rehearsal|rehearsal-cleanup|status>]|status|repair [--bootstrap-config <path>] [--apply]");
17622
17816
  }
17623
17817
  const config = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : operation === "repair" ? (() => {
17624
17818
  throw new Error("repair requires --bootstrap-config so immutable coordinates are revalidated");
@@ -17715,7 +17909,7 @@ async function cmdUnlock(options) {
17715
17909
  if (options.key || options.userExplicit) {
17716
17910
  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
17911
  }
17718
- const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync14(0, "utf8").trim() : "";
17912
+ const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync15(0, "utf8").trim() : "";
17719
17913
  const phrase = phraseText ? phraseText.split(/\s+/) : [];
17720
17914
  if (phrase.length !== 24) {
17721
17915
  throw new Error(`Recovery phrase must contain exactly 24 words; received ${phrase.length}. Use --phrase-file or --phrase-stdin.`);
@@ -17931,7 +18125,7 @@ function projectFromCheckout(options) {
17931
18125
  try {
17932
18126
  return loadConfig({
17933
18127
  cwd: options.projectRoot,
17934
- readFile: (path) => existsSync11(path) ? readFileSync14(path, "utf8") : undefined
18128
+ readFile: (path) => existsSync12(path) ? readFileSync15(path, "utf8") : undefined
17935
18129
  }).config.project;
17936
18130
  } catch (cause) {
17937
18131
  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 +18158,10 @@ async function waitForRuns(options, pipelineKey, keys) {
17964
18158
  while (Date.now() < deadline) {
17965
18159
  const payload = await deploymentRequest(options, `/runs?pipelineKey=${encodeURIComponent(pipelineKey)}`);
17966
18160
  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");
18161
+ const selected = runs.filter((run2) => run2._key && keys.includes(run2._key));
18162
+ if (selected.length === keys.length && selected.every((run2) => terminal.has(run2.outcome ?? ""))) {
18163
+ out.line(JSON.stringify({ ok: selected.every((run2) => run2.outcome === "deployed"), runs: selected }, null, 2));
18164
+ return selected.every((run2) => run2.outcome === "deployed");
17971
18165
  }
17972
18166
  await sleep(2000);
17973
18167
  }
@@ -17994,7 +18188,7 @@ async function cmdDeploy(options, args) {
17994
18188
  out.ok(`Initialized legacy .fz/deploy.json (${created.summary.digest}).`);
17995
18189
  return 0;
17996
18190
  }
17997
- initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(resolve9(options.projectRoot)), force: options.force });
18191
+ initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(resolve10(options.projectRoot)), force: options.force });
17998
18192
  const compiled = await compileDeploymentProject(options.projectRoot);
17999
18193
  if (options.json)
18000
18194
  out.line(JSON.stringify({ source: compiled.source, output: compiled.output, digest: compiled.digest }, null, 2));
@@ -18019,7 +18213,7 @@ async function cmdDeploy(options, args) {
18019
18213
  return problems.length === 0 ? 0 : 1;
18020
18214
  }
18021
18215
  if (operation === "check" || operation === "sync") {
18022
- if (existsSync11(resolve9(options.projectRoot, DEPLOY_SOURCE_FILE))) {
18216
+ if (existsSync12(resolve10(options.projectRoot, DEPLOY_SOURCE_FILE))) {
18023
18217
  const expected = await compileDeploymentProject(options.projectRoot, { write: false });
18024
18218
  const actual = inspectCompiledDeployment(options.projectRoot);
18025
18219
  const current = canonicalJson(expected.plan) === canonicalJson(actual.plan);
@@ -18095,7 +18289,7 @@ async function cmdDeploy(options, args) {
18095
18289
  branch: options.branch,
18096
18290
  cloneUrl: required(options.cloneUrl, "--clone-url"),
18097
18291
  sourceAuth,
18098
- ...options.knownHostsFile ? { knownHosts: readFileSync14(options.knownHostsFile, "utf8") } : {},
18292
+ ...options.knownHostsFile ? { knownHosts: readFileSync15(options.knownHostsFile, "utf8") } : {},
18099
18293
  projectKey
18100
18294
  } });
18101
18295
  const pipelineKey2 = String(created.pipelineKey);
@@ -18135,7 +18329,7 @@ async function cmdDeploy(options, args) {
18135
18329
  out.line(JSON.stringify(released, null, 2));
18136
18330
  return 0;
18137
18331
  }
18138
- const keys = (released.runs ?? []).flatMap((run) => run._key ? [run._key] : []);
18332
+ const keys = (released.runs ?? []).flatMap((run2) => run2._key ? [run2._key] : []);
18139
18333
  if (keys.length === 0)
18140
18334
  throw new Error("The API accepted the release but returned no deployment runs.");
18141
18335
  return await waitForRuns(options, pipelineKey, keys) ? 0 : 1;
@@ -18191,11 +18385,11 @@ function usage() {
18191
18385
  fz agent install Install the node agent as a systemd service, so
18192
18386
  applications on this box read secrets through a
18193
18387
  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>
18388
+ fz bootstrap platform bundle
18389
+ Build one clean exact-revision API Git bundle for genesis
18390
+ fz bootstrap platform remote <apply|status>
18197
18391
  Run typed bootstrap from the operator laptop through
18198
- a pinned host and caller-approved SSH agent
18392
+ a pinned host; apply copies and verifies that bundle
18199
18393
  fz bootstrap platform Install/repair a typed elastic platform compute
18200
18394
  fz bootstrap config <metal|platform|cloudflare>
18201
18395
  Write a validated owner-only metal/Cloudflare file or