@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.
@@ -749,7 +749,7 @@ async function postSignedNode(options, path, body) {
749
749
  }
750
750
 
751
751
  // src/version.ts
752
- var VERSION3 = "0.1.50";
752
+ var VERSION3 = "0.1.52";
753
753
 
754
754
  // src/agent-heartbeat.ts
755
755
  var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
package/dist/bootstrap.js CHANGED
@@ -1207,7 +1207,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1207
1207
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1208
1208
 
1209
1209
  // src/version.ts
1210
- var VERSION = "0.1.50";
1210
+ var VERSION = "0.1.52";
1211
1211
 
1212
1212
  // src/software.ts
1213
1213
  var PINNED_BUN_VERSION = "1.3.14";
@@ -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/community-rehearsal-host.ts
1
+ // src/software.ts
2
2
  import { createHash } from "node:crypto";
3
- import { lstatSync, mkdirSync, readFileSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
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 join = node.name === "dev-fz-n1" ? "" : " --starter.join=10.42.0.21";
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${join}${role}
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
- rmSync(operation.path, { recursive: true, force: true });
428
+ rmSync2(operation.path, { recursive: true, force: true });
172
429
  else if (operation.kind === "write") {
173
- mkdirSync(dirname(operation.path), { recursive: true });
174
- writeFileSync(operation.path, operation.content, { mode: operation.mode });
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
- unlinkSync(operation.path);
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
- symlinkSync(operation.target, operation.path);
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 || createHash("sha256").update(readFileSync(request.archivePath)).digest("hex") !== request.archiveSha256) {
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,
@@ -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 run = async (argv, env = {}) => {
45
- const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
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)
@@ -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 run = async (argv, env = {}) => {
45
- const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
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 run = async (argv, env = {}) => {
4994
- const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
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.50";
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
@@ -4830,7 +4830,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
4830
4830
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
4831
4831
 
4832
4832
  // src/version.ts
4833
- var VERSION2 = "0.1.50";
4833
+ var VERSION2 = "0.1.52";
4834
4834
 
4835
4835
  // src/software.ts
4836
4836
  var PINNED_BUN_VERSION = "1.3.14";
@@ -292,7 +292,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
292
292
  }
293
293
 
294
294
  // src/version.ts
295
- var VERSION = "0.1.50";
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";
@@ -1207,7 +1207,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1207
1207
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1208
1208
 
1209
1209
  // src/version.ts
1210
- var VERSION = "0.1.50";
1210
+ var VERSION = "0.1.52";
1211
1211
 
1212
1212
  // src/software.ts
1213
1213
  var PINNED_BUN_VERSION = "1.3.14";
@@ -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 run = async (argv, env = {}) => {
723
- const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
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.50";
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 run = async (argv, env = {}) => {
723
- const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
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.50";
1710
+ var VERSION3 = "0.1.52";
1701
1711
 
1702
1712
  // src/egress-policy.ts
1703
1713
  import { realpathSync as realpathSync2 } from "node:fs";
@@ -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 run = async (argv, env = {}) => {
45
- const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
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)
@@ -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 run = async (argv, env = {}) => {
45
- const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
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.50";
2
+ export declare const VERSION = "0.1.52";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",