@forgezero/agent 0.1.85 → 0.1.87
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 +13 -14
- package/dist/agent-handover.d.ts +13 -0
- package/dist/agent-heartbeat.d.ts +3 -3
- package/dist/agent-heartbeat.js +235 -62
- package/dist/agent-update-helper.d.ts +11 -2
- package/dist/agent-update-helper.js +201 -45
- package/dist/agent-update.d.ts +4 -0
- package/dist/agent-update.js +19 -1
- package/dist/bootstrap.js +56 -8
- package/dist/credential-schema.js +16 -4
- package/dist/fz-agent.js +569 -260
- package/dist/fz.js +415 -43
- package/dist/guest-enrolment.js +4 -2
- package/dist/metal-bootstrap.js +40 -1
- package/dist/migration-pull.js +4 -2
- package/dist/node-vault.js +16 -4
- package/dist/operator-bootstrap.js +95 -8
- package/dist/platform-bootstrap-runtime.d.ts +7 -0
- package/dist/platform-bootstrap-runtime.js +5 -2
- package/dist/platform-fleet-verification.js +411 -213
- package/dist/platform-genesis-config.d.ts +6 -1
- package/dist/platform-launch-profile.d.ts +44 -0
- package/dist/provision.d.ts +16 -1
- package/dist/provision.js +323 -124
- package/dist/provisioning-pull.js +4 -2
- package/dist/signed-node-http.d.ts +2 -1
- package/dist/socket.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/package.json +4 -4
package/dist/bootstrap.js
CHANGED
|
@@ -1405,9 +1405,13 @@ import { DEFAULT_SOCKET } from "@forgezero/vault";
|
|
|
1405
1405
|
|
|
1406
1406
|
// src/agent-update.ts
|
|
1407
1407
|
var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
|
|
1408
|
+
var DEFAULT_AGENT_CANDIDATE_LINK = `${DEFAULT_AGENT_RELEASE_ROOT}/candidate`;
|
|
1408
1409
|
var DEFAULT_AGENT_UPDATE_SOCKET = "/run/forgezero-update/helper.sock";
|
|
1409
1410
|
var MAX_AGENT_TARBALL_BYTES = 32 * 1024 * 1024;
|
|
1410
1411
|
|
|
1412
|
+
// src/agent-handover.ts
|
|
1413
|
+
var DEFAULT_AGENT_CANDIDATE_READY_SOCKET = "/run/forgezero/candidate-ready.sock";
|
|
1414
|
+
|
|
1411
1415
|
// src/agent-update-helper.ts
|
|
1412
1416
|
var AGENT_UPDATE_GROUP = "forgezero-update";
|
|
1413
1417
|
var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-helper.service";
|
|
@@ -1416,7 +1420,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1416
1420
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1417
1421
|
|
|
1418
1422
|
// src/version.ts
|
|
1419
|
-
var VERSION = "0.1.
|
|
1423
|
+
var VERSION = "0.1.87";
|
|
1420
1424
|
|
|
1421
1425
|
// src/software.ts
|
|
1422
1426
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -1674,6 +1678,7 @@ var LIFECYCLE_GROUP = "forgezero-lifecycle";
|
|
|
1674
1678
|
var DEPLOYMENT_RUNNER_UNIT_PATH = "/etc/systemd/system/forgezero-deploy-runner.service";
|
|
1675
1679
|
var AGENT_SOCKET_UNIT_PATH = "/etc/systemd/system/forgezero-agent.socket";
|
|
1676
1680
|
var AGENT_SOCKET_PROXY_UNIT_PATH = "/etc/systemd/system/forgezero-agent-proxy.service";
|
|
1681
|
+
var AGENT_CANDIDATE_UNIT_PATH = "/etc/systemd/system/forgezero-agent-candidate.service";
|
|
1677
1682
|
var DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
|
|
1678
1683
|
var ENROLMENT_UNIT_PATH = "/etc/systemd/system/forgezero-agent-enrol.service";
|
|
1679
1684
|
var LIFECYCLE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-lifecycle-helper.service";
|
|
@@ -1785,6 +1790,7 @@ Type=simple
|
|
|
1785
1790
|
User=root
|
|
1786
1791
|
Group=${AGENT_UPDATE_GROUP}
|
|
1787
1792
|
Environment=FZ_AGENT_UPDATE_SOCKET=${DEFAULT_AGENT_UPDATE_SOCKET}
|
|
1793
|
+
Environment=FZ_AGENT_PUBLIC_SOCKET=${systemdPath(options.socketPath, "agent socket")}
|
|
1788
1794
|
ExecStart=${bin} update-helper
|
|
1789
1795
|
Restart=always
|
|
1790
1796
|
RestartSec=2
|
|
@@ -1829,13 +1835,11 @@ WantedBy=sockets.target
|
|
|
1829
1835
|
`;
|
|
1830
1836
|
}
|
|
1831
1837
|
function agentSocketProxyUnit(options) {
|
|
1832
|
-
const backend =
|
|
1838
|
+
const backend = agentRoutingSocketPath(options.socketPath);
|
|
1833
1839
|
const user = options.user ?? "forgezero";
|
|
1834
1840
|
return `[Unit]
|
|
1835
1841
|
Description=ForgeZero application Vault socket proxy
|
|
1836
1842
|
Documentation=https://www.forgezero.net/docs/agent
|
|
1837
|
-
Requires=forgezero-agent.service
|
|
1838
|
-
After=forgezero-agent.service
|
|
1839
1843
|
|
|
1840
1844
|
[Service]
|
|
1841
1845
|
User=${user}
|
|
@@ -1856,12 +1860,26 @@ RestrictAddressFamilies=AF_UNIX
|
|
|
1856
1860
|
`;
|
|
1857
1861
|
}
|
|
1858
1862
|
function agentBackendSocketPath(publicSocketPath) {
|
|
1863
|
+
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
1864
|
+
const backend = `${socket}.backend.active`;
|
|
1865
|
+
if (Buffer.byteLength(backend) > 100)
|
|
1866
|
+
throw new Error("agent socket path is too long for a Unix socket");
|
|
1867
|
+
return backend;
|
|
1868
|
+
}
|
|
1869
|
+
function agentRoutingSocketPath(publicSocketPath) {
|
|
1859
1870
|
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
1860
1871
|
const backend = `${socket}.backend`;
|
|
1861
1872
|
if (Buffer.byteLength(backend) > 100)
|
|
1862
1873
|
throw new Error("agent socket path is too long for a Unix socket");
|
|
1863
1874
|
return backend;
|
|
1864
1875
|
}
|
|
1876
|
+
function agentCandidateSocketPath(publicSocketPath) {
|
|
1877
|
+
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
1878
|
+
const backend = `${socket}.backend.candidate`;
|
|
1879
|
+
if (Buffer.byteLength(backend) > 100)
|
|
1880
|
+
throw new Error("agent socket path is too long for a Unix socket");
|
|
1881
|
+
return backend;
|
|
1882
|
+
}
|
|
1865
1883
|
var systemdPath = (value, label) => {
|
|
1866
1884
|
if (!value || !/^\/[A-Za-z0-9._@/-]+$/.test(value))
|
|
1867
1885
|
throw new Error(`invalid ${label} path`);
|
|
@@ -2145,7 +2163,11 @@ function agentUnit(options) {
|
|
|
2145
2163
|
}
|
|
2146
2164
|
const environment = [
|
|
2147
2165
|
"NODE_ENV=production",
|
|
2148
|
-
`FZ_SOCKET_PATH=${agentBackendSocketPath(options.socketPath)}`,
|
|
2166
|
+
`FZ_SOCKET_PATH=${options.backendSocketPath ?? agentBackendSocketPath(options.socketPath)}`,
|
|
2167
|
+
`FZ_AGENT_PUBLIC_SOCKET=${options.socketPath}`,
|
|
2168
|
+
`FZ_AGENT_ROUTE_SOCKET=${agentRoutingSocketPath(options.socketPath)}`,
|
|
2169
|
+
options.handoverCandidate ? "FZ_AGENT_HANDOVER_CANDIDATE=true" : null,
|
|
2170
|
+
options.handoverCandidate ? `FZ_AGENT_HANDOVER_READY_SOCKET=${DEFAULT_AGENT_CANDIDATE_READY_SOCKET}` : null,
|
|
2149
2171
|
`FZ_CONTROL_SOCKET=${controlSocketPath}`,
|
|
2150
2172
|
`FZ_SEED_CREDENTIAL=agent-seed`,
|
|
2151
2173
|
`FZ_AGENT_MODE=${options.mode}`,
|
|
@@ -2282,6 +2304,26 @@ ${deploymentWrites}
|
|
|
2282
2304
|
WantedBy=multi-user.target
|
|
2283
2305
|
`;
|
|
2284
2306
|
}
|
|
2307
|
+
function agentCandidateUnit(options) {
|
|
2308
|
+
return agentUnit({
|
|
2309
|
+
...options,
|
|
2310
|
+
binPath: `${DEFAULT_AGENT_RELEASE_ROOT}/candidate/dist/fz-agent.js`,
|
|
2311
|
+
backendSocketPath: agentCandidateSocketPath(options.socketPath),
|
|
2312
|
+
handoverCandidate: true,
|
|
2313
|
+
gitCredentialPath: undefined,
|
|
2314
|
+
deploymentCredentials: {},
|
|
2315
|
+
bootstrapSshCredentialPath: undefined,
|
|
2316
|
+
bootstrapSshPublicKeyPath: undefined,
|
|
2317
|
+
pullBootstrap: false,
|
|
2318
|
+
pullDeployments: false,
|
|
2319
|
+
pullMigrations: false,
|
|
2320
|
+
lifecycleProfilePath: undefined,
|
|
2321
|
+
bootstrapTargetTelemetryEndpoint: undefined,
|
|
2322
|
+
repository: undefined,
|
|
2323
|
+
bootstrapBundlePath: undefined,
|
|
2324
|
+
bootstrapBundleManifestPath: undefined
|
|
2325
|
+
}).replace("Description=ForgeZero node agent", "Description=ForgeZero candidate node agent");
|
|
2326
|
+
}
|
|
2285
2327
|
var renderOperation = (operation) => {
|
|
2286
2328
|
if (operation.kind === "commands")
|
|
2287
2329
|
return operation.commands.map(({ argv }) => argv.join(" ")).join(`
|
|
@@ -2417,6 +2459,7 @@ function planProvision(options) {
|
|
|
2417
2459
|
auxiliaryUnits: [
|
|
2418
2460
|
{ path: AGENT_SOCKET_UNIT_PATH, unit: agentSocketUnit(options) },
|
|
2419
2461
|
{ path: AGENT_SOCKET_PROXY_UNIT_PATH, unit: agentSocketProxyUnit(options) },
|
|
2462
|
+
{ path: AGENT_CANDIDATE_UNIT_PATH, unit: agentCandidateUnit(options) },
|
|
2420
2463
|
{ path: AGENT_UPDATE_HELPER_UNIT_PATH, unit: agentUpdateHelperUnit(options) },
|
|
2421
2464
|
...options.enforceEgress ? [
|
|
2422
2465
|
{ path: AGENT_EGRESS_UNIT_PATH, unit: agentEgressUnit(options) }
|
|
@@ -3002,12 +3045,14 @@ var httpsOrigin = (name, raw) => {
|
|
|
3002
3045
|
return value.origin;
|
|
3003
3046
|
};
|
|
3004
3047
|
function validatePlatformInitialInventory(value) {
|
|
3005
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
|
|
3048
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes", "deployment"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
|
|
3006
3049
|
throw new Error("initial platform inventory coordinates are invalid");
|
|
3007
3050
|
}
|
|
3008
3051
|
safeAtom("initialInventory.region.label", value.region.label);
|
|
3009
3052
|
if (value.region.city !== undefined)
|
|
3010
3053
|
safeAtom("initialInventory.region.city", value.region.city);
|
|
3054
|
+
if (value.deployment !== undefined && (!value.deployment || typeof value.deployment !== "object" || Array.isArray(value.deployment) || Object.keys(value.deployment).some((key) => !["source", "branch", "revision", "bundleSha256"].includes(key)) || value.deployment.source !== "bootstrap-bundle" || !["dev", "main"].includes(value.deployment.branch) || !/^[a-f0-9]{40}$/.test(value.deployment.revision) || !/^[a-f0-9]{64}$/.test(value.deployment.bundleSha256)))
|
|
3055
|
+
throw new Error("initial platform deployment evidence is invalid");
|
|
3011
3056
|
const computes = validatePlatformGenesisGuests(value.computes.map((compute) => {
|
|
3012
3057
|
if (!compute || typeof compute !== "object" || Array.isArray(compute) || Object.keys(compute).some((key) => ![
|
|
3013
3058
|
"name",
|
|
@@ -3042,7 +3087,8 @@ function validatePlatformInitialInventory(value) {
|
|
|
3042
3087
|
databaseRole: compute.databaseRole,
|
|
3043
3088
|
databaseAgency: compute.databaseAgency,
|
|
3044
3089
|
confidential: true
|
|
3045
|
-
}))
|
|
3090
|
+
})),
|
|
3091
|
+
...value.deployment ? { deployment: { ...value.deployment } } : {}
|
|
3046
3092
|
};
|
|
3047
3093
|
}
|
|
3048
3094
|
var systemdValue = (name, raw) => {
|
|
@@ -4770,8 +4816,10 @@ function strictBootstrapDocument(value) {
|
|
|
4770
4816
|
], "runtime environment");
|
|
4771
4817
|
const environment = runtime.environment;
|
|
4772
4818
|
if (environment.initialInventory !== undefined) {
|
|
4773
|
-
const inventory = exactKeys(environment.initialInventory, ["metalHostname", "region", "computes"], "initial inventory");
|
|
4819
|
+
const inventory = exactKeys(environment.initialInventory, ["metalHostname", "region", "computes", "deployment"], "initial inventory");
|
|
4774
4820
|
exactKeys(inventory.region, ["key", "label", "country", "city", "confidentialCapable"], "initial inventory region");
|
|
4821
|
+
if (inventory.deployment !== undefined)
|
|
4822
|
+
exactKeys(inventory.deployment, ["source", "branch", "revision", "bundleSha256"], "initial deployment evidence");
|
|
4775
4823
|
if (!Array.isArray(inventory.computes))
|
|
4776
4824
|
throw new Error("initial inventory computes must be an array");
|
|
4777
4825
|
for (const compute of inventory.computes)
|
|
@@ -129,9 +129,11 @@ import {
|
|
|
129
129
|
|
|
130
130
|
class SignedNodeHttpError extends Error {
|
|
131
131
|
status;
|
|
132
|
-
|
|
132
|
+
code;
|
|
133
|
+
constructor(status, message, code) {
|
|
133
134
|
super(message);
|
|
134
135
|
this.status = status;
|
|
136
|
+
this.code = code;
|
|
135
137
|
this.name = "SignedNodeHttpError";
|
|
136
138
|
}
|
|
137
139
|
}
|
|
@@ -178,7 +180,7 @@ async function postSignedNode(options, path, body) {
|
|
|
178
180
|
if (!response.ok) {
|
|
179
181
|
const failure = payload;
|
|
180
182
|
const reason = failure ? failure.error?.message ?? failure.message : undefined;
|
|
181
|
-
throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}
|
|
183
|
+
throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}`, failure?.error?.code);
|
|
182
184
|
}
|
|
183
185
|
try {
|
|
184
186
|
return await openResponse(recipient.secretKey, signature, payload);
|
|
@@ -216,8 +218,17 @@ function tenantNodeApiUrl(apiUrl, tenantSlug) {
|
|
|
216
218
|
return url.toString().replace(/\/$/, "");
|
|
217
219
|
}
|
|
218
220
|
function createNodeVaultCache(options) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
+
let cache;
|
|
222
|
+
const post = async (operation, body) => {
|
|
223
|
+
try {
|
|
224
|
+
return await postSignedNode(options, `v1/node/vault/${operation}`, body);
|
|
225
|
+
} catch (cause) {
|
|
226
|
+
if (cause instanceof SignedNodeHttpError && cause.code === "VAULT_API_ONLY")
|
|
227
|
+
cache?.clear();
|
|
228
|
+
throw cause;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
cache = createSecretCache({
|
|
221
232
|
ttlMs: options.ttlMs,
|
|
222
233
|
maxStaleMs: options.maxStaleMs,
|
|
223
234
|
list: async () => {
|
|
@@ -246,6 +257,7 @@ function createNodeVaultCache(options) {
|
|
|
246
257
|
};
|
|
247
258
|
}
|
|
248
259
|
});
|
|
260
|
+
return cache;
|
|
249
261
|
}
|
|
250
262
|
function startNodeVaultSync(cache, options = {}) {
|
|
251
263
|
const interval = Math.max(1000, options.intervalMs ?? 30000);
|