@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
|
@@ -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(
|
|
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
|
|
119
|
-
if (
|
|
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
|
-
|
|
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 };
|
|
@@ -470,7 +669,7 @@ import {
|
|
|
470
669
|
realpathSync,
|
|
471
670
|
renameSync as renameSync2,
|
|
472
671
|
rmSync as rmSync2,
|
|
473
|
-
statSync,
|
|
672
|
+
statSync as statSync2,
|
|
474
673
|
symlinkSync as symlinkSync2,
|
|
475
674
|
writeFileSync as writeFileSync2
|
|
476
675
|
} from "node:fs";
|
|
@@ -914,7 +1113,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
914
1113
|
if (!release.startsWith(`${releases}/`) || release === releases)
|
|
915
1114
|
throw new Error("release is outside configured releases directory");
|
|
916
1115
|
for (const required of ["forgezero.deploy.ts", ".fz/deploy.plan.json", "src/index.ts", "bun.lock"]) {
|
|
917
|
-
const metadata =
|
|
1116
|
+
const metadata = statSync2(join2(release, required));
|
|
918
1117
|
if (!metadata.isFile() || metadata.size < 1)
|
|
919
1118
|
throw new Error(`release is incomplete: ${required}`);
|
|
920
1119
|
}
|
|
@@ -989,7 +1188,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
989
1188
|
await sleep(normalized.drainDeadlineMs);
|
|
990
1189
|
await exec(["/usr/bin/systemctl", "stop", `forgezero@${previousSlot}.service`]);
|
|
991
1190
|
}
|
|
992
|
-
const old = readdirSync(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join2(releases, entry.name)).sort((left, right) =>
|
|
1191
|
+
const old = readdirSync(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join2(releases, entry.name)).sort((left, right) => statSync2(right).mtimeMs - statSync2(left).mtimeMs).slice(normalized.keepReleases);
|
|
993
1192
|
for (const path2 of old)
|
|
994
1193
|
if (path2 !== release)
|
|
995
1194
|
rmSync2(path2, { recursive: true, force: true });
|