@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
@@ -38,6 +38,13 @@ function validateBuiltInAction(definition, context, where) {
38
38
  throw new Error(`${where}.with.requirements must name existing requirements`);
39
39
  return;
40
40
  }
41
+ if (definition.uses === "forgezero.network/ensure@1") {
42
+ exact(withValue, ["target"], `${where}.with`);
43
+ if (typeof withValue.target !== "string" || !Object.values(context.components).some((entry) => entry.target === withValue.target)) {
44
+ throw new Error(`${where}.with.target must name a target used by a component`);
45
+ }
46
+ return;
47
+ }
41
48
  if (definition.uses === "forgezero.exec/argv@1") {
42
49
  const allowed = ["component", "argv", "credentials"];
43
50
  const unknown = Object.keys(withValue).filter((key) => !allowed.includes(key));
@@ -93,6 +100,8 @@ function validateBuiltInProvider(requirement2, where) {
93
100
  const expected = {
94
101
  "forgezero.bun": { capability: "runtime.javascript", versions: ["1.3.14"], keys: ["installScope"] },
95
102
  "forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
103
+ "forgezero.containerd": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "runtimeClass", "namespace", "snapshotter"] },
104
+ "forgezero.kata": { capability: "runtime.container", versions: ["4.0.0"], keys: ["installScope", "runtimeClass", "namespace", "hypervisor", "confidentialGuest"] },
96
105
  "forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
97
106
  "forgezero.arangodb": { capability: "database.arangodb", versions: ["3.11.14"], keys: ["installScope", "runtime", "dataPath", "bind"] },
98
107
  "forgezero.cloudflared": { capability: "network.public-tunnel", versions: ["2026.7.3"], keys: ["installScope", "mode"] },
@@ -113,6 +122,10 @@ function validateBuiltInProvider(requirement2, where) {
113
122
  if (row2.storageDriver !== "overlay2" || row2.dataRoot !== "/var/lib/docker" || row2.liveRestore !== true || row2.logDriver !== "local" || row2.defaultNetwork !== "bridge" || row2.rootless !== false)
114
123
  throw new Error(`${where} Docker config is unsupported by the fixed host strategy`);
115
124
  }
125
+ if (requirement2.provider === "forgezero.containerd" && (row2.runtimeClass !== "runc" || row2.namespace !== "forgezero" || row2.snapshotter !== "overlayfs"))
126
+ throw new Error(`${where} containerd config is unsupported by the fixed host strategy`);
127
+ if (requirement2.provider === "forgezero.kata" && (row2.runtimeClass !== "kata-qemu-snp" || row2.namespace !== "forgezero" || row2.hypervisor !== "qemu" || row2.confidentialGuest !== "sev-snp"))
128
+ throw new Error(`${where} Kata config is unsupported by the fixed host strategy`);
116
129
  if (requirement2.provider === "forgezero.nginx" && (typeof row2.websocket !== "boolean" || !Number.isInteger(row2.maximumBodyMiB) || Number(row2.maximumBodyMiB) < 1 || Number(row2.maximumBodyMiB) > 1024 || !(row2.workerProcesses === "auto" || Number.isInteger(row2.workerProcesses)) || !Number.isInteger(row2.workerConnections)))
117
130
  throw new Error(`${where} Nginx config is invalid`);
118
131
  if (requirement2.provider === "forgezero.arangodb" && (row2.runtime !== "native" || !absolute(row2.dataPath) || row2.bind !== "private"))
@@ -190,7 +203,7 @@ function reference(value, where) {
190
203
  const row2 = object(value, where);
191
204
  exact3(row2, ["$ref"], where);
192
205
  const coordinate = boundedString(row2.$ref, `${where}.$ref`, 256);
193
- if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62})$/.test(coordinate)) {
206
+ if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62}|execution\.(targetName|slot|releaseExecutor|runtime|cpuCores|memoryMiB|storageGiB))$/.test(coordinate)) {
194
207
  fail(where, "contains an unsupported reference.");
195
208
  }
196
209
  return { $ref: coordinate };
@@ -430,10 +443,9 @@ function validateNetwork(value, where) {
430
443
  named(container.network, `${where}.container.network`);
431
444
  }
432
445
  }
