@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
@@ -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 };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Flush and close a Bun child-process input pipe before awaiting the child.
3
+ *
4
+ * Bun's FileSink.end() is asynchronous. Leaving its promise unobserved can
5
+ * keep the write half open after the payload has arrived, so a remote process
6
+ * that reads until EOF never advances.
7
+ */
8
+ export declare function writeAndCloseProcessInput(input: {
9
+ write(value: string | Uint8Array): unknown;
10
+ end(): unknown;
11
+ }, value: string | Uint8Array): Promise<void>;