@forgezero/agent 0.1.62 → 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-agent.js CHANGED
@@ -2,8 +2,8 @@
2
2
  // @bun
3
3
 
4
4
  // src/index.ts
5
- import { randomBytes as randomBytes6 } from "crypto";
6
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync16, existsSync as existsSync20, mkdirSync as mkdirSync16, chmodSync as chmodSync17, lstatSync as lstatSync8, statfsSync as statfsSync2 } from "fs";
5
+ import { randomBytes as randomBytes7 } from "crypto";
6
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync17, existsSync as existsSync21, mkdirSync as mkdirSync17, chmodSync as chmodSync18, lstatSync as lstatSync9, statfsSync as statfsSync2 } from "fs";
7
7
  import { cpus, totalmem } from "os";
8
8
  import { dirname as dirname12, join as join12 } from "path";
9
9
 
@@ -4641,8 +4641,8 @@ function safeOp(line) {
4641
4641
  }
4642
4642
 
4643
4643
  // src/deployment.ts
4644
- import { chmodSync as chmodSync3, existsSync as existsSync3, lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, statfsSync, writeFileSync as writeFileSync2 } from "fs";
4645
- import { createHash as createHash4, randomUUID } from "crypto";
4644
+ import { chmodSync as chmodSync4, existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, statfsSync, writeFileSync as writeFileSync3 } from "fs";
4645
+ import { createHash as createHash5, randomUUID } from "crypto";
4646
4646
  import { dirname, isAbsolute as isAbsolute2, join as join2 } from "path";
4647
4647
 
4648
4648
  // ../runtime/dist/queue.js
