@forgezero/agent 0.1.74 → 0.1.75

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 (42) 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 +1 -1
  5. package/dist/community-rehearsal-host.js +208 -9
  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 +148 -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 +1111 -301
  25. package/dist/fz.js +220 -15
  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 +1 -1
  31. package/dist/platform-bootstrap-runtime.js +209 -10
  32. package/dist/platform-fleet-verification.js +515 -149
  33. package/dist/platform-genesis.js +206 -7
  34. package/dist/provision.js +465 -99
  35. package/dist/service-supervisor.d.ts +6 -0
  36. package/dist/software-helper.d.ts +3 -0
  37. package/dist/software-helper.js +465 -98
  38. package/dist/software.d.ts +4 -1
  39. package/dist/software.js +210 -8
  40. package/dist/ubuntu.js +206 -7
  41. package/dist/version.d.ts +1 -1
  42. package/package.json +12 -4
@@ -682,22 +682,30 @@ import {
682
682
  accessSync,
683
683
  chmodSync as chmodSync3,
684
684
  copyFileSync,
685
+ createReadStream,
685
686
  existsSync as existsSync3,
686
687
  mkdtempSync,
687
688
  mkdirSync as mkdirSync3,
688
689
  readFileSync as readFileSync3,
689
690
  renameSync as renameSync3,
690
691
  rmSync as rmSync3,
692
+ statSync,
691
693
  symlinkSync as symlinkSync2,
692
694
  unlinkSync as unlinkSync2,
693
695
  writeFileSync as writeFileSync3
694
696
  } from "node:fs";
695
697
  import { tmpdir } from "node:os";
696
- import { join as join3 } from "node:path";
698
+ import { dirname as dirname3, join as join3 } from "node:path";
697
699
  var PINNED_BUN_VERSION = "1.3.14";
698
700
  var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
699
701
  var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
700
702
  var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
