@forgezero/agent 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/agent-heartbeat.d.ts +45 -0
- package/dist/agent-heartbeat.js +511 -0
- package/dist/agent-update-helper.d.ts +45 -0
- package/dist/agent-update-helper.js +366 -0
- package/dist/agent-update.d.ts +46 -0
- package/dist/agent-update.js +184 -0
- package/dist/definition.d.ts +1 -7
- package/dist/definition.js +92 -23
- package/dist/deployment-pull.d.ts +2 -0
- package/dist/deployment.d.ts +3 -0
- package/dist/fz-agent.js +7865 -2617
- package/dist/fz.js +8287 -38
- package/dist/guest-enrolment.js +14 -1
- package/dist/index.d.ts +13 -0
- package/dist/metal-helper-socket.js +23 -4
- package/dist/metal-provision.js +23 -4
- package/dist/migration-pull.js +14 -1
- package/dist/node-vault.js +15 -2
- package/dist/provision.d.ts +13 -0
- package/dist/provision.js +685 -6
- package/dist/provisioning-pull.js +14 -1
- package/dist/signed-node-http.d.ts +10 -0
- package/dist/socket.d.ts +2 -0
- package/dist/software-helper.d.ts +14 -0
- package/dist/software-helper.js +203 -0
- package/dist/software.d.ts +27 -0
- package/dist/software.js +95 -0
- package/dist/version.d.ts +1 -1
- package/package.json +24 -4
package/dist/definition.js
CHANGED
|
@@ -1,3 +1,94 @@
|
|
|
1
|
+
// src/software.ts
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
var BUN_INSTALLER_SHA256 = "bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd";
|
|
4
|
+
var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
|
|
5
|
+
var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
|
|
6
|
+
var UBUNTU_2604_X64 = [
|
|
7
|
+
{
|
|
8
|
+
requirement: { id: "bun", version: "1.3.14" },
|
|
9
|
+
check: 'test "$(/usr/local/bin/bun --version 2>/dev/null)" = 1.3.14',
|
|
10
|
+
install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL https://bun.sh/install -o "$tmp/install"; ` + `echo "${BUN_INSTALLER_SHA256} $tmp/install" | sha256sum -c -; ` + `BUN_INSTALL="$tmp/bun" BUN_VERSION=1.3.14 bash "$tmp/install" >/dev/null; ` + `install -d -m 0755 /usr/local/lib/forgezero/runtime; ` + `install -m 0755 "$tmp/bun/bin/bun" /usr/local/lib/forgezero/runtime/bun.next; ` + `mv -Tf /usr/local/lib/forgezero/runtime/bun.next /usr/local/lib/forgezero/runtime/bun; ` + `ln -sfn /usr/local/lib/forgezero/runtime/bun /usr/local/bin/bun`
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
requirement: { id: "nginx", version: "ubuntu-26.04" },
|
|
14
|
+
check: "command -v nginx >/dev/null && systemctl is-active --quiet nginx",
|
|
15
|
+
install: "DEBIAN_FRONTEND=noninteractive apt-get update -qq && apt-get install -y nginx && systemctl enable --now nginx"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
requirement: { id: "arangodb", version: "3.11.14" },
|
|
19
|
+
check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14'`,
|
|
20
|
+
install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install`
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
requirement: { id: "cloudflared", version: "2026.7.3" },
|
|
24
|
+
check: `cloudflared --version 2>/dev/null | grep -q '2026.7.3'`,
|
|
25
|
+
install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://github.com/cloudflare/cloudflared/releases/download/2026.7.3/cloudflared-linux-amd64' -o "$tmp/cloudflared"; ` + `echo "${CLOUDFLARED_SHA256} $tmp/cloudflared" | sha256sum -c -; ` + `install -m 0755 "$tmp/cloudflared" /usr/local/bin/cloudflared`
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
requirement: { id: "ufw", version: "ubuntu-26.04" },
|
|
29
|
+
check: "command -v ufw >/dev/null",
|
|
30
|
+
install: "DEBIAN_FRONTEND=noninteractive apt-get update -qq && apt-get install -y ufw"
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
function observeSoftwareHost(osRelease = readFileSync("/etc/os-release", "utf8"), architecture = process.arch) {
|
|
34
|
+
const values = Object.fromEntries(osRelease.split(`
|
|
35
|
+
`).flatMap((line) => {
|
|
36
|
+
const separator = line.indexOf("=");
|
|
37
|
+
return separator > 0 ? [[line.slice(0, separator), line.slice(separator + 1).replace(/^['"]|['"]$/g, "")]] : [];
|
|
38
|
+
}));
|
|
39
|
+
return {
|
|
40
|
+
os: { id: (values.ID ?? "unknown").toLowerCase(), versionId: values.VERSION_ID ?? "unknown" },
|
|
41
|
+
architecture
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function validateSoftwareRequirements(value) {
|
|
45
|
+
if (!Array.isArray(value) || value.length > 32)
|
|
46
|
+
throw new Error("software requirements must be an array of at most 32 entries");
|
|
47
|
+
const seen = new Set;
|
|
48
|
+
return value.map((item) => {
|
|
49
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
50
|
+
throw new Error("software requirement must be an object");
|
|
51
|
+
const row = item;
|
|
52
|
+
if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
|
|
53
|
+
throw new Error("software requirement contains an unknown field");
|
|
54
|
+
}
|
|
55
|
+
if (!["bun", "nginx", "arangodb", "cloudflared", "ufw"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
|
|
56
|
+
throw new Error("software requirement coordinate is invalid");
|
|
57
|
+
}
|
|
58
|
+
const requirement = { id: row.id, version: row.version };
|
|
59
|
+
if (seen.has(requirement.id))
|
|
60
|
+
throw new Error(`duplicate software requirement: ${requirement.id}`);
|
|
61
|
+
seen.add(requirement.id);
|
|
62
|
+
return requirement;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function ensureSoftwareRequirements(requirementsInput, options) {
|
|
66
|
+
const requirements = validateSoftwareRequirements(requirementsInput);
|
|
67
|
+
const observation = options.observation ?? observeSoftwareHost();
|
|
68
|
+
if (observation.os.id !== "ubuntu" || observation.os.versionId !== "26.04" || observation.architecture !== "x64") {
|
|
69
|
+
throw new Error(`unsupported software strategy: ${observation.os.id} ${observation.os.versionId} ${observation.architecture}`);
|
|
70
|
+
}
|
|
71
|
+
const results = [];
|
|
72
|
+
for (const requirement of requirements) {
|
|
73
|
+
const strategy = UBUNTU_2604_X64.find(({ requirement: candidate }) => candidate.id === requirement.id && candidate.version === requirement.version);
|
|
74
|
+
if (!strategy)
|
|
75
|
+
throw new Error(`unsupported software requirement: ${requirement.id}@${requirement.version}`);
|
|
76
|
+
const before = await options.exec(strategy.check);
|
|
77
|
+
if (before.exitCode === 0) {
|
|
78
|
+
results.push({ ...requirement, changed: false });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const installed = await options.exec(strategy.install);
|
|
82
|
+
if (installed.exitCode !== 0)
|
|
83
|
+
throw new Error(`could not install ${requirement.id}@${requirement.version}: ${installed.output.trim()}`);
|
|
84
|
+
const after = await options.exec(strategy.check);
|
|
85
|
+
if (after.exitCode !== 0)
|
|
86
|
+
throw new Error(`${requirement.id}@${requirement.version} did not pass its post-install check`);
|
|
87
|
+
results.push({ ...requirement, changed: true });
|
|
88
|
+
}
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
|
|
1
92
|
// src/definition.ts
|
|
2
93
|
var PIPELINE_VERSION = 1;
|
|
3
94
|
|
|
@@ -56,15 +147,7 @@ function parseDeployDefinition(value) {
|
|
|
56
147
|
}
|
|
57
148
|
return {
|
|
58
149
|
name: text(role.name, `roles[${index}].name`),
|
|
59
|
-
software: role.software
|
|
60
|
-
const software = record(rawSoftware, `roles[${index}].software[${softwareIndex}]`);
|
|
61
|
-
exactKeys(software, ["name", "check", "install"], `roles[${index}].software[${softwareIndex}]`);
|
|
62
|
-
return {
|
|
63
|
-
name: text(software.name, `roles[${index}].software[${softwareIndex}].name`),
|
|
64
|
-
check: text(software.check, `roles[${index}].software[${softwareIndex}].check`),
|
|
65
|
-
install: text(software.install, `roles[${index}].software[${softwareIndex}].install`)
|
|
66
|
-
};
|
|
67
|
-
})
|
|
150
|
+
software: validateSoftwareRequirements(role.software)
|
|
68
151
|
};
|
|
69
152
|
});
|
|
70
153
|
if (new Set(roles.map((role) => role.name)).size !== roles.length) {
|
|
@@ -132,21 +215,7 @@ function phasePipeline(definition, phase) {
|
|
|
132
215
|
steps: definition.steps.filter((step) => step.phase === phase)
|
|
133
216
|
};
|
|
134
217
|
}
|
|
135
|
-
function prerequisitePipeline(definition, roleName) {
|
|
136
|
-
const role = definition.roles.find((candidate) => candidate.name === roleName);
|
|
137
|
-
if (!role)
|
|
138
|
-
throw new DefinitionError(`pipeline role does not exist: ${roleName}.`);
|
|
139
|
-
return {
|
|
140
|
-
name: `${definition.name}:prerequisites:${role.name}`,
|
|
141
|
-
requireAttestation: definition.requireAttestation,
|
|
142
|
-
steps: role.software.map((software) => ({
|
|
143
|
-
name: `prepare ${software.name}`,
|
|
144
|
-
run: `${software.check} >/dev/null 2>&1 || { ${software.install}; ${software.check}; }`
|
|
145
|
-
}))
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
218
|
export {
|
|
149
|
-
prerequisitePipeline,
|
|
150
219
|
phasePipeline,
|
|
151
220
|
parseDeployDefinition,
|
|
152
221
|
PIPELINE_VERSION,
|
package/dist/deployment.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DrainReport, type QueueTask } from '@forgezero/runtime/queue';
|
|
2
|
+
import type { SoftwareRequirement } from './software';
|
|
2
3
|
import { type RunResult } from './pipeline';
|
|
3
4
|
import type { SecretCache } from './cache';
|
|
4
5
|
import type { AttestationSource } from './socket';
|
|
@@ -60,6 +61,8 @@ export interface DeploymentOptions {
|
|
|
60
61
|
width?: number;
|
|
61
62
|
/** Project commands run through a credential-free Unix identity in production. */
|
|
62
63
|
projectExec?: (input: CommandInput) => Promise<CommandResult>;
|
|
64
|
+
/** Root-owned fixed strategy helper; repository data contains coordinates only. */
|
|
65
|
+
ensureSoftware?: (requirements: readonly SoftwareRequirement[]) => Promise<unknown>;
|
|
63
66
|
exec?: (input: CommandInput) => Promise<CommandResult>;
|
|
64
67
|
now?: () => number;
|
|
65
68
|
readDefinition?: (path: string) => unknown;
|