@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/provision.js CHANGED
@@ -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,187 @@ 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
+
1632
+ // src/process-input.ts
1633
+ async function writeAndCloseProcessInput(input, value) {
1634
+ input.write(value);
1635
+ await input.end();
1636
+ }
1637
+
1638
+ // src/deployment-connectivity.ts
1639
+ var TOKEN = /^[A-Za-z0-9._-]{40,16384}$/;
1640
+ 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;
1641
+ 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])?$/;
1642
+ var NAME2 = /^[a-z][a-z0-9._-]{0,99}$/;
1643
+ var idFor = (key) => createHash3("sha256").update(key).digest("hex").slice(0, 20);
1644
+ var checked2 = async (host, argv2, label) => {
1645
+ const result = await host.exec(argv2);
1646
+ if (result.exitCode !== 0)
1647
+ throw new Error(`${label} failed: ${result.output.slice(0, 512)}`);
1648
+ return result.output.trim();
1649
+ };
1650
+ async function sealWithSystemd(name, path2, value) {
1651
+ mkdirSync4(dirname4(path2), { recursive: true, mode: 448 });
1652
+ const next = `${path2}.next`;
1653
+ const child = Bun.spawn(["/usr/bin/systemd-creds", "encrypt", `--name=${name}`, "-", next], {
1654
+ stdin: "pipe",
1655
+ stdout: "pipe",
1656
+ stderr: "pipe",
1657
+ env: { PATH: "/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" }
1658
+ });
1659
+ await writeAndCloseProcessInput(child.stdin, value);
1660
+ const [stdout, stderr, exitCode] = await Promise.all([
1661
+ new Response(child.stdout).text(),
1662
+ new Response(child.stderr).text(),
1663
+ child.exited
1664
+ ]);
1665
+ if (exitCode !== 0)
1666
+ throw new Error(`systemd credential sealing failed: ${`${stdout}${stderr}`.replaceAll(value, "[REDACTED]").slice(0, 512)}`);
1667
+ renameSync4(next, path2);
1668
+ }
1669
+ var defaultHost = {
1670
+ seal: sealWithSystemd,
1671
+ write(path2, content, mode) {
1672
+ mkdirSync4(dirname4(path2), { recursive: true, mode: 493 });
1673
+ const next = `${path2}.next`;
1674
+ writeFileSync4(next, content, { mode });
1675
+ renameSync4(next, path2);
1676
+ },
1677
+ async exec(argv2) {
1678
+ 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" } });
1679
+ const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
1680
+ return { exitCode, output: `${stdout}${stderr}` };
1681
+ },
1682
+ sleep: (ms) => Bun.sleep(ms)
1683
+ };
1684
+ function validate(request) {
1685
+ if (!request.key || request.key.length > 256)
1686
+ throw new Error("deployment connectivity key is invalid");
1687
+ const publicIntent = request.intent.public;
1688
+ const privateIntent = request.intent.private;
1689
+ if (publicIntent?.mode === "cloudflare-tunnel") {
1690
+ 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)) {
1691
+ throw new Error("public deployment connectivity is malformed");
1692
+ }
1693
+ } else if (request.capabilities.public)
1694
+ throw new Error("unexpected public deployment capability");
1695
+ if (privateIntent?.mode === "cloudflare-warp") {
1696
+ 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)) {
1697
+ throw new Error("private deployment connectivity is malformed");
1698
+ }
1699
+ } else if (request.capabilities.private)
1700
+ throw new Error("unexpected private deployment capability");
1701
+ }
1702
+ async function applyDeploymentConnectivity(request, host = defaultHost) {
1703
+ validate(request);
1704
+ const id = idFor(request.key);
1705
+ const evidence = { key: request.key };
1706
+ if (request.intent.public?.mode === "cloudflare-tunnel") {
1707
+ const capability = request.capabilities.public;
1708
+ const credentialName = `FZ_TUNNEL_${id.toUpperCase()}`;
1709
+ const credentialPath = `/etc/forgezero/creds/deployment-tunnel-${id}.cred`;
1710
+ const unit = `forgezero-cloudflared-${id}.service`;
1711
+ const unitPath = `/etc/systemd/system/${unit}`;
1712
+ const metricsPort = 20000 + Number.parseInt(id.slice(0, 4), 16) % 20000;
1713
+ await host.seal(credentialName, credentialPath, capability.connectorToken);
1714
+ host.write(unitPath, `[Unit]
1715
+ Description=ForgeZero deployment Tunnel ${id}
1716
+ After=network-online.target
1717
+ Wants=network-online.target
1718
+
1719
+ [Service]
1720
+ Type=simple
1721
+ DynamicUser=yes
1722
+ LoadCredentialEncrypted=${credentialName}:${credentialPath}
1723
+ ExecStart=/usr/local/bin/cloudflared tunnel --no-autoupdate --metrics 127.0.0.1:${metricsPort} run --token-file %d/${credentialName}
1724
+ Restart=always
1725
+ RestartSec=5
1726
+ NoNewPrivileges=true
1727
+ PrivateTmp=true
1728
+ ProtectSystem=strict
1729
+ ProtectHome=true
1730
+
1731
+ [Install]
1732
+ WantedBy=multi-user.target
1733
+ `, 420);
1734
+ await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "cloudflared daemon reload");
1735
+ await checked2(host, ["/usr/bin/systemctl", "enable", "--now", unit], "cloudflared start");
1736
+ await checked2(host, ["/usr/bin/systemctl", "is-active", "--quiet", unit], "cloudflared readiness");
1737
+ evidence.public = { hostname: request.intent.public.hostname, tunnelId: capability.tunnelId, unit, active: true };
1738
+ }
1739
+ if (request.intent.private?.mode === "cloudflare-warp") {
1740
+ const capability = request.capabilities.private;
1741
+ const credentialPath = "/etc/forgezero/creds/CF_WARP_CONNECTOR_TOKEN.cred";
1742
+ const unit = "forgezero-deployment-mesh.service";
1743
+ await host.seal("CF_WARP_CONNECTOR_TOKEN", credentialPath, capability.connectorToken);
1744
+ host.write(`/etc/systemd/system/${unit}`, `[Unit]
1745
+ Description=ForgeZero deployment Mesh/WARP connector
1746
+ Requires=warp-svc.service
1747
+ After=network-online.target warp-svc.service
1748
+ Wants=network-online.target
1749
+
1750
+ [Service]
1751
+ Type=oneshot
1752
+ RemainAfterExit=yes
1753
+ LoadCredentialEncrypted=CF_WARP_CONNECTOR_TOKEN:${credentialPath}
1754
+ ExecStart=/usr/local/bin/fz-agent mesh-config
1755
+ NoNewPrivileges=true
1756
+ PrivateTmp=true
1757
+ ProtectSystem=strict
1758
+ ProtectHome=true
1759
+ ReadWritePaths=/var/lib/cloudflare-warp
1760
+
1761
+ [Install]
1762
+ WantedBy=multi-user.target
1763
+ `, 420);
1764
+ await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "WARP daemon reload");
1765
+ await checked2(host, ["/usr/bin/systemctl", "enable", "--now", "warp-svc.service", unit], "WARP connector start");
1766
+ for (let attempt = 0;attempt < 10; attempt += 1) {
1767
+ const status = await host.exec(["/usr/bin/warp-cli", "--accept-tos", "status"]);
1768
+ if (status.exitCode === 0 && /\bconnected\b/i.test(status.output) && !/\bdisconnected\b/i.test(status.output)) {
1769
+ evidence.private = { network: request.intent.private.network, connectorId: capability.connectorId, unit, active: true };
1770
+ break;
1771
+ }
1772
+ await host.sleep(1000);
1773
+ }
1774
+ if (!evidence.private)
1775
+ throw new Error("WARP connector readiness failed");
1776
+ }
1777
+ return evidence;
1778
+ }
1779
+
1428
1780
  // src/software-helper.ts
