@forgezero/agent 0.1.74 → 0.1.76

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.
Files changed (43) hide show
  1. package/README.md +126 -17
  2. package/dist/agent-heartbeat.d.ts +25 -1
  3. package/dist/agent-heartbeat.js +45 -5
  4. package/dist/bootstrap.js +10 -7
  5. package/dist/community-rehearsal-host.js +218 -13
  6. package/dist/credential-schema.d.ts +1 -1
  7. package/dist/credential-schema.js +1 -1
  8. package/dist/definition.js +206 -7
  9. package/dist/deploy-actions.d.ts +7 -0
  10. package/dist/deploy-compiler.js +191 -10
  11. package/dist/deploy-file.js +206 -7
  12. package/dist/deploy-plan-runner.d.ts +3 -0
  13. package/dist/deploy-plan-runner.js +190 -9
  14. package/dist/deploy-plan.js +187 -8
  15. package/dist/deploy-providers.d.ts +19 -0
  16. package/dist/deploy.d.ts +89 -3
  17. package/dist/deploy.js +49 -2
  18. package/dist/deployment-connectivity.d.ts +63 -0
  19. package/dist/deployment-connectivity.js +155 -0
  20. package/dist/deployment-pull.d.ts +2 -0
  21. package/dist/deployment-targets.d.ts +9 -0
  22. package/dist/deployment-targets.js +141 -0
  23. package/dist/deployment.d.ts +69 -0
  24. package/dist/fz-agent.js +1122 -313
  25. package/dist/fz.js +234 -26
  26. package/dist/index.d.ts +1 -0
  27. package/dist/metal-bootstrap.js +1 -1
  28. package/dist/metal-helper-socket.js +210 -11
  29. package/dist/metal-provision.js +210 -11
  30. package/dist/operator-bootstrap.js +11 -9
  31. package/dist/platform-bootstrap-runtime.js +209 -10
  32. package/dist/platform-fleet-verification.js +525 -155
  33. package/dist/platform-genesis.js +206 -7
  34. package/dist/process-input.d.ts +11 -0
  35. package/dist/provision.js +472 -99
  36. package/dist/service-supervisor.d.ts +6 -0
  37. package/dist/software-helper.d.ts +3 -0
  38. package/dist/software-helper.js +472 -98
  39. package/dist/software.d.ts +4 -1
  40. package/dist/software.js +210 -8
  41. package/dist/ubuntu.js +206 -7
  42. package/dist/version.d.ts +1 -1
  43. package/package.json +12 -4
package/dist/fz-agent.js CHANGED
@@ -3,9 +3,9 @@
3
3
 
4
4
  // src/index.ts
5
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
- import { cpus, totalmem } from "os";
8
- import { dirname as dirname12, join as join12 } from "path";
6
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, existsSync as existsSync22, mkdirSync as mkdirSync18, chmodSync as chmodSync18, lstatSync as lstatSync9, statfsSync as statfsSync3 } from "fs";
7
+ import { cpus as cpus2, totalmem as totalmem2 } from "os";
8
+ import { dirname as dirname14, join as join12 } from "path";
9
9
 
10
10
  // ../access/dist/security.js
11
11
  var HEX = Array.from({ length: 256 }, (_, index) => index.toString(16).padStart(2, "0"));
@@ -4643,7 +4643,7 @@ function safeOp(line) {
4643
4643
  // src/deployment.ts
4644
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
4645
  import { createHash as createHash5, randomUUID } from "crypto";
4646
- import { dirname, isAbsolute as isAbsolute2, join as join2 } from "path";
4646
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2 } from "path";
4647
4647
 
4648
4648
  // ../runtime/dist/queue.js
