@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/fz-agent.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
6
|
-
import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, existsSync as
|
|
6
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, existsSync as existsSync23, mkdirSync as mkdirSync18, chmodSync as chmodSync19, lstatSync as lstatSync10, statfsSync as statfsSync3 } from "fs";
|
|
7
7
|
import { cpus as cpus2, totalmem as totalmem2 } from "os";
|
|
8
|
-
import { dirname as
|
|
8
|
+
import { dirname as dirname16, join as join12 } from "path";
|
|
9
9
|
|
|
10
10
|
// ../access/dist/security.js
|
|
11
11
|
var HEX = Array.from({ length: 256 }, (_, index) => index.toString(16).padStart(2, "0"));
|
|
@@ -4201,7 +4201,16 @@ var DEFAULT_SOCKET = "/run/forgezero/vault.sock";
|
|
|
4201
4201
|
|
|
4202
4202
|
// src/socket.ts
|
|
4203
4203
|
import { createServer } from "net";
|
|
4204
|
-
import {
|
|
4204
|
+
import {
|
|
4205
|
+
existsSync,
|
|
4206
|
+
chmodSync,
|
|
4207
|
+
lstatSync,
|
|
4208
|
+
readlinkSync,
|
|
4209
|
+
renameSync,
|
|
4210
|
+
symlinkSync,
|
|
4211
|
+
unlinkSync
|
|
4212
|
+
} from "fs";
|
|
4213
|
+
import { basename, dirname } from "path";
|
|
4205
4214
|
|
|
4206
4215
|
// src/cache.ts
|
|
4207
4216
|
class CacheError extends Error {
|
|
@@ -4326,9 +4335,11 @@ function createSecretCache(options) {
|
|
|
4326
4335
|
// src/signed-node-http.ts
|
|
4327
4336
|
class SignedNodeHttpError extends Error {
|
|
4328
4337
|
status;
|
|
4329
|
-
|
|
4338
|
+
code;
|
|
4339
|
+
constructor(status, message, code) {
|
|
4330
4340
|
super(message);
|
|
4331
4341
|
this.status = status;
|
|
4342
|
+
this.code = code;
|
|
4332
4343
|
this.name = "SignedNodeHttpError";
|
|
4333
4344
|
}
|
|
4334
4345
|
}
|
|
@@ -4375,7 +4386,7 @@ async function postSignedNode(options, path, body) {
|
|
|
4375
4386
|
if (!response.ok) {
|
|
4376
4387
|
const failure = payload;
|
|
4377
4388
|
const reason = failure ? failure.error?.message ?? failure.message : undefined;
|
|
4378
|
-
throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}
|
|
4389
|
+
throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}`, failure?.error?.code);
|
|
4379
4390
|
}
|
|
4380
4391
|
try {
|
|
4381
4392
|
return await openResponse(recipient.secretKey, signature, payload);
|
|
@@ -4413,8 +4424,17 @@ function tenantNodeApiUrl(apiUrl, tenantSlug) {
|
|
|
4413
4424
|
return url.toString().replace(/\/$/, "");
|
|
4414
4425
|
}
|
|
4415
4426
|
function createNodeVaultCache(options) {
|
|
4416
|
-
|
|
4417
|
-
|
|
4427
|
+
let cache;
|
|
4428
|
+
const post = async (operation, body) => {
|
|
4429
|
+
try {
|
|
4430
|
+
return await postSignedNode(options, `v1/node/vault/${operation}`, body);
|
|
4431
|
+
} catch (cause) {
|
|
4432
|
+
if (cause instanceof SignedNodeHttpError && cause.code === "VAULT_API_ONLY")
|
|
4433
|
+
cache?.clear();
|
|
4434
|
+
throw cause;
|
|
4435
|
+
}
|
|
4436
|
+
};
|
|
4437
|
+
cache = createSecretCache({
|
|
4418
4438
|
ttlMs: options.ttlMs,
|
|
4419
4439
|
maxStaleMs: options.maxStaleMs,
|
|
4420
4440
|
list: async () => {
|
|
@@ -4443,6 +4463,7 @@ function createNodeVaultCache(options) {
|
|
|
4443
4463
|
};
|
|
4444
4464
|
}
|
|
4445
4465
|
});
|
|
4466
|
+
return cache;
|
|
4446
4467
|
}
|
|
4447
4468
|
function startNodeVaultSync(cache, options = {}) {
|
|
4448
4469
|
const interval = Math.max(1000, options.intervalMs ?? 30000);
|
|
@@ -4483,6 +4504,32 @@ function handleApplicationRequest(options, request) {
|
|
|
4483
4504
|
return handleRequest(options, request);
|
|
4484
4505
|
}
|
|
4485
4506
|
var MAX_LINE_BYTES = 64 * 1024;
|
|
4507
|
+
function ensureAgentSocketRoute(routePath, activePath) {
|
|
4508
|
+
const target = basename(activePath);
|
|
4509
|
+
if (dirname(routePath) !== dirname(activePath) || target.includes("/") || target === "." || target === "..") {
|
|
4510
|
+
throw new Error("agent socket route and active backend must share one runtime directory");
|
|
4511
|
+
}
|
|
4512
|
+
try {
|
|
4513
|
+
if (lstatSync(routePath).isSymbolicLink()) {
|
|
4514
|
+
const current = readlinkSync(routePath);
|
|
4515
|
+
if (current === target || existsSync(`${dirname(routePath)}/${current}`))
|
|
4516
|
+
return;
|
|
4517
|
+
} else
|
|
4518
|
+
throw new Error("agent socket route must be a symbolic link");
|
|
4519
|
+
} catch (cause) {
|
|
4520
|
+
if (cause.code !== "ENOENT")
|
|
4521
|
+
throw cause;
|
|
4522
|
+
}
|
|
4523
|
+
const next = `${routePath}.${process.pid}.next`;
|
|
4524
|
+
try {
|
|
4525
|
+
symlinkSync(target, next);
|
|
4526
|
+
renameSync(next, routePath);
|
|
4527
|
+
} finally {
|
|
4528
|
+
try {
|
|
4529
|
+
unlinkSync(next);
|
|
4530
|
+
} catch {}
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4486
4533
|
function handleRequest(options, request) {
|
|
4487
4534
|
switch (request?.op) {
|
|
4488
4535
|
case "identity":
|
|
@@ -4641,9 +4688,9 @@ function safeOp(line) {
|
|
|
4641
4688
|
}
|
|
4642
4689
|
|
|
4643
4690
|
// src/deployment.ts
|
|
4644
|
-
import { chmodSync as chmodSync4, existsSync as existsSync4, lstatSync as
|
|
4691
|
+
import { chmodSync as chmodSync4, existsSync as existsSync4, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync4, statfsSync, writeFileSync as writeFileSync3 } from "fs";
|
|
4645
4692
|
import { createHash as createHash5, randomUUID } from "crypto";
|
|
4646
|
-
import { dirname as
|
|
4693
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
4647
4694
|
|
|
4648
4695
|
// ../runtime/dist/queue.js
|
|
4649
4696
|
class QueueStoppedError extends Error {
|
|
@@ -4958,15 +5005,15 @@ import {
|
|
|
4958
5005
|
mkdtempSync,
|
|
4959
5006
|
mkdirSync,
|
|
4960
5007
|
readFileSync,
|
|
4961
|
-
renameSync,
|
|
5008
|
+
renameSync as renameSync2,
|
|
4962
5009
|
rmSync,
|
|
4963
5010
|
statSync,
|
|
4964
|
-
symlinkSync,
|
|
5011
|
+
symlinkSync as symlinkSync2,
|
|
4965
5012
|
unlinkSync as unlinkSync2,
|
|
4966
5013
|
writeFileSync
|
|
4967
5014
|
} from "fs";
|
|
4968
5015
|
import { tmpdir } from "os";
|
|
4969
|
-
import { dirname, join } from "path";
|
|
5016
|
+
import { dirname as dirname2, join } from "path";
|
|
4970
5017
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
4971
5018
|
var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
|
|
4972
5019
|
var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
|
|
@@ -5176,7 +5223,7 @@ var writeOwnedPolicy = (pathValue, content, mode = 420) => {
|
|
|
5176
5223
|
throw new Error(`refusing to overwrite non-ForgeZero policy at ${pathValue}`);
|
|
5177
5224
|
return;
|
|
5178
5225
|
}
|
|
5179
|
-
mkdirSync(
|
|
5226
|
+
mkdirSync(dirname2(pathValue), { recursive: true, mode: 493 });
|
|
5180
5227
|
writeFileSync(pathValue, content, { mode, flag: "wx" });
|
|
5181
5228
|
};
|
|
5182
5229
|
var installContainerdRuntime = async (directory, osVersion) => {
|
|
@@ -5254,7 +5301,7 @@ var installKataRuntime = async (directory) => {
|
|
|
5254
5301
|
try {
|
|
5255
5302
|
unlinkSync2("/usr/local/bin/containerd-shim-kata-v2");
|
|
5256
5303
|
} catch {}
|
|
5257
|
-
|
|
5304
|
+
symlinkSync2("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
|
|
5258
5305
|
const base = CONTAINERD_CONFIG(false);
|
|
5259
5306
|
const kata = CONTAINERD_CONFIG(true);
|
|
5260
5307
|
if (!existsSync2(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
|
|
@@ -5395,11 +5442,11 @@ async function executeSoftwareOperation(operation) {
|
|
|
5395
5442
|
mkdirSync("/usr/local/lib/forgezero/runtime", { recursive: true, mode: 493 });
|
|
5396
5443
|
copyFileSync(join(unpacked, "bun-linux-x64", "bun"), "/usr/local/lib/forgezero/runtime/bun.next");
|
|
5397
5444
|
chmodSync2("/usr/local/lib/forgezero/runtime/bun.next", 493);
|
|
5398
|
-
|
|
5445
|
+
renameSync2("/usr/local/lib/forgezero/runtime/bun.next", "/usr/local/lib/forgezero/runtime/bun");
|
|
5399
5446
|
try {
|
|
5400
5447
|
unlinkSync2("/usr/local/bin/bun");
|
|
5401
5448
|
} catch {}
|
|
5402
|
-
|
|
5449
|
+
symlinkSync2("/usr/local/lib/forgezero/runtime/bun", "/usr/local/bin/bun");
|
|
5403
5450
|
return { exitCode: 0, output: "" };
|
|
5404
5451
|
}
|
|
5405
5452
|
if (software === "cloudflared") {
|
|
@@ -7766,10 +7813,10 @@ import {
|
|
|
7766
7813
|
chmodSync as chmodSync3,
|
|
7767
7814
|
createReadStream as createReadStream2,
|
|
7768
7815
|
existsSync as existsSync3,
|
|
7769
|
-
lstatSync,
|
|
7816
|
+
lstatSync as lstatSync2,
|
|
7770
7817
|
mkdirSync as mkdirSync2,
|
|
7771
7818
|
readFileSync as readFileSync2,
|
|
7772
|
-
renameSync as
|
|
7819
|
+
renameSync as renameSync3,
|
|
7773
7820
|
rmSync as rmSync2,
|
|
7774
7821
|
writeFileSync as writeFileSync2
|
|
7775
7822
|
} from "fs";
|
|
@@ -7865,13 +7912,13 @@ function persistCapacityCalibration(args) {
|
|
|
7865
7912
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be an absolute Agent-owned path");
|
|
7866
7913
|
}
|
|
7867
7914
|
if (!existsSync4(args.directory)) {
|
|
7868
|
-
const parent =
|
|
7915
|
+
const parent = lstatSync3(dirname3(args.directory));
|
|
7869
7916
|
if (!parent.isDirectory() || parent.isSymbolicLink()) {
|
|
7870
7917
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence parent must be a real Agent-owned directory");
|
|
7871
7918
|
}
|
|
7872
7919
|
mkdirSync3(args.directory, { mode: 448 });
|
|
7873
7920
|
}
|
|
7874
|
-
const stats =
|
|
7921
|
+
const stats = lstatSync3(args.directory);
|
|
7875
7922
|
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
7876
7923
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be a real Agent-owned directory");
|
|
7877
7924
|
}
|
|
@@ -7894,7 +7941,7 @@ function persistCapacityCalibration(args) {
|
|
|
7894
7941
|
}, null, 2)}
|
|
7895
7942
|
`, { mode: 384, flag: "wx" });
|
|
7896
7943
|
chmodSync4(next, 384);
|
|
7897
|
-
|
|
7944
|
+
renameSync4(next, path2);
|
|
7898
7945
|
return path2;
|
|
7899
7946
|
}
|
|
7900
7947
|
function createDeploymentManager(options) {
|
|
@@ -7929,10 +7976,10 @@ function createDeploymentManager(options) {
|
|
|
7929
7976
|
}
|
|
7930
7977
|
if (existsSync4(knownHostsPath) && readFileSync3(knownHostsPath, "utf8") === content)
|
|
7931
7978
|
return;
|
|
7932
|
-
mkdirSync3(
|
|
7979
|
+
mkdirSync3(dirname3(knownHostsPath), { recursive: true, mode: 448 });
|
|
7933
7980
|
const next = `${knownHostsPath}.${process.pid}.${randomUUID()}.next`;
|
|
7934
7981
|
writeFileSync3(next, content, { mode: 384, flag: "wx" });
|
|
7935
|
-
|
|
7982
|
+
renameSync4(next, knownHostsPath);
|
|
7936
7983
|
chmodSync4(knownHostsPath, 384);
|
|
7937
7984
|
};
|
|
7938
7985
|
const gitBaseEnvironment = () => ({
|
|
@@ -8058,7 +8105,7 @@ function createDeploymentManager(options) {
|
|
|
8058
8105
|
if (bootstrapBundle) {
|
|
8059
8106
|
let metadata;
|
|
8060
8107
|
try {
|
|
8061
|
-
metadata =
|
|
8108
|
+
metadata = lstatSync3(bootstrapBundle.path);
|
|
8062
8109
|
} catch {
|
|
8063
8110
|
throw new DeploymentError("SOURCE_FAILED", "The attended bootstrap bundle is missing.");
|
|
8064
8111
|
}
|
|
@@ -8346,7 +8393,7 @@ function createDeploymentManager(options) {
|
|
|
8346
8393
|
const component2 = plan.spec.components[String(resolved.component ?? "")];
|
|
8347
8394
|
const mounts = component2?.kind === "database" ? [component2.storage] : component2?.storage ?? [];
|
|
8348
8395
|
for (const mount of mounts) {
|
|
8349
|
-
const candidate = existsSync4(mount.path) ? mount.path :
|
|
8396
|
+
const candidate = existsSync4(mount.path) ? mount.path : dirname3(mount.path);
|
|
8350
8397
|
const stats = statfsSync(candidate);
|
|
8351
8398
|
const freePercent = Number(stats.bavail) / Number(stats.blocks) * 100;
|
|
8352
8399
|
const minimum = mount.class === "ephemeral" ? 0 : mount.minimumFreePercent ?? 0;
|
|
@@ -8922,12 +8969,12 @@ import {
|
|
|
8922
8969
|
existsSync as existsSync6,
|
|
8923
8970
|
mkdirSync as mkdirSync4,
|
|
8924
8971
|
readFileSync as readFileSync4,
|
|
8925
|
-
renameSync as
|
|
8972
|
+
renameSync as renameSync5,
|
|
8926
8973
|
statSync as statSync2,
|
|
8927
8974
|
unlinkSync as unlinkSync4,
|
|
8928
8975
|
writeFileSync as writeFileSync4
|
|
8929
8976
|
} from "fs";
|
|
8930
|
-
import { dirname as
|
|
8977
|
+
import { dirname as dirname4 } from "path";
|
|
8931
8978
|
function privateNetworkAttachmentFromEnvironment(env = process.env) {
|
|
8932
8979
|
const values = {
|
|
8933
8980
|
accountId: env.FZ_CF_ACCOUNT_ID?.trim() ?? "",
|
|
@@ -8973,12 +9020,12 @@ function loadGuestBinding(path2, expectedNodeKey) {
|
|
|
8973
9020
|
return parsed;
|
|
8974
9021
|
}
|
|
8975
9022
|
function persistGuestBinding(path2, binding) {
|
|
8976
|
-
mkdirSync4(
|
|
9023
|
+
mkdirSync4(dirname4(path2), { recursive: true, mode: 448 });
|
|
8977
9024
|
const temporary = `${path2}.next`;
|
|
8978
9025
|
writeFileSync4(temporary, `${JSON.stringify(binding)}
|
|
8979
9026
|
`, { mode: 384 });
|
|
8980
9027
|
chmodSync6(temporary, 384);
|
|
8981
|
-
|
|
9028
|
+
renameSync5(temporary, path2);
|
|
8982
9029
|
}
|
|
8983
9030
|
async function enrolGuestIdentity(options) {
|
|
8984
9031
|
const token = options.token?.trim() ?? (options.tokenPath ? readFileSync4(options.tokenPath, "utf8").trim() : "");
|
|
@@ -9331,7 +9378,7 @@ async function writeAndCloseProcessInput(input, value) {
|
|
|
9331
9378
|
}
|
|
9332
9379
|
|
|
9333
9380
|
// src/version.ts
|
|
9334
|
-
var VERSION2 = "0.1.
|
|
9381
|
+
var VERSION2 = "0.1.87";
|
|
9335
9382
|
|
|
9336
9383
|
// src/ssh-bootstrap.ts
|
|
9337
9384
|
class SshBootstrapError extends Error {
|
|
@@ -10050,7 +10097,7 @@ import {
|
|
|
10050
10097
|
unlinkSync as unlinkSync5,
|
|
10051
10098
|
writeFileSync as writeFileSync6
|
|
10052
10099
|
} from "fs";
|
|
10053
|
-
import { dirname as
|
|
10100
|
+
import { dirname as dirname5, isAbsolute as isAbsolute3, join as join4 } from "path";
|
|
10054
10101
|
import { isIP as isIP2 } from "net";
|
|
10055
10102
|
|
|
10056
10103
|
// src/compute.ts
|
|
@@ -10599,7 +10646,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
10599
10646
|
}
|
|
10600
10647
|
const service = `forgezero-guest@${name}.service`;
|
|
10601
10648
|
const unitPath = join4(profile.unitDir, service);
|
|
10602
|
-
mkdirSync5(
|
|
10649
|
+
mkdirSync5(dirname5(unitPath), { recursive: true });
|
|
10603
10650
|
writeFileSync6(unitPath, guestUnit(spec), { mode: 420 });
|
|
10604
10651
|
await checked2(exec, ["systemctl", "daemon-reload"]);
|
|
10605
10652
|
if (prior?.phase === "running") {
|
|
@@ -11567,7 +11614,7 @@ function requestLifecycleAction(claim, socketPath = DEFAULT_LIFECYCLE_HELPER_SOC
|
|
|
11567
11614
|
}
|
|
11568
11615
|
|
|
11569
11616
|
// src/warp-config.ts
|
|
11570
|
-
import { chmodSync as chmodSync11, mkdirSync as mkdirSync7, renameSync as
|
|
11617
|
+
import { chmodSync as chmodSync11, mkdirSync as mkdirSync7, renameSync as renameSync6, symlinkSync as symlinkSync3, unlinkSync as unlinkSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
11571
11618
|
var xml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11572
11619
|
function renderWarpMdm(options) {
|
|
11573
11620
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(options.organization))
|
|
@@ -11593,14 +11640,14 @@ function materializeWarpMdm(options) {
|
|
|
11593
11640
|
const next = `${runtimePath}.next`;
|
|
11594
11641
|
writeFileSync8(next, renderWarpMdm(options), { mode: 384 });
|
|
11595
11642
|
chmodSync11(next, 384);
|
|
11596
|
-
|
|
11643
|
+
renameSync6(next, runtimePath);
|
|
11597
11644
|
try {
|
|
11598
11645
|
unlinkSync9(servicePath);
|
|
11599
11646
|
} catch (cause) {
|
|
11600
11647
|
if (cause.code !== "ENOENT")
|
|
11601
11648
|
throw cause;
|
|
11602
11649
|
}
|
|
11603
|
-
|
|
11650
|
+
symlinkSync3(runtimePath, servicePath);
|
|
11604
11651
|
return { runtimePath, servicePath };
|
|
11605
11652
|
}
|
|
11606
11653
|
|
|
@@ -11657,14 +11704,15 @@ import {
|
|
|
11657
11704
|
mkdirSync as mkdirSync8,
|
|
11658
11705
|
openSync as openSync2,
|
|
11659
11706
|
readFileSync as readFileSync9,
|
|
11660
|
-
readlinkSync,
|
|
11661
|
-
renameSync as
|
|
11707
|
+
readlinkSync as readlinkSync2,
|
|
11708
|
+
renameSync as renameSync7,
|
|
11662
11709
|
rmSync as rmSync5,
|
|
11663
|
-
symlinkSync as
|
|
11710
|
+
symlinkSync as symlinkSync4,
|
|
11664
11711
|
writeFileSync as writeFileSync9
|
|
11665
11712
|
} from "fs";
|
|
11666
|
-
import { dirname as
|
|
11713
|
+
import { dirname as dirname6, join as join7, resolve as resolve2 } from "path";
|
|
11667
11714
|
var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
|
|
11715
|
+
var DEFAULT_AGENT_CANDIDATE_LINK = `${DEFAULT_AGENT_RELEASE_ROOT}/candidate`;
|
|
11668
11716
|
var DEFAULT_AGENT_UPDATE_SOCKET = "/run/forgezero-update/helper.sock";
|
|
11669
11717
|
var MAX_AGENT_TARBALL_BYTES = 32 * 1024 * 1024;
|
|
11670
11718
|
var VERSION3 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
@@ -11685,7 +11733,7 @@ var syncReleaseDirectory = (directory) => {
|
|
|
11685
11733
|
join7(directory, "dist", "fz-git-ssh.js"),
|
|
11686
11734
|
join7(directory, "dist"),
|
|
11687
11735
|
directory,
|
|
11688
|
-
|
|
11736
|
+
dirname6(directory)
|
|
11689
11737
|
])
|
|
11690
11738
|
syncPath(path2);
|
|
11691
11739
|
};
|
|
@@ -11807,19 +11855,19 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
11807
11855
|
args: ["-xOzf", archive, member]
|
|
11808
11856
|
}, `agent update extraction of ${member}`);
|
|
11809
11857
|
const destination = join7(unpacked, relative);
|
|
11810
|
-
mkdirSync8(
|
|
11858
|
+
mkdirSync8(dirname6(destination), { recursive: true, mode: 448 });
|
|
11811
11859
|
writeFileSync9(destination, extracted.output, { mode: 384, flag: "wx" });
|
|
11812
11860
|
}
|
|
11813
11861
|
await validateReleaseDirectory(unpacked, release, run2);
|
|
11814
11862
|
if (!existsSync12(finalDirectory)) {
|
|
11815
|
-
|
|
11863
|
+
renameSync7(unpacked, finalDirectory);
|
|
11816
11864
|
syncReleaseDirectory(finalDirectory);
|
|
11817
11865
|
} else
|
|
11818
11866
|
await validateReleaseDirectory(finalDirectory, release, run2);
|
|
11819
11867
|
if (!existsSync12(currentLink)) {
|
|
11820
11868
|
throw new Error("agent update requires an active immutable release to roll back to");
|
|
11821
11869
|
}
|
|
11822
|
-
const previousTarget =
|
|
11870
|
+
const previousTarget = readlinkSync2(currentLink);
|
|
11823
11871
|
if (previousTarget !== join7("versions", options.currentVersion)) {
|
|
11824
11872
|
throw new Error("agent update current release does not match the running version");
|
|
11825
11873
|
}
|
|
@@ -11839,43 +11887,116 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
11839
11887
|
}
|
|
11840
11888
|
}
|
|
11841
11889
|
function selectAgentRelease(staged) {
|
|
11842
|
-
const next = join7(
|
|
11890
|
+
const next = join7(dirname6(staged.currentLink), `.current.${randomUUID2()}.next`);
|
|
11843
11891
|
try {
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
syncPath(
|
|
11892
|
+
symlinkSync4(staged.nextTarget, next);
|
|
11893
|
+
renameSync7(next, staged.currentLink);
|
|
11894
|
+
syncPath(dirname6(staged.currentLink));
|
|
11847
11895
|
} finally {
|
|
11848
11896
|
rmSync5(next, { force: true });
|
|
11849
11897
|
}
|
|
11850
11898
|
}
|
|
11851
11899
|
function restoreAgentRelease(staged) {
|
|
11852
|
-
const next = join7(
|
|
11900
|
+
const next = join7(dirname6(staged.currentLink), `.current.${randomUUID2()}.rollback`);
|
|
11901
|
+
try {
|
|
11902
|
+
symlinkSync4(staged.previousTarget, next);
|
|
11903
|
+
renameSync7(next, staged.currentLink);
|
|
11904
|
+
syncPath(dirname6(staged.currentLink));
|
|
11905
|
+
} finally {
|
|
11906
|
+
rmSync5(next, { force: true });
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
function selectAgentCandidate(staged) {
|
|
11910
|
+
const link = join7(dirname6(staged.currentLink), "candidate");
|
|
11911
|
+
const next = `${link}.${randomUUID2()}.next`;
|
|
11853
11912
|
try {
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
syncPath(
|
|
11913
|
+
symlinkSync4(staged.nextTarget, next);
|
|
11914
|
+
renameSync7(next, link);
|
|
11915
|
+
syncPath(dirname6(link));
|
|
11857
11916
|
} finally {
|
|
11858
11917
|
rmSync5(next, { force: true });
|
|
11859
11918
|
}
|
|
11860
11919
|
}
|
|
11920
|
+
function clearAgentCandidate(root = DEFAULT_AGENT_RELEASE_ROOT) {
|
|
11921
|
+
rmSync5(join7(resolve2(root), "candidate"), { force: true });
|
|
11922
|
+
}
|
|
11861
11923
|
|
|
11862
11924
|
// src/agent-update-helper.ts
|
|
11863
|
-
import { randomUUID as
|
|
11925
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
11864
11926
|
import {
|
|
11865
|
-
chmodSync as
|
|
11927
|
+
chmodSync as chmodSync14,
|
|
11866
11928
|
closeSync as closeSync3,
|
|
11867
|
-
existsSync as
|
|
11929
|
+
existsSync as existsSync14,
|
|
11868
11930
|
fsyncSync as fsyncSync2,
|
|
11869
11931
|
mkdirSync as mkdirSync9,
|
|
11870
11932
|
openSync as openSync3,
|
|
11871
11933
|
readFileSync as readFileSync10,
|
|
11872
|
-
renameSync as
|
|
11873
|
-
rmSync as
|
|
11874
|
-
unlinkSync as
|
|
11934
|
+
renameSync as renameSync9,
|
|
11935
|
+
rmSync as rmSync7,
|
|
11936
|
+
unlinkSync as unlinkSync11,
|
|
11875
11937
|
writeFileSync as writeFileSync10
|
|
11876
11938
|
} from "fs";
|
|
11939
|
+
import { connect as connect6, createServer as createServer7 } from "net";
|
|
11940
|
+
import { dirname as dirname8, join as join8, resolve as resolve3 } from "path";
|
|
11941
|
+
|
|
11942
|
+
// src/agent-handover.ts
|
|
11943
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
11944
|
+
import { chmodSync as chmodSync13, existsSync as existsSync13, renameSync as renameSync8, rmSync as rmSync6, symlinkSync as symlinkSync5, unlinkSync as unlinkSync10 } from "fs";
|
|
11945
|
+
import { basename as basename2, dirname as dirname7 } from "path";
|
|
11877
11946
|
import { connect as connect5, createServer as createServer6 } from "net";
|
|
11878
|
-
|
|
11947
|
+
var DEFAULT_AGENT_CANDIDATE_READY_SOCKET = "/run/forgezero/candidate-ready.sock";
|
|
11948
|
+
function switchAgentSocketRoute(route, backend) {
|
|
11949
|
+
if (dirname7(route) !== dirname7(backend))
|
|
11950
|
+
throw new Error("Agent handover sockets must share one runtime directory");
|
|
11951
|
+
const target2 = basename2(backend);
|
|
11952
|
+
const next = `${route}.${randomUUID3()}.next`;
|
|
11953
|
+
try {
|
|
11954
|
+
symlinkSync5(target2, next);
|
|
11955
|
+
renameSync8(next, route);
|
|
11956
|
+
} finally {
|
|
11957
|
+
rmSync6(next, { force: true });
|
|
11958
|
+
}
|
|
11959
|
+
}
|
|
11960
|
+
function startAgentCandidateReady(ready, socketPath = DEFAULT_AGENT_CANDIDATE_READY_SOCKET) {
|
|
11961
|
+
if (existsSync13(socketPath))
|
|
11962
|
+
unlinkSync10(socketPath);
|
|
11963
|
+
const server = createServer6((socket) => socket.end(`${JSON.stringify(ready)}
|
|
11964
|
+
`));
|
|
11965
|
+
server.listen(socketPath, () => chmodSync13(socketPath, 432));
|
|
11966
|
+
return server;
|
|
11967
|
+
}
|
|
11968
|
+
function probeAgentCandidate(expected, socketPath = DEFAULT_AGENT_CANDIDATE_READY_SOCKET, timeoutMs = 5000) {
|
|
11969
|
+
return new Promise((resolve3) => {
|
|
11970
|
+
const socket = connect5(socketPath);
|
|
11971
|
+
let buffer = "";
|
|
11972
|
+
let settled = false;
|
|
11973
|
+
const finish = (value) => {
|
|
11974
|
+
if (settled)
|
|
11975
|
+
return;
|
|
11976
|
+
settled = true;
|
|
11977
|
+
clearTimeout(timer);
|
|
11978
|
+
socket.destroy();
|
|
11979
|
+
resolve3(value);
|
|
11980
|
+
};
|
|
11981
|
+
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
11982
|
+
socket.on("data", (chunk) => {
|
|
11983
|
+
buffer += chunk.toString("utf8");
|
|
11984
|
+
const newline = buffer.indexOf(`
|
|
11985
|
+
`);
|
|
11986
|
+
if (newline < 0)
|
|
11987
|
+
return;
|
|
11988
|
+
try {
|
|
11989
|
+
const value = JSON.parse(buffer.slice(0, newline));
|
|
11990
|
+
finish(value.version === expected.version && typeof value.nodeKey === "string" && typeof value.bound === "boolean" && ["ready", "partial", "unavailable", "unbound"].includes(value.vault ?? ""));
|
|
11991
|
+
} catch {
|
|
11992
|
+
finish(false);
|
|
11993
|
+
}
|
|
11994
|
+
});
|
|
11995
|
+
socket.on("error", () => finish(false));
|
|
11996
|
+
});
|
|
11997
|
+
}
|
|
11998
|
+
|
|
11999
|
+
// src/agent-update-helper.ts
|
|
11879
12000
|
var AGENT_UPDATE_GROUP = "forgezero-update";
|
|
11880
12001
|
var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-helper.service";
|
|
11881
12002
|
var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
|
|
@@ -11970,13 +12091,13 @@ function validateJournal(value, root) {
|
|
|
11970
12091
|
return journal;
|
|
11971
12092
|
}
|
|
11972
12093
|
function readJournal(path2, root) {
|
|
11973
|
-
if (!
|
|
12094
|
+
if (!existsSync14(path2))
|
|
11974
12095
|
return;
|
|
11975
12096
|
return validateJournal(JSON.parse(readFileSync10(path2, "utf8")), root);
|
|
11976
12097
|
}
|
|
11977
12098
|
function writeAtomic(path2, value, mode) {
|
|
11978
|
-
mkdirSync9(
|
|
11979
|
-
const next = `${path2}.${
|
|
12099
|
+
mkdirSync9(dirname8(path2), { recursive: true, mode: 493 });
|
|
12100
|
+
const next = `${path2}.${randomUUID4()}.next`;
|
|
11980
12101
|
let file;
|
|
11981
12102
|
try {
|
|
11982
12103
|
file = openSync3(next, "wx", mode);
|
|
@@ -11985,8 +12106,8 @@ function writeAtomic(path2, value, mode) {
|
|
|
11985
12106
|
fsyncSync2(file);
|
|
11986
12107
|
closeSync3(file);
|
|
11987
12108
|
file = undefined;
|
|
11988
|
-
|
|
11989
|
-
const directory = openSync3(
|
|
12109
|
+
renameSync9(next, path2);
|
|
12110
|
+
const directory = openSync3(dirname8(path2), "r");
|
|
11990
12111
|
try {
|
|
11991
12112
|
fsyncSync2(directory);
|
|
11992
12113
|
} finally {
|
|
@@ -11995,7 +12116,7 @@ function writeAtomic(path2, value, mode) {
|
|
|
11995
12116
|
} finally {
|
|
11996
12117
|
if (file !== undefined)
|
|
11997
12118
|
closeSync3(file);
|
|
11998
|
-
|
|
12119
|
+
rmSync7(next, { force: true });
|
|
11999
12120
|
}
|
|
12000
12121
|
}
|
|
12001
12122
|
var publicReceipt = (journal) => {
|
|
@@ -12028,7 +12149,7 @@ function writeUpdateState(journalPath, receiptPath, journal) {
|
|
|
12028
12149
|
}
|
|
12029
12150
|
function readAgentUpdateReceipt(path2 = AGENT_UPDATE_RECEIPT) {
|
|
12030
12151
|
try {
|
|
12031
|
-
if (!
|
|
12152
|
+
if (!existsSync14(path2))
|
|
12032
12153
|
return;
|
|
12033
12154
|
return validateReceipt(JSON.parse(readFileSync10(path2, "utf8")));
|
|
12034
12155
|
} catch {
|
|
@@ -12068,10 +12189,32 @@ var restartAgent = async (target2, run2) => {
|
|
|
12068
12189
|
throw new Error(`systemd could not restart ${service}`);
|
|
12069
12190
|
}
|
|
12070
12191
|
};
|
|
12192
|
+
var candidateUnit = (target2) => target2 === "compute" ? "forgezero-agent-candidate.service" : "forgezero-metal-agent-candidate.service";
|
|
12193
|
+
var startCandidate = async (staged, target2, run2, probe = (expected) => probeAgentCandidate(expected, DEFAULT_AGENT_CANDIDATE_READY_SOCKET)) => {
|
|
12194
|
+
selectAgentCandidate(staged);
|
|
12195
|
+
if (!await runOk(run2, "/usr/bin/systemctl", ["restart", candidateUnit(target2)])) {
|
|
12196
|
+
clearAgentCandidate(dirname8(staged.currentLink));
|
|
12197
|
+
throw new Error(`systemd could not start ${candidateUnit(target2)}`);
|
|
12198
|
+
}
|
|
12199
|
+
if (!await probe({ version: staged.version })) {
|
|
12200
|
+
await run2({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target2)] });
|
|
12201
|
+
clearAgentCandidate(dirname8(staged.currentLink));
|
|
12202
|
+
throw new Error("the candidate Agent did not prove its identity and durable state");
|
|
12203
|
+
}
|
|
12204
|
+
};
|
|
12205
|
+
var stopCandidate = async (staged, target2, run2) => {
|
|
12206
|
+
await run2({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target2)] });
|
|
12207
|
+
clearAgentCandidate(dirname8(staged.currentLink));
|
|
12208
|
+
};
|
|
12209
|
+
var socketPaths = (publicSocket = process.env.FZ_AGENT_PUBLIC_SOCKET ?? DEFAULT_SOCKET) => ({
|
|
12210
|
+
route: `${publicSocket}.backend`,
|
|
12211
|
+
active: `${publicSocket}.backend.active`,
|
|
12212
|
+
candidate: `${publicSocket}.backend.candidate`
|
|
12213
|
+
});
|
|
12071
12214
|
var targetProbe = (target2, run2) => target2 === "compute" ? () => probeAgentSocket() : async () => await runOk(run2, "/usr/bin/systemctl", ["is-active", "--quiet", "forgezero-metal-agent.service"]) && await runOk(run2, "/usr/bin/systemctl", ["is-active", "--quiet", "forgezero-metal-helper.service"]);
|
|
12072
12215
|
function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
12073
12216
|
return new Promise((resolve4) => {
|
|
12074
|
-
const socket =
|
|
12217
|
+
const socket = connect6(socketPath);
|
|
12075
12218
|
let settled = false;
|
|
12076
12219
|
let buffer = "";
|
|
12077
12220
|
const finish = (value) => {
|
|
@@ -12104,12 +12247,13 @@ function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
|
12104
12247
|
async function activateAgentRelease(staged, options = {}) {
|
|
12105
12248
|
const run2 = options.run ?? runCommand;
|
|
12106
12249
|
const target2 = options.target ?? "compute";
|
|
12107
|
-
const
|
|
12250
|
+
const paths = socketPaths(options.publicSocketPath);
|
|
12251
|
+
const probe = options.probe ?? (target2 === "compute" ? () => probeAgentSocket(paths.active) : targetProbe(target2, run2));
|
|
12108
12252
|
const now = options.now ?? Date.now;
|
|
12109
12253
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12110
12254
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12111
|
-
const previous = readJournal(journalPath,
|
|
12112
|
-
const attemptId = options.attemptId ??
|
|
12255
|
+
const previous = readJournal(journalPath, dirname8(staged.currentLink));
|
|
12256
|
+
const attemptId = options.attemptId ?? randomUUID4();
|
|
12113
12257
|
if (!ATTEMPT_ID.test(attemptId))
|
|
12114
12258
|
throw new Error("Agent update attempt ID is invalid");
|
|
12115
12259
|
const startedAtTs = now();
|
|
@@ -12131,11 +12275,18 @@ async function activateAgentRelease(staged, options = {}) {
|
|
|
12131
12275
|
writeUpdateState(journalPath, receiptPath, journal);
|
|
12132
12276
|
let selectionAttempted = false;
|
|
12133
12277
|
try {
|
|
12278
|
+
if (!options.candidatePrepared)
|
|
12279
|
+
await startCandidate(staged, target2, run2);
|
|
12280
|
+
if (target2 === "compute")
|
|
12281
|
+
switchAgentSocketRoute(paths.route, paths.candidate);
|
|
12134
12282
|
selectionAttempted = true;
|
|
12135
12283
|
selectAgentRelease(staged);
|
|
12136
12284
|
await restartAgent(target2, run2);
|
|
12137
12285
|
if (!await probe())
|
|
12138
|
-
throw new Error("the replacement Agent did not answer its
|
|
12286
|
+
throw new Error("the replacement Agent did not answer its active Vault backend");
|
|
12287
|
+
if (target2 === "compute")
|
|
12288
|
+
switchAgentSocketRoute(paths.route, paths.active);
|
|
12289
|
+
await stopCandidate(staged, target2, run2);
|
|
12139
12290
|
journal = { ...journal, outcome: "active", updatedAtTs: now(), rollbackHealthy: undefined };
|
|
12140
12291
|
writeUpdateState(journalPath, receiptPath, journal);
|
|
12141
12292
|
run2({
|
|
@@ -12153,10 +12304,13 @@ async function activateAgentRelease(staged, options = {}) {
|
|
|
12153
12304
|
restored = true;
|
|
12154
12305
|
await restartAgent(target2, run2);
|
|
12155
12306
|
rollbackHealthy = await probe();
|
|
12307
|
+
if (rollbackHealthy && target2 === "compute")
|
|
12308
|
+
switchAgentSocketRoute(paths.route, paths.active);
|
|
12156
12309
|
} catch {
|
|
12157
12310
|
rollbackHealthy = false;
|
|
12158
12311
|
}
|
|
12159
12312
|
}
|
|
12313
|
+
await stopCandidate(staged, target2, run2).catch(() => {});
|
|
12160
12314
|
const failures = failureCount + 1;
|
|
12161
12315
|
const updatedAtTs = now();
|
|
12162
12316
|
journal = {
|
|
@@ -12176,28 +12330,38 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
12176
12330
|
const root = resolve3(options.root ?? DEFAULT_AGENT_RELEASE_ROOT);
|
|
12177
12331
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12178
12332
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12333
|
+
const run2 = options.run ?? runCommand;
|
|
12179
12334
|
const journal = readJournal(journalPath, root);
|
|
12180
|
-
if (!journal)
|
|
12335
|
+
if (!journal) {
|
|
12336
|
+
for (const target2 of ["compute", "metal"]) {
|
|
12337
|
+
await run2({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target2)] });
|
|
12338
|
+
}
|
|
12339
|
+
clearAgentCandidate(root);
|
|
12181
12340
|
return;
|
|
12341
|
+
}
|
|
12182
12342
|
if (journal.outcome !== "activating") {
|
|
12343
|
+
await stopCandidate(stagedFromJournal(journal, root), journal.target, run2).catch(() => {});
|
|
12183
12344
|
writeAtomic(receiptPath, publicReceipt(journal), 416);
|
|
12184
12345
|
return publicReceipt(journal);
|
|
12185
12346
|
}
|
|
12186
12347
|
const staged = stagedFromJournal(journal, root);
|
|
12187
|
-
if (!
|
|
12348
|
+
if (!existsSync14(join8(root, journal.previousTarget))) {
|
|
12188
12349
|
throw new Error("Agent update rollback release is missing");
|
|
12189
12350
|
}
|
|
12190
|
-
const
|
|
12191
|
-
const probe = options.probe ?? targetProbe(journal.target, run2);
|
|
12351
|
+
const paths = socketPaths(options.publicSocketPath);
|
|
12352
|
+
const probe = options.probe ?? (journal.target === "compute" ? () => probeAgentSocket(paths.active) : targetProbe(journal.target, run2));
|
|
12192
12353
|
restoreAgentRelease(staged);
|
|
12193
12354
|
let rollbackHealthy = false;
|
|
12194
12355
|
let failureMessage = "activation was interrupted before its health verdict became durable";
|
|
12195
12356
|
try {
|
|
12196
12357
|
await restartAgent(journal.target, run2);
|
|
12197
12358
|
rollbackHealthy = await probe();
|
|
12359
|
+
if (rollbackHealthy && journal.target === "compute")
|
|
12360
|
+
switchAgentSocketRoute(paths.route, paths.active);
|
|
12198
12361
|
} catch (cause) {
|
|
12199
12362
|
failureMessage = `${failureMessage}; ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
12200
12363
|
}
|
|
12364
|
+
await stopCandidate(staged, journal.target, run2).catch(() => {});
|
|
12201
12365
|
const failures = journal.failureCount + 1;
|
|
12202
12366
|
const updatedAtTs = (options.now ?? Date.now)();
|
|
12203
12367
|
const recovered = {
|
|
@@ -12214,15 +12378,23 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
12214
12378
|
}
|
|
12215
12379
|
function startAgentUpdateHelper(options = {}) {
|
|
12216
12380
|
const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
|
|
12217
|
-
if (
|
|
12218
|
-
|
|
12219
|
-
mkdirSync9(
|
|
12220
|
-
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
12381
|
+
if (existsSync14(socketPath))
|
|
12382
|
+
unlinkSync11(socketPath);
|
|
12383
|
+
mkdirSync9(dirname8(socketPath), { recursive: true, mode: 488 });
|
|
12221
12384
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12222
12385
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12223
12386
|
const releaseRoot = options.root ?? DEFAULT_AGENT_RELEASE_ROOT;
|
|
12224
|
-
const
|
|
12387
|
+
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
12388
|
+
const activate = options.activate ?? ((staged, target2, attemptId) => activateAgentRelease(staged, {
|
|
12389
|
+
target: target2,
|
|
12390
|
+
attemptId,
|
|
12391
|
+
journalPath,
|
|
12392
|
+
receiptPath,
|
|
12393
|
+
now: options.now,
|
|
12394
|
+
candidatePrepared: true
|
|
12395
|
+
}));
|
|
12225
12396
|
let busy = true;
|
|
12397
|
+
let pending;
|
|
12226
12398
|
let blocked;
|
|
12227
12399
|
(options.recover ?? (() => recoverInterruptedAgentUpdate({
|
|
12228
12400
|
root: releaseRoot,
|
|
@@ -12234,7 +12406,7 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12234
12406
|
}).finally(() => {
|
|
12235
12407
|
busy = false;
|
|
12236
12408
|
});
|
|
12237
|
-
const server =
|
|
12409
|
+
const server = createServer7((socket) => {
|
|
12238
12410
|
let buffer = "";
|
|
12239
12411
|
socket.on("data", (chunk) => {
|
|
12240
12412
|
buffer += chunk.toString("utf8");
|
|
@@ -12253,16 +12425,49 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12253
12425
|
Promise.resolve().then(() => JSON.parse(line)).then(async (request) => {
|
|
12254
12426
|
if (blocked)
|
|
12255
12427
|
throw new Error(`update journal needs operator recovery: ${blocked}`);
|
|
12256
|
-
if (busy)
|
|
12257
|
-
throw new Error("another Agent update or recovery is already active");
|
|
12258
|
-
if (request.op !== "apply")
|
|
12259
|
-
throw new Error("unknown update operation");
|
|
12260
12428
|
if (request.target !== "compute" && request.target !== "metal") {
|
|
12261
12429
|
throw new Error("agent update target is invalid");
|
|
12262
12430
|
}
|
|
12263
|
-
const attemptId = request.attemptId ??
|
|
12431
|
+
const attemptId = request.attemptId ?? randomUUID4();
|
|
12264
12432
|
if (!ATTEMPT_ID.test(attemptId))
|
|
12265
12433
|
throw new Error("Agent update attempt ID is invalid");
|
|
12434
|
+
if (request.op === "commit" || request.op === "abort") {
|
|
12435
|
+
if (!pending || pending.attemptId !== attemptId || pending.target !== request.target || pending.staged.fromVersion !== request.currentVersion || pending.staged.version !== request.targetVersion) {
|
|
12436
|
+
throw new Error("Agent update handover does not match the prepared candidate");
|
|
12437
|
+
}
|
|
12438
|
+
const selected = pending;
|
|
12439
|
+
pending = undefined;
|
|
12440
|
+
if (request.op === "abort") {
|
|
12441
|
+
await stopCandidate(selected.staged, selected.target, runCommand);
|
|
12442
|
+
busy = false;
|
|
12443
|
+
const response3 = {
|
|
12444
|
+
ok: true,
|
|
12445
|
+
status: "aborted",
|
|
12446
|
+
version: selected.staged.version,
|
|
12447
|
+
attemptId
|
|
12448
|
+
};
|
|
12449
|
+
socket.end(`${JSON.stringify(response3)}
|
|
12450
|
+
`);
|
|
12451
|
+
return;
|
|
12452
|
+
}
|
|
12453
|
+
const outcome = await activate(selected.staged, selected.target, attemptId);
|
|
12454
|
+
busy = false;
|
|
12455
|
+
if (outcome?.ok === false)
|
|
12456
|
+
throw new Error(outcome.reason ?? "Agent handover failed");
|
|
12457
|
+
const response2 = {
|
|
12458
|
+
ok: true,
|
|
12459
|
+
status: "active",
|
|
12460
|
+
version: selected.staged.version,
|
|
12461
|
+
attemptId
|
|
12462
|
+
};
|
|
12463
|
+
socket.end(`${JSON.stringify(response2)}
|
|
12464
|
+
`);
|
|
12465
|
+
return;
|
|
12466
|
+
}
|
|
12467
|
+
if (request.op !== "prepare")
|
|
12468
|
+
throw new Error("unknown update operation");
|
|
12469
|
+
if (busy)
|
|
12470
|
+
throw new Error("another Agent update or recovery is already active");
|
|
12266
12471
|
const prior = readJournal(journalPath, releaseRoot);
|
|
12267
12472
|
const now = (options.now ?? Date.now)();
|
|
12268
12473
|
if (prior?.targetVersion === request.release.version && (prior.outcome === "rolled-back" || prior.outcome === "failed") && (prior.retryAfterTs ?? 0) > now)
|
|
@@ -12273,17 +12478,23 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12273
12478
|
currentVersion: request.currentVersion,
|
|
12274
12479
|
root: releaseRoot
|
|
12275
12480
|
});
|
|
12276
|
-
|
|
12481
|
+
await startCandidate(staged, request.target, runCommand);
|
|
12482
|
+
pending = { staged, target: request.target, attemptId };
|
|
12483
|
+
ownsBusy = false;
|
|
12484
|
+
setTimer(() => {
|
|
12485
|
+
if (pending?.attemptId !== attemptId)
|
|
12486
|
+
return;
|
|
12487
|
+
const expired = pending;
|
|
12488
|
+
pending = undefined;
|
|
12489
|
+
stopCandidate(expired.staged, expired.target, runCommand).finally(() => {
|
|
12490
|
+
busy = false;
|
|
12491
|
+
});
|
|
12492
|
+
}, 180000);
|
|
12493
|
+
const response = { ok: true, status: "prepared", version: staged.version, attemptId };
|
|
12277
12494
|
socket.end(`${JSON.stringify(response)}
|
|
12278
12495
|
`);
|
|
12279
|
-
setTimer(() => void activate(staged, request.target, attemptId).catch((cause) => {
|
|
12280
|
-
blocked = cause instanceof Error ? cause.message : String(cause);
|
|
12281
|
-
}).finally(() => {
|
|
12282
|
-
busy = false;
|
|
12283
|
-
}), 100);
|
|
12284
|
-
ownsBusy = false;
|
|
12285
12496
|
}).catch((cause) => {
|
|
12286
|
-
if (ownsBusy)
|
|
12497
|
+
if (ownsBusy && !pending)
|
|
12287
12498
|
busy = false;
|
|
12288
12499
|
const response = {
|
|
12289
12500
|
ok: false,
|
|
@@ -12295,12 +12506,12 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12295
12506
|
});
|
|
12296
12507
|
socket.on("error", () => socket.destroy());
|
|
12297
12508
|
});
|
|
12298
|
-
server.listen(socketPath, () =>
|
|
12509
|
+
server.listen(socketPath, () => chmodSync14(socketPath, 432));
|
|
12299
12510
|
return server;
|
|
12300
12511
|
}
|
|
12301
12512
|
function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
|
|
12302
12513
|
return new Promise((resolve4, reject) => {
|
|
12303
|
-
const socket =
|
|
12514
|
+
const socket = connect6(socketPath, () => socket.write(`${JSON.stringify(request)}
|
|
12304
12515
|
`));
|
|
12305
12516
|
let buffer = "";
|
|
12306
12517
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -12325,7 +12536,7 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
|
|
|
12325
12536
|
}
|
|
12326
12537
|
|
|
12327
12538
|
// src/agent-heartbeat.ts
|
|
12328
|
-
import { existsSync as
|
|
12539
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11, statfsSync as statfsSync2 } from "fs";
|
|
12329
12540
|
import { cpus, freemem, loadavg, totalmem } from "os";
|
|
12330
12541
|
function readAgentHostMetrics() {
|
|
12331
12542
|
const filesystem = statfsSync2("/");
|
|
@@ -12370,7 +12581,7 @@ function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = rea
|
|
|
12370
12581
|
runtimeCapabilities: {
|
|
12371
12582
|
native: true,
|
|
12372
12583
|
ociRunc: ubuntuX64,
|
|
12373
|
-
kataQemuSnp: ubuntuX64 && osVersion === "26.04" &&
|
|
12584
|
+
kataQemuSnp: ubuntuX64 && osVersion === "26.04" && existsSync15("/dev/kvm") && existsSync15("/dev/sev") && enabled("/sys/module/kvm_amd/parameters/sev") && enabled("/sys/module/kvm_amd/parameters/sev_snp")
|
|
12374
12585
|
},
|
|
12375
12586
|
capacity: {
|
|
12376
12587
|
logicalCpu: Math.max(1, metrics.logicalCpu),
|
|
@@ -12422,18 +12633,24 @@ async function heartbeatAgentOnce(options) {
|
|
|
12422
12633
|
});
|
|
12423
12634
|
return response;
|
|
12424
12635
|
}
|
|
12425
|
-
let
|
|
12636
|
+
let candidatePrepared = false;
|
|
12637
|
+
let drained = false;
|
|
12426
12638
|
try {
|
|
12427
|
-
|
|
12428
|
-
|
|
12639
|
+
const update2 = options.applyUpdate ?? ((phase, next, current2, attemptId) => requestAgentUpdate(phase === "prepare" ? { op: "prepare", target: options.updateTarget ?? "compute", release: next, currentVersion: current2, attemptId } : {
|
|
12640
|
+
op: phase,
|
|
12641
|
+
target: options.updateTarget ?? "compute",
|
|
12642
|
+
currentVersion: current2,
|
|
12643
|
+
targetVersion: next.version,
|
|
12644
|
+
attemptId
|
|
12645
|
+
}));
|
|
12429
12646
|
const apply = async () => {
|
|
12430
|
-
const
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12647
|
+
const prepared = await update2("prepare", release, observation.version, desired.attemptId);
|
|
12648
|
+
if (!prepared.ok)
|
|
12649
|
+
throw new AgentUpdateRefusedError(`agent update refused: ${prepared.error.message}`);
|
|
12650
|
+
candidatePrepared = true;
|
|
12651
|
+
await options.prepareUpdate?.(release);
|
|
12652
|
+
drained = true;
|
|
12653
|
+
const applied = await update2("commit", release, observation.version, desired.attemptId);
|
|
12437
12654
|
if (!applied.ok)
|
|
12438
12655
|
throw new AgentUpdateRefusedError(`agent update refused: ${applied.error.message}`);
|
|
12439
12656
|
if (applied.attemptId !== desired.attemptId) {
|
|
@@ -12446,9 +12663,18 @@ async function heartbeatAgentOnce(options) {
|
|
|
12446
12663
|
} else {
|
|
12447
12664
|
await apply();
|
|
12448
12665
|
}
|
|
12449
|
-
options.onEvent?.("update-
|
|
12666
|
+
options.onEvent?.("update-active", { from: observation.version, to: release.version });
|
|
12450
12667
|
} catch (cause) {
|
|
12451
|
-
if (
|
|
12668
|
+
if (candidatePrepared && !drained) {
|
|
12669
|
+
await (options.applyUpdate ?? ((phase, next, current2, attemptId) => requestAgentUpdate({
|
|
12670
|
+
op: phase,
|
|
12671
|
+
target: options.updateTarget ?? "compute",
|
|
12672
|
+
currentVersion: current2,
|
|
12673
|
+
targetVersion: next.version,
|
|
12674
|
+
attemptId
|
|
12675
|
+
})))("abort", release, observation.version, desired.attemptId).catch(() => {});
|
|
12676
|
+
}
|
|
12677
|
+
if (drained)
|
|
12452
12678
|
await options.recoverUpdate?.(cause);
|
|
12453
12679
|
throw cause;
|
|
12454
12680
|
}
|
|
@@ -12489,14 +12715,14 @@ function startAgentHeartbeat(options) {
|
|
|
12489
12715
|
}
|
|
12490
12716
|
|
|
12491
12717
|
// src/software-helper.ts
|
|
12492
|
-
import { chmodSync as
|
|
12493
|
-
import { connect as
|
|
12494
|
-
import { dirname as
|
|
12718
|
+
import { chmodSync as chmodSync15, existsSync as existsSync18, mkdirSync as mkdirSync13, unlinkSync as unlinkSync12 } from "fs";
|
|
12719
|
+
import { connect as connect7, createServer as createServer8 } from "net";
|
|
12720
|
+
import { dirname as dirname12 } from "path";
|
|
12495
12721
|
|
|
12496
12722
|
// src/service-supervisor.ts
|
|
12497
12723
|
import { createHash as createHash9 } from "crypto";
|
|
12498
|
-
import { existsSync as
|
|
12499
|
-
import { dirname as
|
|
12724
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync10, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
12725
|
+
import { dirname as dirname9, join as join9, resolve as resolve4, sep as sep2 } from "path";
|
|
12500
12726
|
var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
|
|
12501
12727
|
var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
|
|
12502
12728
|
var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
|
|
@@ -12506,17 +12732,17 @@ var statePath = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
|
|
|
12506
12732
|
var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
|
|
12507
12733
|
var defaultHost = {
|
|
12508
12734
|
write(path2, content, mode) {
|
|
12509
|
-
mkdirSync10(
|
|
12735
|
+
mkdirSync10(dirname9(path2), { recursive: true, mode: 493 });
|
|
12510
12736
|
const next = `${path2}.next`;
|
|
12511
12737
|
writeFileSync11(next, content, { mode });
|
|
12512
|
-
|
|
12738
|
+
renameSync10(next, path2);
|
|
12513
12739
|
},
|
|
12514
12740
|
read: (path2) => readFileSync12(path2, "utf8"),
|
|
12515
|
-
exists:
|
|
12516
|
-
list: (path2) =>
|
|
12741
|
+
exists: existsSync16,
|
|
12742
|
+
list: (path2) => existsSync16(path2) ? readdirSync2(path2) : [],
|
|
12517
12743
|
realpath: realpathSync2,
|
|
12518
12744
|
mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
|
|
12519
|
-
remove: (path2) =>
|
|
12745
|
+
remove: (path2) => rmSync8(path2, { force: true }),
|
|
12520
12746
|
async exec(argv2) {
|
|
12521
12747
|
const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
|
|
12522
12748
|
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
@@ -12712,20 +12938,20 @@ async function activateSupervisedService(request, host = defaultHost) {
|
|
|
12712
12938
|
|
|
12713
12939
|
// src/container-supervisor.ts
|
|
12714
12940
|
import { createHash as createHash10 } from "crypto";
|
|
12715
|
-
import { existsSync as
|
|
12716
|
-
import { dirname as
|
|
12941
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync13, readdirSync as readdirSync3, realpathSync as realpathSync3, renameSync as renameSync11, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
12942
|
+
import { dirname as dirname10, resolve as resolve5, sep as sep3 } from "path";
|
|
12717
12943
|
var defaultHost2 = {
|
|
12718
12944
|
realpath: realpathSync3,
|
|
12719
|
-
exists:
|
|
12945
|
+
exists: existsSync17,
|
|
12720
12946
|
read: (path2) => readFileSync13(path2, "utf8"),
|
|
12721
12947
|
write(path2, content, mode) {
|
|
12722
|
-
mkdirSync11(
|
|
12948
|
+
mkdirSync11(dirname10(path2), { recursive: true, mode: 493 });
|
|
12723
12949
|
const next = `${path2}.next`;
|
|
12724
12950
|
writeFileSync12(next, content, { mode });
|
|
12725
|
-
|
|
12951
|
+
renameSync11(next, path2);
|
|
12726
12952
|
},
|
|
12727
|
-
remove: (path2) =>
|
|
12728
|
-
list: (path2) =>
|
|
12953
|
+
remove: (path2) => rmSync9(path2, { force: true }),
|
|
12954
|
+
list: (path2) => existsSync17(path2) ? readdirSync3(path2) : [],
|
|
12729
12955
|
mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
|
|
12730
12956
|
async exec(argv2) {
|
|
12731
12957
|
const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" } });
|
|
@@ -13014,9 +13240,9 @@ async function activateContainer(requestValue, host = defaultHost2) {
|
|
|
13014
13240
|
|
|
13015
13241
|
// src/deployment-connectivity.ts
|
|
13016
13242
|
import { createHash as createHash11 } from "crypto";
|
|
13017
|
-
import { mkdirSync as mkdirSync12, renameSync as
|
|
13243
|
+
import { mkdirSync as mkdirSync12, renameSync as renameSync12, writeFileSync as writeFileSync13 } from "fs";
|
|
13018
13244
|
import { createConnection as createConnection2 } from "net";
|
|
13019
|
-
import { dirname as
|
|
13245
|
+
import { dirname as dirname11 } from "path";
|
|
13020
13246
|
var TOKEN2 = /^[A-Za-z0-9._-]{40,16384}$/;
|
|
13021
13247
|
var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
|
|
13022
13248
|
var HOSTNAME2 = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -13029,7 +13255,7 @@ var checked7 = async (host, argv2, label) => {
|
|
|
13029
13255
|
return result.output.trim();
|
|
13030
13256
|
};
|
|
13031
13257
|
async function sealWithSystemd(name, path2, value) {
|
|
13032
|
-
mkdirSync12(
|
|
13258
|
+
mkdirSync12(dirname11(path2), { recursive: true, mode: 448 });
|
|
13033
13259
|
const next = `${path2}.next`;
|
|
13034
13260
|
const child = Bun.spawn(["/usr/bin/systemd-creds", "encrypt", `--name=${name}`, "-", next], {
|
|
13035
13261
|
stdin: "pipe",
|
|
@@ -13045,15 +13271,15 @@ async function sealWithSystemd(name, path2, value) {
|
|
|
13045
13271
|
]);
|
|
13046
13272
|
if (exitCode !== 0)
|
|
13047
13273
|
throw new Error(`systemd credential sealing failed: ${`${stdout}${stderr}`.replaceAll(value, "[REDACTED]").slice(0, 512)}`);
|
|
13048
|
-
|
|
13274
|
+
renameSync12(next, path2);
|
|
13049
13275
|
}
|
|
13050
13276
|
var defaultHost3 = {
|
|
13051
13277
|
seal: sealWithSystemd,
|
|
13052
13278
|
write(path2, content, mode) {
|
|
13053
|
-
mkdirSync12(
|
|
13279
|
+
mkdirSync12(dirname11(path2), { recursive: true, mode: 493 });
|
|
13054
13280
|
const next = `${path2}.next`;
|
|
13055
13281
|
writeFileSync13(next, content, { mode });
|
|
13056
|
-
|
|
13282
|
+
renameSync12(next, path2);
|
|
13057
13283
|
},
|
|
13058
13284
|
async exec(argv2) {
|
|
13059
13285
|
const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" } });
|
|
@@ -13292,9 +13518,9 @@ var MAX_REQUEST_BYTES6 = 128 * 1024;
|
|
|
13292
13518
|
var MAX_PENDING_REQUESTS = 128;
|
|
13293
13519
|
function startSoftwareHelper(options = {}) {
|
|
13294
13520
|
const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
|
|
13295
|
-
if (
|
|
13296
|
-
|
|
13297
|
-
mkdirSync13(
|
|
13521
|
+
if (existsSync18(socketPath))
|
|
13522
|
+
unlinkSync12(socketPath);
|
|
13523
|
+
mkdirSync13(dirname12(socketPath), { recursive: true, mode: 488 });
|
|
13298
13524
|
const ensure = options.ensure ?? ensureSoftwareRequirements;
|
|
13299
13525
|
const activate = options.activate ?? activateSupervisedService;
|
|
13300
13526
|
const buildContainer = options.buildContainer ?? buildContainerImage;
|
|
@@ -13302,7 +13528,7 @@ function startSoftwareHelper(options = {}) {
|
|
|
13302
13528
|
const applyConnectivity = options.applyConnectivity ?? applyDeploymentConnectivity;
|
|
13303
13529
|
let tail = Promise.resolve();
|
|
13304
13530
|
let pending = 0;
|
|
13305
|
-
const server =
|
|
13531
|
+
const server = createServer8((socket) => {
|
|
13306
13532
|
let buffer = "";
|
|
13307
13533
|
socket.on("data", (chunk) => {
|
|
13308
13534
|
buffer += chunk.toString("utf8");
|
|
@@ -13381,12 +13607,12 @@ function startSoftwareHelper(options = {}) {
|
|
|
13381
13607
|
});
|
|
13382
13608
|
socket.on("error", () => socket.destroy());
|
|
13383
13609
|
});
|
|
13384
|
-
server.listen(socketPath, () =>
|
|
13610
|
+
server.listen(socketPath, () => chmodSync15(socketPath, 432));
|
|
13385
13611
|
return server;
|
|
13386
13612
|
}
|
|
13387
13613
|
function requestContainer(op, request, socketPath, timeoutMs) {
|
|
13388
13614
|
return new Promise((resolve6, reject) => {
|
|
13389
|
-
const socket =
|
|
13615
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op, request })}
|
|
13390
13616
|
`));
|
|
13391
13617
|
let buffer = "";
|
|
13392
13618
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -13424,7 +13650,7 @@ function requestDeploymentConnectivity(request, socketPath = DEFAULT_SOFTWARE_HE
|
|
|
13424
13650
|
}
|
|
13425
13651
|
function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 10 * 60000) {
|
|
13426
13652
|
return new Promise((resolve6, reject) => {
|
|
13427
|
-
const socket =
|
|
13653
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
|
|
13428
13654
|
`));
|
|
13429
13655
|
let buffer = "";
|
|
13430
13656
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -13453,7 +13679,7 @@ function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_
|
|
|
13453
13679
|
function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
|
|
13454
13680
|
validateSoftwareRequirements(requirements);
|
|
13455
13681
|
return new Promise((resolve6, reject) => {
|
|
13456
|
-
const socket =
|
|
13682
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op: "ensure", requirements })}
|
|
13457
13683
|
`));
|
|
13458
13684
|
let buffer = "";
|
|
13459
13685
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -14413,19 +14639,19 @@ var METAL_SYSTEMD_CREDENTIALS = {
|
|
|
14413
14639
|
|
|
14414
14640
|
// src/platform-bootstrap-runtime.ts
|
|
14415
14641
|
import {
|
|
14416
|
-
chmodSync as
|
|
14642
|
+
chmodSync as chmodSync16,
|
|
14417
14643
|
chownSync,
|
|
14418
14644
|
copyFileSync as copyFileSync2,
|
|
14419
|
-
existsSync as
|
|
14420
|
-
lstatSync as
|
|
14645
|
+
existsSync as existsSync19,
|
|
14646
|
+
lstatSync as lstatSync4,
|
|
14421
14647
|
mkdirSync as mkdirSync14,
|
|
14422
14648
|
readFileSync as readFileSync14,
|
|
14423
14649
|
readdirSync as readdirSync4,
|
|
14424
14650
|
realpathSync as realpathSync5,
|
|
14425
|
-
renameSync as
|
|
14426
|
-
rmSync as
|
|
14651
|
+
renameSync as renameSync13,
|
|
14652
|
+
rmSync as rmSync10,
|
|
14427
14653
|
statSync as statSync4,
|
|
14428
|
-
symlinkSync as
|
|
14654
|
+
symlinkSync as symlinkSync6,
|
|
14429
14655
|
writeFileSync as writeFileSync14
|
|
14430
14656
|
} from "fs";
|
|
14431
14657
|
import { join as join10 } from "path";
|
|
@@ -14480,21 +14706,21 @@ var platformExec = async (argv2) => {
|
|
|
14480
14706
|
};
|
|
14481
14707
|
var replaceLink = (path2, target2) => {
|
|
14482
14708
|
const pending = `${path2}.next`;
|
|
14483
|
-
|
|
14709
|
+
rmSync10(pending, { force: true });
|
|
14484
14710
|
if (!target2) {
|
|
14485
|
-
|
|
14711
|
+
rmSync10(path2, { force: true });
|
|
14486
14712
|
return;
|
|
14487
14713
|
}
|
|
14488
|
-
|
|
14489
|
-
|
|
14714
|
+
symlinkSync6(target2, pending);
|
|
14715
|
+
renameSync13(pending, path2);
|
|
14490
14716
|
};
|
|
14491
14717
|
var secureRelease = (path2, uid, gid) => {
|
|
14492
14718
|
const visit = (current2) => {
|
|
14493
|
-
const metadata =
|
|
14719
|
+
const metadata = lstatSync4(current2);
|
|
14494
14720
|
if (metadata.isSymbolicLink())
|
|
14495
14721
|
throw new Error("release contains a symbolic link");
|
|
14496
14722
|
chownSync(current2, uid, gid);
|
|
14497
|
-
|
|
14723
|
+
chmodSync16(current2, metadata.isDirectory() ? 365 : 292);
|
|
14498
14724
|
if (metadata.isDirectory())
|
|
14499
14725
|
for (const name of readdirSync4(current2))
|
|
14500
14726
|
visit(join10(current2, name));
|
|
@@ -14519,7 +14745,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14519
14745
|
const slots = join10(normalized.root, "slots");
|
|
14520
14746
|
mkdirSync14(slots, { recursive: true, mode: 493 });
|
|
14521
14747
|
const slotFile = join10(normalized.root, ".forge-slot");
|
|
14522
|
-
const previousSlot =
|
|
14748
|
+
const previousSlot = existsSync19(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
|
|
14523
14749
|
const target2 = previousSlot === "blue" ? "green" : "blue";
|
|
14524
14750
|
const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
|
|
14525
14751
|
const targetLink = join10(slots, target2);
|
|
@@ -14559,25 +14785,25 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14559
14785
|
}
|
|
14560
14786
|
const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
|
|
14561
14787
|
const backup = `${upstream}.forgezero-backup`;
|
|
14562
|
-
if (
|
|
14788
|
+
if (existsSync19(upstream))
|
|
14563
14789
|
copyFileSync2(upstream, backup);
|
|
14564
14790
|
else
|
|
14565
|
-
|
|
14791
|
+
rmSync10(backup, { force: true });
|
|
14566
14792
|
writeFileSync14(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
|
|
14567
14793
|
`, { mode: 420 });
|
|
14568
14794
|
const test = await exec(["/usr/sbin/nginx", "-t"]);
|
|
14569
14795
|
const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
|
|
14570
14796
|
if (reload.exitCode !== 0) {
|
|
14571
|
-
if (
|
|
14572
|
-
|
|
14797
|
+
if (existsSync19(backup))
|
|
14798
|
+
renameSync13(backup, upstream);
|
|
14573
14799
|
else
|
|
14574
|
-
|
|
14800
|
+
rmSync10(upstream, { force: true });
|
|
14575
14801
|
await exec(["/usr/sbin/nginx", "-t"]);
|
|
14576
14802
|
await exec(["/usr/sbin/nginx", "-s", "reload"]);
|
|
14577
14803
|
await stopTarget();
|
|
14578
14804
|
throw new Error("nginx refused the promoted upstream");
|
|
14579
14805
|
}
|
|
14580
|
-
|
|
14806
|
+
rmSync10(backup, { force: true });
|
|
14581
14807
|
writeFileSync14(slotFile, `${target2}
|
|
14582
14808
|
`, { mode: 420 });
|
|
14583
14809
|
if (previousSlot && previousSlot !== target2) {
|
|
@@ -14587,12 +14813,12 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14587
14813
|
const old = readdirSync4(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join10(releases, entry.name)).sort((left, right) => statSync4(right).mtimeMs - statSync4(left).mtimeMs).slice(normalized.keepReleases);
|
|
14588
14814
|
for (const path2 of old)
|
|
14589
14815
|
if (path2 !== release)
|
|
14590
|
-
|
|
14816
|
+
rmSync10(path2, { recursive: true, force: true });
|
|
14591
14817
|
return { release, slot: target2 };
|
|
14592
14818
|
}
|
|
14593
14819
|
|
|
14594
14820
|
// src/recovery-host.ts
|
|
14595
|
-
import { existsSync as
|
|
14821
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, statSync as statSync5 } from "fs";
|
|
14596
14822
|
import { hostname } from "os";
|
|
14597
14823
|
import { join as join11 } from "path";
|
|
14598
14824
|
var execute2 = async (argv2) => {
|
|
@@ -14618,7 +14844,7 @@ var exactState = (path2, expected) => {
|
|
|
14618
14844
|
throw new Error(`state mismatch: ${expected}`);
|
|
14619
14845
|
};
|
|
14620
14846
|
var nonEmpty = (path2) => {
|
|
14621
|
-
if (!
|
|
14847
|
+
if (!existsSync20(path2) || !statSync5(path2).isFile() || statSync5(path2).size < 1)
|
|
14622
14848
|
throw new Error(`required file missing: ${path2}`);
|
|
14623
14849
|
};
|
|
14624
14850
|
var option = (args, name) => {
|
|
@@ -14714,20 +14940,20 @@ async function runRecoveryHost(args, run2 = execute2) {
|
|
|
14714
14940
|
}
|
|
14715
14941
|
|
|
14716
14942
|
// src/platform-fleet-verification.ts
|
|
14717
|
-
import { lstatSync as
|
|
14943
|
+
import { lstatSync as lstatSync7 } from "fs";
|
|
14718
14944
|
|
|
14719
14945
|
// src/bootstrap.ts
|
|
14720
14946
|
import {
|
|
14721
|
-
chmodSync as
|
|
14722
|
-
existsSync as
|
|
14723
|
-
lstatSync as
|
|
14947
|
+
chmodSync as chmodSync18,
|
|
14948
|
+
existsSync as existsSync22,
|
|
14949
|
+
lstatSync as lstatSync6,
|
|
14724
14950
|
mkdirSync as mkdirSync16,
|
|
14725
14951
|
readFileSync as readFileSync17,
|
|
14726
|
-
renameSync as
|
|
14727
|
-
rmSync as
|
|
14952
|
+
renameSync as renameSync15,
|
|
14953
|
+
rmSync as rmSync12,
|
|
14728
14954
|
writeFileSync as writeFileSync16
|
|
14729
14955
|
} from "fs";
|
|
14730
|
-
import { dirname as
|
|
14956
|
+
import { dirname as dirname14 } from "path";
|
|
14731
14957
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14732
14958
|
|
|
14733
14959
|
// src/provision.ts
|
|
@@ -14784,6 +15010,7 @@ var LIFECYCLE_GROUP = "forgezero-lifecycle";
|
|
|
14784
15010
|
var DEPLOYMENT_RUNNER_UNIT_PATH = "/etc/systemd/system/forgezero-deploy-runner.service";
|
|
14785
15011
|
var AGENT_SOCKET_UNIT_PATH = "/etc/systemd/system/forgezero-agent.socket";
|
|
14786
15012
|
var AGENT_SOCKET_PROXY_UNIT_PATH = "/etc/systemd/system/forgezero-agent-proxy.service";
|
|
15013
|
+
var AGENT_CANDIDATE_UNIT_PATH = "/etc/systemd/system/forgezero-agent-candidate.service";
|
|
14787
15014
|
var DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
|
|
14788
15015
|
var ENROLMENT_UNIT_PATH = "/etc/systemd/system/forgezero-agent-enrol.service";
|
|
14789
15016
|
var LIFECYCLE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-lifecycle-helper.service";
|
|
@@ -14895,6 +15122,7 @@ Type=simple
|
|
|
14895
15122
|
User=root
|
|
14896
15123
|
Group=${AGENT_UPDATE_GROUP}
|
|
14897
15124
|
Environment=FZ_AGENT_UPDATE_SOCKET=${DEFAULT_AGENT_UPDATE_SOCKET}
|
|
15125
|
+
Environment=FZ_AGENT_PUBLIC_SOCKET=${systemdPath(options.socketPath, "agent socket")}
|
|
14898
15126
|
ExecStart=${bin} update-helper
|
|
14899
15127
|
Restart=always
|
|
14900
15128
|
RestartSec=2
|
|
@@ -14939,13 +15167,11 @@ WantedBy=sockets.target
|
|
|
14939
15167
|
`;
|
|
14940
15168
|
}
|
|
14941
15169
|
function agentSocketProxyUnit(options) {
|
|
14942
|
-
const backend =
|
|
15170
|
+
const backend = agentRoutingSocketPath(options.socketPath);
|
|
14943
15171
|
const user = options.user ?? "forgezero";
|
|
14944
15172
|
return `[Unit]
|
|
14945
15173
|
Description=ForgeZero application Vault socket proxy
|
|
14946
15174
|
Documentation=https://www.forgezero.net/docs/agent
|
|
14947
|
-
Requires=forgezero-agent.service
|
|
14948
|
-
After=forgezero-agent.service
|
|
14949
15175
|
|
|
14950
15176
|
[Service]
|
|
14951
15177
|
User=${user}
|
|
@@ -14966,12 +15192,26 @@ RestrictAddressFamilies=AF_UNIX
|
|
|
14966
15192
|
`;
|
|
14967
15193
|
}
|
|
14968
15194
|
function agentBackendSocketPath(publicSocketPath) {
|
|
15195
|
+
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
15196
|
+
const backend = `${socket}.backend.active`;
|
|
15197
|
+
if (Buffer.byteLength(backend) > 100)
|
|
15198
|
+
throw new Error("agent socket path is too long for a Unix socket");
|
|
15199
|
+
return backend;
|
|
15200
|
+
}
|
|
15201
|
+
function agentRoutingSocketPath(publicSocketPath) {
|
|
14969
15202
|
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
14970
15203
|
const backend = `${socket}.backend`;
|
|
14971
15204
|
if (Buffer.byteLength(backend) > 100)
|
|
14972
15205
|
throw new Error("agent socket path is too long for a Unix socket");
|
|
14973
15206
|
return backend;
|
|
14974
15207
|
}
|
|
15208
|
+
function agentCandidateSocketPath(publicSocketPath) {
|
|
15209
|
+
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
15210
|
+
const backend = `${socket}.backend.candidate`;
|
|
15211
|
+
if (Buffer.byteLength(backend) > 100)
|
|
15212
|
+
throw new Error("agent socket path is too long for a Unix socket");
|
|
15213
|
+
return backend;
|
|
15214
|
+
}
|
|
14975
15215
|
var systemdPath = (value, label) => {
|
|
14976
15216
|
if (!value || !/^\/[A-Za-z0-9._@/-]+$/.test(value))
|
|
14977
15217
|
throw new Error(`invalid ${label} path`);
|
|
@@ -15255,7 +15495,11 @@ function agentUnit(options) {
|
|
|
15255
15495
|
}
|
|
15256
15496
|
const environment = [
|
|
15257
15497
|
"NODE_ENV=production",
|
|
15258
|
-
`FZ_SOCKET_PATH=${agentBackendSocketPath(options.socketPath)}`,
|
|
15498
|
+
`FZ_SOCKET_PATH=${options.backendSocketPath ?? agentBackendSocketPath(options.socketPath)}`,
|
|
15499
|
+
`FZ_AGENT_PUBLIC_SOCKET=${options.socketPath}`,
|
|
15500
|
+
`FZ_AGENT_ROUTE_SOCKET=${agentRoutingSocketPath(options.socketPath)}`,
|
|
15501
|
+
options.handoverCandidate ? "FZ_AGENT_HANDOVER_CANDIDATE=true" : null,
|
|
15502
|
+
options.handoverCandidate ? `FZ_AGENT_HANDOVER_READY_SOCKET=${DEFAULT_AGENT_CANDIDATE_READY_SOCKET}` : null,
|
|
15259
15503
|
`FZ_CONTROL_SOCKET=${controlSocketPath}`,
|
|
15260
15504
|
`FZ_SEED_CREDENTIAL=agent-seed`,
|
|
15261
15505
|
`FZ_AGENT_MODE=${options.mode}`,
|
|
@@ -15392,6 +15636,26 @@ ${deploymentWrites}
|
|
|
15392
15636
|
WantedBy=multi-user.target
|
|
15393
15637
|
`;
|
|
15394
15638
|
}
|
|
15639
|
+
function agentCandidateUnit(options) {
|
|
15640
|
+
return agentUnit({
|
|
15641
|
+
...options,
|
|
15642
|
+
binPath: `${DEFAULT_AGENT_RELEASE_ROOT}/candidate/dist/fz-agent.js`,
|
|
15643
|
+
backendSocketPath: agentCandidateSocketPath(options.socketPath),
|
|
15644
|
+
handoverCandidate: true,
|
|
15645
|
+
gitCredentialPath: undefined,
|
|
15646
|
+
deploymentCredentials: {},
|
|
15647
|
+
bootstrapSshCredentialPath: undefined,
|
|
15648
|
+
bootstrapSshPublicKeyPath: undefined,
|
|
15649
|
+
pullBootstrap: false,
|
|
15650
|
+
pullDeployments: false,
|
|
15651
|
+
pullMigrations: false,
|
|
15652
|
+
lifecycleProfilePath: undefined,
|
|
15653
|
+
bootstrapTargetTelemetryEndpoint: undefined,
|
|
15654
|
+
repository: undefined,
|
|
15655
|
+
bootstrapBundlePath: undefined,
|
|
15656
|
+
bootstrapBundleManifestPath: undefined
|
|
15657
|
+
}).replace("Description=ForgeZero node agent", "Description=ForgeZero candidate node agent");
|
|
15658
|
+
}
|
|
15395
15659
|
var renderOperation = (operation) => {
|
|
15396
15660
|
if (operation.kind === "commands")
|
|
15397
15661
|
return operation.commands.map(({ argv: argv2 }) => argv2.join(" ")).join(`
|
|
@@ -15527,6 +15791,7 @@ function planProvision(options) {
|
|
|
15527
15791
|
auxiliaryUnits: [
|
|
15528
15792
|
{ path: AGENT_SOCKET_UNIT_PATH, unit: agentSocketUnit(options) },
|
|
15529
15793
|
{ path: AGENT_SOCKET_PROXY_UNIT_PATH, unit: agentSocketProxyUnit(options) },
|
|
15794
|
+
{ path: AGENT_CANDIDATE_UNIT_PATH, unit: agentCandidateUnit(options) },
|
|
15530
15795
|
{ path: AGENT_UPDATE_HELPER_UNIT_PATH, unit: agentUpdateHelperUnit(options) },
|
|
15531
15796
|
...options.enforceEgress ? [
|
|
15532
15797
|
{ path: AGENT_EGRESS_UNIT_PATH, unit: agentEgressUnit(options) }
|
|
@@ -15658,19 +15923,19 @@ function planProvision(options) {
|
|
|
15658
15923
|
// src/cli/agent-install.ts
|
|
15659
15924
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
15660
15925
|
import {
|
|
15661
|
-
chmodSync as
|
|
15926
|
+
chmodSync as chmodSync17,
|
|
15662
15927
|
copyFileSync as copyFileSync3,
|
|
15663
|
-
existsSync as
|
|
15664
|
-
lstatSync as
|
|
15928
|
+
existsSync as existsSync21,
|
|
15929
|
+
lstatSync as lstatSync5,
|
|
15665
15930
|
mkdirSync as mkdirSync15,
|
|
15666
15931
|
readFileSync as readFileSync16,
|
|
15667
15932
|
realpathSync as realpathSync6,
|
|
15668
|
-
renameSync as
|
|
15669
|
-
rmSync as
|
|
15670
|
-
symlinkSync as
|
|
15933
|
+
renameSync as renameSync14,
|
|
15934
|
+
rmSync as rmSync11,
|
|
15935
|
+
symlinkSync as symlinkSync7,
|
|
15671
15936
|
writeFileSync as writeFileSync15
|
|
15672
15937
|
} from "fs";
|
|
15673
|
-
import { dirname as
|
|
15938
|
+
import { dirname as dirname13 } from "path";
|
|
15674
15939
|
async function readCapabilities(run2) {
|
|
15675
15940
|
const answers = {};
|
|
15676
15941
|
const checks = Object.entries(CAPABILITY_CHECKS);
|
|
@@ -15728,51 +15993,51 @@ var runProvisionOperation = async (operation) => {
|
|
|
15728
15993
|
if (operation.kind === "install-runtime") {
|
|
15729
15994
|
const release = `/opt/forgezero/agent/versions/${operation.version}`;
|
|
15730
15995
|
mkdirSync15(`${release}/dist`, { recursive: true, mode: 493 });
|
|
15731
|
-
mkdirSync15(
|
|
15996
|
+
mkdirSync15(dirname13(operation.binary), { recursive: true, mode: 493 });
|
|
15732
15997
|
copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
|
|
15733
|
-
|
|
15734
|
-
const gitSshSource = `${
|
|
15735
|
-
if (!
|
|
15998
|
+
chmodSync17(`${release}/dist/fz-agent.js`, 493);
|
|
15999
|
+
const gitSshSource = `${dirname13(operation.source)}/fz-git-ssh.js`;
|
|
16000
|
+
if (!existsSync21(gitSshSource))
|
|
15736
16001
|
return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
|
|
15737
16002
|
copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
|
|
15738
|
-
|
|
16003
|
+
chmodSync17(`${release}/dist/fz-git-ssh.js`, 493);
|
|
15739
16004
|
const pending = "/opt/forgezero/agent/current.next";
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
16005
|
+
rmSync11(pending, { force: true });
|
|
16006
|
+
symlinkSync7(`versions/${operation.version}`, pending);
|
|
16007
|
+
renameSync14(pending, "/opt/forgezero/agent/current");
|
|
16008
|
+
rmSync11(operation.binary, { force: true });
|
|
16009
|
+
symlinkSync7("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
|
|
15745
16010
|
const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
|
|
15746
|
-
|
|
15747
|
-
|
|
16011
|
+
rmSync11(gitSshBinary, { force: true });
|
|
16012
|
+
symlinkSync7("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
|
|
15748
16013
|
return { stdout: "", exitCode: 0 };
|
|
15749
16014
|
}
|
|
15750
16015
|
if (operation.kind === "ensure-seed") {
|
|
15751
|
-
if (
|
|
16016
|
+
if (existsSync21(operation.credential) && lstatSync5(operation.credential).size > 0)
|
|
15752
16017
|
return { stdout: "", exitCode: 0 };
|
|
15753
16018
|
const seed = randomBytes6(32).toString("base64url");
|
|
15754
16019
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
|
|
15755
16020
|
if (result.exitCode === 0)
|
|
15756
|
-
|
|
16021
|
+
chmodSync17(operation.credential, 256);
|
|
15757
16022
|
return result;
|
|
15758
16023
|
}
|
|
15759
16024
|
if (operation.kind === "ensure-git-identity") {
|
|
15760
16025
|
const key = "/run/forgezero-git-deploy-key";
|
|
15761
16026
|
const publicKey = `${key}.pub`;
|
|
15762
16027
|
try {
|
|
15763
|
-
if (!
|
|
15764
|
-
|
|
15765
|
-
|
|
16028
|
+
if (!existsSync21(operation.credential) || lstatSync5(operation.credential).size < 1) {
|
|
16029
|
+
rmSync11(key, { force: true });
|
|
16030
|
+
rmSync11(publicKey, { force: true });
|
|
15766
16031
|
let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
|
|
15767
16032
|
if (result.exitCode !== 0)
|
|
15768
16033
|
return result;
|
|
15769
16034
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=git-deploy-key", key, operation.credential]);
|
|
15770
16035
|
if (result.exitCode !== 0)
|
|
15771
16036
|
return result;
|
|
15772
|
-
|
|
16037
|
+
chmodSync17(operation.credential, 256);
|
|
15773
16038
|
}
|
|
15774
|
-
if (!
|
|
15775
|
-
if (!
|
|
16039
|
+
if (!existsSync21(operation.publicKey) || lstatSync5(operation.publicKey).size < 1) {
|
|
16040
|
+
if (!existsSync21(key)) {
|
|
15776
16041
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
|
|
15777
16042
|
if (decrypted.exitCode !== 0)
|
|
15778
16043
|
return decrypted;
|
|
@@ -15785,25 +16050,25 @@ var runProvisionOperation = async (operation) => {
|
|
|
15785
16050
|
}
|
|
15786
16051
|
return { stdout: "", exitCode: 0 };
|
|
15787
16052
|
} finally {
|
|
15788
|
-
|
|
15789
|
-
|
|
16053
|
+
rmSync11(key, { force: true });
|
|
16054
|
+
rmSync11(publicKey, { force: true });
|
|
15790
16055
|
}
|
|
15791
16056
|
}
|
|
15792
16057
|
if (operation.kind === "ensure-bootstrap-ssh-identity") {
|
|
15793
16058
|
const key = "/run/forgezero-bootstrap-ssh-key";
|
|
15794
16059
|
const generatedPublicKey = `${key}.pub`;
|
|
15795
16060
|
try {
|
|
15796
|
-
if (!
|
|
15797
|
-
|
|
15798
|
-
|
|
16061
|
+
if (!existsSync21(operation.credential) || lstatSync5(operation.credential).size < 1) {
|
|
16062
|
+
rmSync11(key, { force: true });
|
|
16063
|
+
rmSync11(generatedPublicKey, { force: true });
|
|
15799
16064
|
let result;
|
|
15800
16065
|
if (operation.source) {
|
|
15801
|
-
const source =
|
|
16066
|
+
const source = existsSync21(operation.source) ? lstatSync5(operation.source) : undefined;
|
|
15802
16067
|
if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
|
|
15803
16068
|
return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
|
|
15804
16069
|
}
|
|
15805
16070
|
copyFileSync3(operation.source, key);
|
|
15806
|
-
|
|
16071
|
+
chmodSync17(key, 384);
|
|
15807
16072
|
} else {
|
|
15808
16073
|
result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-bootstrap-runner", "-f", key]);
|
|
15809
16074
|
if (result.exitCode !== 0)
|
|
@@ -15816,48 +16081,48 @@ var runProvisionOperation = async (operation) => {
|
|
|
15816
16081
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
|
|
15817
16082
|
if (result.exitCode !== 0)
|
|
15818
16083
|
return result;
|
|
15819
|
-
|
|
16084
|
+
chmodSync17(operation.credential, 256);
|
|
15820
16085
|
}
|
|
15821
|
-
if (!
|
|
15822
|
-
if (!
|
|
16086
|
+
if (!existsSync21(operation.publicKey) || lstatSync5(operation.publicKey).size < 1) {
|
|
16087
|
+
if (!existsSync21(key)) {
|
|
15823
16088
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
|
|
15824
16089
|
if (decrypted.exitCode !== 0)
|
|
15825
16090
|
return decrypted;
|
|
15826
|
-
|
|
16091
|
+
chmodSync17(key, 384);
|
|
15827
16092
|
}
|
|
15828
16093
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
15829
16094
|
if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
|
|
15830
16095
|
return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
|
|
15831
16096
|
}
|
|
15832
|
-
mkdirSync15(
|
|
16097
|
+
mkdirSync15(dirname13(operation.publicKey), { recursive: true, mode: 493 });
|
|
15833
16098
|
writeFileSync15(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
|
|
15834
16099
|
`, { mode: 292 });
|
|
15835
|
-
|
|
16100
|
+
chmodSync17(operation.publicKey, 292);
|
|
15836
16101
|
}
|
|
15837
16102
|
if (operation.source)
|
|
15838
|
-
|
|
16103
|
+
rmSync11(operation.source, { force: true });
|
|
15839
16104
|
return { stdout: "", exitCode: 0 };
|
|
15840
16105
|
} finally {
|
|
15841
|
-
|
|
15842
|
-
|
|
16106
|
+
rmSync11(key, { force: true });
|
|
16107
|
+
rmSync11(generatedPublicKey, { force: true });
|
|
15843
16108
|
}
|
|
15844
16109
|
}
|
|
15845
16110
|
if (operation.kind === "ensure-enrolment") {
|
|
15846
|
-
if (
|
|
16111
|
+
if (existsSync21(operation.state) && lstatSync5(operation.state).size > 0 || existsSync21(operation.credential) && lstatSync5(operation.credential).size > 0)
|
|
15847
16112
|
return { stdout: "", exitCode: 0 };
|
|
15848
|
-
if (!
|
|
16113
|
+
if (!existsSync21(operation.source))
|
|
15849
16114
|
return { stdout: "enrolment source is missing", exitCode: 1 };
|
|
15850
16115
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
|
|
15851
16116
|
if (result.exitCode === 0) {
|
|
15852
|
-
|
|
15853
|
-
|
|
16117
|
+
chmodSync17(operation.credential, 256);
|
|
16118
|
+
rmSync11(operation.source, { force: true });
|
|
15854
16119
|
}
|
|
15855
16120
|
return result;
|
|
15856
16121
|
}
|
|
15857
16122
|
if (operation.kind === "wait-socket") {
|
|
15858
16123
|
for (let attempt = 0;attempt < operation.attempts; attempt += 1) {
|
|
15859
16124
|
try {
|
|
15860
|
-
if (
|
|
16125
|
+
if (lstatSync5(operation.path).isSocket())
|
|
15861
16126
|
return { stdout: "", exitCode: 0 };
|
|
15862
16127
|
} catch {}
|
|
15863
16128
|
await Bun.sleep(operation.intervalMs);
|
|
@@ -15865,7 +16130,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
15865
16130
|
return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
|
|
15866
16131
|
}
|
|
15867
16132
|
if (operation.kind === "verify-file")
|
|
15868
|
-
return
|
|
16133
|
+
return existsSync21(operation.path) && lstatSync5(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
|
|
15869
16134
|
if (operation.kind === "verify-egress") {
|
|
15870
16135
|
const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
|
|
15871
16136
|
if (active.exitCode !== 0)
|
|
@@ -15899,7 +16164,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
15899
16164
|
const key = "/run/cloudflare-warp-key.gpg";
|
|
15900
16165
|
writeFileSync15(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
|
|
15901
16166
|
let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
|
|
15902
|
-
|
|
16167
|
+
rmSync11(key, { force: true });
|
|
15903
16168
|
if (result.exitCode !== 0)
|
|
15904
16169
|
return result;
|
|
15905
16170
|
const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
|
|
@@ -15920,7 +16185,7 @@ async function localRunner(operation) {
|
|
|
15920
16185
|
if (capability.kind === "version")
|
|
15921
16186
|
return fixed(capability.argv);
|
|
15922
16187
|
try {
|
|
15923
|
-
const metadata =
|
|
16188
|
+
const metadata = lstatSync5(capability.path);
|
|
15924
16189
|
const present = capability.nodeType === "directory" ? metadata.isDirectory() : true;
|
|
15925
16190
|
return { stdout: present ? `yes
|
|
15926
16191
|
` : `no
|
|
@@ -16218,19 +16483,19 @@ function localBootstrapHost() {
|
|
|
16218
16483
|
};
|
|
16219
16484
|
return {
|
|
16220
16485
|
uid: () => process.getuid?.() ?? -1,
|
|
16221
|
-
exists:
|
|
16486
|
+
exists: existsSync22,
|
|
16222
16487
|
read: (path2) => readFileSync17(path2, "utf8"),
|
|
16223
16488
|
write(path2, content, mode) {
|
|
16224
|
-
mkdirSync16(
|
|
16489
|
+
mkdirSync16(dirname14(path2), { recursive: true, mode: 493 });
|
|
16225
16490
|
const temporary = `${path2}.next.${process.pid}`;
|
|
16226
16491
|
writeFileSync16(temporary, content, { mode });
|
|
16227
|
-
|
|
16228
|
-
|
|
16492
|
+
chmodSync18(temporary, mode);
|
|
16493
|
+
renameSync15(temporary, path2);
|
|
16229
16494
|
},
|
|
16230
16495
|
mkdir: (path2, mode) => mkdirSync16(path2, { recursive: true, mode }),
|
|
16231
|
-
remove: (path2) =>
|
|
16496
|
+
remove: (path2) => rmSync12(path2, { force: true }),
|
|
16232
16497
|
inspect(path2) {
|
|
16233
|
-
const value =
|
|
16498
|
+
const value = lstatSync6(path2);
|
|
16234
16499
|
return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
|
|
16235
16500
|
},
|
|
16236
16501
|
exec: execute3,
|
|
@@ -16251,9 +16516,9 @@ function localBootstrapHost() {
|
|
|
16251
16516
|
},
|
|
16252
16517
|
async installAgent(config, phase) {
|
|
16253
16518
|
const capabilities = await readCapabilities(localRunner);
|
|
16254
|
-
const hasBinding =
|
|
16255
|
-
const hasEnrolCredential =
|
|
16256
|
-
const initialBundle = config.kind === "platform" && !
|
|
16519
|
+
const hasBinding = existsSync22("/var/lib/forgezero/enrolment.json");
|
|
16520
|
+
const hasEnrolCredential = existsSync22(ENROL_CREDENTIAL);
|
|
16521
|
+
const initialBundle = config.kind === "platform" && !existsSync22(BOOTSTRAP_RELEASE_EVIDENCE) ? {
|
|
16257
16522
|
path: config.bootstrapBundle.bundleFile,
|
|
16258
16523
|
manifestPath: config.bootstrapBundle.manifestFile,
|
|
16259
16524
|
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync17(config.bootstrapBundle.manifestFile, "utf8")))
|
|
@@ -16269,7 +16534,7 @@ function localBootstrapHost() {
|
|
|
16269
16534
|
databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
|
|
16270
16535
|
databasePorts: [8529]
|
|
16271
16536
|
};
|
|
16272
|
-
mkdirSync16(
|
|
16537
|
+
mkdirSync16(dirname14(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
|
|
16273
16538
|
writeFileSync16(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
|
|
16274
16539
|
`, { mode: 256 });
|
|
16275
16540
|
}
|
|
@@ -16280,7 +16545,7 @@ function localBootstrapHost() {
|
|
|
16280
16545
|
initialBundle
|
|
16281
16546
|
});
|
|
16282
16547
|
for (const unit3 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
|
|
16283
|
-
mkdirSync16(
|
|
16548
|
+
mkdirSync16(dirname14(unit3.path), { recursive: true, mode: 493 });
|
|
16284
16549
|
writeFileSync16(unit3.path, unit3.unit, { mode: 420 });
|
|
16285
16550
|
}
|
|
16286
16551
|
await applyPlan(plan, localRunner);
|
|
@@ -16295,7 +16560,7 @@ var localRuntime = () => ({
|
|
|
16295
16560
|
bootstrapStatus: () => bootstrapStatus(),
|
|
16296
16561
|
characterDevice(path2) {
|
|
16297
16562
|
try {
|
|
16298
|
-
return
|
|
16563
|
+
return lstatSync7(path2).isCharacterDevice();
|
|
16299
16564
|
} catch {
|
|
16300
16565
|
return false;
|
|
16301
16566
|
}
|
|
@@ -16334,9 +16599,9 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
|
|
|
16334
16599
|
|
|
16335
16600
|
// src/community-rehearsal-host.ts
|
|
16336
16601
|
import { createHash as createHash12 } from "crypto";
|
|
16337
|
-
import { lstatSync as
|
|
16602
|
+
import { lstatSync as lstatSync8, mkdirSync as mkdirSync17, readFileSync as readFileSync18, rmSync as rmSync13, symlinkSync as symlinkSync8, unlinkSync as unlinkSync13, writeFileSync as writeFileSync17 } from "fs";
|
|
16338
16603
|
import { isIP as isIP5 } from "net";
|
|
16339
|
-
import { dirname as
|
|
16604
|
+
import { dirname as dirname15 } from "path";
|
|
16340
16605
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
16341
16606
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
16342
16607
|
var COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
@@ -16550,19 +16815,19 @@ async function exec(argv2, stdin) {
|
|
|
16550
16815
|
async function applyOperations(operations) {
|
|
16551
16816
|
for (const operation of operations) {
|
|
16552
16817
|
if (operation.kind === "remove-tree")
|
|
16553
|
-
|
|
16818
|
+
rmSync13(operation.path, { recursive: true, force: true });
|
|
16554
16819
|
else if (operation.kind === "write") {
|
|
16555
|
-
mkdirSync17(
|
|
16820
|
+
mkdirSync17(dirname15(operation.path), { recursive: true });
|
|
16556
16821
|
writeFileSync17(operation.path, operation.content, { mode: operation.mode });
|
|
16557
16822
|
} else if (operation.kind === "unlink") {
|
|
16558
16823
|
try {
|
|
16559
|
-
|
|
16824
|
+
unlinkSync13(operation.path);
|
|
16560
16825
|
} catch (cause) {
|
|
16561
16826
|
if (cause.code !== "ENOENT")
|
|
16562
16827
|
throw cause;
|
|
16563
16828
|
}
|
|
16564
16829
|
} else if (operation.kind === "symlink")
|
|
16565
|
-
|
|
16830
|
+
symlinkSync8(operation.target, operation.path);
|
|
16566
16831
|
else {
|
|
16567
16832
|
const result = await exec(operation.argv, operation.stdin);
|
|
16568
16833
|
if (!(operation.accepted ?? [0]).includes(result.exitCode))
|
|
@@ -16604,7 +16869,7 @@ async function runCommunityRehearsalHost(request) {
|
|
|
16604
16869
|
return { ok: true, action: request.action, node: node.name };
|
|
16605
16870
|
}
|
|
16606
16871
|
if (request.action === "prepare") {
|
|
16607
|
-
const metadata =
|
|
16872
|
+
const metadata = lstatSync8(request.archivePath);
|
|
16608
16873
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash12("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
|
16609
16874
|
throw new Error("community rehearsal archive is not the declared bounded release");
|
|
16610
16875
|
}
|
|
@@ -16654,9 +16919,9 @@ async function runCommunityRehearsalHost(request) {
|
|
|
16654
16919
|
}
|
|
16655
16920
|
|
|
16656
16921
|
// src/supervised-app.ts
|
|
16657
|
-
import { lstatSync as
|
|
16922
|
+
import { lstatSync as lstatSync9, readFileSync as readFileSync19 } from "fs";
|
|
16658
16923
|
function readConfig(path2) {
|
|
16659
|
-
const stat =
|
|
16924
|
+
const stat = lstatSync9(path2);
|
|
16660
16925
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== 0 || (stat.mode & 18) !== 0 || stat.size > 128 * 1024) {
|
|
16661
16926
|
throw new Error("supervised app config is unsafe");
|
|
16662
16927
|
}
|
|
@@ -16839,17 +17104,17 @@ function agentCommandArgs(args, command3) {
|
|
|
16839
17104
|
return args.slice(index + 1);
|
|
16840
17105
|
}
|
|
16841
17106
|
function loadOrCreateSeed(path2) {
|
|
16842
|
-
if (
|
|
17107
|
+
if (existsSync23(path2)) {
|
|
16843
17108
|
const seed2 = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
16844
17109
|
if (seed2.length < 32) {
|
|
16845
17110
|
throw new Error(`agent: the seed at ${path2} is too short to derive a key from.`);
|
|
16846
17111
|
}
|
|
16847
17112
|
return seed2;
|
|
16848
17113
|
}
|
|
16849
|
-
mkdirSync18(
|
|
17114
|
+
mkdirSync18(dirname16(path2), { recursive: true });
|
|
16850
17115
|
const seed = new Uint8Array(randomBytes7(32));
|
|
16851
17116
|
writeFileSync18(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
|
|
16852
|
-
|
|
17117
|
+
chmodSync19(path2, 384);
|
|
16853
17118
|
return seed;
|
|
16854
17119
|
}
|
|
16855
17120
|
var DEFAULT_SOCKET_PATH = DEFAULT_SOCKET;
|
|
@@ -16878,7 +17143,7 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
|
|
|
16878
17143
|
if (!directory)
|
|
16879
17144
|
throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the node identity.");
|
|
16880
17145
|
const path2 = `${directory}/${name}`;
|
|
16881
|
-
if (!
|
|
17146
|
+
if (!existsSync23(path2))
|
|
16882
17147
|
throw new Error(`agent: the systemd credential ${name} is missing at ${path2}.`);
|
|
16883
17148
|
const seed = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
16884
17149
|
if (seed.length < 32)
|
|
@@ -17040,7 +17305,7 @@ if (import.meta.main) {
|
|
|
17040
17305
|
if (args.length !== 1)
|
|
17041
17306
|
throw new Error("project-release-check accepts no coordinates");
|
|
17042
17307
|
for (const relative of ["src/index.ts", "bun.lock", "src/generated/shared/contract-manifest.json"]) {
|
|
17043
|
-
const stat =
|
|
17308
|
+
const stat = lstatSync10(join12(process.cwd(), relative));
|
|
17044
17309
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1) {
|
|
17045
17310
|
throw new Error(`project release is missing ${relative}`);
|
|
17046
17311
|
}
|
|
@@ -17379,9 +17644,9 @@ if (import.meta.main) {
|
|
|
17379
17644
|
vcpu: cpus2().length,
|
|
17380
17645
|
memoryGib: Math.max(1, Math.floor(totalmem2() / 1024 ** 3)),
|
|
17381
17646
|
diskGib: Math.max(1, Math.floor(Number(filesystem.blocks) * Number(filesystem.bsize) / 1024 ** 3)),
|
|
17382
|
-
kvm:
|
|
17383
|
-
snpHost:
|
|
17384
|
-
helper:
|
|
17647
|
+
kvm: existsSync23("/dev/kvm"),
|
|
17648
|
+
snpHost: existsSync23("/dev/sev"),
|
|
17649
|
+
helper: existsSync23(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
17385
17650
|
}
|
|
17386
17651
|
};
|
|
17387
17652
|
const envelope = signRequest(keys2, keys2.ed25519.publicKey, {
|
|
@@ -17408,6 +17673,19 @@ if (import.meta.main) {
|
|
|
17408
17673
|
const keys2 = deriveKeysFromSeed(seed);
|
|
17409
17674
|
seed.fill(0);
|
|
17410
17675
|
const nodeKey2 = process.env.FZ_NODE_KEY ?? keys2.ed25519.publicKey;
|
|
17676
|
+
if (process.env.FZ_AGENT_HANDOVER_CANDIDATE === "true") {
|
|
17677
|
+
const ready = startAgentCandidateReady({
|
|
17678
|
+
version: VERSION2,
|
|
17679
|
+
nodeKey: nodeKey2,
|
|
17680
|
+
bound: true,
|
|
17681
|
+
vault: "unbound"
|
|
17682
|
+
}, process.env.FZ_AGENT_HANDOVER_READY_SOCKET ?? DEFAULT_AGENT_CANDIDATE_READY_SOCKET);
|
|
17683
|
+
console.log(`[metal-agent] candidate ${VERSION2} ready for atomic handover`);
|
|
17684
|
+
const stop2 = () => ready.close(() => process.exit(0));
|
|
17685
|
+
process.on("SIGTERM", stop2);
|
|
17686
|
+
process.on("SIGINT", stop2);
|
|
17687
|
+
await new Promise(() => {});
|
|
17688
|
+
}
|
|
17411
17689
|
const pull = startProvisioningPull({
|
|
17412
17690
|
apiUrl: process.env.FZ_API,
|
|
17413
17691
|
nodeKey: nodeKey2,
|
|
@@ -17416,9 +17694,9 @@ if (import.meta.main) {
|
|
|
17416
17694
|
metalHostname: process.env.FZ_METAL_HOSTNAME,
|
|
17417
17695
|
run: (claim) => requestMetalProvision(claim, process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET),
|
|
17418
17696
|
metalPreflight: () => ({
|
|
17419
|
-
snpHost:
|
|
17420
|
-
kvm:
|
|
17421
|
-
helper:
|
|
17697
|
+
snpHost: existsSync23("/dev/sev"),
|
|
17698
|
+
kvm: existsSync23("/dev/kvm"),
|
|
17699
|
+
helper: existsSync23(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
17422
17700
|
}),
|
|
17423
17701
|
onEvent: (event, detail) => console.log(`[metal-agent] ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
|
|
17424
17702
|
});
|
|
@@ -17501,7 +17779,11 @@ if (import.meta.main) {
|
|
|
17501
17779
|
const telemetry = new AgentTelemetryRuntime(createAgentTelemetry(resolveAgentTelemetryConfig()));
|
|
17502
17780
|
telemetry.event("agent.started");
|
|
17503
17781
|
telemetry.setDraining(false);
|
|
17504
|
-
const
|
|
17782
|
+
const handoverCandidate = process.env.FZ_AGENT_HANDOVER_CANDIDATE === "true";
|
|
17783
|
+
if (!handoverCandidate && process.env.FZ_AGENT_ROUTE_SOCKET && process.env.FZ_SOCKET_PATH) {
|
|
17784
|
+
ensureAgentSocketRoute(process.env.FZ_AGENT_ROUTE_SOCKET, process.env.FZ_SOCKET_PATH);
|
|
17785
|
+
}
|
|
17786
|
+
const attestationSource = existsSync23("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
|
|
17505
17787
|
const running = runAgent({
|
|
17506
17788
|
socketPath: process.env.FZ_SOCKET_PATH ?? DEFAULT_SOCKET_PATH,
|
|
17507
17789
|
seedCredential: process.env.FZ_SEED_CREDENTIAL,
|
|
@@ -17516,8 +17798,8 @@ if (import.meta.main) {
|
|
|
17516
17798
|
const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
|
|
17517
17799
|
let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
|
|
17518
17800
|
const enrolmentCredential = process.env.FZ_ENROL_TOKEN_CREDENTIAL;
|
|
17519
|
-
const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY &&
|
|
17520
|
-
const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE &&
|
|
17801
|
+
const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync23(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
|
|
17802
|
+
const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync23(process.env.FZ_ENROL_TOKEN_FILE));
|
|
17521
17803
|
if (!binding && (enrolmentCredentialAvailable || legacyEnrolmentFileAvailable) && process.env.FZ_API) {
|
|
17522
17804
|
binding = await enrolGuestIdentity({
|
|
17523
17805
|
apiUrl: process.env.FZ_API,
|
|
@@ -17537,6 +17819,7 @@ if (import.meta.main) {
|
|
|
17537
17819
|
let nodeApiUrl = binding && process.env.FZ_API ? binding.realm === "platform" ? process.env.FZ_API : tenantNodeApiUrl(process.env.FZ_API, binding.tenantSlug) : process.env.FZ_API;
|
|
17538
17820
|
let secretCache;
|
|
17539
17821
|
let vaultSync;
|
|
17822
|
+
let initialVaultState = "unbound";
|
|
17540
17823
|
if (binding && attestationSource && nodeApiUrl) {
|
|
17541
17824
|
const result = await telemetry.observe("attestation.refresh", () => attestNodeOnce({
|
|
17542
17825
|
apiUrl: nodeApiUrl,
|
|
@@ -17554,6 +17837,7 @@ if (import.meta.main) {
|
|
|
17554
17837
|
projectKey: binding.projectKey
|
|
17555
17838
|
});
|
|
17556
17839
|
const initialVault = await telemetry.observe("vault.sync", () => initializeVaultReplica(secretCache), (result) => result.state === "ready" ? "success" : "failed");
|
|
17840
|
+
initialVaultState = initialVault.state;
|
|
17557
17841
|
running.setVault(secretCache, binding.projectKey);
|
|
17558
17842
|
vaultSync = startNodeVaultSync(secretCache, {
|
|
17559
17843
|
telemetry,
|
|
@@ -17567,6 +17851,31 @@ if (import.meta.main) {
|
|
|
17567
17851
|
console.error("[agent] vault replica unavailable; authenticated refresh remains active and only explicit " + "same-name systemd deployment fallbacks are available");
|
|
17568
17852
|
}
|
|
17569
17853
|
}
|
|
17854
|
+
if (handoverCandidate) {
|
|
17855
|
+
const ready = startAgentCandidateReady({
|
|
17856
|
+
version: VERSION2,
|
|
17857
|
+
nodeKey,
|
|
17858
|
+
bound: Boolean(binding),
|
|
17859
|
+
vault: initialVaultState
|
|
17860
|
+
}, process.env.FZ_AGENT_HANDOVER_READY_SOCKET ?? DEFAULT_AGENT_CANDIDATE_READY_SOCKET);
|
|
17861
|
+
console.log(`[agent] candidate ${VERSION2} ready for atomic handover`);
|
|
17862
|
+
let stopping = false;
|
|
17863
|
+
const stop = async () => {
|
|
17864
|
+
if (stopping)
|
|
17865
|
+
return;
|
|
17866
|
+
stopping = true;
|
|
17867
|
+
await vaultSync?.stop();
|
|
17868
|
+
await Promise.all([
|
|
17869
|
+
new Promise((resolve6) => ready.close(() => resolve6())),
|
|
17870
|
+
new Promise((resolve6) => server.close(() => resolve6()))
|
|
17871
|
+
]);
|
|
17872
|
+
await telemetry.close();
|
|
17873
|
+
process.exit(0);
|
|
17874
|
+
};
|
|
17875
|
+
process.on("SIGTERM", () => void stop());
|
|
17876
|
+
process.on("SIGINT", () => void stop());
|
|
17877
|
+
await new Promise(() => {});
|
|
17878
|
+
}
|
|
17570
17879
|
const attestationLoop = attestationSource && nodeApiUrl ? startNodeAttestation({
|
|
17571
17880
|
apiUrl: nodeApiUrl,
|
|
17572
17881
|
nodeKey,
|
|
@@ -17595,7 +17904,7 @@ if (import.meta.main) {
|
|
|
17595
17904
|
const bootstrapEnabled = process.env.FZ_BOOTSTRAP_PULL === "true";
|
|
17596
17905
|
const bootstrapCredential = process.env.FZ_BOOTSTRAP_SSH_KEY_CREDENTIAL;
|
|
17597
17906
|
const bootstrapKeyPath = bootstrapCredential && process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/${bootstrapCredential}` : undefined;
|
|
17598
|
-
if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !
|
|
17907
|
+
if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync23(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
|
|
17599
17908
|
throw new Error("agent: bootstrap runner requires enrolment, API, local SSH credential and target OTLP coordinate");
|
|
17600
17909
|
}
|
|
17601
17910
|
const bootstrapPull = bootstrapEnabled ? startSshBootstrapPull({
|