1429
- import { chmodSync as chmodSync4, existsSync as existsSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync3 } from "node:fs";
1781
+ import { chmodSync as chmodSync4, existsSync as existsSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync3 } from "node:fs";
1430
1782
  import { connect as connect2, createServer as createServer2 } from "node:net";
1431
- import { dirname as dirname5 } from "node:path";
1783
+ import { dirname as dirname7 } from "node:path";
1432
1784
 
1433
1785
  // 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";
1786
+ import { createHash as createHash4 } from "node:crypto";
1787
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, readdirSync, realpathSync, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
1788
+ import { dirname as dirname5, join as join4, resolve as resolve3, sep } from "node:path";
1437
1789
  var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
1438
1790
  var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
1439
1791
  var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
1440
1792
  var SERVICE_NGINX_DIRECTORY = "/etc/nginx/conf.d";
1441
- var idFor = (key) => createHash3("sha256").update(key).digest("hex").slice(0, 24);
1793
+ var idFor2 = (key) => createHash4("sha256").update(key).digest("hex").slice(0, 24);
1442
1794
  var statePath = (id) => `${SERVICE_STATE_DIRECTORY}/${id}.json`;
1443
1795
  var within = (root, path2) => path2 === root || path2.startsWith(`${root}${sep}`);