@@ -4980,7 +4980,8 @@ var SOFTWARE_CATALOG = [
4980
4980
  { id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4981
4981
  { id: "cloudflare-warp", version: "2026.6.822.0-min", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4982
4982
  { id: "ufw", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4983
- { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
4983
+ { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4984
+ { id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
4984
4985
  ];
4985
4986
  var UBUNTU_2604_X64 = [
4986
4987
  { requirement: { id: "bun", version: "1.3.14" } },
@@ -4990,7 +4991,8 @@ var UBUNTU_2604_X64 = [
4990
4991
  { requirement: { id: "cloudflared", version: "2026.7.3" } },
4991
4992
  { requirement: { id: "cloudflare-warp", version: "2026.6.822.0-min" } },
4992
4993
  { requirement: { id: "ufw", version: "ubuntu-26.04" } },
4993
- { requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
4994
+ { requirement: { id: "openssh-client", version: "ubuntu-26.04" } },
4995
+ { requirement: { id: "git", version: "ubuntu-26.04" } }
4994
4996
  ];
4995
4997
  var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
4996
4998
  var DOCKER_DAEMON_PATH = "/etc/docker/daemon.json";
@@ -5077,7 +5079,7 @@ async function executeSoftwareOperation(operation) {
5077
5079
  const supported = observed && observed.some((part, index) => part > minimum[index] && observed.slice(0, index).every((prior, priorIndex) => prior === minimum[priorIndex])) || observed?.every((part, index) => part === minimum[index]);
5078
5080
  return { ...result, exitCode: result.exitCode === 0 && supported ? 0 : 1 };
5079
5081
  });
5080
- const binaries = software === "ufw" ? ["/usr/sbin/ufw"] : ["/usr/bin/ssh", "/usr/bin/scp", "/usr/bin/ssh-keyscan", "/usr/bin/ssh-keygen"];
5082
+ const binaries = software === "ufw" ? ["/usr/sbin/ufw"] : software === "git" ? ["/usr/bin/git"] : ["/usr/bin/ssh", "/usr/bin/scp", "/usr/bin/ssh-keyscan", "/usr/bin/ssh-keygen"];
5081
5083
  try {
5082
5084
  binaries.forEach((binary) => accessSync(binary));
5083
5085
  return { exitCode: 0, output: "" };
@@ -5085,7 +5087,7 @@ async function executeSoftwareOperation(operation) {
5085
5087
  return { exitCode: 1, output: cause instanceof Error ? cause.message : String(cause) };
5086
5088
  }
5087
5089
  }
5088
- if (software === "docker" || software === "nginx" || software === "ufw" || software === "openssh-client") {
5090
+ if (software === "docker" || software === "nginx" || software === "ufw" || software === "openssh-client" || software === "git") {
5089
5091
  const packageName = software === "openssh-client" ? "openssh-client" : software === "docker" ? "docker.io" : software;
5090
5092
  const installed = await aptInstall(packageName);
5091
5093
  if (installed.exitCode !== 0 || software !== "nginx" && software !== "docker")
@@ -5185,7 +5187,7 @@ function validateSoftwareRequirements(value, _options = {}) {
5185
5187
  if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
5186
5188
  throw new Error("software requirement contains an unknown field");
5187
5189
  }
5188
- 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)) {
5190
+ 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)) {
5189
5191
  throw new Error("software requirement coordinate is invalid");
5190
5192
  }
5191
5193
  const requirement = { id: row.id, version: row.version };
@@ -7120,6 +7122,65 @@ async function runDeploymentPlan(options) {
7120
7122
  // src/deploy-compiler.ts
7121
7123
  var DEPLOY_PLAN_FILE = ".fz/deploy.plan.json";
7122
7124
 
7125
+ // src/bootstrap-bundle.ts
7126
+ import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
7127
+ import {
7128
+ chmodSync as chmodSync3,
7129
+ createReadStream,
7130
+ existsSync as existsSync3,
7131
+ lstatSync,
7132
+ mkdirSync as mkdirSync2,
7133
+ readFileSync as readFileSync2,
7134
+ renameSync as renameSync2,
7135
+ rmSync as rmSync2,
7136
+ writeFileSync as writeFileSync2
7137
+ } from "fs";
7138
+ var BOOTSTRAP_BUNDLE_FORMAT = 1;
7139
+ var BOOTSTRAP_BUNDLE_KIND = "forgezero-api-git-bundle";
7140
+ var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
7141
+ async function sha256File(path2) {
7142
+ const hash = createHash4("sha256");
7143
+ await new Promise((resolveDone, reject) => {
7144
+ const stream = createReadStream(path2);
7145
+ stream.on("data", (chunk) => hash.update(chunk));
7146
+ stream.on("error", reject);
7147
+ stream.on("end", resolveDone);
7148
+ });
7149
+ return hash.digest("hex");
7150
+ }
7151
+ var branchName = (value) => {
7152
+ 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("-")) {
7153
+ throw new Error("bootstrap bundle branch is malformed");
7154
+ }
7155
+ return value;
7156
+ };
7157
+ function parseBootstrapBundleManifest(value) {
7158
+ if (!value || typeof value !== "object" || Array.isArray(value))
7159
+ throw new Error("bootstrap bundle manifest must be an object");
7160
+ const source = value;
7161
+ const allowed = ["format", "kind", "branch", "revision", "sha256", "bytes", "createdAt"];
7162
+ const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
7163
+ if (unknown.length)
7164
+ throw new Error(`bootstrap bundle manifest contains unknown field ${unknown[0]}`);
7165
+ if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
7166
+ throw new Error("bootstrap bundle manifest format is unsupported");
7167
+ }
7168
+ branchName(source.branch);
7169
+ if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
7170
+ throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
7171
+ }
7172
+ if (typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256)) {
7173
+ throw new Error("bootstrap bundle digest is malformed");
7174
+ }
7175
+ if (!Number.isSafeInteger(source.bytes) || Number(source.bytes) < 1 || Number(source.bytes) > MAX_BOOTSTRAP_BUNDLE_BYTES) {
7176
+ throw new Error("bootstrap bundle size is outside the supported boundary");
7177
+ }
7178
+ if (typeof source.createdAt !== "string" || Number.isNaN(Date.parse(source.createdAt))) {
7179
+ throw new Error("bootstrap bundle creation time is malformed");
7180
+ }
7181
+ return source;
7182
+ }
7183
+
7123
7184
  // src/deployment.ts
7124
7185
  class DeploymentError extends Error {
7125
7186
  code;
@@ -7165,24 +7226,24 @@ function persistCapacityCalibration(args) {
7165
7226
  if (!isAbsolute2(args.directory)) {
7166
7227
  throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be an absolute Agent-owned path");
7167
7228
  }
7168
- if (!existsSync3(args.directory)) {
7169
- const parent = lstatSync(dirname(args.directory));
7229
+ if (!existsSync4(args.directory)) {
7230
+ const parent = lstatSync2(dirname(args.directory));
7170
7231
  if (!parent.isDirectory() || parent.isSymbolicLink()) {
7171
7232
  throw new DeploymentError("PIPELINE_FAILED", "capacity evidence parent must be a real Agent-owned directory");
7172
7233
  }
7173
- mkdirSync2(args.directory, { mode: 448 });
7234
+ mkdirSync3(args.directory, { mode: 448 });
7174
7235
  }
7175
- const stats = lstatSync(args.directory);
7236
+ const stats = lstatSync2(args.directory);
7176
7237
  if (!stats.isDirectory() || stats.isSymbolicLink()) {
7177
7238
  throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be a real Agent-owned directory");
7178
7239
  }
7179
- const identity = createHash4("sha256").update(`${args.deploymentKey}\x00${args.profile}\x00${args.revision}`).digest("hex").slice(0, 24);
7240
+ const identity = createHash5("sha256").update(`${args.deploymentKey}\x00${args.profile}\x00${args.revision}`).digest("hex").slice(0, 24);
7180
7241
  const path2 = join2(args.directory, `${identity}-${args.measuredAtTs}-${randomUUID().slice(0, 8)}.json`);
7181
7242
  const next = `${path2}.next`;
7182
7243
  const recommendedCoordinate = {
7183
7244
  FZ_CONCURRENCY_LIMIT: String(args.calibration.recommendedConcurrency)
7184
7245
  };
7185
- writeFileSync2(next, `${JSON.stringify({
7246
+ writeFileSync3(next, `${JSON.stringify({
7186
7247
  format: 1,
7187
7248
  kind: "forgezero-node-capacity-calibration",
7188
7249
  deploymentKey: args.deploymentKey,
@@ -7194,22 +7255,29 @@ function persistCapacityCalibration(args) {
7194
7255
  calibration: args.calibration
7195
7256
  }, null, 2)}
7196
7257
  `, { mode: 384, flag: "wx" });
7197
- chmodSync3(next, 384);
7198
- renameSync2(next, path2);
7258
+ chmodSync4(next, 384);
7259
+ renameSync3(next, path2);
7199
7260
  return path2;
7200
7261
  }
7201
7262
  function createDeploymentManager(options) {
7202
- try {
7203
- gitNetworkTarget(options.repository);
7204
- } catch (cause) {
7205
- throw new DeploymentError("SOURCE_FAILED", cause instanceof GitEgressError ? cause.message : "The Git source is malformed.");
7263
+ const bootstrapBundle = options.bootstrapBundle ? { path: options.bootstrapBundle.path, manifest: parseBootstrapBundleManifest(options.bootstrapBundle.manifest) } : undefined;
7264
+ if (bootstrapBundle) {
7265
+ if (!isAbsolute2(bootstrapBundle.path) || bootstrapBundle.path !== options.repository || bootstrapBundle.manifest.branch !== options.branch) {
7266
+ throw new DeploymentError("SOURCE_FAILED", "The bootstrap bundle source coordinates disagree.");
7267
+ }
7268
+ } else {
7269
+ try {
7270
+ gitNetworkTarget(options.repository);
7271
+ } catch (cause) {
7272
+ throw new DeploymentError("SOURCE_FAILED", cause instanceof GitEgressError ? cause.message : "The Git source is malformed.");
7273
+ }
7206
7274
  }
7207
7275
  const queue = createQueue({ width: options.width ?? 4 });
7208
7276
  const activeRevisions = new Map;
7209
7277
  const exec = options.exec ?? spawnExact;
7210
7278
  const projectExec = options.projectExec ?? exec;
7211
7279
  const now = options.now ?? Date.now;
7212
- const readDefinition = options.readDefinition ?? ((path2) => JSON.parse(readFileSync2(path2, "utf8")));
7280
+ const readDefinition = options.readDefinition ?? ((path2) => JSON.parse(readFileSync3(path2, "utf8")));
7213
7281
  const credentialsDirectory = process.env.CREDENTIALS_DIRECTORY;
7214
7282
  const gitCredentialPath = options.gitCredentialPath ?? (credentialsDirectory ? join2(credentialsDirectory, "git-deploy-key") : undefined);
7215
7283
  const knownHostsPath = options.knownHostsPath ?? "/etc/forgezero/git/known_hosts";
@@ -7221,13 +7289,13 @@ function createDeploymentManager(options) {
7221
7289
  if (content.length > 16385 || /\r|\0/.test(content)) {
7222
7290
  throw new DeploymentError("SOURCE_FAILED", "The pinned Git host keys are malformed.");
7223
7291
  }
7224
- if (existsSync3(knownHostsPath) && readFileSync2(knownHostsPath, "utf8") === content)
7292
+ if (existsSync4(knownHostsPath) && readFileSync3(knownHostsPath, "utf8") === content)
7225
7293
  return;
7226
- mkdirSync2(dirname(knownHostsPath), { recursive: true, mode: 448 });
7294
+ mkdirSync3(dirname(knownHostsPath), { recursive: true, mode: 448 });
7227
7295
  const next = `${knownHostsPath}.${process.pid}.${randomUUID()}.next`;
7228
- writeFileSync2(next, content, { mode: 384, flag: "wx" });
7229
- renameSync2(next, knownHostsPath);
7230
- chmodSync3(knownHostsPath, 384);
7296
+ writeFileSync3(next, content, { mode: 384, flag: "wx" });
7297
+ renameSync3(next, knownHostsPath);
7298
+ chmodSync4(knownHostsPath, 384);
7231
7299
  };
7232
7300
  const gitBaseEnvironment = () => ({
7233
7301
  GIT_TERMINAL_PROMPT: "0",
@@ -7264,6 +7332,8 @@ function createDeploymentManager(options) {
7264
7332
  return httpsGitVersion;
7265
7333
  };
7266
7334
  const gitEnvironment = async () => {
7335
+ if (bootstrapBundle)
7336
+ return gitBaseEnvironment();
7267
7337
  let target;
7268
7338
  try {
7269
7339
  target = await resolvePinnedGitTarget(options.repository, options.resolveGitHost);
@@ -7285,22 +7355,25 @@ function createDeploymentManager(options) {
7285
7355
  ["http.curloptResolve", curlResolveValue(target)]
7286
7356
  ]);
7287
7357
  }
7288
- if (auth.kind === "vault-token") {
7358
+ if (auth.kind === "vault-token" || auth.kind === "ephemeral-token") {
7289
7359
  if (!https)
7290
7360
  throw new DeploymentError("SOURCE_FAILED", "Vault Git tokens may only be sent to HTTPS sources.");
7291
- if (!/^[A-Z_][A-Z0-9_]{0,127}$/.test(auth.secret)) {
7361
+ if (auth.kind === "vault-token" && !/^[A-Z_][A-Z0-9_]{0,127}$/.test(auth.secret)) {
7292
7362
  throw new DeploymentError("SOURCE_FAILED", "The Git token secret name is invalid.");
7293
7363
  }
7294
- if (!options.cache) {
7364
+ if (auth.kind === "vault-token" && !options.cache) {
7295
7365
  throw new DeploymentError("SECRET_MISSING", `Git source credential ${auth.secret} is unavailable.`);
7296
7366
  }
7297
7367
  const username = auth.username ?? "x-access-token";
7298
7368
  if (!/^[A-Za-z0-9._@+-]{1,128}$/.test(username)) {
7299
7369
  throw new DeploymentError("SOURCE_FAILED", "The Git token username is invalid.");
7300
7370
  }
7301
- const token = await options.cache.get(auth.secret);
7371
+ if (auth.kind === "ephemeral-token" && auth.expiresAtTs <= Date.now() + 30000) {
7372
+ throw new DeploymentError("SOURCE_FAILED", "The one-claim Git credential is expired or too close to expiry.");
7373
+ }
7374
+ const token = auth.kind === "ephemeral-token" ? auth.token : await options.cache.get(auth.secret);
7302
7375
  if (!token || token.length > 8192 || /[\r\n\0]/.test(token)) {
7303
- throw new DeploymentError("SOURCE_FAILED", `Git source credential ${auth.secret} is malformed.`);
7376
+ throw new DeploymentError("SOURCE_FAILED", "The Git source credential is malformed.");
7304
7377
  }
7305
7378
  const origin = new URL(options.repository).origin;
7306
7379
  return withGitConfig(gitBaseEnvironment(), [
@@ -7344,11 +7417,26 @@ function createDeploymentManager(options) {
7344
7417
  await checked({ argv: ["/usr/bin/test", "-d", releasesDirectory] }, "SOURCE_FAILED");
7345
7418
  await checked({ argv: ["/usr/bin/test", "-w", releasesDirectory] }, "SOURCE_FAILED");
7346
7419
  await checked({ argv: ["/usr/bin/install", "-d", "-m", "0770", release] }, "SOURCE_FAILED");
7420
+ if (bootstrapBundle) {
7421
+ let metadata;
7422
+ try {
7423
+ metadata = lstatSync2(bootstrapBundle.path);
7424
+ } catch {
7425
+ throw new DeploymentError("SOURCE_FAILED", "The attended bootstrap bundle is missing.");
7426
+ }
7427
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size !== bootstrapBundle.manifest.bytes || await sha256File(bootstrapBundle.path) !== bootstrapBundle.manifest.sha256) {
7428
+ throw new DeploymentError("SOURCE_FAILED", "The attended bootstrap bundle failed its manifest check.");
7429
+ }
7430
+ await checked({ argv: ["git", "bundle", "verify", bootstrapBundle.path], env }, "SOURCE_FAILED");
7431
+ }
7347
7432
  await checked({
7348
7433
  argv: ["git", "clone", "--quiet", "--no-checkout", "--branch", options.branch, "--depth", "100", options.repository, release],
7349
7434
  env
7350
7435
  }, "SOURCE_FAILED");
7351
- if (request.revision) {
7436
+ if (bootstrapBundle && request.revision && request.revision !== bootstrapBundle.manifest.revision) {
7437
+ throw new DeploymentError("BAD_REVISION", "The requested revision is not the attended bootstrap bundle revision.");
7438
+ }
7439
+ if (request.revision && !bootstrapBundle) {
7352
7440
  let present = await exec({
7353
7441
  argv: ["git", "cat-file", "-e", `${request.revision}^{commit}`],
7354
7442
  cwd: release,
@@ -7371,17 +7459,18 @@ function createDeploymentManager(options) {
7371
7459
  env
7372
7460
  }, "SOURCE_FAILED");
7373
7461
  }
7462
+ const desiredRevision = request.revision ?? bootstrapBundle?.manifest.revision ?? `origin/${options.branch}`;
7374
7463
  await checked({
7375
- argv: ["git", "checkout", "--quiet", "--detach", request.revision ?? `origin/${options.branch}`],
7464
+ argv: ["git", "checkout", "--quiet", "--detach", desiredRevision],
7376
7465
  cwd: release,
7377
7466
  env
7378
7467
  }, "SOURCE_FAILED");
7379
7468
  const head = (await checked({ argv: ["git", "rev-parse", "HEAD"], cwd: release, env }, "SOURCE_FAILED")).output.trim();
7380
- if (request.revision && head !== request.revision) {
7381
- throw new DeploymentError("SOURCE_FAILED", `Git checked out ${head}, not requested ${request.revision}.`);
7469
+ if ((request.revision || bootstrapBundle) && head !== desiredRevision) {
7470
+ throw new DeploymentError("SOURCE_FAILED", `Git checked out ${head}, not requested ${desiredRevision}.`);
7382
7471
  }
7383
7472
  const planPath = join2(release, DEPLOY_PLAN_FILE);
7384
- if (existsSync3(planPath)) {
7473
+ if (existsSync4(planPath)) {
7385
7474
  const plan = parseDeploymentPlan(readDefinition(planPath));
7386
7475
  for (const workflow of Object.values(plan.spec.workflows))
7387
7476
  for (const stage2 of Object.values(workflow.stages)) {
@@ -7498,7 +7587,7 @@ function createDeploymentManager(options) {
7498
7587
  const component2 = plan.spec.components[String(resolved.component ?? "")];
7499
7588
  const mounts = component2?.kind === "database" ? [component2.storage] : component2?.storage ?? [];
7500
7589
  for (const mount of mounts) {
7501
- const candidate = existsSync3(mount.path) ? mount.path : dirname(mount.path);
7590
+ const candidate = existsSync4(mount.path) ? mount.path : dirname(mount.path);
7502
7591
  const stats = statfsSync(candidate);
7503
7592
  const freePercent = Number(stats.bavail) / Number(stats.blocks) * 100;
7504
7593
  const minimum = mount.class === "ephemeral" ? 0 : mount.minimumFreePercent ?? 0;
@@ -7706,6 +7795,8 @@ function createDeploymentManager(options) {
7706
7795
  };
7707
7796
  return {
7708
7797
  async latestRevision() {
7798
+ if (bootstrapBundle)
7799
+ return bootstrapBundle.manifest.revision;
7709
7800
  const result = await checked({
7710
7801
  argv: ["git", "ls-remote", "--exit-code", options.repository, `refs/heads/${options.branch}`],
7711
7802
  env: await gitEnvironment()
@@ -7750,7 +7841,7 @@ function createDeploymentManager(options) {
7750
7841
  }
7751
7842
 
7752
7843
  // src/control.ts
7753
- import { chmodSync as chmodSync4, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
7844
+ import { chmodSync as chmodSync5, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "fs";
7754
7845
  import { connect, createServer as createServer2 } from "net";
7755
7846
  var DEFAULT_CONTROL_SOCKET = "/run/forgezero/control.sock";
7756
7847
  var MAX_REQUEST_BYTES = 16 * 1024;
@@ -7807,7 +7898,7 @@ async function handleControl(manager, request) {
7807
7898
  }
7808
7899
  }
7809
7900
  function startControlServer(manager, socketPath = DEFAULT_CONTROL_SOCKET) {
7810
- if (existsSync4(socketPath))
7901
+ if (existsSync5(socketPath))
7811
7902
  unlinkSync3(socketPath);
7812
7903
  const server = createServer2((socket) => {
7813
7904
  let buffer = "";
@@ -7829,7 +7920,7 @@ function startControlServer(manager, socketPath = DEFAULT_CONTROL_SOCKET) {
7829
7920
  });
7830
7921
  socket.on("error", () => socket.destroy());
7831
7922
  });
7832
- server.listen(socketPath, () => chmodSync4(socketPath, 384));
7923
+ server.listen(socketPath, () => chmodSync5(socketPath, 384));
7833
7924
  return server;
7834
7925
  }
7835
7926
  function requestControl(request, socketPath = DEFAULT_CONTROL_SOCKET) {
@@ -7868,6 +7959,7 @@ async function postSigned(options, operation, body) {
7868
7959
  }
7869
7960
  async function deployClaim(options, claim) {
7870
7961
  const manager = options.manager ?? options.managerFor?.(claim);
7962
+ const claimOwnedManager = !options.manager && claim.source.auth?.kind === "ephemeral-token";
7871
7963
  if (!manager)
7872
7964
  throw new Error("deployment pull has no manager for this claim");
7873
7965
  const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
@@ -7918,6 +8010,8 @@ async function deployClaim(options, claim) {
7918
8010
  stopped = true;
7919
8011
  clearTimer(timer);
7920
8012
  await renewal;
8013
+ if (claimOwnedManager)
8014
+ await manager.stop();
7921
8015
  }
7922
8016
  if (claimLost)
7923
8017
  throw claimLost;
@@ -8026,14 +8120,14 @@ function startDeploymentPull(options) {
8026
8120
 
8027
8121
  // src/guest-enrolment.ts
8028
8122
  import {
8029
- chmodSync as chmodSync5,
8030
- existsSync as existsSync5,
8031
- mkdirSync as mkdirSync3,
8032
- readFileSync as readFileSync3,
8033
- renameSync as renameSync3,
8123
+ chmodSync as chmodSync6,
8124
+ existsSync as existsSync6,
8125
+ mkdirSync as mkdirSync4,
8126
+ readFileSync as readFileSync4,
8127
+ renameSync as renameSync4,
8034
8128
  statSync,
8035
8129
  unlinkSync as unlinkSync4,
8036
- writeFileSync as writeFileSync3
8130
+ writeFileSync as writeFileSync4
8037
8131
  } from "fs";
8038
8132
  import { dirname as dirname2 } from "path";
8039
8133
  function privateNetworkAttachmentFromEnvironment(env = process.env) {
@@ -8063,7 +8157,7 @@ var validBinding = (value, expectedNodeKey) => {
8063
8157
  return ["nodeKey", "computeReference", "projectKey", "environmentKey"].every((key) => typeof row2[key] === "string" && row2[key].length > 0) && (row2.realm === "platform" || row2.realm === "tenant" && typeof row2.tenantSlug === "string" && row2.tenantSlug.length > 0) && (!expectedNodeKey || row2.nodeKey === expectedNodeKey);
8064
8158
  };
8065
8159
  function loadGuestBinding(path2, expectedNodeKey) {
8066
- if (!existsSync5(path2))
8160
+ if (!existsSync6(path2))
8067
8161
  return null;
8068
8162
  const mode = statSync(path2).mode & 511;
8069
8163
  if ((mode & 63) !== 0) {
@@ -8071,7 +8165,7 @@ function loadGuestBinding(path2, expectedNodeKey) {
8071
8165
  }
8072
8166
  let parsed;
8073
8167
  try {
8074
- parsed = JSON.parse(readFileSync3(path2, "utf8"));
8168
+ parsed = JSON.parse(readFileSync4(path2, "utf8"));
8075
8169
  } catch {
8076
8170
  throw new Error(`guest enrolment state at ${path2} is malformed`);
8077
8171
  }
@@ -8081,15 +8175,15 @@ function loadGuestBinding(path2, expectedNodeKey) {
8081
8175
  return parsed;
8082
8176
  }
8083
8177
  function persistGuestBinding(path2, binding) {
8084
- mkdirSync3(dirname2(path2), { recursive: true, mode: 448 });
8178
+ mkdirSync4(dirname2(path2), { recursive: true, mode: 448 });
8085
8179
  const temporary = `${path2}.next`;
8086
- writeFileSync3(temporary, `${JSON.stringify(binding)}
8180
+ writeFileSync4(temporary, `${JSON.stringify(binding)}
8087
8181
  `, { mode: 384 });
8088
- chmodSync5(temporary, 384);
8089
- renameSync3(temporary, path2);
8182
+ chmodSync6(temporary, 384);
8183
+ renameSync4(temporary, path2);
8090
8184
  }
8091
8185
  async function enrolGuestIdentity(options) {
8092
- const token = options.token?.trim() ?? (options.tokenPath ? readFileSync3(options.tokenPath, "utf8").trim() : "");
8186
+ const token = options.token?.trim() ?? (options.tokenPath ? readFileSync4(options.tokenPath, "utf8").trim() : "");
8093
8187
  if (!token.startsWith("fze_"))
8094
8188
  throw new Error("guest enrolment credential is malformed");
8095
8189
  const payload = await postSignedNode({
@@ -8426,8 +8520,8 @@ function startMigrationPull(options) {
8426
8520
  }
8427
8521
 
8428
8522
  // src/ssh-bootstrap.ts
8429
- import { createHash as createHash5 } from "crypto";
8430
- import { chmodSync as chmodSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
8523
+ import { createHash as createHash6 } from "crypto";
8524
+ import { chmodSync as chmodSync7, mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
8431
8525
  import { tmpdir as tmpdir2 } from "os";
8432
8526
  import { join as join3 } from "path";
8433
8527
  import { fileURLToPath } from "url";
@@ -8448,7 +8542,7 @@ function assertSupportedGuestImage(imageKey) {
8448
8542
  }
8449
8543
 
8450
8544
  // src/version.ts
8451
- var VERSION2 = "0.1.62";
8545
+ var VERSION2 = "0.1.63";
8452
8546
 
8453
8547
  // src/ssh-bootstrap.ts
8454
8548
  class SshBootstrapError extends Error {
@@ -8566,7 +8660,7 @@ async function pinnedKnownHost(claim, pinned, directory, exec) {
8566
8660
  if (!/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}$/.test(key))
8567
8661
  throw new SshBootstrapError("HOST_KEY_INVALID");
8568
8662
  const path2 = join3(directory, "known_hosts");
8569
- writeFileSync4(path2, `${pinned.hostKeyAlias} ${key}
8663
+ writeFileSync5(path2, `${pinned.hostKeyAlias} ${key}
8570
8664
  `, { mode: 384, flag: "wx" });
8571
8665
  return path2;
8572
8666
  }
@@ -8576,11 +8670,11 @@ var verifiedBunArchive = async (directory) => {
8576
8670
  if (!response.ok)
8577
8671
  throw new SshBootstrapError("BUN_DOWNLOAD_FAILED");
8578
8672
  const bytes = new Uint8Array(await response.arrayBuffer());
8579
- if (createHash5("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256) {
8673
+ if (createHash6("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256) {
8580
8674
  throw new SshBootstrapError("BUN_CHECKSUM_MISMATCH");
8581
8675
  }
8582
8676
  const archive = join3(directory, "bun.zip");
8583
- writeFileSync4(archive, bytes, { mode: 384, flag: "wx" });
8677
+ writeFileSync5(archive, bytes, { mode: 384, flag: "wx" });
8584
8678
  return archive;
8585
8679
  };
8586
8680
  var remote = (exec, ssh, destination, code, argv2, secret = false, stdin) => checked(exec, ["ssh", ...ssh, destination, "--", ...argv2], code, { secret, ...stdin !== undefined ? { stdin } : {} });
@@ -8596,14 +8690,14 @@ async function executeSshBootstrap(claim, options) {
8596
8690
  throw new SshBootstrapError("TARGET_API_INVALID");
8597
8691
  const exec = options.exec ?? defaultExec;
8598
8692
  const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-ssh-bootstrap-"));
8599
- chmodSync6(directory, 448);
8693
+ chmodSync7(directory, 448);
8600
8694
  let sshKeyPath = options.sshKeyPath;
8601
8695
  let remoteDirectory = "";
8602
8696
  let pinned;
8603
8697
  try {
8604
8698
  if (claim.target.credential.source === "vault") {
8605
8699
  sshKeyPath = join3(directory, "identity");
8606
- writeFileSync4(sshKeyPath, `${claim.target.credential.privateKey.trim()}
8700
+ writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
8607
8701
  `, { mode: 384, flag: "wx" });
8608
8702
  }
8609
8703
  const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
@@ -8630,11 +8724,11 @@ async function executeSshBootstrap(claim, options) {
8630
8724
  `
8631
8725
  };
8632
8726
  for (const [name, content] of Object.entries(files))
8633
- writeFileSync4(join3(directory, name), content, { mode: 384, flag: "wx" });
8727
+ writeFileSync5(join3(directory, name), content, { mode: 384, flag: "wx" });
8634
8728
  const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
8635
8729
  const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
8636
8730
  for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
8637
- if (!readFileSync4(source).length)
8731
+ if (!readFileSync5(source).length)
8638
8732
  throw new SshBootstrapError("PACKAGE_ARTIFACT_MISSING");
8639
8733
  await checked(exec, [
8640
8734
  "scp",
@@ -8798,7 +8892,7 @@ async function executeSshBootstrap(claim, options) {
8798
8892
  ]);
8799
8893
  } catch {}
8800
8894
  }
8801
- rmSync2(directory, { recursive: true, force: true });
8895
+ rmSync3(directory, { recursive: true, force: true });
8802
8896
  }
8803
8897
  }
8804
8898
  var post3 = (options, operation, body) => postSignedNode(options, `v1/node/bootstrap/${operation}`, body);
@@ -8908,14 +9002,14 @@ async function executeMetalAdmission(claim, options) {
8908
9002
  }
8909
9003
  const exec = options.exec ?? defaultExec;
8910
9004
  const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-metal-admission-"));
8911
- chmodSync6(directory, 448);
9005
+ chmodSync7(directory, 448);
8912
9006
  let sshKeyPath = options.sshKeyPath;
8913
9007
  let remoteDirectory = "";
8914
9008
  let pinned;
8915
9009
  try {
8916
9010
  if (claim.target.credential.source === "vault") {
8917
9011
  sshKeyPath = join3(directory, "identity");
8918
- writeFileSync4(sshKeyPath, `${claim.target.credential.privateKey.trim()}
9012
+ writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
8919
9013
  `, { mode: 384, flag: "wx" });
8920
9014
  }
8921
9015
  const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
@@ -8932,7 +9026,7 @@ async function executeMetalAdmission(claim, options) {
8932
9026
  const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
8933
9027
  const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
8934
9028
  for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
8935
- if (!readFileSync4(source).length)
9029
+ if (!readFileSync5(source).length)
8936
9030
  throw new SshBootstrapError("PACKAGE_ARTIFACT_MISSING");
8937
9031
  await checked(exec, [
8938
9032
  "scp",
@@ -8963,7 +9057,7 @@ async function executeMetalAdmission(claim, options) {
8963
9057
  }
8964
9058
  };
8965
9059
  const configPath = join3(directory, "metal.json");
8966
- writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
9060
+ writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
8967
9061
  `, { mode: 384, flag: "wx" });
8968
9062
  await checked(exec, [
8969
9063
  "scp",
@@ -9065,7 +9159,7 @@ async function executeMetalAdmission(claim, options) {
9065
9159
  ]);
9066
9160
  } catch {}
9067
9161
  }
9068
- rmSync2(directory, { recursive: true, force: true });
9162
+ rmSync3(directory, { recursive: true, force: true });
9069
9163
  }
9070
9164
  }
9071
9165
  async function pullMetalAdmissionOnce(options) {
@@ -9150,20 +9244,20 @@ function startMetalAdmissionPull(options) {
9150
9244
  }
9151
9245
 
9152
9246
  // src/metal-helper-socket.ts
9153
- import { chmodSync as chmodSync7, existsSync as existsSync7, unlinkSync as unlinkSync6 } from "fs";
9247
+ import { chmodSync as chmodSync8, existsSync as existsSync8, unlinkSync as unlinkSync6 } from "fs";
9154
9248
  import { connect as connect2, createServer as createServer3 } from "net";
9155
9249
 
9156
9250
  // src/metal-provision.ts
9157
- import { createHash as createHash6 } from "crypto";
9251
+ import { createHash as createHash7 } from "crypto";
9158
9252
  import {
9159
- existsSync as existsSync6,
9160
- mkdirSync as mkdirSync4,
9161
- readFileSync as readFileSync5,
9253
+ existsSync as existsSync7,
9254
+ mkdirSync as mkdirSync5,
9255
+ readFileSync as readFileSync6,
9162
9256
  readdirSync,
9163
- rmSync as rmSync3,
9257
+ rmSync as rmSync4,
9164
9258
  statSync as statSync2,
9165
9259
  unlinkSync as unlinkSync5,
9166
- writeFileSync as writeFileSync5
9260
+ writeFileSync as writeFileSync6
9167
9261
  } from "fs";
9168
9262
  import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4 } from "path";
9169
9263
  import { isIP as isIP2 } from "net";
@@ -9293,8 +9387,8 @@ function membersOfLinuxList(value, label) {
9293
9387
  throw new MetalProvisionError(`${label} list overlaps itself`);
9294
9388
  return members;
9295
9389
  }
9296
- var guestNameFor = (computeKey) => `fzg-${createHash6("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
9297
- var tapNameFor = (computeKey) => `fzt${createHash6("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
9390
+ var guestNameFor = (computeKey) => `fzg-${createHash7("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
9391
+ var tapNameFor = (computeKey) => `fzt${createHash7("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
9298
9392
  var macForAddress = (address) => {
9299
9393
  const octets = address.split(".").map(Number);
9300
9394
  if (octets.length !== 4 || octets.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) {
@@ -9374,9 +9468,9 @@ function validateMetalProfile(profile) {
9374
9468
  }
9375
9469
  }
9376
9470
  var readManifests = (stateDir) => {
9377
- if (!existsSync6(stateDir))
9471
+ if (!existsSync7(stateDir))
9378
9472
  return [];
9379
- return readdirSync(stateDir).filter((name) => name.endsWith(".json")).map((name) => JSON.parse(readFileSync5(join4(stateDir, name), "utf8")));
9473
+ return readdirSync(stateDir).filter((name) => name.endsWith(".json")).map((name) => JSON.parse(readFileSync6(join4(stateDir, name), "utf8")));
9380
9474
  };
9381
9475
  function allocateAddress(profile, computeKey, rows) {
9382
9476
  const existing = rows.find((row2) => row2.computeKey === computeKey);
@@ -9384,7 +9478,7 @@ function allocateAddress(profile, computeKey, rows) {
9384
9478
  return existing.address;
9385
9479
  const used = new Set(rows.map((row2) => row2.address));
9386
9480
  const width = profile.addressEnd - profile.addressStart + 1;
9387
- const start = createHash6("sha256").update(computeKey).digest().readUInt16BE(0) % width;
9481
+ const start = createHash7("sha256").update(computeKey).digest().readUInt16BE(0) % width;
9388
9482
  for (let offset = 0;offset < width; offset += 1) {
9389
9483
  const last = profile.addressStart + (start + offset) % width;
9390
9484
  const address = `${profile.subnetPrefix}.${last}`;
@@ -9480,8 +9574,6 @@ function guestBootstrapOperations(profile, attested = Boolean(profile.confidenti
9480
9574
  "FZ_AGENT_USER=forgezero-agent",
9481
9575
  "FZ_SOCKET_PATH=/run/forgezero/vault.sock",
9482
9576
  "FZ_SEED_CREDENTIAL_PATH=/etc/forgezero/creds/agent-seed.cred",
9483
- "FZ_GIT_CREDENTIAL_PATH=/etc/forgezero/creds/git-deploy-key.cred",
9484
- "FZ_GIT_PUBLIC_KEY_PATH=/etc/forgezero/git/deploy.pub",
9485
9577
  "FZ_DEPLOY_ROOT=/opt/forgezero",
9486
9578
  `FZ_DEPLOY_PULL=${hasEnrolment}`,
9487
9579
  `FZ_AGENT_EGRESS_ENFORCE=${hasEnrolment}`,
@@ -9561,7 +9653,7 @@ async function provisionMetalGuest(profile, claim, exec) {
9561
9653
  if (digest !== image.sha256)
9562
9654
  throw new MetalProvisionError("configured image checksum mismatch");
9563
9655
  for (const path2 of [profile.stateDir, profile.seedDir, profile.unitDir])
9564
- mkdirSync4(path2, { recursive: true, mode: 448 });
9656
+ mkdirSync5(path2, { recursive: true, mode: 448 });
9565
9657
  const manifests = readManifests(profile.stateDir);
9566
9658
  if (claim.access) {
9567
9659
  if (!/^[a-z_][a-z0-9_-]{0,31}$/.test(claim.access.sshUser)) {
@@ -9591,7 +9683,7 @@ async function provisionMetalGuest(profile, claim, exec) {
9591
9683
  allowedMemoryNodes: cpuPool.memoryNodes,
9592
9684
  phase: "allocating"
9593
9685
  };
9594
- const save = () => writeFileSync5(manifestPath, `${JSON.stringify(manifest, null, 2)}
9686
+ const save = () => writeFileSync6(manifestPath, `${JSON.stringify(manifest, null, 2)}
9595
9687
  `, { mode: 384 });
9596
9688
  save();
9597
9689
  const lv = `/dev/${profile.volumeGroup}/${name}`;
@@ -9605,9 +9697,9 @@ async function provisionMetalGuest(profile, claim, exec) {
9605
9697
  }
9606
9698
  const seedBase = join4(profile.seedDir, name);
9607
9699
  const init = cloudInit(profile, claim, manifest);
9608
- writeFileSync5(`${seedBase}-user-data`, init.userData, { mode: 384 });
9609
- writeFileSync5(`${seedBase}-meta-data`, init.metaData, { mode: 384 });
9610
- writeFileSync5(`${seedBase}-network-config`, init.networkConfig, { mode: 384 });
9700
+ writeFileSync6(`${seedBase}-user-data`, init.userData, { mode: 384 });
9701
+ writeFileSync6(`${seedBase}-meta-data`, init.metaData, { mode: 384 });
9702
+ writeFileSync6(`${seedBase}-network-config`, init.networkConfig, { mode: 384 });
9611
9703
  const seed = `${seedBase}-seed.iso`;
9612
9704
  await checked2(exec, [
9613
9705
  "cloud-localds",
@@ -9640,8 +9732,8 @@ async function provisionMetalGuest(profile, claim, exec) {
9640
9732
  }
9641
9733
  const service = `forgezero-guest@${name}.service`;
9642
9734
  const unitPath = join4(profile.unitDir, service);
9643
- mkdirSync4(dirname3(unitPath), { recursive: true });
9644
- writeFileSync5(unitPath, guestUnit(spec), { mode: 420 });
9735
+ mkdirSync5(dirname3(unitPath), { recursive: true });
9736
+ writeFileSync6(unitPath, guestUnit(spec), { mode: 420 });
9645
9737
  await checked2(exec, ["systemctl", "daemon-reload"]);
9646
9738
  if (prior?.phase === "running") {
9647
9739
  await checked2(exec, ["systemctl", "restart", service]);
@@ -9658,10 +9750,10 @@ function legacyPlatformManifest(profile, claim, name) {
9658
9750
  return;
9659
9751
  const legacyDir = profile.legacyStateDir ?? "/etc/forgezero/guests";
9660
9752
  const legacyPath = join4(legacyDir, `${name}.conf`);
9661
- if (!existsSync6(legacyPath))
9753
+ if (!existsSync7(legacyPath))
9662
9754
  return;
9663
9755
  const values = new Map;
9664
- for (const line of readFileSync5(legacyPath, "utf8").split(/\r?\n/)) {
9756
+ for (const line of readFileSync6(legacyPath, "utf8").split(/\r?\n/)) {
9665
9757
  if (!line || line.startsWith("#"))
9666
9758
  continue;
9667
9759
  const match = /^([A-Z][A-Z0-9_]*)=([^\s'"`$;|&<>]+)$/.exec(line);
@@ -9695,7 +9787,7 @@ async function removeMetalGuest(profile, claim, exec) {
9695
9787
  const service = `forgezero-guest@${name}.service`;
9696
9788
  const unitPath = join4(profile.unitDir, service);
9697
9789
  const lv = `/dev/${profile.volumeGroup}/${name}`;
9698
- const manifest = existsSync6(manifestPath) ? JSON.parse(readFileSync5(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
9790
+ const manifest = existsSync7(manifestPath) ? JSON.parse(readFileSync6(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
9699
9791
  if (!manifest) {
9700
9792
  const seedBase = join4(profile.seedDir, name);
9701
9793
  const localArtifacts = [
@@ -9705,7 +9797,7 @@ async function removeMetalGuest(profile, claim, exec) {
9705
9797
  `${seedBase}-meta-data`,
9706
9798
  `${seedBase}-network-config`
9707
9799
  ];
9708
- if (localArtifacts.some(existsSync6)) {
9800
+ if (localArtifacts.some(existsSync7)) {
9709
9801
  throw new MetalProvisionError("guest artifacts exist without owned inventory");
9710
9802
  }
9711
9803
  const [volume, active] = await Promise.all([
@@ -9722,7 +9814,7 @@ async function removeMetalGuest(profile, claim, exec) {
9722
9814
  if (!currentIdentity && !legacyPlatformIdentity) {
9723
9815
  throw new MetalProvisionError("compute identity conflicts with host inventory");
9724
9816
  }
9725
- if (existsSync6(unitPath))
9817
+ if (existsSync7(unitPath))
9726
9818
  await checked2(exec, ["systemctl", "disable", "--now", service]);
9727
9819
  else if (legacyPlatformIdentity)
9728
9820
  await checked2(exec, ["systemctl", "disable", "--now", service]);
@@ -9739,15 +9831,15 @@ async function removeMetalGuest(profile, claim, exec) {
9739
9831
  join4(profile.seedDir, `${name}-network-config`),
9740
9832
  manifestPath
9741
9833
  ])
9742
- if (existsSync6(path2))
9834
+ if (existsSync7(path2))
9743
9835
  unlinkSync5(path2);
9744
9836
  if (legacyPlatformIdentity) {
9745
9837
  const legacyConfig = join4(profile.legacyStateDir ?? "/etc/forgezero/guests", `${name}.conf`);
9746
9838
  const legacyDropIn = join4(profile.unitDir, `${service}.d`);
9747
- if (existsSync6(legacyConfig))
9839
+ if (existsSync7(legacyConfig))
9748
9840
  unlinkSync5(legacyConfig);
9749
- if (existsSync6(legacyDropIn))
9750
- rmSync3(legacyDropIn, { recursive: true });
9841
+ if (existsSync7(legacyDropIn))
9842
+ rmSync4(legacyDropIn, { recursive: true });
9751
9843
  }
9752
9844
  await checked2(exec, ["systemctl", "daemon-reload"]);
9753
9845
  return {};
@@ -9785,7 +9877,7 @@ command exceeded ${COMMAND_TIMEOUT_MS}ms`.trim() : stderr
9785
9877
  function startMetalHelper(options) {
9786
9878
  validateMetalProfile(options.profile);
9787
9879
  const socketPath = options.socketPath ?? DEFAULT_METAL_HELPER_SOCKET;
9788
- if (existsSync7(socketPath))
9880
+ if (existsSync8(socketPath))
9789
9881
  unlinkSync6(socketPath);
9790
9882
  let tail = Promise.resolve();
9791
9883
  const server = createServer3((socket) => {
@@ -9832,7 +9924,7 @@ function startMetalHelper(options) {
9832
9924
  });
9833
9925
  socket.on("error", () => socket.destroy());
9834
9926
  });
9835
- server.listen(socketPath, () => chmodSync7(socketPath, 432));
9927
+ server.listen(socketPath, () => chmodSync8(socketPath, 432));
9836
9928
  return {
9837
9929
  server,
9838
9930
  async stop() {
@@ -9872,7 +9964,7 @@ function requestMetalProvision(claim, socketPath = DEFAULT_METAL_HELPER_SOCKET)
9872
9964
  }
9873
9965
 
9874
9966
  // src/deployment-runner.ts
9875
- import { chmodSync as chmodSync8, existsSync as existsSync8, realpathSync, unlinkSync as unlinkSync7 } from "fs";
9967
+ import { chmodSync as chmodSync9, existsSync as existsSync9, realpathSync, unlinkSync as unlinkSync7 } from "fs";
9876
9968
  import { isAbsolute as isAbsolute4, resolve, sep } from "path";
9877
9969
  import { connect as connect3, createServer as createServer4 } from "net";
9878
9970
  var DEFAULT_DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
@@ -9989,7 +10081,7 @@ function startDeploymentRunner(options) {
9989
10081
  const root = resolve(options.root);
9990
10082
  const home = resolve(options.home);
9991
10083
  const socketPath = options.socketPath ?? DEFAULT_DEPLOYMENT_RUNNER_SOCKET;
9992
- if (existsSync8(socketPath))
10084
+ if (existsSync9(socketPath))
9993
10085
  unlinkSync7(socketPath);
9994
10086
  const active = new Set;
9995
10087
  const server = createServer4((socket) => {
@@ -10034,7 +10126,7 @@ function startDeploymentRunner(options) {
10034
10126
  const ready = new Promise((resolveReady, rejectReady) => {
10035
10127
  server.once("error", rejectReady);
10036
10128
  server.listen(socketPath, () => {
10037
- chmodSync8(socketPath, options.socketMode ?? 432);
10129
+ chmodSync9(socketPath, options.socketMode ?? 432);
10038
10130
  server.off("error", rejectReady);
10039
10131
  resolveReady();
10040
10132
  });
@@ -10085,7 +10177,7 @@ function requestDeploymentCommand(input, socketPath = DEFAULT_DEPLOYMENT_RUNNER_
10085
10177
  }
10086
10178
 
10087
10179
  // src/snp-attestation.ts
10088
- import { existsSync as existsSync9 } from "fs";
10180
+ import { existsSync as existsSync10 } from "fs";
10089
10181
 
10090
10182
  // ../runtime/dist/snp.js
10091
10183
  var REPORT_BYTES = 1184;
@@ -10178,7 +10270,7 @@ function createSnpAttestationSource(options = {}) {
10178
10270
  const python = options.python ?? "python3";
10179
10271
  const timeoutMs = options.timeoutMs ?? 1e4;
10180
10272
  const spawn = options.spawn ?? Bun.spawn;
10181
- const exists = options.exists ?? existsSync9;
10273
+ const exists = options.exists ?? existsSync10;
10182
10274
  return {
10183
10275
  name: "linux-sev-guest",
10184
10276
  async report(nonce) {
@@ -10264,7 +10356,7 @@ function startNodeAttestation(options) {
10264
10356
  }
10265
10357
 
10266
10358
  // src/metal-isolation.ts
10267
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
10359
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
10268
10360
  import { join as join5 } from "path";
10269
10361
  var members = (list) => list.split(",").flatMap((part) => {
10270
10362
  const [first, last = first] = part.split("-").map(Number);
@@ -10342,16 +10434,16 @@ async function applyMetalIsolation(profile, exec = defaultExec2) {
10342
10434
  validateMetalProfile(profile);
10343
10435
  await requireGuestsInSlice(exec);
10344
10436
  const unitDir = profile.unitDir;
10345
- mkdirSync5(unitDir, { recursive: true });
10346
- writeFileSync6(join5(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
10437
+ mkdirSync6(unitDir, { recursive: true });
10438
+ writeFileSync7(join5(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
10347
10439
  for (const unit of ["system.slice", "user.slice"]) {
10348
10440
  const directory = join5(unitDir, `${unit}.d`);
10349
- mkdirSync5(directory, { recursive: true });
10350
- writeFileSync6(join5(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
10441
+ mkdirSync6(directory, { recursive: true });
10442
+ writeFileSync7(join5(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
10351
10443
  }
10352
10444
  const initDirectory = join5(unitDir, "init.scope.d");
10353
- mkdirSync5(initDirectory, { recursive: true });
10354
- writeFileSync6(join5(initDirectory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "scope"), { mode: 420 });
10445
+ mkdirSync6(initDirectory, { recursive: true });
10446
+ writeFileSync7(join5(initDirectory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "scope"), { mode: 420 });
10355
10447
  await checked3(exec, ["systemctl", "daemon-reload"]);
10356
10448
  await requireGuestsInSlice(exec);
10357
10449
  const properties = [`AllowedCPUs=${profile.housekeepingCpus}`];
@@ -10386,7 +10478,7 @@ async function closeServerWithin(server, timeoutMs) {
10386
10478
  }
10387
10479
 
10388
10480
  // src/lifecycle-helper.ts
10389
- import { chmodSync as chmodSync9, existsSync as existsSync10, readFileSync as readFileSync6, unlinkSync as unlinkSync8 } from "fs";
10481
+ import { chmodSync as chmodSync10, existsSync as existsSync11, readFileSync as readFileSync7, unlinkSync as unlinkSync8 } from "fs";
10390
10482
  import { connect as connect4, createConnection, createServer as createServer5, isIP as isIP3 } from "net";
10391
10483
  var DEFAULT_LIFECYCLE_HELPER_SOCKET = "/run/forgezero-lifecycle/helper.sock";
10392
10484
  var MAX_REQUEST_BYTES4 = 16 * 1024;
@@ -10430,7 +10522,7 @@ function validateLifecycleProfile(profile) {
10430
10522
  }
10431
10523
  }
10432
10524
  function loadLifecycleProfile(path2) {
10433
- const profile = JSON.parse(readFileSync6(path2, "utf8"));
10525
+ const profile = JSON.parse(readFileSync7(path2, "utf8"));
10434
10526
  validateLifecycleProfile(profile);
10435
10527
  return profile;
10436
10528
  }
@@ -10527,7 +10619,7 @@ async function executeLifecycleAction(profile, claim, exec = spawnLifecycleComma
10527
10619
  function startLifecycleHelper(options) {
10528
10620
  validateLifecycleProfile(options.profile);
10529
10621
  const socketPath = options.socketPath ?? DEFAULT_LIFECYCLE_HELPER_SOCKET;
10530
- if (existsSync10(socketPath))
10622
+ if (existsSync11(socketPath))
10531
10623
  unlinkSync8(socketPath);
10532
10624
  let tail = Promise.resolve();
10533
10625
  const server = createServer5((socket) => {
@@ -10568,7 +10660,7 @@ function startLifecycleHelper(options) {
10568
10660
  });
10569
10661
  socket.on("error", () => socket.destroy());
10570
10662
  });
10571
- server.listen(socketPath, () => chmodSync9(socketPath, 432));
10663
+ server.listen(socketPath, () => chmodSync10(socketPath, 432));
10572
10664
  return {
10573
10665
  server,
10574
10666
  async stop() {
@@ -10608,7 +10700,7 @@ function requestLifecycleAction(claim, socketPath = DEFAULT_LIFECYCLE_HELPER_SOC
10608
10700
  }
10609
10701
 
10610
10702
  // src/warp-config.ts
10611
- import { chmodSync as chmodSync10, mkdirSync as mkdirSync6, renameSync as renameSync4, symlinkSync as symlinkSync2, unlinkSync as unlinkSync9, writeFileSync as writeFileSync7 } from "fs";
10703
+ import { chmodSync as chmodSync11, mkdirSync as mkdirSync7, renameSync as renameSync5, symlinkSync as symlinkSync2, unlinkSync as unlinkSync9, writeFileSync as writeFileSync8 } from "fs";
10612
10704
  var xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
10613
10705
  function renderWarpMdm(options) {
10614
10706
  if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(options.organization))
@@ -10629,12 +10721,12 @@ function renderWarpMdm(options) {
10629
10721
  function materializeWarpMdm(options) {
10630
10722
  const runtimePath = options.runtimePath ?? "/run/forgezero-warp/mdm.xml";
10631
10723
  const servicePath = options.servicePath ?? "/var/lib/cloudflare-warp/mdm.xml";
10632
- mkdirSync6(runtimePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
10633
- mkdirSync6(servicePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
10724
+ mkdirSync7(runtimePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
10725
+ mkdirSync7(servicePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
10634
10726
  const next = `${runtimePath}.next`;
10635
- writeFileSync7(next, renderWarpMdm(options), { mode: 384 });
10636
- chmodSync10(next, 384);
10637
- renameSync4(next, runtimePath);
10727
+ writeFileSync8(next, renderWarpMdm(options), { mode: 384 });
10728
+ chmodSync11(next, 384);
10729
+ renameSync5(next, runtimePath);
10638
10730
  try {
10639
10731
  unlinkSync9(servicePath);
10640
10732
  } catch (cause) {
@@ -10646,7 +10738,7 @@ function materializeWarpMdm(options) {
10646
10738
  }
10647
10739
 
10648
10740
  // src/mesh-connector.ts
10649
- import { constants, closeSync, fstatSync, openSync, readFileSync as readFileSync7 } from "fs";
10741
+ import { constants, closeSync, fstatSync, openSync, readFileSync as readFileSync8 } from "fs";
10650
10742
  import { join as join6 } from "path";
10651
10743
  var TOKEN = /^[A-Za-z0-9._-]{40,16384}$/;
10652
10744
  function readMeshConnectorCredential(name = "CF_WARP_CONNECTOR_TOKEN") {
@@ -10663,7 +10755,7 @@ function readMeshConnectorCredential(name = "CF_WARP_CONNECTOR_TOKEN") {
10663
10755
  if (!metadata.isFile() || metadata.nlink !== 1 || metadata.size < 40 || metadata.size > 16384) {
10664
10756
  throw new Error("Mesh connector systemd credential is malformed");
10665
10757
  }
10666
- const token = readFileSync7(descriptor, "utf8").trim();
10758
+ const token = readFileSync8(descriptor, "utf8").trim();
10667
10759
  if (!TOKEN.test(token))
10668
10760
  throw new Error("Mesh connector systemd credential is malformed");
10669
10761
  return token;
@@ -10689,20 +10781,20 @@ async function configureMeshConnector(input) {
10689
10781
  }
10690
10782
 
10691
10783
  // src/agent-update.ts
10692
- import { createHash as createHash7, timingSafeEqual, randomUUID as randomUUID2 } from "crypto";
10784
+ import { createHash as createHash8, timingSafeEqual, randomUUID as randomUUID2 } from "crypto";
10693
10785
  import {
10694
- chmodSync as chmodSync11,
10786
+ chmodSync as chmodSync12,
10695
10787
  closeSync as closeSync2,
10696
- existsSync as existsSync11,
10788
+ existsSync as existsSync12,
10697
10789
  fsyncSync,
10698
- mkdirSync as mkdirSync7,
10790
+ mkdirSync as mkdirSync8,
10699
10791
  openSync as openSync2,
10700
- readFileSync as readFileSync8,
10792
+ readFileSync as readFileSync9,
10701
10793
  readlinkSync,
10702
- renameSync as renameSync5,
10703
- rmSync as rmSync4,
10794
+ renameSync as renameSync6,
10795
+ rmSync as rmSync5,
10704
10796
  symlinkSync as symlinkSync3,
10705
- writeFileSync as writeFileSync8
10797
+ writeFileSync as writeFileSync9
10706
10798
  } from "fs";
10707
10799
  import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
10708
10800
  var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
@@ -10784,9 +10876,9 @@ var checked4 = async (run2, input, label) => {
10784
10876
  return result;
10785
10877
  };
10786
10878
  async function validateReleaseDirectory(directory, release, run2) {
10787
- chmodSync11(directory, 493);
10788
- chmodSync11(join7(directory, "dist"), 493);
10789
- const manifest = JSON.parse(readFileSync8(join7(directory, "package.json"), "utf8"));
10879
+ chmodSync12(directory, 493);
10880
+ chmodSync12(join7(directory, "dist"), 493);
10881
+ const manifest = JSON.parse(readFileSync9(join7(directory, "package.json"), "utf8"));
10790
10882
  if (manifest.name !== release.package || manifest.version !== release.version) {
10791
10883
  throw new Error("agent update manifest does not match the selected release");
10792
10884
  }
@@ -10794,11 +10886,11 @@ async function validateReleaseDirectory(directory, release, run2) {
10794
10886
  const cli = join7(directory, "dist", "fz.js");
10795
10887
  const gitSsh = join7(directory, "dist", "fz-git-ssh.js");
10796
10888
  for (const binary of [agent, cli, gitSsh]) {
10797
- if (!readFileSync8(binary, "utf8").startsWith(`#!/usr/bin/env bun
10889
+ if (!readFileSync9(binary, "utf8").startsWith(`#!/usr/bin/env bun
10798
10890
  `)) {
10799
10891
  throw new Error("agent update artifact is not a self-contained Bun executable");
10800
10892
  }
10801
- chmodSync11(binary, 493);
10893
+ chmodSync12(binary, 493);
10802
10894
  }
10803
10895
  const version = (await checked4(run2, { command: agent, args: ["--version"] }, "agent update smoke test")).output.trim();
10804
10896
  if (version !== release.version)
@@ -10817,7 +10909,7 @@ async function stageAgentRelease(releaseInput, options) {
10817
10909
  const archive = join7(stage2, "agent.tgz");
10818
10910
  const unpacked = join7(stage2, "unpacked");
10819
10911
  const run2 = options.run ?? command;
10820
- mkdirSync7(unpacked, { recursive: true, mode: 448 });
10912
+ mkdirSync8(unpacked, { recursive: true, mode: 448 });
10821
10913
  try {
10822
10914
  const response = await (options.fetch ?? globalThis.fetch)(release.tarball, {
10823
10915
  redirect: "error",
@@ -10833,10 +10925,10 @@ async function stageAgentRelease(releaseInput, options) {
10833
10925
  throw new Error("agent update tarball is empty or exceeds the size limit");
10834
10926
  }
10835
10927
  const expected = Buffer.from(release.integrity.slice("sha512-".length), "base64");
10836
- const actual = createHash7("sha512").update(bytes).digest();
10928
+ const actual = createHash8("sha512").update(bytes).digest();
10837
10929
  if (!timingSafeEqual(actual, expected))
10838
10930
  throw new Error("agent update integrity mismatch");
10839
- writeFileSync8(archive, bytes, { mode: 384, flag: "wx" });
10931
+ writeFileSync9(archive, bytes, { mode: 384, flag: "wx" });
10840
10932
  for (const [member, relative] of [
10841
10933
  ["package/package.json", "package.json"],
10842
10934
  ["package/dist/fz-agent.js", "dist/fz-agent.js"],
@@ -10848,23 +10940,23 @@ async function stageAgentRelease(releaseInput, options) {
10848
10940
  args: ["-xOzf", archive, member]
10849
10941
  }, `agent update extraction of ${member}`);
10850
10942
  const destination = join7(unpacked, relative);
10851
- mkdirSync7(dirname4(destination), { recursive: true, mode: 448 });
10852
- writeFileSync8(destination, extracted.output, { mode: 384, flag: "wx" });
10943
+ mkdirSync8(dirname4(destination), { recursive: true, mode: 448 });
10944
+ writeFileSync9(destination, extracted.output, { mode: 384, flag: "wx" });
10853
10945
  }
10854
10946
  await validateReleaseDirectory(unpacked, release, run2);
10855
- if (!existsSync11(finalDirectory)) {
10856
- renameSync5(unpacked, finalDirectory);
10947
+ if (!existsSync12(finalDirectory)) {
10948
+ renameSync6(unpacked, finalDirectory);
10857
10949
  syncReleaseDirectory(finalDirectory);
10858
10950
  } else
10859
10951
  await validateReleaseDirectory(finalDirectory, release, run2);
10860
- if (!existsSync11(currentLink)) {
10952
+ if (!existsSync12(currentLink)) {
10861
10953
  throw new Error("agent update requires an active immutable release to roll back to");
10862
10954
  }
10863
10955
  const previousTarget = readlinkSync(currentLink);
10864
10956
  if (previousTarget !== join7("versions", options.currentVersion)) {
10865
10957
  throw new Error("agent update current release does not match the running version");
10866
10958
  }
10867
- if (!existsSync11(join7(root, previousTarget))) {
10959
+ if (!existsSync12(join7(root, previousTarget))) {
10868
10960
  throw new Error("agent update rollback release is missing");
10869
10961
  }
10870
10962
  return {
@@ -10876,44 +10968,44 @@ async function stageAgentRelease(releaseInput, options) {
10876
10968
  currentLink
10877
10969
  };
10878
10970
  } finally {
10879
- rmSync4(stage2, { recursive: true, force: true });
10971
+ rmSync5(stage2, { recursive: true, force: true });
10880
10972
  }
10881
10973
  }
10882
10974
  function selectAgentRelease(staged) {
10883
10975
  const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.next`);
10884
10976
  try {
10885
10977
  symlinkSync3(staged.nextTarget, next);
10886
- renameSync5(next, staged.currentLink);
10978
+ renameSync6(next, staged.currentLink);
10887
10979
  syncPath(dirname4(staged.currentLink));
10888
10980
  } finally {
10889
- rmSync4(next, { force: true });
10981
+ rmSync5(next, { force: true });
10890
10982
  }
10891
10983
  }
10892
10984
  function restoreAgentRelease(staged) {
10893
10985
  const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.rollback`);
10894
10986
  try {
10895
10987
  symlinkSync3(staged.previousTarget, next);
10896
- renameSync5(next, staged.currentLink);
10988
+ renameSync6(next, staged.currentLink);
10897
10989
  syncPath(dirname4(staged.currentLink));
10898
10990
  } finally {
10899
- rmSync4(next, { force: true });
10991
+ rmSync5(next, { force: true });
10900
10992
  }
10901
10993
  }
10902
10994
 
10903
10995
  // src/agent-update-helper.ts
10904
10996
  import { randomUUID as randomUUID3 } from "crypto";
10905
10997
  import {
10906
- chmodSync as chmodSync12,
10998
+ chmodSync as chmodSync13,
10907
10999
  closeSync as closeSync3,
10908
- existsSync as existsSync12,
11000
+ existsSync as existsSync13,
10909
11001
  fsyncSync as fsyncSync2,
10910
- mkdirSync as mkdirSync8,
11002
+ mkdirSync as mkdirSync9,
10911
11003
  openSync as openSync3,
10912
- readFileSync as readFileSync9,
10913
- renameSync as renameSync6,
10914
- rmSync as rmSync5,
11004
+ readFileSync as readFileSync10,
11005
+ renameSync as renameSync7,
11006
+ rmSync as rmSync6,
10915
11007
  unlinkSync as unlinkSync10,
10916
- writeFileSync as writeFileSync9
11008
+ writeFileSync as writeFileSync10
10917
11009
  } from "fs";
10918
11010
  import { connect as connect5, createServer as createServer6 } from "net";
10919
11011
  import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
@@ -11011,22 +11103,22 @@ function validateJournal(value, root) {
11011
11103
  return journal;
11012
11104
  }
11013
11105
  function readJournal(path2, root) {
11014
- if (!existsSync12(path2))
11106
+ if (!existsSync13(path2))
11015
11107
  return;
11016
- return validateJournal(JSON.parse(readFileSync9(path2, "utf8")), root);
11108
+ return validateJournal(JSON.parse(readFileSync10(path2, "utf8")), root);
11017
11109
  }
11018
11110
  function writeAtomic(path2, value, mode) {
11019
- mkdirSync8(dirname5(path2), { recursive: true, mode: 493 });
11111
+ mkdirSync9(dirname5(path2), { recursive: true, mode: 493 });
11020
11112
  const next = `${path2}.${randomUUID3()}.next`;
11021
11113
  let file;
11022
11114
  try {
11023
11115
  file = openSync3(next, "wx", mode);
11024
- writeFileSync9(file, `${JSON.stringify(value)}
11116
+ writeFileSync10(file, `${JSON.stringify(value)}
11025
11117
  `);
11026
11118
  fsyncSync2(file);
11027
11119
  closeSync3(file);
11028
11120
  file = undefined;
11029
- renameSync6(next, path2);
11121
+ renameSync7(next, path2);
11030
11122
  const directory = openSync3(dirname5(path2), "r");
11031
11123
  try {
11032
11124
  fsyncSync2(directory);
@@ -11036,7 +11128,7 @@ function writeAtomic(path2, value, mode) {
11036
11128
  } finally {
11037
11129
  if (file !== undefined)
11038
11130
  closeSync3(file);
11039
- rmSync5(next, { force: true });
11131
+ rmSync6(next, { force: true });
11040
11132
  }
11041
11133
  }
11042
11134
  var publicReceipt = (journal) => {
@@ -11069,9 +11161,9 @@ function writeUpdateState(journalPath, receiptPath, journal) {
11069
11161
  }
11070
11162
  function readAgentUpdateReceipt(path2 = AGENT_UPDATE_RECEIPT) {
11071
11163
  try {
11072
- if (!existsSync12(path2))
11164
+ if (!existsSync13(path2))
11073
11165
  return;
11074
- return validateReceipt(JSON.parse(readFileSync9(path2, "utf8")));
11166
+ return validateReceipt(JSON.parse(readFileSync10(path2, "utf8")));
11075
11167
  } catch {
11076
11168
  return;
11077
11169
  }
@@ -11225,7 +11317,7 @@ async function recoverInterruptedAgentUpdate(options = {}) {
11225
11317
  return publicReceipt(journal);
11226
11318
  }
11227
11319
  const staged = stagedFromJournal(journal, root);
11228
- if (!existsSync12(join8(root, journal.previousTarget))) {
11320
+ if (!existsSync13(join8(root, journal.previousTarget))) {
11229
11321
  throw new Error("Agent update rollback release is missing");
11230
11322
  }
11231
11323
  const run2 = options.run ?? runCommand;
@@ -11255,9 +11347,9 @@ async function recoverInterruptedAgentUpdate(options = {}) {
11255
11347
  }
11256
11348
  function startAgentUpdateHelper(options = {}) {
11257
11349
  const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
11258
- if (existsSync12(socketPath))
11350
+ if (existsSync13(socketPath))
11259
11351
  unlinkSync10(socketPath);
11260
- mkdirSync8(dirname5(socketPath), { recursive: true, mode: 488 });
11352
+ mkdirSync9(dirname5(socketPath), { recursive: true, mode: 488 });
11261
11353
  const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
11262
11354
  const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
11263
11355
  const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
@@ -11336,7 +11428,7 @@ function startAgentUpdateHelper(options = {}) {
11336
11428
  });
11337
11429
  socket.on("error", () => socket.destroy());
11338
11430
  });
11339
- server.listen(socketPath, () => chmodSync12(socketPath, 432));
11431
+ server.listen(socketPath, () => chmodSync13(socketPath, 432));
11340
11432
  return server;
11341
11433
  }
11342
11434
  function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
@@ -11366,13 +11458,13 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
11366
11458
  }
11367
11459
 
11368
11460
  // src/agent-heartbeat.ts
11369
- import { readFileSync as readFileSync10 } from "fs";
11461
+ import { readFileSync as readFileSync11 } from "fs";
11370
11462
  var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
11371
11463
  var remoteOutcome4 = (cause) => cause instanceof SignedNodeHttpError && cause.status < 500 ? "refused" : cause instanceof SignedNodeHttpError ? "retryable" : "failed";
11372
11464
 
11373
11465
  class AgentUpdateRefusedError extends Error {
11374
11466
  }
11375
- function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = readFileSync10("/etc/os-release", "utf8"), architecture = process.arch) {
11467
+ function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = readFileSync11("/etc/os-release", "utf8"), architecture = process.arch) {
11376
11468
  const values = Object.fromEntries(osRelease.split(`
11377
11469
  `).flatMap((line) => {
11378
11470
  const separator = line.indexOf("=");
@@ -11490,34 +11582,34 @@ function startAgentHeartbeat(options) {
11490
11582
  }
11491
11583
 
11492
11584
  // src/software-helper.ts
11493
- import { chmodSync as chmodSync13, existsSync as existsSync15, mkdirSync as mkdirSync11, unlinkSync as unlinkSync11 } from "fs";
11585
+ import { chmodSync as chmodSync14, existsSync as existsSync16, mkdirSync as mkdirSync12, unlinkSync as unlinkSync11 } from "fs";
11494
11586
  import { connect as connect6, createServer as createServer7 } from "net";
11495
11587
  import { dirname as dirname8 } from "path";
11496
11588
 
11497
11589
  // src/service-supervisor.ts
11498
- import { createHash as createHash8 } from "crypto";
11499
- import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync11, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync7, rmSync as rmSync6, writeFileSync as writeFileSync10 } from "fs";
11590
+ import { createHash as createHash9 } from "crypto";
11591
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "fs";
11500
11592
  import { dirname as dirname6, join as join9, resolve as resolve4, sep as sep2 } from "path";
11501
11593
  var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
11502
11594
  var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
11503
11595
  var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
11504
11596
  var SERVICE_NGINX_DIRECTORY = "/etc/nginx/conf.d";
11505
- var idFor = (key) => createHash8("sha256").update(key).digest("hex").slice(0, 24);
11597
+ var idFor = (key) => createHash9("sha256").update(key).digest("hex").slice(0, 24);
11506
11598
  var statePath = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
11507
11599
  var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
11508
11600
  var defaultHost = {
11509
11601
  write(path2, content, mode) {
11510
- mkdirSync9(dirname6(path2), { recursive: true, mode: 493 });
11602
+ mkdirSync10(dirname6(path2), { recursive: true, mode: 493 });
11511
11603
  const next = `${path2}.next`;
11512
- writeFileSync10(next, content, { mode });
11513
- renameSync7(next, path2);
11604
+ writeFileSync11(next, content, { mode });
11605
+ renameSync8(next, path2);
11514
11606
  },
11515
- read: (path2) => readFileSync11(path2, "utf8"),
11516
- exists: existsSync13,
11517
- list: (path2) => existsSync13(path2) ? readdirSync2(path2) : [],
11607
+ read: (path2) => readFileSync12(path2, "utf8"),
11608
+ exists: existsSync14,
11609
+ list: (path2) => existsSync14(path2) ? readdirSync2(path2) : [],
11518
11610
  realpath: realpathSync2,
11519
- mkdir: (path2, mode) => mkdirSync9(path2, { recursive: true, mode }),
11520
- remove: (path2) => rmSync6(path2, { force: true }),
11611
+ mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
11612
+ remove: (path2) => rmSync7(path2, { force: true }),
11521
11613
  async exec(argv2) {
11522
11614
  const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
11523
11615
  PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
@@ -11575,7 +11667,7 @@ function allocatedPorts(request, id2, previous, states) {
11575
11667
  if (previous && previous.applicationPorts.length === required && previous.applicationPorts.every((port) => port >= allocation.from && port <= allocation.to && !occupied.has(port)))
11576
11668
  return [...previous.applicationPorts];
11577
11669
  const width = allocation.to - allocation.from + 1;
11578
- const start = Number.parseInt(createHash8("sha256").update(request.key).digest("hex").slice(0, 8), 16) % width;
11670
+ const start = Number.parseInt(createHash9("sha256").update(request.key).digest("hex").slice(0, 8), 16) % width;
11579
11671
  for (let offset = 0;offset < width; offset += 1) {
11580
11672
  const first = allocation.from + (start + offset) % width;
11581
11673
  const candidate = Array.from({ length: required }, (_, index) => first + index);
@@ -11712,22 +11804,22 @@ async function activateSupervisedService(request, host = defaultHost) {
11712
11804
  }
11713
11805
 
11714
11806
  // src/container-supervisor.ts
11715
- import { createHash as createHash9 } from "crypto";
11716
- import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readdirSync as readdirSync3, realpathSync as realpathSync3, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "fs";
11807
+ import { createHash as createHash10 } from "crypto";
11808
+ import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync13, readdirSync as readdirSync3, realpathSync as realpathSync3, renameSync as renameSync9, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "fs";
11717
11809
  import { dirname as dirname7, resolve as resolve5, sep as sep3 } from "path";
11718
11810
  var defaultHost2 = {
11719
11811
  realpath: realpathSync3,
11720
- exists: existsSync14,
11721
- read: (path2) => readFileSync12(path2, "utf8"),
11812
+ exists: existsSync15,
11813
+ read: (path2) => readFileSync13(path2, "utf8"),
11722
11814
  write(path2, content, mode) {
11723
- mkdirSync10(dirname7(path2), { recursive: true, mode: 493 });
11815
+ mkdirSync11(dirname7(path2), { recursive: true, mode: 493 });
11724
11816
  const next = `${path2}.next`;
11725
- writeFileSync11(next, content, { mode });
11726
- renameSync8(next, path2);
11817
+ writeFileSync12(next, content, { mode });
11818
+ renameSync9(next, path2);
11727
11819
  },
11728
- remove: (path2) => rmSync7(path2, { force: true }),
11729
- list: (path2) => existsSync14(path2) ? readdirSync3(path2) : [],
11730
- mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
11820
+ remove: (path2) => rmSync8(path2, { force: true }),
11821
+ list: (path2) => existsSync15(path2) ? readdirSync3(path2) : [],
11822
+ mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
11731
11823
  async exec(argv2) {
11732
11824
  const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" } });
11733
11825
  const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
@@ -11745,7 +11837,7 @@ var defaultHost2 = {
11745
11837
  now: Date.now
11746
11838
  };
11747
11839
  var within3 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep3}`);
11748
- var idFor2 = (key) => createHash9("sha256").update(key).digest("hex").slice(0, 24);
11840
+ var idFor2 = (key) => createHash10("sha256").update(key).digest("hex").slice(0, 24);
11749
11841
  var statePath2 = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
11750
11842
  var nameFor = (id2, slot) => `forgezero-${id2}-slot${slot}`;
11751
11843
  function validateRequest(request, host) {
@@ -12010,9 +12102,9 @@ var MAX_REQUEST_BYTES6 = 128 * 1024;
12010
12102
  var MAX_PENDING_REQUESTS = 128;
12011
12103
  function startSoftwareHelper(options = {}) {
12012
12104
  const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
12013
- if (existsSync15(socketPath))
12105
+ if (existsSync16(socketPath))
12014
12106
  unlinkSync11(socketPath);
12015
- mkdirSync11(dirname8(socketPath), { recursive: true, mode: 488 });
12107
+ mkdirSync12(dirname8(socketPath), { recursive: true, mode: 488 });
12016
12108
  const ensure = options.ensure ?? ensureSoftwareRequirements;
12017
12109
  const activate = options.activate ?? activateSupervisedService;
12018
12110
  const buildContainer = options.buildContainer ?? buildContainerImage;
@@ -12092,7 +12184,7 @@ function startSoftwareHelper(options = {}) {
12092
12184
  });
12093
12185
  socket.on("error", () => socket.destroy());
12094
12186
  });
12095
- server.listen(socketPath, () => chmodSync13(socketPath, 432));
12187
+ server.listen(socketPath, () => chmodSync14(socketPath, 432));
12096
12188
  return server;
12097
12189
  }
12098
12190
  function requestContainer(op, request, socketPath, timeoutMs) {
@@ -13121,20 +13213,20 @@ var METAL_SYSTEMD_CREDENTIALS = {
13121
13213
 
13122
13214
  // src/platform-bootstrap-runtime.ts
13123
13215
  import {
13124
- chmodSync as chmodSync14,
13216
+ chmodSync as chmodSync15,
13125
13217
  chownSync,
13126
13218
  copyFileSync as copyFileSync2,
13127
- existsSync as existsSync16,
13128
- lstatSync as lstatSync2,
13129
- mkdirSync as mkdirSync12,
13130
- readFileSync as readFileSync13,
13219
+ existsSync as existsSync17,
13220
+ lstatSync as lstatSync3,
13221
+ mkdirSync as mkdirSync13,
13222
+ readFileSync as readFileSync14,
13131
13223
  readdirSync as readdirSync4,
13132
13224
  realpathSync as realpathSync5,
13133
- renameSync as renameSync9,
13134
- rmSync as rmSync8,
13225
+ renameSync as renameSync10,
13226
+ rmSync as rmSync9,
13135
13227
  statSync as statSync3,
13136
13228
  symlinkSync as symlinkSync4,
13137
- writeFileSync as writeFileSync12
13229
+ writeFileSync as writeFileSync13
13138
13230
  } from "fs";
13139
13231
  import { join as join10 } from "path";
13140
13232
  var boundedInteger2 = (name, value, minimum, maximum) => {
@@ -13188,21 +13280,21 @@ var platformExec = async (argv2) => {
13188
13280
  };
13189
13281
  var replaceLink = (path2, target2) => {
13190
13282
  const pending = `${path2}.next`;
13191
- rmSync8(pending, { force: true });
13283
+ rmSync9(pending, { force: true });
13192
13284
  if (!target2) {
13193
- rmSync8(path2, { force: true });
13285
+ rmSync9(path2, { force: true });
13194
13286
  return;
13195
13287
  }
13196
13288
  symlinkSync4(target2, pending);
13197
- renameSync9(pending, path2);
13289
+ renameSync10(pending, path2);
13198
13290
  };
13199
13291
  var secureRelease = (path2, uid, gid) => {
13200
13292
  const visit = (current) => {
13201
- const metadata = lstatSync2(current);
13293
+ const metadata = lstatSync3(current);
13202
13294
  if (metadata.isSymbolicLink())
13203
13295
  throw new Error("release contains a symbolic link");
13204
13296
  chownSync(current, uid, gid);
13205
- chmodSync14(current, metadata.isDirectory() ? 365 : 292);
13297
+ chmodSync15(current, metadata.isDirectory() ? 365 : 292);
13206
13298
  if (metadata.isDirectory())
13207
13299
  for (const name of readdirSync4(current))
13208
13300
  visit(join10(current, name));
@@ -13225,9 +13317,9 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13225
13317
  const request = options.fetch ?? fetch;
13226
13318
  const sleep2 = options.sleep ?? Bun.sleep;
13227
13319
  const slots = join10(normalized.root, "slots");
13228
- mkdirSync12(slots, { recursive: true, mode: 493 });
13320
+ mkdirSync13(slots, { recursive: true, mode: 493 });
13229
13321
  const slotFile = join10(normalized.root, ".forge-slot");
13230
- const previousSlot = existsSync16(slotFile) ? readFileSync13(slotFile, "utf8").trim() : undefined;
13322
+ const previousSlot = existsSync17(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
13231
13323
  const target2 = previousSlot === "blue" ? "green" : "blue";
13232
13324
  const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
13233
13325
  const targetLink = join10(slots, target2);
@@ -13267,26 +13359,26 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13267
13359
  }
13268
13360
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
13269
13361
  const backup = `${upstream}.forgezero-backup`;
13270
- if (existsSync16(upstream))
13362
+ if (existsSync17(upstream))
13271
13363
  copyFileSync2(upstream, backup);
13272
13364
  else
13273
- rmSync8(backup, { force: true });
13274
- writeFileSync12(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
13365
+ rmSync9(backup, { force: true });
13366
+ writeFileSync13(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
13275
13367
  `, { mode: 420 });
13276
13368
  const test = await exec(["/usr/sbin/nginx", "-t"]);
13277
13369
  const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
13278
13370
  if (reload.exitCode !== 0) {
13279
- if (existsSync16(backup))
13280
- renameSync9(backup, upstream);
13371
+ if (existsSync17(backup))
13372
+ renameSync10(backup, upstream);
13281
13373
  else
13282
- rmSync8(upstream, { force: true });
13374
+ rmSync9(upstream, { force: true });
13283
13375
  await exec(["/usr/sbin/nginx", "-t"]);
13284
13376
  await exec(["/usr/sbin/nginx", "-s", "reload"]);
13285
13377
  await stopTarget();
13286
13378
  throw new Error("nginx refused the promoted upstream");
13287
13379
  }
13288
- rmSync8(backup, { force: true });
13289
- writeFileSync12(slotFile, `${target2}
13380
+ rmSync9(backup, { force: true });
13381
+ writeFileSync13(slotFile, `${target2}
13290
13382
  `, { mode: 420 });
13291
13383
  if (previousSlot && previousSlot !== target2) {
13292
13384
  await sleep2(normalized.drainDeadlineMs);
@@ -13295,12 +13387,12 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13295
13387
  const old = readdirSync4(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join10(releases, entry.name)).sort((left, right) => statSync3(right).mtimeMs - statSync3(left).mtimeMs).slice(normalized.keepReleases);
13296
13388
  for (const path2 of old)
13297
13389
  if (path2 !== release)
13298
- rmSync8(path2, { recursive: true, force: true });
13390
+ rmSync9(path2, { recursive: true, force: true });
13299
13391
  return { release, slot: target2 };
13300
13392
  }
13301
13393
 
13302
13394
  // src/recovery-host.ts
13303
- import { existsSync as existsSync17, readFileSync as readFileSync14, statSync as statSync4 } from "fs";
13395
+ import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync4 } from "fs";
13304
13396
  import { hostname } from "os";
13305
13397
  import { join as join11 } from "path";
13306
13398
  var execute2 = async (argv2) => {
@@ -13315,18 +13407,18 @@ var checked7 = async (run2, argv2, expected = 0) => {
13315
13407
  return result;
13316
13408
  };
13317
13409
  var activeSlot = () => {
13318
- const slot = readFileSync14("/opt/forgezero/.forge-slot", "utf8").trim();
13410
+ const slot = readFileSync15("/opt/forgezero/.forge-slot", "utf8").trim();
13319
13411
  if (slot !== "blue" && slot !== "green")
13320
13412
  throw new Error("invalid active slot");
13321
13413
  return slot;
13322
13414
  };
13323
13415
  var exactState = (path2, expected) => {
13324
- if (!readFileSync14(path2, "utf8").split(`
13416
+ if (!readFileSync15(path2, "utf8").split(`
13325
13417
  `).includes(expected))
13326
13418
  throw new Error(`state mismatch: ${expected}`);
13327
13419
  };
13328
13420
  var nonEmpty = (path2) => {
13329
- if (!existsSync17(path2) || !statSync4(path2).isFile() || statSync4(path2).size < 1)
13421
+ if (!existsSync18(path2) || !statSync4(path2).isFile() || statSync4(path2).size < 1)
13330
13422
  throw new Error(`required file missing: ${path2}`);
13331
13423
  };
13332
13424
  var option = (args, name) => {
@@ -13422,18 +13514,18 @@ async function runRecoveryHost(args, run2 = execute2) {
13422
13514
  }
13423
13515
 
13424
13516
  // src/platform-fleet-verification.ts
13425
- import { lstatSync as lstatSync5 } from "fs";
13517
+ import { lstatSync as lstatSync6 } from "fs";
13426
13518
 
13427
13519
  // src/bootstrap.ts
13428
13520
  import {
13429
- chmodSync as chmodSync16,
13430
- existsSync as existsSync19,
13431
- lstatSync as lstatSync4,
13432
- mkdirSync as mkdirSync14,
13433
- readFileSync as readFileSync16,
13434
- renameSync as renameSync11,
13435
- rmSync as rmSync10,
13436
- writeFileSync as writeFileSync14
13521
+ chmodSync as chmodSync17,
13522
+ existsSync as existsSync20,
13523
+ lstatSync as lstatSync5,
13524
+ mkdirSync as mkdirSync15,
13525
+ readFileSync as readFileSync17,
13526
+ renameSync as renameSync12,
13527
+ rmSync as rmSync11,
13528
+ writeFileSync as writeFileSync15
13437
13529
  } from "fs";
13438
13530
  import { dirname as dirname10 } from "path";
13439
13531
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -13505,7 +13597,11 @@ function agentEgressUnit(options) {
13505
13597
  const user = options.user ?? "forgezero";
13506
13598
  if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(user))
13507
13599
  throw new Error("invalid Agent service user");
13508
- const deploymentEnabled = Boolean(options.repository || options.pullDeployments);
13600
+ const bootstrapBundleEnabled = Boolean(options.bootstrapBundlePath && options.bootstrapBundleManifestPath);
13601
+ if (Boolean(options.bootstrapBundlePath) !== Boolean(options.bootstrapBundleManifestPath) || bootstrapBundleEnabled && ![options.bootstrapBundlePath, options.bootstrapBundleManifestPath].every((path2) => path2.startsWith("/") && !/[\r\n\0:]/.test(path2))) {
13602
+ throw new Error("bootstrap bundle and manifest must be supplied together as absolute paths");
13603
+ }
13604
+ const deploymentEnabled = Boolean(options.repository || bootstrapBundleEnabled || options.pullDeployments);
13509
13605
  const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
13510
13606
  const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
13511
13607
  if (deploymentEnabled && runnerPublicTcpPorts.length < 1) {
@@ -13975,6 +14071,8 @@ function agentUnit(options) {
13975
14071
  options.gitPublicKeyPath ? `FZ_GIT_PUBLIC_KEY_FILE=${options.gitPublicKeyPath}` : null,
13976
14072
  options.repository ? `FZ_DEPLOY_REPO=${options.repository}` : null,
13977
14073
  options.branch ? `FZ_DEPLOY_BRANCH=${options.branch}` : null,
14074
+ options.bootstrapBundlePath ? `FZ_BOOTSTRAP_BUNDLE=${options.bootstrapBundlePath}` : null,
14075
+ options.bootstrapBundleManifestPath ? `FZ_BOOTSTRAP_BUNDLE_MANIFEST=${options.bootstrapBundleManifestPath}` : null,
13978
14076
  options.profile ? `FZ_DEPLOY_PROFILE=${options.profile}` : null,
13979
14077
  options.repository && options.branch ? `FZ_DEPLOY_KEY=${options.project ?? "platform"}:${options.environment ?? "production"}` : null,
13980
14078
  deploymentEnabled ? `FZ_DEPLOY_ROOT=${deployRoot}` : null,
@@ -14357,19 +14455,19 @@ function planProvision(options) {
14357
14455
  }
14358
14456
 
14359
14457
  // src/cli/agent-install.ts
14360
- import { randomBytes as randomBytes5 } from "crypto";
14458
+ import { randomBytes as randomBytes6 } from "crypto";
14361
14459
  import {
14362
- chmodSync as chmodSync15,
14460
+ chmodSync as chmodSync16,
14363
14461
  copyFileSync as copyFileSync3,
14364
- existsSync as existsSync18,
14365
- lstatSync as lstatSync3,
14366
- mkdirSync as mkdirSync13,
14367
- readFileSync as readFileSync15,
14462
+ existsSync as existsSync19,
14463
+ lstatSync as lstatSync4,
14464
+ mkdirSync as mkdirSync14,
14465
+ readFileSync as readFileSync16,
14368
14466
  realpathSync as realpathSync6,
14369
- renameSync as renameSync10,
14370
- rmSync as rmSync9,
14467
+ renameSync as renameSync11,
14468
+ rmSync as rmSync10,
14371
14469
  symlinkSync as symlinkSync5,
14372
- writeFileSync as writeFileSync13
14470
+ writeFileSync as writeFileSync14
14373
14471
  } from "fs";
14374
14472
  import { dirname as dirname9 } from "path";
14375
14473
  async function readCapabilities(run2) {
@@ -14430,52 +14528,52 @@ var runProvisionOperation = async (operation) => {
14430
14528
  }
14431
14529
  if (operation.kind === "install-runtime") {
14432
14530
  const release = `/opt/forgezero/agent/versions/${operation.version}`;
14433
- mkdirSync13(`${release}/dist`, { recursive: true, mode: 493 });
14434
- mkdirSync13(dirname9(operation.binary), { recursive: true, mode: 493 });
14531
+ mkdirSync14(`${release}/dist`, { recursive: true, mode: 493 });
14532
+ mkdirSync14(dirname9(operation.binary), { recursive: true, mode: 493 });
14435
14533
  copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
14436
- chmodSync15(`${release}/dist/fz-agent.js`, 493);
14534
+ chmodSync16(`${release}/dist/fz-agent.js`, 493);
14437
14535
  const gitSshSource = `${dirname9(operation.source)}/fz-git-ssh.js`;
14438
- if (!existsSync18(gitSshSource))
14536
+ if (!existsSync19(gitSshSource))
14439
14537
  return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
14440
14538
  copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
14441
- chmodSync15(`${release}/dist/fz-git-ssh.js`, 493);
14539
+ chmodSync16(`${release}/dist/fz-git-ssh.js`, 493);
14442
14540
  const pending = "/opt/forgezero/agent/current.next";
14443
- rmSync9(pending, { force: true });
14541
+ rmSync10(pending, { force: true });
14444
14542
  symlinkSync5(`versions/${operation.version}`, pending);
14445
- renameSync10(pending, "/opt/forgezero/agent/current");
14446
- rmSync9(operation.binary, { force: true });
14543
+ renameSync11(pending, "/opt/forgezero/agent/current");
14544
+ rmSync10(operation.binary, { force: true });
14447
14545
  symlinkSync5("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
14448
14546
  const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
14449
- rmSync9(gitSshBinary, { force: true });
14547
+ rmSync10(gitSshBinary, { force: true });
14450
14548
  symlinkSync5("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
14451
14549
  return { stdout: "", exitCode: 0 };
14452
14550
  }
14453
14551
  if (operation.kind === "ensure-seed") {
14454
- if (existsSync18(operation.credential) && lstatSync3(operation.credential).size > 0)
14552
+ if (existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
14455
14553
  return { stdout: "", exitCode: 0 };
14456
- const seed = randomBytes5(32).toString("base64url");
14554
+ const seed = randomBytes6(32).toString("base64url");
14457
14555
  const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
14458
14556
  if (result.exitCode === 0)
14459
- chmodSync15(operation.credential, 256);
14557
+ chmodSync16(operation.credential, 256);
14460
14558
  return result;
14461
14559
  }
14462
14560
  if (operation.kind === "ensure-git-identity") {
14463
14561
  const key = "/run/forgezero-git-deploy-key";
14464
14562
  const publicKey = `${key}.pub`;
14465
14563
  try {
14466
- if (!existsSync18(operation.credential) || lstatSync3(operation.credential).size < 1) {
14467
- rmSync9(key, { force: true });
14468
- rmSync9(publicKey, { force: true });
14564
+ if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
14565
+ rmSync10(key, { force: true });
14566
+ rmSync10(publicKey, { force: true });
14469
14567
  let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
14470
14568
  if (result.exitCode !== 0)
14471
14569
  return result;
14472
14570
  result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=git-deploy-key", key, operation.credential]);
14473
14571
  if (result.exitCode !== 0)
14474
14572
  return result;
14475
- chmodSync15(operation.credential, 256);
14573
+ chmodSync16(operation.credential, 256);
14476
14574
  }
14477
- if (!existsSync18(operation.publicKey) || lstatSync3(operation.publicKey).size < 1) {
14478
- if (!existsSync18(key)) {
14575
+ if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
14576
+ if (!existsSync19(key)) {
14479
14577
  const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
14480
14578
  if (decrypted.exitCode !== 0)
14481
14579
  return decrypted;
@@ -14483,30 +14581,30 @@ var runProvisionOperation = async (operation) => {
14483
14581
  const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
14484
14582
  if (derived.exitCode !== 0)
14485
14583
  return derived;
14486
- writeFileSync13(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
14584
+ writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
14487
14585
  `, { mode: 292 });
14488
14586
  }
14489
14587
  return { stdout: "", exitCode: 0 };
14490
14588
  } finally {
14491
- rmSync9(key, { force: true });
14492
- rmSync9(publicKey, { force: true });
14589
+ rmSync10(key, { force: true });
14590
+ rmSync10(publicKey, { force: true });
14493
14591
  }
14494
14592
  }
14495
14593
  if (operation.kind === "ensure-bootstrap-ssh-identity") {
14496
14594
  const key = "/run/forgezero-bootstrap-ssh-key";
14497
14595
  const generatedPublicKey = `${key}.pub`;
14498
14596
  try {
14499
- if (!existsSync18(operation.credential) || lstatSync3(operation.credential).size < 1) {
14500
- rmSync9(key, { force: true });
14501
- rmSync9(generatedPublicKey, { force: true });
14597
+ if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
14598
+ rmSync10(key, { force: true });
14599
+ rmSync10(generatedPublicKey, { force: true });
14502
14600
  let result;
14503
14601
  if (operation.source) {
14504
- const source = existsSync18(operation.source) ? lstatSync3(operation.source) : undefined;
14602
+ const source = existsSync19(operation.source) ? lstatSync4(operation.source) : undefined;
14505
14603
  if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
14506
14604
  return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
14507
14605
  }
14508
14606
  copyFileSync3(operation.source, key);
14509
- chmodSync15(key, 384);
14607
+ chmodSync16(key, 384);
14510
14608
  } else {
14511
14609
  result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-bootstrap-runner", "-f", key]);
14512
14610
  if (result.exitCode !== 0)
@@ -14519,48 +14617,48 @@ var runProvisionOperation = async (operation) => {
14519
14617
  result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
14520
14618
  if (result.exitCode !== 0)
14521
14619
  return result;
14522
- chmodSync15(operation.credential, 256);
14620
+ chmodSync16(operation.credential, 256);
14523
14621
  }
14524
- if (!existsSync18(operation.publicKey) || lstatSync3(operation.publicKey).size < 1) {
14525
- if (!existsSync18(key)) {
14622
+ if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
14623
+ if (!existsSync19(key)) {
14526
14624
  const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
14527
14625
  if (decrypted.exitCode !== 0)
14528
14626
  return decrypted;
14529
- chmodSync15(key, 384);
14627
+ chmodSync16(key, 384);
14530
14628
  }
14531
14629
  const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
14532
14630
  if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
14533
14631
  return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
14534
14632
  }
14535
- mkdirSync13(dirname9(operation.publicKey), { recursive: true, mode: 493 });
14536
- writeFileSync13(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
14633
+ mkdirSync14(dirname9(operation.publicKey), { recursive: true, mode: 493 });
14634
+ writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
14537
14635
  `, { mode: 292 });
14538
- chmodSync15(operation.publicKey, 292);
14636
+ chmodSync16(operation.publicKey, 292);
14539
14637
  }
14540
14638
  if (operation.source)
14541
- rmSync9(operation.source, { force: true });
14639
+ rmSync10(operation.source, { force: true });
14542
14640
  return { stdout: "", exitCode: 0 };
14543
14641
  } finally {
14544
- rmSync9(key, { force: true });
14545
- rmSync9(generatedPublicKey, { force: true });
14642
+ rmSync10(key, { force: true });
14643
+ rmSync10(generatedPublicKey, { force: true });
14546
14644
  }
14547
14645
  }
14548
14646
  if (operation.kind === "ensure-enrolment") {
14549
- if (existsSync18(operation.state) && lstatSync3(operation.state).size > 0 || existsSync18(operation.credential) && lstatSync3(operation.credential).size > 0)
14647
+ if (existsSync19(operation.state) && lstatSync4(operation.state).size > 0 || existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
14550
14648
  return { stdout: "", exitCode: 0 };
14551
- if (!existsSync18(operation.source))
14649
+ if (!existsSync19(operation.source))
14552
14650
  return { stdout: "enrolment source is missing", exitCode: 1 };
14553
14651
  const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
14554
14652
  if (result.exitCode === 0) {
14555
- chmodSync15(operation.credential, 256);
14556
- rmSync9(operation.source, { force: true });
14653
+ chmodSync16(operation.credential, 256);
14654
+ rmSync10(operation.source, { force: true });
14557
14655
  }
14558
14656
  return result;
14559
14657
  }
14560
14658
  if (operation.kind === "wait-socket") {
14561
14659
  for (let attempt = 0;attempt < operation.attempts; attempt += 1) {
14562
14660
  try {
14563
- if (lstatSync3(operation.path).isSocket())
14661
+ if (lstatSync4(operation.path).isSocket())
14564
14662
  return { stdout: "", exitCode: 0 };
14565
14663
  } catch {}
14566
14664
  await Bun.sleep(operation.intervalMs);
@@ -14568,7 +14666,7 @@ var runProvisionOperation = async (operation) => {
14568
14666
  return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
14569
14667
  }
14570
14668
  if (operation.kind === "verify-file")
14571
- return existsSync18(operation.path) && lstatSync3(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
14669
+ return existsSync19(operation.path) && lstatSync4(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
14572
14670
  if (operation.kind === "verify-egress") {
14573
14671
  const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
14574
14672
  if (active.exitCode !== 0)
@@ -14590,25 +14688,25 @@ var runProvisionOperation = async (operation) => {
14590
14688
  }
14591
14689
  }
14592
14690
  if (operation.kind === "install-warp") {
14593
- const os = readFileSync15("/etc/os-release", "utf8");
14691
+ const os = readFileSync16("/etc/os-release", "utf8");
14594
14692
  if (!/^ID=ubuntu$/m.test(os) || !/^VERSION_ID="?26\.04"?$/m.test(os))
14595
14693
  return { stdout: "unsupported WARP host OS", exitCode: 1 };
14596
14694
  const response = await fetch("https://pkg.cloudflareclient.com/pubkey.gpg", { signal: AbortSignal.timeout(30000) });
14597
14695
  if (!response.ok)
14598
14696
  return { stdout: `WARP key HTTP ${response.status}`, exitCode: 1 };
14599
- mkdirSync13("/usr/share/keyrings", { recursive: true, mode: 493 });
14600
- mkdirSync13("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
14601
- mkdirSync13("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
14697
+ mkdirSync14("/usr/share/keyrings", { recursive: true, mode: 493 });
14698
+ mkdirSync14("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
14699
+ mkdirSync14("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
14602
14700
  const key = "/run/cloudflare-warp-key.gpg";
14603
- writeFileSync13(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
14701
+ writeFileSync14(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
14604
14702
  let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
14605
- rmSync9(key, { force: true });
14703
+ rmSync10(key, { force: true });
14606
14704
  if (result.exitCode !== 0)
14607
14705
  return result;
14608
14706
  const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
14609
14707
  if (!codename)
14610
14708
  return { stdout: "Ubuntu codename missing", exitCode: 1 };
14611
- writeFileSync13("/etc/apt/sources.list.d/cloudflare-client.list", `deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ ${codename} main
14709
+ writeFileSync14("/etc/apt/sources.list.d/cloudflare-client.list", `deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ ${codename} main
14612
14710
  `, { mode: 420 });
14613
14711
  result = await fixed(["/usr/bin/apt-get", "update", "-qq"]);
14614
14712
  return result.exitCode === 0 ? fixed(["/usr/bin/apt-get", "install", "-y", "cloudflare-warp"]) : result;
@@ -14623,7 +14721,7 @@ async function localRunner(operation) {
14623
14721
  if (capability.kind === "version")
14624
14722
  return fixed(capability.argv);
14625
14723
  try {
14626
- const metadata = lstatSync3(capability.path);
14724
+ const metadata = lstatSync4(capability.path);
14627
14725
  const present = capability.nodeType === "directory" ? metadata.isDirectory() : true;
14628
14726
  return { stdout: present ? `yes
14629
14727
  ` : `no
@@ -14670,6 +14768,7 @@ var SEED_CREDENTIAL = `${CREDS}/seed-sync-root.cred`;
14670
14768
  var BACKUP_RECOVERY_CREDENTIAL = `${CREDS}/backup-recovery-root.cred`;
14671
14769
  var BOOTSTRAP_SSH_CREDENTIAL = `${CREDS}/bootstrap-ssh-key.cred`;
14672
14770
  var BOOTSTRAP_SSH_PUBLIC_KEY = "/etc/forgezero/bootstrap/runner.pub";
14771
+ var BOOTSTRAP_RELEASE_EVIDENCE = "/var/lib/forgezero/bootstrap-release.json";
14673
14772
  var LIFECYCLE_PROFILE = "/etc/forgezero/lifecycle.json";
14674
14773
  var CONTROL_SOCKET = "/run/forgezero/control.sock";
14675
14774
  var CLOUDFLARED_METRICS_ADDRESS = "127.0.0.1:20241";
@@ -14862,19 +14961,19 @@ function localBootstrapHost() {
14862
14961
  };
14863
14962
  return {
14864
14963
  uid: () => process.getuid?.() ?? -1,
14865
- exists: existsSync19,
14866
- read: (path2) => readFileSync16(path2, "utf8"),
14964
+ exists: existsSync20,
14965
+ read: (path2) => readFileSync17(path2, "utf8"),
14867
14966
  write(path2, content, mode) {
14868
- mkdirSync14(dirname10(path2), { recursive: true, mode: 493 });
14967
+ mkdirSync15(dirname10(path2), { recursive: true, mode: 493 });
14869
14968
  const temporary = `${path2}.next.${process.pid}`;
14870
- writeFileSync14(temporary, content, { mode });
14871
- chmodSync16(temporary, mode);
14872
- renameSync11(temporary, path2);
14969
+ writeFileSync15(temporary, content, { mode });
14970
+ chmodSync17(temporary, mode);
14971
+ renameSync12(temporary, path2);
14873
14972
  },
14874
- mkdir: (path2, mode) => mkdirSync14(path2, { recursive: true, mode }),
14875
- remove: (path2) => rmSync10(path2, { force: true }),
14973
+ mkdir: (path2, mode) => mkdirSync15(path2, { recursive: true, mode }),
14974
+ remove: (path2) => rmSync11(path2, { force: true }),
14876
14975
  inspect(path2) {
14877
- const value = lstatSync4(path2);
14976
+ const value = lstatSync5(path2);
14878
14977
  return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
14879
14978
  },
14880
14979
  exec: execute3,
@@ -14896,7 +14995,12 @@ function localBootstrapHost() {
14896
14995
  async installAgent(config) {
14897
14996
  const capabilities = await readCapabilities(localRunner);
14898
14997
  const deployRoot = config.deployRoot ?? "/opt/forgezero";
14899
- const hasBinding = config.kind === "enrolled-compute" || existsSync19(ENROL_CREDENTIAL) || existsSync19("/var/lib/forgezero/enrolment.json");
14998
+ const hasBinding = existsSync20("/var/lib/forgezero/enrolment.json");
14999
+ const initialBundle = config.kind === "platform" && !existsSync20(BOOTSTRAP_RELEASE_EVIDENCE) ? {
15000
+ path: config.bootstrapBundle.bundleFile,
15001
+ manifestPath: config.bootstrapBundle.manifestFile,
15002
+ manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync17(config.bootstrapBundle.manifestFile, "utf8")))
15003
+ } : undefined;
14900
15004
  if (config.kind === "platform") {
14901
15005
  const lifecycle = config.database.role === "none" ? {
14902
15006
  apiUnits: ["forgezero@blue.service", "forgezero@green.service"],
@@ -14908,8 +15012,8 @@ function localBootstrapHost() {
14908
15012
  databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
14909
15013
  databasePorts: [8529]
14910
15014
  };
14911
- mkdirSync14(dirname10(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
14912
- writeFileSync14(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
15015
+ mkdirSync15(dirname10(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
15016
+ writeFileSync15(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
14913
15017
  `, { mode: 256 });
14914
15018
  }
14915
15019
  const plan = planInstall({
@@ -14917,15 +15021,17 @@ function localBootstrapHost() {
14917
15021
  socketPath: DEFAULT_SOCKET,
14918
15022
  seedPath: "/var/lib/forgezero/node.seed",
14919
15023
  controlSocketPath: "/run/forgezero/control.sock",
14920
- repository: config.repository,
14921
- branch: config.branch,
15024
+ repository: initialBundle?.path ?? (config.kind === "enrolled-compute" ? config.repository : undefined),
15025
+ branch: initialBundle?.manifest.branch ?? (config.kind === "enrolled-compute" ? config.branch : undefined),
15026
+ bootstrapBundlePath: initialBundle?.path,
15027
+ bootstrapBundleManifestPath: initialBundle?.manifestPath,
14922
15028
  profile: config.kind === "platform" ? config.profile : config.profile,
14923
15029
  deployRoot,
14924
15030
  deploymentCredentials: config.deploymentCredentials,
14925
15031
  publicApiUrl: config.apiUrl,
14926
- gitCredentialPath: "/etc/forgezero/creds/git-deploy-key.cred",
14927
- gitPublicKeyPath: "/etc/forgezero/git/deploy.pub",
14928
- generateGitIdentity: true,
15032
+ gitCredentialPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
15033
+ gitPublicKeyPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
15034
+ generateGitIdentity: config.kind === "enrolled-compute" && config.gitDeployKey === true,
14929
15035
  pullDeployments: hasBinding,
14930
15036
  pullMigrations: config.kind === "platform" && hasBinding,
14931
15037
  pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
@@ -14939,9 +15045,11 @@ function localBootstrapHost() {
14939
15045
  telemetryEndpoint: config.telemetryEndpoint,
14940
15046
  binPath: "/usr/local/lib/forgezero/agent/fz-agent",
14941
15047
  sourceBinPath: PACKAGED_AGENT_BIN,
15048
+ ...existsSync20(ENROL_CREDENTIAL) || hasBinding ? {
15049
+ enrolTokenCredentialPath: existsSync20(ENROL_CREDENTIAL) ? ENROL_CREDENTIAL : undefined,
15050
+ enrolStatePath: "/var/lib/forgezero/enrolment.json"
15051
+ } : {},
14942
15052
  ...hasBinding ? {
14943
- enrolTokenCredentialPath: ENROL_CREDENTIAL,
14944
- enrolStatePath: "/var/lib/forgezero/enrolment.json",
14945
15053
  apiUrl: config.apiUrl,
14946
15054
  project: config.kind === "enrolled-compute" ? config.realm : "platform",
14947
15055
  environment: config.kind === "enrolled-compute" ? undefined : config.environment,
@@ -14949,8 +15057,8 @@ function localBootstrapHost() {
14949
15057
  } : {}
14950
15058
  });
14951
15059
  for (const unit3 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
14952
- mkdirSync14(dirname10(unit3.path), { recursive: true, mode: 493 });
14953
- writeFileSync14(unit3.path, unit3.unit, { mode: 420 });
15060
+ mkdirSync15(dirname10(unit3.path), { recursive: true, mode: 493 });
15061
+ writeFileSync15(unit3.path, unit3.unit, { mode: 420 });
14954
15062
  }
14955
15063
  await applyPlan(plan, localRunner);
14956
15064
  return plan;
@@ -14964,7 +15072,7 @@ var localRuntime = () => ({
14964
15072
  bootstrapStatus: () => bootstrapStatus(),
14965
15073
  characterDevice(path2) {
14966
15074
  try {
14967
- return lstatSync5(path2).isCharacterDevice();
15075
+ return lstatSync6(path2).isCharacterDevice();
14968
15076
  } catch {
14969
15077
  return false;
14970
15078
  }
@@ -15002,8 +15110,8 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
15002
15110
  }
15003
15111
 
15004
15112
  // src/community-rehearsal-host.ts
15005
- import { createHash as createHash10 } from "crypto";
15006
- import { lstatSync as lstatSync6, mkdirSync as mkdirSync15, readFileSync as readFileSync17, rmSync as rmSync11, symlinkSync as symlinkSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync15 } from "fs";
15113
+ import { createHash as createHash11 } from "crypto";
15114
+ import { lstatSync as lstatSync7, mkdirSync as mkdirSync16, readFileSync as readFileSync18, rmSync as rmSync12, symlinkSync as symlinkSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
15007
15115
  import { dirname as dirname11 } from "path";
15008
15116
  var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
15009
15117
  var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
@@ -15200,10 +15308,10 @@ async function exec(argv2, stdin) {
15200
15308
  async function applyOperations(operations) {
15201
15309
  for (const operation of operations) {
15202
15310
  if (operation.kind === "remove-tree")
15203
- rmSync11(operation.path, { recursive: true, force: true });
15311
+ rmSync12(operation.path, { recursive: true, force: true });
15204
15312
  else if (operation.kind === "write") {
15205
- mkdirSync15(dirname11(operation.path), { recursive: true });
15206
- writeFileSync15(operation.path, operation.content, { mode: operation.mode });
15313
+ mkdirSync16(dirname11(operation.path), { recursive: true });
15314
+ writeFileSync16(operation.path, operation.content, { mode: operation.mode });
15207
15315
  } else if (operation.kind === "unlink") {
15208
15316
  try {
15209
15317
  unlinkSync12(operation.path);
@@ -15251,8 +15359,8 @@ async function runCommunityRehearsalHost(request) {
15251
15359
  return { ok: true, action: request.action, node: node.name };
15252
15360
  }
15253
15361
  if (request.action === "prepare") {
15254
- const metadata = lstatSync6(request.archivePath);
15255
- if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash10("sha256").update(readFileSync17(request.archivePath)).digest("hex") !== request.archiveSha256) {
15362
+ const metadata = lstatSync7(request.archivePath);
15363
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash11("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
15256
15364
  throw new Error("community rehearsal archive is not the declared bounded release");
15257
15365
  }
15258
15366
  await ensureCommunityRehearsalArango();
@@ -15300,13 +15408,13 @@ async function runCommunityRehearsalHost(request) {
15300
15408
  }
15301
15409
 
15302
15410
  // src/supervised-app.ts
15303
- import { lstatSync as lstatSync7, readFileSync as readFileSync18 } from "fs";
15411
+ import { lstatSync as lstatSync8, readFileSync as readFileSync19 } from "fs";
15304
15412
  function readConfig(path2) {
15305
- const stat = lstatSync7(path2);
15413
+ const stat = lstatSync8(path2);
15306
15414
  if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== 0 || (stat.mode & 18) !== 0 || stat.size > 128 * 1024) {
15307
15415
  throw new Error("supervised app config is unsafe");
15308
15416
  }
15309
- const value = JSON.parse(readFileSync18(path2, "utf8"));
15417
+ const value = JSON.parse(readFileSync19(path2, "utf8"));
15310
15418
  if (value.format !== 1 || typeof value.root !== "string" || !value.root.startsWith("/") || typeof value.release !== "string" || !value.release.startsWith(`${value.root}/releases/`) || typeof value.home !== "string" || value.home !== `${value.root}/app-home` || !Array.isArray(value.argv) || value.argv.length < 1 || value.argv.length > 256 || value.argv.some((argument) => typeof argument !== "string" || !argument || argument.includes("\x00")) || !value.environment || typeof value.environment !== "object" || Array.isArray(value.environment) || Object.entries(value.environment).some(([name, item]) => !/^FZ_(?:APP_PORT|RELEASE)$/.test(name) || typeof item !== "string" || /[\r\n\0]/.test(item))) {
15311
15419
  throw new Error("supervised app config is malformed");
15312
15420
  }
@@ -15352,17 +15460,17 @@ function agentCommandArgs(args, command2) {
15352
15460
  return args.slice(index + 1);
15353
15461
  }
15354
15462
  function loadOrCreateSeed(path2) {
15355
- if (existsSync20(path2)) {
15356
- const seed2 = new Uint8Array(Buffer.from(readFileSync19(path2, "utf8").trim(), "base64url"));
15463
+ if (existsSync21(path2)) {
15464
+ const seed2 = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
15357
15465
  if (seed2.length < 32) {
15358
15466
  throw new Error(`agent: the seed at ${path2} is too short to derive a key from.`);
15359
15467
  }
15360
15468
  return seed2;
15361
15469
  }
15362
- mkdirSync16(dirname12(path2), { recursive: true });
15363
- const seed = new Uint8Array(randomBytes6(32));
15364
- writeFileSync16(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
15365
- chmodSync17(path2, 384);
15470
+ mkdirSync17(dirname12(path2), { recursive: true });
15471
+ const seed = new Uint8Array(randomBytes7(32));
15472
+ writeFileSync17(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
15473
+ chmodSync18(path2, 384);
15366
15474
  return seed;
15367
15475
  }
15368
15476
  var DEFAULT_SOCKET_PATH = DEFAULT_SOCKET;
@@ -15391,9 +15499,9 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
15391
15499
  if (!directory)
15392
15500
  throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the node identity.");
15393
15501
  const path2 = `${directory}/${name}`;
15394
- if (!existsSync20(path2))
15502
+ if (!existsSync21(path2))
15395
15503
  throw new Error(`agent: the systemd credential ${name} is missing at ${path2}.`);
15396
- const seed = new Uint8Array(Buffer.from(readFileSync19(path2, "utf8").trim(), "base64url"));
15504
+ const seed = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
15397
15505
  if (seed.length < 32)
15398
15506
  throw new Error(`agent: the systemd credential ${name} is too short to derive a key from.`);
15399
15507
  return seed;
@@ -15403,7 +15511,7 @@ function loadTextCredential(name, directory = process.env.CREDENTIALS_DIRECTORY)
15403
15511
  throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the credential.");
15404
15512
  if (!/^[A-Za-z0-9_.-]+$/.test(name))
15405
15513
  throw new Error("agent: invalid systemd credential name.");
15406
- const value = readFileSync19(`${directory}/${name}`, "utf8").trim();
15514
+ const value = readFileSync20(`${directory}/${name}`, "utf8").trim();
15407
15515
  if (!value)
15408
15516
  throw new Error(`agent: systemd credential ${name} is empty.`);
15409
15517
  return value;
@@ -15528,7 +15636,7 @@ if (import.meta.main) {
15528
15636
  if (configPath !== "/etc/forgezero/deploy-activation.json" || !release) {
15529
15637
  throw new Error("platform-activate requires the fixed config and one absolute release path");
15530
15638
  }
15531
- const config = JSON.parse(readFileSync19(configPath, "utf8"));
15639
+ const config = JSON.parse(readFileSync20(configPath, "utf8"));
15532
15640
  const result = await activatePlatformRelease(config, release);
15533
15641
  console.log(`promoted ${result.release} on ${result.slot}`);
15534
15642
  process.exit(0);
@@ -15553,13 +15661,13 @@ if (import.meta.main) {
15553
15661
  if (args.length !== 1)
15554
15662
  throw new Error("project-release-check accepts no coordinates");
15555
15663
  for (const relative of ["src/index.ts", "bun.lock", "src/generated/shared/contract-manifest.json"]) {
15556
- const stat = lstatSync8(join12(process.cwd(), relative));
15664
+ const stat = lstatSync9(join12(process.cwd(), relative));
15557
15665
  if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1) {
15558
15666
  throw new Error(`project release is missing ${relative}`);
15559
15667
  }
15560
15668
  }
15561
15669
  for (const relative of ["package.json", "bun.lock"]) {
15562
- const contents = readFileSync19(join12(process.cwd(), relative), "utf8");
15670
+ const contents = readFileSync20(join12(process.cwd(), relative), "utf8");
15563
15671
  if (/(?:workspace|file):/.test(contents)) {
15564
15672
  throw new Error(`project release ${relative} contains a local dependency`);
15565
15673
  }
@@ -15604,8 +15712,8 @@ if (import.meta.main) {
15604
15712
  keys: keys2,
15605
15713
  label: process.env.FZ_NODE_LABEL,
15606
15714
  edgeHostname: process.env.FZ_NODE_HOSTNAME,
15607
- gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync19(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
15608
- bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ? readFileSync19(process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
15715
+ gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
15716
+ bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
15609
15717
  privateNetworkAttachment: privateNetworkAttachmentFromEnvironment()
15610
15718
  });
15611
15719
  console.log(`[agent] enrolled ${binding2.computeReference} in project ${binding2.projectKey}/${binding2.environmentKey}`);
@@ -15647,7 +15755,7 @@ if (import.meta.main) {
15647
15755
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
15648
15756
  if (!profilePath)
15649
15757
  throw new Error("metal-helper requires --profile=/absolute/path.json");
15650
- const profile2 = JSON.parse(readFileSync19(profilePath, "utf8"));
15758
+ const profile2 = JSON.parse(readFileSync20(profilePath, "utf8"));
15651
15759
  const helper = startMetalHelper({
15652
15760
  profile: profile2,
15653
15761
  socketPath: process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET
@@ -15797,7 +15905,7 @@ if (import.meta.main) {
15797
15905
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
15798
15906
  if (!profilePath)
15799
15907
  throw new Error("metal-isolation requires --profile=/absolute/path.json");
15800
- const profile2 = JSON.parse(readFileSync19(profilePath, "utf8"));
15908
+ const profile2 = JSON.parse(readFileSync20(profilePath, "utf8"));
15801
15909
  await applyMetalIsolation(profile2);
15802
15910
  console.log("[metal-isolation] host and guest cgroup boundaries active");
15803
15911
  process.exit(0);
@@ -15842,7 +15950,7 @@ if (import.meta.main) {
15842
15950
  if (claimArg !== "-" && !claimArg.startsWith("/")) {
15843
15951
  throw new Error("metal-apply claim path must be absolute");
15844
15952
  }
15845
- const raw = readFileSync19(claimArg === "-" ? "/dev/stdin" : claimArg, "utf8");
15953
+ const raw = readFileSync20(claimArg === "-" ? "/dev/stdin" : claimArg, "utf8");
15846
15954
  if (Buffer.byteLength(raw) > 32 * 1024)
15847
15955
  throw new Error("metal-apply claim exceeds 32 KiB");
15848
15956
  const claim = JSON.parse(raw);
@@ -15867,7 +15975,7 @@ if (import.meta.main) {
15867
15975
  if (commandArgs.length !== 1 || commandArgs[0] !== "--request=-") {
15868
15976
  throw new Error("community-rehearsal requires exactly --request=-");
15869
15977
  }
15870
- const raw = readFileSync19("/dev/stdin", "utf8");
15978
+ const raw = readFileSync20("/dev/stdin", "utf8");
15871
15979
  if (Buffer.byteLength(raw) > 128 * 1024)
15872
15980
  throw new Error("community rehearsal request exceeds 128 KiB");
15873
15981
  const request = parseCommunityRehearsalHostRequest(JSON.parse(raw));
@@ -15878,7 +15986,7 @@ if (import.meta.main) {
15878
15986
  if (args.length !== 1 || args[0] !== "--request=-") {
15879
15987
  throw new Error("metal-admission-proof requires exactly --request=-");
15880
15988
  }
15881
- const raw = readFileSync19("/dev/stdin", "utf8");
15989
+ const raw = readFileSync20("/dev/stdin", "utf8");
15882
15990
  if (Buffer.byteLength(raw) > 32 * 1024)
15883
15991
  throw new Error("metal admission proof request exceeds 32 KiB");
15884
15992
  const request = JSON.parse(raw);
@@ -15896,9 +16004,9 @@ if (import.meta.main) {
15896
16004
  vcpu: cpus().length,
15897
16005
  memoryGib: Math.max(1, Math.floor(totalmem() / 1024 ** 3)),
15898
16006
  diskGib: Math.max(1, Math.floor(Number(filesystem.blocks) * Number(filesystem.bsize) / 1024 ** 3)),
15899
- kvm: existsSync20("/dev/kvm"),
15900
- snpHost: existsSync20("/dev/sev"),
15901
- helper: existsSync20(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16007
+ kvm: existsSync21("/dev/kvm"),
16008
+ snpHost: existsSync21("/dev/sev"),
16009
+ helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
15902
16010
  }
15903
16011
  };
15904
16012
  const envelope = signRequest(keys2, keys2.ed25519.publicKey, {
@@ -15933,9 +16041,9 @@ if (import.meta.main) {
15933
16041
  metalHostname: process.env.FZ_METAL_HOSTNAME,
15934
16042
  run: (claim) => requestMetalProvision(claim, process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET),
15935
16043
  metalPreflight: () => ({
15936
- snpHost: existsSync20("/dev/sev"),
15937
- kvm: existsSync20("/dev/kvm"),
15938
- helper: existsSync20(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16044
+ snpHost: existsSync21("/dev/sev"),
16045
+ kvm: existsSync21("/dev/kvm"),
16046
+ helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
15939
16047
  }),
15940
16048
  onEvent: (event, detail) => console.log(`[metal-agent] ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
15941
16049
  });
@@ -16018,7 +16126,7 @@ if (import.meta.main) {
16018
16126
  const telemetry = new AgentTelemetryRuntime(createAgentTelemetry(resolveAgentTelemetryConfig()));
16019
16127
  telemetry.event("agent.started");
16020
16128
  telemetry.setDraining(false);
16021
- const attestationSource = existsSync20("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
16129
+ const attestationSource = existsSync21("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
16022
16130
  const running = runAgent({
16023
16131
  socketPath: process.env.FZ_SOCKET_PATH ?? DEFAULT_SOCKET_PATH,
16024
16132
  seedCredential: process.env.FZ_SEED_CREDENTIAL,
@@ -16033,8 +16141,8 @@ if (import.meta.main) {
16033
16141
  const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
16034
16142
  let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
16035
16143
  const enrolmentCredential = process.env.FZ_ENROL_TOKEN_CREDENTIAL;
16036
- const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync20(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
16037
- const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync20(process.env.FZ_ENROL_TOKEN_FILE));
16144
+ const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync21(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
16145
+ const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync21(process.env.FZ_ENROL_TOKEN_FILE));
16038
16146
  if (!binding && (enrolmentCredentialAvailable || legacyEnrolmentFileAvailable) && process.env.FZ_API) {
16039
16147
  binding = await enrolGuestIdentity({
16040
16148
  apiUrl: process.env.FZ_API,
@@ -16045,8 +16153,8 @@ if (import.meta.main) {
16045
16153
  keys,
16046
16154
  label: process.env.FZ_NODE_LABEL,
16047
16155
  edgeHostname: process.env.FZ_NODE_HOSTNAME,
16048
- gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync19(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
16049
- bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ? readFileSync19(process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
16156
+ gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
16157
+ bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
16050
16158
  privateNetworkAttachment: privateNetworkAttachmentFromEnvironment()
16051
16159
  });
16052
16160
  console.log(`[agent] enrolled ${binding.computeReference} in project ${binding.projectKey}/${binding.environmentKey}`);
@@ -16112,7 +16220,7 @@ if (import.meta.main) {
16112
16220
  const bootstrapEnabled = process.env.FZ_BOOTSTRAP_PULL === "true";
16113
16221
  const bootstrapCredential = process.env.FZ_BOOTSTRAP_SSH_KEY_CREDENTIAL;
16114
16222
  const bootstrapKeyPath = bootstrapCredential && process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/${bootstrapCredential}` : undefined;
16115
- if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync20(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
16223
+ if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync21(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
16116
16224
  throw new Error("agent: bootstrap runner requires enrolment, API, local SSH credential and target OTLP coordinate");
16117
16225
  }
16118
16226
  const bootstrapPull = bootstrapEnabled ? startSshBootstrapPull({
@@ -16149,12 +16257,21 @@ if (import.meta.main) {
16149
16257
  const repository = process.env.FZ_DEPLOY_REPO;
16150
16258
  const branch = process.env.FZ_DEPLOY_BRANCH;
16151
16259
  const profile = process.env.FZ_DEPLOY_PROFILE;
16260
+ const bootstrapBundlePath = process.env.FZ_BOOTSTRAP_BUNDLE;
16261
+ const bootstrapBundleManifestPath = process.env.FZ_BOOTSTRAP_BUNDLE_MANIFEST;
16262
+ if (Boolean(bootstrapBundlePath) !== Boolean(bootstrapBundleManifestPath)) {
16263
+ throw new Error("agent: bootstrap bundle and manifest coordinates must be supplied together");
16264
+ }
16265
+ const bootstrapBundle = bootstrapBundlePath && bootstrapBundleManifestPath ? {
16266
+ path: bootstrapBundlePath,
16267
+ manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync20(bootstrapBundleManifestPath, "utf8")))
16268
+ } : undefined;
16152
16269
  const root = process.env.FZ_DEPLOY_ROOT;
16153
16270
  const pullEnabled = process.env.FZ_DEPLOY_PULL === "true" && Boolean(process.env.FZ_API);
16154
16271
  if (pullEnabled && !binding) {
16155
16272
  throw new Error("agent: outbound guest deployment requires a persisted tenant enrolment binding");
16156
16273
  }
16157
- if (root && (repository && branch && profile || pullEnabled)) {
16274
+ if (root && (repository && branch && profile || bootstrapBundle && branch && profile || pullEnabled)) {
16158
16275
  const managers = new Map;
16159
16276
  const managerOptions = (source, key) => ({
16160
16277
  key,
@@ -16181,7 +16298,10 @@ if (import.meta.main) {
16181
16298
  activateContainer: (request) => requestContainerActivation(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
16182
16299
  projectExec: process.env.FZ_DEPLOY_RUNNER_SOCKET ? (input) => requestDeploymentCommand(input, process.env.FZ_DEPLOY_RUNNER_SOCKET) : undefined
16183
16300
  });
16184
- const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) : undefined;
16301
+ const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) : bootstrapBundle && branch && profile ? createDeploymentManager({
16302
+ ...managerOptions({ repository: bootstrapBundle.path, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `bootstrap:${bootstrapBundle.manifest.sha256}:${profile}`),
16303
+ bootstrapBundle
16304
+ }) : undefined;
16185
16305
  if (staticManager)
16186
16306
  managers.set("__static__", staticManager);
16187
16307
  const control = staticManager ? startControlServer(staticManager, process.env.FZ_CONTROL_SOCKET ?? DEFAULT_CONTROL_SOCKET) : undefined;
@@ -16196,6 +16316,9 @@ if (import.meta.main) {
16196
16316
  canClaim: () => deploymentIntake?.state === "running",
16197
16317
  manager: staticManager,
16198
16318
  managerFor: staticManager ? undefined : (claim) => {
16319
+ if (claim.source.auth?.kind === "ephemeral-token") {
16320
+ return createDeploymentManager(managerOptions(claim.source, claim.pipelineKey));
16321
+ }
16199
16322
  const cacheKey = [
16200
16323
  claim.pipelineKey,
16201
16324
  claim.source.repository,