@forgezero/agent 0.1.62 → 0.1.64
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 +45 -16
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap-bundle.d.ts +31 -0
- package/dist/bootstrap-bundle.js +165 -0
- package/dist/bootstrap.d.ts +24 -18
- package/dist/bootstrap.js +509 -269
- package/dist/cli/agent-install.d.ts +2 -0
- package/dist/community-rehearsal-host.js +7 -5
- package/dist/definition.js +7 -5
- package/dist/deploy-file.js +7 -5
- package/dist/deployment.d.ts +27 -0
- package/dist/fz-agent.js +594 -421
- package/dist/fz.js +847 -372
- package/dist/metal-bootstrap.js +1 -1
- package/dist/metal-helper-socket.js +0 -2
- package/dist/metal-provision.js +0 -2
- package/dist/operator-bootstrap.d.ts +43 -1
- package/dist/operator-bootstrap.js +788 -367
- package/dist/platform-bootstrap-runtime.d.ts +0 -2
- package/dist/platform-bootstrap-runtime.js +0 -4
- package/dist/platform-fleet-verification.js +190 -98
- package/dist/provision.d.ts +4 -1
- package/dist/provision.js +41 -32
- package/dist/software-helper.js +7 -5
- package/dist/software.d.ts +1 -1
- package/dist/software.js +7 -5
- package/dist/version.d.ts +1 -1
- package/package.json +5 -1
|
@@ -60,8 +60,6 @@ export interface PlatformSharedEnvironment {
|
|
|
60
60
|
endpoint: string;
|
|
61
61
|
producer: string;
|
|
62
62
|
};
|
|
63
|
-
repository: string;
|
|
64
|
-
branch: string;
|
|
65
63
|
deployProfile: string;
|
|
66
64
|
}
|
|
67
65
|
export declare function validatePlatformSharedEnvironment(input: PlatformSharedEnvironment): PlatformSharedEnvironment;
|
|
@@ -100,8 +100,6 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
100
100
|
databaseUser: input.databaseUser,
|
|
101
101
|
sharedDirectory: input.sharedDirectory,
|
|
102
102
|
seedSyncEpoch: input.seedSyncEpoch,
|
|
103
|
-
repository: input.repository,
|
|
104
|
-
branch: input.branch,
|
|
105
103
|
deployProfile: input.deployProfile
|
|
106
104
|
}))
|
|
107
105
|
safeAtom(name, value);
|
|
@@ -184,8 +182,6 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
184
182
|
FZ_AGENT_OTLP_ENDPOINT: value.agentOtlpEndpoint,
|
|
185
183
|
FZ_CUSTODIAN_EMAIL: value.custodianEmail ?? "",
|
|
186
184
|
FZ_PROFILE: value.deployProfile,
|
|
187
|
-
FZ_REPO: value.repository,
|
|
188
|
-
FZ_BRANCH: value.branch,
|
|
189
185
|
FZ_EMAIL_PROVIDER: value.email?.provider ?? "",
|
|
190
186
|
FZ_SMTP_HOST: value.email?.provider === "smtp" ? value.email.host : "",
|
|
191
187
|
FZ_SMTP_PORT: value.email?.provider === "smtp" ? String(value.email.port) : "",
|
|
@@ -709,7 +709,8 @@ var SOFTWARE_CATALOG = [
|
|
|
709
709
|
{ id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
710
710
|
{ id: "cloudflare-warp", version: "2026.6.822.0-min", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
711
711
|
{ id: "ufw", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
712
|
-
{ id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
|
|
712
|
+
{ id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
713
|
+
{ id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
|
|
713
714
|
];
|
|
714
715
|
var UBUNTU_2604_X64 = [
|
|
715
716
|
{ requirement: { id: "bun", version: "1.3.14" } },
|
|
@@ -719,7 +720,8 @@ var UBUNTU_2604_X64 = [
|
|
|
719
720
|
{ requirement: { id: "cloudflared", version: "2026.7.3" } },
|
|
720
721
|
{ requirement: { id: "cloudflare-warp", version: "2026.6.822.0-min" } },
|
|
721
722
|
{ requirement: { id: "ufw", version: "ubuntu-26.04" } },
|
|
722
|
-
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
723
|
+
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } },
|
|
724
|
+
{ requirement: { id: "git", version: "ubuntu-26.04" } }
|
|
723
725
|
];
|
|
724
726
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
725
727
|
var DOCKER_DAEMON_PATH = "/etc/docker/daemon.json";
|
|
@@ -806,7 +808,7 @@ async function executeSoftwareOperation(operation) {
|
|
|
806
808
|
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]);
|
|
807
809
|
return { ...result, exitCode: result.exitCode === 0 && supported ? 0 : 1 };
|
|
808
810
|
});
|
|
809
|
-
const binaries = software === "ufw" ? ["/usr/sbin/ufw"] : ["/usr/bin/ssh", "/usr/bin/scp", "/usr/bin/ssh-keyscan", "/usr/bin/ssh-keygen"];
|
|
811
|
+
const binaries = software === "ufw" ? ["/usr/sbin/ufw"] : software === "git" ? ["/usr/bin/git"] : ["/usr/bin/ssh", "/usr/bin/scp", "/usr/bin/ssh-keyscan", "/usr/bin/ssh-keygen"];
|
|
810
812
|
try {
|
|
811
813
|
binaries.forEach((binary) => accessSync(binary));
|
|
812
814
|
return { exitCode: 0, output: "" };
|
|
@@ -814,7 +816,7 @@ async function executeSoftwareOperation(operation) {
|
|
|
814
816
|
return { exitCode: 1, output: cause instanceof Error ? cause.message : String(cause) };
|
|
815
817
|
}
|
|
816
818
|
}
|
|
817
|
-
if (software === "docker" || software === "nginx" || software === "ufw" || software === "openssh-client") {
|
|
819
|
+
if (software === "docker" || software === "nginx" || software === "ufw" || software === "openssh-client" || software === "git") {
|
|
818
820
|
const packageName = software === "openssh-client" ? "openssh-client" : software === "docker" ? "docker.io" : software;
|
|
819
821
|
const installed = await aptInstall(packageName);
|
|
820
822
|
if (installed.exitCode !== 0 || software !== "nginx" && software !== "docker")
|
|
@@ -914,7 +916,7 @@ function validateSoftwareRequirements(value, _options = {}) {
|
|
|
914
916
|
if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
|
|
915
917
|
throw new Error("software requirement contains an unknown field");
|
|
916
918
|
}
|
|
917
|
-
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)) {
|
|
919
|
+
if (!["bun", "docker", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client", "git"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
|
|
918
920
|
throw new Error("software requirement coordinate is invalid");
|
|
919
921
|
}
|
|
920
922
|
const requirement = { id: row.id, version: row.version };
|
|
@@ -2086,7 +2088,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2086
2088
|
}
|
|
2087
2089
|
|
|
2088
2090
|
// src/version.ts
|
|
2089
|
-
var VERSION3 = "0.1.
|
|
2091
|
+
var VERSION3 = "0.1.64";
|
|
2090
2092
|
|
|
2091
2093
|
// src/egress-policy.ts
|
|
2092
2094
|
import { realpathSync as realpathSync3 } from "node:fs";
|
|
@@ -2215,7 +2217,11 @@ function agentEgressUnit(options) {
|
|
|
2215
2217
|
const user = options.user ?? "forgezero";
|
|
2216
2218
|
if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(user))
|
|
2217
2219
|
throw new Error("invalid Agent service user");
|
|
2218
|
-
const
|
|
2220
|
+
const bootstrapBundleEnabled = Boolean(options.bootstrapBundlePath && options.bootstrapBundleManifestPath);
|
|
2221
|
+
if (Boolean(options.bootstrapBundlePath) !== Boolean(options.bootstrapBundleManifestPath) || bootstrapBundleEnabled && ![options.bootstrapBundlePath, options.bootstrapBundleManifestPath].every((path2) => path2.startsWith("/") && !/[\r\n\0:]/.test(path2))) {
|
|
2222
|
+
throw new Error("bootstrap bundle and manifest must be supplied together as absolute paths");
|
|
2223
|
+
}
|
|
2224
|
+
const deploymentEnabled = Boolean(options.repository || bootstrapBundleEnabled || options.pullDeployments);
|
|
2219
2225
|
const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
|
|
2220
2226
|
const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
|
|
2221
2227
|
if (deploymentEnabled && runnerPublicTcpPorts.length < 1) {
|
|
@@ -2621,17 +2627,16 @@ function agentUnit(options) {
|
|
|
2621
2627
|
if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
|
|
2622
2628
|
throw new Error("migration pull and lifecycle profile must be supplied together");
|
|
2623
2629
|
}
|
|
2624
|
-
const
|
|
2625
|
-
if (
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
|
|
2630
|
+
const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
|
|
2631
|
+
if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath)) {
|
|
2632
|
+
throw new Error("bootstrap SSH credential and public key paths must be supplied together");
|
|
2633
|
+
}
|
|
2634
|
+
const bootstrapEnabled = Boolean(options.pullBootstrap);
|
|
2635
|
+
if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
|
|
2636
|
+
throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
|
|
2632
2637
|
}
|
|
2633
2638
|
const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
|
|
2634
|
-
const bootstrapSshCredentialPath =
|
|
2639
|
+
const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
|
|
2635
2640
|
const warpValues = [
|
|
2636
2641
|
options.warpOrganization,
|
|
2637
2642
|
options.warpClientIdCredentialPath,
|
|
@@ -2685,6 +2690,8 @@ function agentUnit(options) {
|
|
|
2685
2690
|
options.gitPublicKeyPath ? `FZ_GIT_PUBLIC_KEY_FILE=${options.gitPublicKeyPath}` : null,
|
|
2686
2691
|
options.repository ? `FZ_DEPLOY_REPO=${options.repository}` : null,
|
|
2687
2692
|
options.branch ? `FZ_DEPLOY_BRANCH=${options.branch}` : null,
|
|
2693
|
+
options.bootstrapBundlePath ? `FZ_BOOTSTRAP_BUNDLE=${options.bootstrapBundlePath}` : null,
|
|
2694
|
+
options.bootstrapBundleManifestPath ? `FZ_BOOTSTRAP_BUNDLE_MANIFEST=${options.bootstrapBundleManifestPath}` : null,
|
|
2688
2695
|
options.profile ? `FZ_DEPLOY_PROFILE=${options.profile}` : null,
|
|
2689
2696
|
options.repository && options.branch ? `FZ_DEPLOY_KEY=${options.project ?? "platform"}:${options.environment ?? "production"}` : null,
|
|
2690
2697
|
deploymentEnabled ? `FZ_DEPLOY_ROOT=${deployRoot}` : null,
|
|
@@ -2868,15 +2875,13 @@ function planProvision(options) {
|
|
|
2868
2875
|
if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
|
|
2869
2876
|
throw new Error("migration pull and lifecycle profile must be supplied together");
|
|
2870
2877
|
}
|
|
2871
|
-
const
|
|
2872
|
-
if (
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
].some(Boolean) && !bootstrapEnabled) {
|
|
2879
|
-
throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
|
|
2878
|
+
const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
|
|
2879
|
+
if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath) || options.bootstrapSshSourcePath && !bootstrapIdentityEnabled) {
|
|
2880
|
+
throw new Error("bootstrap SSH credential and public key paths must be supplied together");
|
|
2881
|
+
}
|
|
2882
|
+
const bootstrapEnabled = Boolean(options.pullBootstrap);
|
|
2883
|
+
if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
|
|
2884
|
+
throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
|
|
2880
2885
|
}
|
|
2881
2886
|
const warpValues = [
|
|
2882
2887
|
options.warpOrganization,
|
|
@@ -2886,13 +2891,13 @@ function planProvision(options) {
|
|
|
2886
2891
|
const warpEnabled = warpValues.every(Boolean);
|
|
2887
2892
|
if (warpValues.some(Boolean) && !warpEnabled)
|
|
2888
2893
|
throw new Error("WARP configuration must be supplied together");
|
|
2889
|
-
const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath
|
|
2890
|
-
if (
|
|
2891
|
-
throw new Error("direct enrolment credential
|
|
2894
|
+
const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath);
|
|
2895
|
+
if (options.enrolTokenCredentialPath && !options.enrolStatePath || options.enrolTokenSourcePath && (!options.enrolTokenCredentialPath || !options.enrolStatePath)) {
|
|
2896
|
+
throw new Error("direct enrolment credential requires its durable state path");
|
|
2892
2897
|
}
|
|
2893
2898
|
const enrolTokenSourcePath = options.enrolTokenSourcePath ? systemdPath(options.enrolTokenSourcePath, "enrolment source") : undefined;
|
|
2894
2899
|
const enrolTokenCredentialPath = enrolmentEnabled ? systemdPath(options.enrolTokenCredentialPath, "enrolment credential") : undefined;
|
|
2895
|
-
const enrolStatePath =
|
|
2900
|
+
const enrolStatePath = options.enrolStatePath ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
|
|
2896
2901
|
const enrolStateDir = enrolStatePath?.replace(/\/[^/]+$/, "");
|
|
2897
2902
|
const sourceBinPath = options.sourceBinPath ? systemdPath(options.sourceBinPath, "agent source binary") : undefined;
|
|
2898
2903
|
const binPath = options.binPath ? systemdPath(options.binPath, "agent binary") : undefined;
|
|
@@ -2904,8 +2909,8 @@ function planProvision(options) {
|
|
|
2904
2909
|
const gitPublicKeyDir = gitPublicKeyPath?.replace(/\/[^/]+$/, "");
|
|
2905
2910
|
const lifecycleProfilePath = lifecycleEnabled ? systemdPath(options.lifecycleProfilePath, "lifecycle profile") : undefined;
|
|
2906
2911
|
const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
|
|
2907
|
-
const bootstrapSshCredentialPath =
|
|
2908
|
-
const bootstrapSshPublicKeyPath =
|
|
2912
|
+
const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
|
|
2913
|
+
const bootstrapSshPublicKeyPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
|
|
2909
2914
|
const bootstrapSshSourcePath = options.bootstrapSshSourcePath ? systemdPath(options.bootstrapSshSourcePath, "bootstrap SSH private-key source") : undefined;
|
|
2910
2915
|
const bootstrapSshPublicKeyDir = bootstrapSshPublicKeyPath?.replace(/\/[^/]+$/, "");
|
|
2911
2916
|
const warpClientIdCredentialPath = warpEnabled ? systemdPath(options.warpClientIdCredentialPath, "WARP client-id credential") : undefined;
|
|
@@ -3005,7 +3010,7 @@ function planProvision(options) {
|
|
|
3005
3010
|
step("Git deploy identity directory", { kind: "directories", directories: [{ path: gitPublicKeyDir, mode: 493, owner: "root", group: "root" }] }),
|
|
3006
3011
|
step("unique encrypted Git deploy identity", { kind: "ensure-git-identity", credential: gitCredentialPath, publicKey: gitPublicKeyPath })
|
|
3007
3012
|
] : [],
|
|
3008
|
-
...
|
|
3013
|
+
...bootstrapIdentityEnabled ? [
|
|
3009
3014
|
step("bootstrap SSH public identity directory", { kind: "directories", directories: [
|
|
3010
3015
|
{ path: bootstrapSshPublicKeyDir, mode: 493, owner: "root", group: "root" }
|
|
3011
3016
|
] }),
|
|
@@ -3041,6 +3046,10 @@ function planProvision(options) {
|
|
|
3041
3046
|
{ argv: ["/usr/bin/rm", "-f", "/etc/systemd/system/forgezero-deploy-runner.socket"] },
|
|
3042
3047
|
{ argv: ["/usr/bin/systemctl", "daemon-reload"] }
|
|
3043
3048
|
] })] : [],
|
|
3049
|
+
...enrolStatePath && !enrolmentEnabled ? [step("retire consumed enrolment unit", { kind: "commands", commands: [
|
|
3050
|
+
{ argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-agent-enrol.service"], acceptedExitCodes: [0, 1, 5] },
|
|
3051
|
+
{ argv: ["/usr/bin/rm", "-f", ENROLMENT_UNIT_PATH] }
|
|
3052
|
+
] })] : [],
|
|
3044
3053
|
step("enable and converge services", { kind: "commands", commands: [
|
|
3045
3054
|
{ argv: ["/usr/bin/systemctl", "enable", ...enabledUnits] },
|
|
3046
3055
|
{ argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
|
|
@@ -3168,8 +3177,6 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
3168
3177
|
databaseUser: input.databaseUser,
|
|
3169
3178
|
sharedDirectory: input.sharedDirectory,
|
|
3170
3179
|
seedSyncEpoch: input.seedSyncEpoch,
|
|
3171
|
-
repository: input.repository,
|
|
3172
|
-
branch: input.branch,
|
|
3173
3180
|
deployProfile: input.deployProfile
|
|
3174
3181
|
}))
|
|
3175
3182
|
safeAtom(name, value);
|
|
@@ -3252,8 +3259,6 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
3252
3259
|
FZ_AGENT_OTLP_ENDPOINT: value.agentOtlpEndpoint,
|
|
3253
3260
|
FZ_CUSTODIAN_EMAIL: value.custodianEmail ?? "",
|
|
3254
3261
|
FZ_PROFILE: value.deployProfile,
|
|
3255
|
-
FZ_REPO: value.repository,
|
|
3256
|
-
FZ_BRANCH: value.branch,
|
|
3257
3262
|
FZ_EMAIL_PROVIDER: value.email?.provider ?? "",
|
|
3258
3263
|
FZ_SMTP_HOST: value.email?.provider === "smtp" ? value.email.host : "",
|
|
3259
3264
|
FZ_SMTP_PORT: value.email?.provider === "smtp" ? String(value.email.port) : "",
|
|
@@ -3592,21 +3597,21 @@ function planLocalOtlpProof(endpoint, collectorUnit) {
|
|
|
3592
3597
|
}
|
|
3593
3598
|
|
|
3594
3599
|
// src/platform-fleet-verification.ts
|
|
3595
|
-
import { lstatSync as
|
|
3600
|
+
import { lstatSync as lstatSync5 } from "node:fs";
|
|
3596
3601
|
|
|
3597
3602
|
// src/bootstrap.ts
|
|
3598
|
-
import { createHash as
|
|
3603
|
+
import { createHash as createHash6, createHmac as createHmac2, randomBytes as randomBytes3 } from "node:crypto";
|
|
3599
3604
|
import {
|
|
3600
|
-
chmodSync as
|
|
3601
|
-
existsSync as
|
|
3602
|
-
lstatSync as
|
|
3603
|
-
mkdirSync as
|
|
3604
|
-
readFileSync as
|
|
3605
|
-
renameSync as
|
|
3606
|
-
rmSync as
|
|
3607
|
-
writeFileSync as
|
|
3605
|
+
chmodSync as chmodSync8,
|
|
3606
|
+
existsSync as existsSync10,
|
|
3607
|
+
lstatSync as lstatSync4,
|
|
3608
|
+
mkdirSync as mkdirSync10,
|
|
3609
|
+
readFileSync as readFileSync9,
|
|
3610
|
+
renameSync as renameSync9,
|
|
3611
|
+
rmSync as rmSync9,
|
|
3612
|
+
writeFileSync as writeFileSync9
|
|
3608
3613
|
} from "node:fs";
|
|
3609
|
-
import { dirname as
|
|
3614
|
+
import { dirname as dirname9 } from "node:path";
|
|
3610
3615
|
import { fileURLToPath } from "node:url";
|
|
3611
3616
|
|
|
3612
3617
|
// src/cli/agent-install.ts
|
|
@@ -3918,6 +3923,58 @@ var UNIT = `/etc/systemd/system/${FORGEZERO_OTEL_COLLECTOR_UNIT}`;
|
|
|
3918
3923
|
|
|
3919
3924
|
// src/bootstrap.ts
|
|
3920
3925
|
import { DEFAULT_SOCKET as DEFAULT_SOCKET2 } from "@forgezero/vault";
|
|
3926
|
+
|
|
3927
|
+
// src/bootstrap-bundle.ts
|
|
3928
|
+
import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
|
|
3929
|
+
import {
|
|
3930
|
+
chmodSync as chmodSync7,
|
|
3931
|
+
createReadStream,
|
|
3932
|
+
existsSync as existsSync9,
|
|
3933
|
+
lstatSync as lstatSync3,
|
|
3934
|
+
mkdirSync as mkdirSync9,
|
|
3935
|
+
readFileSync as readFileSync8,
|
|
3936
|
+
renameSync as renameSync8,
|
|
3937
|
+
rmSync as rmSync8,
|
|
3938
|
+
writeFileSync as writeFileSync8
|
|
3939
|
+
} from "node:fs";
|
|
3940
|
+
import { dirname as dirname8, isAbsolute, resolve as resolve6 } from "node:path";
|
|
3941
|
+
var BOOTSTRAP_BUNDLE_FORMAT = 1;
|
|
3942
|
+
var BOOTSTRAP_BUNDLE_KIND = "forgezero-api-git-bundle";
|
|
3943
|
+
var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
|
|
3944
|
+
var branchName = (value) => {
|
|
3945
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9](?:[A-Za-z0-9._/-]{0,126}[A-Za-z0-9])?$/.test(value) || value.includes("..") || value.includes("//") || value.startsWith("-")) {
|
|
3946
|
+
throw new Error("bootstrap bundle branch is malformed");
|
|
3947
|
+
}
|
|
3948
|
+
return value;
|
|
3949
|
+
};
|
|
3950
|
+
function parseBootstrapBundleManifest(value) {
|
|
3951
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3952
|
+
throw new Error("bootstrap bundle manifest must be an object");
|
|
3953
|
+
const source = value;
|
|
3954
|
+
const allowed = ["format", "kind", "branch", "revision", "sha256", "bytes", "createdAt"];
|
|
3955
|
+
const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
|
|
3956
|
+
if (unknown.length)
|
|
3957
|
+
throw new Error(`bootstrap bundle manifest contains unknown field ${unknown[0]}`);
|
|
3958
|
+
if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
|
|
3959
|
+
throw new Error("bootstrap bundle manifest format is unsupported");
|
|
3960
|
+
}
|
|
3961
|
+
branchName(source.branch);
|
|
3962
|
+
if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
|
|
3963
|
+
throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
|
|
3964
|
+
}
|
|
3965
|
+
if (typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256)) {
|
|
3966
|
+
throw new Error("bootstrap bundle digest is malformed");
|
|
3967
|
+
}
|
|
3968
|
+
if (!Number.isSafeInteger(source.bytes) || Number(source.bytes) < 1 || Number(source.bytes) > MAX_BOOTSTRAP_BUNDLE_BYTES) {
|
|
3969
|
+
throw new Error("bootstrap bundle size is outside the supported boundary");
|
|
3970
|
+
}
|
|
3971
|
+
if (typeof source.createdAt !== "string" || Number.isNaN(Date.parse(source.createdAt))) {
|
|
3972
|
+
throw new Error("bootstrap bundle creation time is malformed");
|
|
3973
|
+
}
|
|
3974
|
+
return source;
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
// src/bootstrap.ts
|
|
3921
3978
|
var platformBootstrapRunner = (config) => config.kind === "platform" && config.database.role === "master";
|
|
3922
3979
|
var BOOTSTRAP_STATE_PATH = "/var/lib/forgezero/bootstrap.json";
|
|
3923
3980
|
var STATE_PATH = BOOTSTRAP_STATE_PATH;
|
|
@@ -3934,6 +3991,7 @@ var SEED_CREDENTIAL = `${CREDS}/seed-sync-root.cred`;
|
|
|
3934
3991
|
var BACKUP_RECOVERY_CREDENTIAL = `${CREDS}/backup-recovery-root.cred`;
|
|
3935
3992
|
var BOOTSTRAP_SSH_CREDENTIAL = `${CREDS}/bootstrap-ssh-key.cred`;
|
|
3936
3993
|
var BOOTSTRAP_SSH_PUBLIC_KEY = "/etc/forgezero/bootstrap/runner.pub";
|
|
3994
|
+
var BOOTSTRAP_RELEASE_EVIDENCE = "/var/lib/forgezero/bootstrap-release.json";
|
|
3937
3995
|
var LIFECYCLE_PROFILE = "/etc/forgezero/lifecycle.json";
|
|
3938
3996
|
var CONTROL_SOCKET = "/run/forgezero/control.sock";
|
|
3939
3997
|
var CLOUDFLARED_METRICS_ADDRESS = "127.0.0.1:20241";
|
|
@@ -4110,6 +4168,65 @@ async function bootstrapStatus(host = localBootstrapHost()) {
|
|
|
4110
4168
|
problems.push("durable Agent enrolment state is missing");
|
|
4111
4169
|
return { initialized: problems.length === 0, kind: state.kind, profile: state.profile, services, problems };
|
|
4112
4170
|
}
|
|
4171
|
+
function planBootstrapAgentInstall(config, phase, context) {
|
|
4172
|
+
const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
|
|
4173
|
+
if (phase === "bootstrap") {
|
|
4174
|
+
if (config.kind !== "platform" || hasBinding || !initialBundle) {
|
|
4175
|
+
throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
|
|
4176
|
+
}
|
|
4177
|
+
} else if (phase === "enrol") {
|
|
4178
|
+
if (hasBinding || !hasEnrolCredential) {
|
|
4179
|
+
throw new Error("Agent enrolment requires one unbound host and its sealed one-use credential");
|
|
4180
|
+
}
|
|
4181
|
+
} else if (!hasBinding) {
|
|
4182
|
+
throw new Error("bound Agent convergence requires durable enrolment state");
|
|
4183
|
+
}
|
|
4184
|
+
const bound = phase === "bound";
|
|
4185
|
+
const enrolling = phase === "enrol";
|
|
4186
|
+
const authenticated = enrolling || bound;
|
|
4187
|
+
const bootstrapRunner = platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner);
|
|
4188
|
+
const options = {
|
|
4189
|
+
capabilities,
|
|
4190
|
+
socketPath: DEFAULT_SOCKET2,
|
|
4191
|
+
seedPath: "/var/lib/forgezero/node.seed",
|
|
4192
|
+
controlSocketPath: CONTROL_SOCKET,
|
|
4193
|
+
repository: phase === "bootstrap" ? initialBundle.path : bound && config.kind === "enrolled-compute" ? config.repository : undefined,
|
|
4194
|
+
branch: phase === "bootstrap" ? initialBundle.manifest.branch : bound && config.kind === "enrolled-compute" ? config.branch : undefined,
|
|
4195
|
+
bootstrapBundlePath: phase === "bootstrap" ? initialBundle.path : undefined,
|
|
4196
|
+
bootstrapBundleManifestPath: phase === "bootstrap" ? initialBundle.manifestPath : undefined,
|
|
4197
|
+
profile: config.profile,
|
|
4198
|
+
deployRoot: config.deployRoot ?? "/opt/forgezero",
|
|
4199
|
+
deploymentCredentials: config.deploymentCredentials,
|
|
4200
|
+
publicApiUrl: config.apiUrl,
|
|
4201
|
+
gitCredentialPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
|
|
4202
|
+
gitPublicKeyPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
|
|
4203
|
+
generateGitIdentity: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey === true,
|
|
4204
|
+
pullDeployments: bound,
|
|
4205
|
+
pullMigrations: bound && config.kind === "platform",
|
|
4206
|
+
pullBootstrap: bound && bootstrapRunner,
|
|
4207
|
+
bootstrapSshCredentialPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
|
|
4208
|
+
bootstrapSshSourcePath: enrolling && config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
|
|
4209
|
+
bootstrapSshPublicKeyPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
|
|
4210
|
+
bootstrapTargetTelemetryEndpoint: bound && bootstrapRunner ? platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined : undefined,
|
|
4211
|
+
lifecycleProfilePath: bound && config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
|
|
4212
|
+
enforceEgress: true,
|
|
4213
|
+
nodeHostname: config.nodeHostname,
|
|
4214
|
+
telemetryEndpoint: config.telemetryEndpoint,
|
|
4215
|
+
binPath: "/usr/local/lib/forgezero/agent/fz-agent",
|
|
4216
|
+
sourceBinPath: PACKAGED_AGENT_BIN,
|
|
4217
|
+
...enrolling ? {
|
|
4218
|
+
enrolTokenCredentialPath: ENROL_CREDENTIAL,
|
|
4219
|
+
enrolStatePath: "/var/lib/forgezero/enrolment.json"
|
|
4220
|
+
} : bound ? { enrolStatePath: "/var/lib/forgezero/enrolment.json" } : {},
|
|
4221
|
+
...authenticated ? {
|
|
4222
|
+
apiUrl: config.apiUrl,
|
|
4223
|
+
project: config.kind === "enrolled-compute" ? config.realm : "platform",
|
|
4224
|
+
environment: config.kind === "enrolled-compute" ? undefined : config.environment,
|
|
4225
|
+
nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
|
|
4226
|
+
} : {}
|
|
4227
|
+
};
|
|
4228
|
+
return planInstall(options);
|
|
4229
|
+
}
|
|
4113
4230
|
function localBootstrapHost() {
|
|
4114
4231
|
const execute = async (argv2, options = {}) => {
|
|
4115
4232
|
const child = Bun.spawn([...argv2], { stdin: options.stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
|
|
@@ -4126,19 +4243,19 @@ function localBootstrapHost() {
|
|
|
4126
4243
|
};
|
|
4127
4244
|
return {
|
|
4128
4245
|
uid: () => process.getuid?.() ?? -1,
|
|
4129
|
-
exists:
|
|
4130
|
-
read: (path2) =>
|
|
4246
|
+
exists: existsSync10,
|
|
4247
|
+
read: (path2) => readFileSync9(path2, "utf8"),
|
|
4131
4248
|
write(path2, content, mode) {
|
|
4132
|
-
|
|
4249
|
+
mkdirSync10(dirname9(path2), { recursive: true, mode: 493 });
|
|
4133
4250
|
const temporary = `${path2}.next.${process.pid}`;
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4251
|
+
writeFileSync9(temporary, content, { mode });
|
|
4252
|
+
chmodSync8(temporary, mode);
|
|
4253
|
+
renameSync9(temporary, path2);
|
|
4137
4254
|
},
|
|
4138
|
-
mkdir: (path2, mode) =>
|
|
4139
|
-
remove: (path2) =>
|
|
4255
|
+
mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
|
|
4256
|
+
remove: (path2) => rmSync9(path2, { force: true }),
|
|
4140
4257
|
inspect(path2) {
|
|
4141
|
-
const value =
|
|
4258
|
+
const value = lstatSync4(path2);
|
|
4142
4259
|
return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
|
|
4143
4260
|
},
|
|
4144
4261
|
exec: execute,
|
|
@@ -4157,10 +4274,15 @@ function localBootstrapHost() {
|
|
|
4157
4274
|
throw new Error(`Agent software requirements failed: ${result.output.trim()}`);
|
|
4158
4275
|
return result;
|
|
4159
4276
|
},
|
|
4160
|
-
async installAgent(config) {
|
|
4277
|
+
async installAgent(config, phase) {
|
|
4161
4278
|
const capabilities = await readCapabilities(localRunner);
|
|
4162
|
-
const
|
|
4163
|
-
const
|
|
4279
|
+
const hasBinding = existsSync10("/var/lib/forgezero/enrolment.json");
|
|
4280
|
+
const hasEnrolCredential = existsSync10(ENROL_CREDENTIAL);
|
|
4281
|
+
const initialBundle = config.kind === "platform" && !existsSync10(BOOTSTRAP_RELEASE_EVIDENCE) ? {
|
|
4282
|
+
path: config.bootstrapBundle.bundleFile,
|
|
4283
|
+
manifestPath: config.bootstrapBundle.manifestFile,
|
|
4284
|
+
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync9(config.bootstrapBundle.manifestFile, "utf8")))
|
|
4285
|
+
} : undefined;
|
|
4164
4286
|
if (config.kind === "platform") {
|
|
4165
4287
|
const lifecycle = config.database.role === "none" ? {
|
|
4166
4288
|
apiUnits: ["forgezero@blue.service", "forgezero@green.service"],
|
|
@@ -4172,49 +4294,19 @@ function localBootstrapHost() {
|
|
|
4172
4294
|
databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
|
|
4173
4295
|
databasePorts: [8529]
|
|
4174
4296
|
};
|
|
4175
|
-
|
|
4176
|
-
|
|
4297
|
+
mkdirSync10(dirname9(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
|
|
4298
|
+
writeFileSync9(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
|
|
4177
4299
|
`, { mode: 256 });
|
|
4178
4300
|
}
|
|
4179
|
-
const plan =
|
|
4301
|
+
const plan = planBootstrapAgentInstall(config, phase, {
|
|
4180
4302
|
capabilities,
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
repository: config.repository,
|
|
4185
|
-
branch: config.branch,
|
|
4186
|
-
profile: config.kind === "platform" ? config.profile : config.profile,
|
|
4187
|
-
deployRoot,
|
|
4188
|
-
deploymentCredentials: config.deploymentCredentials,
|
|
4189
|
-
publicApiUrl: config.apiUrl,
|
|
4190
|
-
gitCredentialPath: "/etc/forgezero/creds/git-deploy-key.cred",
|
|
4191
|
-
gitPublicKeyPath: "/etc/forgezero/git/deploy.pub",
|
|
4192
|
-
generateGitIdentity: true,
|
|
4193
|
-
pullDeployments: hasBinding,
|
|
4194
|
-
pullMigrations: config.kind === "platform" && hasBinding,
|
|
4195
|
-
pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
|
|
4196
|
-
bootstrapSshCredentialPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
|
|
4197
|
-
bootstrapSshSourcePath: config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
|
|
4198
|
-
bootstrapSshPublicKeyPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
|
|
4199
|
-
bootstrapTargetTelemetryEndpoint: platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined,
|
|
4200
|
-
lifecycleProfilePath: config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
|
|
4201
|
-
enforceEgress: true,
|
|
4202
|
-
nodeHostname: config.nodeHostname,
|
|
4203
|
-
telemetryEndpoint: config.telemetryEndpoint,
|
|
4204
|
-
binPath: "/usr/local/lib/forgezero/agent/fz-agent",
|
|
4205
|
-
sourceBinPath: PACKAGED_AGENT_BIN,
|
|
4206
|
-
...hasBinding ? {
|
|
4207
|
-
enrolTokenCredentialPath: ENROL_CREDENTIAL,
|
|
4208
|
-
enrolStatePath: "/var/lib/forgezero/enrolment.json",
|
|
4209
|
-
apiUrl: config.apiUrl,
|
|
4210
|
-
project: config.kind === "enrolled-compute" ? config.realm : "platform",
|
|
4211
|
-
environment: config.kind === "enrolled-compute" ? undefined : config.environment,
|
|
4212
|
-
nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
|
|
4213
|
-
} : {}
|
|
4303
|
+
hasBinding,
|
|
4304
|
+
hasEnrolCredential,
|
|
4305
|
+
initialBundle
|
|
4214
4306
|
});
|
|
4215
4307
|
for (const unit2 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
|
|
4216
|
-
|
|
4217
|
-
|
|
4308
|
+
mkdirSync10(dirname9(unit2.path), { recursive: true, mode: 493 });
|
|
4309
|
+
writeFileSync9(unit2.path, unit2.unit, { mode: 420 });
|
|
4218
4310
|
}
|
|
4219
4311
|
await applyPlan(plan, localRunner);
|
|
4220
4312
|
return plan;
|
|
@@ -4228,7 +4320,7 @@ var localRuntime = () => ({
|
|
|
4228
4320
|
bootstrapStatus: () => bootstrapStatus(),
|
|
4229
4321
|
characterDevice(path2) {
|
|
4230
4322
|
try {
|
|
4231
|
-
return
|
|
4323
|
+
return lstatSync5(path2).isCharacterDevice();
|
|
4232
4324
|
} catch {
|
|
4233
4325
|
return false;
|
|
4234
4326
|
}
|
package/dist/provision.d.ts
CHANGED
|
@@ -92,6 +92,9 @@ export interface UnitOptions {
|
|
|
92
92
|
controlSocketPath?: string;
|
|
93
93
|
repository?: string;
|
|
94
94
|
branch?: string;
|
|
95
|
+
/** Attended release-generation-one artifact; both coordinates are required together. */
|
|
96
|
+
bootstrapBundlePath?: string;
|
|
97
|
+
bootstrapBundleManifestPath?: string;
|
|
95
98
|
profile?: string;
|
|
96
99
|
deployRoot?: string;
|
|
97
100
|
publicApiUrl?: string;
|
|
@@ -164,7 +167,7 @@ export declare const WARP_SERVICE_DROP_IN_PATH = "/etc/systemd/system/warp-svc.s
|
|
|
164
167
|
export declare const AGENT_EGRESS_UNIT_PATH = "/etc/systemd/system/forgezero-agent-egress.service";
|
|
165
168
|
export declare const DEFAULT_RUNNER_PUBLIC_TCP_PORTS: readonly [443];
|
|
166
169
|
/** Root-owned policy monitor. The credential-bearing Agent is bound to it. */
|
|
167
|
-
export declare function agentEgressUnit(options: Pick<UnitOptions, 'binPath' | 'user' | 'repository' | 'pullDeployments' | 'runnerLoopbackPorts' | 'runnerPublicTcpPorts'>): string;
|
|
170
|
+
export declare function agentEgressUnit(options: Pick<UnitOptions, 'binPath' | 'user' | 'repository' | 'pullDeployments' | 'bootstrapBundlePath' | 'bootstrapBundleManifestPath' | 'runnerLoopbackPorts' | 'runnerPublicTcpPorts'>): string;
|
|
168
171
|
/** Root may execute only package-owned OS strategies selected by id + version. */
|
|
169
172
|
export declare function softwareHelperUnit(options: Pick<UnitOptions, 'binPath' | 'deployRoot'>): string;
|
|
170
173
|
/** Fixed root boundary for verified, health-gated Agent replacement. */
|