1444
- var defaultHost = {
1796
+ var defaultHost2 = {
1445
1797
  write(path2, content, mode) {
1446
- mkdirSync4(dirname3(path2), { recursive: true, mode: 493 });
1798
+ mkdirSync5(dirname5(path2), { recursive: true, mode: 493 });
1447
1799
  const next = `${path2}.next`;
1448
- writeFileSync4(next, content, { mode });
1449
- renameSync4(next, path2);
1800
+ writeFileSync5(next, content, { mode });
1801
+ renameSync5(next, path2);
1450
1802
  },
1451
1803
  read: (path2) => readFileSync4(path2, "utf8"),
1452
1804
  exists: existsSync4,
1453
1805
  list: (path2) => existsSync4(path2) ? readdirSync(path2) : [],
1454
1806
  realpath: realpathSync,
1455
- mkdir: (path2, mode) => mkdirSync4(path2, { recursive: true, mode }),
1807
+ mkdir: (path2, mode) => mkdirSync5(path2, { recursive: true, mode }),
1456
1808
  remove: (path2) => rmSync4(path2, { force: true }),
1457
1809
  async exec(argv2) {
1458
1810
  const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
@@ -1511,7 +1863,7 @@ function allocatedPorts(request, id, previous, states) {
1511
1863
  if (previous && previous.applicationPorts.length === required && previous.applicationPorts.every((port) => port >= allocation.from && port <= allocation.to && !occupied.has(port)))
1512
1864
  return [...previous.applicationPorts];
1513
1865
  const width = allocation.to - allocation.from + 1;
1514
- const start = Number.parseInt(createHash3("sha256").update(request.key).digest("hex").slice(0, 8), 16) % width;
1866
+ const start = Number.parseInt(createHash4("sha256").update(request.key).digest("hex").slice(0, 8), 16) % width;
1515
1867
  for (let offset = 0;offset < width; offset += 1) {
1516
1868
  const first = allocation.from + (start + offset) % width;
1517
1869
  const candidate = Array.from({ length: required }, (_, index) => first + index);
@@ -1569,12 +1921,12 @@ ${request.service.websocket ? ` proxy_set_header Upgrade $http_upgrade;
1569
1921
  }
1570
1922
  }
1571
1923
  `;
1572
- async function checked2(host, argv2, code) {
1924
+ async function checked3(host, argv2, code) {
1573
1925
  const result = await host.exec(argv2);
1574
1926
  if (result.exitCode !== 0)
1575
1927
  throw new Error(`${code}: ${result.output.slice(0, 512)}`);
1576
1928
  }
1577
- async function activateSupervisedService(request, host = defaultHost) {
1929
+ async function activateSupervisedService(request, host = defaultHost2) {
1578
1930
  if (!/^[A-Za-z0-9][A-Za-z0-9_.:@/-]{0,255}$/.test(request.key) || !/^[a-f0-9]{40}$/.test(request.revision)) {
1579
1931
  throw new Error("SERVICE_IDENTITY_INVALID");
1580
1932
  }
@@ -1583,7 +1935,7 @@ async function activateSupervisedService(request, host = defaultHost) {
1583
1935
  if (!within(root, release))
1584
1936
  throw new Error("SERVICE_RELEASE_OUTSIDE_ROOT");
1585
1937
  request = { ...request, root, release, service: validateDeploymentService(request.service) };
1586
- const id = idFor(request.key);
1938
+ const id = idFor2(request.key);
1587
1939
  host.mkdir(SERVICE_STATE_DIRECTORY, 448);
1588
1940
  host.mkdir(SERVICE_CONFIG_DIRECTORY, 457);
1589
1941
  const previous = readState(host, statePath(id));
@@ -1594,11 +1946,11 @@ async function activateSupervisedService(request, host = defaultHost) {
1594
1946
  const unitName = `forgezero-app-${id}-slot${nextSlot}.service`;
1595
1947
  host.write(configPath, appConfig({ ...request, release }, port), 292);
1596
1948
  host.write(`${SERVICE_UNIT_DIRECTORY}/${unitName}`, unit(id, nextSlot), 420);
1597
- await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "SERVICE_UNIT_RELOAD_FAILED");
1949
+ await checked3(host, ["/usr/bin/systemctl", "daemon-reload"], "SERVICE_UNIT_RELOAD_FAILED");
1598
1950
  if (request.service.strategy === "direct" && previous) {
1599
1951
  await host.exec(["/usr/bin/systemctl", "stop", `forgezero-app-${id}-slot${previous.activeSlot}.service`]);
1600
1952
  }
1601
- await checked2(host, ["/usr/bin/systemctl", "restart", unitName], "SERVICE_START_FAILED");
1953
+ await checked3(host, ["/usr/bin/systemctl", "restart", unitName], "SERVICE_START_FAILED");
1602
1954
  let healthy = false;
1603
1955
  for (let attempt = 0;attempt < 30; attempt += 1) {
1604
1956
  const active = await host.exec(["/usr/bin/systemctl", "is-active", "--quiet", unitName]);
@@ -1616,8 +1968,8 @@ async function activateSupervisedService(request, host = defaultHost) {
1616
1968
  const oldNginx = host.exists(nginxPath) ? host.read(nginxPath) : null;
1617
1969
  try {
1618
1970
  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");
1971
+ await checked3(host, ["/usr/sbin/nginx", "-t"], "SERVICE_NGINX_INVALID");
1972
+ await checked3(host, ["/usr/bin/systemctl", "reload", "nginx.service"], "SERVICE_NGINX_RELOAD_FAILED");
1621
1973
  } catch (cause) {
1622
1974
  if (oldNginx === null)
1623
1975
  host.remove(nginxPath);
@@ -1648,22 +2000,22 @@ async function activateSupervisedService(request, host = defaultHost) {
1648
2000
  }
1649
2001
 
1650
2002
  // 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 = {
2003
+ import { createHash as createHash5 } from "node:crypto";
2004
+ 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";
2005
+ import { dirname as dirname6, resolve as resolve4, sep as sep2 } from "node:path";
2006
+ var defaultHost3 = {
1655
2007
  realpath: realpathSync2,
1656
2008
  exists: existsSync5,
1657
2009
  read: (path2) => readFileSync5(path2, "utf8"),
1658
2010
  write(path2, content, mode) {
1659
- mkdirSync5(dirname4(path2), { recursive: true, mode: 493 });
2011
+ mkdirSync6(dirname6(path2), { recursive: true, mode: 493 });
1660
2012
  const next = `${path2}.next`;
1661
- writeFileSync5(next, content, { mode });
1662
- renameSync5(next, path2);
2013
+ writeFileSync6(next, content, { mode });
2014
+ renameSync6(next, path2);
1663
2015
  },
1664
2016
  remove: (path2) => rmSync5(path2, { force: true }),
1665
2017
  list: (path2) => existsSync5(path2) ? readdirSync2(path2) : [],
1666
- mkdir: (path2, mode) => mkdirSync5(path2, { recursive: true, mode }),
2018
+ mkdir: (path2, mode) => mkdirSync6(path2, { recursive: true, mode }),
1667
2019
  async exec(argv2) {
1668
2020
  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
2021
  const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
@@ -1681,9 +2033,41 @@ var defaultHost2 = {
1681
2033
  now: Date.now
1682
2034
  };
1683
2035
  var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
1684
- var idFor2 = (key) => createHash4("sha256").update(key).digest("hex").slice(0, 24);
2036
+ var idFor3 = (key) => createHash5("sha256").update(key).digest("hex").slice(0, 24);
1685
2037
  var statePath2 = (id) => `${SERVICE_STATE_DIRECTORY}/${id}.json`;
1686
2038
  var nameFor = (id, slot) => `forgezero-${id}-slot${slot}`;
2039
+ function backendFor(application) {
2040
+ const runtime = application.runtime;
2041
+ if (runtime.kind !== "container")
2042
+ throw new Error("CONTAINER_RUNTIME_REQUIRED");
2043
+ if (runtime.provider === "forgezero.docker") {
2044
+ if (runtime.runtimeClass && runtime.runtimeClass !== "runc")
2045
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
2046
+ return { engine: "docker", runtimeClass: "runc", prefix: ["/usr/bin/docker"] };
2047
+ }
2048
+ if (runtime.provider === "forgezero.containerd") {
2049
+ if ((runtime.runtimeClass ?? "runc") !== "runc")
2050
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
2051
+ return {
2052
+ engine: "containerd",
2053
+ runtimeClass: "runc",
2054
+ prefix: ["/usr/local/bin/nerdctl", "--namespace", "forgezero"],
2055
+ runtimeName: "io.containerd.runc.v2"
2056
+ };
2057
+ }
2058
+ if (runtime.provider === "forgezero.kata") {
2059
+ if (runtime.runtimeClass !== "kata-qemu-snp")
2060
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
2061
+ return {
2062
+ engine: "containerd",
2063
+ runtimeClass: "kata-qemu-snp",
2064
+ prefix: ["/usr/local/bin/nerdctl", "--namespace", "forgezero"],
2065
+ runtimeName: "io.containerd.kata.v2"
2066
+ };
2067
+ }
2068
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_UNSUPPORTED");
2069
+ }
2070
+ var command2 = (backend, ...argv2) => [...backend.prefix, ...argv2];
1687
2071
  function validateRequest(request, host) {
1688
2072
  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
2073
  throw new Error("CONTAINER_IDENTITY_INVALID");
@@ -1694,6 +2078,7 @@ function validateRequest(request, host) {
1694
2078
  const application = request.application;
1695
2079
  if (application.kind !== "application" || application.runtime.kind !== "container")
1696
2080
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
2081
+ backendFor(application);
1697
2082
  if (!application.resources.cpu || !application.resources.memory || !application.resources.pids)
1698
2083
  throw new Error("CONTAINER_LIMITS_REQUIRED");
1699
2084
  if (application.runtime.security.privileged !== false || application.runtime.security.noNewPrivileges !== true || !application.runtime.security.dropCapabilities.includes("ALL"))
@@ -1704,23 +2089,24 @@ function validateRequest(request, host) {
1704
2089
  throw new Error("CONTAINER_STABLE_PORT_REQUIRED");
1705
2090
  return { ...request, root, release };
1706
2091
  }
1707
- async function checked3(host, argv2, code) {
2092
+ async function checked4(host, argv2, code) {
1708
2093
  const result = await host.exec(argv2);
1709
2094
  if (result.exitCode !== 0)
1710
2095
  throw new Error(`${code}: ${result.output.slice(0, 512)}`);
1711
2096
  return result.output.trim();
1712
2097
  }
1713
- async function buildContainerImage(requestValue, host = defaultHost2) {
2098
+ async function buildContainerImage(requestValue, host = defaultHost3) {
1714
2099
  const request = validateRequest(requestValue, host);
1715
2100
  const runtime = request.application.runtime;
1716
2101
  if (runtime.kind !== "container")
1717
2102
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
2103
+ const backend = backendFor(request.application);
1718
2104
  const source = runtime.image.source;
1719
2105
  if (source.kind === "registry") {
1720
2106
  if (!source.reference.includes("@sha256:"))
1721
2107
  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");
2108
+ await checked4(host, command2(backend, "pull", "--quiet", source.reference), "CONTAINER_PULL_FAILED");
2109
+ const digest2 = await checked4(host, command2(backend, "image", "inspect", "--format={{.Id}}", source.reference), "CONTAINER_INSPECT_FAILED");
1724
2110
  if (!/^sha256:[a-f0-9]{64}$/.test(digest2))
1725
2111
  throw new Error("CONTAINER_DIGEST_INVALID");
1726
2112
  return { image: source.reference, digest: digest2 };
@@ -1729,23 +2115,9 @@ async function buildContainerImage(requestValue, host = defaultHost2) {
1729
2115
  const dockerfile = host.realpath(resolve4(request.release, source.dockerfile));
1730
2116
  if (!within2(request.release, context) || !within2(context, dockerfile))
1731
2117
  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");
2118
+ const tag = `forgezero/${idFor3(request.key)}:${request.revision}`;
2119
+ 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");
2120
+ const digest = await checked4(host, command2(backend, "image", "inspect", "--format={{.Id}}", tag), "CONTAINER_INSPECT_FAILED");
1749
2121
  if (!/^sha256:[a-f0-9]{64}$/.test(digest))
1750
2122
  throw new Error("CONTAINER_DIGEST_INVALID");
1751
2123
  return { image: tag, digest };
@@ -1813,7 +2185,7 @@ ${service.websocket ? ` proxy_set_header Upgrade $http_upgrade;
1813
2185
  }
1814
2186
  `;
1815
2187
  }
1816
- function runArgv(request, id, slot, port, networkName) {
2188
+ function runArgv(request, backend, id, slot, port, networkName) {
1817
2189
  const { application } = request;
1818
2190
  const resources = application.resources;
1819
2191
  const runtime = application.runtime;
@@ -1821,35 +2193,7 @@ function runArgv(request, id, slot, port, networkName) {
1821
2193
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
1822
2194
  const memory = resources.memory;
1823
2195
  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
- ];
2196
+ 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
2197
  for (const mount of application.storage ?? []) {
1854
2198
  if (mount.class === "ephemeral")
1855
2199
  argv2.push("--tmpfs", `${mount.path}:rw,noexec,nosuid,size=${mount.sizeMiB}m`);
@@ -1859,11 +2203,12 @@ function runArgv(request, id, slot, port, networkName) {
1859
2203
  argv2.push(request.image, ...runtime.entrypoint ?? []);
1860
2204
  return argv2;
1861
2205
  }
1862
- async function activateContainer(requestValue, host = defaultHost2) {
2206
+ async function activateContainer(requestValue, host = defaultHost3) {
1863
2207
  const request = validateRequest(requestValue, host);
1864
2208
  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
2209
  throw new Error("CONTAINER_IMAGE_INVALID");
1866
- const id = idFor2(request.key);
2210
+ const backend = backendFor(request.application);
2211
+ const id = idFor3(request.key);
1867
2212
  host.mkdir(SERVICE_STATE_DIRECTORY, 448);
1868
2213
  host.mkdir(SERVICE_NGINX_DIRECTORY, 493);
1869
2214
  const previous = readState2(host, id);
@@ -1875,9 +2220,9 @@ async function activateContainer(requestValue, host = defaultHost2) {
1875
2220
  if (containerNetwork.mode === "bridge") {
1876
2221
  networkName = containerNetwork.network ? `forgezero-${id}-${containerNetwork.network}` : "bridge";
1877
2222
  if (networkName !== "bridge") {
1878
- const inspected = await host.exec(["/usr/bin/docker", "network", "inspect", '--format={{index .Labels "net.forgezero.owner"}}', networkName]);
2223
+ const inspected = await host.exec(command2(backend, "network", "inspect", '--format={{index .Labels "net.forgezero.owner"}}', networkName));
1879
2224
  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");
2225
+ await checked4(host, command2(backend, "network", "create", "--driver", "bridge", "--label", `net.forgezero.owner=${id}`, networkName), "CONTAINER_NETWORK_CREATE_FAILED");
1881
2226
  } else if (inspected.output.trim() !== id)
1882
2227
  throw new Error("CONTAINER_NETWORK_OWNERSHIP_INVALID");
1883
2228
  }
@@ -1886,8 +2231,8 @@ async function activateContainer(requestValue, host = defaultHost2) {
1886
2231
  const nextSlot = slots === 2 && previous?.activeSlot === 0 ? 1 : 0;
1887
2232
  const port = ports[nextSlot];
1888
2233
  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");
2234
+ await host.exec(command2(backend, "rm", "--force", name));
2235
+ await checked4(host, runArgv(request, backend, id, nextSlot, port, networkName), "CONTAINER_START_FAILED");
1891
2236
  let healthy = false;
1892
2237
  const health = request.application.service.health;
1893
2238
  for (let attempt = 0;attempt < (health.attempts ?? 30); attempt += 1) {
@@ -1898,15 +2243,28 @@ async function activateContainer(requestValue, host = defaultHost2) {
1898
2243
  await host.sleep(health.intervalMs ?? 1000);
1899
2244
  }
1900
2245
  if (!healthy) {
1901
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
2246
+ await host.exec(command2(backend, "rm", "--force", name));
1902
2247
  throw new Error("CONTAINER_HEALTH_FAILED");
1903
2248
  }
2249
+ let runtimeEvidence;
2250
+ if (backend.engine === "containerd") {
2251
+ const raw = await checked4(host, ["/usr/bin/ctr", "--namespace", "forgezero", "containers", "info", name], "CONTAINER_RUNTIME_EVIDENCE_FAILED");
2252
+ let observed = "";
2253
+ try {
2254
+ observed = String(JSON.parse(raw).Runtime?.Name ?? "");
2255
+ } catch {
2256
+ throw new Error("CONTAINER_RUNTIME_EVIDENCE_INVALID");
2257
+ }
2258
+ if (observed !== backend.runtimeName)
2259
+ throw new Error("CONTAINER_RUNTIME_DOWNGRADE");
2260
+ runtimeEvidence = observed;
2261
+ }
1904
2262
  const nginxPath = `${SERVICE_NGINX_DIRECTORY}/forgezero-${id}.conf`;
1905
2263
  const previousNginx = host.exists(nginxPath) ? host.read(nginxPath) : null;
1906
2264
  host.write(nginxPath, nginx2(request, id, port), 420);
1907
2265
  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");
2266
+ await checked4(host, ["/usr/sbin/nginx", "-t"], "CONTAINER_NGINX_INVALID");
2267
+ await checked4(host, ["/usr/bin/systemctl", "reload", "nginx.service"], "CONTAINER_NGINX_RELOAD_FAILED");
1910
2268
  } catch (cause) {
1911
2269
  if (previousNginx === null)
1912
2270
  host.remove(nginxPath);
@@ -1914,7 +2272,7 @@ async function activateContainer(requestValue, host = defaultHost2) {
1914
2272
  host.write(nginxPath, previousNginx, 420);
1915
2273
  await host.exec(["/usr/sbin/nginx", "-t"]);
1916
2274
  await host.exec(["/usr/bin/systemctl", "reload", "nginx.service"]);
1917
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
2275
+ await host.exec(command2(backend, "rm", "--force", name));
1918
2276
  throw cause;
1919
2277
  }
1920
2278
  const state = {
@@ -1927,13 +2285,18 @@ async function activateContainer(requestValue, host = defaultHost2) {
1927
2285
  applicationPorts: ports,
1928
2286
  activeSlot: nextSlot,
1929
2287
  healthPath: health.path,
2288
+ containerRuntime: {
2289
+ engine: backend.engine,
2290
+ runtimeClass: backend.runtimeClass,
2291
+ ...backend.engine === "containerd" ? { namespace: "forgezero", evidence: runtimeEvidence } : {}
2292
+ },
1930
2293
  updatedAtTs: host.now()
1931
2294
  };
1932
2295
  host.write(statePath2(id), `${JSON.stringify(state, null, 2)}
1933
2296
  `, 384);
1934
2297
  if (previous && previous.activeSlot !== nextSlot) {
1935
2298
  await host.sleep(request.application.rollout.drainMs ?? 30000);
1936
- await host.exec(["/usr/bin/docker", "rm", "--force", nameFor(id, previous.activeSlot)]);
2299
+ await host.exec(command2(backend, "rm", "--force", nameFor(id, previous.activeSlot)));
1937
2300
  }
1938
2301
  return state;
1939
2302
  }
@@ -1948,11 +2311,12 @@ function startSoftwareHelper(options = {}) {
1948
2311
  const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
1949
2312
  if (existsSync6(socketPath))
1950
2313
  unlinkSync3(socketPath);
1951
- mkdirSync6(dirname5(socketPath), { recursive: true, mode: 488 });
2314
+ mkdirSync7(dirname7(socketPath), { recursive: true, mode: 488 });
1952
2315
  const ensure = options.ensure ?? ensureSoftwareRequirements;
1953
2316
  const activate = options.activate ?? activateSupervisedService;
1954
2317
  const buildContainer = options.buildContainer ?? buildContainerImage;
1955
2318
  const activateTypedContainer = options.activateContainer ?? activateContainer;
2319
+ const applyConnectivity = options.applyConnectivity ?? applyDeploymentConnectivity;
1956
2320
  let tail = Promise.resolve();
1957
2321
  let pending = 0;
1958
2322
  const server = createServer2((socket) => {
@@ -2009,6 +2373,12 @@ function startSoftwareHelper(options = {}) {
2009
2373
  throw new Error("container deployment root is not owned by this helper");
2010
2374
  const state = await activateTypedContainer(containerRequest);
2011
2375
  socket.end(`${JSON.stringify({ ok: true, state })}
2376
+ `);
2377
+ return;
2378
+ }
2379
+ if (request.op === "apply-connectivity" && request.request) {
2380
+ const result = await applyConnectivity(request.request);
2381
+ socket.end(`${JSON.stringify({ ok: true, result })}
2012
2382
  `);
2013
2383
  return;
2014
2384
  }
@@ -2066,6 +2436,9 @@ function requestContainerBuild(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOC
2066
2436
  function requestContainerActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
2067
2437
  return requestContainer("activate-container", request, socketPath, timeoutMs);
2068
2438
  }
2439
+ function requestDeploymentConnectivity(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
2440
+ return requestContainer("apply-connectivity", request, socketPath, timeoutMs);
2441
+ }
2069
2442
  function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 10 * 60000) {
2070
2443
  return new Promise((resolve5, reject) => {
2071
2444
  const socket = connect2(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
@@ -2125,7 +2498,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2125
2498
  }
2126
2499
 
2127
2500
  // src/version.ts
2128
- var VERSION3 = "0.1.74";
2501
+ var VERSION3 = "0.1.76";
2129
2502
 
2130
2503
  // src/egress-policy.ts
2131
2504
  import { realpathSync as realpathSync3 } from "node:fs";