@forgezero/agent 0.1.50 → 0.1.51

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.51";
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.51";
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,244 @@
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 run = async (argv, env = {}) => {
45
+ const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: path, LANG: "C", LC_ALL: "C", ...env } });
46
+ const [stdout, stderr, exitCode] = await Promise.all([
47
+ new Response(child.stdout).text(),
48
+ new Response(child.stderr).text(),
49
+ child.exited
50
+ ]);
51
+ return { exitCode, output: `${stdout}${stderr}` };
52
+ };
53
+ var download = async (url, destination, sha256) => {
54
+ const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
55
+ if (!response.ok)
56
+ throw new Error(`download failed with HTTP ${response.status}`);
57
+ const bytes = new Uint8Array(await response.arrayBuffer());
58
+ if (createHash("sha256").update(bytes).digest("hex") !== sha256)
59
+ throw new Error("download checksum mismatch");
60
+ writeFileSync(destination, bytes, { mode: 384, flag: "wx" });
61
+ };
62
+ var successful = (result, pattern) => result.exitCode === 0 && (!pattern || pattern.test(result.output));
63
+ var aptInstall = async (name) => {
64
+ const environment = { DEBIAN_FRONTEND: "noninteractive" };
65
+ const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
66
+ return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", name], environment) : update;
67
+ };
68
+ async function executeSoftwareOperation(operation) {
69
+ const { software, version } = operation;
70
+ if (!UBUNTU_2604_X64.some(({ requirement }) => requirement.id === software && requirement.version === version)) {
71
+ return { exitCode: 2, output: "unsupported software operation" };
72
+ }
73
+ if (operation.kind === "check") {
74
+ if (software === "bun")
75
+ return run(["/usr/local/bin/bun", "--version"]).then((r) => ({ ...r, exitCode: successful(r, /^1\.3\.14\s*$/m) ? 0 : 1 }));
76
+ if (software === "nginx") {
77
+ const binary = await run(["/usr/sbin/nginx", "-v"]);
78
+ return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
79
+ }
80
+ if (software === "arangodb") {
81
+ const binary = await run(["/usr/bin/arangod", "--version"]);
82
+ if (!successful(binary, /3\.11\.14/))
83
+ return { ...binary, exitCode: 1 };
84
+ const [active, enabled] = await Promise.all([
85
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "arangodb3.service"]),
86
+ run(["/usr/bin/systemctl", "is-enabled", "--quiet", "arangodb3.service"])
87
+ ]);
88
+ return active.exitCode !== 0 && enabled.exitCode !== 0 ? { exitCode: 0, output: binary.output } : { exitCode: 1, output: "vendor standalone unit remains active or enabled" };
89
+ }
90
+ if (software === "cloudflared")
91
+ return run(["/usr/local/bin/cloudflared", "--version"]).then((r) => ({ ...r, exitCode: successful(r, /2026\.7\.3/) ? 0 : 1 }));
92
+ if (software === "cloudflare-warp")
93
+ return run(["/usr/bin/warp-cli", "--version"]).then((result) => {
94
+ const match = result.output.match(/(\d{4})\.(\d+)\.(\d+)\.(\d+)/);
95
+ const observed = match?.slice(1).map(Number);
96
+ const minimum = [2026, 6, 822, 0];
97
+ 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]);
98
+ return { ...result, exitCode: result.exitCode === 0 && supported ? 0 : 1 };
99
+ });
100
+ const binaries = software === "ufw" ? ["/usr/sbin/ufw"] : ["/usr/bin/ssh", "/usr/bin/scp", "/usr/bin/ssh-keyscan", "/usr/bin/ssh-keygen"];
101
+ try {
102
+ binaries.forEach((binary) => accessSync(binary));
103
+ return { exitCode: 0, output: "" };
104
+ } catch (cause) {
105
+ return { exitCode: 1, output: cause instanceof Error ? cause.message : String(cause) };
106
+ }
107
+ }
108
+ if (software === "nginx" || software === "ufw" || software === "openssh-client") {
109
+ const installed = await aptInstall(software === "openssh-client" ? "openssh-client" : software);
110
+ if (installed.exitCode !== 0 || software !== "nginx")
111
+ return installed;
112
+ return run(["/usr/bin/systemctl", "enable", "--now", "nginx.service"]);
113
+ }
114
+ const directory = mkdtempSync(join(tmpdir(), "forgezero-software-"));
115
+ try {
116
+ if (software === "cloudflare-warp") {
117
+ const key = join(directory, "cloudflare-warp-key.gpg");
118
+ await download("https://pkg.cloudflareclient.com/pubkey.gpg", key, "0f37fc298c98e88ee3c0ee68c95b69f1dba9eb477abe3167e13982105911264d");
119
+ mkdirSync("/usr/share/keyrings", { recursive: true, mode: 493 });
120
+ const dearmored = await run([
121
+ "/usr/bin/gpg",
122
+ "--batch",
123
+ "--yes",
124
+ "--dearmor",
125
+ "-o",
126
+ "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg",
127
+ key
128
+ ]);
129
+ if (dearmored.exitCode !== 0)
130
+ return dearmored;
131
+ mkdirSync("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
132
+ 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
133
+ `, { mode: 420 });
134
+ return aptInstall("cloudflare-warp");
135
+ }
136
+ if (software === "bun") {
137
+ const archive = join(directory, "bun.zip");
138
+ await download("https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-linux-x64.zip", archive, BUN_RELEASE_SHA256);
139
+ const unpacked = join(directory, "unpacked");
140
+ mkdirSync(unpacked, { mode: 448 });
141
+ const unzipped = await run(["/usr/bin/unzip", "-q", archive, "-d", unpacked]);
142
+ if (unzipped.exitCode !== 0)
143
+ return unzipped;
144
+ mkdirSync("/usr/local/lib/forgezero/runtime", { recursive: true, mode: 493 });
145
+ copyFileSync(join(unpacked, "bun-linux-x64", "bun"), "/usr/local/lib/forgezero/runtime/bun.next");
146
+ chmodSync("/usr/local/lib/forgezero/runtime/bun.next", 493);
147
+ renameSync("/usr/local/lib/forgezero/runtime/bun.next", "/usr/local/lib/forgezero/runtime/bun");
148
+ try {
149
+ unlinkSync("/usr/local/bin/bun");
150
+ } catch {}
151
+ symlinkSync("/usr/local/lib/forgezero/runtime/bun", "/usr/local/bin/bun");
152
+ return { exitCode: 0, output: "" };
153
+ }
154
+ if (software === "cloudflared") {
155
+ const binary = join(directory, "cloudflared");
156
+ await download("https://github.com/cloudflare/cloudflared/releases/download/2026.7.3/cloudflared-linux-amd64", binary, CLOUDFLARED_SHA256);
157
+ chmodSync(binary, 493);
158
+ copyFileSync(binary, "/usr/local/bin/cloudflared");
159
+ chmodSync("/usr/local/bin/cloudflared", 493);
160
+ return { exitCode: 0, output: "" };
161
+ }
162
+ const deb = join(directory, "arangodb.deb");
163
+ await download("https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb", deb, ARANGO_SHA256);
164
+ let installed = await run(["/usr/bin/dpkg", "-i", deb], { DEBIAN_FRONTEND: "noninteractive" });
165
+ if (installed.exitCode !== 0)
166
+ installed = await run(["/usr/bin/apt-get", "-y", "-f", "install"], { DEBIAN_FRONTEND: "noninteractive" });
167
+ if (installed.exitCode !== 0)
168
+ return installed;
169
+ await run(["/usr/bin/systemctl", "disable", "--now", "arangodb3.service"]);
170
+ return { exitCode: 0, output: "" };
171
+ } finally {
172
+ rmSync(directory, { recursive: true, force: true });
173
+ }
174
+ }
175
+ function observeSoftwareHost(osRelease = readFileSync("/etc/os-release", "utf8"), architecture = process.arch) {
176
+ const values = Object.fromEntries(osRelease.split(`
177
+ `).flatMap((line) => {
178
+ const separator = line.indexOf("=");
179
+ return separator > 0 ? [[line.slice(0, separator), line.slice(separator + 1).replace(/^['"]|['"]$/g, "")]] : [];
180
+ }));
181
+ return {
182
+ os: { id: (values.ID ?? "unknown").toLowerCase(), versionId: values.VERSION_ID ?? "unknown" },
183
+ architecture
184
+ };
185
+ }
186
+ function validateSoftwareRequirements(value, _options = {}) {
187
+ if (!Array.isArray(value) || value.length > 32)
188
+ throw new Error("software requirements must be an array of at most 32 entries");
189
+ const seen = new Set;
190
+ return value.map((item) => {
191
+ if (!item || typeof item !== "object" || Array.isArray(item))
192
+ throw new Error("software requirement must be an object");
193
+ const row = item;
194
+ if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
195
+ throw new Error("software requirement contains an unknown field");
196
+ }
197
+ 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)) {
198
+ throw new Error("software requirement coordinate is invalid");
199
+ }
200
+ const requirement = { id: row.id, version: row.version };
201
+ if (seen.has(requirement.id))
202
+ throw new Error(`duplicate software requirement: ${requirement.id}`);
203
+ seen.add(requirement.id);
204
+ const catalog = SOFTWARE_CATALOG.find((candidate) => candidate.id === requirement.id && candidate.version === requirement.version);
205
+ if (!catalog || catalog.status !== "active") {
206
+ throw new Error(`software requirement is not active: ${requirement.id}@${requirement.version}`);
207
+ }
208
+ return requirement;
209
+ });
210
+ }
211
+ async function ensureSoftwareRequirements(requirementsInput, options) {
212
+ const requirements = validateSoftwareRequirements(requirementsInput);
213
+ const observation = options.observation ?? observeSoftwareHost();
214
+ const os = OS_CATALOG.find((candidate) => candidate.id === observation.os.id && candidate.version === observation.os.versionId && candidate.architecture === observation.architecture);
215
+ if (!os || os.status !== "active") {
216
+ throw new Error(`unsupported software strategy: ${observation.os.id} ${observation.os.versionId} ${observation.architecture}`);
217
+ }
218
+ const results = [];
219
+ for (const requirement of requirements) {
220
+ const strategy = UBUNTU_2604_X64.find(({ requirement: candidate }) => candidate.id === requirement.id && candidate.version === requirement.version);
221
+ if (!strategy)
222
+ throw new Error(`unsupported software requirement: ${requirement.id}@${requirement.version}`);
223
+ const before = await options.exec({ kind: "check", software: requirement.id, version: requirement.version });
224
+ if (before.exitCode === 0) {
225
+ results.push({ ...requirement, changed: false });
226
+ continue;
227
+ }
228
+ const installed = await options.exec({ kind: "install", software: requirement.id, version: requirement.version });
229
+ if (installed.exitCode !== 0)
230
+ throw new Error(`could not install ${requirement.id}@${requirement.version}: ${installed.output.trim()}`);
231
+ const after = await options.exec({ kind: "check", software: requirement.id, version: requirement.version });
232
+ if (after.exitCode !== 0)
233
+ throw new Error(`${requirement.id}@${requirement.version} did not pass its post-install check`);
234
+ results.push({ ...requirement, changed: true });
235
+ }
236
+ return results;
237
+ }
238
+
239
+ // src/community-rehearsal-host.ts
240
+ import { createHash as createHash2 } from "node:crypto";
241
+ import { lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
4
242
  import { dirname } from "node:path";
5
243
  var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
6
244
  var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
@@ -13,6 +251,15 @@ var NODES = new Map([
13
251
  ["dev-fz-n4", { address: "10.42.0.24", agency: false }]
14
252
  ]);
15
253
  var RELEASE = /^[a-f0-9]{64}$/;
254
+ async function ensureCommunityRehearsalArango(execute = executeSoftwareOperation) {
255
+ const requirement = { software: "arangodb", version: COMMUNITY_REHEARSAL_VERSION };
256
+ if ((await execute({ kind: "check", ...requirement })).exitCode === 0)
257
+ return;
258
+ const installed = await execute({ kind: "install", ...requirement });
259
+ if (installed.exitCode !== 0 || (await execute({ kind: "check", ...requirement })).exitCode !== 0) {
260
+ throw new Error(`unable to install and verify ArangoDB ${COMMUNITY_REHEARSAL_VERSION}`);
261
+ }
262
+ }
16
263
  var exactObject = (value) => {
17
264
  if (!value || typeof value !== "object" || Array.isArray(value))
18
265
  throw new Error("community rehearsal request must be an object");
@@ -55,7 +302,7 @@ function parseCommunityRehearsalHostRequest(value) {
55
302
  throw new Error("unsupported community rehearsal host action");
56
303
  }
57
304
  var databaseUnit = (node) => {
58
- const join = node.name === "dev-fz-n1" ? "" : " --starter.join=10.42.0.21";
305
+ const join2 = node.name === "dev-fz-n1" ? "" : " --starter.join=10.42.0.21";
59
306
  const role = node.agency ? "" : " --cluster.start-agent=false --cluster.start-coordinator=true --cluster.start-dbserver=true";
60
307
  return `[Unit]
61
308
  Description=ForgeZero isolated Community ${COMMUNITY_REHEARSAL_VERSION} rehearsal cluster
@@ -67,7 +314,7 @@ Type=simple
67
314
  User=arangodb
68
315
  Group=arangodb
69
316
  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}
317
+ 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
318
  Restart=always
72
319
  RestartSec=5
73
320
  LimitNOFILE=100000
@@ -168,19 +415,19 @@ async function exec(argv, stdin) {
168
415
  async function applyOperations(operations) {
169
416
  for (const operation of operations) {
170
417
  if (operation.kind === "remove-tree")
171
- rmSync(operation.path, { recursive: true, force: true });
418
+ rmSync2(operation.path, { recursive: true, force: true });
172
419
  else if (operation.kind === "write") {
173
- mkdirSync(dirname(operation.path), { recursive: true });
174
- writeFileSync(operation.path, operation.content, { mode: operation.mode });
420
+ mkdirSync2(dirname(operation.path), { recursive: true });
421
+ writeFileSync2(operation.path, operation.content, { mode: operation.mode });
175
422
  } else if (operation.kind === "unlink") {
176
423
  try {
177
- unlinkSync(operation.path);
424
+ unlinkSync2(operation.path);
178
425
  } catch (cause) {
179
426
  if (cause.code !== "ENOENT")
180
427
  throw cause;
181
428
  }
182
429
  } else if (operation.kind === "symlink")
183
- symlinkSync(operation.target, operation.path);
430
+ symlinkSync2(operation.target, operation.path);
184
431
  else {
185
432
  const result = await exec(operation.argv, operation.stdin);
186
433
  if (!(operation.accepted ?? [0]).includes(result.exitCode))
@@ -216,9 +463,10 @@ async function runCommunityRehearsalHost(request) {
216
463
  const node = exactNode(request.node);
217
464
  if (request.action === "prepare") {
218
465
  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) {
466
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash2("sha256").update(readFileSync2(request.archivePath)).digest("hex") !== request.archiveSha256) {
220
467
  throw new Error("community rehearsal archive is not the declared bounded release");
221
468
  }
469
+ await ensureCommunityRehearsalArango();
222
470
  await applyOperations(planCommunityRehearsalPrepare(request));
223
471
  return { ok: true, action: request.action, node: node.name, release: request.release };
224
472
  }
@@ -265,6 +513,7 @@ export {
265
513
  runCommunityRehearsalHost,
266
514
  planCommunityRehearsalPrepare,
267
515
  parseCommunityRehearsalHostRequest,
516
+ ensureCommunityRehearsalArango,
268
517
  COMMUNITY_REHEARSAL_VERSION,
269
518
  COMMUNITY_REHEARSAL_ROOT,
270
519
  COMMUNITY_DATABASE_ROOT,
package/dist/fz-agent.js CHANGED
@@ -7000,7 +7000,7 @@ function assertSupportedGuestImage(imageKey) {
7000
7000
  }
7001
7001
 
7002
7002
  // src/version.ts
7003
- var VERSION = "0.1.50";
7003
+ var VERSION = "0.1.51";
7004
7004
 
7005
7005
  // src/ssh-bootstrap.ts
7006
7006
  class SshBootstrapError extends Error {
@@ -13216,6 +13216,15 @@ var NODES = new Map([
13216
13216
  ["dev-fz-n4", { address: "10.42.0.24", agency: false }]
13217
13217
  ]);
13218
13218
  var RELEASE = /^[a-f0-9]{64}$/;
13219
+ async function ensureCommunityRehearsalArango(execute3 = executeSoftwareOperation) {
13220
+ const requirement = { software: "arangodb", version: COMMUNITY_REHEARSAL_VERSION };
13221
+ if ((await execute3({ kind: "check", ...requirement })).exitCode === 0)
13222
+ return;
13223
+ const installed = await execute3({ kind: "install", ...requirement });
13224
+ if (installed.exitCode !== 0 || (await execute3({ kind: "check", ...requirement })).exitCode !== 0) {
13225
+ throw new Error(`unable to install and verify ArangoDB ${COMMUNITY_REHEARSAL_VERSION}`);
13226
+ }
13227
+ }
13219
13228
  var exactObject = (value) => {
13220
13229
  if (!value || typeof value !== "object" || Array.isArray(value))
13221
13230
  throw new Error("community rehearsal request must be an object");
@@ -13422,6 +13431,7 @@ async function runCommunityRehearsalHost(request) {
13422
13431
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash8("sha256").update(readFileSync16(request.archivePath)).digest("hex") !== request.archiveSha256) {
13423
13432
  throw new Error("community rehearsal archive is not the declared bounded release");
13424
13433
  }
13434
+ await ensureCommunityRehearsalArango();
13425
13435
  await applyOperations(planCommunityRehearsalPrepare(request));
13426
13436
  return { ok: true, action: request.action, node: node.name, release: request.release };
13427
13437
  }
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.51";
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.51";
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.51";
1211
1211
 
1212
1212
  // src/software.ts
1213
1213
  var PINNED_BUN_VERSION = "1.3.14";
@@ -1697,7 +1697,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
1697
1697
  }
1698
1698
 
1699
1699
  // src/version.ts
1700
- var VERSION3 = "0.1.50";
1700
+ var VERSION3 = "0.1.51";
1701
1701
 
1702
1702
  // src/egress-policy.ts
1703
1703
  import { realpathSync as realpathSync2 } from "node:fs";
package/dist/provision.js CHANGED
@@ -1697,7 +1697,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
1697
1697
  }
1698
1698
 
1699
1699
  // src/version.ts
1700
- var VERSION3 = "0.1.50";
1700
+ var VERSION3 = "0.1.51";
1701
1701
 
1702
1702
  // src/egress-policy.ts
1703
1703
  import { realpathSync as realpathSync2 } from "node:fs";
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.51";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.50",
3
+ "version": "0.1.51",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",