@forgezero/agent 0.1.53 → 0.1.55
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 +118 -9
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +110 -69
- package/dist/community-rehearsal-host.js +37 -5
- package/dist/container-supervisor.d.ts +36 -0
- package/dist/definition.js +37 -5
- package/dist/deploy-actions.d.ts +87 -0
- package/dist/deploy-compiler.d.ts +26 -0
- package/dist/deploy-compiler.js +1050 -0
- package/dist/deploy-file.js +44 -12
- package/dist/deploy-plan-runner.d.ts +65 -0
- package/dist/deploy-plan-runner.js +1205 -0
- package/dist/deploy-plan.d.ts +41 -0
- package/dist/deploy-plan.js +925 -0
- package/dist/deploy-providers.d.ts +85 -0
- package/dist/deploy.d.ts +439 -0
- package/dist/deploy.js +169 -0
- package/dist/deployment.d.ts +5 -0
- package/dist/fz-agent.js +2113 -326
- package/dist/fz.js +1444 -313
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +167 -126
- package/dist/platform-bootstrap-runtime.js +1 -1
- package/dist/platform-fleet-verification.js +499 -120
- package/dist/provision.js +399 -20
- package/dist/software-helper.d.ts +5 -0
- package/dist/software-helper.js +399 -18
- package/dist/software.d.ts +1 -1
- package/dist/software.js +37 -5
- package/dist/version.d.ts +1 -1
- package/package.json +17 -1
package/dist/deploy-file.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
accessSync,
|
|
5
5
|
chmodSync,
|
|
6
6
|
copyFileSync,
|
|
7
|
+
existsSync,
|
|
7
8
|
mkdtempSync,
|
|
8
9
|
mkdirSync,
|
|
9
10
|
readFileSync,
|
|
@@ -24,6 +25,7 @@ var OS_CATALOG = [
|
|
|
24
25
|
];
|
|
25
26
|
var SOFTWARE_CATALOG = [
|
|
26
27
|
{ id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
28
|
+
{ id: "docker", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
27
29
|
{ id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
28
30
|
{ id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
29
31
|
{ id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
@@ -33,6 +35,7 @@ var SOFTWARE_CATALOG = [
|
|
|
33
35
|
];
|
|
34
36
|
var UBUNTU_2604_X64 = [
|
|
35
37
|
{ requirement: { id: "bun", version: "1.3.14" } },
|
|
38
|
+
{ requirement: { id: "docker", version: "ubuntu-26.04" } },
|
|
36
39
|
{ requirement: { id: "nginx", version: "ubuntu-26.04" } },
|
|
37
40
|
{ requirement: { id: "arangodb", version: "3.11.14" } },
|
|
38
41
|
{ requirement: { id: "cloudflared", version: "2026.7.3" } },
|
|
@@ -41,6 +44,15 @@ var UBUNTU_2604_X64 = [
|
|
|
41
44
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
42
45
|
];
|
|
43
46
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
47
|
+
var DOCKER_DAEMON_PATH = "/etc/docker/daemon.json";
|
|
48
|
+
var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
|
|
49
|
+
"data-root": "/var/lib/docker",
|
|
50
|
+
"storage-driver": "overlay2",
|
|
51
|
+
"live-restore": true,
|
|
52
|
+
"log-driver": "local",
|
|
53
|
+
"log-opts": { "max-size": "10m", "max-file": "3" }
|
|
54
|
+
}, null, 2)}
|
|
55
|
+
`;
|
|
44
56
|
var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
|
|
45
57
|
var runSoftwareCommand = async (argv, env = {}) => {
|
|
46
58
|
let child;
|
|
@@ -83,6 +95,15 @@ async function executeSoftwareOperation(operation) {
|
|
|
83
95
|
if (operation.kind === "check") {
|
|
84
96
|
if (software === "bun")
|
|
85
97
|
return run(["/usr/local/bin/bun", "--version"]).then((r) => ({ ...r, exitCode: successful(r, /^1\.3\.14\s*$/m) ? 0 : 1 }));
|
|
98
|
+
if (software === "docker") {
|
|
99
|
+
const binary = await run(["/usr/bin/docker", "--version"]);
|
|
100
|
+
if (!successful(binary, /Docker version/))
|
|
101
|
+
return { ...binary, exitCode: 1 };
|
|
102
|
+
if (!existsSync(DOCKER_DAEMON_PATH) || readFileSync(DOCKER_DAEMON_PATH, "utf8") !== DOCKER_DAEMON_CONFIG)
|
|
103
|
+
return { exitCode: 1, output: "Docker daemon policy is absent or differs from the reviewed ForgeZero policy" };
|
|
104
|
+
const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "docker.service"]);
|
|
105
|
+
return active.exitCode === 0 ? { exitCode: 0, output: binary.output } : active;
|
|
106
|
+
}
|
|
86
107
|
if (software === "nginx") {
|
|
87
108
|
const binary = await run(["/usr/sbin/nginx", "-v"]);
|
|
88
109
|
return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
|
|
@@ -115,11 +136,22 @@ async function executeSoftwareOperation(operation) {
|
|
|
115
136
|
return { exitCode: 1, output: cause instanceof Error ? cause.message : String(cause) };
|
|
116
137
|
}
|
|
117
138
|
}
|
|
118
|
-
if (software === "nginx" || software === "ufw" || software === "openssh-client") {
|
|
119
|
-
const
|
|
120
|
-
|
|
139
|
+
if (software === "docker" || software === "nginx" || software === "ufw" || software === "openssh-client") {
|
|
140
|
+
const packageName = software === "openssh-client" ? "openssh-client" : software === "docker" ? "docker.io" : software;
|
|
141
|
+
const installed = await aptInstall(packageName);
|
|
142
|
+
if (installed.exitCode !== 0 || software !== "nginx" && software !== "docker")
|
|
121
143
|
return installed;
|
|
122
|
-
|
|
144
|
+
if (software === "docker") {
|
|
145
|
+
mkdirSync("/etc/docker", { recursive: true, mode: 493 });
|
|
146
|
+
if (existsSync(DOCKER_DAEMON_PATH) && readFileSync(DOCKER_DAEMON_PATH, "utf8") !== DOCKER_DAEMON_CONFIG) {
|
|
147
|
+
return { exitCode: 2, output: "refusing to overwrite an existing Docker daemon configuration that differs from the reviewed ForgeZero policy" };
|
|
148
|
+
}
|
|
149
|
+
if (!existsSync(DOCKER_DAEMON_PATH))
|
|
150
|
+
writeFileSync(DOCKER_DAEMON_PATH, DOCKER_DAEMON_CONFIG, { mode: 420, flag: "wx" });
|
|
151
|
+
const enabled = await run(["/usr/bin/systemctl", "enable", "docker.service"]);
|
|
152
|
+
return enabled.exitCode === 0 ? run(["/usr/bin/systemctl", "restart", "docker.service"]) : enabled;
|
|
153
|
+
}
|
|
154
|
+
return run(["/usr/bin/systemctl", "enable", "--now", `${software}.service`]);
|
|
123
155
|
}
|
|
124
156
|
const directory = mkdtempSync(join(tmpdir(), "forgezero-software-"));
|
|
125
157
|
try {
|
|
@@ -204,7 +236,7 @@ function validateSoftwareRequirements(value, _options = {}) {
|
|
|
204
236
|
if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
|
|
205
237
|
throw new Error("software requirement contains an unknown field");
|
|
206
238
|
}
|
|
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)) {
|
|
239
|
+
if (!["bun", "docker", "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
240
|
throw new Error("software requirement coordinate is invalid");
|
|
209
241
|
}
|
|
210
242
|
const requirement = { id: row.id, version: row.version };
|
|
@@ -678,7 +710,7 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
|
|
|
678
710
|
|
|
679
711
|
// src/deploy-file.ts
|
|
680
712
|
import { createHash as createHash2 } from "node:crypto";
|
|
681
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
713
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
682
714
|
import { basename, join as join2 } from "node:path";
|
|
683
715
|
var DEPLOY_FILE = ".fz/deploy.json";
|
|
684
716
|
var DEPLOY_TODO_PREFIX = "ForgeZero pipeline TODO:";
|
|
@@ -699,17 +731,17 @@ var safeName = (value) => {
|
|
|
699
731
|
};
|
|
700
732
|
function packageHints(root) {
|
|
701
733
|
const packagePath = join2(root, "package.json");
|
|
702
|
-
if (!
|
|
703
|
-
return { bun:
|
|
734
|
+
if (!existsSync2(packagePath))
|
|
735
|
+
return { bun: existsSync2(join2(root, "bun.lock")) };
|
|
704
736
|
try {
|
|
705
737
|
const manifest = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
706
738
|
return {
|
|
707
739
|
name: typeof manifest.name === "string" ? manifest.name : undefined,
|
|
708
740
|
build: typeof manifest.scripts?.build === "string" ? ["bun", "run", "build"] : undefined,
|
|
709
|
-
bun:
|
|
741
|
+
bun: existsSync2(join2(root, "bun.lock")) || existsSync2(join2(root, "bun.lockb"))
|
|
710
742
|
};
|
|
711
743
|
} catch {
|
|
712
|
-
return { bun:
|
|
744
|
+
return { bun: existsSync2(join2(root, "bun.lock")) };
|
|
713
745
|
}
|
|
714
746
|
}
|
|
715
747
|
var blocker = (instruction) => ["fz-agent", "pipeline-todo", `${DEPLOY_TODO_PREFIX} ${instruction}`];
|
|
@@ -746,7 +778,7 @@ function defaultDeployFile(root, options = {}) {
|
|
|
746
778
|
}
|
|
747
779
|
function inspectDeployFile(root, options = {}) {
|
|
748
780
|
const path2 = join2(root, DEPLOY_FILE);
|
|
749
|
-
if (!
|
|
781
|
+
if (!existsSync2(path2))
|
|
750
782
|
throw new Error(`${DEPLOY_FILE} does not exist; run \`fz deploy init\`.`);
|
|
751
783
|
const raw = JSON.parse(readFileSync2(path2, "utf8"));
|
|
752
784
|
const definition = parseDeployDefinition(raw, options);
|
|
@@ -772,7 +804,7 @@ function inspectDeployFile(root, options = {}) {
|
|
|
772
804
|
}
|
|
773
805
|
function initializeDeployFile(root, options = {}) {
|
|
774
806
|
const path2 = join2(root, DEPLOY_FILE);
|
|
775
|
-
if (
|
|
807
|
+
if (existsSync2(path2) && !options.force) {
|
|
776
808
|
throw new Error(`${DEPLOY_FILE} already exists; use --force only when replacing it deliberately.`);
|
|
777
809
|
}
|
|
778
810
|
const raw = defaultDeployFile(root, options);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Condition, JsonObject, JsonValue } from './deploy';
|
|
2
|
+
import { type DeploymentPlan, type PlannedStage, type PlannedStep } from './deploy-plan';
|
|
3
|
+
export type PlanStepStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'skipped' | 'compensated' | 'manual-intervention';
|
|
4
|
+
export type ActionFailureKind = 'network' | 'timeout' | 'provider-unavailable' | 'rate-limited' | 'conflict' | 'refused' | 'failed';
|
|
5
|
+
export declare class DeploymentActionError extends Error {
|
|
6
|
+
readonly kind: ActionFailureKind;
|
|
7
|
+
constructor(kind: ActionFailureKind, message: string);
|
|
8
|
+
}
|
|
9
|
+
export interface ActionExecutionResult {
|
|
10
|
+
outputs?: JsonObject;
|
|
11
|
+
changed?: boolean;
|
|
12
|
+
evidence?: JsonObject;
|
|
13
|
+
}
|
|
14
|
+
export interface ActionContext {
|
|
15
|
+
plan: DeploymentPlan;
|
|
16
|
+
workflow: string;
|
|
17
|
+
stage: PlannedStage;
|
|
18
|
+
step: PlannedStep;
|
|
19
|
+
inputs: Readonly<Record<string, JsonValue>>;
|
|
20
|
+
resolved: JsonObject;
|
|
21
|
+
attempt: number;
|
|
22
|
+
signal: AbortSignal;
|
|
23
|
+
}
|
|
24
|
+
export type ActionHandler = (context: ActionContext) => Promise<ActionExecutionResult>;
|
|
25
|
+
export interface StepExecutionRecord {
|
|
26
|
+
id: string;
|
|
27
|
+
stage: string;
|
|
28
|
+
action: string;
|
|
29
|
+
status: PlanStepStatus;
|
|
30
|
+
attempts: number;
|
|
31
|
+
startedAtTs?: number;
|
|
32
|
+
finishedAtTs?: number;
|
|
33
|
+
outputs: JsonObject;
|
|
34
|
+
changed: boolean;
|
|
35
|
+
error?: {
|
|
36
|
+
kind: ActionFailureKind;
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
evidence?: JsonObject;
|
|
40
|
+
}
|
|
41
|
+
export interface PlanRunResult {
|
|
42
|
+
workflow: string;
|
|
43
|
+
ok: boolean;
|
|
44
|
+
status: 'succeeded' | 'failed' | 'manual-intervention';
|
|
45
|
+
steps: readonly StepExecutionRecord[];
|
|
46
|
+
}
|
|
47
|
+
interface EvaluationState {
|
|
48
|
+
inputs: Readonly<Record<string, JsonValue>>;
|
|
49
|
+
steps: Map<string, StepExecutionRecord>;
|
|
50
|
+
plan: DeploymentPlan;
|
|
51
|
+
}
|
|
52
|
+
export declare function evaluateCondition(value: Condition, state: EvaluationState): boolean;
|
|
53
|
+
export interface RunDeploymentPlanOptions {
|
|
54
|
+
plan: DeploymentPlan | unknown;
|
|
55
|
+
workflow: string;
|
|
56
|
+
inputs?: Readonly<Record<string, JsonValue>>;
|
|
57
|
+
actions: Readonly<Record<string, ActionHandler>>;
|
|
58
|
+
now?: () => number;
|
|
59
|
+
sleep?: (ms: number) => Promise<void>;
|
|
60
|
+
lock?: <T>(group: string, limit: number, run: () => Promise<T>) => Promise<T>;
|
|
61
|
+
/** Control-plane placement filter; false records a deterministic skip. */
|
|
62
|
+
shouldRun?: (step: PlannedStep) => boolean;
|
|
63
|
+
}
|
|
64
|
+
export declare function runDeploymentPlan(options: RunDeploymentPlanOptions): Promise<PlanRunResult>;
|
|
65
|
+
export {};
|