@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.
- package/README.md +126 -17
- package/dist/agent-heartbeat.d.ts +25 -1
- package/dist/agent-heartbeat.js +45 -5
- package/dist/bootstrap.js +1 -1
- package/dist/community-rehearsal-host.js +208 -9
- package/dist/credential-schema.d.ts +1 -1
- package/dist/credential-schema.js +1 -1
- package/dist/definition.js +206 -7
- package/dist/deploy-actions.d.ts +7 -0
- package/dist/deploy-compiler.js +191 -10
- package/dist/deploy-file.js +206 -7
- package/dist/deploy-plan-runner.d.ts +3 -0
- package/dist/deploy-plan-runner.js +190 -9
- package/dist/deploy-plan.js +187 -8
- package/dist/deploy-providers.d.ts +19 -0
- package/dist/deploy.d.ts +89 -3
- package/dist/deploy.js +49 -2
- package/dist/deployment-connectivity.d.ts +63 -0
- package/dist/deployment-connectivity.js +148 -0
- package/dist/deployment-pull.d.ts +2 -0
- package/dist/deployment-targets.d.ts +9 -0
- package/dist/deployment-targets.js +141 -0
- package/dist/deployment.d.ts +69 -0
- package/dist/fz-agent.js +1111 -301
- package/dist/fz.js +220 -15
- package/dist/index.d.ts +1 -0
- package/dist/metal-bootstrap.js +1 -1
- package/dist/metal-helper-socket.js +210 -11
- package/dist/metal-provision.js +210 -11
- package/dist/operator-bootstrap.js +1 -1
- package/dist/platform-bootstrap-runtime.js +209 -10
- package/dist/platform-fleet-verification.js +515 -149
- package/dist/platform-genesis.js +206 -7
- package/dist/provision.js +465 -99
- package/dist/service-supervisor.d.ts +6 -0
- package/dist/software-helper.d.ts +3 -0
- package/dist/software-helper.js +465 -98
- package/dist/software.d.ts +4 -1
- package/dist/software.js +210 -8
- package/dist/ubuntu.js +206 -7
- package/dist/version.d.ts +1 -1
- package/package.json +12 -4
|
@@ -144,22 +144,30 @@ import {
|
|
|
144
144
|
accessSync,
|
|
145
145
|
chmodSync,
|
|
146
146
|
copyFileSync,
|
|
147
|
+
createReadStream,
|
|
147
148
|
existsSync,
|
|
148
149
|
mkdtempSync,
|
|
149
150
|
mkdirSync,
|
|
150
151
|
readFileSync,
|
|
151
152
|
renameSync,
|
|
152
153
|
rmSync,
|
|
154
|
+
statSync,
|
|
153
155
|
symlinkSync,
|
|
154
156
|
unlinkSync,
|
|
155
157
|
writeFileSync
|
|
156
158
|
} from "node:fs";
|
|
157
159
|
import { tmpdir } from "node:os";
|
|
158
|
-
import { join } from "node:path";
|
|
160
|
+
import { dirname, join } from "node:path";
|
|
159
161
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
160
162
|
var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
|
|
161
163
|
var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
|
|
162
164
|
var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
|
|
165
|
+
var NERDCTL_VERSION = "2.3.5";
|
|
166
|
+
var BUILDKIT_VERSION = "0.32.2";
|
|
167
|
+
var KATA_CONTAINERS_VERSION = "4.0.0";
|
|
168
|
+
var NERDCTL_SHA256 = "de3206aeb7cbd5f20f5fb1f55c1e3bf2db1be567812a8a3f5e65eba2488347ee";
|
|
169
|
+
var BUILDKIT_SHA256 = "2975d0f651ad96ba8b80b9992ae1f9a964f4408569af5b6dc36544165c3926af";
|
|
170
|
+
var KATA_SHA256 = "2c3b9dfeba355582b40aee462b12916c9740654d0230f696adf719d67b063a8c";
|
|
163
171
|
var OS_CATALOG = [
|
|
164
172
|
{
|
|
165
173
|
id: "ubuntu",
|
|
@@ -189,6 +197,8 @@ var OS_CATALOG = [
|
|
|
189
197
|
var SOFTWARE_CATALOG = [
|
|
190
198
|
{ id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
191
199
|
{ id: "docker", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
200
|
+
{ id: "containerd", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
201
|
+
{ id: "kata-containers", version: "4.0.0", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
192
202
|
{ id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
193
203
|
{ id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
194
204
|
{ id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
@@ -197,6 +207,7 @@ var SOFTWARE_CATALOG = [
|
|
|
197
207
|
{ id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
198
208
|
{ id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
199
209
|
{ id: "docker", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
210
|
+
{ id: "containerd", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
200
211
|
{ id: "nginx", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
201
212
|
{ id: "ufw", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
202
213
|
{ id: "openssh-client", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
@@ -205,6 +216,8 @@ var SOFTWARE_CATALOG = [
|
|
|
205
216
|
var UBUNTU_2604_X64 = [
|
|
206
217
|
{ requirement: { id: "bun", version: "1.3.14" } },
|
|
207
218
|
{ requirement: { id: "docker", version: "ubuntu-26.04" } },
|
|
219
|
+
{ requirement: { id: "containerd", version: "ubuntu-26.04" } },
|
|
220
|
+
{ requirement: { id: "kata-containers", version: "4.0.0" } },
|
|
208
221
|
{ requirement: { id: "nginx", version: "ubuntu-26.04" } },
|
|
209
222
|
{ requirement: { id: "arangodb", version: "3.11.14" } },
|
|
210
223
|
{ requirement: { id: "cloudflared", version: "2026.7.3" } },
|
|
@@ -216,6 +229,7 @@ var UBUNTU_2604_X64 = [
|
|
|
216
229
|
var UBUNTU_2404_X64 = [
|
|
217
230
|
{ requirement: { id: "bun", version: "1.3.14" } },
|
|
218
231
|
{ requirement: { id: "docker", version: "ubuntu-24.04" } },
|
|
232
|
+
{ requirement: { id: "containerd", version: "ubuntu-24.04" } },
|
|
219
233
|
{ requirement: { id: "nginx", version: "ubuntu-24.04" } },
|
|
220
234
|
{ requirement: { id: "ufw", version: "ubuntu-24.04" } },
|
|
221
235
|
{ requirement: { id: "openssh-client", version: "ubuntu-24.04" } },
|
|
@@ -232,6 +246,46 @@ var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
|
|
|
232
246
|
"log-opts": { "max-size": "10m", "max-file": "3" }
|
|
233
247
|
}, null, 2)}
|
|
234
248
|
`;
|
|
249
|
+
var CONTAINERD_CONFIG_PATH = "/etc/containerd/config.toml";
|
|
250
|
+
var KATA_SNP_CONFIG_PATH = "/opt/kata/share/defaults/kata-containers/configuration-qemu-snp.toml";
|
|
251
|
+
var KATA_ACTIVE_CONFIG_PATH = "/etc/kata-containers/configuration.toml";
|
|
252
|
+
var CONTAINERD_CONFIG = (kata) => `version = 2
|
|
253
|
+
root = "/var/lib/forgezero/containerd"
|
|
254
|
+
state = "/run/forgezero/containerd"
|
|
255
|
+
|
|
256
|
+
[grpc]
|
|
257
|
+
address = "/run/containerd/containerd.sock"
|
|
258
|
+
|
|
259
|
+
[plugins."io.containerd.grpc.v1.cri".containerd]
|
|
260
|
+
snapshotter = "overlayfs"
|
|
261
|
+
default_runtime_name = "runc"
|
|
262
|
+
|
|
263
|
+
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
|
|
264
|
+
runtime_type = "io.containerd.runc.v2"
|
|
265
|
+
${kata ? `
|
|
266
|
+
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp]
|
|
267
|
+
runtime_type = "io.containerd.kata.v2"
|
|
268
|
+
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp.options]
|
|
269
|
+
ConfigPath = "${KATA_ACTIVE_CONFIG_PATH}"
|
|
270
|
+
` : ""}`;
|
|
271
|
+
var BUILDKIT_UNIT = `[Unit]
|
|
272
|
+
Description=ForgeZero BuildKit daemon
|
|
273
|
+
After=network-online.target containerd.service
|
|
274
|
+
Wants=network-online.target
|
|
275
|
+
Requires=containerd.service
|
|
276
|
+
|
|
277
|
+
[Service]
|
|
278
|
+
Type=notify
|
|
279
|
+
ExecStart=/usr/local/bin/buildkitd --addr unix:///run/buildkit/buildkitd.sock --root /var/lib/forgezero/buildkit
|
|
280
|
+
Restart=on-failure
|
|
281
|
+
RestartSec=2
|
|
282
|
+
NoNewPrivileges=yes
|
|
283
|
+
ProtectSystem=strict
|
|
284
|
+
ReadWritePaths=/var/lib/forgezero/buildkit /run/buildkit
|
|
285
|
+
|
|
286
|
+
[Install]
|
|
287
|
+
WantedBy=multi-user.target
|
|
288
|
+
`;
|
|
235
289
|
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
236
290
|
var runSoftwareCommand = async (argv, env = {}) => {
|
|
237
291
|
let child;
|
|
@@ -251,14 +305,28 @@ var runSoftwareCommand = async (argv, env = {}) => {
|
|
|
251
305
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
252
306
|
};
|
|
253
307
|
var run = runSoftwareCommand;
|
|
254
|
-
var download = async (url, destination, sha256) => {
|
|
255
|
-
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(
|
|
308
|
+
var download = async (url, destination, sha256, maximumBytes = 512 * 1024 * 1024) => {
|
|
309
|
+
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(30 * 60000) });
|
|
256
310
|
if (!response.ok)
|
|
257
311
|
throw new Error(`download failed with HTTP ${response.status}`);
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
312
|
+
const declared = Number(response.headers.get("content-length") ?? 0);
|
|
313
|
+
if (declared > maximumBytes)
|
|
314
|
+
throw new Error("download exceeds reviewed size bound");
|
|
315
|
+
if (existsSync(destination))
|
|
316
|
+
throw new Error("download destination already exists");
|
|
317
|
+
await Bun.write(destination, response);
|
|
318
|
+
chmodSync(destination, 384);
|
|
319
|
+
if (statSync(destination).size > maximumBytes) {
|
|
320
|
+
rmSync(destination, { force: true });
|
|
321
|
+
throw new Error("download exceeds reviewed size bound");
|
|
322
|
+
}
|
|
323
|
+
const digest = createHash("sha256");
|
|
324
|
+
for await (const chunk of createReadStream(destination))
|
|
325
|
+
digest.update(chunk);
|
|
326
|
+
if (digest.digest("hex") !== sha256) {
|
|
327
|
+
rmSync(destination, { force: true });
|
|
260
328
|
throw new Error("download checksum mismatch");
|
|
261
|
-
|
|
329
|
+
}
|
|
262
330
|
};
|
|
263
331
|
var successful = (result, pattern) => result.exitCode === 0 && (!pattern || pattern.test(result.output));
|
|
264
332
|
var aptInstall = async (name) => {
|
|
@@ -266,6 +334,106 @@ var aptInstall = async (name) => {
|
|
|
266
334
|
const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
|
|
267
335
|
return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", name], environment) : update;
|
|
268
336
|
};
|
|
337
|
+
var aptInstallMany = async (names) => {
|
|
338
|
+
const environment = { DEBIAN_FRONTEND: "noninteractive" };
|
|
339
|
+
const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
|
|
340
|
+
return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", ...names], environment) : update;
|
|
341
|
+
};
|
|
342
|
+
var writeOwnedPolicy = (pathValue, content, mode = 420) => {
|
|
343
|
+
if (existsSync(pathValue)) {
|
|
344
|
+
if (readFileSync(pathValue, "utf8") !== content)
|
|
345
|
+
throw new Error(`refusing to overwrite non-ForgeZero policy at ${pathValue}`);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
mkdirSync(dirname(pathValue), { recursive: true, mode: 493 });
|
|
349
|
+
writeFileSync(pathValue, content, { mode, flag: "wx" });
|
|
350
|
+
};
|
|
351
|
+
var installContainerdRuntime = async (directory, osVersion) => {
|
|
352
|
+
const installed = await aptInstallMany(["containerd", "runc", "containernetworking-plugins"]);
|
|
353
|
+
if (installed.exitCode !== 0)
|
|
354
|
+
return installed;
|
|
355
|
+
const nerdctl = join(directory, "nerdctl.tar.gz");
|
|
356
|
+
await download(`https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz`, nerdctl, NERDCTL_SHA256);
|
|
357
|
+
const nerdctlExtract = await run(["/usr/bin/tar", "-xzf", nerdctl, "-C", "/usr/local/bin", "nerdctl"]);
|
|
358
|
+
if (nerdctlExtract.exitCode !== 0)
|
|
359
|
+
return nerdctlExtract;
|
|
360
|
+
chmodSync("/usr/local/bin/nerdctl", 493);
|
|
361
|
+
const buildkit = join(directory, "buildkit.tar.gz");
|
|
362
|
+
await download(`https://github.com/moby/buildkit/releases/download/v${BUILDKIT_VERSION}/buildkit-v${BUILDKIT_VERSION}.linux-amd64.tar.gz`, buildkit, BUILDKIT_SHA256);
|
|
363
|
+
const unpacked = join(directory, "buildkit");
|
|
364
|
+
mkdirSync(unpacked, { mode: 448 });
|
|
365
|
+
const buildkitExtract = await run(["/usr/bin/tar", "-xzf", buildkit, "-C", unpacked]);
|
|
366
|
+
if (buildkitExtract.exitCode !== 0)
|
|
367
|
+
return buildkitExtract;
|
|
368
|
+
for (const binary of ["buildctl", "buildkitd"]) {
|
|
369
|
+
copyFileSync(join(unpacked, "bin", binary), `/usr/local/bin/${binary}`);
|
|
370
|
+
chmodSync(`/usr/local/bin/${binary}`, 493);
|
|
371
|
+
}
|
|
372
|
+
mkdirSync("/etc/containerd", { recursive: true, mode: 493 });
|
|
373
|
+
const expected = CONTAINERD_CONFIG(false);
|
|
374
|
+
const kataExpected = CONTAINERD_CONFIG(true);
|
|
375
|
+
if (existsSync(CONTAINERD_CONFIG_PATH) && ![expected, kataExpected].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
|
|
376
|
+
return { exitCode: 2, output: "refusing to overwrite an existing containerd configuration that differs from the reviewed ForgeZero policy" };
|
|
377
|
+
}
|
|
378
|
+
if (!existsSync(CONTAINERD_CONFIG_PATH))
|
|
379
|
+
writeFileSync(CONTAINERD_CONFIG_PATH, expected, { mode: 420, flag: "wx" });
|
|
380
|
+
writeOwnedPolicy("/etc/systemd/system/forgezero-buildkit.service", BUILDKIT_UNIT);
|
|
381
|
+
mkdirSync("/var/lib/forgezero/containerd", { recursive: true, mode: 448 });
|
|
382
|
+
mkdirSync("/var/lib/forgezero/buildkit", { recursive: true, mode: 448 });
|
|
383
|
+
const reload = await run(["/usr/bin/systemctl", "daemon-reload"]);
|
|
384
|
+
if (reload.exitCode !== 0)
|
|
385
|
+
return reload;
|
|
386
|
+
const enabled = await run(["/usr/bin/systemctl", "enable", "--now", "containerd.service", "forgezero-buildkit.service"]);
|
|
387
|
+
return enabled.exitCode === 0 ? { exitCode: 0, output: `containerd ${osVersion} with nerdctl ${NERDCTL_VERSION} and BuildKit ${BUILDKIT_VERSION}` } : enabled;
|
|
388
|
+
};
|
|
389
|
+
var installKataRuntime = async (directory) => {
|
|
390
|
+
for (const pathValue of ["/dev/kvm", "/dev/sev"])
|
|
391
|
+
if (!existsSync(pathValue))
|
|
392
|
+
return { exitCode: 2, output: `${pathValue} is required for Kata SEV-SNP` };
|
|
393
|
+
for (const parameter of ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"]) {
|
|
394
|
+
if (!existsSync(parameter) || !/^(1|Y)$/i.test(readFileSync(parameter, "utf8").trim()))
|
|
395
|
+
return { exitCode: 2, output: `${parameter} does not enable SEV-SNP` };
|
|
396
|
+
}
|
|
397
|
+
const dependencies = await aptInstallMany(["zstd"]);
|
|
398
|
+
if (dependencies.exitCode !== 0)
|
|
399
|
+
return dependencies;
|
|
400
|
+
const archive = join(directory, "kata.tar.zst");
|
|
401
|
+
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);
|
|
402
|
+
const tar = join(directory, "kata.tar");
|
|
403
|
+
const expanded = await run(["/usr/bin/unzstd", "--quiet", archive, "--output", tar]);
|
|
404
|
+
if (expanded.exitCode !== 0)
|
|
405
|
+
return expanded;
|
|
406
|
+
const extracted = await run(["/usr/bin/tar", "-xf", tar, "-C", "/"]);
|
|
407
|
+
if (extracted.exitCode !== 0)
|
|
408
|
+
return extracted;
|
|
409
|
+
if (!existsSync("/opt/kata/bin/containerd-shim-kata-v2") || !existsSync(KATA_SNP_CONFIG_PATH)) {
|
|
410
|
+
return { exitCode: 2, output: "Kata archive does not contain the reviewed QEMU SNP runtime and configuration" };
|
|
411
|
+
}
|
|
412
|
+
const snpConfig = readFileSync(KATA_SNP_CONFIG_PATH, "utf8");
|
|
413
|
+
if (!/^\s*confidential_guest\s*=\s*true\s*$/m.test(snpConfig) || !/^\s*sev_snp_guest\s*=\s*true\s*$/m.test(snpConfig)) {
|
|
414
|
+
return { exitCode: 2, output: "Kata QEMU configuration does not enable confidential SEV-SNP guests" };
|
|
415
|
+
}
|
|
416
|
+
mkdirSync("/etc/kata-containers", { recursive: true, mode: 493 });
|
|
417
|
+
if (existsSync(KATA_ACTIVE_CONFIG_PATH) && readFileSync(KATA_ACTIVE_CONFIG_PATH, "utf8") !== snpConfig) {
|
|
418
|
+
return { exitCode: 2, output: "refusing to overwrite a non-ForgeZero Kata runtime configuration" };
|
|
419
|
+
}
|
|
420
|
+
if (!existsSync(KATA_ACTIVE_CONFIG_PATH))
|
|
421
|
+
copyFileSync(KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH);
|
|
422
|
+
chmodSync(KATA_ACTIVE_CONFIG_PATH, 420);
|
|
423
|
+
try {
|
|
424
|
+
unlinkSync("/usr/local/bin/containerd-shim-kata-v2");
|
|
425
|
+
} catch {}
|
|
426
|
+
symlinkSync("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
|
|
427
|
+
const base = CONTAINERD_CONFIG(false);
|
|
428
|
+
const kata = CONTAINERD_CONFIG(true);
|
|
429
|
+
if (!existsSync(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
|
|
430
|
+
return { exitCode: 2, output: "containerd must use the reviewed ForgeZero policy before Kata is installed" };
|
|
431
|
+
}
|
|
432
|
+
if (readFileSync(CONTAINERD_CONFIG_PATH, "utf8") !== kata)
|
|
433
|
+
writeFileSync(CONTAINERD_CONFIG_PATH, kata, { mode: 420 });
|
|
434
|
+
const restarted = await run(["/usr/bin/systemctl", "restart", "containerd.service"]);
|
|
435
|
+
return restarted.exitCode === 0 ? { exitCode: 0, output: `Kata Containers ${KATA_CONTAINERS_VERSION} QEMU SNP` } : restarted;
|
|
436
|
+
};
|
|
269
437
|
async function executeSoftwareOperation(operation) {
|
|
270
438
|
const { software, version } = operation;
|
|
271
439
|
if (![...UBUNTU_2604_X64, ...UBUNTU_2404_X64].some(({ requirement }) => requirement.id === software && requirement.version === version)) {
|
|
@@ -283,6 +451,26 @@ async function executeSoftwareOperation(operation) {
|
|
|
283
451
|
const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "docker.service"]);
|
|
284
452
|
return active.exitCode === 0 ? { exitCode: 0, output: binary.output } : active;
|
|
285
453
|
}
|
|
454
|
+
if (software === "containerd") {
|
|
455
|
+
const [containerd, nerdctl, buildkit, active, builder] = await Promise.all([
|
|
456
|
+
run(["/usr/bin/containerd", "--version"]),
|
|
457
|
+
run(["/usr/local/bin/nerdctl", "--version"]),
|
|
458
|
+
run(["/usr/local/bin/buildkitd", "--version"]),
|
|
459
|
+
run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]),
|
|
460
|
+
run(["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-buildkit.service"])
|
|
461
|
+
]);
|
|
462
|
+
const policy = existsSync(CONTAINERD_CONFIG_PATH) ? readFileSync(CONTAINERD_CONFIG_PATH, "utf8") : "";
|
|
463
|
+
const validPolicy = policy === CONTAINERD_CONFIG(false) || policy === CONTAINERD_CONFIG(true);
|
|
464
|
+
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" };
|
|
465
|
+
}
|
|
466
|
+
if (software === "kata-containers") {
|
|
467
|
+
const shim = await run(["/opt/kata/bin/containerd-shim-kata-v2", "--version"]);
|
|
468
|
+
const policy = existsSync(CONTAINERD_CONFIG_PATH) ? readFileSync(CONTAINERD_CONFIG_PATH, "utf8") : "";
|
|
469
|
+
const activeConfig = existsSync(KATA_ACTIVE_CONFIG_PATH) ? readFileSync(KATA_ACTIVE_CONFIG_PATH, "utf8") : "";
|
|
470
|
+
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()));
|
|
471
|
+
const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]);
|
|
472
|
+
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" };
|
|
473
|
+
}
|
|
286
474
|
if (software === "nginx") {
|
|
287
475
|
const binary = await run(["/usr/sbin/nginx", "-v"]);
|
|
288
476
|
return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
|
|
@@ -334,6 +522,17 @@ async function executeSoftwareOperation(operation) {
|
|
|
334
522
|
}
|
|
335
523
|
const directory = mkdtempSync(join(tmpdir(), "forgezero-software-"));
|
|
336
524
|
try {
|
|
525
|
+
if (software === "containerd")
|
|
526
|
+
return installContainerdRuntime(directory, version === "ubuntu-24.04" ? "24.04" : "26.04");
|
|
527
|
+
if (software === "kata-containers") {
|
|
528
|
+
const containerd = await executeSoftwareOperation({ kind: "check", software: "containerd", version: "ubuntu-26.04" });
|
|
529
|
+
if (containerd.exitCode !== 0) {
|
|
530
|
+
const installed2 = await installContainerdRuntime(directory, "26.04");
|
|
531
|
+
if (installed2.exitCode !== 0)
|
|
532
|
+
return installed2;
|
|
533
|
+
}
|
|
534
|
+
return installKataRuntime(directory);
|
|
535
|
+
}
|
|
337
536
|
if (software === "cloudflare-warp") {
|
|
338
537
|
const key = join(directory, "cloudflare-warp-key.gpg");
|
|
339
538
|
await download("https://pkg.cloudflareclient.com/pubkey.gpg", key, "0f37fc298c98e88ee3c0ee68c95b69f1dba9eb477abe3167e13982105911264d");
|
|
@@ -415,7 +614,7 @@ function validateSoftwareRequirements(value, _options = {}) {
|
|
|
415
614
|
if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
|
|
416
615
|
throw new Error("software requirement contains an unknown field");
|
|
417
616
|
}
|
|
418
|
-
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)) {
|
|
617
|
+
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)) {
|
|
419
618
|
throw new Error("software requirement coordinate is invalid");
|
|
420
619
|
}
|
|
421
620
|
const requirement = { id: row.id, version: row.version };
|
|
@@ -495,11 +694,11 @@ import {
|
|
|
495
694
|
readFileSync as readFileSync2,
|
|
496
695
|
readdirSync,
|
|
497
696
|
rmSync as rmSync2,
|
|
498
|
-
statSync,
|
|
697
|
+
statSync as statSync2,
|
|
499
698
|
unlinkSync as unlinkSync2,
|
|
500
699
|
writeFileSync as writeFileSync2
|
|
501
700
|
} from "node:fs";
|
|
502
|
-
import { dirname, isAbsolute, join as join2 } from "node:path";
|
|
701
|
+
import { dirname as dirname2, isAbsolute, join as join2 } from "node:path";
|
|
503
702
|
import { isIP } from "node:net";
|
|
504
703
|
var SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
|
|
505
704
|
var DEVICE = /^[a-zA-Z][a-zA-Z0-9_.-]{0,14}$/;
|
|
@@ -794,7 +993,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
794
993
|
if (!image || !isAbsolute(image.path) || !SHA256.test(image.sha256)) {
|
|
795
994
|
throw new MetalProvisionError(`image ${claim.spec.imageKey} is not configured locally`);
|
|
796
995
|
}
|
|
797
|
-
if (!
|
|
996
|
+
if (!statSync2(image.path).isFile())
|
|
798
997
|
throw new MetalProvisionError("configured image is not a file");
|
|
799
998
|
const digest = (await checked(exec, ["sha256sum", image.path])).stdout.trim().split(/\s+/)[0];
|
|
800
999
|
if (digest !== image.sha256)
|
|
@@ -879,7 +1078,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
879
1078
|
}
|
|
880
1079
|
const service = `forgezero-guest@${name}.service`;
|
|
881
1080
|
const unitPath = join2(profile.unitDir, service);
|
|
882
|
-
mkdirSync2(
|
|
1081
|
+
mkdirSync2(dirname2(unitPath), { recursive: true });
|
|
883
1082
|
writeFileSync2(unitPath, guestUnit(spec), { mode: 420 });
|
|
884
1083
|
await checked(exec, ["systemctl", "daemon-reload"]);
|
|
885
1084
|
if (prior?.phase === "running") {
|