@forgezero/agent 0.1.50 → 0.1.52
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/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +1 -1
- package/dist/community-rehearsal-host.d.ts +2 -0
- package/dist/community-rehearsal-host.js +269 -10
- package/dist/definition.js +12 -2
- package/dist/deploy-file.js +12 -2
- package/dist/fz-agent.js +23 -3
- package/dist/fz.js +1 -1
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +1 -1
- package/dist/platform-fleet-verification.js +13 -3
- package/dist/provision.js +13 -3
- package/dist/software-helper.js +12 -2
- package/dist/software.d.ts +2 -0
- package/dist/software.js +14 -2
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SoftwareExec } from './software';
|
|
1
2
|
export declare const COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
2
3
|
export declare const COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
3
4
|
export declare const COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
@@ -43,6 +44,7 @@ export type CommunityHostOperation = {
|
|
|
43
44
|
stdin?: string;
|
|
44
45
|
accepted?: readonly number[];
|
|
45
46
|
};
|
|
47
|
+
export declare function ensureCommunityRehearsalArango(execute?: SoftwareExec): Promise<void>;
|
|
46
48
|
export declare function parseCommunityRehearsalHostRequest(value: unknown): CommunityRehearsalHostRequest;
|
|
47
49
|
export declare function planCommunityRehearsalPrepare(request: Extract<CommunityRehearsalHostRequest, {
|
|
48
50
|
action: 'prepare';
|
|
@@ -1,6 +1,254 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/software.ts
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
accessSync,
|
|
5
|
+
chmodSync,
|
|
6
|
+
copyFileSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
symlinkSync,
|
|
13
|
+
unlinkSync,
|
|
14
|
+
writeFileSync
|
|
15
|
+
} from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
var PINNED_BUN_VERSION = "1.3.14";
|
|
19
|
+
var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
|
|
20
|
+
var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
|
|
21
|
+
var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
|
|
22
|
+
var OS_CATALOG = [
|
|
23
|
+
{ id: "ubuntu", version: "26.04", architecture: "x64", status: "active" }
|
|
24
|
+
];
|
|
25
|
+
var SOFTWARE_CATALOG = [
|
|
26
|
+
{ id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
27
|
+
{ id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
28
|
+
{ id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
29
|
+
{ id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
30
|
+
{ id: "cloudflare-warp", version: "2026.6.822.0-min", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
31
|
+
{ id: "ufw", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
32
|
+
{ id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
|
|
33
|
+
];
|
|
34
|
+
var UBUNTU_2604_X64 = [
|
|
35
|
+
{ requirement: { id: "bun", version: "1.3.14" } },
|
|
36
|
+
{ requirement: { id: "nginx", version: "ubuntu-26.04" } },
|
|
37
|
+
{ requirement: { id: "arangodb", version: "3.11.14" } },
|
|
38
|
+
{ requirement: { id: "cloudflared", version: "2026.7.3" } },
|
|
39
|
+
{ requirement: { id: "cloudflare-warp", version: "2026.6.822.0-min" } },
|
|
40
|
+
{ requirement: { id: "ufw", version: "ubuntu-26.04" } },
|
|
41
|
+
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
42
|
+
];
|
|
43
|
+
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
44
|
+
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
45
|
+
var runSoftwareCommand = async (argv, env = {}) => {
|
|
46
|
+
let child;
|
|
47
|
+
try {
|
|
48
|
+
child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
|
|
49
|
+
} catch (cause) {
|
|
50
|
+
const failure = softwareSpawnFailure(cause, argv);
|
|
51
|
+
if (failure)
|
|
52
|
+
return failure;
|
|
53
|
+
throw cause;
|
|
54
|
+
}
|
|
55
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
56
|
+
new Response(child.stdout).text(),
|
|
57
|
+
new Response(child.stderr).text(),
|
|
58
|
+
child.exited
|
|
59
|
+
]);
|
|
60
|
+
return { exitCode, output: `${stdout}${stderr}` };
|
|
61
|
+
};
|
|
62
|
+
var run = runSoftwareCommand;
|
|
63
|
+
var download = async (url, destination, sha256) => {
|
|
64
|
+
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
|
|
65
|
+
if (!response.ok)
|
|
66
|
+
throw new Error(`download failed with HTTP ${response.status}`);
|
|
67
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
68
|
+
if (createHash("sha256").update(bytes).digest("hex") !== sha256)
|
|
69
|
+
throw new Error("download checksum mismatch");
|
|
70
|
+
writeFileSync(destination, bytes, { mode: 384, flag: "wx" });
|
|
71
|
+
};
|
|
72
|
+
var successful = (result, pattern) => result.exitCode === 0 && (!pattern || pattern.test(result.output));
|
|
73
|
+
var aptInstall = async (name) => {
|
|
74
|
+
const environment = { DEBIAN_FRONTEND: "noninteractive" };
|
|
75
|
+
const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
|
|
76
|
+
return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", name], environment) : update;
|
|
77
|
+
};
|
|
78
|
+
async function executeSoftwareOperation(operation) {
|
|
79
|
+
const { software, version } = operation;
|
|
80
|
+
if (!UBUNTU_2604_X64.some(({ requirement }) => requirement.id === software && requirement.version === version)) {
|
|
81
|
+
return { exitCode: 2, output: "unsupported software operation" };
|
|
82
|
+
}
|
|
83
|
+
if (operation.kind === "check") {
|
|
84
|
+
if (software === "bun")
|
|
85
|
+
return run(["/usr/local/bin/bun", "--version"]).then((r) => ({ ...r, exitCode: successful(r, /^1\.3\.14\s*$/m) ? 0 : 1 }));
|
|
86
|
+
if (software === "nginx") {
|
|
87
|
+
const binary = await run(["/usr/sbin/nginx", "-v"]);
|
|
88
|
+
return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
|
|
89
|
+
}
|
|
90
|
+
if (software === "arangodb") {
|
|
91
|
+
const binary = await run(["/usr/bin/arangod", "--version"]);
|
|
92
|
+
if (!successful(binary, /3\.11\.14/))
|
|
93
|
+
return { ...binary, exitCode: 1 };
|
|
94
|
+
const [active, enabled] = await Promise.all([
|
|
95
|
+
run(["/usr/bin/systemctl", "is-active", "--quiet", "arangodb3.service"]),
|
|
96
|
+
run(["/usr/bin/systemctl", "is-enabled", "--quiet", "arangodb3.service"])
|
|
97
|
+
]);
|
|
98
|
+
return active.exitCode !== 0 && enabled.exitCode !== 0 ? { exitCode: 0, output: binary.output } : { exitCode: 1, output: "vendor standalone unit remains active or enabled" };
|
|
99
|
+
}
|
|
100
|
+
if (software === "cloudflared")
|
|
101
|
+
return run(["/usr/local/bin/cloudflared", "--version"]).then((r) => ({ ...r, exitCode: successful(r, /2026\.7\.3/) ? 0 : 1 }));
|
|
102
|
+
if (software === "cloudflare-warp")
|
|
103
|
+
return run(["/usr/bin/warp-cli", "--version"]).then((result) => {
|
|
104
|
+
const match = result.output.match(/(\d{4})\.(\d+)\.(\d+)\.(\d+)/);
|
|
105
|
+
const observed = match?.slice(1).map(Number);
|
|
106
|
+
const minimum = [2026, 6, 822, 0];
|
|
107
|
+
const supported = observed && observed.some((part, index) => part > minimum[index] && observed.slice(0, index).every((prior, priorIndex) => prior === minimum[priorIndex])) || observed?.every((part, index) => part === minimum[index]);
|
|
108
|
+
return { ...result, exitCode: result.exitCode === 0 && supported ? 0 : 1 };
|
|
109
|
+
});
|
|
110
|
+
const binaries = software === "ufw" ? ["/usr/sbin/ufw"] : ["/usr/bin/ssh", "/usr/bin/scp", "/usr/bin/ssh-keyscan", "/usr/bin/ssh-keygen"];
|
|
111
|
+
try {
|
|
112
|
+
binaries.forEach((binary) => accessSync(binary));
|
|
113
|
+
return { exitCode: 0, output: "" };
|
|
114
|
+
} catch (cause) {
|
|
115
|
+
return { exitCode: 1, output: cause instanceof Error ? cause.message : String(cause) };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (software === "nginx" || software === "ufw" || software === "openssh-client") {
|
|
119
|
+
const installed = await aptInstall(software === "openssh-client" ? "openssh-client" : software);
|
|
120
|
+
if (installed.exitCode !== 0 || software !== "nginx")
|
|
121
|
+
return installed;
|
|
122
|
+
return run(["/usr/bin/systemctl", "enable", "--now", "nginx.service"]);
|
|
123
|
+
}
|
|
124
|
+
const directory = mkdtempSync(join(tmpdir(), "forgezero-software-"));
|
|
125
|
+
try {
|
|
126
|
+
if (software === "cloudflare-warp") {
|
|
127
|
+
const key = join(directory, "cloudflare-warp-key.gpg");
|
|
128
|
+
await download("https://pkg.cloudflareclient.com/pubkey.gpg", key, "0f37fc298c98e88ee3c0ee68c95b69f1dba9eb477abe3167e13982105911264d");
|
|
129
|
+
mkdirSync("/usr/share/keyrings", { recursive: true, mode: 493 });
|
|
130
|
+
const dearmored = await run([
|
|
131
|
+
"/usr/bin/gpg",
|
|
132
|
+
"--batch",
|
|
133
|
+
"--yes",
|
|
134
|
+
"--dearmor",
|
|
135
|
+
"-o",
|
|
136
|
+
"/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg",
|
|
137
|
+
key
|
|
138
|
+
]);
|
|
139
|
+
if (dearmored.exitCode !== 0)
|
|
140
|
+
return dearmored;
|
|
141
|
+
mkdirSync("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
|
|
142
|
+
writeFileSync("/etc/apt/sources.list.d/cloudflare-client.list", `deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ resolute main
|
|
143
|
+
`, { mode: 420 });
|
|
144
|
+
return aptInstall("cloudflare-warp");
|
|
145
|
+
}
|
|
146
|
+
if (software === "bun") {
|
|
147
|
+
const archive = join(directory, "bun.zip");
|
|
148
|
+
await download("https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-linux-x64.zip", archive, BUN_RELEASE_SHA256);
|
|
149
|
+
const unpacked = join(directory, "unpacked");
|
|
150
|
+
mkdirSync(unpacked, { mode: 448 });
|
|
151
|
+
const unzipped = await run(["/usr/bin/unzip", "-q", archive, "-d", unpacked]);
|
|
152
|
+
if (unzipped.exitCode !== 0)
|
|
153
|
+
return unzipped;
|
|
154
|
+
mkdirSync("/usr/local/lib/forgezero/runtime", { recursive: true, mode: 493 });
|
|
155
|
+
copyFileSync(join(unpacked, "bun-linux-x64", "bun"), "/usr/local/lib/forgezero/runtime/bun.next");
|
|
156
|
+
chmodSync("/usr/local/lib/forgezero/runtime/bun.next", 493);
|
|
157
|
+
renameSync("/usr/local/lib/forgezero/runtime/bun.next", "/usr/local/lib/forgezero/runtime/bun");
|
|
158
|
+
try {
|
|
159
|
+
unlinkSync("/usr/local/bin/bun");
|
|
160
|
+
} catch {}
|
|
161
|
+
symlinkSync("/usr/local/lib/forgezero/runtime/bun", "/usr/local/bin/bun");
|
|
162
|
+
return { exitCode: 0, output: "" };
|
|
163
|
+
}
|
|
164
|
+
if (software === "cloudflared") {
|
|
165
|
+
const binary = join(directory, "cloudflared");
|
|
166
|
+
await download("https://github.com/cloudflare/cloudflared/releases/download/2026.7.3/cloudflared-linux-amd64", binary, CLOUDFLARED_SHA256);
|
|
167
|
+
chmodSync(binary, 493);
|
|
168
|
+
copyFileSync(binary, "/usr/local/bin/cloudflared");
|
|
169
|
+
chmodSync("/usr/local/bin/cloudflared", 493);
|
|
170
|
+
return { exitCode: 0, output: "" };
|
|
171
|
+
}
|
|
172
|
+
const deb = join(directory, "arangodb.deb");
|
|
173
|
+
await download("https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb", deb, ARANGO_SHA256);
|
|
174
|
+
let installed = await run(["/usr/bin/dpkg", "-i", deb], { DEBIAN_FRONTEND: "noninteractive" });
|
|
175
|
+
if (installed.exitCode !== 0)
|
|
176
|
+
installed = await run(["/usr/bin/apt-get", "-y", "-f", "install"], { DEBIAN_FRONTEND: "noninteractive" });
|
|
177
|
+
if (installed.exitCode !== 0)
|
|
178
|
+
return installed;
|
|
179
|
+
await run(["/usr/bin/systemctl", "disable", "--now", "arangodb3.service"]);
|
|
180
|
+
return { exitCode: 0, output: "" };
|
|
181
|
+
} finally {
|
|
182
|
+
rmSync(directory, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function observeSoftwareHost(osRelease = readFileSync("/etc/os-release", "utf8"), architecture = process.arch) {
|
|
186
|
+
const values = Object.fromEntries(osRelease.split(`
|
|
187
|
+
`).flatMap((line) => {
|
|
188
|
+
const separator = line.indexOf("=");
|
|
189
|
+
return separator > 0 ? [[line.slice(0, separator), line.slice(separator + 1).replace(/^['"]|['"]$/g, "")]] : [];
|
|
190
|
+
}));
|
|
191
|
+
return {
|
|
192
|
+
os: { id: (values.ID ?? "unknown").toLowerCase(), versionId: values.VERSION_ID ?? "unknown" },
|
|
193
|
+
architecture
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function validateSoftwareRequirements(value, _options = {}) {
|
|
197
|
+
if (!Array.isArray(value) || value.length > 32)
|
|
198
|
+
throw new Error("software requirements must be an array of at most 32 entries");
|
|
199
|
+
const seen = new Set;
|
|
200
|
+
return value.map((item) => {
|
|
201
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
202
|
+
throw new Error("software requirement must be an object");
|
|
203
|
+
const row = item;
|
|
204
|
+
if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
|
|
205
|
+
throw new Error("software requirement contains an unknown field");
|
|
206
|
+
}
|
|
207
|
+
if (!["bun", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
|
|
208
|
+
throw new Error("software requirement coordinate is invalid");
|
|
209
|
+
}
|
|
210
|
+
const requirement = { id: row.id, version: row.version };
|
|
211
|
+
if (seen.has(requirement.id))
|
|
212
|
+
throw new Error(`duplicate software requirement: ${requirement.id}`);
|
|
213
|
+
seen.add(requirement.id);
|
|
214
|
+
const catalog = SOFTWARE_CATALOG.find((candidate) => candidate.id === requirement.id && candidate.version === requirement.version);
|
|
215
|
+
if (!catalog || catalog.status !== "active") {
|
|
216
|
+
throw new Error(`software requirement is not active: ${requirement.id}@${requirement.version}`);
|
|
217
|
+
}
|
|
218
|
+
return requirement;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async function ensureSoftwareRequirements(requirementsInput, options) {
|
|
222
|
+
const requirements = validateSoftwareRequirements(requirementsInput);
|
|
223
|
+
const observation = options.observation ?? observeSoftwareHost();
|
|
224
|
+
const os = OS_CATALOG.find((candidate) => candidate.id === observation.os.id && candidate.version === observation.os.versionId && candidate.architecture === observation.architecture);
|
|
225
|
+
if (!os || os.status !== "active") {
|
|
226
|
+
throw new Error(`unsupported software strategy: ${observation.os.id} ${observation.os.versionId} ${observation.architecture}`);
|
|
227
|
+
}
|
|
228
|
+
const results = [];
|
|
229
|
+
for (const requirement of requirements) {
|
|
230
|
+
const strategy = UBUNTU_2604_X64.find(({ requirement: candidate }) => candidate.id === requirement.id && candidate.version === requirement.version);
|
|
231
|
+
if (!strategy)
|
|
232
|
+
throw new Error(`unsupported software requirement: ${requirement.id}@${requirement.version}`);
|
|
233
|
+
const before = await options.exec({ kind: "check", software: requirement.id, version: requirement.version });
|
|
234
|
+
if (before.exitCode === 0) {
|
|
235
|
+
results.push({ ...requirement, changed: false });
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const installed = await options.exec({ kind: "install", software: requirement.id, version: requirement.version });
|
|
239
|
+
if (installed.exitCode !== 0)
|
|
240
|
+
throw new Error(`could not install ${requirement.id}@${requirement.version}: ${installed.output.trim()}`);
|
|
241
|
+
const after = await options.exec({ kind: "check", software: requirement.id, version: requirement.version });
|
|
242
|
+
if (after.exitCode !== 0)
|
|
243
|
+
throw new Error(`${requirement.id}@${requirement.version} did not pass its post-install check`);
|
|
244
|
+
results.push({ ...requirement, changed: true });
|
|
245
|
+
}
|
|
246
|
+
return results;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/community-rehearsal-host.ts
|
|
250
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
251
|
+
import { lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4
252
|
import { dirname } from "node:path";
|
|
5
253
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
6
254
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
@@ -13,6 +261,15 @@ var NODES = new Map([
|
|
|
13
261
|
["dev-fz-n4", { address: "10.42.0.24", agency: false }]
|
|
14
262
|
]);
|
|
15
263
|
var RELEASE = /^[a-f0-9]{64}$/;
|
|
264
|
+
async function ensureCommunityRehearsalArango(execute = executeSoftwareOperation) {
|
|
265
|
+
const requirement = { software: "arangodb", version: COMMUNITY_REHEARSAL_VERSION };
|
|
266
|
+
if ((await execute({ kind: "check", ...requirement })).exitCode === 0)
|
|
267
|
+
return;
|
|
268
|
+
const installed = await execute({ kind: "install", ...requirement });
|
|
269
|
+
if (installed.exitCode !== 0 || (await execute({ kind: "check", ...requirement })).exitCode !== 0) {
|
|
270
|
+
throw new Error(`unable to install and verify ArangoDB ${COMMUNITY_REHEARSAL_VERSION}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
16
273
|
var exactObject = (value) => {
|
|
17
274
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
18
275
|
throw new Error("community rehearsal request must be an object");
|
|
@@ -55,7 +312,7 @@ function parseCommunityRehearsalHostRequest(value) {
|
|
|
55
312
|
throw new Error("unsupported community rehearsal host action");
|
|
56
313
|
}
|
|
57
314
|
var databaseUnit = (node) => {
|
|
58
|
-
const
|
|
315
|
+
const join2 = node.name === "dev-fz-n1" ? "" : " --starter.join=10.42.0.21";
|
|
59
316
|
const role = node.agency ? "" : " --cluster.start-agent=false --cluster.start-coordinator=true --cluster.start-dbserver=true";
|
|
60
317
|
return `[Unit]
|
|
61
318
|
Description=ForgeZero isolated Community ${COMMUNITY_REHEARSAL_VERSION} rehearsal cluster
|
|
@@ -67,7 +324,7 @@ Type=simple
|
|
|
67
324
|
User=arangodb
|
|
68
325
|
Group=arangodb
|
|
69
326
|
LoadCredentialEncrypted=arangodb-jwt:${COMMUNITY_CREDENTIAL_ROOT}/arangodb-jwt.cred
|
|
70
|
-
ExecStart=/usr/bin/arangodb --starter.mode=cluster --starter.address=${node.address} --starter.host=${node.address} --starter.data-dir=${COMMUNITY_DATABASE_ROOT} --starter.disable-ipv6 --auth.jwt-secret=%d/arangodb-jwt${
|
|
327
|
+
ExecStart=/usr/bin/arangodb --starter.mode=cluster --starter.address=${node.address} --starter.host=${node.address} --starter.data-dir=${COMMUNITY_DATABASE_ROOT} --starter.disable-ipv6 --auth.jwt-secret=%d/arangodb-jwt${join2}${role}
|
|
71
328
|
Restart=always
|
|
72
329
|
RestartSec=5
|
|
73
330
|
LimitNOFILE=100000
|
|
@@ -168,19 +425,19 @@ async function exec(argv, stdin) {
|
|
|
168
425
|
async function applyOperations(operations) {
|
|
169
426
|
for (const operation of operations) {
|
|
170
427
|
if (operation.kind === "remove-tree")
|
|
171
|
-
|
|
428
|
+
rmSync2(operation.path, { recursive: true, force: true });
|
|
172
429
|
else if (operation.kind === "write") {
|
|
173
|
-
|
|
174
|
-
|
|
430
|
+
mkdirSync2(dirname(operation.path), { recursive: true });
|
|
431
|
+
writeFileSync2(operation.path, operation.content, { mode: operation.mode });
|
|
175
432
|
} else if (operation.kind === "unlink") {
|
|
176
433
|
try {
|
|
177
|
-
|
|
434
|
+
unlinkSync2(operation.path);
|
|
178
435
|
} catch (cause) {
|
|
179
436
|
if (cause.code !== "ENOENT")
|
|
180
437
|
throw cause;
|
|
181
438
|
}
|
|
182
439
|
} else if (operation.kind === "symlink")
|
|
183
|
-
|
|
440
|
+
symlinkSync2(operation.target, operation.path);
|
|
184
441
|
else {
|
|
185
442
|
const result = await exec(operation.argv, operation.stdin);
|
|
186
443
|
if (!(operation.accepted ?? [0]).includes(result.exitCode))
|
|
@@ -216,9 +473,10 @@ async function runCommunityRehearsalHost(request) {
|
|
|
216
473
|
const node = exactNode(request.node);
|
|
217
474
|
if (request.action === "prepare") {
|
|
218
475
|
const metadata = lstatSync(request.archivePath);
|
|
219
|
-
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 ||
|
|
476
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash2("sha256").update(readFileSync2(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
|
220
477
|
throw new Error("community rehearsal archive is not the declared bounded release");
|
|
221
478
|
}
|
|
479
|
+
await ensureCommunityRehearsalArango();
|
|
222
480
|
await applyOperations(planCommunityRehearsalPrepare(request));
|
|
223
481
|
return { ok: true, action: request.action, node: node.name, release: request.release };
|
|
224
482
|
}
|
|
@@ -265,6 +523,7 @@ export {
|
|
|
265
523
|
runCommunityRehearsalHost,
|
|
266
524
|
planCommunityRehearsalPrepare,
|
|
267
525
|
parseCommunityRehearsalHostRequest,
|
|
526
|
+
ensureCommunityRehearsalArango,
|
|
268
527
|
COMMUNITY_REHEARSAL_VERSION,
|
|
269
528
|
COMMUNITY_REHEARSAL_ROOT,
|
|
270
529
|
COMMUNITY_DATABASE_ROOT,
|
package/dist/definition.js
CHANGED
|
@@ -41,8 +41,17 @@ var UBUNTU_2604_X64 = [
|
|
|
41
41
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
42
42
|
];
|
|
43
43
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
44
|
-
var
|
|
45
|
-
|
|
44
|
+
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
45
|
+
var runSoftwareCommand = async (argv, env = {}) => {
|
|
46
|
+
let child;
|
|
47
|
+
try {
|
|
48
|
+
child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
|
|
49
|
+
} catch (cause) {
|
|
50
|
+
const failure = softwareSpawnFailure(cause, argv);
|
|
51
|
+
if (failure)
|
|
52
|
+
return failure;
|
|
53
|
+
throw cause;
|
|
54
|
+
}
|
|
46
55
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
47
56
|
new Response(child.stdout).text(),
|
|
48
57
|
new Response(child.stderr).text(),
|
|
@@ -50,6 +59,7 @@ var run = async (argv, env = {}) => {
|
|
|
50
59
|
]);
|
|
51
60
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
52
61
|
};
|
|
62
|
+
var run = runSoftwareCommand;
|
|
53
63
|
var download = async (url, destination, sha256) => {
|
|
54
64
|
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
|
|
55
65
|
if (!response.ok)
|
package/dist/deploy-file.js
CHANGED
|
@@ -41,8 +41,17 @@ var UBUNTU_2604_X64 = [
|
|
|
41
41
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
42
42
|
];
|
|
43
43
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
44
|
-
var
|
|
45
|
-
|
|
44
|
+
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
45
|
+
var runSoftwareCommand = async (argv, env = {}) => {
|
|
46
|
+
let child;
|
|
47
|
+
try {
|
|
48
|
+
child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
|
|
49
|
+
} catch (cause) {
|
|
50
|
+
const failure = softwareSpawnFailure(cause, argv);
|
|
51
|
+
if (failure)
|
|
52
|
+
return failure;
|
|
53
|
+
throw cause;
|
|
54
|
+
}
|
|
46
55
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
47
56
|
new Response(child.stdout).text(),
|
|
48
57
|
new Response(child.stderr).text(),
|
|
@@ -50,6 +59,7 @@ var run = async (argv, env = {}) => {
|
|
|
50
59
|
]);
|
|
51
60
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
52
61
|
};
|
|
62
|
+
var run = runSoftwareCommand;
|
|
53
63
|
var download = async (url, destination, sha256) => {
|
|
54
64
|
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
|
|
55
65
|
if (!response.ok)
|
package/dist/fz-agent.js
CHANGED
|
@@ -4990,8 +4990,17 @@ var UBUNTU_2604_X64 = [
|
|
|
4990
4990
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
4991
4991
|
];
|
|
4992
4992
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
4993
|
-
var
|
|
4994
|
-
|
|
4993
|
+
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
4994
|
+
var runSoftwareCommand = async (argv, env = {}) => {
|
|
4995
|
+
let child;
|
|
4996
|
+
try {
|
|
4997
|
+
child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
|
|
4998
|
+
} catch (cause) {
|
|
4999
|
+
const failure = softwareSpawnFailure(cause, argv);
|
|
5000
|
+
if (failure)
|
|
5001
|
+
return failure;
|
|
5002
|
+
throw cause;
|
|
5003
|
+
}
|
|
4995
5004
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
4996
5005
|
new Response(child.stdout).text(),
|
|
4997
5006
|
new Response(child.stderr).text(),
|
|
@@ -4999,6 +5008,7 @@ var run = async (argv, env = {}) => {
|
|
|
4999
5008
|
]);
|
|
5000
5009
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
5001
5010
|
};
|
|
5011
|
+
var run = runSoftwareCommand;
|
|
5002
5012
|
var download = async (url, destination, sha2562) => {
|
|
5003
5013
|
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
|
|
5004
5014
|
if (!response.ok)
|
|
@@ -7000,7 +7010,7 @@ function assertSupportedGuestImage(imageKey) {
|
|
|
7000
7010
|
}
|
|
7001
7011
|
|
|
7002
7012
|
// src/version.ts
|
|
7003
|
-
var VERSION = "0.1.
|
|
7013
|
+
var VERSION = "0.1.52";
|
|
7004
7014
|
|
|
7005
7015
|
// src/ssh-bootstrap.ts
|
|
7006
7016
|
class SshBootstrapError extends Error {
|
|
@@ -13216,6 +13226,15 @@ var NODES = new Map([
|
|
|
13216
13226
|
["dev-fz-n4", { address: "10.42.0.24", agency: false }]
|
|
13217
13227
|
]);
|
|
13218
13228
|
var RELEASE = /^[a-f0-9]{64}$/;
|
|
13229
|
+
async function ensureCommunityRehearsalArango(execute3 = executeSoftwareOperation) {
|
|
13230
|
+
const requirement = { software: "arangodb", version: COMMUNITY_REHEARSAL_VERSION };
|
|
13231
|
+
if ((await execute3({ kind: "check", ...requirement })).exitCode === 0)
|
|
13232
|
+
return;
|
|
13233
|
+
const installed = await execute3({ kind: "install", ...requirement });
|
|
13234
|
+
if (installed.exitCode !== 0 || (await execute3({ kind: "check", ...requirement })).exitCode !== 0) {
|
|
13235
|
+
throw new Error(`unable to install and verify ArangoDB ${COMMUNITY_REHEARSAL_VERSION}`);
|
|
13236
|
+
}
|
|
13237
|
+
}
|
|
13219
13238
|
var exactObject = (value) => {
|
|
13220
13239
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
13221
13240
|
throw new Error("community rehearsal request must be an object");
|
|
@@ -13422,6 +13441,7 @@ async function runCommunityRehearsalHost(request) {
|
|
|
13422
13441
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash8("sha256").update(readFileSync16(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
|
13423
13442
|
throw new Error("community rehearsal archive is not the declared bounded release");
|
|
13424
13443
|
}
|
|
13444
|
+
await ensureCommunityRehearsalArango();
|
|
13425
13445
|
await applyOperations(planCommunityRehearsalPrepare(request));
|
|
13426
13446
|
return { ok: true, action: request.action, node: node.name, release: request.release };
|
|
13427
13447
|
}
|
package/dist/fz.js
CHANGED
package/dist/metal-bootstrap.js
CHANGED
|
@@ -292,7 +292,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// src/version.ts
|
|
295
|
-
var VERSION = "0.1.
|
|
295
|
+
var VERSION = "0.1.52";
|
|
296
296
|
|
|
297
297
|
// src/otel-collector.ts
|
|
298
298
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -719,8 +719,17 @@ var UBUNTU_2604_X64 = [
|
|
|
719
719
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
720
720
|
];
|
|
721
721
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
722
|
-
var
|
|
723
|
-
|
|
722
|
+
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
723
|
+
var runSoftwareCommand = async (argv, env = {}) => {
|
|
724
|
+
let child;
|
|
725
|
+
try {
|
|
726
|
+
child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
|
|
727
|
+
} catch (cause) {
|
|
728
|
+
const failure = softwareSpawnFailure(cause, argv);
|
|
729
|
+
if (failure)
|
|
730
|
+
return failure;
|
|
731
|
+
throw cause;
|
|
732
|
+
}
|
|
724
733
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
725
734
|
new Response(child.stdout).text(),
|
|
726
735
|
new Response(child.stderr).text(),
|
|
@@ -728,6 +737,7 @@ var run = async (argv, env = {}) => {
|
|
|
728
737
|
]);
|
|
729
738
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
730
739
|
};
|
|
740
|
+
var run = runSoftwareCommand;
|
|
731
741
|
var download = async (url, destination, sha256) => {
|
|
732
742
|
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
|
|
733
743
|
if (!response.ok)
|
|
@@ -1697,7 +1707,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
1697
1707
|
}
|
|
1698
1708
|
|
|
1699
1709
|
// src/version.ts
|
|
1700
|
-
var VERSION3 = "0.1.
|
|
1710
|
+
var VERSION3 = "0.1.52";
|
|
1701
1711
|
|
|
1702
1712
|
// src/egress-policy.ts
|
|
1703
1713
|
import { realpathSync as realpathSync2 } from "node:fs";
|
package/dist/provision.js
CHANGED
|
@@ -719,8 +719,17 @@ var UBUNTU_2604_X64 = [
|
|
|
719
719
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
720
720
|
];
|
|
721
721
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
722
|
-
var
|
|
723
|
-
|
|
722
|
+
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
723
|
+
var runSoftwareCommand = async (argv, env = {}) => {
|
|
724
|
+
let child;
|
|
725
|
+
try {
|
|
726
|
+
child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
|
|
727
|
+
} catch (cause) {
|
|
728
|
+
const failure = softwareSpawnFailure(cause, argv);
|
|
729
|
+
if (failure)
|
|
730
|
+
return failure;
|
|
731
|
+
throw cause;
|
|
732
|
+
}
|
|
724
733
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
725
734
|
new Response(child.stdout).text(),
|
|
726
735
|
new Response(child.stderr).text(),
|
|
@@ -728,6 +737,7 @@ var run = async (argv, env = {}) => {
|
|
|
728
737
|
]);
|
|
729
738
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
730
739
|
};
|
|
740
|
+
var run = runSoftwareCommand;
|
|
731
741
|
var download = async (url, destination, sha256) => {
|
|
732
742
|
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
|
|
733
743
|
if (!response.ok)
|
|
@@ -1697,7 +1707,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
1697
1707
|
}
|
|
1698
1708
|
|
|
1699
1709
|
// src/version.ts
|
|
1700
|
-
var VERSION3 = "0.1.
|
|
1710
|
+
var VERSION3 = "0.1.52";
|
|
1701
1711
|
|
|
1702
1712
|
// src/egress-policy.ts
|
|
1703
1713
|
import { realpathSync as realpathSync2 } from "node:fs";
|
package/dist/software-helper.js
CHANGED
|
@@ -41,8 +41,17 @@ var UBUNTU_2604_X64 = [
|
|
|
41
41
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
42
42
|
];
|
|
43
43
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
44
|
-
var
|
|
45
|
-
|
|
44
|
+
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
45
|
+
var runSoftwareCommand = async (argv, env = {}) => {
|
|
46
|
+
let child;
|
|
47
|
+
try {
|
|
48
|
+
child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
|
|
49
|
+
} catch (cause) {
|
|
50
|
+
const failure = softwareSpawnFailure(cause, argv);
|
|
51
|
+
if (failure)
|
|
52
|
+
return failure;
|
|
53
|
+
throw cause;
|
|
54
|
+
}
|
|
46
55
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
47
56
|
new Response(child.stdout).text(),
|
|
48
57
|
new Response(child.stderr).text(),
|
|
@@ -50,6 +59,7 @@ var run = async (argv, env = {}) => {
|
|
|
50
59
|
]);
|
|
51
60
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
52
61
|
};
|
|
62
|
+
var run = runSoftwareCommand;
|
|
53
63
|
var download = async (url, destination, sha256) => {
|
|
54
64
|
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
|
|
55
65
|
if (!response.ok)
|
package/dist/software.d.ts
CHANGED
|
@@ -46,6 +46,8 @@ export declare const BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fe
|
|
|
46
46
|
/** Public, command-free catalog. Root strategies remain private below. */
|
|
47
47
|
export declare const OS_CATALOG: readonly OsCatalogEntry[];
|
|
48
48
|
export declare const SOFTWARE_CATALOG: readonly SoftwareCatalogEntry[];
|
|
49
|
+
export declare const softwareSpawnFailure: (cause: unknown, argv: readonly string[]) => SoftwareCommandResult | undefined;
|
|
50
|
+
export declare const runSoftwareCommand: (argv: readonly string[], env?: Record<string, string>) => Promise<SoftwareCommandResult>;
|
|
49
51
|
/** Closed, fixed-argv privileged strategy executor. No repository value becomes a command. */
|
|
50
52
|
export declare function executeSoftwareOperation(operation: SoftwareOperation): Promise<SoftwareCommandResult>;
|
|
51
53
|
export declare function observeSoftwareHost(osRelease?: string, architecture?: NodeJS.Architecture): SoftwareObservation;
|
package/dist/software.js
CHANGED
|
@@ -41,8 +41,17 @@ var UBUNTU_2604_X64 = [
|
|
|
41
41
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
42
42
|
];
|
|
43
43
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
44
|
-
var
|
|
45
|
-
|
|
44
|
+
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
45
|
+
var runSoftwareCommand = async (argv, env = {}) => {
|
|
46
|
+
let child;
|
|
47
|
+
try {
|
|
48
|
+
child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
|
|
49
|
+
} catch (cause) {
|
|
50
|
+
const failure = softwareSpawnFailure(cause, argv);
|
|
51
|
+
if (failure)
|
|
52
|
+
return failure;
|
|
53
|
+
throw cause;
|
|
54
|
+
}
|
|
46
55
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
47
56
|
new Response(child.stdout).text(),
|
|
48
57
|
new Response(child.stderr).text(),
|
|
@@ -50,6 +59,7 @@ var run = async (argv, env = {}) => {
|
|
|
50
59
|
]);
|
|
51
60
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
52
61
|
};
|
|
62
|
+
var run = runSoftwareCommand;
|
|
53
63
|
var download = async (url, destination, sha256) => {
|
|
54
64
|
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
|
|
55
65
|
if (!response.ok)
|
|
@@ -237,6 +247,8 @@ async function ensureSoftwareRequirements(requirementsInput, options) {
|
|
|
237
247
|
}
|
|
238
248
|
export {
|
|
239
249
|
validateSoftwareRequirements,
|
|
250
|
+
softwareSpawnFailure,
|
|
251
|
+
runSoftwareCommand,
|
|
240
252
|
observeSoftwareHost,
|
|
241
253
|
executeSoftwareOperation,
|
|
242
254
|
ensureSoftwareRequirements,
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** One package version shared by both public binaries. Pinned to package.json by tests. */
|
|
2
|
-
export declare const VERSION = "0.1.
|
|
2
|
+
export declare const VERSION = "0.1.52";
|