4649
4649
  class QueueStoppedError extends Error {
@@ -4953,22 +4953,30 @@ import {
4953
4953
  accessSync,
4954
4954
  chmodSync as chmodSync2,
4955
4955
  copyFileSync,
4956
+ createReadStream,
4956
4957
  existsSync as existsSync2,
4957
4958
  mkdtempSync,
4958
4959
  mkdirSync,
4959
4960
  readFileSync,
4960
4961
  renameSync,
4961
4962
  rmSync,
4963
+ statSync,
4962
4964
  symlinkSync,
4963
4965
  unlinkSync as unlinkSync2,
4964
4966
  writeFileSync
4965
4967
  } from "fs";
4966
4968
  import { tmpdir } from "os";
4967
- import { join } from "path";
4969
+ import { dirname, join } from "path";
4968
4970
  var PINNED_BUN_VERSION = "1.3.14";
4969
4971
  var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
4970
4972
  var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
4971
4973
  var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
4974
+ var NERDCTL_VERSION = "2.3.5";
4975
+ var BUILDKIT_VERSION = "0.32.2";
4976
+ var KATA_CONTAINERS_VERSION = "4.0.0";
4977
+ var NERDCTL_SHA256 = "de3206aeb7cbd5f20f5fb1f55c1e3bf2db1be567812a8a3f5e65eba2488347ee";
4978
+ var BUILDKIT_SHA256 = "2975d0f651ad96ba8b80b9992ae1f9a964f4408569af5b6dc36544165c3926af";
4979
+ var KATA_SHA256 = "2c3b9dfeba355582b40aee462b12916c9740654d0230f696adf719d67b063a8c";
4972
4980
  var OS_CATALOG = [
4973
4981
  {
4974
4982
  id: "ubuntu",
@@ -4998,6 +5006,8 @@ var OS_CATALOG = [
4998
5006
  var SOFTWARE_CATALOG = [
4999
5007
  { id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5000
5008
  { id: "docker", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5009
+ { id: "containerd", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5010
+ { id: "kata-containers", version: "4.0.0", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5001
5011
  { id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5002
5012
  { id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5003
5013
  { id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -5006,6 +5016,7 @@ var SOFTWARE_CATALOG = [
5006
5016
  { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5007
5017
  { id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5008
5018
  { id: "docker", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5019
+ { id: "containerd", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5009
5020
  { id: "nginx", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5010
5021
  { id: "ufw", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5011
5022
  { id: "openssh-client", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -5014,6 +5025,8 @@ var SOFTWARE_CATALOG = [
5014
5025
  var UBUNTU_2604_X64 = [
5015
5026
  { requirement: { id: "bun", version: "1.3.14" } },
5016
5027
  { requirement: { id: "docker", version: "ubuntu-26.04" } },
5028
+ { requirement: { id: "containerd", version: "ubuntu-26.04" } },
5029
+ { requirement: { id: "kata-containers", version: "4.0.0" } },
5017
5030
  { requirement: { id: "nginx", version: "ubuntu-26.04" } },
5018
5031
  { requirement: { id: "arangodb", version: "3.11.14" } },
5019
5032
  { requirement: { id: "cloudflared", version: "2026.7.3" } },
@@ -5025,6 +5038,7 @@ var UBUNTU_2604_X64 = [
5025
5038
  var UBUNTU_2404_X64 = [
5026
5039
  { requirement: { id: "bun", version: "1.3.14" } },
5027
5040
  { requirement: { id: "docker", version: "ubuntu-24.04" } },
5041
+ { requirement: { id: "containerd", version: "ubuntu-24.04" } },
5028
5042
  { requirement: { id: "nginx", version: "ubuntu-24.04" } },
5029
5043
  { requirement: { id: "ufw", version: "ubuntu-24.04" } },
5030
5044
  { requirement: { id: "openssh-client", version: "ubuntu-24.04" } },
@@ -5041,6 +5055,46 @@ var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
5041
5055
  "log-opts": { "max-size": "10m", "max-file": "3" }
5042
5056
  }, null, 2)}
5043
5057
  `;
5058
+ var CONTAINERD_CONFIG_PATH = "/etc/containerd/config.toml";
5059
+ var KATA_SNP_CONFIG_PATH = "/opt/kata/share/defaults/kata-containers/configuration-qemu-snp.toml";
5060
+ var KATA_ACTIVE_CONFIG_PATH = "/etc/kata-containers/configuration.toml";
5061
+ var CONTAINERD_CONFIG = (kata) => `version = 2
5062
+ root = "/var/lib/forgezero/containerd"
5063
+ state = "/run/forgezero/containerd"
5064
+
5065
+ [grpc]
5066
+ address = "/run/containerd/containerd.sock"
5067
+
5068
+ [plugins."io.containerd.grpc.v1.cri".containerd]
5069
+ snapshotter = "overlayfs"
5070
+ default_runtime_name = "runc"
5071
+
5072
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
5073
+ runtime_type = "io.containerd.runc.v2"
5074
+ ${kata ? `
5075
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp]
5076
+ runtime_type = "io.containerd.kata.v2"
5077
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp.options]
5078
+ ConfigPath = "${KATA_ACTIVE_CONFIG_PATH}"
5079
+ ` : ""}`;
5080
+ var BUILDKIT_UNIT = `[Unit]
5081
+ Description=ForgeZero BuildKit daemon
5082
+ After=network-online.target containerd.service
5083
+ Wants=network-online.target
5084
+ Requires=containerd.service
5085
+
5086
+ [Service]
5087
+ Type=notify
5088
+ ExecStart=/usr/local/bin/buildkitd --addr unix:///run/buildkit/buildkitd.sock --root /var/lib/forgezero/buildkit
5089
+ Restart=on-failure
5090
+ RestartSec=2
5091
+ NoNewPrivileges=yes
5092
+ ProtectSystem=strict
5093
+ ReadWritePaths=/var/lib/forgezero/buildkit /run/buildkit
5094
+
5095
+ [Install]
5096
+ WantedBy=multi-user.target
5097
+ `;
5044
5098
  var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
5045
5099
  var runSoftwareCommand = async (argv, env = {}) => {
5046
5100
  let child;
@@ -5060,14 +5114,28 @@ var runSoftwareCommand = async (argv, env = {}) => {
5060
5114
  return { exitCode, output: `${stdout}${stderr}` };
5061
5115
  };
5062
5116
  var run = runSoftwareCommand;
5063
- var download = async (url, destination, sha2562) => {
5064
- const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
5117
+ var download = async (url, destination, sha2562, maximumBytes = 512 * 1024 * 1024) => {
5118
+ const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(30 * 60000) });
5065
5119
  if (!response.ok)
5066
5120
  throw new Error(`download failed with HTTP ${response.status}`);
5067
- const bytes = new Uint8Array(await response.arrayBuffer());
5068
- if (createHash("sha256").update(bytes).digest("hex") !== sha2562)
5121
+ const declared = Number(response.headers.get("content-length") ?? 0);
5122
+ if (declared > maximumBytes)
5123
+ throw new Error("download exceeds reviewed size bound");
5124
+ if (existsSync2(destination))
5125
+ throw new Error("download destination already exists");
5126
+ await Bun.write(destination, response);
5127
+ chmodSync2(destination, 384);
5128
+ if (statSync(destination).size > maximumBytes) {
5129
+ rmSync(destination, { force: true });
5130
+ throw new Error("download exceeds reviewed size bound");
5131
+ }
5132
+ const digest = createHash("sha256");
5133
+ for await (const chunk of createReadStream(destination))
5134
+ digest.update(chunk);
5135
+ if (digest.digest("hex") !== sha2562) {
5136
+ rmSync(destination, { force: true });
5069
5137
  throw new Error("download checksum mismatch");
5070
- writeFileSync(destination, bytes, { mode: 384, flag: "wx" });
5138
+ }
5071
5139
  };
5072
5140
  var successful = (result, pattern) => result.exitCode === 0 && (!pattern || pattern.test(result.output));
5073
5141
  var aptInstall = async (name) => {
@@ -5075,6 +5143,106 @@ var aptInstall = async (name) => {
5075
5143
  const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
5076
5144
  return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", name], environment) : update;
5077
5145
  };
5146
+ var aptInstallMany = async (names) => {
5147
+ const environment = { DEBIAN_FRONTEND: "noninteractive" };
5148
+ const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
5149
+ return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", ...names], environment) : update;
5150
+ };
5151
+ var writeOwnedPolicy = (pathValue, content, mode = 420) => {
5152
+ if (existsSync2(pathValue)) {
5153
+ if (readFileSync(pathValue, "utf8") !== content)
5154
+ throw new Error(`refusing to overwrite non-ForgeZero policy at ${pathValue}`);
5155
+ return;
5156
+ }
5157
+ mkdirSync(dirname(pathValue), { recursive: true, mode: 493 });
5158
+ writeFileSync(pathValue, content, { mode, flag: "wx" });
5159
+ };
5160
+ var installContainerdRuntime = async (directory, osVersion) => {
5161
+ const installed = await aptInstallMany(["containerd", "runc", "containernetworking-plugins"]);
5162
+ if (installed.exitCode !== 0)
5163
+ return installed;
5164
+ const nerdctl = join(directory, "nerdctl.tar.gz");
5165
+ await download(`https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz`, nerdctl, NERDCTL_SHA256);
5166
+ const nerdctlExtract = await run(["/usr/bin/tar", "-xzf", nerdctl, "-C", "/usr/local/bin", "nerdctl"]);
5167
+ if (nerdctlExtract.exitCode !== 0)
5168
+ return nerdctlExtract;
5169
+ chmodSync2("/usr/local/bin/nerdctl", 493);
5170
+ const buildkit = join(directory, "buildkit.tar.gz");
5171
+ await download(`https://github.com/moby/buildkit/releases/download/v${BUILDKIT_VERSION}/buildkit-v${BUILDKIT_VERSION}.linux-amd64.tar.gz`, buildkit, BUILDKIT_SHA256);
5172
+ const unpacked = join(directory, "buildkit");
5173
+ mkdirSync(unpacked, { mode: 448 });
5174
+ const buildkitExtract = await run(["/usr/bin/tar", "-xzf", buildkit, "-C", unpacked]);
5175
+ if (buildkitExtract.exitCode !== 0)
5176
+ return buildkitExtract;
5177
+ for (const binary of ["buildctl", "buildkitd"]) {
5178
+ copyFileSync(join(unpacked, "bin", binary), `/usr/local/bin/${binary}`);
5179
+ chmodSync2(`/usr/local/bin/${binary}`, 493);
5180
+ }
5181
+ mkdirSync("/etc/containerd", { recursive: true, mode: 493 });
5182
+ const expected = CONTAINERD_CONFIG(false);
5183
+ const kataExpected = CONTAINERD_CONFIG(true);
5184
+ if (existsSync2(CONTAINERD_CONFIG_PATH) && ![expected, kataExpected].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
5185
+ return { exitCode: 2, output: "refusing to overwrite an existing containerd configuration that differs from the reviewed ForgeZero policy" };
5186
+ }
5187
+ if (!existsSync2(CONTAINERD_CONFIG_PATH))
5188
+ writeFileSync(CONTAINERD_CONFIG_PATH, expected, { mode: 420, flag: "wx" });
5189
+ writeOwnedPolicy("/etc/systemd/system/forgezero-buildkit.service", BUILDKIT_UNIT);
5190
+ mkdirSync("/var/lib/forgezero/containerd", { recursive: true, mode: 448 });
5191
+ mkdirSync("/var/lib/forgezero/buildkit", { recursive: true, mode: 448 });
5192
+ const reload = await run(["/usr/bin/systemctl", "daemon-reload"]);
5193
+ if (reload.exitCode !== 0)
5194
+ return reload;
5195
+ const enabled = await run(["/usr/bin/systemctl", "enable", "--now", "containerd.service", "forgezero-buildkit.service"]);
5196
+ return enabled.exitCode === 0 ? { exitCode: 0, output: `containerd ${osVersion} with nerdctl ${NERDCTL_VERSION} and BuildKit ${BUILDKIT_VERSION}` } : enabled;
5197
+ };
5198
+ var installKataRuntime = async (directory) => {
5199
+ for (const pathValue of ["/dev/kvm", "/dev/sev"])
5200
+ if (!existsSync2(pathValue))
5201
+ return { exitCode: 2, output: `${pathValue} is required for Kata SEV-SNP` };
5202
+ for (const parameter of ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"]) {
5203
+ if (!existsSync2(parameter) || !/^(1|Y)$/i.test(readFileSync(parameter, "utf8").trim()))
5204
+ return { exitCode: 2, output: `${parameter} does not enable SEV-SNP` };
5205
+ }
5206
+ const dependencies = await aptInstallMany(["zstd"]);
5207
+ if (dependencies.exitCode !== 0)
5208
+ return dependencies;
5209
+ const archive = join(directory, "kata.tar.zst");
5210
+ await download(`https://github.com/kata-containers/kata-containers/releases/download/${KATA_CONTAINERS_VERSION}/kata-static-${KATA_CONTAINERS_VERSION}-amd64.tar.zst`, archive, KATA_SHA256, 2200000000);
5211
+ const tar = join(directory, "kata.tar");
5212
+ const expanded = await run(["/usr/bin/unzstd", "--quiet", archive, "--output", tar]);
5213
+ if (expanded.exitCode !== 0)
5214
+ return expanded;
5215
+ const extracted = await run(["/usr/bin/tar", "-xf", tar, "-C", "/"]);
5216
+ if (extracted.exitCode !== 0)
5217
+ return extracted;
5218
+ if (!existsSync2("/opt/kata/bin/containerd-shim-kata-v2") || !existsSync2(KATA_SNP_CONFIG_PATH)) {
5219
+ return { exitCode: 2, output: "Kata archive does not contain the reviewed QEMU SNP runtime and configuration" };
5220
+ }
5221
+ const snpConfig = readFileSync(KATA_SNP_CONFIG_PATH, "utf8");
5222
+ if (!/^\s*confidential_guest\s*=\s*true\s*$/m.test(snpConfig) || !/^\s*sev_snp_guest\s*=\s*true\s*$/m.test(snpConfig)) {
5223
+ return { exitCode: 2, output: "Kata QEMU configuration does not enable confidential SEV-SNP guests" };
5224
+ }
5225
+ mkdirSync("/etc/kata-containers", { recursive: true, mode: 493 });
5226
+ if (existsSync2(KATA_ACTIVE_CONFIG_PATH) && readFileSync(KATA_ACTIVE_CONFIG_PATH, "utf8") !== snpConfig) {
5227
+ return { exitCode: 2, output: "refusing to overwrite a non-ForgeZero Kata runtime configuration" };
5228
+ }
5229
+ if (!existsSync2(KATA_ACTIVE_CONFIG_PATH))
5230
+ copyFileSync(KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH);
5231
+ chmodSync2(KATA_ACTIVE_CONFIG_PATH, 420);
5232
+ try {
5233
+ unlinkSync2("/usr/local/bin/containerd-shim-kata-v2");
5234
+ } catch {}
5235
+ symlinkSync("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
5236
+ const base = CONTAINERD_CONFIG(false);
5237
+ const kata = CONTAINERD_CONFIG(true);
5238
+ if (!existsSync2(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
5239
+ return { exitCode: 2, output: "containerd must use the reviewed ForgeZero policy before Kata is installed" };
5240
+ }
5241
+ if (readFileSync(CONTAINERD_CONFIG_PATH, "utf8") !== kata)
5242
+ writeFileSync(CONTAINERD_CONFIG_PATH, kata, { mode: 420 });
5243
+ const restarted = await run(["/usr/bin/systemctl", "restart", "containerd.service"]);
5244
+ return restarted.exitCode === 0 ? { exitCode: 0, output: `Kata Containers ${KATA_CONTAINERS_VERSION} QEMU SNP` } : restarted;
5245
+ };
5078
5246
  async function executeSoftwareOperation(operation) {
5079
5247
  const { software, version } = operation;
5080
5248
  if (![...UBUNTU_2604_X64, ...UBUNTU_2404_X64].some(({ requirement }) => requirement.id === software && requirement.version === version)) {
@@ -5092,6 +5260,26 @@ async function executeSoftwareOperation(operation) {
5092
5260
  const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "docker.service"]);
5093
5261
  return active.exitCode === 0 ? { exitCode: 0, output: binary.output } : active;
5094
5262
  }
5263
+ if (software === "containerd") {
5264
+ const [containerd, nerdctl, buildkit, active, builder] = await Promise.all([
5265
+ run(["/usr/bin/containerd", "--version"]),
5266
+ run(["/usr/local/bin/nerdctl", "--version"]),
5267
+ run(["/usr/local/bin/buildkitd", "--version"]),
5268
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]),
5269
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-buildkit.service"])
5270
+ ]);
5271
+ const policy = existsSync2(CONTAINERD_CONFIG_PATH) ? readFileSync(CONTAINERD_CONFIG_PATH, "utf8") : "";
5272
+ const validPolicy = policy === CONTAINERD_CONFIG(false) || policy === CONTAINERD_CONFIG(true);
5273
+ return successful(containerd, /containerd/) && successful(nerdctl, new RegExp(NERDCTL_VERSION.replaceAll(".", "\\."))) && successful(buildkit, new RegExp(BUILDKIT_VERSION.replaceAll(".", "\\."))) && active.exitCode === 0 && builder.exitCode === 0 && validPolicy ? { exitCode: 0, output: `${containerd.output}${nerdctl.output}${buildkit.output}` } : { exitCode: 1, output: "containerd, nerdctl, BuildKit, or ForgeZero storage/runtime policy is unavailable" };
5274
+ }
5275
+ if (software === "kata-containers") {
5276
+ const shim = await run(["/opt/kata/bin/containerd-shim-kata-v2", "--version"]);
5277
+ const policy = existsSync2(CONTAINERD_CONFIG_PATH) ? readFileSync(CONTAINERD_CONFIG_PATH, "utf8") : "";
5278
+ const activeConfig = existsSync2(KATA_ACTIVE_CONFIG_PATH) ? readFileSync(KATA_ACTIVE_CONFIG_PATH, "utf8") : "";
5279
+ const host = ["/dev/kvm", "/dev/sev", KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH].every(existsSync2) && ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"].every((parameter) => existsSync2(parameter) && /^(1|Y)$/i.test(readFileSync(parameter, "utf8").trim()));
5280
+ const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]);
5281
+ return successful(shim, /kata.*4\.0\.0/i) && policy === CONTAINERD_CONFIG(true) && activeConfig === (existsSync2(KATA_SNP_CONFIG_PATH) ? readFileSync(KATA_SNP_CONFIG_PATH, "utf8") : "") && host && active.exitCode === 0 ? { exitCode: 0, output: shim.output } : { exitCode: 1, output: "Kata QEMU SNP runtime, host capability, or containerd policy is unavailable" };
5282
+ }
5095
5283
  if (software === "nginx") {
5096
5284
  const binary = await run(["/usr/sbin/nginx", "-v"]);
5097
5285
  return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
@@ -5143,6 +5331,17 @@ async function executeSoftwareOperation(operation) {
5143
5331
  }
5144
5332
  const directory = mkdtempSync(join(tmpdir(), "forgezero-software-"));
5145
5333
  try {
5334
+ if (software === "containerd")
5335
+ return installContainerdRuntime(directory, version === "ubuntu-24.04" ? "24.04" : "26.04");
5336
+ if (software === "kata-containers") {
5337
+ const containerd = await executeSoftwareOperation({ kind: "check", software: "containerd", version: "ubuntu-26.04" });
5338
+ if (containerd.exitCode !== 0) {
5339
+ const installed2 = await installContainerdRuntime(directory, "26.04");
5340
+ if (installed2.exitCode !== 0)
5341
+ return installed2;
5342
+ }
5343
+ return installKataRuntime(directory);
5344
+ }
5146
5345
  if (software === "cloudflare-warp") {
5147
5346
  const key = join(directory, "cloudflare-warp-key.gpg");
5148
5347
  await download("https://pkg.cloudflareclient.com/pubkey.gpg", key, "0f37fc298c98e88ee3c0ee68c95b69f1dba9eb477abe3167e13982105911264d");
@@ -5224,7 +5423,7 @@ function validateSoftwareRequirements(value, _options = {}) {
5224
5423
  if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
5225
5424
  throw new Error("software requirement contains an unknown field");
5226
5425
  }
5227
- 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)) {
5426
+ if (!["bun", "docker", "containerd", "kata-containers", "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)) {
5228
5427
  throw new Error("software requirement coordinate is invalid");
5229
5428
  }
5230
5429
  const requirement = { id: row.id, version: row.version };
@@ -5998,6 +6197,13 @@ function validateBuiltInAction(definition, context, where) {
5998
6197
  throw new Error(`${where}.with.requirements must name existing requirements`);
5999
6198
  return;
6000
6199
  }
6200
+ if (definition.uses === "forgezero.network/ensure@1") {
6201
+ exact(withValue, ["target"], `${where}.with`);
6202
+ if (typeof withValue.target !== "string" || !Object.values(context.components).some((entry) => entry.target === withValue.target)) {
6203
+ throw new Error(`${where}.with.target must name a target used by a component`);
6204
+ }
6205
+ return;
6206
+ }
6001
6207
  if (definition.uses === "forgezero.exec/argv@1") {
6002
6208
  const allowed = ["component", "argv", "credentials"];
6003
6209
  const unknown = Object.keys(withValue).filter((key) => !allowed.includes(key));
@@ -6053,6 +6259,8 @@ function validateBuiltInProvider(requirement2, where) {
6053
6259
  const expected = {
6054
6260
  "forgezero.bun": { capability: "runtime.javascript", versions: ["1.3.14"], keys: ["installScope"] },
6055
6261
  "forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
6262
+ "forgezero.containerd": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "runtimeClass", "namespace", "snapshotter"] },
6263
+ "forgezero.kata": { capability: "runtime.container", versions: ["4.0.0"], keys: ["installScope", "runtimeClass", "namespace", "hypervisor", "confidentialGuest"] },
6056
6264
  "forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
6057
6265
  "forgezero.arangodb": { capability: "database.arangodb", versions: ["3.11.14"], keys: ["installScope", "runtime", "dataPath", "bind"] },
6058
6266
  "forgezero.cloudflared": { capability: "network.public-tunnel", versions: ["2026.7.3"], keys: ["installScope", "mode"] },
@@ -6073,6 +6281,10 @@ function validateBuiltInProvider(requirement2, where) {
6073
6281
  if (row2.storageDriver !== "overlay2" || row2.dataRoot !== "/var/lib/docker" || row2.liveRestore !== true || row2.logDriver !== "local" || row2.defaultNetwork !== "bridge" || row2.rootless !== false)
6074
6282
  throw new Error(`${where} Docker config is unsupported by the fixed host strategy`);
6075
6283
  }
6284
+ if (requirement2.provider === "forgezero.containerd" && (row2.runtimeClass !== "runc" || row2.namespace !== "forgezero" || row2.snapshotter !== "overlayfs"))
6285
+ throw new Error(`${where} containerd config is unsupported by the fixed host strategy`);
6286
+ if (requirement2.provider === "forgezero.kata" && (row2.runtimeClass !== "kata-qemu-snp" || row2.namespace !== "forgezero" || row2.hypervisor !== "qemu" || row2.confidentialGuest !== "sev-snp"))
6287
+ throw new Error(`${where} Kata config is unsupported by the fixed host strategy`);
6076
6288
  if (requirement2.provider === "forgezero.nginx" && (typeof row2.websocket !== "boolean" || !Number.isInteger(row2.maximumBodyMiB) || Number(row2.maximumBodyMiB) < 1 || Number(row2.maximumBodyMiB) > 1024 || !(row2.workerProcesses === "auto" || Number.isInteger(row2.workerProcesses)) || !Number.isInteger(row2.workerConnections)))
6077
6289
  throw new Error(`${where} Nginx config is invalid`);
6078
6290
  if (requirement2.provider === "forgezero.arangodb" && (row2.runtime !== "native" || !absolute(row2.dataPath) || row2.bind !== "private"))
@@ -6150,7 +6362,7 @@ function reference(value, where) {
6150
6362
  const row2 = object(value, where);
6151
6363
  exact3(row2, ["$ref"], where);
6152
6364
  const coordinate = boundedString(row2.$ref, `${where}.$ref`, 256);
6153
- if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62})$/.test(coordinate)) {
6365
+ if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62}|execution\.(targetName|slot|releaseExecutor|runtime|cpuCores|memoryMiB|storageGiB))$/.test(coordinate)) {
6154
6366
  fail(where, "contains an unsupported reference.");
6155
6367
  }
6156
6368
  return { $ref: coordinate };
@@ -6390,10 +6602,9 @@ function validateNetwork(value, where) {
6390
6602
  named(container.network, `${where}.container.network`);
6391
6603
  }
6392
6604
  }
6393
- function validateComponent(value, where, targetNames, requirements) {
6605
+ function validateComponent(value, where, targets, requirements) {
6394
6606
  const row2 = object(value, where);
6395
- if (!targetNames.has(String(row2.target)))
6396
- fail(`${where}.target`, "must name an existing target.");
6607
+ const selectedTarget = targets.get(String(row2.target)) ?? fail(`${where}.target`, "must name an existing target.");
6397
6608
  if (row2.kind === "application") {
6398
6609
  exact3(row2, ["kind", "target", "runtime", "service", "resources", "storage", "network", "rollout"], where);
6399
6610
  const runtime = object(row2.runtime, `${where}.runtime`);
@@ -6404,7 +6615,9 @@ function validateComponent(value, where, targetNames, requirements) {
6404
6615
  exact3(runtime, ["kind", "provider", "requirement", "argv"], `${where}.runtime`);
6405
6616
  validateArgv(runtime.argv, `${where}.runtime.argv`);
6406
6617
  } else if (runtime.kind === "container") {
6407
- exact3(runtime, ["kind", "provider", "requirement", "image", "entrypoint", "security"], `${where}.runtime`);
6618
+ exact3(runtime, ["kind", "provider", "requirement", "runtimeClass", "image", "entrypoint", "security"], `${where}.runtime`);
6619
+ if (runtime.runtimeClass !== undefined && !["runc", "kata-qemu-snp"].includes(String(runtime.runtimeClass)))
6620
+ fail(`${where}.runtime.runtimeClass`, "is unsupported.");
6408
6621
  const image = object(runtime.image, `${where}.runtime.image`);
6409
6622
  exact3(image, ["source"], `${where}.runtime.image`);
6410
6623
  const source = object(image.source, `${where}.runtime.image.source`);
@@ -6438,6 +6651,23 @@ function validateComponent(value, where, targetNames, requirements) {
6438
6651
  fail(`${where}.runtime.requirement`, "must provide runtime.container.");
6439
6652
  if (runtimeRequirement && runtime.kind === "native" && !runtimeRequirement.capability.startsWith("runtime."))
6440
6653
  fail(`${where}.runtime.requirement`, "must provide a runtime capability.");
6654
+ const execution = selectedTarget.allocation.execution;
6655
+ if (execution === "native" && runtime.kind !== "native")
6656
+ fail(`${where}.runtime.kind`, "must be native for the selected target.");
6657
+ if (execution !== "native" && runtime.kind !== "container")
6658
+ fail(`${where}.runtime.kind`, "must be container for the selected target.");
6659
+ if (runtime.kind === "container" && execution === "oci-runc") {
6660
+ if (!["forgezero.containerd", "forgezero.docker"].includes(String(runtime.provider)))
6661
+ fail(`${where}.runtime.provider`, "must be forgezero.containerd for an OCI runc target.");
6662
+ if (runtime.runtimeClass !== undefined && runtime.runtimeClass !== "runc")
6663
+ fail(`${where}.runtime.runtimeClass`, "must be runc for the selected target.");
6664
+ }
6665
+ if (runtime.kind === "container" && execution === "oci-kata-qemu-snp") {
6666
+ if (runtime.provider !== "forgezero.kata")
6667
+ fail(`${where}.runtime.provider`, "must be forgezero.kata for a Kata QEMU SNP target.");
6668
+ if (runtime.runtimeClass !== "kata-qemu-snp")
6669
+ fail(`${where}.runtime.runtimeClass`, "must be kata-qemu-snp for the selected target.");
6670
+ }
6441
6671
  const service = object(row2.service, `${where}.service`);
6442
6672
  exact3(service, ["protocol", "port", "health", "websocket", "maximumConnections"], `${where}.service`);
6443
6673
  if (service.protocol !== "http")
@@ -6469,6 +6699,18 @@ function validateComponent(value, where, targetNames, requirements) {
6469
6699
  const network = object(row2.network, `${where}.network`);
6470
6700
  if (network.container === undefined)
6471
6701
  fail(`${where}.network.container`, "must select bridge or reviewed host networking.");
6702
+ if (selectedTarget.allocation.sharing === "shared") {
6703
+ if (object(runtime.security, `${where}.runtime.security`).root !== "read-only")
6704
+ fail(`${where}.runtime.security.root`, "must be read-only on a shared compute.");
6705
+ if (network.container.mode !== "bridge")
6706
+ fail(`${where}.network.container.mode`, "must be bridge on a shared compute.");
6707
+ if (Array.isArray(row2.storage) && row2.storage.some((mount) => mount.class !== "ephemeral")) {
6708
+ fail(`${where}.storage`, "may contain only size-bounded tmpfs mounts on a shared compute.");
6709
+ }
6710
+ }
6711
+ }
6712
+ if (runtime.kind === "native" && selectedTarget.allocation.sharing !== "exclusive") {
6713
+ fail(`${where}.target`, "native applications require an exclusive compute.");
6472
6714
  }
6473
6715
  if (row2.storage !== undefined) {
6474
6716
  if (!Array.isArray(row2.storage) || row2.storage.length > 32)
@@ -6505,6 +6747,8 @@ function validateComponent(value, where, targetNames, requirements) {
6505
6747
  fail(`${where}.runtime.mode`, "must match the provider runtime mode.");
6506
6748
  if (runtime.mode !== "native")
6507
6749
  fail(`${where}.runtime.mode`, "must be native in deployment plan v1.");
6750
+ if (selectedTarget.allocation.execution !== "native")
6751
+ fail(`${where}.target`, "must select a native execution target for database components.");
6508
6752
  const topology = object(row2.topology, `${where}.topology`);
6509
6753
  if (topology.mode === "standalone")
6510
6754
  exact3(topology, ["mode"], `${where}.topology`);
@@ -6701,15 +6945,20 @@ function compileDeployment(sourceValue) {
6701
6945
  if (targetEntries.length < 1 || targetEntries.length > 64)
6702
6946
  fail("deployment.spec.targets", "must contain 1 to 64 targets.");
6703
6947
  const targetNames = new Set(targetEntries.map(([targetName]) => named(targetName, "deployment.spec.targets key")));
6948
+ const targetDefinitions = new Map;
6704
6949
  for (const [targetName, value] of targetEntries) {
6705
6950
  const target = object(value, `deployment.spec.targets.${targetName}`);
6706
- exact3(target, ["kind", "selector", "cardinality", "placement"], `deployment.spec.targets.${targetName}`);
6951
+ exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "placement"], `deployment.spec.targets.${targetName}`);
6707
6952
  if (target.kind !== "compute")
6708
6953
  fail(`deployment.spec.targets.${targetName}.kind`, "must be compute.");
6709
6954
  const selector = object(target.selector, `deployment.spec.targets.${targetName}.selector`);
6710
- exact3(selector, ["profiles", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
6955
+ exact3(selector, ["profiles", "operatingSystem", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
6711
6956
  if (!Array.isArray(selector.profiles) || selector.profiles.length < 1 || selector.profiles.length > 32 || selector.profiles.some((profile) => typeof profile !== "string" || !NAME2.test(profile)) || new Set(selector.profiles).size !== selector.profiles.length)
6712
6957
  fail(`deployment.spec.targets.${targetName}.selector.profiles`, "is invalid.");
6958
+ const operatingSystem = object(selector.operatingSystem, `deployment.spec.targets.${targetName}.selector.operatingSystem`);
6959
+ exact3(operatingSystem, ["id", "version", "architecture"], `deployment.spec.targets.${targetName}.selector.operatingSystem`);
6960
+ if (operatingSystem.id !== "ubuntu" || !["24.04", "26.04"].includes(String(operatingSystem.version)) || operatingSystem.architecture !== "x64")
6961
+ fail(`deployment.spec.targets.${targetName}.selector.operatingSystem`, "is unsupported.");
6713
6962
  if (selector.confidentialCompute !== undefined && !["required", "preferred", "disabled"].includes(String(selector.confidentialCompute)))
6714
6963
  fail(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "is unsupported.");
6715
6964
  if (selector.labels !== undefined)
@@ -6722,8 +6971,108 @@ function compileDeployment(sourceValue) {
6722
6971
  integer(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`, minimum, maximum);
6723
6972
  else
6724
6973
  reference(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`);
6974
+ const allocation = object(target.allocation, `deployment.spec.targets.${targetName}.allocation`);
6975
+ exact3(allocation, ["execution", "sharing", "resources", "existing"], `deployment.spec.targets.${targetName}.allocation`);
6976
+ if (!["native", "oci-runc", "oci-kata-qemu-snp"].includes(String(allocation.execution)))
6977
+ fail(`deployment.spec.targets.${targetName}.allocation.execution`, "is unsupported.");
6978
+ if (!["exclusive", "shared"].includes(String(allocation.sharing)))
6979
+ fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "is unsupported.");
6980
+ if (!["prefer", "require"].includes(String(allocation.existing)))
6981
+ fail(`deployment.spec.targets.${targetName}.allocation.existing`, "is unsupported.");
6982
+ const capacity = object(allocation.resources, `deployment.spec.targets.${targetName}.allocation.resources`);
6983
+ exact3(capacity, ["cpuCores", "memoryMiB", "storageGiB"], `deployment.spec.targets.${targetName}.allocation.resources`);
6984
+ const capacityValue = (value2, where, minimumValue, maximumValue) => {
6985
+ if (typeof value2 === "number")
6986
+ integer(value2, where, minimumValue, maximumValue);
6987
+ else
6988
+ reference(value2, where);
6989
+ };
6990
+ capacityValue(capacity.cpuCores, `deployment.spec.targets.${targetName}.allocation.resources.cpuCores`, 1, 1024);
6991
+ capacityValue(capacity.memoryMiB, `deployment.spec.targets.${targetName}.allocation.resources.memoryMiB`, 128, 4194304);
6992
+ capacityValue(capacity.storageGiB, `deployment.spec.targets.${targetName}.allocation.resources.storageGiB`, 1, 1048576);
6993
+ if (allocation.execution === "oci-kata-qemu-snp" && selector.confidentialCompute !== "required")
6994
+ fail(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "must be required for Kata QEMU SNP.");
6995
+ if ((allocation.execution === "oci-kata-qemu-snp" || selector.confidentialCompute === "required") && operatingSystem.version !== "26.04")
6996
+ fail(`deployment.spec.targets.${targetName}.selector.operatingSystem.version`, "must be Ubuntu 26.04 for SEV-SNP.");
6997
+ if (allocation.execution !== "oci-kata-qemu-snp" && selector.confidentialCompute === "required" && allocation.execution !== "native")
6998
+ fail(`deployment.spec.targets.${targetName}.allocation.execution`, "cannot satisfy required confidential compute.");
6999
+ if (target.provisioning !== undefined) {
7000
+ const provisioning = object(target.provisioning, `deployment.spec.targets.${targetName}.provisioning`);
7001
+ exact3(provisioning, ["planKey", "regionKey", "imageKey", "environmentKey", "ownership", "metalHostname", "maxMonthlySpendMinor"], `deployment.spec.targets.${targetName}.provisioning`);
7002
+ for (const key of ["planKey", "regionKey", "imageKey", "environmentKey"])
7003
+ named(provisioning[key], `deployment.spec.targets.${targetName}.provisioning.${key}`);
7004
+ if (!["platform", "tenant-metal"].includes(String(provisioning.ownership)) || provisioning.ownership === "tenant-metal" !== Boolean(provisioning.metalHostname))
7005
+ fail(`deployment.spec.targets.${targetName}.provisioning.ownership`, "must bind platform capacity or one tenant metal hostname.");
7006
+ if (provisioning.metalHostname !== undefined && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(String(provisioning.metalHostname)))
7007
+ fail(`deployment.spec.targets.${targetName}.provisioning.metalHostname`, "is invalid.");
7008
+ if (typeof provisioning.maxMonthlySpendMinor !== "string" || !/^\d{1,18}$/.test(provisioning.maxMonthlySpendMinor))
7009
+ fail(`deployment.spec.targets.${targetName}.provisioning.maxMonthlySpendMinor`, "must be a bounded decimal minor-unit amount.");
7010
+ }
7011
+ if (allocation.existing === "prefer" && target.provisioning === undefined)
7012
+ fail(`deployment.spec.targets.${targetName}.provisioning`, "is required when placement may provision after reuse.");
7013
+ if (allocation.existing === "require" && target.provisioning !== undefined)
7014
+ fail(`deployment.spec.targets.${targetName}.provisioning`, "must be omitted for reuse-only placement.");
7015
+ if (target.connectivity !== undefined) {
7016
+ const connectivity = object(target.connectivity, `deployment.spec.targets.${targetName}.connectivity`);
7017
+ exact3(connectivity, ["private", "public"], `deployment.spec.targets.${targetName}.connectivity`);
7018
+ const declaredPrivate = connectivity.private === undefined ? undefined : object(connectivity.private, `deployment.spec.targets.${targetName}.connectivity.private`);
7019
+ if (declaredPrivate?.mode === "cloudflare-warp" && allocation.sharing !== "exclusive")
7020
+ fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "must be exclusive for a Cloudflare WARP connector.");
7021
+ if (connectivity.private !== undefined) {
7022
+ const privateNetwork = declaredPrivate;
7023
+ if (privateNetwork.mode === "cloudflare-warp") {
7024
+ exact3(privateNetwork, ["mode", "credential", "network"], `deployment.spec.targets.${targetName}.connectivity.private`);
7025
+ if (!credentials[named(privateNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.private.credential`)])
7026
+ fail(`deployment.spec.targets.${targetName}.connectivity.private.credential`, "must name a declared credential.");
7027
+ if (typeof privateNetwork.network === "string")
7028
+ named(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
7029
+ else
7030
+ reference(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
7031
+ } else {
7032
+ exact3(privateNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.private`);
7033
+ if (!["disabled", "private-lan"].includes(String(privateNetwork.mode)))
7034
+ fail(`deployment.spec.targets.${targetName}.connectivity.private.mode`, "is unsupported.");
7035
+ }
7036
+ }
7037
+ if (connectivity.public !== undefined) {
7038
+ const publicNetwork = object(connectivity.public, `deployment.spec.targets.${targetName}.connectivity.public`);
7039
+ if (publicNetwork.mode === "cloudflare-tunnel") {
7040
+ exact3(publicNetwork, ["mode", "credential", "zone", "hostname"], `deployment.spec.targets.${targetName}.connectivity.public`);
7041
+ if (!credentials[named(publicNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.public.credential`)])
7042
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.credential`, "must name a declared credential.");
7043
+ if (typeof publicNetwork.zone === "string") {
7044
+ if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(publicNetwork.zone))
7045
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.zone`, "is invalid.");
7046
+ } else
7047
+ reference(publicNetwork.zone, `deployment.spec.targets.${targetName}.connectivity.public.zone`);
7048
+ const hostname = object(publicNetwork.hostname, `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
7049
+ const value2 = (coordinate, where, pattern) => {
7050
+ if (typeof coordinate === "string") {
7051
+ if (!pattern.test(coordinate))
7052
+ fail(where, "is invalid.");
7053
+ } else
7054
+ reference(coordinate, where);
7055
+ };
7056
+ if (hostname.mode === "static") {
7057
+ exact3(hostname, ["mode", "label"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
7058
+ value2(hostname.label, `deployment.spec.targets.${targetName}.connectivity.public.hostname.label`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
7059
+ } else if (hostname.mode === "indexed") {
7060
+ exact3(hostname, ["mode", "prefix", "startAt"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
7061
+ value2(hostname.prefix, `deployment.spec.targets.${targetName}.connectivity.public.hostname.prefix`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
7062
+ if (hostname.startAt !== undefined)
7063
+ integer(hostname.startAt, `deployment.spec.targets.${targetName}.connectivity.public.hostname.startAt`, 0, 1e6);
7064
+ } else
7065
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.hostname.mode`, "is unsupported.");
7066
+ } else {
7067
+ exact3(publicNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.public`);
7068
+ if (publicNetwork.mode !== "disabled")
7069
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.mode`, "is unsupported.");
7070
+ }
7071
+ }
7072
+ }
6725
7073
  if (target.placement !== undefined)
6726
7074
  jsonValue(target.placement, `deployment.spec.targets.${targetName}.placement`);
7075
+ targetDefinitions.set(targetName, target);
6727
7076
  }
6728
7077
  const requirements = object(spec.requirements ?? {}, "deployment.spec.requirements");
6729
7078
  if (Object.keys(requirements).length > 64)
@@ -6737,13 +7086,34 @@ function compileDeployment(sourceValue) {
6737
7086
  const key = `${requirementValue.provider}@${requirementValue.contract}:${requirementValue.capability}`;
6738
7087
  requiredProviders.set(key, { provider: requirementValue.provider, contract: requirementValue.contract, capability: requirementValue.capability });
6739
7088
  }
7089
+ for (const [targetName, target] of targetDefinitions) {
7090
+ if (target.connectivity?.public?.mode === "cloudflare-tunnel" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.public-tunnel")) {
7091
+ fail(`deployment.spec.targets.${targetName}.connectivity.public`, "requires one network.public-tunnel provider requirement.");
7092
+ }
7093
+ if (target.connectivity?.private?.mode === "cloudflare-warp" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
7094
+ fail(`deployment.spec.targets.${targetName}.connectivity.private`, "requires one network.private provider requirement.");
7095
+ }
7096
+ }
6740
7097
  const components = object(spec.components, "deployment.spec.components");
6741
7098
  const componentEntries = Object.entries(components);
6742
7099
  if (componentEntries.length < 1 || componentEntries.length > 128)
6743
7100
  fail("deployment.spec.components", "must contain 1 to 128 components.");
6744
7101
  for (const [componentName, value] of componentEntries) {
6745
7102
  named(componentName, "deployment.spec.components key");
6746
- validateComponent(value, `deployment.spec.components.${componentName}`, targetNames, requirementDefinitions);
7103
+ validateComponent(value, `deployment.spec.components.${componentName}`, targetDefinitions, requirementDefinitions);
7104
+ }
7105
+ for (const [targetName, target] of targetDefinitions) {
7106
+ if (target.connectivity?.public?.mode !== "cloudflare-tunnel")
7107
+ continue;
7108
+ const publicApplications = componentEntries.filter(([, component2]) => {
7109
+ const candidate = component2;
7110
+ return candidate.kind === "application" && candidate.target === targetName && candidate.network?.public?.mode === "cloudflare-tunnel";
7111
+ });
7112
+ if (publicApplications.length !== 1)
7113
+ fail(`deployment.spec.targets.${targetName}.connectivity.public`, "requires exactly one application on the target with network.public cloudflare-tunnel.");
7114
+ const application = publicApplications[0][1];
7115
+ if (application.kind !== "application" || !application.network?.ingress?.stablePort)
7116
+ fail(`deployment.spec.components.${publicApplications[0][0]}.network.ingress`, "requires one stablePort for Tunnel ingress.");
6747
7117
  }
6748
7118
  const actionContext = {
6749
7119
  components,
@@ -6785,6 +7155,14 @@ function compileDeployment(sourceValue) {
6785
7155
  });
6786
7156
  plannedWorkflows.push({ id: workflowName, ...workflowValue.concurrency ? { concurrency: workflowValue.concurrency } : {}, stages: plannedStages });
6787
7157
  }
7158
+ const deployWorkflow = plannedWorkflows.find(({ id: id2 }) => id2 === "deploy");
7159
+ for (const [targetName, target] of targetDefinitions) {
7160
+ if (!target.connectivity || target.connectivity.public?.mode !== "cloudflare-tunnel" && target.connectivity.private?.mode !== "cloudflare-warp")
7161
+ continue;
7162
+ const ensures = deployWorkflow?.stages.flatMap(({ steps }) => steps).filter((step2) => step2.uses === "forgezero.network/ensure@1" && step2.with.target === targetName && (step2.scope.kind === "each-target" || step2.scope.kind === "target-batches") && step2.scope.target === targetName) ?? [];
7163
+ if (ensures.length !== 1)
7164
+ fail(`deployment.spec.workflows.deploy`, `must contain exactly one network.ensure step scoped to connected target ${targetName}.`);
7165
+ }
6788
7166
  const typedSource = source;
6789
7167
  return {
6790
7168
  format: DEPLOY_PLAN_FORMAT,
@@ -6871,6 +7249,143 @@ function parseDeploymentPlan(value) {
6871
7249
  return { ...compiled, sourceDigest: row2.sourceDigest };
6872
7250
  }
6873
7251
 
7252
+ // src/deployment-targets.ts
7253
+ class DeploymentInputError extends Error {
7254
+ constructor(message) {
7255
+ super(message);
7256
+ this.name = "DeploymentInputError";
7257
+ }
7258
+ }
7259
+ var HOSTNAME = /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/;
7260
+ function inputValue(name, definition, supplied) {
7261
+ let value = supplied ?? definition.default;
7262
+ if (value === undefined) {
7263
+ if (definition.required)
7264
+ throw new DeploymentInputError(`deployment input ${name} is required`);
7265
+ return;
7266
+ }
7267
+ if (typeof value === "string") {
7268
+ const text2 = value;
7269
+ if (definition.type === "boolean" && (text2 === "true" || text2 === "false"))
7270
+ value = text2 === "true";
7271
+ if ((definition.type === "integer" || definition.type === "number") && text2.trim() !== "")
7272
+ value = Number(text2);
7273
+ }
7274
+ if (definition.type === "boolean" && typeof value !== "boolean")
7275
+ throw new DeploymentInputError(`deployment input ${name} must be boolean`);
7276
+ if (definition.type === "integer" && (!Number.isSafeInteger(value) || Number(value) < (definition.minimum ?? Number.MIN_SAFE_INTEGER) || Number(value) > (definition.maximum ?? Number.MAX_SAFE_INTEGER)))
7277
+ throw new DeploymentInputError(`deployment input ${name} must be a bounded integer`);
7278
+ if (definition.type === "number" && (typeof value !== "number" || !Number.isFinite(value) || value < (definition.minimum ?? -Number.MAX_VALUE) || value > (definition.maximum ?? Number.MAX_VALUE)))
7279
+ throw new DeploymentInputError(`deployment input ${name} must be a bounded number`);
7280
+ if (definition.type === "enum" && (typeof value !== "string" || !definition.values.includes(value)))
7281
+ throw new DeploymentInputError(`deployment input ${name} must be one of its declared values`);
7282
+ if (definition.type === "hostname" && (typeof value !== "string" || !HOSTNAME.test(value)))
7283
+ throw new DeploymentInputError(`deployment input ${name} must be a hostname`);
7284
+ if (definition.type === "string") {
7285
+ if (typeof value !== "string" || value.length < (definition.minimumLength ?? 0) || value.length > (definition.maximumLength ?? 4096))
7286
+ throw new DeploymentInputError(`deployment input ${name} must be a bounded string`);
7287
+ if (definition.pattern !== undefined && !new RegExp(definition.pattern).test(value))
7288
+ throw new DeploymentInputError(`deployment input ${name} does not match its pattern`);
7289
+ }
7290
+ return value;
7291
+ }
7292
+ function resolveDeploymentTargets(plan, supplied = {}) {
7293
+ const inputs = resolveDeploymentInputs(plan, supplied);
7294
+ const stringCoordinate = (value, label) => {
7295
+ const resolved = typeof value === "string" ? value : value.$ref.startsWith("inputs.") ? inputs[value.$ref.slice("inputs.".length)] : undefined;
7296
+ if (typeof resolved !== "string" || resolved.length < 1 || resolved.length > 253) {
7297
+ throw new DeploymentInputError(`${label} did not resolve to a bounded string`);
7298
+ }
7299
+ return resolved.toLowerCase();
7300
+ };
7301
+ const integerCoordinate = (value, label, minimum, maximum) => {
7302
+ const resolved = typeof value === "number" ? value : value.$ref.startsWith("inputs.") ? inputs[value.$ref.slice("inputs.".length)] : undefined;
7303
+ if (!Number.isSafeInteger(resolved) || Number(resolved) < minimum || Number(resolved) > maximum) {
7304
+ throw new DeploymentInputError(`${label} did not resolve to a bounded integer`);
7305
+ }
7306
+ return Number(resolved);
7307
+ };
7308
+ const credentialSource = (alias, label) => {
7309
+ const source = plan.spec.credentials?.[alias]?.source.name;
7310
+ if (!source)
7311
+ throw new DeploymentInputError(`${label} did not resolve to a declared Vault credential`);
7312
+ return source;
7313
+ };
7314
+ return Object.entries(plan.spec.targets).map(([name, target]) => {
7315
+ const desired = typeof target.cardinality.desired === "number" ? target.cardinality.desired : target.cardinality.desired.$ref.startsWith("inputs.") ? inputs[target.cardinality.desired.$ref.slice("inputs.".length)] : undefined;
7316
+ if (!Number.isSafeInteger(desired) || Number(desired) < target.cardinality.minimum || Number(desired) > target.cardinality.maximum) {
7317
+ throw new DeploymentInputError(`target ${name} desired cardinality did not resolve to a bounded integer`);
7318
+ }
7319
+ let connectivity;
7320
+ if (target.connectivity) {
7321
+ const privateNetwork = target.connectivity.private;
7322
+ const publicNetwork = target.connectivity.public;
7323
+ connectivity = {
7324
+ ...privateNetwork ? { private: privateNetwork.mode === "cloudflare-warp" ? {
7325
+ mode: "cloudflare-warp",
7326
+ credential: credentialSource(privateNetwork.credential, `target ${name} WARP credential`),
7327
+ network: stringCoordinate(privateNetwork.network, `target ${name} WARP network`)
7328
+ } : { mode: privateNetwork.mode } } : {},
7329
+ ...publicNetwork ? { public: publicNetwork.mode === "cloudflare-tunnel" ? {
7330
+ mode: "cloudflare-tunnel",
7331
+ credential: credentialSource(publicNetwork.credential, `target ${name} Tunnel credential`),
7332
+ zone: stringCoordinate(publicNetwork.zone, `target ${name} Tunnel zone`),
7333
+ hostname: publicNetwork.hostname.mode === "static" ? {
7334
+ mode: "static",
7335
+ label: stringCoordinate(publicNetwork.hostname.label, `target ${name} Tunnel label`)
7336
+ } : {
7337
+ mode: "indexed",
7338
+ prefix: stringCoordinate(publicNetwork.hostname.prefix, `target ${name} Tunnel prefix`),
7339
+ startAt: publicNetwork.hostname.startAt ?? 1
7340
+ },
7341
+ service: (() => {
7342
+ const applications = Object.values(plan.spec.components).filter((component2) => component2.target === name && component2.kind === "application" && component2.network?.public?.mode === "cloudflare-tunnel");
7343
+ if (applications.length !== 1)
7344
+ throw new DeploymentInputError(`target ${name} Tunnel requires exactly one public application`);
7345
+ const port = applications[0].network.ingress?.stablePort;
7346
+ if (!Number.isInteger(port))
7347
+ throw new DeploymentInputError(`target ${name} Tunnel application requires a stable ingress port`);
7348
+ return { protocol: "http", port: Number(port) };
7349
+ })()
7350
+ } : { mode: "disabled" } } : {}
7351
+ };
7352
+ }
7353
+ return {
7354
+ name,
7355
+ profiles: [...target.selector.profiles],
7356
+ operatingSystem: { ...target.selector.operatingSystem },
7357
+ confidentialCompute: target.selector.confidentialCompute ?? "disabled",
7358
+ labels: { ...target.selector.labels ?? {} },
7359
+ minimum: target.cardinality.minimum,
7360
+ desired: Number(desired),
7361
+ maximum: target.cardinality.maximum,
7362
+ allocation: {
7363
+ ...target.allocation,
7364
+ resources: {
7365
+ cpuCores: integerCoordinate(target.allocation.resources.cpuCores, `target ${name} cpuCores`, 1, 1024),
7366
+ memoryMiB: integerCoordinate(target.allocation.resources.memoryMiB, `target ${name} memoryMiB`, 128, 4194304),
7367
+ storageGiB: integerCoordinate(target.allocation.resources.storageGiB, `target ${name} storageGiB`, 1, 1048576)
7368
+ }
7369
+ },
7370
+ ...target.provisioning ? { provisioning: { ...target.provisioning } } : {},
7371
+ ...connectivity ? { connectivity } : {}
7372
+ };
7373
+ });
7374
+ }
7375
+ function resolveDeploymentInputs(plan, supplied = {}) {
7376
+ for (const name of Object.keys(supplied))
7377
+ if (!(name in (plan.spec.inputs ?? {}))) {
7378
+ throw new DeploymentInputError(`deployment input ${name} is not declared`);
7379
+ }
7380
+ const inputs = {};
7381
+ for (const [name, definition] of Object.entries(plan.spec.inputs ?? {})) {
7382
+ const value = inputValue(name, definition, supplied[name]);
7383
+ if (value !== undefined)
7384
+ inputs[name] = value;
7385
+ }
7386
+ return inputs;
7387
+ }
7388
+
6874
7389
  // src/deploy-plan-runner.ts
6875
7390
  class DeploymentActionError extends Error {
6876
7391
  kind;
@@ -6906,6 +7421,8 @@ function resolveReference(reference2, state) {
6906
7421
  return nested(state.plan.spec.targets[parts[1]], parts.slice(2));
6907
7422
  if (parts[0] === "deployment")
6908
7423
  return nested(state.plan, parts.slice(1));
7424
+ if (parts[0] === "execution")
7425
+ return nested(state.execution, parts.slice(1));
6909
7426
  return;
6910
7427
  }
6911
7428
  function resolveOperand(value, state) {
@@ -7108,7 +7625,7 @@ async function runDeploymentPlan(options) {
7108
7625
  for (const [name, definition] of Object.entries(inputDefinitions))
7109
7626
  inputValues[name] = validateInputValue(definition, supplied[name], name);
7110
7627
  const execute = async () => {
7111
- const state = { inputs: inputValues, steps: new Map, plan };
7628
+ const state = { inputs: inputValues, steps: new Map, plan, execution: options.execution };
7112
7629
  const records = [];
7113
7630
  const successful2 = [];
7114
7631
  for (const stage2 of workflow.stages) {
@@ -7163,7 +7680,7 @@ var DEPLOY_PLAN_FILE = ".fz/deploy.plan.json";
7163
7680
  import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
7164
7681
  import {
7165
7682
  chmodSync as chmodSync3,
7166
- createReadStream,
7683
+ createReadStream as createReadStream2,
7167
7684
  existsSync as existsSync3,
7168
7685
  lstatSync,
7169
7686
  mkdirSync as mkdirSync2,
@@ -7178,7 +7695,7 @@ var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
7178
7695
  async function sha256File(path2) {
7179
7696
  const hash = createHash4("sha256");
7180
7697
  await new Promise((resolveDone, reject) => {
7181
- const stream = createReadStream(path2);
7698
+ const stream = createReadStream2(path2);
7182
7699
  stream.on("data", (chunk) => hash.update(chunk));
7183
7700
  stream.on("error", reject);
7184
7701
  stream.on("end", resolveDone);
@@ -7264,7 +7781,7 @@ function persistCapacityCalibration(args) {
7264
7781
  throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be an absolute Agent-owned path");
7265
7782
  }
7266
7783
  if (!existsSync4(args.directory)) {
7267
- const parent = lstatSync2(dirname(args.directory));
7784
+ const parent = lstatSync2(dirname2(args.directory));
7268
7785
  if (!parent.isDirectory() || parent.isSymbolicLink()) {
7269
7786
  throw new DeploymentError("PIPELINE_FAILED", "capacity evidence parent must be a real Agent-owned directory");
7270
7787
  }
@@ -7328,7 +7845,7 @@ function createDeploymentManager(options) {
7328
7845
  }
7329
7846
  if (existsSync4(knownHostsPath) && readFileSync3(knownHostsPath, "utf8") === content)
7330
7847
  return;
7331
- mkdirSync3(dirname(knownHostsPath), { recursive: true, mode: 448 });
7848
+ mkdirSync3(dirname2(knownHostsPath), { recursive: true, mode: 448 });
7332
7849
  const next = `${knownHostsPath}.${process.pid}.${randomUUID()}.next`;
7333
7850
  writeFileSync3(next, content, { mode: 384, flag: "wx" });
7334
7851
  renameSync3(next, knownHostsPath);
@@ -7515,8 +8032,8 @@ function createDeploymentManager(options) {
7515
8032
  throw new DeploymentError("PIPELINE_FAILED", "rolling/canary plans require a fenced multi-target rollout lease that this control protocol does not yet supply");
7516
8033
  }
7517
8034
  }
7518
- const targetNames = new Set(Object.entries(plan.spec.targets).filter(([, target]) => target.selector.profiles.includes(options.profile)).map(([name]) => name));
7519
- if (targetNames.size === 0)
8035
+ const matchingTargetNames = Object.entries(plan.spec.targets).filter(([, target]) => target.selector.profiles.includes(options.profile)).map(([name]) => name);
8036
+ if (matchingTargetNames.length === 0)
7520
8037
  throw new DeploymentError("PIPELINE_FAILED", `deployment plan has no target for profile ${options.profile}`);
7521
8038
  const assurance = plan.spec.security?.attestation ?? "disabled";
7522
8039
  if (assurance !== "disabled") {
@@ -7539,35 +8056,66 @@ function createDeploymentManager(options) {
7539
8056
  FZ_DEPLOY_PROFILE: options.profile,
7540
8057
  ...options.publicApiUrl ? { PUBLIC_API_URL: options.publicApiUrl } : {}
7541
8058
  };
7542
- const inputValues = {};
7543
- for (const [name, definition2] of Object.entries(plan.spec.inputs ?? {})) {
7544
- const raw = options.environment?.[name];
7545
- if (raw === undefined) {
7546
- if (definition2.default !== undefined)
7547
- inputValues[name] = definition2.default;
7548
- continue;
8059
+ const resolvedTargets = resolveDeploymentTargets(plan, options.environment ?? {}).filter((target) => target.profiles.includes(options.profile));
8060
+ const assigned = request.assignment;
8061
+ let targetNames;
8062
+ if (assigned) {
8063
+ const selected = resolvedTargets.find(({ name }) => name === assigned.targetName);
8064
+ const mismatches = [
8065
+ !selected ? "target" : null,
8066
+ assigned.definitionDigest !== plan.sourceDigest ? "digest" : null,
8067
+ !Number.isSafeInteger(assigned.slot) || assigned.slot < 0 || selected && assigned.slot >= selected.maximum ? "slot" : null,
8068
+ selected && (assigned.operatingSystem.id !== selected.operatingSystem.id || assigned.operatingSystem.version !== selected.operatingSystem.version || assigned.operatingSystem.architecture !== selected.operatingSystem.architecture) ? "operating-system" : null,
8069
+ selected && assigned.confidentialCompute !== selected.confidentialCompute ? "confidential-compute" : null,
8070
+ selected && assigned.execution !== selected.allocation.execution ? "execution" : null,
8071
+ selected && assigned.sharing !== selected.allocation.sharing ? "sharing" : null,
8072
+ selected && (assigned.resources.cpuCores !== selected.allocation.resources.cpuCores || assigned.resources.memoryMiB !== selected.allocation.resources.memoryMiB || assigned.resources.storageGiB !== selected.allocation.resources.storageGiB) ? "resources" : null
8073
+ ].filter(Boolean);
8074
+ if (!selected || mismatches.length > 0) {
8075
+ throw new DeploymentError("PIPELINE_FAILED", `the control-plane target assignment does not match this exact deployment plan (${mismatches.join(", ")})`);
7549
8076
  }
7550
- inputValues[name] = definition2.type === "boolean" ? raw === "true" : definition2.type === "integer" || definition2.type === "number" ? Number(raw) : raw;
8077
+ const expectedPrivate = selected.connectivity?.private;
8078
+ const expectedPublic = selected.connectivity?.public;
8079
+ const assignedPrivate = assigned.connectivity?.private;
8080
+ const assignedPublic = assigned.connectivity?.public;
8081
+ const publicMatches = expectedPublic?.mode === "cloudflare-tunnel" ? assignedPublic?.mode === "cloudflare-tunnel" && assignedPublic.credential === expectedPublic.credential && assignedPublic.service.protocol === expectedPublic.service.protocol && assignedPublic.service.port === expectedPublic.service.port && assignedPublic.hostname === (expectedPublic.hostname.mode === "static" ? `${expectedPublic.hostname.label}.${expectedPublic.zone}` : `${expectedPublic.hostname.prefix}${expectedPublic.hostname.startAt + assigned.slot}.${expectedPublic.zone}`) : expectedPublic?.mode === "disabled" ? assignedPublic?.mode === "disabled" : assignedPublic === undefined;
8082
+ if (JSON.stringify(assignedPrivate) !== JSON.stringify(expectedPrivate) || !publicMatches) {
8083
+ throw new DeploymentError("PIPELINE_FAILED", "the control-plane connectivity assignment does not match this target slot");
8084
+ }
8085
+ targetNames = new Set([assigned.targetName]);
8086
+ } else {
8087
+ if (matchingTargetNames.length !== 1) {
8088
+ throw new DeploymentError("PIPELINE_FAILED", "an unassigned bootstrap claim must resolve to exactly one target for its profile");
8089
+ }
8090
+ targetNames = new Set(matchingTargetNames);
7551
8091
  }
7552
- const resolvedTargets = Object.entries(plan.spec.targets).filter(([, target]) => target.selector.profiles.includes(options.profile)).map(([name, target]) => {
7553
- const desired = typeof target.cardinality.desired === "number" ? target.cardinality.desired : target.cardinality.desired.$ref.startsWith("inputs.") ? inputValues[target.cardinality.desired.$ref.slice("inputs.".length)] : undefined;
7554
- if (!Number.isSafeInteger(desired) || Number(desired) < target.cardinality.minimum || Number(desired) > target.cardinality.maximum) {
7555
- throw new DeploymentError("PIPELINE_FAILED", `target ${name} desired cardinality did not resolve to a bounded integer`);
8092
+ if (assigned)
8093
+ Object.assign(phaseEnvironment2, {
8094
+ FZ_TARGET_NAME: assigned.targetName,
8095
+ FZ_TARGET_SLOT: String(assigned.slot),
8096
+ FZ_TARGET_CPU_CORES: String(assigned.resources.cpuCores),
8097
+ FZ_TARGET_MEMORY_MIB: String(assigned.resources.memoryMiB),
8098
+ FZ_TARGET_STORAGE_GIB: String(assigned.resources.storageGiB),
8099
+ FZ_TARGET_EXECUTION: assigned.execution
8100
+ });
8101
+ for (const targetName of targetNames) {
8102
+ const capacity2 = resolvedTargets.find(({ name }) => name === targetName)?.allocation.resources;
8103
+ if (!capacity2)
8104
+ throw new DeploymentError("PIPELINE_FAILED", `target ${targetName} has no resolved capacity`);
8105
+ const components = Object.values(plan.spec.components).filter(({ target }) => target === targetName);
8106
+ const cpu = components.reduce((sum, component2) => sum + (component2.resources?.cpu?.limit ?? 0), 0);
8107
+ const memoryMiB = components.reduce((sum, component2) => sum + (component2.resources?.memory?.limitMiB ?? 0), 0);
8108
+ const ephemeralMiB = components.reduce((sum, component2) => sum + (component2.kind === "application" ? (component2.storage ?? []).reduce((storage, mount) => storage + (mount.class === "ephemeral" ? mount.sizeMiB : 0), 0) : 0), 0);
8109
+ if (cpu > capacity2.cpuCores || memoryMiB > capacity2.memoryMiB || ephemeralMiB > capacity2.storageGiB * 1024) {
8110
+ throw new DeploymentError("PIPELINE_FAILED", `component limits exceed the reserved capacity of target ${targetName}`);
7556
8111
  }
7557
- return {
7558
- name,
7559
- profiles: [...target.selector.profiles],
7560
- confidentialCompute: target.selector.confidentialCompute ?? "disabled",
7561
- labels: { ...target.selector.labels ?? {} },
7562
- minimum: target.cardinality.minimum,
7563
- desired: Number(desired),
7564
- maximum: target.cardinality.maximum
7565
- };
7566
- });
8112
+ }
8113
+ const inputValues = resolveDeploymentInputs(plan, options.environment ?? {});
7567
8114
  const projectExec2 = options.projectExec ?? spawnExact;
7568
8115
  const materializePlanArgv = (argv2) => argv2.map((argument) => {
7569
- if (argument === "${FZ_RELEASE}")
7570
- return release;
8116
+ const coordinate = argument.match(/^\$\{(FZ_[A-Z0-9_]+)\}$/)?.[1];
8117
+ if (coordinate && phaseEnvironment2[coordinate] !== undefined)
8118
+ return phaseEnvironment2[coordinate];
7571
8119
  if (argument.includes("${")) {
7572
8120
  throw new DeploymentActionError("refused", `unsupported deployment argv coordinate ${argument}`);
7573
8121
  }
@@ -7576,6 +8124,8 @@ function createDeploymentManager(options) {
7576
8124
  const providerSoftware = {
7577
8125
  "forgezero.bun": "bun",
7578
8126
  "forgezero.docker": "docker",
8127
+ "forgezero.containerd": "containerd",
8128
+ "forgezero.kata": "kata-containers",
7579
8129
  "forgezero.nginx": "nginx",
7580
8130
  "forgezero.arangodb": "arangodb",
7581
8131
  "forgezero.cloudflared": "cloudflared",
@@ -7601,6 +8151,29 @@ function createDeploymentManager(options) {
7601
8151
  await options.ensureSoftware(requirements);
7602
8152
  return { changed: true };
7603
8153
  },
8154
+ "forgezero.network/ensure@1": async ({ resolved }) => {
8155
+ if (!assigned || typeof resolved.target !== "string" || resolved.target !== assigned.targetName) {
8156
+ throw new DeploymentActionError("refused", "network.ensure must target this exact control-plane assignment");
8157
+ }
8158
+ if (!assigned.connectivity)
8159
+ return { changed: false, evidence: { target: assigned.targetName } };
8160
+ if (!options.ensureSoftware || !options.applyConnectivity) {
8161
+ throw new DeploymentActionError("provider-unavailable", "the supervised connectivity helper is unavailable");
8162
+ }
8163
+ const requirements = [];
8164
+ if (assigned.connectivity.public?.mode === "cloudflare-tunnel")
8165
+ requirements.push({ id: "cloudflared", version: "latest" });
8166
+ if (assigned.connectivity.private?.mode === "cloudflare-warp")
8167
+ requirements.push({ id: "cloudflare-warp", version: "latest" });
8168
+ if (requirements.length > 0)
8169
+ await options.ensureSoftware(requirements);
8170
+ const evidence = await options.applyConnectivity({
8171
+ key: `${options.key}:${assigned.targetName}:${assigned.slot}`,
8172
+ intent: assigned.connectivity,
8173
+ capabilities: request.connectivityCapabilities ?? {}
8174
+ });
8175
+ return { changed: true, evidence: structuredClone(evidence) };
8176
+ },
7604
8177
  "forgezero.exec/argv@1": async ({ resolved, step: step2 }) => {
7605
8178
  if (!Array.isArray(resolved.argv) || resolved.argv.some((value) => typeof value !== "string"))
7606
8179
  throw new DeploymentActionError("refused", "exec.argv requires an exact argv array");
@@ -7642,7 +8215,7 @@ function createDeploymentManager(options) {
7642
8215
  const component2 = plan.spec.components[String(resolved.component ?? "")];
7643
8216
  const mounts = component2?.kind === "database" ? [component2.storage] : component2?.storage ?? [];
7644
8217
  for (const mount of mounts) {
7645
- const candidate = existsSync4(mount.path) ? mount.path : dirname(mount.path);
8218
+ const candidate = existsSync4(mount.path) ? mount.path : dirname2(mount.path);
7646
8219
  const stats = statfsSync(candidate);
7647
8220
  const freePercent = Number(stats.bavail) / Number(stats.blocks) * 100;
7648
8221
  const minimum = mount.class === "ephemeral" ? 0 : mount.minimumFreePercent ?? 0;
@@ -7699,6 +8272,23 @@ function createDeploymentManager(options) {
7699
8272
  workflow: "deploy",
7700
8273
  inputs: inputValues,
7701
8274
  actions: actions2,
8275
+ execution: assigned ? {
8276
+ targetName: assigned.targetName,
8277
+ slot: assigned.slot,
8278
+ releaseExecutor: request.releaseExecutor === true,
8279
+ runtime: assigned.execution,
8280
+ cpuCores: assigned.resources.cpuCores,
8281
+ memoryMiB: assigned.resources.memoryMiB,
8282
+ storageGiB: assigned.resources.storageGiB
8283
+ } : {
8284
+ targetName: matchingTargetNames[0],
8285
+ slot: 0,
8286
+ releaseExecutor: request.releaseExecutor === true,
8287
+ runtime: resolvedTargets[0].allocation.execution,
8288
+ cpuCores: resolvedTargets[0].allocation.resources.cpuCores,
8289
+ memoryMiB: resolvedTargets[0].allocation.resources.memoryMiB,
8290
+ storageGiB: resolvedTargets[0].allocation.resources.storageGiB
8291
+ },
7702
8292
  shouldRun: (step2) => step2.scope.kind === "release-executor" || step2.scope.kind === "elected-one" ? request.releaseExecutor === true : targetNames.has(step2.scope.target)
7703
8293
  });
7704
8294
  const phase = {
@@ -8060,7 +8650,12 @@ async function deployClaim(options, claim) {
8060
8650
  schedule();
8061
8651
  let result;
8062
8652
  try {
8063
- const run2 = () => manager.deploy({ revision: claim.revision, releaseExecutor: claim.releaseExecutor }).result;
8653
+ const run2 = () => manager.deploy({
8654
+ revision: claim.revision,
8655
+ releaseExecutor: claim.releaseExecutor,
8656
+ ...claim.assignment ? { assignment: claim.assignment } : {},
8657
+ ...claim.connectivityCapabilities ? { connectivityCapabilities: claim.connectivityCapabilities } : {}
8658
+ }).result;
8064
8659
  result = options.telemetry ? await options.telemetry.observe("deployment.run", run2) : await run2();
8065
8660
  } finally {
8066
8661
  stopped = true;
@@ -8130,7 +8725,11 @@ async function pullDeploymentOnce(options) {
8130
8725
  ...result.resolvedTargets ? { resolvedTargets: result.resolvedTargets.map((target) => ({
8131
8726
  ...target,
8132
8727
  profiles: [...target.profiles],
8133
- labels: { ...target.labels }
8728
+ operatingSystem: { ...target.operatingSystem },
8729
+ labels: { ...target.labels },
8730
+ allocation: { ...target.allocation, resources: { ...target.allocation.resources } },
8731
+ ...target.provisioning ? { provisioning: { ...target.provisioning } } : {},
8732
+ ...target.connectivity ? { connectivity: structuredClone(target.connectivity) } : {}
8134
8733
  })) } : {},
8135
8734
  ...result.capacity ? { capacity: {
8136
8735
  recommendedConcurrency: result.capacity.recommendedConcurrency,
@@ -8186,11 +8785,11 @@ import {
8186
8785
  mkdirSync as mkdirSync4,
8187
8786
  readFileSync as readFileSync4,
8188
8787
  renameSync as renameSync4,
8189
- statSync,
8788
+ statSync as statSync2,
8190
8789
  unlinkSync as unlinkSync4,
8191
8790
  writeFileSync as writeFileSync4
8192
8791
  } from "fs";
8193
- import { dirname as dirname2 } from "path";
8792
+ import { dirname as dirname3 } from "path";
8194
8793
  function privateNetworkAttachmentFromEnvironment(env = process.env) {
8195
8794
  const values = {
8196
8795
  accountId: env.FZ_CF_ACCOUNT_ID?.trim() ?? "",
@@ -8220,7 +8819,7 @@ var validBinding = (value, expectedNodeKey) => {
8220
8819
  function loadGuestBinding(path2, expectedNodeKey) {
8221
8820
  if (!existsSync6(path2))
8222
8821
  return null;
8223
- const mode = statSync(path2).mode & 511;
8822
+ const mode = statSync2(path2).mode & 511;
8224
8823
  if ((mode & 63) !== 0) {
8225
8824
  throw new Error(`guest enrolment state at ${path2} is not private`);
8226
8825
  }
@@ -8236,7 +8835,7 @@ function loadGuestBinding(path2, expectedNodeKey) {
8236
8835
  return parsed;
8237
8836
  }
8238
8837
  function persistGuestBinding(path2, binding) {
8239
- mkdirSync4(dirname2(path2), { recursive: true, mode: 448 });
8838
+ mkdirSync4(dirname3(path2), { recursive: true, mode: 448 });
8240
8839
  const temporary = `${path2}.next`;
8241
8840
  writeFileSync4(temporary, `${JSON.stringify(binding)}
8242
8841
  `, { mode: 384 });
@@ -8587,8 +9186,14 @@ import { tmpdir as tmpdir2 } from "os";
8587
9186
  import { join as join3 } from "path";
8588
9187
  import { fileURLToPath } from "url";
8589
9188
 
9189
+ // src/process-input.ts
9190
+ async function writeAndCloseProcessInput(input, value) {
9191
+ input.write(value);
9192
+ await input.end();
9193
+ }
9194
+
8590
9195
  // src/version.ts
8591
- var VERSION2 = "0.1.74";
9196
+ var VERSION2 = "0.1.76";
8592
9197
 
8593
9198
  // src/ssh-bootstrap.ts
8594
9199
  class SshBootstrapError extends Error {
@@ -8606,8 +9211,7 @@ var defaultExec = async (argv2, options = {}) => {
8606
9211
  env: { PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin", LANG: "C", LC_ALL: "C" }
8607
9212
  });
8608
9213
  if (options.stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
8609
- child.stdin.write(options.stdin);
8610
- child.stdin.end();
9214
+ await writeAndCloseProcessInput(child.stdin, options.stdin);
8611
9215
  }
8612
9216
  const [stdout, stderr, exitCode] = await Promise.all([
8613
9217
  new Response(child.stdout).text(),
@@ -9304,11 +9908,11 @@ import {
9304
9908
  readFileSync as readFileSync6,
9305
9909
  readdirSync,
9306
9910
  rmSync as rmSync4,
9307
- statSync as statSync2,
9911
+ statSync as statSync3,
9308
9912
  unlinkSync as unlinkSync5,
9309
9913
  writeFileSync as writeFileSync6
9310
9914
  } from "fs";
9311
- import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4 } from "path";
9915
+ import { dirname as dirname4, isAbsolute as isAbsolute3, join as join4 } from "path";
9312
9916
  import { isIP as isIP2 } from "net";
9313
9917
 
9314
9918
  // src/compute.ts
@@ -9733,7 +10337,7 @@ async function provisionMetalGuest(profile, claim, exec) {
9733
10337
  if (!image || !isAbsolute3(image.path) || !SHA256.test(image.sha256)) {
9734
10338
  throw new MetalProvisionError(`image ${claim.spec.imageKey} is not configured locally`);
9735
10339
  }
9736
- if (!statSync2(image.path).isFile())
10340
+ if (!statSync3(image.path).isFile())
9737
10341
  throw new MetalProvisionError("configured image is not a file");
9738
10342
  const digest = (await checked2(exec, ["sha256sum", image.path])).stdout.trim().split(/\s+/)[0];
9739
10343
  if (digest !== image.sha256)
@@ -9818,7 +10422,7 @@ async function provisionMetalGuest(profile, claim, exec) {
9818
10422
  }
9819
10423
  const service = `forgezero-guest@${name}.service`;
9820
10424
  const unitPath = join4(profile.unitDir, service);
9821
- mkdirSync5(dirname3(unitPath), { recursive: true });
10425
+ mkdirSync5(dirname4(unitPath), { recursive: true });
9822
10426
  writeFileSync6(unitPath, guestUnit(spec), { mode: 420 });
9823
10427
  await checked2(exec, ["systemctl", "daemon-reload"]);
9824
10428
  if (prior?.phase === "running") {
@@ -10882,7 +11486,7 @@ import {
10882
11486
  symlinkSync as symlinkSync3,
10883
11487
  writeFileSync as writeFileSync9
10884
11488
  } from "fs";
10885
- import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
11489
+ import { dirname as dirname5, join as join7, resolve as resolve2 } from "path";
10886
11490
  var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
10887
11491
  var DEFAULT_AGENT_UPDATE_SOCKET = "/run/forgezero-update/helper.sock";
10888
11492
  var MAX_AGENT_TARBALL_BYTES = 32 * 1024 * 1024;
@@ -10904,7 +11508,7 @@ var syncReleaseDirectory = (directory) => {
10904
11508
  join7(directory, "dist", "fz-git-ssh.js"),
10905
11509
  join7(directory, "dist"),
10906
11510
  directory,
10907
- dirname4(directory)
11511
+ dirname5(directory)
10908
11512
  ])
10909
11513
  syncPath(path2);
10910
11514
  };
@@ -11026,7 +11630,7 @@ async function stageAgentRelease(releaseInput, options) {
11026
11630
  args: ["-xOzf", archive, member]
11027
11631
  }, `agent update extraction of ${member}`);
11028
11632
  const destination = join7(unpacked, relative);
11029
- mkdirSync8(dirname4(destination), { recursive: true, mode: 448 });
11633
+ mkdirSync8(dirname5(destination), { recursive: true, mode: 448 });
11030
11634
  writeFileSync9(destination, extracted.output, { mode: 384, flag: "wx" });
11031
11635
  }
11032
11636
  await validateReleaseDirectory(unpacked, release, run2);
@@ -11058,21 +11662,21 @@ async function stageAgentRelease(releaseInput, options) {
11058
11662
  }
11059
11663
  }
11060
11664
  function selectAgentRelease(staged) {
11061
- const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.next`);
11665
+ const next = join7(dirname5(staged.currentLink), `.current.${randomUUID2()}.next`);
11062
11666
  try {
11063
11667
  symlinkSync3(staged.nextTarget, next);
11064
11668
  renameSync6(next, staged.currentLink);
11065
- syncPath(dirname4(staged.currentLink));
11669
+ syncPath(dirname5(staged.currentLink));
11066
11670
  } finally {
11067
11671
  rmSync5(next, { force: true });
11068
11672
  }
11069
11673
  }
11070
11674
  function restoreAgentRelease(staged) {
11071
- const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.rollback`);
11675
+ const next = join7(dirname5(staged.currentLink), `.current.${randomUUID2()}.rollback`);
11072
11676
  try {
11073
11677
  symlinkSync3(staged.previousTarget, next);
11074
11678
  renameSync6(next, staged.currentLink);
11075
- syncPath(dirname4(staged.currentLink));
11679
+ syncPath(dirname5(staged.currentLink));
11076
11680
  } finally {
11077
11681
  rmSync5(next, { force: true });
11078
11682
  }
@@ -11094,7 +11698,7 @@ import {
11094
11698
  writeFileSync as writeFileSync10
11095
11699
  } from "fs";
11096
11700
  import { connect as connect5, createServer as createServer6 } from "net";
11097
- import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
11701
+ import { dirname as dirname6, join as join8, resolve as resolve3 } from "path";
11098
11702
  var AGENT_UPDATE_GROUP = "forgezero-update";
11099
11703
  var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-helper.service";
11100
11704
  var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
@@ -11194,7 +11798,7 @@ function readJournal(path2, root) {
11194
11798
  return validateJournal(JSON.parse(readFileSync10(path2, "utf8")), root);
11195
11799
  }
11196
11800
  function writeAtomic(path2, value, mode) {
11197
- mkdirSync9(dirname5(path2), { recursive: true, mode: 493 });
11801
+ mkdirSync9(dirname6(path2), { recursive: true, mode: 493 });
11198
11802
  const next = `${path2}.${randomUUID3()}.next`;
11199
11803
  let file;
11200
11804
  try {
@@ -11205,7 +11809,7 @@ function writeAtomic(path2, value, mode) {
11205
11809
  closeSync3(file);
11206
11810
  file = undefined;
11207
11811
  renameSync7(next, path2);
11208
- const directory = openSync3(dirname5(path2), "r");
11812
+ const directory = openSync3(dirname6(path2), "r");
11209
11813
  try {
11210
11814
  fsyncSync2(directory);
11211
11815
  } finally {
@@ -11327,7 +11931,7 @@ async function activateAgentRelease(staged, options = {}) {
11327
11931
  const now = options.now ?? Date.now;
11328
11932
  const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
11329
11933
  const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
11330
- const previous = readJournal(journalPath, dirname5(staged.currentLink));
11934
+ const previous = readJournal(journalPath, dirname6(staged.currentLink));
11331
11935
  const attemptId = options.attemptId ?? randomUUID3();
11332
11936
  if (!ATTEMPT_ID.test(attemptId))
11333
11937
  throw new Error("Agent update attempt ID is invalid");
@@ -11435,7 +12039,7 @@ function startAgentUpdateHelper(options = {}) {
11435
12039
  const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
11436
12040
  if (existsSync13(socketPath))
11437
12041
  unlinkSync10(socketPath);
11438
- mkdirSync9(dirname5(socketPath), { recursive: true, mode: 488 });
12042
+ mkdirSync9(dirname6(socketPath), { recursive: true, mode: 488 });
11439
12043
  const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
11440
12044
  const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
11441
12045
  const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
@@ -11544,23 +12148,63 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
11544
12148
  }
11545
12149
 
11546
12150
  // src/agent-heartbeat.ts
11547
- import { readFileSync as readFileSync11 } from "fs";
12151
+ import { existsSync as existsSync14, readFileSync as readFileSync11, statfsSync as statfsSync2 } from "fs";
12152
+ import { cpus, freemem, loadavg, totalmem } from "os";
12153
+ function readAgentHostMetrics() {
12154
+ const filesystem = statfsSync2("/");
12155
+ return {
12156
+ logicalCpu: cpus().length,
12157
+ memoryBytes: totalmem(),
12158
+ memoryFreeBytes: freemem(),
12159
+ storageBytes: Number(filesystem.blocks) * Number(filesystem.bsize),
12160
+ storageFreeBytes: Number(filesystem.bavail) * Number(filesystem.bsize),
12161
+ load1m: loadavg()[0]
12162
+ };
12163
+ }
11548
12164
  var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
11549
12165
  var remoteOutcome4 = (cause) => cause instanceof SignedNodeHttpError && cause.status < 500 ? "refused" : cause instanceof SignedNodeHttpError ? "retryable" : "failed";
11550
12166
 
11551
12167
  class AgentUpdateRefusedError extends Error {
11552
12168
  }
11553
- function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = readFileSync11("/etc/os-release", "utf8"), architecture = process.arch) {
12169
+ function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = readFileSync11("/etc/os-release", "utf8"), architecture = process.arch, metrics = readAgentHostMetrics()) {
11554
12170
  const values = Object.fromEntries(osRelease.split(`
11555
12171
  `).flatMap((line) => {
11556
12172
  const separator = line.indexOf("=");
11557
12173
  return separator > 0 ? [[line.slice(0, separator), unquote(line.slice(separator + 1))]] : [];
11558
12174
  }));
12175
+ const osId = (values.ID ?? "unknown").toLowerCase();
12176
+ const osVersion = values.VERSION_ID ?? "unknown";
12177
+ const ubuntuX64 = osId === "ubuntu" && ["24.04", "26.04"].includes(osVersion) && architecture === "x64";
12178
+ const enabled = (path2) => {
12179
+ try {
12180
+ return /^(?:1|y)$/i.test(readFileSync11(path2, "utf8").trim());
12181
+ } catch {
12182
+ return false;
12183
+ }
12184
+ };
12185
+ const gib = 1024 ** 3;
12186
+ const mib = 1024 ** 2;
12187
+ const storageGiB = Math.max(1, Math.floor(metrics.storageBytes / gib));
11559
12188
  return {
11560
12189
  version,
11561
- os: { id: (values.ID ?? "unknown").toLowerCase(), versionId: values.VERSION_ID ?? "unknown" },
12190
+ os: { id: osId, versionId: osVersion },
11562
12191
  architecture,
11563
- mode
12192
+ mode,
12193
+ runtimeCapabilities: {
12194
+ native: true,
12195
+ ociRunc: ubuntuX64,
12196
+ kataQemuSnp: ubuntuX64 && osVersion === "26.04" && existsSync14("/dev/kvm") && existsSync14("/dev/sev") && enabled("/sys/module/kvm_amd/parameters/sev") && enabled("/sys/module/kvm_amd/parameters/sev_snp")
12197
+ },
12198
+ capacity: {
12199
+ logicalCpu: Math.max(1, metrics.logicalCpu),
12200
+ memoryMiB: Math.max(1, Math.floor(metrics.memoryBytes / mib)),
12201
+ storageGiB
12202
+ },
12203
+ utilization: {
12204
+ load1mMilli: Math.max(0, Math.round(metrics.load1m * 1000)),
12205
+ memoryUsedMiB: Math.max(0, Math.floor((metrics.memoryBytes - metrics.memoryFreeBytes) / mib)),
12206
+ storageUsedGiB: Math.max(0, Math.min(storageGiB, Math.ceil((metrics.storageBytes - metrics.storageFreeBytes) / gib)))
12207
+ }
11564
12208
  };
11565
12209
  }
11566
12210
  async function heartbeatAgentOnce(options) {
@@ -11668,14 +12312,14 @@ function startAgentHeartbeat(options) {
11668
12312
  }
11669
12313
 
11670
12314
  // src/software-helper.ts
11671
- import { chmodSync as chmodSync14, existsSync as existsSync16, mkdirSync as mkdirSync12, unlinkSync as unlinkSync11 } from "fs";
12315
+ import { chmodSync as chmodSync14, existsSync as existsSync17, mkdirSync as mkdirSync13, unlinkSync as unlinkSync11 } from "fs";
11672
12316
  import { connect as connect6, createServer as createServer7 } from "net";
11673
- import { dirname as dirname8 } from "path";
12317
+ import { dirname as dirname10 } from "path";
11674
12318
 
11675
12319
  // src/service-supervisor.ts
11676
12320
  import { createHash as createHash9 } from "crypto";
11677
- 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";
11678
- import { dirname as dirname6, join as join9, resolve as resolve4, sep as sep2 } from "path";
12321
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "fs";
12322
+ import { dirname as dirname7, join as join9, resolve as resolve4, sep as sep2 } from "path";
11679
12323
  var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
11680
12324
  var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
11681
12325
  var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
@@ -11685,14 +12329,14 @@ var statePath = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
11685
12329
  var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
11686
12330
  var defaultHost = {
11687
12331
  write(path2, content, mode) {
11688
- mkdirSync10(dirname6(path2), { recursive: true, mode: 493 });
12332
+ mkdirSync10(dirname7(path2), { recursive: true, mode: 493 });
11689
12333
  const next = `${path2}.next`;
11690
12334
  writeFileSync11(next, content, { mode });
11691
12335
  renameSync8(next, path2);
11692
12336
  },
11693
12337
  read: (path2) => readFileSync12(path2, "utf8"),
11694
- exists: existsSync14,
11695
- list: (path2) => existsSync14(path2) ? readdirSync2(path2) : [],
12338
+ exists: existsSync15,
12339
+ list: (path2) => existsSync15(path2) ? readdirSync2(path2) : [],
11696
12340
  realpath: realpathSync2,
11697
12341
  mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
11698
12342
  remove: (path2) => rmSync7(path2, { force: true }),
@@ -11891,20 +12535,20 @@ async function activateSupervisedService(request, host = defaultHost) {
11891
12535
 
11892
12536
  // src/container-supervisor.ts
11893
12537
  import { createHash as createHash10 } from "crypto";
11894
- 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";
11895
- import { dirname as dirname7, resolve as resolve5, sep as sep3 } from "path";
12538
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync13, readdirSync as readdirSync3, realpathSync as realpathSync3, renameSync as renameSync9, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "fs";
12539
+ import { dirname as dirname8, resolve as resolve5, sep as sep3 } from "path";
11896
12540
  var defaultHost2 = {
11897
12541
  realpath: realpathSync3,
11898
- exists: existsSync15,
12542
+ exists: existsSync16,
11899
12543
  read: (path2) => readFileSync13(path2, "utf8"),
11900
12544
  write(path2, content, mode) {
11901
- mkdirSync11(dirname7(path2), { recursive: true, mode: 493 });
12545
+ mkdirSync11(dirname8(path2), { recursive: true, mode: 493 });
11902
12546
  const next = `${path2}.next`;
11903
12547
  writeFileSync12(next, content, { mode });
11904
12548
  renameSync9(next, path2);
11905
12549
  },
11906
12550
  remove: (path2) => rmSync8(path2, { force: true }),
11907
- list: (path2) => existsSync15(path2) ? readdirSync3(path2) : [],
12551
+ list: (path2) => existsSync16(path2) ? readdirSync3(path2) : [],
11908
12552
  mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
11909
12553
  async exec(argv2) {
11910
12554
  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" } });
@@ -11926,6 +12570,38 @@ var within3 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep3
11926
12570
  var idFor2 = (key) => createHash10("sha256").update(key).digest("hex").slice(0, 24);
11927
12571
  var statePath2 = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
11928
12572
  var nameFor = (id2, slot) => `forgezero-${id2}-slot${slot}`;
12573
+ function backendFor(application) {
12574
+ const runtime = application.runtime;
12575
+ if (runtime.kind !== "container")
12576
+ throw new Error("CONTAINER_RUNTIME_REQUIRED");
12577
+ if (runtime.provider === "forgezero.docker") {
12578
+ if (runtime.runtimeClass && runtime.runtimeClass !== "runc")
12579
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
12580
+ return { engine: "docker", runtimeClass: "runc", prefix: ["/usr/bin/docker"] };
12581
+ }
12582
+ if (runtime.provider === "forgezero.containerd") {
12583
+ if ((runtime.runtimeClass ?? "runc") !== "runc")
12584
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
12585
+ return {
12586
+ engine: "containerd",
12587
+ runtimeClass: "runc",
12588
+ prefix: ["/usr/local/bin/nerdctl", "--namespace", "forgezero"],
12589
+ runtimeName: "io.containerd.runc.v2"
12590
+ };
12591
+ }
12592
+ if (runtime.provider === "forgezero.kata") {
12593
+ if (runtime.runtimeClass !== "kata-qemu-snp")
12594
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
12595
+ return {
12596
+ engine: "containerd",
12597
+ runtimeClass: "kata-qemu-snp",
12598
+ prefix: ["/usr/local/bin/nerdctl", "--namespace", "forgezero"],
12599
+ runtimeName: "io.containerd.kata.v2"
12600
+ };
12601
+ }
12602
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_UNSUPPORTED");
12603
+ }
12604
+ var command2 = (backend, ...argv2) => [...backend.prefix, ...argv2];
11929
12605
  function validateRequest(request, host) {
11930
12606
  if (!/^[A-Za-z0-9][A-Za-z0-9_.:@/-]{0,255}$/.test(request.key) || !/^[a-f0-9]{40}$/.test(request.revision) || !/^[a-z][A-Za-z0-9-]{0,62}$/.test(request.component))
11931
12607
  throw new Error("CONTAINER_IDENTITY_INVALID");
@@ -11936,6 +12612,7 @@ function validateRequest(request, host) {
11936
12612
  const application = request.application;
11937
12613
  if (application.kind !== "application" || application.runtime.kind !== "container")
11938
12614
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
12615
+ backendFor(application);
11939
12616
  if (!application.resources.cpu || !application.resources.memory || !application.resources.pids)
11940
12617
  throw new Error("CONTAINER_LIMITS_REQUIRED");
11941
12618
  if (application.runtime.security.privileged !== false || application.runtime.security.noNewPrivileges !== true || !application.runtime.security.dropCapabilities.includes("ALL"))
@@ -11957,12 +12634,13 @@ async function buildContainerImage(requestValue, host = defaultHost2) {
11957
12634
  const runtime = request.application.runtime;
11958
12635
  if (runtime.kind !== "container")
11959
12636
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
12637
+ const backend = backendFor(request.application);
11960
12638
  const source = runtime.image.source;
11961
12639
  if (source.kind === "registry") {
11962
12640
  if (!source.reference.includes("@sha256:"))
11963
12641
  throw new Error("CONTAINER_IMAGE_DIGEST_REQUIRED");
11964
- await checked6(host, ["/usr/bin/docker", "pull", "--quiet", source.reference], "CONTAINER_PULL_FAILED");
11965
- const digest2 = await checked6(host, ["/usr/bin/docker", "image", "inspect", "--format={{.Id}}", source.reference], "CONTAINER_INSPECT_FAILED");
12642
+ await checked6(host, command2(backend, "pull", "--quiet", source.reference), "CONTAINER_PULL_FAILED");
12643
+ const digest2 = await checked6(host, command2(backend, "image", "inspect", "--format={{.Id}}", source.reference), "CONTAINER_INSPECT_FAILED");
11966
12644
  if (!/^sha256:[a-f0-9]{64}$/.test(digest2))
11967
12645
  throw new Error("CONTAINER_DIGEST_INVALID");
11968
12646
  return { image: source.reference, digest: digest2 };
@@ -11972,22 +12650,8 @@ async function buildContainerImage(requestValue, host = defaultHost2) {
11972
12650
  if (!within3(request.release, context) || !within3(context, dockerfile))
11973
12651
  throw new Error("CONTAINER_BUILD_PATH_INVALID");
11974
12652
  const tag = `forgezero/${idFor2(request.key)}:${request.revision}`;
11975
- await checked6(host, [
11976
- "/usr/bin/docker",
11977
- "build",
11978
- "--pull",
11979
- ...request.noCache ? ["--no-cache"] : [],
11980
- "--file",
11981
- dockerfile,
11982
- "--tag",
11983
- tag,
11984
- "--label",
11985
- `net.forgezero.revision=${request.revision}`,
11986
- "--label",
11987
- `net.forgezero.component=${request.component}`,
11988
- context
11989
- ], "CONTAINER_BUILD_FAILED");
11990
- const digest = await checked6(host, ["/usr/bin/docker", "image", "inspect", "--format={{.Id}}", tag], "CONTAINER_INSPECT_FAILED");
12653
+ await checked6(host, command2(backend, "build", "--pull", ...request.noCache ? ["--no-cache"] : [], "--file", dockerfile, "--tag", tag, "--label", `net.forgezero.revision=${request.revision}`, "--label", `net.forgezero.component=${request.component}`, context), "CONTAINER_BUILD_FAILED");
12654
+ const digest = await checked6(host, command2(backend, "image", "inspect", "--format={{.Id}}", tag), "CONTAINER_INSPECT_FAILED");
11991
12655
  if (!/^sha256:[a-f0-9]{64}$/.test(digest))
11992
12656
  throw new Error("CONTAINER_DIGEST_INVALID");
11993
12657
  return { image: tag, digest };
@@ -12055,7 +12719,7 @@ ${service.websocket ? ` proxy_set_header Upgrade $http_upgrade;
12055
12719
  }
12056
12720
  `;
12057
12721
  }
12058
- function runArgv(request, id2, slot, port, networkName) {
12722
+ function runArgv(request, backend, id2, slot, port, networkName) {
12059
12723
  const { application } = request;
12060
12724
  const resources = application.resources;
12061
12725
  const runtime = application.runtime;
@@ -12063,35 +12727,7 @@ function runArgv(request, id2, slot, port, networkName) {
12063
12727
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
12064
12728
  const memory = resources.memory;
12065
12729
  const network = application.network.container;
12066
- const argv2 = [
12067
- "/usr/bin/docker",
12068
- "run",
12069
- "--detach",
12070
- "--name",
12071
- nameFor(id2, slot),
12072
- "--restart",
12073
- "unless-stopped",
12074
- "--label",
12075
- `net.forgezero.key=${request.key}`,
12076
- "--label",
12077
- `net.forgezero.revision=${request.revision}`,
12078
- "--cpus",
12079
- String(resources.cpu.limit),
12080
- "--cpu-shares",
12081
- String(resources.cpu.weight ?? 100),
12082
- "--memory",
12083
- `${memory.limitMiB}m`,
12084
- ...memory.reservationMiB ? ["--memory-reservation", `${memory.reservationMiB}m`] : [],
12085
- ...memory.swap === "disabled" ? ["--memory-swap", `${memory.limitMiB}m`] : [],
12086
- "--pids-limit",
12087
- String(resources.pids.limit),
12088
- "--security-opt",
12089
- "no-new-privileges=true",
12090
- "--cap-drop",
12091
- "ALL",
12092
- ...runtime.security.root === "read-only" ? ["--read-only"] : [],
12093
- ...network.mode === "host" ? ["--network", "host", "--env", `FZ_APP_PORT=${port}`] : ["--network", networkName, "--publish", `127.0.0.1:${port}:${application.service.port}`]
12094
- ];
12730
+ const argv2 = command2(backend, "run", "--detach", "--name", nameFor(id2, slot), "--restart", "unless-stopped", "--label", `net.forgezero.key=${request.key}`, "--label", `net.forgezero.revision=${request.revision}`, ...backend.runtimeName ? ["--runtime", backend.runtimeName] : [], "--cpus", String(resources.cpu.limit), "--cpu-shares", String(resources.cpu.weight ?? 100), "--memory", `${memory.limitMiB}m`, ...memory.reservationMiB ? ["--memory-reservation", `${memory.reservationMiB}m`] : [], ...memory.swap === "disabled" ? ["--memory-swap", `${memory.limitMiB}m`] : [], "--pids-limit", String(resources.pids.limit), "--security-opt", "no-new-privileges=true", "--cap-drop", "ALL", ...runtime.security.root === "read-only" ? ["--read-only"] : [], ...network.mode === "host" ? ["--network", "host", "--env", `FZ_APP_PORT=${port}`] : ["--network", networkName, "--publish", `127.0.0.1:${port}:${application.service.port}`]);
12095
12731
  for (const mount of application.storage ?? []) {
12096
12732
  if (mount.class === "ephemeral")
12097
12733
  argv2.push("--tmpfs", `${mount.path}:rw,noexec,nosuid,size=${mount.sizeMiB}m`);
@@ -12105,6 +12741,7 @@ async function activateContainer(requestValue, host = defaultHost2) {
12105
12741
  const request = validateRequest(requestValue, host);
12106
12742
  if (!/^sha256:[a-f0-9]{64}$/.test(request.image) && !/^forgezero\/[a-f0-9]{24}:[a-f0-9]{40}$/.test(request.image) && !request.image.includes("@sha256:"))
12107
12743
  throw new Error("CONTAINER_IMAGE_INVALID");
12744
+ const backend = backendFor(request.application);
12108
12745
  const id2 = idFor2(request.key);
12109
12746
  host.mkdir(SERVICE_STATE_DIRECTORY, 448);
12110
12747
  host.mkdir(SERVICE_NGINX_DIRECTORY, 493);
@@ -12117,9 +12754,9 @@ async function activateContainer(requestValue, host = defaultHost2) {
12117
12754
  if (containerNetwork.mode === "bridge") {
12118
12755
  networkName = containerNetwork.network ? `forgezero-${id2}-${containerNetwork.network}` : "bridge";
12119
12756
  if (networkName !== "bridge") {
12120
- const inspected = await host.exec(["/usr/bin/docker", "network", "inspect", '--format={{index .Labels "net.forgezero.owner"}}', networkName]);
12757
+ const inspected = await host.exec(command2(backend, "network", "inspect", '--format={{index .Labels "net.forgezero.owner"}}', networkName));
12121
12758
  if (inspected.exitCode !== 0) {
12122
- await checked6(host, ["/usr/bin/docker", "network", "create", "--driver", "bridge", "--label", `net.forgezero.owner=${id2}`, networkName], "CONTAINER_NETWORK_CREATE_FAILED");
12759
+ await checked6(host, command2(backend, "network", "create", "--driver", "bridge", "--label", `net.forgezero.owner=${id2}`, networkName), "CONTAINER_NETWORK_CREATE_FAILED");
12123
12760
  } else if (inspected.output.trim() !== id2)
12124
12761
  throw new Error("CONTAINER_NETWORK_OWNERSHIP_INVALID");
12125
12762
  }
@@ -12128,8 +12765,8 @@ async function activateContainer(requestValue, host = defaultHost2) {
12128
12765
  const nextSlot = slots === 2 && previous?.activeSlot === 0 ? 1 : 0;
12129
12766
  const port = ports[nextSlot];
12130
12767
  const name = nameFor(id2, nextSlot);
12131
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
12132
- await checked6(host, runArgv(request, id2, nextSlot, port, networkName), "CONTAINER_START_FAILED");
12768
+ await host.exec(command2(backend, "rm", "--force", name));
12769
+ await checked6(host, runArgv(request, backend, id2, nextSlot, port, networkName), "CONTAINER_START_FAILED");
12133
12770
  let healthy = false;
12134
12771
  const health = request.application.service.health;
12135
12772
  for (let attempt = 0;attempt < (health.attempts ?? 30); attempt += 1) {
@@ -12140,9 +12777,22 @@ async function activateContainer(requestValue, host = defaultHost2) {
12140
12777
  await host.sleep(health.intervalMs ?? 1000);
12141
12778
  }
12142
12779
  if (!healthy) {
12143
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
12780
+ await host.exec(command2(backend, "rm", "--force", name));
12144
12781
  throw new Error("CONTAINER_HEALTH_FAILED");
12145
12782
  }
12783
+ let runtimeEvidence;
12784
+ if (backend.engine === "containerd") {
12785
+ const raw = await checked6(host, ["/usr/bin/ctr", "--namespace", "forgezero", "containers", "info", name], "CONTAINER_RUNTIME_EVIDENCE_FAILED");
12786
+ let observed = "";
12787
+ try {
12788
+ observed = String(JSON.parse(raw).Runtime?.Name ?? "");
12789
+ } catch {
12790
+ throw new Error("CONTAINER_RUNTIME_EVIDENCE_INVALID");
12791
+ }
12792
+ if (observed !== backend.runtimeName)
12793
+ throw new Error("CONTAINER_RUNTIME_DOWNGRADE");
12794
+ runtimeEvidence = observed;
12795
+ }
12146
12796
  const nginxPath = `${SERVICE_NGINX_DIRECTORY}/forgezero-${id2}.conf`;
12147
12797
  const previousNginx = host.exists(nginxPath) ? host.read(nginxPath) : null;
12148
12798
  host.write(nginxPath, nginx2(request, id2, port), 420);
@@ -12156,7 +12806,7 @@ async function activateContainer(requestValue, host = defaultHost2) {
12156
12806
  host.write(nginxPath, previousNginx, 420);
12157
12807
  await host.exec(["/usr/sbin/nginx", "-t"]);
12158
12808
  await host.exec(["/usr/bin/systemctl", "reload", "nginx.service"]);
12159
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
12809
+ await host.exec(command2(backend, "rm", "--force", name));
12160
12810
  throw cause;
12161
12811
  }
12162
12812
  const state = {
@@ -12169,17 +12819,167 @@ async function activateContainer(requestValue, host = defaultHost2) {
12169
12819
  applicationPorts: ports,
12170
12820
  activeSlot: nextSlot,
12171
12821
  healthPath: health.path,
12822
+ containerRuntime: {
12823
+ engine: backend.engine,
12824
+ runtimeClass: backend.runtimeClass,
12825
+ ...backend.engine === "containerd" ? { namespace: "forgezero", evidence: runtimeEvidence } : {}
12826
+ },
12172
12827
  updatedAtTs: host.now()
12173
12828
  };
12174
12829
  host.write(statePath2(id2), `${JSON.stringify(state, null, 2)}
12175
12830
  `, 384);
12176
12831
  if (previous && previous.activeSlot !== nextSlot) {
12177
12832
  await host.sleep(request.application.rollout.drainMs ?? 30000);
12178
- await host.exec(["/usr/bin/docker", "rm", "--force", nameFor(id2, previous.activeSlot)]);
12833
+ await host.exec(command2(backend, "rm", "--force", nameFor(id2, previous.activeSlot)));
12179
12834
  }
12180
12835
  return state;
12181
12836
  }
12182
12837
 
12838
+ // src/deployment-connectivity.ts
12839
+ import { createHash as createHash11 } from "crypto";
12840
+ import { mkdirSync as mkdirSync12, renameSync as renameSync10, writeFileSync as writeFileSync13 } from "fs";
12841
+ import { dirname as dirname9 } from "path";
12842
+ var TOKEN2 = /^[A-Za-z0-9._-]{40,16384}$/;
12843
+ var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
12844
+ var HOSTNAME2 = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
12845
+ var NAME3 = /^[a-z][a-z0-9._-]{0,99}$/;
12846
+ var idFor3 = (key) => createHash11("sha256").update(key).digest("hex").slice(0, 20);
12847
+ var checked7 = async (host, argv2, label) => {
12848
+ const result = await host.exec(argv2);
12849
+ if (result.exitCode !== 0)
12850
+ throw new Error(`${label} failed: ${result.output.slice(0, 512)}`);
12851
+ return result.output.trim();
12852
+ };
12853
+ async function sealWithSystemd(name, path2, value) {
12854
+ mkdirSync12(dirname9(path2), { recursive: true, mode: 448 });
12855
+ const next = `${path2}.next`;
12856
+ const child = Bun.spawn(["/usr/bin/systemd-creds", "encrypt", `--name=${name}`, "-", next], {
12857
+ stdin: "pipe",
12858
+ stdout: "pipe",
12859
+ stderr: "pipe",
12860
+ env: { PATH: "/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" }
12861
+ });
12862
+ await writeAndCloseProcessInput(child.stdin, value);
12863
+ const [stdout, stderr, exitCode] = await Promise.all([
12864
+ new Response(child.stdout).text(),
12865
+ new Response(child.stderr).text(),
12866
+ child.exited
12867
+ ]);
12868
+ if (exitCode !== 0)
12869
+ throw new Error(`systemd credential sealing failed: ${`${stdout}${stderr}`.replaceAll(value, "[REDACTED]").slice(0, 512)}`);
12870
+ renameSync10(next, path2);
12871
+ }
12872
+ var defaultHost3 = {
12873
+ seal: sealWithSystemd,
12874
+ write(path2, content, mode) {
12875
+ mkdirSync12(dirname9(path2), { recursive: true, mode: 493 });
12876
+ const next = `${path2}.next`;
12877
+ writeFileSync13(next, content, { mode });
12878
+ renameSync10(next, path2);
12879
+ },
12880
+ async exec(argv2) {
12881
+ 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" } });
12882
+ const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
12883
+ return { exitCode, output: `${stdout}${stderr}` };
12884
+ },
12885
+ sleep: (ms) => Bun.sleep(ms)
12886
+ };
12887
+ function validate2(request) {
12888
+ if (!request.key || request.key.length > 256)
12889
+ throw new Error("deployment connectivity key is invalid");
12890
+ const publicIntent = request.intent.public;
12891
+ const privateIntent = request.intent.private;
12892
+ if (publicIntent?.mode === "cloudflare-tunnel") {
12893
+ if (!NAME3.test(publicIntent.credential) || !HOSTNAME2.test(publicIntent.hostname) || !NAME3.test(publicIntent.tunnelKey) || publicIntent.service.protocol !== "http" || !Number.isInteger(publicIntent.service.port) || publicIntent.service.port < 1 || publicIntent.service.port > 65535 || !request.capabilities.public || !UUID.test(request.capabilities.public.tunnelId) || !TOKEN2.test(request.capabilities.public.connectorToken)) {
12894
+ throw new Error("public deployment connectivity is malformed");
12895
+ }
12896
+ } else if (request.capabilities.public)
12897
+ throw new Error("unexpected public deployment capability");
12898
+ if (privateIntent?.mode === "cloudflare-warp") {
12899
+ if (!NAME3.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !TOKEN2.test(request.capabilities.private.connectorToken)) {
12900
+ throw new Error("private deployment connectivity is malformed");
12901
+ }
12902
+ } else if (request.capabilities.private)
12903
+ throw new Error("unexpected private deployment capability");
12904
+ }
12905
+ async function applyDeploymentConnectivity(request, host = defaultHost3) {
12906
+ validate2(request);
12907
+ const id2 = idFor3(request.key);
12908
+ const evidence = { key: request.key };
12909
+ if (request.intent.public?.mode === "cloudflare-tunnel") {
12910
+ const capability = request.capabilities.public;
12911
+ const credentialName = `FZ_TUNNEL_${id2.toUpperCase()}`;
12912
+ const credentialPath = `/etc/forgezero/creds/deployment-tunnel-${id2}.cred`;
12913
+ const unit2 = `forgezero-cloudflared-${id2}.service`;
12914
+ const unitPath = `/etc/systemd/system/${unit2}`;
12915
+ const metricsPort = 20000 + Number.parseInt(id2.slice(0, 4), 16) % 20000;
12916
+ await host.seal(credentialName, credentialPath, capability.connectorToken);
12917
+ host.write(unitPath, `[Unit]
12918
+ Description=ForgeZero deployment Tunnel ${id2}
12919
+ After=network-online.target
12920
+ Wants=network-online.target
12921
+
12922
+ [Service]
12923
+ Type=simple
12924
+ DynamicUser=yes
12925
+ LoadCredentialEncrypted=${credentialName}:${credentialPath}
12926
+ ExecStart=/usr/local/bin/cloudflared tunnel --no-autoupdate --metrics 127.0.0.1:${metricsPort} run --token-file %d/${credentialName}
12927
+ Restart=always
12928
+ RestartSec=5
12929
+ NoNewPrivileges=true
12930
+ PrivateTmp=true
12931
+ ProtectSystem=strict
12932
+ ProtectHome=true
12933
+
12934
+ [Install]
12935
+ WantedBy=multi-user.target
12936
+ `, 420);
12937
+ await checked7(host, ["/usr/bin/systemctl", "daemon-reload"], "cloudflared daemon reload");
12938
+ await checked7(host, ["/usr/bin/systemctl", "enable", "--now", unit2], "cloudflared start");
12939
+ await checked7(host, ["/usr/bin/systemctl", "is-active", "--quiet", unit2], "cloudflared readiness");
12940
+ evidence.public = { hostname: request.intent.public.hostname, tunnelId: capability.tunnelId, unit: unit2, active: true };
12941
+ }
12942
+ if (request.intent.private?.mode === "cloudflare-warp") {
12943
+ const capability = request.capabilities.private;
12944
+ const credentialPath = "/etc/forgezero/creds/CF_WARP_CONNECTOR_TOKEN.cred";
12945
+ const unit2 = "forgezero-deployment-mesh.service";
12946
+ await host.seal("CF_WARP_CONNECTOR_TOKEN", credentialPath, capability.connectorToken);
12947
+ host.write(`/etc/systemd/system/${unit2}`, `[Unit]
12948
+ Description=ForgeZero deployment Mesh/WARP connector
12949
+ Requires=warp-svc.service
12950
+ After=network-online.target warp-svc.service
12951
+ Wants=network-online.target
12952
+
12953
+ [Service]
12954
+ Type=oneshot
12955
+ RemainAfterExit=yes
12956
+ LoadCredentialEncrypted=CF_WARP_CONNECTOR_TOKEN:${credentialPath}
12957
+ ExecStart=/usr/local/bin/fz-agent mesh-config
12958
+ NoNewPrivileges=true
12959
+ PrivateTmp=true
12960
+ ProtectSystem=strict
12961
+ ProtectHome=true
12962
+ ReadWritePaths=/var/lib/cloudflare-warp
12963
+
12964
+ [Install]
12965
+ WantedBy=multi-user.target
12966
+ `, 420);
12967
+ await checked7(host, ["/usr/bin/systemctl", "daemon-reload"], "WARP daemon reload");
12968
+ await checked7(host, ["/usr/bin/systemctl", "enable", "--now", "warp-svc.service", unit2], "WARP connector start");
12969
+ for (let attempt = 0;attempt < 10; attempt += 1) {
12970
+ const status = await host.exec(["/usr/bin/warp-cli", "--accept-tos", "status"]);
12971
+ if (status.exitCode === 0 && /\bconnected\b/i.test(status.output) && !/\bdisconnected\b/i.test(status.output)) {
12972
+ evidence.private = { network: request.intent.private.network, connectorId: capability.connectorId, unit: unit2, active: true };
12973
+ break;
12974
+ }
12975
+ await host.sleep(1000);
12976
+ }
12977
+ if (!evidence.private)
12978
+ throw new Error("WARP connector readiness failed");
12979
+ }
12980
+ return evidence;
12981
+ }
12982
+
12183
12983
  // src/software-helper.ts
12184
12984
  var DEFAULT_SOFTWARE_HELPER_SOCKET = "/run/forgezero-software/helper.sock";
12185
12985
  var SOFTWARE_HELPER_GROUP = "forgezero-software";
@@ -12188,13 +12988,14 @@ var MAX_REQUEST_BYTES6 = 128 * 1024;
12188
12988
  var MAX_PENDING_REQUESTS = 128;
12189
12989
  function startSoftwareHelper(options = {}) {
12190
12990
  const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
12191
- if (existsSync16(socketPath))
12991
+ if (existsSync17(socketPath))
12192
12992
  unlinkSync11(socketPath);
12193
- mkdirSync12(dirname8(socketPath), { recursive: true, mode: 488 });
12993
+ mkdirSync13(dirname10(socketPath), { recursive: true, mode: 488 });
12194
12994
  const ensure = options.ensure ?? ensureSoftwareRequirements;
12195
12995
  const activate = options.activate ?? activateSupervisedService;
12196
12996
  const buildContainer = options.buildContainer ?? buildContainerImage;
12197
12997
  const activateTypedContainer = options.activateContainer ?? activateContainer;
12998
+ const applyConnectivity = options.applyConnectivity ?? applyDeploymentConnectivity;
12198
12999
  let tail = Promise.resolve();
12199
13000
  let pending = 0;
12200
13001
  const server = createServer7((socket) => {
@@ -12251,6 +13052,12 @@ function startSoftwareHelper(options = {}) {
12251
13052
  throw new Error("container deployment root is not owned by this helper");
12252
13053
  const state = await activateTypedContainer(containerRequest);
12253
13054
  socket.end(`${JSON.stringify({ ok: true, state })}
13055
+ `);
13056
+ return;
13057
+ }
13058
+ if (request.op === "apply-connectivity" && request.request) {
13059
+ const result = await applyConnectivity(request.request);
13060
+ socket.end(`${JSON.stringify({ ok: true, result })}
12254
13061
  `);
12255
13062
  return;
12256
13063
  }
@@ -12308,6 +13115,9 @@ function requestContainerBuild(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOC
12308
13115
  function requestContainerActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
12309
13116
  return requestContainer("activate-container", request, socketPath, timeoutMs);
12310
13117
  }
13118
+ function requestDeploymentConnectivity(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
13119
+ return requestContainer("apply-connectivity", request, socketPath, timeoutMs);
13120
+ }
12311
13121
  function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 10 * 60000) {
12312
13122
  return new Promise((resolve6, reject) => {
12313
13123
  const socket = connect6(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
@@ -12521,9 +13331,9 @@ var systemCommand = (argv2, stdin) => {
12521
13331
  stderr: result.stderr.toString()
12522
13332
  };
12523
13333
  };
12524
- function resolveServiceUid(user, command2 = systemCommand) {
13334
+ function resolveServiceUid(user, command3 = systemCommand) {
12525
13335
  assertServiceUser(user);
12526
- const result = command2(["/usr/bin/id", "-u", user]);
13336
+ const result = command3(["/usr/bin/id", "-u", user]);
12527
13337
  const uid = Number(result.stdout.trim());
12528
13338
  if (result.exitCode !== 0 || !/^\d+$/.test(result.stdout.trim())) {
12529
13339
  throw new Error(`Agent egress service account ${user} does not exist.`);
@@ -12548,14 +13358,14 @@ function assertResolvedStub(realpath = realpathSync4) {
12548
13358
  throw new Error("Agent egress requires the active systemd-resolved stub.");
12549
13359
  }
12550
13360
  }
12551
- var listPolicy = (command2) => command2(["/usr/sbin/nft", "--numeric", "list", "table", "inet", AGENT_EGRESS_TABLE]);
12552
- function verifyAgentEgressPolicy(uids, command2 = systemCommand, loopback) {
13361
+ var listPolicy = (command3) => command3(["/usr/sbin/nft", "--numeric", "list", "table", "inet", AGENT_EGRESS_TABLE]);
13362
+ function verifyAgentEgressPolicy(uids, command3 = systemCommand, loopback) {
12553
13363
  if (uids.length < 1)
12554
13364
  return false;
12555
13365
  for (const uid of uids)
12556
13366
  assertUid(uid);
12557
13367
  const uniqueUids = [...new Set(uids)].sort((left, right) => left - right);
12558
- const result = listPolicy(command2);
13368
+ const result = listPolicy(command3);
12559
13369
  if (result.exitCode !== 0)
12560
13370
  return false;
12561
13371
  let loopbackComment = "";
@@ -12601,44 +13411,44 @@ function applyAgentEgressPolicy(options) {
12601
13411
  if (typeof getuid !== "function" || getuid() !== 0) {
12602
13412
  throw new Error("Agent egress policy must run as root.");
12603
13413
  }
12604
- const command2 = options.command ?? systemCommand;
13414
+ const command3 = options.command ?? systemCommand;
12605
13415
  assertResolvedStub(options.realpath);
12606
13416
  if (options.users.length < 1)
12607
13417
  throw new Error("Agent egress policy needs at least one service user.");
12608
- const uids = [...new Set(options.users.map((user) => resolveServiceUid(user, command2)))];
13418
+ const uids = [...new Set(options.users.map((user) => resolveServiceUid(user, command3)))];
12609
13419
  const loopback = options.loopback ? {
12610
- uid: resolveServiceUid(options.loopback.user, command2),
13420
+ uid: resolveServiceUid(options.loopback.user, command3),
12611
13421
  tcpPorts: normalizeEgressTcpPorts(options.loopback.tcpPorts),
12612
13422
  publicTcpPorts: options.loopback.publicTcpPorts === undefined ? undefined : normalizeEgressTcpPorts(options.loopback.publicTcpPorts)
12613
13423
  } : undefined;
12614
13424
  if (loopback && !uids.includes(loopback.uid))
12615
13425
  uids.push(loopback.uid);
12616
- const current2 = listPolicy(command2);
12617
- const applied = command2(["/usr/sbin/nft", "--file", "-"], renderAgentEgressNft(uids, current2.exitCode === 0, loopback));
13426
+ const current2 = listPolicy(command3);
13427
+ const applied = command3(["/usr/sbin/nft", "--file", "-"], renderAgentEgressNft(uids, current2.exitCode === 0, loopback));
12618
13428
  if (applied.exitCode !== 0) {
12619
13429
  throw new Error(`Could not apply Agent egress policy: ${applied.stderr.trim() || "nft refused it"}`);
12620
13430
  }
12621
- if (!verifyAgentEgressPolicy(uids, command2, loopback)) {
13431
+ if (!verifyAgentEgressPolicy(uids, command3, loopback)) {
12622
13432
  throw new Error("Agent egress policy did not verify after installation.");
12623
13433
  }
12624
13434
  return uids;
12625
13435
  }
12626
13436
  async function superviseAgentEgressPolicy(options) {
12627
- const command2 = options.command ?? systemCommand;
13437
+ const command3 = options.command ?? systemCommand;
12628
13438
  const uids = applyAgentEgressPolicy({
12629
13439
  users: options.users,
12630
13440
  loopback: options.loopback,
12631
- command: command2,
13441
+ command: command3,
12632
13442
  realpath: options.realpath,
12633
13443
  getuid: options.getuid
12634
13444
  });
12635
13445
  const loopback = options.loopback ? {
12636
- uid: resolveServiceUid(options.loopback.user, command2),
13446
+ uid: resolveServiceUid(options.loopback.user, command3),
12637
13447
  tcpPorts: normalizeEgressTcpPorts(options.loopback.tcpPorts),
12638
13448
  publicTcpPorts: options.loopback.publicTcpPorts === undefined ? undefined : normalizeEgressTcpPorts(options.loopback.publicTcpPorts)
12639
13449
  } : undefined;
12640
13450
  (options.notifyReady ?? (() => {
12641
- const result = command2(["/usr/bin/systemd-notify", "--ready"]);
13451
+ const result = command3(["/usr/bin/systemd-notify", "--ready"]);
12642
13452
  if (result.exitCode !== 0)
12643
13453
  throw new Error("Could not notify systemd that Agent egress is ready.");
12644
13454
  }))();
@@ -12649,7 +13459,7 @@ async function superviseAgentEgressPolicy(options) {
12649
13459
  }
12650
13460
  for (;; ) {
12651
13461
  await wait(intervalMs);
12652
- if (!verifyAgentEgressPolicy(uids, command2, loopback)) {
13462
+ if (!verifyAgentEgressPolicy(uids, command3, loopback)) {
12653
13463
  throw new Error("Agent egress policy disappeared or changed; stopping the bound Agent.");
12654
13464
  }
12655
13465
  }
@@ -13260,7 +14070,7 @@ var AGENT_CREDENTIAL_POLICY = {
13260
14070
  "platform-compute": { vault: true, systemdFallback: true, hiddenInput: false },
13261
14071
  "tenant-compute": { vault: true, systemdFallback: true, hiddenInput: false }
13262
14072
  };
13263
- var CREDENTIAL_NAME = /^[A-Z_][A-Z0-9_]*$/;
14073
+ var CREDENTIAL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
13264
14074
  var SCOPE_PART2 = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
13265
14075
  function deploymentCredentialSchema(input) {
13266
14076
  if (!SCOPE_PART2.test(input.projectKey) || !SCOPE_PART2.test(input.environmentKey)) {
@@ -13302,17 +14112,17 @@ import {
13302
14112
  chmodSync as chmodSync15,
13303
14113
  chownSync,
13304
14114
  copyFileSync as copyFileSync2,
13305
- existsSync as existsSync17,
14115
+ existsSync as existsSync18,
13306
14116
  lstatSync as lstatSync3,
13307
- mkdirSync as mkdirSync13,
14117
+ mkdirSync as mkdirSync14,
13308
14118
  readFileSync as readFileSync14,
13309
14119
  readdirSync as readdirSync4,
13310
14120
  realpathSync as realpathSync5,
13311
- renameSync as renameSync10,
14121
+ renameSync as renameSync11,
13312
14122
  rmSync as rmSync9,
13313
- statSync as statSync3,
14123
+ statSync as statSync4,
13314
14124
  symlinkSync as symlinkSync4,
13315
- writeFileSync as writeFileSync13
14125
+ writeFileSync as writeFileSync14
13316
14126
  } from "fs";
13317
14127
  import { join as join10 } from "path";
13318
14128
  var boundedInteger2 = (name, value, minimum, maximum) => {
@@ -13372,7 +14182,7 @@ var replaceLink = (path2, target2) => {
13372
14182
  return;
13373
14183
  }
13374
14184
  symlinkSync4(target2, pending);
13375
- renameSync10(pending, path2);
14185
+ renameSync11(pending, path2);
13376
14186
  };
13377
14187
  var secureRelease = (path2, uid, gid) => {
13378
14188
  const visit = (current2) => {
@@ -13395,7 +14205,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13395
14205
  if (!release.startsWith(`${releases}/`) || release === releases)
13396
14206
  throw new Error("release is outside configured releases directory");
13397
14207
  for (const required of ["forgezero.deploy.ts", ".fz/deploy.plan.json", "src/index.ts", "bun.lock"]) {
13398
- const metadata = statSync3(join10(release, required));
14208
+ const metadata = statSync4(join10(release, required));
13399
14209
  if (!metadata.isFile() || metadata.size < 1)
13400
14210
  throw new Error(`release is incomplete: ${required}`);
13401
14211
  }
@@ -13403,9 +14213,9 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13403
14213
  const request = options.fetch ?? fetch;
13404
14214
  const sleep2 = options.sleep ?? Bun.sleep;
13405
14215
  const slots = join10(normalized.root, "slots");
13406
- mkdirSync13(slots, { recursive: true, mode: 493 });
14216
+ mkdirSync14(slots, { recursive: true, mode: 493 });
13407
14217
  const slotFile = join10(normalized.root, ".forge-slot");
13408
- const previousSlot = existsSync17(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
14218
+ const previousSlot = existsSync18(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
13409
14219
  const target2 = previousSlot === "blue" ? "green" : "blue";
13410
14220
  const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
13411
14221
  const targetLink = join10(slots, target2);
@@ -13445,17 +14255,17 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13445
14255
  }
13446
14256
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
13447
14257
  const backup = `${upstream}.forgezero-backup`;
13448
- if (existsSync17(upstream))
14258
+ if (existsSync18(upstream))
13449
14259
  copyFileSync2(upstream, backup);
13450
14260
  else
13451
14261
  rmSync9(backup, { force: true });
13452
- writeFileSync13(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
14262
+ writeFileSync14(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
13453
14263
  `, { mode: 420 });
13454
14264
  const test = await exec(["/usr/sbin/nginx", "-t"]);
13455
14265
  const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
13456
14266
  if (reload.exitCode !== 0) {
13457
- if (existsSync17(backup))
13458
- renameSync10(backup, upstream);
14267
+ if (existsSync18(backup))
14268
+ renameSync11(backup, upstream);
13459
14269
  else
13460
14270
  rmSync9(upstream, { force: true });
13461
14271
  await exec(["/usr/sbin/nginx", "-t"]);
@@ -13464,13 +14274,13 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13464
14274
  throw new Error("nginx refused the promoted upstream");
13465
14275
  }
13466
14276
  rmSync9(backup, { force: true });
13467
- writeFileSync13(slotFile, `${target2}
14277
+ writeFileSync14(slotFile, `${target2}
13468
14278
  `, { mode: 420 });
13469
14279
  if (previousSlot && previousSlot !== target2) {
13470
14280
  await sleep2(normalized.drainDeadlineMs);
13471
14281
  await exec(["/usr/bin/systemctl", "stop", `forgezero@${previousSlot}.service`]);
13472
14282
  }
13473
- 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);
14283
+ const old = readdirSync4(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join10(releases, entry.name)).sort((left, right) => statSync4(right).mtimeMs - statSync4(left).mtimeMs).slice(normalized.keepReleases);
13474
14284
  for (const path2 of old)
13475
14285
  if (path2 !== release)
13476
14286
  rmSync9(path2, { recursive: true, force: true });
@@ -13478,7 +14288,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13478
14288
  }
13479
14289
 
13480
14290
  // src/recovery-host.ts
13481
- import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync4 } from "fs";
14291
+ import { existsSync as existsSync19, readFileSync as readFileSync15, statSync as statSync5 } from "fs";
13482
14292
  import { hostname } from "os";
13483
14293
  import { join as join11 } from "path";
13484
14294
  var execute2 = async (argv2) => {
@@ -13486,7 +14296,7 @@ var execute2 = async (argv2) => {
13486
14296
  const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
13487
14297
  return { stdout, stderr, exitCode };
13488
14298
  };
13489
- var checked7 = async (run2, argv2, expected = 0) => {
14299
+ var checked8 = async (run2, argv2, expected = 0) => {
13490
14300
  const result = await run2(argv2);
13491
14301
  if (result.exitCode !== expected)
13492
14302
  throw new Error(`${argv2[0]} failed (${result.exitCode})`);
@@ -13504,7 +14314,7 @@ var exactState = (path2, expected) => {
13504
14314
  throw new Error(`state mismatch: ${expected}`);
13505
14315
  };
13506
14316
  var nonEmpty = (path2) => {
13507
- if (!existsSync18(path2) || !statSync4(path2).isFile() || statSync4(path2).size < 1)
14317
+ if (!existsSync19(path2) || !statSync5(path2).isFile() || statSync5(path2).size < 1)
13508
14318
  throw new Error(`required file missing: ${path2}`);
13509
14319
  };
13510
14320
  var option = (args, name) => {
@@ -13548,29 +14358,29 @@ async function runRecoveryHost(args, run2 = execute2) {
13548
14358
  const slot = activeSlot();
13549
14359
  nonEmpty(join11("/opt/forgezero/slots", slot, "src/server/maintenance/restore-backup.ts"));
13550
14360
  nonEmpty(join11("/opt/forgezero/slots", slot, "src/server/maintenance/verify-recovery-cluster.ts"));
13551
- await checked7(run2, ["/usr/bin/test", "-x", "/usr/local/bin/fz"]);
13552
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], hasDatabase ? 0 : 3);
14361
+ await checked8(run2, ["/usr/bin/test", "-x", "/usr/local/bin/fz"]);
14362
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], hasDatabase ? 0 : 3);
13553
14363
  if (args.includes("--require-stopped")) {
13554
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
13555
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
14364
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
14365
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
13556
14366
  }
13557
14367
  return;
13558
14368
  }
13559
14369
  if (action === "fence") {
13560
- await checked7(run2, ["/usr/bin/systemctl", "stop", "forgezero@blue.service", "forgezero@green.service"]);
13561
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
13562
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
14370
+ await checked8(run2, ["/usr/bin/systemctl", "stop", "forgezero@blue.service", "forgezero@green.service"]);
14371
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
14372
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
13563
14373
  const role = option(args, "db-role");
13564
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], role === "none" ? 3 : 0);
14374
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], role === "none" ? 3 : 0);
13565
14375
  return;
13566
14376
  }
13567
14377
  if (action === "start-active") {
13568
14378
  const slot = activeSlot();
13569
14379
  const inactive = slot === "blue" ? "green" : "blue";
13570
- await checked7(run2, ["/usr/bin/systemctl", "stop", `forgezero@${inactive}.service`]);
13571
- await checked7(run2, ["/usr/bin/systemctl", "start", `forgezero@${slot}.service`]);
13572
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${slot}.service`]);
13573
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${inactive}.service`], 3);
14380
+ await checked8(run2, ["/usr/bin/systemctl", "stop", `forgezero@${inactive}.service`]);
14381
+ await checked8(run2, ["/usr/bin/systemctl", "start", `forgezero@${slot}.service`]);
14382
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${slot}.service`]);
14383
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${inactive}.service`], 3);
13574
14384
  return;
13575
14385
  }
13576
14386
  if (action === "maintenance") {
@@ -13593,7 +14403,7 @@ async function runRecoveryHost(args, run2 = execute2) {
13593
14403
  "--property=LoadCredentialEncrypted=backup-recovery-root:/etc/forgezero/creds/backup-recovery-root.cred"
13594
14404
  ] : []
13595
14405
  ];
13596
- await checked7(run2, ["/usr/bin/systemd-run", "--quiet", "--wait", "--pipe", "--collect", `--unit=${unit2(option(args, "unit"))}`, ...properties, "/usr/local/bin/fz", ...fzArgs]);
14406
+ await checked8(run2, ["/usr/bin/systemd-run", "--quiet", "--wait", "--pipe", "--collect", `--unit=${unit2(option(args, "unit"))}`, ...properties, "/usr/local/bin/fz", ...fzArgs]);
13597
14407
  return;
13598
14408
  }
13599
14409
  throw new Error("unknown recovery-host action");
@@ -13605,15 +14415,15 @@ import { lstatSync as lstatSync6 } from "fs";
13605
14415
  // src/bootstrap.ts
13606
14416
  import {
13607
14417
  chmodSync as chmodSync17,
13608
- existsSync as existsSync20,
14418
+ existsSync as existsSync21,
13609
14419
  lstatSync as lstatSync5,
13610
- mkdirSync as mkdirSync15,
14420
+ mkdirSync as mkdirSync16,
13611
14421
  readFileSync as readFileSync17,
13612
- renameSync as renameSync12,
14422
+ renameSync as renameSync13,
13613
14423
  rmSync as rmSync11,
13614
- writeFileSync as writeFileSync15
14424
+ writeFileSync as writeFileSync16
13615
14425
  } from "fs";
13616
- import { dirname as dirname10 } from "path";
14426
+ import { dirname as dirname12 } from "path";
13617
14427
  import { fileURLToPath as fileURLToPath2 } from "url";
13618
14428
 
13619
14429
  // src/provision.ts
@@ -14546,17 +15356,17 @@ import { randomBytes as randomBytes6 } from "crypto";
14546
15356
  import {
14547
15357
  chmodSync as chmodSync16,
14548
15358
  copyFileSync as copyFileSync3,
14549
- existsSync as existsSync19,
15359
+ existsSync as existsSync20,
14550
15360
  lstatSync as lstatSync4,
14551
- mkdirSync as mkdirSync14,
15361
+ mkdirSync as mkdirSync15,
14552
15362
  readFileSync as readFileSync16,
14553
15363
  realpathSync as realpathSync6,
14554
- renameSync as renameSync11,
15364
+ renameSync as renameSync12,
14555
15365
  rmSync as rmSync10,
14556
15366
  symlinkSync as symlinkSync5,
14557
- writeFileSync as writeFileSync14
15367
+ writeFileSync as writeFileSync15
14558
15368
  } from "fs";
14559
- import { dirname as dirname9 } from "path";
15369
+ import { dirname as dirname11 } from "path";
14560
15370
  async function readCapabilities(run2) {
14561
15371
  const answers = {};
14562
15372
  const checks = Object.entries(CAPABILITY_CHECKS);
@@ -14577,10 +15387,8 @@ var fixed = async (argv2, stdin) => {
14577
15387
  stderr: "pipe",
14578
15388
  env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C", DEBIAN_FRONTEND: "noninteractive" }
14579
15389
  });
14580
- if (stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
14581
- child.stdin.write(stdin);
14582
- child.stdin.end();
14583
- }
15390
+ if (stdin !== undefined && child.stdin && typeof child.stdin !== "number")
15391
+ await writeAndCloseProcessInput(child.stdin, stdin);
14584
15392
  const [stdout, stderr, exitCode] = await Promise.all([
14585
15393
  new Response(child.stdout).text(),
14586
15394
  new Response(child.stderr).text(),
@@ -14591,10 +15399,10 @@ var fixed = async (argv2, stdin) => {
14591
15399
  var runProvisionOperation = async (operation) => {
14592
15400
  if (operation.kind === "commands") {
14593
15401
  let output = "";
14594
- for (const command2 of operation.commands) {
14595
- const result = await fixed(command2.argv);
15402
+ for (const command3 of operation.commands) {
15403
+ const result = await fixed(command3.argv);
14596
15404
  output += result.stdout;
14597
- if (!(command2.acceptedExitCodes ?? [0]).includes(result.exitCode))
15405
+ if (!(command3.acceptedExitCodes ?? [0]).includes(result.exitCode))
14598
15406
  return { stdout: output, exitCode: result.exitCode };
14599
15407
  }
14600
15408
  return { stdout: output, exitCode: 0 };
@@ -14615,19 +15423,19 @@ var runProvisionOperation = async (operation) => {
14615
15423
  }
14616
15424
  if (operation.kind === "install-runtime") {
14617
15425
  const release = `/opt/forgezero/agent/versions/${operation.version}`;
14618
- mkdirSync14(`${release}/dist`, { recursive: true, mode: 493 });
14619
- mkdirSync14(dirname9(operation.binary), { recursive: true, mode: 493 });
15426
+ mkdirSync15(`${release}/dist`, { recursive: true, mode: 493 });
15427
+ mkdirSync15(dirname11(operation.binary), { recursive: true, mode: 493 });
14620
15428
  copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
14621
15429
  chmodSync16(`${release}/dist/fz-agent.js`, 493);
14622
- const gitSshSource = `${dirname9(operation.source)}/fz-git-ssh.js`;
14623
- if (!existsSync19(gitSshSource))
15430
+ const gitSshSource = `${dirname11(operation.source)}/fz-git-ssh.js`;
15431
+ if (!existsSync20(gitSshSource))
14624
15432
  return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
14625
15433
  copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
14626
15434
  chmodSync16(`${release}/dist/fz-git-ssh.js`, 493);
14627
15435
  const pending = "/opt/forgezero/agent/current.next";
14628
15436
  rmSync10(pending, { force: true });
14629
15437
  symlinkSync5(`versions/${operation.version}`, pending);
14630
- renameSync11(pending, "/opt/forgezero/agent/current");
15438
+ renameSync12(pending, "/opt/forgezero/agent/current");
14631
15439
  rmSync10(operation.binary, { force: true });
14632
15440
  symlinkSync5("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
14633
15441
  const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
@@ -14636,7 +15444,7 @@ var runProvisionOperation = async (operation) => {
14636
15444
  return { stdout: "", exitCode: 0 };
14637
15445
  }
14638
15446
  if (operation.kind === "ensure-seed") {
14639
- if (existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
15447
+ if (existsSync20(operation.credential) && lstatSync4(operation.credential).size > 0)
14640
15448
  return { stdout: "", exitCode: 0 };
14641
15449
  const seed = randomBytes6(32).toString("base64url");
14642
15450
  const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
@@ -14648,7 +15456,7 @@ var runProvisionOperation = async (operation) => {
14648
15456
  const key = "/run/forgezero-git-deploy-key";
14649
15457
  const publicKey = `${key}.pub`;
14650
15458
  try {
14651
- if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
15459
+ if (!existsSync20(operation.credential) || lstatSync4(operation.credential).size < 1) {
14652
15460
  rmSync10(key, { force: true });
14653
15461
  rmSync10(publicKey, { force: true });
14654
15462
  let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
@@ -14659,8 +15467,8 @@ var runProvisionOperation = async (operation) => {
14659
15467
  return result;
14660
15468
  chmodSync16(operation.credential, 256);
14661
15469
  }
14662
- if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
14663
- if (!existsSync19(key)) {
15470
+ if (!existsSync20(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
15471
+ if (!existsSync20(key)) {
14664
15472
  const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
14665
15473
  if (decrypted.exitCode !== 0)
14666
15474
  return decrypted;
@@ -14668,7 +15476,7 @@ var runProvisionOperation = async (operation) => {
14668
15476
  const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
14669
15477
  if (derived.exitCode !== 0)
14670
15478
  return derived;
14671
- writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
15479
+ writeFileSync15(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
14672
15480
  `, { mode: 292 });
14673
15481
  }
14674
15482
  return { stdout: "", exitCode: 0 };
@@ -14681,12 +15489,12 @@ var runProvisionOperation = async (operation) => {
14681
15489
  const key = "/run/forgezero-bootstrap-ssh-key";
14682
15490
  const generatedPublicKey = `${key}.pub`;
14683
15491
  try {
14684
- if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
15492
+ if (!existsSync20(operation.credential) || lstatSync4(operation.credential).size < 1) {
14685
15493
  rmSync10(key, { force: true });
14686
15494
  rmSync10(generatedPublicKey, { force: true });
14687
15495
  let result;
14688
15496
  if (operation.source) {
14689
- const source = existsSync19(operation.source) ? lstatSync4(operation.source) : undefined;
15497
+ const source = existsSync20(operation.source) ? lstatSync4(operation.source) : undefined;
14690
15498
  if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
14691
15499
  return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
14692
15500
  }
@@ -14706,8 +15514,8 @@ var runProvisionOperation = async (operation) => {
14706
15514
  return result;
14707
15515
  chmodSync16(operation.credential, 256);
14708
15516
  }
14709
- if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
14710
- if (!existsSync19(key)) {
15517
+ if (!existsSync20(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
15518
+ if (!existsSync20(key)) {
14711
15519
  const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
14712
15520
  if (decrypted.exitCode !== 0)
14713
15521
  return decrypted;
@@ -14717,8 +15525,8 @@ var runProvisionOperation = async (operation) => {
14717
15525
  if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
14718
15526
  return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
14719
15527
  }
14720
- mkdirSync14(dirname9(operation.publicKey), { recursive: true, mode: 493 });
14721
- writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
15528
+ mkdirSync15(dirname11(operation.publicKey), { recursive: true, mode: 493 });
15529
+ writeFileSync15(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
14722
15530
  `, { mode: 292 });
14723
15531
  chmodSync16(operation.publicKey, 292);
14724
15532
  }
@@ -14731,9 +15539,9 @@ var runProvisionOperation = async (operation) => {
14731
15539
  }
14732
15540
  }
14733
15541
  if (operation.kind === "ensure-enrolment") {
14734
- if (existsSync19(operation.state) && lstatSync4(operation.state).size > 0 || existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
15542
+ if (existsSync20(operation.state) && lstatSync4(operation.state).size > 0 || existsSync20(operation.credential) && lstatSync4(operation.credential).size > 0)
14735
15543
  return { stdout: "", exitCode: 0 };
14736
- if (!existsSync19(operation.source))
15544
+ if (!existsSync20(operation.source))
14737
15545
  return { stdout: "enrolment source is missing", exitCode: 1 };
14738
15546
  const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
14739
15547
  if (result.exitCode === 0) {
@@ -14753,7 +15561,7 @@ var runProvisionOperation = async (operation) => {
14753
15561
  return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
14754
15562
  }
14755
15563
  if (operation.kind === "verify-file")
14756
- return existsSync19(operation.path) && lstatSync4(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
15564
+ return existsSync20(operation.path) && lstatSync4(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
14757
15565
  if (operation.kind === "verify-egress") {
14758
15566
  const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
14759
15567
  if (active.exitCode !== 0)
@@ -14781,11 +15589,11 @@ var runProvisionOperation = async (operation) => {
14781
15589
  const response = await fetch("https://pkg.cloudflareclient.com/pubkey.gpg", { signal: AbortSignal.timeout(30000) });
14782
15590
  if (!response.ok)
14783
15591
  return { stdout: `WARP key HTTP ${response.status}`, exitCode: 1 };
14784
- mkdirSync14("/usr/share/keyrings", { recursive: true, mode: 493 });
14785
- mkdirSync14("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
14786
- mkdirSync14("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
15592
+ mkdirSync15("/usr/share/keyrings", { recursive: true, mode: 493 });
15593
+ mkdirSync15("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
15594
+ mkdirSync15("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
14787
15595
  const key = "/run/cloudflare-warp-key.gpg";
14788
- writeFileSync14(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
15596
+ writeFileSync15(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
14789
15597
  let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
14790
15598
  rmSync10(key, { force: true });
14791
15599
  if (result.exitCode !== 0)
@@ -14793,7 +15601,7 @@ var runProvisionOperation = async (operation) => {
14793
15601
  const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
14794
15602
  if (!codename)
14795
15603
  return { stdout: "Ubuntu codename missing", exitCode: 1 };
14796
- 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
15604
+ writeFileSync15("/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
14797
15605
  `, { mode: 420 });
14798
15606
  result = await fixed(["/usr/bin/apt-get", "update", "-qq"]);
14799
15607
  return result.exitCode === 0 ? fixed(["/usr/bin/apt-get", "install", "-y", "cloudflare-warp"]) : result;
@@ -15095,8 +15903,7 @@ function localBootstrapHost() {
15095
15903
  const execute3 = async (argv2, options = {}) => {
15096
15904
  const child = Bun.spawn([...argv2], { stdin: options.stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
15097
15905
  if (options.stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
15098
- child.stdin.write(options.stdin);
15099
- child.stdin.end();
15906
+ await writeAndCloseProcessInput(child.stdin, options.stdin);
15100
15907
  }
15101
15908
  const [stdout, stderr, exitCode] = await Promise.all([
15102
15909
  new Response(child.stdout).text(),
@@ -15107,16 +15914,16 @@ function localBootstrapHost() {
15107
15914
  };
15108
15915
  return {
15109
15916
  uid: () => process.getuid?.() ?? -1,
15110
- exists: existsSync20,
15917
+ exists: existsSync21,
15111
15918
  read: (path2) => readFileSync17(path2, "utf8"),
15112
15919
  write(path2, content, mode) {
15113
- mkdirSync15(dirname10(path2), { recursive: true, mode: 493 });
15920
+ mkdirSync16(dirname12(path2), { recursive: true, mode: 493 });
15114
15921
  const temporary = `${path2}.next.${process.pid}`;
15115
- writeFileSync15(temporary, content, { mode });
15922
+ writeFileSync16(temporary, content, { mode });
15116
15923
  chmodSync17(temporary, mode);
15117
- renameSync12(temporary, path2);
15924
+ renameSync13(temporary, path2);
15118
15925
  },
15119
- mkdir: (path2, mode) => mkdirSync15(path2, { recursive: true, mode }),
15926
+ mkdir: (path2, mode) => mkdirSync16(path2, { recursive: true, mode }),
15120
15927
  remove: (path2) => rmSync11(path2, { force: true }),
15121
15928
  inspect(path2) {
15122
15929
  const value = lstatSync5(path2);
@@ -15140,9 +15947,9 @@ function localBootstrapHost() {
15140
15947
  },
15141
15948
  async installAgent(config, phase) {
15142
15949
  const capabilities = await readCapabilities(localRunner);
15143
- const hasBinding = existsSync20("/var/lib/forgezero/enrolment.json");
15144
- const hasEnrolCredential = existsSync20(ENROL_CREDENTIAL);
15145
- const initialBundle = config.kind === "platform" && !existsSync20(BOOTSTRAP_RELEASE_EVIDENCE) ? {
15950
+ const hasBinding = existsSync21("/var/lib/forgezero/enrolment.json");
15951
+ const hasEnrolCredential = existsSync21(ENROL_CREDENTIAL);
15952
+ const initialBundle = config.kind === "platform" && !existsSync21(BOOTSTRAP_RELEASE_EVIDENCE) ? {
15146
15953
  path: config.bootstrapBundle.bundleFile,
15147
15954
  manifestPath: config.bootstrapBundle.manifestFile,
15148
15955
  manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync17(config.bootstrapBundle.manifestFile, "utf8")))
@@ -15158,8 +15965,8 @@ function localBootstrapHost() {
15158
15965
  databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
15159
15966
  databasePorts: [8529]
15160
15967
  };
15161
- mkdirSync15(dirname10(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
15162
- writeFileSync15(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
15968
+ mkdirSync16(dirname12(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
15969
+ writeFileSync16(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
15163
15970
  `, { mode: 256 });
15164
15971
  }
15165
15972
  const plan = planBootstrapAgentInstall(config, phase, {
@@ -15169,8 +15976,8 @@ function localBootstrapHost() {
15169
15976
  initialBundle
15170
15977
  });
15171
15978
  for (const unit3 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
15172
- mkdirSync15(dirname10(unit3.path), { recursive: true, mode: 493 });
15173
- writeFileSync15(unit3.path, unit3.unit, { mode: 420 });
15979
+ mkdirSync16(dirname12(unit3.path), { recursive: true, mode: 493 });
15980
+ writeFileSync16(unit3.path, unit3.unit, { mode: 420 });
15174
15981
  }
15175
15982
  await applyPlan(plan, localRunner);
15176
15983
  return plan;
@@ -15222,10 +16029,10 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
15222
16029
  }
15223
16030
 
15224
16031
  // src/community-rehearsal-host.ts
15225
- import { createHash as createHash11 } from "crypto";
15226
- import { lstatSync as lstatSync7, mkdirSync as mkdirSync16, readFileSync as readFileSync18, rmSync as rmSync12, symlinkSync as symlinkSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
16032
+ import { createHash as createHash12 } from "crypto";
16033
+ import { lstatSync as lstatSync7, mkdirSync as mkdirSync17, readFileSync as readFileSync18, rmSync as rmSync12, symlinkSync as symlinkSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync17 } from "fs";
15227
16034
  import { isIP as isIP5 } from "net";
15228
- import { dirname as dirname11 } from "path";
16035
+ import { dirname as dirname13 } from "path";
15229
16036
  var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
15230
16037
  var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
15231
16038
  var COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
@@ -15427,10 +16234,8 @@ function planCommunityRehearsalCleanup() {
15427
16234
  }
15428
16235
  async function exec(argv2, stdin) {
15429
16236
  const child = Bun.spawn([...argv2], { stdin: stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
15430
- if (stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
15431
- child.stdin.write(stdin);
15432
- child.stdin.end();
15433
- }
16237
+ if (stdin !== undefined && child.stdin && typeof child.stdin !== "number")
16238
+ await writeAndCloseProcessInput(child.stdin, stdin);
15434
16239
  const [stdout, stderr, exitCode] = await Promise.all([
15435
16240
  new Response(child.stdout).text(),
15436
16241
  new Response(child.stderr).text(),
@@ -15443,8 +16248,8 @@ async function applyOperations(operations) {
15443
16248
  if (operation.kind === "remove-tree")
15444
16249
  rmSync12(operation.path, { recursive: true, force: true });
15445
16250
  else if (operation.kind === "write") {
15446
- mkdirSync16(dirname11(operation.path), { recursive: true });
15447
- writeFileSync16(operation.path, operation.content, { mode: operation.mode });
16251
+ mkdirSync17(dirname13(operation.path), { recursive: true });
16252
+ writeFileSync17(operation.path, operation.content, { mode: operation.mode });
15448
16253
  } else if (operation.kind === "unlink") {
15449
16254
  try {
15450
16255
  unlinkSync12(operation.path);
@@ -15496,7 +16301,7 @@ async function runCommunityRehearsalHost(request) {
15496
16301
  }
15497
16302
  if (request.action === "prepare") {
15498
16303
  const metadata = lstatSync7(request.archivePath);
15499
- if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash11("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
16304
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash12("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
15500
16305
  throw new Error("community rehearsal archive is not the declared bounded release");
15501
16306
  }
15502
16307
  await ensureCommunityRehearsalArango();
@@ -15590,23 +16395,23 @@ async function runSupervisedApp(path2) {
15590
16395
  }
15591
16396
 
15592
16397
  // src/index.ts
15593
- function agentCommandArgs(args, command2) {
15594
- const index = args.indexOf(command2);
16398
+ function agentCommandArgs(args, command3) {
16399
+ const index = args.indexOf(command3);
15595
16400
  if (index < 0)
15596
- throw new Error(`missing Agent command ${command2}`);
16401
+ throw new Error(`missing Agent command ${command3}`);
15597
16402
  return args.slice(index + 1);
15598
16403
  }
15599
16404
  function loadOrCreateSeed(path2) {
15600
- if (existsSync21(path2)) {
16405
+ if (existsSync22(path2)) {
15601
16406
  const seed2 = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
15602
16407
  if (seed2.length < 32) {
15603
16408
  throw new Error(`agent: the seed at ${path2} is too short to derive a key from.`);
15604
16409
  }
15605
16410
  return seed2;
15606
16411
  }
15607
- mkdirSync17(dirname12(path2), { recursive: true });
16412
+ mkdirSync18(dirname14(path2), { recursive: true });
15608
16413
  const seed = new Uint8Array(randomBytes7(32));
15609
- writeFileSync17(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
16414
+ writeFileSync18(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
15610
16415
  chmodSync18(path2, 384);
15611
16416
  return seed;
15612
16417
  }
@@ -15636,7 +16441,7 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
15636
16441
  if (!directory)
15637
16442
  throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the node identity.");
15638
16443
  const path2 = `${directory}/${name}`;
15639
- if (!existsSync21(path2))
16444
+ if (!existsSync22(path2))
15640
16445
  throw new Error(`agent: the systemd credential ${name} is missing at ${path2}.`);
15641
16446
  const seed = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
15642
16447
  if (seed.length < 32)
@@ -15658,7 +16463,7 @@ function createSystemdDeploymentSecrets(names, directory = process.env.CREDENTIA
15658
16463
  if (allowed.size === 0)
15659
16464
  return;
15660
16465
  for (const name of allowed) {
15661
- if (!/^[A-Z_][A-Z0-9_]*$/.test(name))
16466
+ if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/.test(name))
15662
16467
  throw new Error(`agent: invalid pipeline credential name ${name}.`);
15663
16468
  }
15664
16469
  return {
@@ -15764,8 +16569,8 @@ if (import.meta.main) {
15764
16569
  console.log(VERSION2);
15765
16570
  process.exit(0);
15766
16571
  }
15767
- const command2 = args.find((arg) => !arg.startsWith("-"));
15768
- if (command2 === "platform-activate") {
16572
+ const command3 = args.find((arg) => !arg.startsWith("-"));
16573
+ if (command3 === "platform-activate") {
15769
16574
  if (process.getuid?.() !== 0)
15770
16575
  throw new Error("platform-activate must run as root");
15771
16576
  const configPath = args.find((arg) => arg.startsWith("--config="))?.slice("--config=".length);
@@ -15778,14 +16583,14 @@ if (import.meta.main) {
15778
16583
  console.log(`promoted ${result.release} on ${result.slot}`);
15779
16584
  process.exit(0);
15780
16585
  }
15781
- if (command2 === "recovery-host") {
16586
+ if (command3 === "recovery-host") {
15782
16587
  if (process.getuid?.() !== 0)
15783
16588
  throw new Error("recovery-host must run as root");
15784
16589
  const commandIndex = args.indexOf("recovery-host");
15785
16590
  await runRecoveryHost(args.slice(commandIndex + 1));
15786
16591
  process.exit(0);
15787
16592
  }
15788
- if (command2 === "pipeline-todo") {
16593
+ if (command3 === "pipeline-todo") {
15789
16594
  const index = args.indexOf("pipeline-todo");
15790
16595
  const message = args[index + 1];
15791
16596
  if (!message || args.length !== index + 2 || message.length > 2000 || /[\r\n\0]/.test(message)) {
@@ -15794,7 +16599,7 @@ if (import.meta.main) {
15794
16599
  console.error(message);
15795
16600
  process.exit(78);
15796
16601
  }
15797
- if (command2 === "project-release-check") {
16602
+ if (command3 === "project-release-check") {
15798
16603
  if (args.length !== 1)
15799
16604
  throw new Error("project-release-check accepts no coordinates");
15800
16605
  for (const relative of ["src/index.ts", "bun.lock", "src/generated/shared/contract-manifest.json"]) {
@@ -15813,7 +16618,7 @@ if (import.meta.main) {
15813
16618
  process.exit(0);
15814
16619
  }
15815
16620
  const allowFileSeed = process.env.FZ_ALLOW_FILE_SEED === "1";
15816
- if (command2 === "identity") {
16621
+ if (command3 === "identity") {
15817
16622
  const seed = configuredAgentSeed({
15818
16623
  credential: process.env.FZ_SEED_CREDENTIAL,
15819
16624
  path: process.env.FZ_SEED_PATH,
@@ -15827,7 +16632,7 @@ if (import.meta.main) {
15827
16632
  }, null, 2));
15828
16633
  process.exit(0);
15829
16634
  }
15830
- if (command2 === "enrol") {
16635
+ if (command3 === "enrol") {
15831
16636
  if (!process.env.FZ_API)
15832
16637
  throw new Error("agent enrolment requires FZ_API");
15833
16638
  if (!process.env.FZ_ENROL_TOKEN_CREDENTIAL) {
@@ -15856,7 +16661,7 @@ if (import.meta.main) {
15856
16661
  console.log(`[agent] enrolled ${binding2.computeReference} in project ${binding2.projectKey}/${binding2.environmentKey}`);
15857
16662
  process.exit(0);
15858
16663
  }
15859
- if (command2 === "egress-policy" || command2 === "egress-policy-check") {
16664
+ if (command3 === "egress-policy" || command3 === "egress-policy-check") {
15860
16665
  const users = args.filter((arg) => arg.startsWith("--user=")).map((arg) => arg.slice("--user=".length));
15861
16666
  if (users.length < 1)
15862
16667
  throw new Error("egress-policy requires --user=<service-user>");
@@ -15875,7 +16680,7 @@ if (import.meta.main) {
15875
16680
  tcpPorts: loopbackTcpPorts,
15876
16681
  publicTcpPorts: publicTcpPorts.length > 0 ? publicTcpPorts : undefined
15877
16682
  } : undefined;
15878
- if (command2 === "egress-policy-check") {
16683
+ if (command3 === "egress-policy-check") {
15879
16684
  const uids = users.map((user) => resolveServiceUid(user));
15880
16685
  const grant = loopback ? {
15881
16686
  uid: resolveServiceUid(loopback.user),
@@ -15888,7 +16693,7 @@ if (import.meta.main) {
15888
16693
  }
15889
16694
  await superviseAgentEgressPolicy({ users, loopback });
15890
16695
  }
15891
- if (command2 === "metal-helper") {
16696
+ if (command3 === "metal-helper") {
15892
16697
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
15893
16698
  if (!profilePath)
15894
16699
  throw new Error("metal-helper requires --profile=/absolute/path.json");
@@ -15910,7 +16715,7 @@ if (import.meta.main) {
15910
16715
  process.on("SIGINT", () => void stop());
15911
16716
  await new Promise(() => {});
15912
16717
  }
15913
- if (command2 === "lifecycle-helper") {
16718
+ if (command3 === "lifecycle-helper") {
15914
16719
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
15915
16720
  if (!profilePath?.startsWith("/"))
15916
16721
  throw new Error("lifecycle-helper requires --profile=/absolute/path.json");
@@ -15934,7 +16739,7 @@ if (import.meta.main) {
15934
16739
  process.on("SIGINT", () => void stop());
15935
16740
  await new Promise(() => {});
15936
16741
  }
15937
- if (command2 === "warp-config") {
16742
+ if (command3 === "warp-config") {
15938
16743
  const organization = args.find((arg) => arg.startsWith("--organization="))?.slice("--organization=".length);
15939
16744
  if (!organization)
15940
16745
  throw new Error("warp-config requires --organization=<team-name>");
@@ -15952,7 +16757,7 @@ if (import.meta.main) {
15952
16757
  console.log("[warp-config] enrollment materialized in tmpfs");
15953
16758
  process.exit(0);
15954
16759
  }
15955
- if (command2 === "mesh-config") {
16760
+ if (command3 === "mesh-config") {
15956
16761
  if (typeof process.getuid !== "function" || process.getuid() !== 0)
15957
16762
  throw new Error("mesh-config must run as root");
15958
16763
  const token = readMeshConnectorCredential();
@@ -15975,7 +16780,7 @@ if (import.meta.main) {
15975
16780
  console.log("[mesh-config] Cloudflare Mesh connector is connected");
15976
16781
  process.exit(0);
15977
16782
  }
15978
- if (command2 === "update-helper") {
16783
+ if (command3 === "update-helper") {
15979
16784
  if (typeof process.getuid !== "function" || process.getuid() !== 0) {
15980
16785
  throw new Error("update-helper must run as root");
15981
16786
  }
@@ -15994,7 +16799,7 @@ if (import.meta.main) {
15994
16799
  process.on("SIGINT", () => void stop());
15995
16800
  await new Promise(() => {});
15996
16801
  }
15997
- if (command2 === "software-helper") {
16802
+ if (command3 === "software-helper") {
15998
16803
  if (typeof process.getuid !== "function" || process.getuid() !== 0) {
15999
16804
  throw new Error("software-helper must run as root");
16000
16805
  }
@@ -16016,13 +16821,13 @@ if (import.meta.main) {
16016
16821
  process.on("SIGINT", () => void stop());
16017
16822
  await new Promise(() => {});
16018
16823
  }
16019
- if (command2 === "supervised-app") {
16824
+ if (command3 === "supervised-app") {
16020
16825
  const config = args.find((arg) => arg.startsWith("--config="))?.slice("--config=".length);
16021
16826
  if (!config)
16022
16827
  throw new Error("supervised-app requires --config=<absolute-path>");
16023
16828
  process.exit(await runSupervisedApp(config));
16024
16829
  }
16025
- if (command2 === "software-ensure") {
16830
+ if (command3 === "software-ensure") {
16026
16831
  const requirements = args.filter((arg) => arg.startsWith("--require=")).map((arg) => {
16027
16832
  const coordinate = arg.slice("--require=".length);
16028
16833
  const separator = coordinate.indexOf("@");
@@ -16038,7 +16843,7 @@ if (import.meta.main) {
16038
16843
  console.log(JSON.stringify({ ok: true, results }));
16039
16844
  process.exit(0);
16040
16845
  }
16041
- if (command2 === "metal-isolation") {
16846
+ if (command3 === "metal-isolation") {
16042
16847
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
16043
16848
  if (!profilePath)
16044
16849
  throw new Error("metal-isolation requires --profile=/absolute/path.json");
@@ -16047,7 +16852,7 @@ if (import.meta.main) {
16047
16852
  console.log("[metal-isolation] host and guest cgroup boundaries active");
16048
16853
  process.exit(0);
16049
16854
  }
16050
- if (command2 === "deploy-runner") {
16855
+ if (command3 === "deploy-runner") {
16051
16856
  const root2 = args.find((arg) => arg.startsWith("--root="))?.slice("--root=".length);
16052
16857
  const home = args.find((arg) => arg.startsWith("--home="))?.slice("--home=".length);
16053
16858
  if (!root2 || !home)
@@ -16077,7 +16882,7 @@ if (import.meta.main) {
16077
16882
  process.on("SIGINT", () => void stop());
16078
16883
  await new Promise(() => {});
16079
16884
  }
16080
- if (command2 === "metal-apply") {
16885
+ if (command3 === "metal-apply") {
16081
16886
  const claimArg = args.find((arg) => arg.startsWith("--claim="))?.slice("--claim=".length);
16082
16887
  if (!claimArg)
16083
16888
  throw new Error("metal-apply requires --claim=- or --claim=/absolute/path.json");
@@ -16098,8 +16903,8 @@ if (import.meta.main) {
16098
16903
  console.log(JSON.stringify({ ok: true, computeKey: claim.computeKey, ...result }));
16099
16904
  process.exit(0);
16100
16905
  }
16101
- if (command2 === "platform-fleet-verify") {
16102
- const commandArgs = agentCommandArgs(args, command2);
16906
+ if (command3 === "platform-fleet-verify") {
16907
+ const commandArgs = agentCommandArgs(args, command3);
16103
16908
  const expectedVersion = commandArgs.find((arg) => arg.startsWith("--expected-version="))?.slice("--expected-version=".length);
16104
16909
  if (!expectedVersion || commandArgs.length !== 1) {
16105
16910
  throw new Error("platform-fleet-verify requires exactly --expected-version=<semver>");
@@ -16107,8 +16912,8 @@ if (import.meta.main) {
16107
16912
  console.log(JSON.stringify(await verifyPlatformFleetHost(expectedVersion)));
16108
16913
  process.exit(0);
16109
16914
  }
16110
- if (command2 === "community-rehearsal") {
16111
- const commandArgs = agentCommandArgs(args, command2);
16915
+ if (command3 === "community-rehearsal") {
16916
+ const commandArgs = agentCommandArgs(args, command3);
16112
16917
  if (commandArgs.length !== 1 || commandArgs[0] !== "--request=-") {
16113
16918
  throw new Error("community-rehearsal requires exactly --request=-");
16114
16919
  }
@@ -16119,7 +16924,7 @@ if (import.meta.main) {
16119
16924
  console.log(JSON.stringify(await runCommunityRehearsalHost(request)));
16120
16925
  process.exit(0);
16121
16926
  }
16122
- if (command2 === "metal-admission-proof") {
16927
+ if (command3 === "metal-admission-proof") {
16123
16928
  if (args.length !== 1 || args[0] !== "--request=-") {
16124
16929
  throw new Error("metal-admission-proof requires exactly --request=-");
16125
16930
  }
@@ -16133,17 +16938,17 @@ if (import.meta.main) {
16133
16938
  const seed = configuredAgentSeed({ credential: process.env.FZ_SEED_CREDENTIAL, allowFileSeed: false });
16134
16939
  const keys2 = deriveKeysFromSeed(seed);
16135
16940
  seed.fill(0);
16136
- const filesystem = statfsSync2("/var/lib/forgezero");
16941
+ const filesystem = statfsSync3("/var/lib/forgezero");
16137
16942
  const body = {
16138
16943
  ...request,
16139
16944
  publicKeys: { ed25519: keys2.ed25519.publicKey, mlDsa: keys2.mlDsa.publicKey },
16140
16945
  preflight: {
16141
- vcpu: cpus().length,
16142
- memoryGib: Math.max(1, Math.floor(totalmem() / 1024 ** 3)),
16946
+ vcpu: cpus2().length,
16947
+ memoryGib: Math.max(1, Math.floor(totalmem2() / 1024 ** 3)),
16143
16948
  diskGib: Math.max(1, Math.floor(Number(filesystem.blocks) * Number(filesystem.bsize) / 1024 ** 3)),
16144
- kvm: existsSync21("/dev/kvm"),
16145
- snpHost: existsSync21("/dev/sev"),
16146
- helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16949
+ kvm: existsSync22("/dev/kvm"),
16950
+ snpHost: existsSync22("/dev/sev"),
16951
+ helper: existsSync22(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16147
16952
  }
16148
16953
  };
16149
16954
  const envelope = signRequest(keys2, keys2.ed25519.publicKey, {
@@ -16178,9 +16983,9 @@ if (import.meta.main) {
16178
16983
  metalHostname: process.env.FZ_METAL_HOSTNAME,
16179
16984
  run: (claim) => requestMetalProvision(claim, process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET),
16180
16985
  metalPreflight: () => ({
16181
- snpHost: existsSync21("/dev/sev"),
16182
- kvm: existsSync21("/dev/kvm"),
16183
- helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16986
+ snpHost: existsSync22("/dev/sev"),
16987
+ kvm: existsSync22("/dev/kvm"),
16988
+ helper: existsSync22(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16184
16989
  }),
16185
16990
  onEvent: (event, detail) => console.log(`[metal-agent] ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
16186
16991
  });
@@ -16235,22 +17040,22 @@ if (import.meta.main) {
16235
17040
  process.on("SIGINT", () => void stop("SIGINT"));
16236
17041
  await new Promise(() => {});
16237
17042
  }
16238
- if (command2 && ["deploy", "status", "pause", "resume", "pause-key", "resume-key", "stop-key", "start-key", "cancel"].includes(command2)) {
17043
+ if (command3 && ["deploy", "status", "pause", "resume", "pause-key", "resume-key", "stop-key", "start-key", "cancel"].includes(command3)) {
16239
17044
  const revisionArg = args.find((arg) => arg.startsWith("--revision="));
16240
17045
  const socketArg = args.find((arg) => arg.startsWith("--control-socket="));
16241
17046
  const keyArg = args.find((arg) => arg.startsWith("--key="));
16242
17047
  const idArg = args.find((arg) => arg.startsWith("--id="));
16243
17048
  const socketPath = socketArg?.slice("--control-socket=".length) ?? process.env.FZ_CONTROL_SOCKET ?? DEFAULT_CONTROL_SOCKET;
16244
- const request = command2 === "deploy" ? {
17049
+ const request = command3 === "deploy" ? {
16245
17050
  op: "deploy",
16246
17051
  request: {
16247
17052
  revision: revisionArg?.slice("--revision=".length),
16248
17053
  releaseExecutor: args.includes("--release-executor")
16249
17054
  }
16250
- } : command2 === "cancel" ? { op: "cancel", id: idArg?.slice("--id=".length) ?? "" } : ["pause-key", "resume-key", "stop-key", "start-key"].includes(command2) ? {
16251
- op: command2,
17055
+ } : command3 === "cancel" ? { op: "cancel", id: idArg?.slice("--id=".length) ?? "" } : ["pause-key", "resume-key", "stop-key", "start-key"].includes(command3) ? {
17056
+ op: command3,
16252
17057
  key: keyArg?.slice("--key=".length) ?? ""
16253
- } : { op: command2 };
17058
+ } : { op: command3 };
16254
17059
  try {
16255
17060
  const response = await requestControl(request, socketPath);
16256
17061
  console.log(JSON.stringify(response, null, 2));
@@ -16263,7 +17068,7 @@ if (import.meta.main) {
16263
17068
  const telemetry = new AgentTelemetryRuntime(createAgentTelemetry(resolveAgentTelemetryConfig()));
16264
17069
  telemetry.event("agent.started");
16265
17070
  telemetry.setDraining(false);
16266
- const attestationSource = existsSync21("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
17071
+ const attestationSource = existsSync22("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
16267
17072
  const running = runAgent({
16268
17073
  socketPath: process.env.FZ_SOCKET_PATH ?? DEFAULT_SOCKET_PATH,
16269
17074
  seedCredential: process.env.FZ_SEED_CREDENTIAL,
@@ -16278,8 +17083,8 @@ if (import.meta.main) {
16278
17083
  const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
16279
17084
  let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
16280
17085
  const enrolmentCredential = process.env.FZ_ENROL_TOKEN_CREDENTIAL;
16281
- const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync21(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
16282
- const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync21(process.env.FZ_ENROL_TOKEN_FILE));
17086
+ const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync22(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
17087
+ const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync22(process.env.FZ_ENROL_TOKEN_FILE));
16283
17088
  if (!binding && (enrolmentCredentialAvailable || legacyEnrolmentFileAvailable) && process.env.FZ_API) {
16284
17089
  binding = await enrolGuestIdentity({
16285
17090
  apiUrl: process.env.FZ_API,
@@ -16357,7 +17162,7 @@ if (import.meta.main) {
16357
17162
  const bootstrapEnabled = process.env.FZ_BOOTSTRAP_PULL === "true";
16358
17163
  const bootstrapCredential = process.env.FZ_BOOTSTRAP_SSH_KEY_CREDENTIAL;
16359
17164
  const bootstrapKeyPath = bootstrapCredential && process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/${bootstrapCredential}` : undefined;
16360
- if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync21(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
17165
+ if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync22(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
16361
17166
  throw new Error("agent: bootstrap runner requires enrolment, API, local SSH credential and target OTLP coordinate");
16362
17167
  }
16363
17168
  const bootstrapPull = bootstrapEnabled ? startSshBootstrapPull({
@@ -16433,6 +17238,7 @@ if (import.meta.main) {
16433
17238
  activateService: (request) => requestServiceActivation(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
16434
17239
  buildContainer: (request) => requestContainerBuild(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
16435
17240
  activateContainer: (request) => requestContainerActivation(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
17241
+ applyConnectivity: (request) => requestDeploymentConnectivity(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
16436
17242
  projectExec: process.env.FZ_DEPLOY_RUNNER_SOCKET ? (input) => requestDeploymentCommand(input, process.env.FZ_DEPLOY_RUNNER_SOCKET) : undefined
16437
17243
  });
16438
17244
  const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) : bootstrapBundle && branch && profile ? createDeploymentManager({
@@ -16650,6 +17456,8 @@ export {
16650
17456
  selectAgentRelease,
16651
17457
  runAgent,
16652
17458
  restoreAgentRelease,
17459
+ resolveDeploymentTargets,
17460
+ resolveDeploymentInputs,
16653
17461
  resolveAgentTelemetryConfig,
16654
17462
  requestSoftware,
16655
17463
  requestMetalProvision,
@@ -16727,6 +17535,7 @@ export {
16727
17535
  OS_CATALOG,
16728
17536
  METAL_SYSTEMD_CREDENTIALS,
16729
17537
  MAX_AGENT_TARBALL_BYTES,
17538
+ DeploymentInputError,
16730
17539
  DeploymentError,
16731
17540
  DEFAULT_SOFTWARE_HELPER_SOCKET,
16732
17541
  DEFAULT_SOCKET_PATH,