703
+ var NERDCTL_VERSION = "2.3.5";
704
+ var BUILDKIT_VERSION = "0.32.2";
705
+ var KATA_CONTAINERS_VERSION = "4.0.0";
706
+ var NERDCTL_SHA256 = "de3206aeb7cbd5f20f5fb1f55c1e3bf2db1be567812a8a3f5e65eba2488347ee";
707
+ var BUILDKIT_SHA256 = "2975d0f651ad96ba8b80b9992ae1f9a964f4408569af5b6dc36544165c3926af";
708
+ var KATA_SHA256 = "2c3b9dfeba355582b40aee462b12916c9740654d0230f696adf719d67b063a8c";
701
709
  var OS_CATALOG = [
702
710
  {
703
711
  id: "ubuntu",
@@ -727,6 +735,8 @@ var OS_CATALOG = [
727
735
  var SOFTWARE_CATALOG = [
728
736
  { id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
729
737
  { id: "docker", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
738
+ { id: "containerd", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
739
+ { id: "kata-containers", version: "4.0.0", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
730
740
  { id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
731
741
  { id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
732
742
  { id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -735,6 +745,7 @@ var SOFTWARE_CATALOG = [
735
745
  { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
736
746
  { id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
737
747
  { id: "docker", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
748
+ { id: "containerd", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
738
749
  { id: "nginx", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
739
750
  { id: "ufw", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
740
751
  { id: "openssh-client", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -743,6 +754,8 @@ var SOFTWARE_CATALOG = [
743
754
  var UBUNTU_2604_X64 = [
744
755
  { requirement: { id: "bun", version: "1.3.14" } },
745
756
  { requirement: { id: "docker", version: "ubuntu-26.04" } },
757
+ { requirement: { id: "containerd", version: "ubuntu-26.04" } },
758
+ { requirement: { id: "kata-containers", version: "4.0.0" } },
746
759
  { requirement: { id: "nginx", version: "ubuntu-26.04" } },
747
760
  { requirement: { id: "arangodb", version: "3.11.14" } },
748
761
  { requirement: { id: "cloudflared", version: "2026.7.3" } },
@@ -754,6 +767,7 @@ var UBUNTU_2604_X64 = [
754
767
  var UBUNTU_2404_X64 = [
755
768
  { requirement: { id: "bun", version: "1.3.14" } },
756
769
  { requirement: { id: "docker", version: "ubuntu-24.04" } },
770
+ { requirement: { id: "containerd", version: "ubuntu-24.04" } },
757
771
  { requirement: { id: "nginx", version: "ubuntu-24.04" } },
758
772
  { requirement: { id: "ufw", version: "ubuntu-24.04" } },
759
773
  { requirement: { id: "openssh-client", version: "ubuntu-24.04" } },
@@ -770,6 +784,46 @@ var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
770
784
  "log-opts": { "max-size": "10m", "max-file": "3" }
771
785
  }, null, 2)}
772
786
  `;
787
+ var CONTAINERD_CONFIG_PATH = "/etc/containerd/config.toml";
788
+ var KATA_SNP_CONFIG_PATH = "/opt/kata/share/defaults/kata-containers/configuration-qemu-snp.toml";
789
+ var KATA_ACTIVE_CONFIG_PATH = "/etc/kata-containers/configuration.toml";
790
+ var CONTAINERD_CONFIG = (kata) => `version = 2
791
+ root = "/var/lib/forgezero/containerd"
792
+ state = "/run/forgezero/containerd"
793
+
794
+ [grpc]
795
+ address = "/run/containerd/containerd.sock"
796
+
797
+ [plugins."io.containerd.grpc.v1.cri".containerd]
798
+ snapshotter = "overlayfs"
799
+ default_runtime_name = "runc"
800
+
801
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
802
+ runtime_type = "io.containerd.runc.v2"
803
+ ${kata ? `
804
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp]
805
+ runtime_type = "io.containerd.kata.v2"
806
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp.options]
807
+ ConfigPath = "${KATA_ACTIVE_CONFIG_PATH}"
808
+ ` : ""}`;
809
+ var BUILDKIT_UNIT = `[Unit]
810
+ Description=ForgeZero BuildKit daemon
811
+ After=network-online.target containerd.service
812
+ Wants=network-online.target
813
+ Requires=containerd.service
814
+
815
+ [Service]
816
+ Type=notify
817
+ ExecStart=/usr/local/bin/buildkitd --addr unix:///run/buildkit/buildkitd.sock --root /var/lib/forgezero/buildkit
818
+ Restart=on-failure
819
+ RestartSec=2
820
+ NoNewPrivileges=yes
821
+ ProtectSystem=strict
822
+ ReadWritePaths=/var/lib/forgezero/buildkit /run/buildkit
823
+
824
+ [Install]
825
+ WantedBy=multi-user.target
826
+ `;
773
827
  var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
774
828
  var runSoftwareCommand = async (argv, env = {}) => {
775
829
  let child;
@@ -789,14 +843,28 @@ var runSoftwareCommand = async (argv, env = {}) => {
789
843
  return { exitCode, output: `${stdout}${stderr}` };
790
844
  };
791
845
  var run = runSoftwareCommand;
792
- var download = async (url, destination, sha256) => {
793
- const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
846
+ var download = async (url, destination, sha256, maximumBytes = 512 * 1024 * 1024) => {
847
+ const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(30 * 60000) });
794
848
  if (!response.ok)
795
849
  throw new Error(`download failed with HTTP ${response.status}`);
796
- const bytes = new Uint8Array(await response.arrayBuffer());
797
- if (createHash2("sha256").update(bytes).digest("hex") !== sha256)
850
+ const declared = Number(response.headers.get("content-length") ?? 0);
851
+ if (declared > maximumBytes)
852
+ throw new Error("download exceeds reviewed size bound");
853
+ if (existsSync3(destination))
854
+ throw new Error("download destination already exists");
855
+ await Bun.write(destination, response);
856
+ chmodSync3(destination, 384);
857
+ if (statSync(destination).size > maximumBytes) {
858
+ rmSync3(destination, { force: true });
859
+ throw new Error("download exceeds reviewed size bound");
860
+ }
861
+ const digest = createHash2("sha256");
862
+ for await (const chunk of createReadStream(destination))
863
+ digest.update(chunk);
864
+ if (digest.digest("hex") !== sha256) {
865
+ rmSync3(destination, { force: true });
798
866
  throw new Error("download checksum mismatch");
799
- writeFileSync3(destination, bytes, { mode: 384, flag: "wx" });
867
+ }
800
868
  };
801
869
  var successful = (result, pattern) => result.exitCode === 0 && (!pattern || pattern.test(result.output));
802
870
  var aptInstall = async (name) => {
@@ -804,6 +872,106 @@ var aptInstall = async (name) => {
804
872
  const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
805
873
  return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", name], environment) : update;
806
874
  };
875
+ var aptInstallMany = async (names) => {
876
+ const environment = { DEBIAN_FRONTEND: "noninteractive" };
877
+ const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
878
+ return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", ...names], environment) : update;
879
+ };
880
+ var writeOwnedPolicy = (pathValue, content, mode = 420) => {
881
+ if (existsSync3(pathValue)) {
882
+ if (readFileSync3(pathValue, "utf8") !== content)
883
+ throw new Error(`refusing to overwrite non-ForgeZero policy at ${pathValue}`);
884
+ return;
885
+ }
886
+ mkdirSync3(dirname3(pathValue), { recursive: true, mode: 493 });
887
+ writeFileSync3(pathValue, content, { mode, flag: "wx" });
888
+ };
889
+ var installContainerdRuntime = async (directory, osVersion) => {
890
+ const installed = await aptInstallMany(["containerd", "runc", "containernetworking-plugins"]);
891
+ if (installed.exitCode !== 0)
892
+ return installed;
893
+ const nerdctl = join3(directory, "nerdctl.tar.gz");
894
+ await download(`https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz`, nerdctl, NERDCTL_SHA256);
895
+ const nerdctlExtract = await run(["/usr/bin/tar", "-xzf", nerdctl, "-C", "/usr/local/bin", "nerdctl"]);
896
+ if (nerdctlExtract.exitCode !== 0)
897
+ return nerdctlExtract;
898
+ chmodSync3("/usr/local/bin/nerdctl", 493);
899
+ const buildkit = join3(directory, "buildkit.tar.gz");
900
+ await download(`https://github.com/moby/buildkit/releases/download/v${BUILDKIT_VERSION}/buildkit-v${BUILDKIT_VERSION}.linux-amd64.tar.gz`, buildkit, BUILDKIT_SHA256);
901
+ const unpacked = join3(directory, "buildkit");
902
+ mkdirSync3(unpacked, { mode: 448 });
903
+ const buildkitExtract = await run(["/usr/bin/tar", "-xzf", buildkit, "-C", unpacked]);
904
+ if (buildkitExtract.exitCode !== 0)
905
+ return buildkitExtract;
906
+ for (const binary of ["buildctl", "buildkitd"]) {
907
+ copyFileSync(join3(unpacked, "bin", binary), `/usr/local/bin/${binary}`);
908
+ chmodSync3(`/usr/local/bin/${binary}`, 493);
909
+ }
910
+ mkdirSync3("/etc/containerd", { recursive: true, mode: 493 });
911
+ const expected = CONTAINERD_CONFIG(false);
912
+ const kataExpected = CONTAINERD_CONFIG(true);
913
+ if (existsSync3(CONTAINERD_CONFIG_PATH) && ![expected, kataExpected].includes(readFileSync3(CONTAINERD_CONFIG_PATH, "utf8"))) {
914
+ return { exitCode: 2, output: "refusing to overwrite an existing containerd configuration that differs from the reviewed ForgeZero policy" };
915
+ }
916
+ if (!existsSync3(CONTAINERD_CONFIG_PATH))
917
+ writeFileSync3(CONTAINERD_CONFIG_PATH, expected, { mode: 420, flag: "wx" });
918
+ writeOwnedPolicy("/etc/systemd/system/forgezero-buildkit.service", BUILDKIT_UNIT);
919
+ mkdirSync3("/var/lib/forgezero/containerd", { recursive: true, mode: 448 });
920
+ mkdirSync3("/var/lib/forgezero/buildkit", { recursive: true, mode: 448 });
921
+ const reload = await run(["/usr/bin/systemctl", "daemon-reload"]);
922
+ if (reload.exitCode !== 0)
923
+ return reload;
924
+ const enabled = await run(["/usr/bin/systemctl", "enable", "--now", "containerd.service", "forgezero-buildkit.service"]);
925
+ return enabled.exitCode === 0 ? { exitCode: 0, output: `containerd ${osVersion} with nerdctl ${NERDCTL_VERSION} and BuildKit ${BUILDKIT_VERSION}` } : enabled;
926
+ };
927
+ var installKataRuntime = async (directory) => {
928
+ for (const pathValue of ["/dev/kvm", "/dev/sev"])
929
+ if (!existsSync3(pathValue))
930
+ return { exitCode: 2, output: `${pathValue} is required for Kata SEV-SNP` };
931
+ for (const parameter of ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"]) {
932
+ if (!existsSync3(parameter) || !/^(1|Y)$/i.test(readFileSync3(parameter, "utf8").trim()))
933
+ return { exitCode: 2, output: `${parameter} does not enable SEV-SNP` };
934
+ }
935
+ const dependencies = await aptInstallMany(["zstd"]);
936
+ if (dependencies.exitCode !== 0)
937
+ return dependencies;
938
+ const archive = join3(directory, "kata.tar.zst");
939
+ 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);
940
+ const tar = join3(directory, "kata.tar");
941
+ const expanded = await run(["/usr/bin/unzstd", "--quiet", archive, "--output", tar]);
942
+ if (expanded.exitCode !== 0)
943
+ return expanded;
944
+ const extracted = await run(["/usr/bin/tar", "-xf", tar, "-C", "/"]);
945
+ if (extracted.exitCode !== 0)
946
+ return extracted;
947
+ if (!existsSync3("/opt/kata/bin/containerd-shim-kata-v2") || !existsSync3(KATA_SNP_CONFIG_PATH)) {
948
+ return { exitCode: 2, output: "Kata archive does not contain the reviewed QEMU SNP runtime and configuration" };
949
+ }
950
+ const snpConfig = readFileSync3(KATA_SNP_CONFIG_PATH, "utf8");
951
+ if (!/^\s*confidential_guest\s*=\s*true\s*$/m.test(snpConfig) || !/^\s*sev_snp_guest\s*=\s*true\s*$/m.test(snpConfig)) {
952
+ return { exitCode: 2, output: "Kata QEMU configuration does not enable confidential SEV-SNP guests" };
953
+ }
954
+ mkdirSync3("/etc/kata-containers", { recursive: true, mode: 493 });
955
+ if (existsSync3(KATA_ACTIVE_CONFIG_PATH) && readFileSync3(KATA_ACTIVE_CONFIG_PATH, "utf8") !== snpConfig) {
956
+ return { exitCode: 2, output: "refusing to overwrite a non-ForgeZero Kata runtime configuration" };
957
+ }
958
+ if (!existsSync3(KATA_ACTIVE_CONFIG_PATH))
959
+ copyFileSync(KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH);
960
+ chmodSync3(KATA_ACTIVE_CONFIG_PATH, 420);
961
+ try {
962
+ unlinkSync2("/usr/local/bin/containerd-shim-kata-v2");
963
+ } catch {}
964
+ symlinkSync2("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
965
+ const base = CONTAINERD_CONFIG(false);
966
+ const kata = CONTAINERD_CONFIG(true);
967
+ if (!existsSync3(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync3(CONTAINERD_CONFIG_PATH, "utf8"))) {
968
+ return { exitCode: 2, output: "containerd must use the reviewed ForgeZero policy before Kata is installed" };
969
+ }
970
+ if (readFileSync3(CONTAINERD_CONFIG_PATH, "utf8") !== kata)
971
+ writeFileSync3(CONTAINERD_CONFIG_PATH, kata, { mode: 420 });
972
+ const restarted = await run(["/usr/bin/systemctl", "restart", "containerd.service"]);
973
+ return restarted.exitCode === 0 ? { exitCode: 0, output: `Kata Containers ${KATA_CONTAINERS_VERSION} QEMU SNP` } : restarted;
974
+ };
807
975
  async function executeSoftwareOperation(operation) {
808
976
  const { software, version } = operation;
809
977
  if (![...UBUNTU_2604_X64, ...UBUNTU_2404_X64].some(({ requirement }) => requirement.id === software && requirement.version === version)) {
@@ -821,6 +989,26 @@ async function executeSoftwareOperation(operation) {
821
989
  const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "docker.service"]);
822
990
  return active.exitCode === 0 ? { exitCode: 0, output: binary.output } : active;
823
991
  }
992
+ if (software === "containerd") {
993
+ const [containerd, nerdctl, buildkit, active, builder] = await Promise.all([
994
+ run(["/usr/bin/containerd", "--version"]),
995
+ run(["/usr/local/bin/nerdctl", "--version"]),
996
+ run(["/usr/local/bin/buildkitd", "--version"]),
997
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]),
998
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-buildkit.service"])
999
+ ]);
1000
+ const policy = existsSync3(CONTAINERD_CONFIG_PATH) ? readFileSync3(CONTAINERD_CONFIG_PATH, "utf8") : "";
1001
+ const validPolicy = policy === CONTAINERD_CONFIG(false) || policy === CONTAINERD_CONFIG(true);
1002
+ 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" };
1003
+ }
1004
+ if (software === "kata-containers") {
1005
+ const shim = await run(["/opt/kata/bin/containerd-shim-kata-v2", "--version"]);
1006
+ const policy = existsSync3(CONTAINERD_CONFIG_PATH) ? readFileSync3(CONTAINERD_CONFIG_PATH, "utf8") : "";
1007
+ const activeConfig = existsSync3(KATA_ACTIVE_CONFIG_PATH) ? readFileSync3(KATA_ACTIVE_CONFIG_PATH, "utf8") : "";
1008
+ const host = ["/dev/kvm", "/dev/sev", KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH].every(existsSync3) && ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"].every((parameter) => existsSync3(parameter) && /^(1|Y)$/i.test(readFileSync3(parameter, "utf8").trim()));
1009
+ const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]);
1010
+ return successful(shim, /kata.*4\.0\.0/i) && policy === CONTAINERD_CONFIG(true) && activeConfig === (existsSync3(KATA_SNP_CONFIG_PATH) ? readFileSync3(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" };
1011
+ }
824
1012
  if (software === "nginx") {
825
1013
  const binary = await run(["/usr/sbin/nginx", "-v"]);
826
1014
  return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
@@ -872,6 +1060,17 @@ async function executeSoftwareOperation(operation) {
872
1060
  }
873
1061
  const directory = mkdtempSync(join3(tmpdir(), "forgezero-software-"));
874
1062
  try {
1063
+ if (software === "containerd")
1064
+ return installContainerdRuntime(directory, version === "ubuntu-24.04" ? "24.04" : "26.04");
1065
+ if (software === "kata-containers") {
1066
+ const containerd = await executeSoftwareOperation({ kind: "check", software: "containerd", version: "ubuntu-26.04" });
1067
+ if (containerd.exitCode !== 0) {
1068
+ const installed2 = await installContainerdRuntime(directory, "26.04");
1069
+ if (installed2.exitCode !== 0)
1070
+ return installed2;
1071
+ }
1072
+ return installKataRuntime(directory);
1073
+ }
875
1074
  if (software === "cloudflare-warp") {
876
1075
  const key = join3(directory, "cloudflare-warp-key.gpg");
877
1076
  await download("https://pkg.cloudflareclient.com/pubkey.gpg", key, "0f37fc298c98e88ee3c0ee68c95b69f1dba9eb477abe3167e13982105911264d");
@@ -953,7 +1152,7 @@ function validateSoftwareRequirements(value, _options = {}) {
953
1152
  if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
954
1153
  throw new Error("software requirement contains an unknown field");
955
1154
  }
956
- 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)) {
1155
+ 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)) {
957
1156
  throw new Error("software requirement coordinate is invalid");
958
1157
  }
959
1158
  const requirement = { id: row.id, version: row.version };
@@ -1425,34 +1624,180 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
1425
1624
  };
1426
1625
  }
1427
1626
 
1627
+ // src/deployment-connectivity.ts
1628
+ import { createHash as createHash3 } from "node:crypto";
1629
+ import { mkdirSync as mkdirSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
1630
+ import { dirname as dirname4 } from "node:path";
1631
+ var TOKEN = /^[A-Za-z0-9._-]{40,16384}$/;
1632
+ 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;
1633
+ var HOSTNAME = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
1634
+ var NAME2 = /^[a-z][a-z0-9._-]{0,99}$/;
1635
+ var idFor = (key) => createHash3("sha256").update(key).digest("hex").slice(0, 20);
1636
+ var checked2 = async (host, argv2, label) => {
1637
+ const result = await host.exec(argv2);
1638
+ if (result.exitCode !== 0)
1639
+ throw new Error(`${label} failed: ${result.output.slice(0, 512)}`);
1640
+ return result.output.trim();
1641
+ };
1642
+ async function sealWithSystemd(name, path2, value) {
1643
+ mkdirSync4(dirname4(path2), { recursive: true, mode: 448 });
1644
+ const next = `${path2}.next`;
1645
+ const child = Bun.spawn(["/usr/bin/systemd-creds", "encrypt", `--name=${name}`, "-", next], {
1646
+ stdin: "pipe",
1647
+ stdout: "pipe",
1648
+ stderr: "pipe",
1649
+ env: { PATH: "/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" }
1650
+ });
1651
+ child.stdin.write(value);
1652
+ child.stdin.end();
1653
+ const [stdout, stderr, exitCode] = await Promise.all([
1654
+ new Response(child.stdout).text(),
1655
+ new Response(child.stderr).text(),
1656
+ child.exited
1657
+ ]);
1658
+ if (exitCode !== 0)
1659
+ throw new Error(`systemd credential sealing failed: ${`${stdout}${stderr}`.replaceAll(value, "[REDACTED]").slice(0, 512)}`);
1660
+ renameSync4(next, path2);
1661
+ }
1662
+ var defaultHost = {
1663
+ seal: sealWithSystemd,
1664
+ write(path2, content, mode) {
1665
+ mkdirSync4(dirname4(path2), { recursive: true, mode: 493 });
1666
+ const next = `${path2}.next`;
1667
+ writeFileSync4(next, content, { mode });
1668
+ renameSync4(next, path2);
1669
+ },
1670
+ async exec(argv2) {
1671
+ 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" } });
1672
+ const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
1673
+ return { exitCode, output: `${stdout}${stderr}` };
1674
+ },
1675
+ sleep: (ms) => Bun.sleep(ms)
1676
+ };
1677
+ function validate(request) {
1678
+ if (!request.key || request.key.length > 256)
1679
+ throw new Error("deployment connectivity key is invalid");
1680
+ const publicIntent = request.intent.public;
1681
+ const privateIntent = request.intent.private;
1682
+ if (publicIntent?.mode === "cloudflare-tunnel") {
1683
+ if (!NAME2.test(publicIntent.credential) || !HOSTNAME.test(publicIntent.hostname) || !NAME2.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) || !TOKEN.test(request.capabilities.public.connectorToken)) {
1684
+ throw new Error("public deployment connectivity is malformed");
1685
+ }
1686
+ } else if (request.capabilities.public)
1687
+ throw new Error("unexpected public deployment capability");
1688
+ if (privateIntent?.mode === "cloudflare-warp") {
1689
+ if (!NAME2.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !TOKEN.test(request.capabilities.private.connectorToken)) {
1690
+ throw new Error("private deployment connectivity is malformed");
1691
+ }
1692
+ } else if (request.capabilities.private)
1693
+ throw new Error("unexpected private deployment capability");
1694
+ }
1695
+ async function applyDeploymentConnectivity(request, host = defaultHost) {
1696
+ validate(request);
1697
+ const id = idFor(request.key);
1698
+ const evidence = { key: request.key };
1699
+ if (request.intent.public?.mode === "cloudflare-tunnel") {
1700
+ const capability = request.capabilities.public;
1701
+ const credentialName = `FZ_TUNNEL_${id.toUpperCase()}`;
1702
+ const credentialPath = `/etc/forgezero/creds/deployment-tunnel-${id}.cred`;
1703
+ const unit = `forgezero-cloudflared-${id}.service`;
1704
+ const unitPath = `/etc/systemd/system/${unit}`;
1705
+ const metricsPort = 20000 + Number.parseInt(id.slice(0, 4), 16) % 20000;
1706
+ await host.seal(credentialName, credentialPath, capability.connectorToken);
1707
+ host.write(unitPath, `[Unit]
1708
+ Description=ForgeZero deployment Tunnel ${id}
1709
+ After=network-online.target
1710
+ Wants=network-online.target
1711
+
1712
+ [Service]
1713
+ Type=simple
1714
+ DynamicUser=yes
1715
+ LoadCredentialEncrypted=${credentialName}:${credentialPath}
1716
+ ExecStart=/usr/local/bin/cloudflared tunnel --no-autoupdate --metrics 127.0.0.1:${metricsPort} run --token-file %d/${credentialName}
1717
+ Restart=always
1718
+ RestartSec=5
1719
+ NoNewPrivileges=true
1720
+ PrivateTmp=true
1721
+ ProtectSystem=strict
1722
+ ProtectHome=true
1723
+
1724
+ [Install]
1725
+ WantedBy=multi-user.target
1726
+ `, 420);
1727
+ await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "cloudflared daemon reload");
1728
+ await checked2(host, ["/usr/bin/systemctl", "enable", "--now", unit], "cloudflared start");
1729
+ await checked2(host, ["/usr/bin/systemctl", "is-active", "--quiet", unit], "cloudflared readiness");
1730
+ evidence.public = { hostname: request.intent.public.hostname, tunnelId: capability.tunnelId, unit, active: true };
1731
+ }
1732
+ if (request.intent.private?.mode === "cloudflare-warp") {
1733
+ const capability = request.capabilities.private;
1734
+ const credentialPath = "/etc/forgezero/creds/CF_WARP_CONNECTOR_TOKEN.cred";
1735
+ const unit = "forgezero-deployment-mesh.service";
1736
+ await host.seal("CF_WARP_CONNECTOR_TOKEN", credentialPath, capability.connectorToken);
1737
+ host.write(`/etc/systemd/system/${unit}`, `[Unit]
1738
+ Description=ForgeZero deployment Mesh/WARP connector
1739
+ Requires=warp-svc.service
1740
+ After=network-online.target warp-svc.service
1741
+ Wants=network-online.target
1742
+
1743
+ [Service]
1744
+ Type=oneshot
1745
+ RemainAfterExit=yes
1746
+ LoadCredentialEncrypted=CF_WARP_CONNECTOR_TOKEN:${credentialPath}
1747
+ ExecStart=/usr/local/bin/fz-agent mesh-config
1748
+ NoNewPrivileges=true
1749
+ PrivateTmp=true
1750
+ ProtectSystem=strict
1751
+ ProtectHome=true
1752
+ ReadWritePaths=/var/lib/cloudflare-warp
1753
+
1754
+ [Install]
1755
+ WantedBy=multi-user.target
1756
+ `, 420);
1757
+ await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "WARP daemon reload");
1758
+ await checked2(host, ["/usr/bin/systemctl", "enable", "--now", "warp-svc.service", unit], "WARP connector start");
1759
+ for (let attempt = 0;attempt < 10; attempt += 1) {
1760
+ const status = await host.exec(["/usr/bin/warp-cli", "--accept-tos", "status"]);
1761
+ if (status.exitCode === 0 && /\bconnected\b/i.test(status.output) && !/\bdisconnected\b/i.test(status.output)) {
1762
+ evidence.private = { network: request.intent.private.network, connectorId: capability.connectorId, unit, active: true };
1763
+ break;
1764
+ }
1765
+ await host.sleep(1000);
1766
+ }
1767
+ if (!evidence.private)
1768
+ throw new Error("WARP connector readiness failed");
1769
+ }
1770
+ return evidence;
1771
+ }
1772
+
1428
1773
  // src/software-helper.ts
1429
- import { chmodSync as chmodSync4, existsSync as existsSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync3 } from "node:fs";
1774
+ import { chmodSync as chmodSync4, existsSync as existsSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync3 } from "node:fs";
1430
1775
  import { connect as connect2, createServer as createServer2 } from "node:net";
1431
- import { dirname as dirname5 } from "node:path";
1776
+ import { dirname as dirname7 } from "node:path";
1432
1777
 
1433
1778
  // src/service-supervisor.ts
1434
- import { createHash as createHash3 } from "node:crypto";
1435
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync, realpathSync, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
1436
- import { dirname as dirname3, join as join4, resolve as resolve3, sep } from "node:path";
1779
+ import { createHash as createHash4 } from "node:crypto";
1780
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, readdirSync, realpathSync, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
1781
+ import { dirname as dirname5, join as join4, resolve as resolve3, sep } from "node:path";
1437
1782
  var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
1438
1783
  var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
1439
1784
  var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
1440
1785
  var SERVICE_NGINX_DIRECTORY = "/etc/nginx/conf.d";
1441
- var idFor = (key) => createHash3("sha256").update(key).digest("hex").slice(0, 24);
1786
+ var idFor2 = (key) => createHash4("sha256").update(key).digest("hex").slice(0, 24);
1442
1787
  var statePath = (id) => `${SERVICE_STATE_DIRECTORY}/${id}.json`;
1443
1788
  var within = (root, path2) => path2 === root || path2.startsWith(`${root}${sep}`);
1444
- var defaultHost = {
1789
+ var defaultHost2 = {
1445
1790
  write(path2, content, mode) {
1446
- mkdirSync4(dirname3(path2), { recursive: true, mode: 493 });
1791
+ mkdirSync5(dirname5(path2), { recursive: true, mode: 493 });
1447
1792
  const next = `${path2}.next`;
1448
- writeFileSync4(next, content, { mode });
1449
- renameSync4(next, path2);
1793
+ writeFileSync5(next, content, { mode });
1794
+ renameSync5(next, path2);
1450
1795
  },
1451
1796
  read: (path2) => readFileSync4(path2, "utf8"),
1452
1797
  exists: existsSync4,
1453
1798
  list: (path2) => existsSync4(path2) ? readdirSync(path2) : [],
1454
1799
  realpath: realpathSync,
1455
- mkdir: (path2, mode) => mkdirSync4(path2, { recursive: true, mode }),
1800
+ mkdir: (path2, mode) => mkdirSync5(path2, { recursive: true, mode }),
1456
1801
  remove: (path2) => rmSync4(path2, { force: true }),
1457
1802
  async exec(argv2) {
1458
1803
  const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
@@ -1511,7 +1856,7 @@ function allocatedPorts(request, id, previous, states) {
1511
1856
  if (previous && previous.applicationPorts.length === required && previous.applicationPorts.every((port) => port >= allocation.from && port <= allocation.to && !occupied.has(port)))
1512
1857
  return [...previous.applicationPorts];
1513
1858
  const width = allocation.to - allocation.from + 1;
1514
- const start = Number.parseInt(createHash3("sha256").update(request.key).digest("hex").slice(0, 8), 16) % width;
1859
+ const start = Number.parseInt(createHash4("sha256").update(request.key).digest("hex").slice(0, 8), 16) % width;
1515
1860
  for (let offset = 0;offset < width; offset += 1) {
1516
1861
  const first = allocation.from + (start + offset) % width;
1517
1862
  const candidate = Array.from({ length: required }, (_, index) => first + index);
@@ -1569,12 +1914,12 @@ ${request.service.websocket ? ` proxy_set_header Upgrade $http_upgrade;
1569
1914
  }
1570
1915
  }
1571
1916
  `;
1572
- async function checked2(host, argv2, code) {
1917
+ async function checked3(host, argv2, code) {
1573
1918
  const result = await host.exec(argv2);
1574
1919
  if (result.exitCode !== 0)
1575
1920
  throw new Error(`${code}: ${result.output.slice(0, 512)}`);
1576
1921
  }
1577
- async function activateSupervisedService(request, host = defaultHost) {
1922
+ async function activateSupervisedService(request, host = defaultHost2) {
1578
1923
  if (!/^[A-Za-z0-9][A-Za-z0-9_.:@/-]{0,255}$/.test(request.key) || !/^[a-f0-9]{40}$/.test(request.revision)) {
1579
1924
  throw new Error("SERVICE_IDENTITY_INVALID");
1580
1925
  }
@@ -1583,7 +1928,7 @@ async function activateSupervisedService(request, host = defaultHost) {
1583
1928
  if (!within(root, release))
1584
1929
  throw new Error("SERVICE_RELEASE_OUTSIDE_ROOT");
1585
1930
  request = { ...request, root, release, service: validateDeploymentService(request.service) };
1586
- const id = idFor(request.key);
1931
+ const id = idFor2(request.key);
1587
1932
  host.mkdir(SERVICE_STATE_DIRECTORY, 448);
1588
1933
  host.mkdir(SERVICE_CONFIG_DIRECTORY, 457);
1589
1934
  const previous = readState(host, statePath(id));
@@ -1594,11 +1939,11 @@ async function activateSupervisedService(request, host = defaultHost) {
1594
1939
  const unitName = `forgezero-app-${id}-slot${nextSlot}.service`;
1595
1940
  host.write(configPath, appConfig({ ...request, release }, port), 292);
1596
1941
  host.write(`${SERVICE_UNIT_DIRECTORY}/${unitName}`, unit(id, nextSlot), 420);
1597
- await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "SERVICE_UNIT_RELOAD_FAILED");
1942
+ await checked3(host, ["/usr/bin/systemctl", "daemon-reload"], "SERVICE_UNIT_RELOAD_FAILED");
1598
1943
  if (request.service.strategy === "direct" && previous) {
1599
1944
  await host.exec(["/usr/bin/systemctl", "stop", `forgezero-app-${id}-slot${previous.activeSlot}.service`]);
1600
1945
  }
1601
- await checked2(host, ["/usr/bin/systemctl", "restart", unitName], "SERVICE_START_FAILED");
1946
+ await checked3(host, ["/usr/bin/systemctl", "restart", unitName], "SERVICE_START_FAILED");
1602
1947
  let healthy = false;
1603
1948
  for (let attempt = 0;attempt < 30; attempt += 1) {
1604
1949
  const active = await host.exec(["/usr/bin/systemctl", "is-active", "--quiet", unitName]);
@@ -1616,8 +1961,8 @@ async function activateSupervisedService(request, host = defaultHost) {
1616
1961
  const oldNginx = host.exists(nginxPath) ? host.read(nginxPath) : null;
1617
1962
  try {
1618
1963
  host.write(nginxPath, nginx(request, id, port), 420);
1619
- await checked2(host, ["/usr/sbin/nginx", "-t"], "SERVICE_NGINX_INVALID");
1620
- await checked2(host, ["/usr/bin/systemctl", "reload", "nginx.service"], "SERVICE_NGINX_RELOAD_FAILED");
1964
+ await checked3(host, ["/usr/sbin/nginx", "-t"], "SERVICE_NGINX_INVALID");
1965
+ await checked3(host, ["/usr/bin/systemctl", "reload", "nginx.service"], "SERVICE_NGINX_RELOAD_FAILED");
1621
1966
  } catch (cause) {
1622
1967
  if (oldNginx === null)
1623
1968
  host.remove(nginxPath);
@@ -1648,22 +1993,22 @@ async function activateSupervisedService(request, host = defaultHost) {
1648
1993
  }
1649
1994
 
1650
1995
  // src/container-supervisor.ts
1651
- import { createHash as createHash4 } from "node:crypto";
1652
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync5, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "node:fs";
1653
- import { dirname as dirname4, resolve as resolve4, sep as sep2 } from "node:path";
1654
- var defaultHost2 = {
1996
+ import { createHash as createHash5 } from "node:crypto";
1997
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync5, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
1998
+ import { dirname as dirname6, resolve as resolve4, sep as sep2 } from "node:path";
1999
+ var defaultHost3 = {
1655
2000
  realpath: realpathSync2,
1656
2001
  exists: existsSync5,
1657
2002
  read: (path2) => readFileSync5(path2, "utf8"),
1658
2003
  write(path2, content, mode) {
1659
- mkdirSync5(dirname4(path2), { recursive: true, mode: 493 });
2004
+ mkdirSync6(dirname6(path2), { recursive: true, mode: 493 });
1660
2005
  const next = `${path2}.next`;
1661
- writeFileSync5(next, content, { mode });
1662
- renameSync5(next, path2);
2006
+ writeFileSync6(next, content, { mode });
2007
+ renameSync6(next, path2);
1663
2008
  },
1664
2009
  remove: (path2) => rmSync5(path2, { force: true }),
1665
2010
  list: (path2) => existsSync5(path2) ? readdirSync2(path2) : [],
1666
- mkdir: (path2, mode) => mkdirSync5(path2, { recursive: true, mode }),
2011
+ mkdir: (path2, mode) => mkdirSync6(path2, { recursive: true, mode }),
1667
2012
  async exec(argv2) {
1668
2013
  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" } });
1669
2014
  const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
@@ -1681,9 +2026,41 @@ var defaultHost2 = {
1681
2026
  now: Date.now
1682
2027
  };
1683
2028
  var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
1684
- var idFor2 = (key) => createHash4("sha256").update(key).digest("hex").slice(0, 24);
2029
+ var idFor3 = (key) => createHash5("sha256").update(key).digest("hex").slice(0, 24);
1685
2030
  var statePath2 = (id) => `${SERVICE_STATE_DIRECTORY}/${id}.json`;
1686
2031
  var nameFor = (id, slot) => `forgezero-${id}-slot${slot}`;
2032
+ function backendFor(application) {
2033
+ const runtime = application.runtime;
2034
+ if (runtime.kind !== "container")
2035
+ throw new Error("CONTAINER_RUNTIME_REQUIRED");
2036
+ if (runtime.provider === "forgezero.docker") {
2037
+ if (runtime.runtimeClass && runtime.runtimeClass !== "runc")
2038
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
2039
+ return { engine: "docker", runtimeClass: "runc", prefix: ["/usr/bin/docker"] };
2040
+ }
2041
+ if (runtime.provider === "forgezero.containerd") {
2042
+ if ((runtime.runtimeClass ?? "runc") !== "runc")
2043
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
2044
+ return {
2045
+ engine: "containerd",
2046
+ runtimeClass: "runc",
2047
+ prefix: ["/usr/local/bin/nerdctl", "--namespace", "forgezero"],
2048
+ runtimeName: "io.containerd.runc.v2"
2049
+ };
2050
+ }
2051
+ if (runtime.provider === "forgezero.kata") {
2052
+ if (runtime.runtimeClass !== "kata-qemu-snp")
2053
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
2054
+ return {
2055
+ engine: "containerd",
2056
+ runtimeClass: "kata-qemu-snp",
2057
+ prefix: ["/usr/local/bin/nerdctl", "--namespace", "forgezero"],
2058
+ runtimeName: "io.containerd.kata.v2"
2059
+ };
2060
+ }
2061
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_UNSUPPORTED");
2062
+ }
2063
+ var command2 = (backend, ...argv2) => [...backend.prefix, ...argv2];
1687
2064
  function validateRequest(request, host) {
1688
2065
  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))
1689
2066
  throw new Error("CONTAINER_IDENTITY_INVALID");
@@ -1694,6 +2071,7 @@ function validateRequest(request, host) {
1694
2071
  const application = request.application;
1695
2072
  if (application.kind !== "application" || application.runtime.kind !== "container")
1696
2073
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
2074
+ backendFor(application);
1697
2075
  if (!application.resources.cpu || !application.resources.memory || !application.resources.pids)
1698
2076
  throw new Error("CONTAINER_LIMITS_REQUIRED");
1699
2077
  if (application.runtime.security.privileged !== false || application.runtime.security.noNewPrivileges !== true || !application.runtime.security.dropCapabilities.includes("ALL"))
@@ -1704,23 +2082,24 @@ function validateRequest(request, host) {
1704
2082
  throw new Error("CONTAINER_STABLE_PORT_REQUIRED");
1705
2083
  return { ...request, root, release };
1706
2084
  }
1707
- async function checked3(host, argv2, code) {
2085
+ async function checked4(host, argv2, code) {
1708
2086
  const result = await host.exec(argv2);
1709
2087
  if (result.exitCode !== 0)
1710
2088
  throw new Error(`${code}: ${result.output.slice(0, 512)}`);
1711
2089
  return result.output.trim();
1712
2090
  }
1713
- async function buildContainerImage(requestValue, host = defaultHost2) {
2091
+ async function buildContainerImage(requestValue, host = defaultHost3) {
1714
2092
  const request = validateRequest(requestValue, host);
1715
2093
  const runtime = request.application.runtime;
1716
2094
  if (runtime.kind !== "container")
1717
2095
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
2096
+ const backend = backendFor(request.application);
1718
2097
  const source = runtime.image.source;
1719
2098
  if (source.kind === "registry") {
1720
2099
  if (!source.reference.includes("@sha256:"))
1721
2100
  throw new Error("CONTAINER_IMAGE_DIGEST_REQUIRED");
1722
- await checked3(host, ["/usr/bin/docker", "pull", "--quiet", source.reference], "CONTAINER_PULL_FAILED");
1723
- const digest2 = await checked3(host, ["/usr/bin/docker", "image", "inspect", "--format={{.Id}}", source.reference], "CONTAINER_INSPECT_FAILED");
2101
+ await checked4(host, command2(backend, "pull", "--quiet", source.reference), "CONTAINER_PULL_FAILED");
2102
+ const digest2 = await checked4(host, command2(backend, "image", "inspect", "--format={{.Id}}", source.reference), "CONTAINER_INSPECT_FAILED");
1724
2103
  if (!/^sha256:[a-f0-9]{64}$/.test(digest2))
1725
2104
  throw new Error("CONTAINER_DIGEST_INVALID");
1726
2105
  return { image: source.reference, digest: digest2 };
@@ -1729,23 +2108,9 @@ async function buildContainerImage(requestValue, host = defaultHost2) {
1729
2108
  const dockerfile = host.realpath(resolve4(request.release, source.dockerfile));
1730
2109
  if (!within2(request.release, context) || !within2(context, dockerfile))
1731
2110
  throw new Error("CONTAINER_BUILD_PATH_INVALID");
1732
- const tag = `forgezero/${idFor2(request.key)}:${request.revision}`;
1733
- await checked3(host, [
1734
- "/usr/bin/docker",
1735
- "build",
1736
- "--pull",
1737
- ...request.noCache ? ["--no-cache"] : [],
1738
- "--file",
1739
- dockerfile,
1740
- "--tag",
1741
- tag,
1742
- "--label",
1743
- `net.forgezero.revision=${request.revision}`,
1744
- "--label",
1745
- `net.forgezero.component=${request.component}`,
1746
- context
1747
- ], "CONTAINER_BUILD_FAILED");
1748
- const digest = await checked3(host, ["/usr/bin/docker", "image", "inspect", "--format={{.Id}}", tag], "CONTAINER_INSPECT_FAILED");
2111
+ const tag = `forgezero/${idFor3(request.key)}:${request.revision}`;
2112
+ await checked4(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");
2113
+ const digest = await checked4(host, command2(backend, "image", "inspect", "--format={{.Id}}", tag), "CONTAINER_INSPECT_FAILED");
1749
2114
  if (!/^sha256:[a-f0-9]{64}$/.test(digest))
1750
2115
  throw new Error("CONTAINER_DIGEST_INVALID");
1751
2116
  return { image: tag, digest };
@@ -1813,7 +2178,7 @@ ${service.websocket ? ` proxy_set_header Upgrade $http_upgrade;
1813
2178
  }
1814
2179
  `;
1815
2180
  }
1816
- function runArgv(request, id, slot, port, networkName) {
2181
+ function runArgv(request, backend, id, slot, port, networkName) {
1817
2182
  const { application } = request;
1818
2183
  const resources = application.resources;
1819
2184
  const runtime = application.runtime;
@@ -1821,35 +2186,7 @@ function runArgv(request, id, slot, port, networkName) {
1821
2186
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
1822
2187
  const memory = resources.memory;
1823
2188
  const network = application.network.container;
1824
- const argv2 = [
1825
- "/usr/bin/docker",
1826
- "run",
1827
- "--detach",
1828
- "--name",
1829
- nameFor(id, slot),
1830
- "--restart",
1831
- "unless-stopped",
1832
- "--label",
1833
- `net.forgezero.key=${request.key}`,
1834
- "--label",
1835
- `net.forgezero.revision=${request.revision}`,
1836
- "--cpus",
1837
- String(resources.cpu.limit),
1838
- "--cpu-shares",
1839
- String(resources.cpu.weight ?? 100),
1840
- "--memory",
1841
- `${memory.limitMiB}m`,
1842
- ...memory.reservationMiB ? ["--memory-reservation", `${memory.reservationMiB}m`] : [],
1843
- ...memory.swap === "disabled" ? ["--memory-swap", `${memory.limitMiB}m`] : [],
1844
- "--pids-limit",
1845
- String(resources.pids.limit),
1846
- "--security-opt",
1847
- "no-new-privileges=true",
1848
- "--cap-drop",
1849
- "ALL",
1850
- ...runtime.security.root === "read-only" ? ["--read-only"] : [],
1851
- ...network.mode === "host" ? ["--network", "host", "--env", `FZ_APP_PORT=${port}`] : ["--network", networkName, "--publish", `127.0.0.1:${port}:${application.service.port}`]
1852
- ];
2189
+ const argv2 = command2(backend, "run", "--detach", "--name", nameFor(id, 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}`]);
1853
2190
  for (const mount of application.storage ?? []) {
1854
2191
  if (mount.class === "ephemeral")
1855
2192
  argv2.push("--tmpfs", `${mount.path}:rw,noexec,nosuid,size=${mount.sizeMiB}m`);
@@ -1859,11 +2196,12 @@ function runArgv(request, id, slot, port, networkName) {
1859
2196
  argv2.push(request.image, ...runtime.entrypoint ?? []);
1860
2197
  return argv2;
1861
2198
  }
1862
- async function activateContainer(requestValue, host = defaultHost2) {
2199
+ async function activateContainer(requestValue, host = defaultHost3) {
1863
2200
  const request = validateRequest(requestValue, host);
1864
2201
  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:"))
1865
2202
  throw new Error("CONTAINER_IMAGE_INVALID");
1866
- const id = idFor2(request.key);
2203
+ const backend = backendFor(request.application);
2204
+ const id = idFor3(request.key);
1867
2205
  host.mkdir(SERVICE_STATE_DIRECTORY, 448);
1868
2206
  host.mkdir(SERVICE_NGINX_DIRECTORY, 493);
1869
2207
  const previous = readState2(host, id);
@@ -1875,9 +2213,9 @@ async function activateContainer(requestValue, host = defaultHost2) {
1875
2213
  if (containerNetwork.mode === "bridge") {
1876
2214
  networkName = containerNetwork.network ? `forgezero-${id}-${containerNetwork.network}` : "bridge";
1877
2215
  if (networkName !== "bridge") {
1878
- const inspected = await host.exec(["/usr/bin/docker", "network", "inspect", '--format={{index .Labels "net.forgezero.owner"}}', networkName]);
2216
+ const inspected = await host.exec(command2(backend, "network", "inspect", '--format={{index .Labels "net.forgezero.owner"}}', networkName));
1879
2217
  if (inspected.exitCode !== 0) {
1880
- await checked3(host, ["/usr/bin/docker", "network", "create", "--driver", "bridge", "--label", `net.forgezero.owner=${id}`, networkName], "CONTAINER_NETWORK_CREATE_FAILED");
2218
+ await checked4(host, command2(backend, "network", "create", "--driver", "bridge", "--label", `net.forgezero.owner=${id}`, networkName), "CONTAINER_NETWORK_CREATE_FAILED");
1881
2219
  } else if (inspected.output.trim() !== id)
1882
2220
  throw new Error("CONTAINER_NETWORK_OWNERSHIP_INVALID");
1883
2221
  }
@@ -1886,8 +2224,8 @@ async function activateContainer(requestValue, host = defaultHost2) {
1886
2224
  const nextSlot = slots === 2 && previous?.activeSlot === 0 ? 1 : 0;
1887
2225
  const port = ports[nextSlot];
1888
2226
  const name = nameFor(id, nextSlot);
1889
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
1890
- await checked3(host, runArgv(request, id, nextSlot, port, networkName), "CONTAINER_START_FAILED");
2227
+ await host.exec(command2(backend, "rm", "--force", name));
2228
+ await checked4(host, runArgv(request, backend, id, nextSlot, port, networkName), "CONTAINER_START_FAILED");
1891
2229
  let healthy = false;
1892
2230
  const health = request.application.service.health;
1893
2231
  for (let attempt = 0;attempt < (health.attempts ?? 30); attempt += 1) {
@@ -1898,15 +2236,28 @@ async function activateContainer(requestValue, host = defaultHost2) {
1898
2236
  await host.sleep(health.intervalMs ?? 1000);
1899
2237
  }
1900
2238
  if (!healthy) {
1901
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
2239
+ await host.exec(command2(backend, "rm", "--force", name));
1902
2240
  throw new Error("CONTAINER_HEALTH_FAILED");
1903
2241
  }
2242
+ let runtimeEvidence;
2243
+ if (backend.engine === "containerd") {
2244
+ const raw = await checked4(host, ["/usr/bin/ctr", "--namespace", "forgezero", "containers", "info", name], "CONTAINER_RUNTIME_EVIDENCE_FAILED");
2245
+ let observed = "";
2246
+ try {
2247
+ observed = String(JSON.parse(raw).Runtime?.Name ?? "");
2248
+ } catch {
2249
+ throw new Error("CONTAINER_RUNTIME_EVIDENCE_INVALID");
2250
+ }
2251
+ if (observed !== backend.runtimeName)
2252
+ throw new Error("CONTAINER_RUNTIME_DOWNGRADE");
2253
+ runtimeEvidence = observed;
2254
+ }
1904
2255
  const nginxPath = `${SERVICE_NGINX_DIRECTORY}/forgezero-${id}.conf`;
1905
2256
  const previousNginx = host.exists(nginxPath) ? host.read(nginxPath) : null;
1906
2257
  host.write(nginxPath, nginx2(request, id, port), 420);
1907
2258
  try {
1908
- await checked3(host, ["/usr/sbin/nginx", "-t"], "CONTAINER_NGINX_INVALID");
1909
- await checked3(host, ["/usr/bin/systemctl", "reload", "nginx.service"], "CONTAINER_NGINX_RELOAD_FAILED");
2259
+ await checked4(host, ["/usr/sbin/nginx", "-t"], "CONTAINER_NGINX_INVALID");
2260
+ await checked4(host, ["/usr/bin/systemctl", "reload", "nginx.service"], "CONTAINER_NGINX_RELOAD_FAILED");
1910
2261
  } catch (cause) {
1911
2262
  if (previousNginx === null)
1912
2263
  host.remove(nginxPath);
@@ -1914,7 +2265,7 @@ async function activateContainer(requestValue, host = defaultHost2) {
1914
2265
  host.write(nginxPath, previousNginx, 420);
1915
2266
  await host.exec(["/usr/sbin/nginx", "-t"]);
1916
2267
  await host.exec(["/usr/bin/systemctl", "reload", "nginx.service"]);
1917
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
2268
+ await host.exec(command2(backend, "rm", "--force", name));
1918
2269
  throw cause;
1919
2270
  }
1920
2271
  const state = {
@@ -1927,13 +2278,18 @@ async function activateContainer(requestValue, host = defaultHost2) {
1927
2278
  applicationPorts: ports,
1928
2279
  activeSlot: nextSlot,
1929
2280
  healthPath: health.path,
2281
+ containerRuntime: {
2282
+ engine: backend.engine,
2283
+ runtimeClass: backend.runtimeClass,
2284
+ ...backend.engine === "containerd" ? { namespace: "forgezero", evidence: runtimeEvidence } : {}
2285
+ },
1930
2286
  updatedAtTs: host.now()
1931
2287
  };
1932
2288
  host.write(statePath2(id), `${JSON.stringify(state, null, 2)}
1933
2289
  `, 384);
1934
2290
  if (previous && previous.activeSlot !== nextSlot) {
1935
2291
  await host.sleep(request.application.rollout.drainMs ?? 30000);
1936
- await host.exec(["/usr/bin/docker", "rm", "--force", nameFor(id, previous.activeSlot)]);
2292
+ await host.exec(command2(backend, "rm", "--force", nameFor(id, previous.activeSlot)));
1937
2293
  }
1938
2294
  return state;
1939
2295
  }
@@ -1948,11 +2304,12 @@ function startSoftwareHelper(options = {}) {
1948
2304
  const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
1949
2305
  if (existsSync6(socketPath))
1950
2306
  unlinkSync3(socketPath);
1951
- mkdirSync6(dirname5(socketPath), { recursive: true, mode: 488 });
2307
+ mkdirSync7(dirname7(socketPath), { recursive: true, mode: 488 });
1952
2308
  const ensure = options.ensure ?? ensureSoftwareRequirements;
1953
2309
  const activate = options.activate ?? activateSupervisedService;
1954
2310
  const buildContainer = options.buildContainer ?? buildContainerImage;
1955
2311
  const activateTypedContainer = options.activateContainer ?? activateContainer;
2312
+ const applyConnectivity = options.applyConnectivity ?? applyDeploymentConnectivity;
1956
2313
  let tail = Promise.resolve();
1957
2314
  let pending = 0;
1958
2315
  const server = createServer2((socket) => {
@@ -2009,6 +2366,12 @@ function startSoftwareHelper(options = {}) {
2009
2366
  throw new Error("container deployment root is not owned by this helper");
2010
2367
  const state = await activateTypedContainer(containerRequest);
2011
2368
  socket.end(`${JSON.stringify({ ok: true, state })}
2369
+ `);
2370
+ return;
2371
+ }
2372
+ if (request.op === "apply-connectivity" && request.request) {
2373
+ const result = await applyConnectivity(request.request);
2374
+ socket.end(`${JSON.stringify({ ok: true, result })}
2012
2375
  `);
2013
2376
  return;
2014
2377
  }
@@ -2066,6 +2429,9 @@ function requestContainerBuild(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOC
2066
2429
  function requestContainerActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
2067
2430
  return requestContainer("activate-container", request, socketPath, timeoutMs);
2068
2431
  }
2432
+ function requestDeploymentConnectivity(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
2433
+ return requestContainer("apply-connectivity", request, socketPath, timeoutMs);
2434
+ }
2069
2435
  function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 10 * 60000) {
2070
2436
  return new Promise((resolve5, reject) => {
2071
2437
  const socket = connect2(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
@@ -2125,7 +2491,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2125
2491
  }
2126
2492
 
2127
2493
  // src/version.ts
2128
- var VERSION3 = "0.1.74";
2494
+ var VERSION3 = "0.1.75";
2129
2495
 
2130
2496
  // src/egress-policy.ts
2131
2497
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -3259,15 +3625,15 @@ import {
3259
3625
  copyFileSync as copyFileSync2,
3260
3626
  existsSync as existsSync7,
3261
3627
  lstatSync,
3262
- mkdirSync as mkdirSync7,
3628
+ mkdirSync as mkdirSync8,
3263
3629
  readFileSync as readFileSync6,
3264
3630
  readdirSync as readdirSync3,
3265
3631
  realpathSync as realpathSync4,
3266
- renameSync as renameSync6,
3632
+ renameSync as renameSync7,
3267
3633
  rmSync as rmSync6,
3268
- statSync,
3634
+ statSync as statSync2,
3269
3635
  symlinkSync as symlinkSync3,
3270
- writeFileSync as writeFileSync6
3636
+ writeFileSync as writeFileSync7
3271
3637
  } from "node:fs";
3272
3638
  import { join as join5 } from "node:path";
3273
3639
  var safeAtom = (name, value) => {
@@ -3686,7 +4052,7 @@ var replaceLink = (path2, target) => {
3686
4052
  return;
3687
4053
  }
3688
4054
  symlinkSync3(target, pending);
3689
- renameSync6(pending, path2);
4055
+ renameSync7(pending, path2);
3690
4056
  };
3691
4057
  var secureRelease = (path2, uid, gid) => {
3692
4058
  const visit = (current2) => {
@@ -3709,7 +4075,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
3709
4075
  if (!release.startsWith(`${releases}/`) || release === releases)
3710
4076
  throw new Error("release is outside configured releases directory");
3711
4077
  for (const required of ["forgezero.deploy.ts", ".fz/deploy.plan.json", "src/index.ts", "bun.lock"]) {
3712
- const metadata = statSync(join5(release, required));
4078
+ const metadata = statSync2(join5(release, required));
3713
4079
  if (!metadata.isFile() || metadata.size < 1)
3714
4080
  throw new Error(`release is incomplete: ${required}`);
3715
4081
  }
@@ -3717,7 +4083,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
3717
4083
  const request = options.fetch ?? fetch;
3718
4084
  const sleep = options.sleep ?? Bun.sleep;
3719
4085
  const slots = join5(normalized.root, "slots");
3720
- mkdirSync7(slots, { recursive: true, mode: 493 });
4086
+ mkdirSync8(slots, { recursive: true, mode: 493 });
3721
4087
  const slotFile = join5(normalized.root, ".forge-slot");
3722
4088
  const previousSlot = existsSync7(slotFile) ? readFileSync6(slotFile, "utf8").trim() : undefined;
3723
4089
  const target = previousSlot === "blue" ? "green" : "blue";
@@ -3763,13 +4129,13 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
3763
4129
  copyFileSync2(upstream, backup);
3764
4130
  else
3765
4131
  rmSync6(backup, { force: true });
3766
- writeFileSync6(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
4132
+ writeFileSync7(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
3767
4133
  `, { mode: 420 });
3768
4134
  const test = await exec(["/usr/sbin/nginx", "-t"]);
3769
4135
  const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
3770
4136
  if (reload.exitCode !== 0) {
3771
4137
  if (existsSync7(backup))
3772
- renameSync6(backup, upstream);
4138
+ renameSync7(backup, upstream);
3773
4139
  else
3774
4140
  rmSync6(upstream, { force: true });
3775
4141
  await exec(["/usr/sbin/nginx", "-t"]);
@@ -3778,13 +4144,13 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
3778
4144
  throw new Error("nginx refused the promoted upstream");
3779
4145
  }
3780
4146
  rmSync6(backup, { force: true });
3781
- writeFileSync6(slotFile, `${target}
4147
+ writeFileSync7(slotFile, `${target}
3782
4148
  `, { mode: 420 });
3783
4149
  if (previousSlot && previousSlot !== target) {
3784
4150
  await sleep(normalized.drainDeadlineMs);
3785
4151
  await exec(["/usr/bin/systemctl", "stop", `forgezero@${previousSlot}.service`]);
3786
4152
  }
3787
- const old = readdirSync3(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join5(releases, entry.name)).sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs).slice(normalized.keepReleases);
4153
+ const old = readdirSync3(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join5(releases, entry.name)).sort((left, right) => statSync2(right).mtimeMs - statSync2(left).mtimeMs).slice(normalized.keepReleases);
3788
4154
  for (const path2 of old)
3789
4155
  if (path2 !== release)
3790
4156
  rmSync6(path2, { recursive: true, force: true });
@@ -3831,18 +4197,18 @@ function planLocalOtlpProof(endpoint, collectorUnit) {
3831
4197
  import { lstatSync as lstatSync5 } from "node:fs";
3832
4198
 
3833
4199
  // src/bootstrap.ts
3834
- import { createHash as createHash6, createHmac as createHmac2, randomBytes as randomBytes3 } from "node:crypto";
4200
+ import { createHash as createHash7, createHmac as createHmac2, randomBytes as randomBytes3 } from "node:crypto";
3835
4201
  import {
3836
4202
  chmodSync as chmodSync8,
3837
4203
  existsSync as existsSync10,
3838
4204
  lstatSync as lstatSync4,
3839
- mkdirSync as mkdirSync10,
4205
+ mkdirSync as mkdirSync11,
3840
4206
  readFileSync as readFileSync9,
3841
- renameSync as renameSync9,
4207
+ renameSync as renameSync10,
3842
4208
  rmSync as rmSync9,
3843
- writeFileSync as writeFileSync9
4209
+ writeFileSync as writeFileSync10
3844
4210
  } from "node:fs";
3845
- import { dirname as dirname9 } from "node:path";
4211
+ import { dirname as dirname11 } from "node:path";
3846
4212
  import { fileURLToPath } from "node:url";
3847
4213
 
3848
4214
  // src/cli/agent-install.ts
@@ -3852,15 +4218,15 @@ import {
3852
4218
  copyFileSync as copyFileSync3,
3853
4219
  existsSync as existsSync8,
3854
4220
  lstatSync as lstatSync2,
3855
- mkdirSync as mkdirSync8,
4221
+ mkdirSync as mkdirSync9,
3856
4222
  readFileSync as readFileSync7,
3857
4223
  realpathSync as realpathSync5,
3858
- renameSync as renameSync7,
4224
+ renameSync as renameSync8,
3859
4225
  rmSync as rmSync7,
3860
4226
  symlinkSync as symlinkSync4,
3861
- writeFileSync as writeFileSync7
4227
+ writeFileSync as writeFileSync8
3862
4228
  } from "node:fs";
3863
- import { dirname as dirname6 } from "node:path";
4229
+ import { dirname as dirname8 } from "node:path";
3864
4230
  async function readCapabilities(run2) {
3865
4231
  const answers = {};
3866
4232
  const checks = Object.entries(CAPABILITY_CHECKS);
@@ -3895,10 +4261,10 @@ var fixed = async (argv2, stdin) => {
3895
4261
  var runProvisionOperation = async (operation) => {
3896
4262
  if (operation.kind === "commands") {
3897
4263
  let output = "";
3898
- for (const command2 of operation.commands) {
3899
- const result = await fixed(command2.argv);
4264
+ for (const command3 of operation.commands) {
4265
+ const result = await fixed(command3.argv);
3900
4266
  output += result.stdout;
3901
- if (!(command2.acceptedExitCodes ?? [0]).includes(result.exitCode))
4267
+ if (!(command3.acceptedExitCodes ?? [0]).includes(result.exitCode))
3902
4268
  return { stdout: output, exitCode: result.exitCode };
3903
4269
  }
3904
4270
  return { stdout: output, exitCode: 0 };
@@ -3919,11 +4285,11 @@ var runProvisionOperation = async (operation) => {
3919
4285
  }
3920
4286
  if (operation.kind === "install-runtime") {
3921
4287
  const release = `/opt/forgezero/agent/versions/${operation.version}`;
3922
- mkdirSync8(`${release}/dist`, { recursive: true, mode: 493 });
3923
- mkdirSync8(dirname6(operation.binary), { recursive: true, mode: 493 });
4288
+ mkdirSync9(`${release}/dist`, { recursive: true, mode: 493 });
4289
+ mkdirSync9(dirname8(operation.binary), { recursive: true, mode: 493 });
3924
4290
  copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
3925
4291
  chmodSync6(`${release}/dist/fz-agent.js`, 493);
3926
- const gitSshSource = `${dirname6(operation.source)}/fz-git-ssh.js`;
4292
+ const gitSshSource = `${dirname8(operation.source)}/fz-git-ssh.js`;
3927
4293
  if (!existsSync8(gitSshSource))
3928
4294
  return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
3929
4295
  copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
@@ -3931,7 +4297,7 @@ var runProvisionOperation = async (operation) => {
3931
4297
  const pending = "/opt/forgezero/agent/current.next";
3932
4298
  rmSync7(pending, { force: true });
3933
4299
  symlinkSync4(`versions/${operation.version}`, pending);
3934
- renameSync7(pending, "/opt/forgezero/agent/current");
4300
+ renameSync8(pending, "/opt/forgezero/agent/current");
3935
4301
  rmSync7(operation.binary, { force: true });
3936
4302
  symlinkSync4("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
3937
4303
  const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
@@ -3972,7 +4338,7 @@ var runProvisionOperation = async (operation) => {
3972
4338
  const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
3973
4339
  if (derived.exitCode !== 0)
3974
4340
  return derived;
3975
- writeFileSync7(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
4341
+ writeFileSync8(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
3976
4342
  `, { mode: 292 });
3977
4343
  }
3978
4344
  return { stdout: "", exitCode: 0 };
@@ -4021,8 +4387,8 @@ var runProvisionOperation = async (operation) => {
4021
4387
  if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
4022
4388
  return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
4023
4389
  }
4024
- mkdirSync8(dirname6(operation.publicKey), { recursive: true, mode: 493 });
4025
- writeFileSync7(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
4390
+ mkdirSync9(dirname8(operation.publicKey), { recursive: true, mode: 493 });
4391
+ writeFileSync8(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
4026
4392
  `, { mode: 292 });
4027
4393
  chmodSync6(operation.publicKey, 292);
4028
4394
  }
@@ -4085,11 +4451,11 @@ var runProvisionOperation = async (operation) => {
4085
4451
  const response = await fetch("https://pkg.cloudflareclient.com/pubkey.gpg", { signal: AbortSignal.timeout(30000) });
4086
4452
  if (!response.ok)
4087
4453
  return { stdout: `WARP key HTTP ${response.status}`, exitCode: 1 };
4088
- mkdirSync8("/usr/share/keyrings", { recursive: true, mode: 493 });
4089
- mkdirSync8("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
4090
- mkdirSync8("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
4454
+ mkdirSync9("/usr/share/keyrings", { recursive: true, mode: 493 });
4455
+ mkdirSync9("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
4456
+ mkdirSync9("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
4091
4457
  const key = "/run/cloudflare-warp-key.gpg";
4092
- writeFileSync7(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
4458
+ writeFileSync8(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
4093
4459
  let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
4094
4460
  rmSync7(key, { force: true });
4095
4461
  if (result.exitCode !== 0)
@@ -4097,7 +4463,7 @@ var runProvisionOperation = async (operation) => {
4097
4463
  const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
4098
4464
  if (!codename)
4099
4465
  return { stdout: "Ubuntu codename missing", exitCode: 1 };
4100
- writeFileSync7("/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
4466
+ writeFileSync8("/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
4101
4467
  `, { mode: 420 });
4102
4468
  result = await fixed(["/usr/bin/apt-get", "update", "-qq"]);
4103
4469
  return result.exitCode === 0 ? fixed(["/usr/bin/apt-get", "install", "-y", "cloudflare-warp"]) : result;
@@ -4142,7 +4508,7 @@ async function applyPlan(plan, run2) {
4142
4508
  import { constants } from "node:fs";
4143
4509
  import { createHmac, randomUUID as randomUUID3 } from "node:crypto";
4144
4510
  import { chmod, lstat, mkdir, open, readdir, rename, rmdir, stat, unlink } from "node:fs/promises";
4145
- import { dirname as dirname7, join as join6, resolve as resolve5 } from "node:path";
4511
+ import { dirname as dirname9, join as join6, resolve as resolve5 } from "node:path";
4146
4512
  import { isIP as isIP3 } from "node:net";
4147
4513
 
4148
4514
  // src/cloudflare-edge.ts
@@ -4156,19 +4522,19 @@ var UNIT = `/etc/systemd/system/${FORGEZERO_OTEL_COLLECTOR_UNIT}`;
4156
4522
  import { DEFAULT_SOCKET as DEFAULT_SOCKET2 } from "@forgezero/vault";
4157
4523
 
4158
4524
  // src/bootstrap-bundle.ts
4159
- import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
4525
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
4160
4526
  import {
4161
4527
  chmodSync as chmodSync7,
4162
- createReadStream,
4528
+ createReadStream as createReadStream2,
4163
4529
  existsSync as existsSync9,
4164
4530
  lstatSync as lstatSync3,
4165
- mkdirSync as mkdirSync9,
4531
+ mkdirSync as mkdirSync10,
4166
4532
  readFileSync as readFileSync8,
4167
- renameSync as renameSync8,
4533
+ renameSync as renameSync9,
4168
4534
  rmSync as rmSync8,
4169
- writeFileSync as writeFileSync8
4535
+ writeFileSync as writeFileSync9
4170
4536
  } from "node:fs";
4171
- import { dirname as dirname8, isAbsolute, resolve as resolve6 } from "node:path";
4537
+ import { dirname as dirname10, isAbsolute, resolve as resolve6 } from "node:path";
4172
4538
  var BOOTSTRAP_BUNDLE_FORMAT = 1;
4173
4539
  var BOOTSTRAP_BUNDLE_KIND = "forgezero-api-git-bundle";
4174
4540
  var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
@@ -4477,13 +4843,13 @@ function localBootstrapHost() {
4477
4843
  exists: existsSync10,
4478
4844
  read: (path2) => readFileSync9(path2, "utf8"),
4479
4845
  write(path2, content, mode) {
4480
- mkdirSync10(dirname9(path2), { recursive: true, mode: 493 });
4846
+ mkdirSync11(dirname11(path2), { recursive: true, mode: 493 });
4481
4847
  const temporary = `${path2}.next.${process.pid}`;
4482
- writeFileSync9(temporary, content, { mode });
4848
+ writeFileSync10(temporary, content, { mode });
4483
4849
  chmodSync8(temporary, mode);
4484
- renameSync9(temporary, path2);
4850
+ renameSync10(temporary, path2);
4485
4851
  },
4486
- mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
4852
+ mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
4487
4853
  remove: (path2) => rmSync9(path2, { force: true }),
4488
4854
  inspect(path2) {
4489
4855
  const value = lstatSync4(path2);
@@ -4525,8 +4891,8 @@ function localBootstrapHost() {
4525
4891
  databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
4526
4892
  databasePorts: [8529]
4527
4893
  };
4528
- mkdirSync10(dirname9(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
4529
- writeFileSync9(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
4894
+ mkdirSync11(dirname11(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
4895
+ writeFileSync10(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
4530
4896
  `, { mode: 256 });
4531
4897
  }
4532
4898
  const plan = planBootstrapAgentInstall(config, phase, {
@@ -4536,8 +4902,8 @@ function localBootstrapHost() {
4536
4902
  initialBundle
4537
4903
  });
4538
4904
  for (const unit2 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
4539
- mkdirSync10(dirname9(unit2.path), { recursive: true, mode: 493 });
4540
- writeFileSync9(unit2.path, unit2.unit, { mode: 420 });
4905
+ mkdirSync11(dirname11(unit2.path), { recursive: true, mode: 493 });
4906
+ writeFileSync10(unit2.path, unit2.unit, { mode: 420 });
4541
4907
  }
4542
4908
  await applyPlan(plan, localRunner);
4543
4909
  return plan;