433
- function validateComponent(value, where, targetNames, requirements) {
446
+ function validateComponent(value, where, targets, requirements) {
434
447
  const row2 = object(value, where);
435
- if (!targetNames.has(String(row2.target)))
436
- fail(`${where}.target`, "must name an existing target.");
448
+ const selectedTarget = targets.get(String(row2.target)) ?? fail(`${where}.target`, "must name an existing target.");
437
449
  if (row2.kind === "application") {
438
450
  exact3(row2, ["kind", "target", "runtime", "service", "resources", "storage", "network", "rollout"], where);
439
451
  const runtime = object(row2.runtime, `${where}.runtime`);
@@ -444,7 +456,9 @@ function validateComponent(value, where, targetNames, requirements) {
444
456
  exact3(runtime, ["kind", "provider", "requirement", "argv"], `${where}.runtime`);
445
457
  validateArgv(runtime.argv, `${where}.runtime.argv`);
446
458
  } else if (runtime.kind === "container") {
447
- exact3(runtime, ["kind", "provider", "requirement", "image", "entrypoint", "security"], `${where}.runtime`);
459
+ exact3(runtime, ["kind", "provider", "requirement", "runtimeClass", "image", "entrypoint", "security"], `${where}.runtime`);
460
+ if (runtime.runtimeClass !== undefined && !["runc", "kata-qemu-snp"].includes(String(runtime.runtimeClass)))
461
+ fail(`${where}.runtime.runtimeClass`, "is unsupported.");
448
462
  const image = object(runtime.image, `${where}.runtime.image`);
449
463
  exact3(image, ["source"], `${where}.runtime.image`);
450
464
  const source = object(image.source, `${where}.runtime.image.source`);
@@ -478,6 +492,23 @@ function validateComponent(value, where, targetNames, requirements) {
478
492
  fail(`${where}.runtime.requirement`, "must provide runtime.container.");
479
493
  if (runtimeRequirement && runtime.kind === "native" && !runtimeRequirement.capability.startsWith("runtime."))
480
494
  fail(`${where}.runtime.requirement`, "must provide a runtime capability.");
495
+ const execution = selectedTarget.allocation.execution;
496
+ if (execution === "native" && runtime.kind !== "native")
497
+ fail(`${where}.runtime.kind`, "must be native for the selected target.");
498
+ if (execution !== "native" && runtime.kind !== "container")
499
+ fail(`${where}.runtime.kind`, "must be container for the selected target.");
500
+ if (runtime.kind === "container" && execution === "oci-runc") {
501
+ if (!["forgezero.containerd", "forgezero.docker"].includes(String(runtime.provider)))
502
+ fail(`${where}.runtime.provider`, "must be forgezero.containerd for an OCI runc target.");
503
+ if (runtime.runtimeClass !== undefined && runtime.runtimeClass !== "runc")
504
+ fail(`${where}.runtime.runtimeClass`, "must be runc for the selected target.");
505
+ }
506
+ if (runtime.kind === "container" && execution === "oci-kata-qemu-snp") {
507
+ if (runtime.provider !== "forgezero.kata")
508
+ fail(`${where}.runtime.provider`, "must be forgezero.kata for a Kata QEMU SNP target.");
509
+ if (runtime.runtimeClass !== "kata-qemu-snp")
510
+ fail(`${where}.runtime.runtimeClass`, "must be kata-qemu-snp for the selected target.");
511
+ }
481
512
  const service = object(row2.service, `${where}.service`);
482
513
  exact3(service, ["protocol", "port", "health", "websocket", "maximumConnections"], `${where}.service`);
483
514
  if (service.protocol !== "http")
@@ -509,6 +540,18 @@ function validateComponent(value, where, targetNames, requirements) {
509
540
  const network = object(row2.network, `${where}.network`);
510
541
  if (network.container === undefined)
511
542
  fail(`${where}.network.container`, "must select bridge or reviewed host networking.");
543
+ if (selectedTarget.allocation.sharing === "shared") {
544
+ if (object(runtime.security, `${where}.runtime.security`).root !== "read-only")
545
+ fail(`${where}.runtime.security.root`, "must be read-only on a shared compute.");
546
+ if (network.container.mode !== "bridge")
547
+ fail(`${where}.network.container.mode`, "must be bridge on a shared compute.");
548
+ if (Array.isArray(row2.storage) && row2.storage.some((mount) => mount.class !== "ephemeral")) {
549
+ fail(`${where}.storage`, "may contain only size-bounded tmpfs mounts on a shared compute.");
550
+ }
551
+ }
552
+ }
553
+ if (runtime.kind === "native" && selectedTarget.allocation.sharing !== "exclusive") {
554
+ fail(`${where}.target`, "native applications require an exclusive compute.");
512
555
  }
513
556
  if (row2.storage !== undefined) {
514
557
  if (!Array.isArray(row2.storage) || row2.storage.length > 32)
@@ -545,6 +588,8 @@ function validateComponent(value, where, targetNames, requirements) {
545
588
  fail(`${where}.runtime.mode`, "must match the provider runtime mode.");
546
589
  if (runtime.mode !== "native")
547
590
  fail(`${where}.runtime.mode`, "must be native in deployment plan v1.");
591
+ if (selectedTarget.allocation.execution !== "native")
592
+ fail(`${where}.target`, "must select a native execution target for database components.");
548
593
  const topology = object(row2.topology, `${where}.topology`);
549
594
  if (topology.mode === "standalone")
550
595
  exact3(topology, ["mode"], `${where}.topology`);
@@ -741,15 +786,20 @@ function compileDeployment(sourceValue) {
741
786
  if (targetEntries.length < 1 || targetEntries.length > 64)
742
787
  fail("deployment.spec.targets", "must contain 1 to 64 targets.");
743
788
  const targetNames = new Set(targetEntries.map(([targetName]) => named(targetName, "deployment.spec.targets key")));
789
+ const targetDefinitions = new Map;
744
790
  for (const [targetName, value] of targetEntries) {
745
791
  const target = object(value, `deployment.spec.targets.${targetName}`);
746
- exact3(target, ["kind", "selector", "cardinality", "placement"], `deployment.spec.targets.${targetName}`);
792
+ exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "placement"], `deployment.spec.targets.${targetName}`);
747
793
  if (target.kind !== "compute")
748
794
  fail(`deployment.spec.targets.${targetName}.kind`, "must be compute.");
749
795
  const selector = object(target.selector, `deployment.spec.targets.${targetName}.selector`);
750
- exact3(selector, ["profiles", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
796
+ exact3(selector, ["profiles", "operatingSystem", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
751
797
  if (!Array.isArray(selector.profiles) || selector.profiles.length < 1 || selector.profiles.length > 32 || selector.profiles.some((profile) => typeof profile !== "string" || !NAME.test(profile)) || new Set(selector.profiles).size !== selector.profiles.length)
752
798
  fail(`deployment.spec.targets.${targetName}.selector.profiles`, "is invalid.");
799
+ const operatingSystem = object(selector.operatingSystem, `deployment.spec.targets.${targetName}.selector.operatingSystem`);
800
+ exact3(operatingSystem, ["id", "version", "architecture"], `deployment.spec.targets.${targetName}.selector.operatingSystem`);
801
+ if (operatingSystem.id !== "ubuntu" || !["24.04", "26.04"].includes(String(operatingSystem.version)) || operatingSystem.architecture !== "x64")
802
+ fail(`deployment.spec.targets.${targetName}.selector.operatingSystem`, "is unsupported.");
753
803
  if (selector.confidentialCompute !== undefined && !["required", "preferred", "disabled"].includes(String(selector.confidentialCompute)))
754
804
  fail(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "is unsupported.");
755
805
  if (selector.labels !== undefined)
@@ -762,8 +812,108 @@ function compileDeployment(sourceValue) {
762
812
  integer(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`, minimum, maximum);
763
813
  else
764
814
  reference(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`);
815
+ const allocation = object(target.allocation, `deployment.spec.targets.${targetName}.allocation`);
816
+ exact3(allocation, ["execution", "sharing", "resources", "existing"], `deployment.spec.targets.${targetName}.allocation`);
817
+ if (!["native", "oci-runc", "oci-kata-qemu-snp"].includes(String(allocation.execution)))
818
+ fail(`deployment.spec.targets.${targetName}.allocation.execution`, "is unsupported.");
819
+ if (!["exclusive", "shared"].includes(String(allocation.sharing)))
820
+ fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "is unsupported.");
821
+ if (!["prefer", "require"].includes(String(allocation.existing)))
822
+ fail(`deployment.spec.targets.${targetName}.allocation.existing`, "is unsupported.");
823
+ const capacity = object(allocation.resources, `deployment.spec.targets.${targetName}.allocation.resources`);
824
+ exact3(capacity, ["cpuCores", "memoryMiB", "storageGiB"], `deployment.spec.targets.${targetName}.allocation.resources`);
825
+ const capacityValue = (value2, where, minimumValue, maximumValue) => {
826
+ if (typeof value2 === "number")
827
+ integer(value2, where, minimumValue, maximumValue);
828
+ else
829
+ reference(value2, where);
830
+ };
831
+ capacityValue(capacity.cpuCores, `deployment.spec.targets.${targetName}.allocation.resources.cpuCores`, 1, 1024);
832
+ capacityValue(capacity.memoryMiB, `deployment.spec.targets.${targetName}.allocation.resources.memoryMiB`, 128, 4194304);
833
+ capacityValue(capacity.storageGiB, `deployment.spec.targets.${targetName}.allocation.resources.storageGiB`, 1, 1048576);
834
+ if (allocation.execution === "oci-kata-qemu-snp" && selector.confidentialCompute !== "required")
835
+ fail(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "must be required for Kata QEMU SNP.");
836
+ if ((allocation.execution === "oci-kata-qemu-snp" || selector.confidentialCompute === "required") && operatingSystem.version !== "26.04")
837
+ fail(`deployment.spec.targets.${targetName}.selector.operatingSystem.version`, "must be Ubuntu 26.04 for SEV-SNP.");
838
+ if (allocation.execution !== "oci-kata-qemu-snp" && selector.confidentialCompute === "required" && allocation.execution !== "native")
839
+ fail(`deployment.spec.targets.${targetName}.allocation.execution`, "cannot satisfy required confidential compute.");
840
+ if (target.provisioning !== undefined) {
841
+ const provisioning = object(target.provisioning, `deployment.spec.targets.${targetName}.provisioning`);
842
+ exact3(provisioning, ["planKey", "regionKey", "imageKey", "environmentKey", "ownership", "metalHostname", "maxMonthlySpendMinor"], `deployment.spec.targets.${targetName}.provisioning`);
843
+ for (const key of ["planKey", "regionKey", "imageKey", "environmentKey"])
844
+ named(provisioning[key], `deployment.spec.targets.${targetName}.provisioning.${key}`);
845
+ if (!["platform", "tenant-metal"].includes(String(provisioning.ownership)) || provisioning.ownership === "tenant-metal" !== Boolean(provisioning.metalHostname))
846
+ fail(`deployment.spec.targets.${targetName}.provisioning.ownership`, "must bind platform capacity or one tenant metal hostname.");
847
+ if (provisioning.metalHostname !== undefined && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(String(provisioning.metalHostname)))
848
+ fail(`deployment.spec.targets.${targetName}.provisioning.metalHostname`, "is invalid.");
849
+ if (typeof provisioning.maxMonthlySpendMinor !== "string" || !/^\d{1,18}$/.test(provisioning.maxMonthlySpendMinor))
850
+ fail(`deployment.spec.targets.${targetName}.provisioning.maxMonthlySpendMinor`, "must be a bounded decimal minor-unit amount.");
851
+ }
852
+ if (allocation.existing === "prefer" && target.provisioning === undefined)
853
+ fail(`deployment.spec.targets.${targetName}.provisioning`, "is required when placement may provision after reuse.");
854
+ if (allocation.existing === "require" && target.provisioning !== undefined)
855
+ fail(`deployment.spec.targets.${targetName}.provisioning`, "must be omitted for reuse-only placement.");
856
+ if (target.connectivity !== undefined) {
857
+ const connectivity = object(target.connectivity, `deployment.spec.targets.${targetName}.connectivity`);
858
+ exact3(connectivity, ["private", "public"], `deployment.spec.targets.${targetName}.connectivity`);
859
+ const declaredPrivate = connectivity.private === undefined ? undefined : object(connectivity.private, `deployment.spec.targets.${targetName}.connectivity.private`);
860
+ if (declaredPrivate?.mode === "cloudflare-warp" && allocation.sharing !== "exclusive")
861
+ fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "must be exclusive for a Cloudflare WARP connector.");
862
+ if (connectivity.private !== undefined) {
863
+ const privateNetwork = declaredPrivate;
864
+ if (privateNetwork.mode === "cloudflare-warp") {
865
+ exact3(privateNetwork, ["mode", "credential", "network"], `deployment.spec.targets.${targetName}.connectivity.private`);
866
+ if (!credentials[named(privateNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.private.credential`)])
867
+ fail(`deployment.spec.targets.${targetName}.connectivity.private.credential`, "must name a declared credential.");
868
+ if (typeof privateNetwork.network === "string")
869
+ named(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
870
+ else
871
+ reference(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
872
+ } else {
873
+ exact3(privateNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.private`);
874
+ if (!["disabled", "private-lan"].includes(String(privateNetwork.mode)))
875
+ fail(`deployment.spec.targets.${targetName}.connectivity.private.mode`, "is unsupported.");
876
+ }
877
+ }
878
+ if (connectivity.public !== undefined) {
879
+ const publicNetwork = object(connectivity.public, `deployment.spec.targets.${targetName}.connectivity.public`);
880
+ if (publicNetwork.mode === "cloudflare-tunnel") {
881
+ exact3(publicNetwork, ["mode", "credential", "zone", "hostname"], `deployment.spec.targets.${targetName}.connectivity.public`);
882
+ if (!credentials[named(publicNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.public.credential`)])
883
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.credential`, "must name a declared credential.");
884
+ if (typeof publicNetwork.zone === "string") {
885
+ if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(publicNetwork.zone))
886
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.zone`, "is invalid.");
887
+ } else
888
+ reference(publicNetwork.zone, `deployment.spec.targets.${targetName}.connectivity.public.zone`);
889
+ const hostname = object(publicNetwork.hostname, `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
890
+ const value2 = (coordinate, where, pattern) => {
891
+ if (typeof coordinate === "string") {
892
+ if (!pattern.test(coordinate))
893
+ fail(where, "is invalid.");
894
+ } else
895
+ reference(coordinate, where);
896
+ };
897
+ if (hostname.mode === "static") {
898
+ exact3(hostname, ["mode", "label"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
899
+ value2(hostname.label, `deployment.spec.targets.${targetName}.connectivity.public.hostname.label`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
900
+ } else if (hostname.mode === "indexed") {
901
+ exact3(hostname, ["mode", "prefix", "startAt"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
902
+ value2(hostname.prefix, `deployment.spec.targets.${targetName}.connectivity.public.hostname.prefix`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
903
+ if (hostname.startAt !== undefined)
904
+ integer(hostname.startAt, `deployment.spec.targets.${targetName}.connectivity.public.hostname.startAt`, 0, 1e6);
905
+ } else
906
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.hostname.mode`, "is unsupported.");
907
+ } else {
908
+ exact3(publicNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.public`);
909
+ if (publicNetwork.mode !== "disabled")
910
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.mode`, "is unsupported.");
911
+ }
912
+ }
913
+ }
765
914
  if (target.placement !== undefined)
766
915
  jsonValue(target.placement, `deployment.spec.targets.${targetName}.placement`);
916
+ targetDefinitions.set(targetName, target);
767
917
  }
768
918
  const requirements = object(spec.requirements ?? {}, "deployment.spec.requirements");
769
919
  if (Object.keys(requirements).length > 64)
@@ -777,13 +927,34 @@ function compileDeployment(sourceValue) {
777
927
  const key = `${requirementValue.provider}@${requirementValue.contract}:${requirementValue.capability}`;
778
928
  requiredProviders.set(key, { provider: requirementValue.provider, contract: requirementValue.contract, capability: requirementValue.capability });
779
929
  }
930
+ for (const [targetName, target] of targetDefinitions) {
931
+ if (target.connectivity?.public?.mode === "cloudflare-tunnel" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.public-tunnel")) {
932
+ fail(`deployment.spec.targets.${targetName}.connectivity.public`, "requires one network.public-tunnel provider requirement.");
933
+ }
934
+ if (target.connectivity?.private?.mode === "cloudflare-warp" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
935
+ fail(`deployment.spec.targets.${targetName}.connectivity.private`, "requires one network.private provider requirement.");
936
+ }
937
+ }
780
938
  const components = object(spec.components, "deployment.spec.components");
781
939
  const componentEntries = Object.entries(components);
782
940
  if (componentEntries.length < 1 || componentEntries.length > 128)
783
941
  fail("deployment.spec.components", "must contain 1 to 128 components.");
784
942
  for (const [componentName, value] of componentEntries) {
785
943
  named(componentName, "deployment.spec.components key");
786
- validateComponent(value, `deployment.spec.components.${componentName}`, targetNames, requirementDefinitions);
944
+ validateComponent(value, `deployment.spec.components.${componentName}`, targetDefinitions, requirementDefinitions);
945
+ }
946
+ for (const [targetName, target] of targetDefinitions) {
947
+ if (target.connectivity?.public?.mode !== "cloudflare-tunnel")
948
+ continue;
949
+ const publicApplications = componentEntries.filter(([, component2]) => {
950
+ const candidate = component2;
951
+ return candidate.kind === "application" && candidate.target === targetName && candidate.network?.public?.mode === "cloudflare-tunnel";
952
+ });
953
+ if (publicApplications.length !== 1)
954
+ fail(`deployment.spec.targets.${targetName}.connectivity.public`, "requires exactly one application on the target with network.public cloudflare-tunnel.");
955
+ const application = publicApplications[0][1];
956
+ if (application.kind !== "application" || !application.network?.ingress?.stablePort)
957
+ fail(`deployment.spec.components.${publicApplications[0][0]}.network.ingress`, "requires one stablePort for Tunnel ingress.");
787
958
  }
788
959
  const actionContext = {
789
960
  components,
@@ -825,6 +996,14 @@ function compileDeployment(sourceValue) {
825
996
  });
826
997
  plannedWorkflows.push({ id: workflowName, ...workflowValue.concurrency ? { concurrency: workflowValue.concurrency } : {}, stages: plannedStages });
827
998
  }
999
+ const deployWorkflow = plannedWorkflows.find(({ id }) => id === "deploy");
1000
+ for (const [targetName, target] of targetDefinitions) {
1001
+ if (!target.connectivity || target.connectivity.public?.mode !== "cloudflare-tunnel" && target.connectivity.private?.mode !== "cloudflare-warp")
1002
+ continue;
1003
+ const ensures = deployWorkflow?.stages.flatMap(({ steps }) => steps).filter((step2) => step2.uses === "forgezero.network/ensure@1" && step2.with.target === targetName && (step2.scope.kind === "each-target" || step2.scope.kind === "target-batches") && step2.scope.target === targetName) ?? [];
1004
+ if (ensures.length !== 1)
1005
+ fail(`deployment.spec.workflows.deploy`, `must contain exactly one network.ensure step scoped to connected target ${targetName}.`);
1006
+ }
828
1007
  const typedSource = source;
829
1008
  return {
830
1009
  format: DEPLOY_PLAN_FORMAT,
@@ -936,8 +1115,10 @@ export default defineDeployment({
936
1115
  inputs: { replicas: input.integer({ minimum: 1, maximum: 32, default: 1 }) },
937
1116
  targets: {
938
1117
  app: target.compute({
939
- selector: { profiles: ['app'] },
940
- cardinality: { minimum: 1, desired: input.ref('replicas'), maximum: 32 }
1118
+ profiles: ['app'],
1119
+ replicas: { minimum: 1, desired: input.ref('replicas'), maximum: 32 },
1120
+ resources: { cpuCores: 1, memoryMiB: 512, storageGiB: 8 },
1121
+ os: 'ubuntu-24.04', runtime: 'native', isolation: 'standard', reuse: 'require'
941
1122
  })
942
1123
  },
943
1124
  requirements: { bun: providers.bun.require() },
@@ -4,22 +4,30 @@ import {
4
4
  accessSync,
5
5
  chmodSync,
6
6
  copyFileSync,
7
+ createReadStream,
7
8
  existsSync,
8
9
  mkdtempSync,
9
10
  mkdirSync,
10
11
  readFileSync,
11
12
  renameSync,
12
13
  rmSync,
14
+ statSync,
13
15
  symlinkSync,
14
16
  unlinkSync,
15
17
  writeFileSync
16
18
  } from "node:fs";
17
19
  import { tmpdir } from "node:os";
18
- import { join } from "node:path";
20
+ import { dirname, join } from "node:path";
19
21
  var PINNED_BUN_VERSION = "1.3.14";
20
22
  var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
21
23
  var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
22
24
  var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
25
+ var NERDCTL_VERSION = "2.3.5";
26
+ var BUILDKIT_VERSION = "0.32.2";
27
+ var KATA_CONTAINERS_VERSION = "4.0.0";
28
+ var NERDCTL_SHA256 = "de3206aeb7cbd5f20f5fb1f55c1e3bf2db1be567812a8a3f5e65eba2488347ee";
29
+ var BUILDKIT_SHA256 = "2975d0f651ad96ba8b80b9992ae1f9a964f4408569af5b6dc36544165c3926af";
30
+ var KATA_SHA256 = "2c3b9dfeba355582b40aee462b12916c9740654d0230f696adf719d67b063a8c";
23
31
  var OS_CATALOG = [
24
32
  {
25
33
  id: "ubuntu",
@@ -49,6 +57,8 @@ var OS_CATALOG = [
49
57
  var SOFTWARE_CATALOG = [
50
58
  { id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
51
59
  { id: "docker", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
60
+ { id: "containerd", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
61
+ { id: "kata-containers", version: "4.0.0", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
52
62
  { id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
53
63
  { id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
54
64
  { id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -57,6 +67,7 @@ var SOFTWARE_CATALOG = [
57
67
  { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
58
68
  { id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
59
69
  { id: "docker", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
70
+ { id: "containerd", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
60
71
  { id: "nginx", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
61
72
  { id: "ufw", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
62
73
  { id: "openssh-client", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -65,6 +76,8 @@ var SOFTWARE_CATALOG = [
65
76
  var UBUNTU_2604_X64 = [
66
77
  { requirement: { id: "bun", version: "1.3.14" } },
67
78
  { requirement: { id: "docker", version: "ubuntu-26.04" } },
79
+ { requirement: { id: "containerd", version: "ubuntu-26.04" } },
80
+ { requirement: { id: "kata-containers", version: "4.0.0" } },
68
81
  { requirement: { id: "nginx", version: "ubuntu-26.04" } },
69
82
  { requirement: { id: "arangodb", version: "3.11.14" } },
70
83
  { requirement: { id: "cloudflared", version: "2026.7.3" } },
@@ -76,6 +89,7 @@ var UBUNTU_2604_X64 = [
76
89
  var UBUNTU_2404_X64 = [
77
90
  { requirement: { id: "bun", version: "1.3.14" } },
78
91
  { requirement: { id: "docker", version: "ubuntu-24.04" } },
92
+ { requirement: { id: "containerd", version: "ubuntu-24.04" } },
79
93
  { requirement: { id: "nginx", version: "ubuntu-24.04" } },
80
94
  { requirement: { id: "ufw", version: "ubuntu-24.04" } },
81
95
  { requirement: { id: "openssh-client", version: "ubuntu-24.04" } },
@@ -92,6 +106,46 @@ var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
92
106
  "log-opts": { "max-size": "10m", "max-file": "3" }
93
107
  }, null, 2)}
94
108
  `;
109
+ var CONTAINERD_CONFIG_PATH = "/etc/containerd/config.toml";
110
+ var KATA_SNP_CONFIG_PATH = "/opt/kata/share/defaults/kata-containers/configuration-qemu-snp.toml";
111
+ var KATA_ACTIVE_CONFIG_PATH = "/etc/kata-containers/configuration.toml";
112
+ var CONTAINERD_CONFIG = (kata) => `version = 2
113
+ root = "/var/lib/forgezero/containerd"
114
+ state = "/run/forgezero/containerd"
115
+
116
+ [grpc]
117
+ address = "/run/containerd/containerd.sock"
118
+
119
+ [plugins."io.containerd.grpc.v1.cri".containerd]
120
+ snapshotter = "overlayfs"
121
+ default_runtime_name = "runc"
122
+
123
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
124
+ runtime_type = "io.containerd.runc.v2"
125
+ ${kata ? `
126
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp]
127
+ runtime_type = "io.containerd.kata.v2"
128
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp.options]
129
+ ConfigPath = "${KATA_ACTIVE_CONFIG_PATH}"
130
+ ` : ""}`;
131
+ var BUILDKIT_UNIT = `[Unit]
132
+ Description=ForgeZero BuildKit daemon
133
+ After=network-online.target containerd.service
134
+ Wants=network-online.target
135
+ Requires=containerd.service
136
+
137
+ [Service]
138
+ Type=notify
139
+ ExecStart=/usr/local/bin/buildkitd --addr unix:///run/buildkit/buildkitd.sock --root /var/lib/forgezero/buildkit
140
+ Restart=on-failure
141
+ RestartSec=2
142
+ NoNewPrivileges=yes
143
+ ProtectSystem=strict
144
+ ReadWritePaths=/var/lib/forgezero/buildkit /run/buildkit
145
+
146
+ [Install]
147
+ WantedBy=multi-user.target
148
+ `;
95
149
  var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
96
150
  var runSoftwareCommand = async (argv, env = {}) => {
97
151
  let child;
@@ -111,14 +165,28 @@ var runSoftwareCommand = async (argv, env = {}) => {
111
165
  return { exitCode, output: `${stdout}${stderr}` };
112
166
  };
113
167
  var run = runSoftwareCommand;
114
- var download = async (url, destination, sha256) => {
115
- const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
168
+ var download = async (url, destination, sha256, maximumBytes = 512 * 1024 * 1024) => {
169
+ const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(30 * 60000) });
116
170
  if (!response.ok)
117
171
  throw new Error(`download failed with HTTP ${response.status}`);
118
- const bytes = new Uint8Array(await response.arrayBuffer());
119
- if (createHash("sha256").update(bytes).digest("hex") !== sha256)
172
+ const declared = Number(response.headers.get("content-length") ?? 0);
173
+ if (declared > maximumBytes)
174
+ throw new Error("download exceeds reviewed size bound");
175
+ if (existsSync(destination))
176
+ throw new Error("download destination already exists");
177
+ await Bun.write(destination, response);
178
+ chmodSync(destination, 384);
179
+ if (statSync(destination).size > maximumBytes) {
180
+ rmSync(destination, { force: true });
181
+ throw new Error("download exceeds reviewed size bound");
182
+ }
183
+ const digest = createHash("sha256");
184
+ for await (const chunk of createReadStream(destination))
185
+ digest.update(chunk);
186
+ if (digest.digest("hex") !== sha256) {
187
+ rmSync(destination, { force: true });
120
188
  throw new Error("download checksum mismatch");
121
- writeFileSync(destination, bytes, { mode: 384, flag: "wx" });
189
+ }
122
190
  };
123
191
  var successful = (result, pattern) => result.exitCode === 0 && (!pattern || pattern.test(result.output));
124
192
  var aptInstall = async (name) => {
@@ -126,6 +194,106 @@ var aptInstall = async (name) => {
126
194
  const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
127
195
  return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", name], environment) : update;
128
196
  };
197
+ var aptInstallMany = async (names) => {
198
+ const environment = { DEBIAN_FRONTEND: "noninteractive" };
199
+ const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
200
+ return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", ...names], environment) : update;
201
+ };
202
+ var writeOwnedPolicy = (pathValue, content, mode = 420) => {
203
+ if (existsSync(pathValue)) {
204
+ if (readFileSync(pathValue, "utf8") !== content)
205
+ throw new Error(`refusing to overwrite non-ForgeZero policy at ${pathValue}`);
206
+ return;
207
+ }
208
+ mkdirSync(dirname(pathValue), { recursive: true, mode: 493 });
209
+ writeFileSync(pathValue, content, { mode, flag: "wx" });
210
+ };
211
+ var installContainerdRuntime = async (directory, osVersion) => {
212
+ const installed = await aptInstallMany(["containerd", "runc", "containernetworking-plugins"]);
213
+ if (installed.exitCode !== 0)
214
+ return installed;
215
+ const nerdctl = join(directory, "nerdctl.tar.gz");
216
+ await download(`https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz`, nerdctl, NERDCTL_SHA256);
217
+ const nerdctlExtract = await run(["/usr/bin/tar", "-xzf", nerdctl, "-C", "/usr/local/bin", "nerdctl"]);
218
+ if (nerdctlExtract.exitCode !== 0)
219
+ return nerdctlExtract;
220
+ chmodSync("/usr/local/bin/nerdctl", 493);
221
+ const buildkit = join(directory, "buildkit.tar.gz");
222
+ await download(`https://github.com/moby/buildkit/releases/download/v${BUILDKIT_VERSION}/buildkit-v${BUILDKIT_VERSION}.linux-amd64.tar.gz`, buildkit, BUILDKIT_SHA256);
223
+ const unpacked = join(directory, "buildkit");
224
+ mkdirSync(unpacked, { mode: 448 });
225
+ const buildkitExtract = await run(["/usr/bin/tar", "-xzf", buildkit, "-C", unpacked]);
226
+ if (buildkitExtract.exitCode !== 0)
227
+ return buildkitExtract;
228
+ for (const binary of ["buildctl", "buildkitd"]) {
229
+ copyFileSync(join(unpacked, "bin", binary), `/usr/local/bin/${binary}`);
230
+ chmodSync(`/usr/local/bin/${binary}`, 493);
231
+ }
232
+ mkdirSync("/etc/containerd", { recursive: true, mode: 493 });
233
+ const expected = CONTAINERD_CONFIG(false);
234
+ const kataExpected = CONTAINERD_CONFIG(true);
235
+ if (existsSync(CONTAINERD_CONFIG_PATH) && ![expected, kataExpected].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
236
+ return { exitCode: 2, output: "refusing to overwrite an existing containerd configuration that differs from the reviewed ForgeZero policy" };
237
+ }
238
+ if (!existsSync(CONTAINERD_CONFIG_PATH))
239
+ writeFileSync(CONTAINERD_CONFIG_PATH, expected, { mode: 420, flag: "wx" });
240
+ writeOwnedPolicy("/etc/systemd/system/forgezero-buildkit.service", BUILDKIT_UNIT);
241
+ mkdirSync("/var/lib/forgezero/containerd", { recursive: true, mode: 448 });
242
+ mkdirSync("/var/lib/forgezero/buildkit", { recursive: true, mode: 448 });
243
+ const reload = await run(["/usr/bin/systemctl", "daemon-reload"]);
244
+ if (reload.exitCode !== 0)
245
+ return reload;
246
+ const enabled = await run(["/usr/bin/systemctl", "enable", "--now", "containerd.service", "forgezero-buildkit.service"]);
247
+ return enabled.exitCode === 0 ? { exitCode: 0, output: `containerd ${osVersion} with nerdctl ${NERDCTL_VERSION} and BuildKit ${BUILDKIT_VERSION}` } : enabled;
248
+ };
249
+ var installKataRuntime = async (directory) => {
250
+ for (const pathValue of ["/dev/kvm", "/dev/sev"])
251
+ if (!existsSync(pathValue))
252
+ return { exitCode: 2, output: `${pathValue} is required for Kata SEV-SNP` };
253
+ for (const parameter of ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"]) {
254
+ if (!existsSync(parameter) || !/^(1|Y)$/i.test(readFileSync(parameter, "utf8").trim()))
255
+ return { exitCode: 2, output: `${parameter} does not enable SEV-SNP` };
256
+ }
257
+ const dependencies = await aptInstallMany(["zstd"]);
258
+ if (dependencies.exitCode !== 0)
259
+ return dependencies;
260
+ const archive = join(directory, "kata.tar.zst");
261
+ 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);
262
+ const tar = join(directory, "kata.tar");
263
+ const expanded = await run(["/usr/bin/unzstd", "--quiet", archive, "--output", tar]);
264
+ if (expanded.exitCode !== 0)
265
+ return expanded;
266
+ const extracted = await run(["/usr/bin/tar", "-xf", tar, "-C", "/"]);
267
+ if (extracted.exitCode !== 0)
268
+ return extracted;
269
+ if (!existsSync("/opt/kata/bin/containerd-shim-kata-v2") || !existsSync(KATA_SNP_CONFIG_PATH)) {
270
+ return { exitCode: 2, output: "Kata archive does not contain the reviewed QEMU SNP runtime and configuration" };
271
+ }
272
+ const snpConfig = readFileSync(KATA_SNP_CONFIG_PATH, "utf8");
273
+ if (!/^\s*confidential_guest\s*=\s*true\s*$/m.test(snpConfig) || !/^\s*sev_snp_guest\s*=\s*true\s*$/m.test(snpConfig)) {
274
+ return { exitCode: 2, output: "Kata QEMU configuration does not enable confidential SEV-SNP guests" };
275
+ }
276
+ mkdirSync("/etc/kata-containers", { recursive: true, mode: 493 });
277
+ if (existsSync(KATA_ACTIVE_CONFIG_PATH) && readFileSync(KATA_ACTIVE_CONFIG_PATH, "utf8") !== snpConfig) {
278
+ return { exitCode: 2, output: "refusing to overwrite a non-ForgeZero Kata runtime configuration" };
279
+ }
280
+ if (!existsSync(KATA_ACTIVE_CONFIG_PATH))
281
+ copyFileSync(KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH);
282
+ chmodSync(KATA_ACTIVE_CONFIG_PATH, 420);
283
+ try {
284
+ unlinkSync("/usr/local/bin/containerd-shim-kata-v2");
285
+ } catch {}
286
+ symlinkSync("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
287
+ const base = CONTAINERD_CONFIG(false);
288
+ const kata = CONTAINERD_CONFIG(true);
289
+ if (!existsSync(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
290
+ return { exitCode: 2, output: "containerd must use the reviewed ForgeZero policy before Kata is installed" };
291
+ }
292
+ if (readFileSync(CONTAINERD_CONFIG_PATH, "utf8") !== kata)
293
+ writeFileSync(CONTAINERD_CONFIG_PATH, kata, { mode: 420 });
294
+ const restarted = await run(["/usr/bin/systemctl", "restart", "containerd.service"]);
295
+ return restarted.exitCode === 0 ? { exitCode: 0, output: `Kata Containers ${KATA_CONTAINERS_VERSION} QEMU SNP` } : restarted;
296
+ };
129
297
  async function executeSoftwareOperation(operation) {
130
298
  const { software, version } = operation;
131
299
  if (![...UBUNTU_2604_X64, ...UBUNTU_2404_X64].some(({ requirement }) => requirement.id === software && requirement.version === version)) {
@@ -143,6 +311,26 @@ async function executeSoftwareOperation(operation) {
143
311
  const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "docker.service"]);
144
312
  return active.exitCode === 0 ? { exitCode: 0, output: binary.output } : active;
145
313
  }
314
+ if (software === "containerd") {
315
+ const [containerd, nerdctl, buildkit, active, builder] = await Promise.all([
316
+ run(["/usr/bin/containerd", "--version"]),
317
+ run(["/usr/local/bin/nerdctl", "--version"]),
318
+ run(["/usr/local/bin/buildkitd", "--version"]),
319
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]),
320
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-buildkit.service"])
321
+ ]);
322
+ const policy = existsSync(CONTAINERD_CONFIG_PATH) ? readFileSync(CONTAINERD_CONFIG_PATH, "utf8") : "";
323
+ const validPolicy = policy === CONTAINERD_CONFIG(false) || policy === CONTAINERD_CONFIG(true);
324
+ 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" };
325
+ }
326
+ if (software === "kata-containers") {
327
+ const shim = await run(["/opt/kata/bin/containerd-shim-kata-v2", "--version"]);
328
+ const policy = existsSync(CONTAINERD_CONFIG_PATH) ? readFileSync(CONTAINERD_CONFIG_PATH, "utf8") : "";
329
+ const activeConfig = existsSync(KATA_ACTIVE_CONFIG_PATH) ? readFileSync(KATA_ACTIVE_CONFIG_PATH, "utf8") : "";
330
+ const host = ["/dev/kvm", "/dev/sev", KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH].every(existsSync) && ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"].every((parameter) => existsSync(parameter) && /^(1|Y)$/i.test(readFileSync(parameter, "utf8").trim()));
331
+ const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]);
332
+ return successful(shim, /kata.*4\.0\.0/i) && policy === CONTAINERD_CONFIG(true) && activeConfig === (existsSync(KATA_SNP_CONFIG_PATH) ? readFileSync(KATA_SNP_CONFIG_PATH, "utf8") : "") && host && active.exitCode === 0 ? { exitCode: 0, output: shim.output } : { exitCode: 1, output: "Kata QEMU SNP runtime, host capability, or containerd policy is unavailable" };
333
+ }
146
334
  if (software === "nginx") {
147
335
  const binary = await run(["/usr/sbin/nginx", "-v"]);
148
336
  return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
@@ -194,6 +382,17 @@ async function executeSoftwareOperation(operation) {
194
382
  }
195
383
  const directory = mkdtempSync(join(tmpdir(), "forgezero-software-"));
196
384
  try {
385
+ if (software === "containerd")
386
+ return installContainerdRuntime(directory, version === "ubuntu-24.04" ? "24.04" : "26.04");
387
+ if (software === "kata-containers") {
388
+ const containerd = await executeSoftwareOperation({ kind: "check", software: "containerd", version: "ubuntu-26.04" });
389
+ if (containerd.exitCode !== 0) {
390
+ const installed2 = await installContainerdRuntime(directory, "26.04");
391
+ if (installed2.exitCode !== 0)
392
+ return installed2;
393
+ }
394
+ return installKataRuntime(directory);
395
+ }
197
396
  if (software === "cloudflare-warp") {
198
397
  const key = join(directory, "cloudflare-warp-key.gpg");
199
398
  await download("https://pkg.cloudflareclient.com/pubkey.gpg", key, "0f37fc298c98e88ee3c0ee68c95b69f1dba9eb477abe3167e13982105911264d");
@@ -275,7 +474,7 @@ function validateSoftwareRequirements(value, _options = {}) {
275
474
  if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
276
475
  throw new Error("software requirement contains an unknown field");
277
476
  }
278
- 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)) {
477
+ 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)) {
279
478
  throw new Error("software requirement coordinate is invalid");
280
479
  }
281
480
  const requirement = { id: row.id, version: row.version };