@forgezero/agent 0.1.86 → 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/dist/agent-handover.d.ts +13 -0
- package/dist/agent-heartbeat.d.ts +3 -3
- package/dist/agent-heartbeat.js +231 -60
- 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 +48 -5
- package/dist/fz-agent.js +553 -256
- package/dist/fz.js +87 -5
- package/dist/metal-bootstrap.js +40 -1
- package/dist/operator-bootstrap.js +87 -5
- package/dist/platform-fleet-verification.js +406 -211
- package/dist/provision.d.ts +16 -1
- package/dist/provision.js +323 -124
- package/dist/socket.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
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 {
|
|
@@ -4495,6 +4504,32 @@ function handleApplicationRequest(options, request) {
|
|
|
4495
4504
|
return handleRequest(options, request);
|
|
4496
4505
|
}
|
|
4497
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
|
+
}
|
|
4498
4533
|
function handleRequest(options, request) {
|
|
4499
4534
|
switch (request?.op) {
|
|
4500
4535
|
case "identity":
|
|
@@ -4653,9 +4688,9 @@ function safeOp(line) {
|
|
|
4653
4688
|
}
|
|
4654
4689
|
|
|
4655
4690
|
// src/deployment.ts
|
|
4656
|
-
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";
|
|
4657
4692
|
import { createHash as createHash5, randomUUID } from "crypto";
|
|
4658
|
-
import { dirname as
|
|
4693
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
4659
4694
|
|
|
4660
4695
|
// ../runtime/dist/queue.js
|
|
4661
4696
|
class QueueStoppedError extends Error {
|
|
@@ -4970,15 +5005,15 @@ import {
|
|
|
4970
5005
|
mkdtempSync,
|
|
4971
5006
|
mkdirSync,
|
|
4972
5007
|
readFileSync,
|
|
4973
|
-
renameSync,
|
|
5008
|
+
renameSync as renameSync2,
|
|
4974
5009
|
rmSync,
|
|
4975
5010
|
statSync,
|
|
4976
|
-
symlinkSync,
|
|
5011
|
+
symlinkSync as symlinkSync2,
|
|
4977
5012
|
unlinkSync as unlinkSync2,
|
|
4978
5013
|
writeFileSync
|
|
4979
5014
|
} from "fs";
|
|
4980
5015
|
import { tmpdir } from "os";
|
|
4981
|
-
import { dirname, join } from "path";
|
|
5016
|
+
import { dirname as dirname2, join } from "path";
|
|
4982
5017
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
4983
5018
|
var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
|
|
4984
5019
|
var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
|
|
@@ -5188,7 +5223,7 @@ var writeOwnedPolicy = (pathValue, content, mode = 420) => {
|
|
|
5188
5223
|
throw new Error(`refusing to overwrite non-ForgeZero policy at ${pathValue}`);
|
|
5189
5224
|
return;
|
|
5190
5225
|
}
|
|
5191
|
-
mkdirSync(
|
|
5226
|
+
mkdirSync(dirname2(pathValue), { recursive: true, mode: 493 });
|
|
5192
5227
|
writeFileSync(pathValue, content, { mode, flag: "wx" });
|
|
5193
5228
|
};
|
|
5194
5229
|
var installContainerdRuntime = async (directory, osVersion) => {
|
|
@@ -5266,7 +5301,7 @@ var installKataRuntime = async (directory) => {
|
|
|
5266
5301
|
try {
|
|
5267
5302
|
unlinkSync2("/usr/local/bin/containerd-shim-kata-v2");
|
|
5268
5303
|
} catch {}
|
|
5269
|
-
|
|
5304
|
+
symlinkSync2("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
|
|
5270
5305
|
const base = CONTAINERD_CONFIG(false);
|
|
5271
5306
|
const kata = CONTAINERD_CONFIG(true);
|
|
5272
5307
|
if (!existsSync2(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
|
|
@@ -5407,11 +5442,11 @@ async function executeSoftwareOperation(operation) {
|
|
|
5407
5442
|
mkdirSync("/usr/local/lib/forgezero/runtime", { recursive: true, mode: 493 });
|
|
5408
5443
|
copyFileSync(join(unpacked, "bun-linux-x64", "bun"), "/usr/local/lib/forgezero/runtime/bun.next");
|
|
5409
5444
|
chmodSync2("/usr/local/lib/forgezero/runtime/bun.next", 493);
|
|
5410
|
-
|
|
5445
|
+
renameSync2("/usr/local/lib/forgezero/runtime/bun.next", "/usr/local/lib/forgezero/runtime/bun");
|
|
5411
5446
|
try {
|
|
5412
5447
|
unlinkSync2("/usr/local/bin/bun");
|
|
5413
5448
|
} catch {}
|
|
5414
|
-
|
|
5449
|
+
symlinkSync2("/usr/local/lib/forgezero/runtime/bun", "/usr/local/bin/bun");
|
|
5415
5450
|
return { exitCode: 0, output: "" };
|
|
5416
5451
|
}
|
|
5417
5452
|
if (software === "cloudflared") {
|
|
@@ -7778,10 +7813,10 @@ import {
|
|
|
7778
7813
|
chmodSync as chmodSync3,
|
|
7779
7814
|
createReadStream as createReadStream2,
|
|
7780
7815
|
existsSync as existsSync3,
|
|
7781
|
-
lstatSync,
|
|
7816
|
+
lstatSync as lstatSync2,
|
|
7782
7817
|
mkdirSync as mkdirSync2,
|
|
7783
7818
|
readFileSync as readFileSync2,
|
|
7784
|
-
renameSync as
|
|
7819
|
+
renameSync as renameSync3,
|
|
7785
7820
|
rmSync as rmSync2,
|
|
7786
7821
|
writeFileSync as writeFileSync2
|
|
7787
7822
|
} from "fs";
|
|
@@ -7877,13 +7912,13 @@ function persistCapacityCalibration(args) {
|
|
|
7877
7912
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be an absolute Agent-owned path");
|
|
7878
7913
|
}
|
|
7879
7914
|
if (!existsSync4(args.directory)) {
|
|
7880
|
-
const parent =
|
|
7915
|
+
const parent = lstatSync3(dirname3(args.directory));
|
|
7881
7916
|
if (!parent.isDirectory() || parent.isSymbolicLink()) {
|
|
7882
7917
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence parent must be a real Agent-owned directory");
|
|
7883
7918
|
}
|
|
7884
7919
|
mkdirSync3(args.directory, { mode: 448 });
|
|
7885
7920
|
}
|
|
7886
|
-
const stats =
|
|
7921
|
+
const stats = lstatSync3(args.directory);
|
|
7887
7922
|
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
7888
7923
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be a real Agent-owned directory");
|
|
7889
7924
|
}
|
|
@@ -7906,7 +7941,7 @@ function persistCapacityCalibration(args) {
|
|
|
7906
7941
|
}, null, 2)}
|
|
7907
7942
|
`, { mode: 384, flag: "wx" });
|
|
7908
7943
|
chmodSync4(next, 384);
|
|
7909
|
-
|
|
7944
|
+
renameSync4(next, path2);
|
|
7910
7945
|
return path2;
|
|
7911
7946
|
}
|
|
7912
7947
|
function createDeploymentManager(options) {
|
|
@@ -7941,10 +7976,10 @@ function createDeploymentManager(options) {
|
|
|
7941
7976
|
}
|
|
7942
7977
|
if (existsSync4(knownHostsPath) && readFileSync3(knownHostsPath, "utf8") === content)
|
|
7943
7978
|
return;
|
|
7944
|
-
mkdirSync3(
|
|
7979
|
+
mkdirSync3(dirname3(knownHostsPath), { recursive: true, mode: 448 });
|
|
7945
7980
|
const next = `${knownHostsPath}.${process.pid}.${randomUUID()}.next`;
|
|
7946
7981
|
writeFileSync3(next, content, { mode: 384, flag: "wx" });
|
|
7947
|
-
|
|
7982
|
+
renameSync4(next, knownHostsPath);
|
|
7948
7983
|
chmodSync4(knownHostsPath, 384);
|
|
7949
7984
|
};
|
|
7950
7985
|
const gitBaseEnvironment = () => ({
|
|
@@ -8070,7 +8105,7 @@ function createDeploymentManager(options) {
|
|
|
8070
8105
|
if (bootstrapBundle) {
|
|
8071
8106
|
let metadata;
|
|
8072
8107
|
try {
|
|
8073
|
-
metadata =
|
|
8108
|
+
metadata = lstatSync3(bootstrapBundle.path);
|
|
8074
8109
|
} catch {
|
|
8075
8110
|
throw new DeploymentError("SOURCE_FAILED", "The attended bootstrap bundle is missing.");
|
|
8076
8111
|
}
|
|
@@ -8358,7 +8393,7 @@ function createDeploymentManager(options) {
|
|
|
8358
8393
|
const component2 = plan.spec.components[String(resolved.component ?? "")];
|
|
8359
8394
|
const mounts = component2?.kind === "database" ? [component2.storage] : component2?.storage ?? [];
|
|
8360
8395
|
for (const mount of mounts) {
|
|
8361
|
-
const candidate = existsSync4(mount.path) ? mount.path :
|
|
8396
|
+
const candidate = existsSync4(mount.path) ? mount.path : dirname3(mount.path);
|
|
8362
8397
|
const stats = statfsSync(candidate);
|
|
8363
8398
|
const freePercent = Number(stats.bavail) / Number(stats.blocks) * 100;
|
|
8364
8399
|
const minimum = mount.class === "ephemeral" ? 0 : mount.minimumFreePercent ?? 0;
|
|
@@ -8934,12 +8969,12 @@ import {
|
|
|
8934
8969
|
existsSync as existsSync6,
|
|
8935
8970
|
mkdirSync as mkdirSync4,
|
|
8936
8971
|
readFileSync as readFileSync4,
|
|
8937
|
-
renameSync as
|
|
8972
|
+
renameSync as renameSync5,
|
|
8938
8973
|
statSync as statSync2,
|
|
8939
8974
|
unlinkSync as unlinkSync4,
|
|
8940
8975
|
writeFileSync as writeFileSync4
|
|
8941
8976
|
} from "fs";
|
|
8942
|
-
import { dirname as
|
|
8977
|
+
import { dirname as dirname4 } from "path";
|
|
8943
8978
|
function privateNetworkAttachmentFromEnvironment(env = process.env) {
|
|
8944
8979
|
const values = {
|
|
8945
8980
|
accountId: env.FZ_CF_ACCOUNT_ID?.trim() ?? "",
|
|
@@ -8985,12 +9020,12 @@ function loadGuestBinding(path2, expectedNodeKey) {
|
|
|
8985
9020
|
return parsed;
|
|
8986
9021
|
}
|
|
8987
9022
|
function persistGuestBinding(path2, binding) {
|
|
8988
|
-
mkdirSync4(
|
|
9023
|
+
mkdirSync4(dirname4(path2), { recursive: true, mode: 448 });
|
|
8989
9024
|
const temporary = `${path2}.next`;
|
|
8990
9025
|
writeFileSync4(temporary, `${JSON.stringify(binding)}
|
|
8991
9026
|
`, { mode: 384 });
|
|
8992
9027
|
chmodSync6(temporary, 384);
|
|
8993
|
-
|
|
9028
|
+
renameSync5(temporary, path2);
|
|
8994
9029
|
}
|
|
8995
9030
|
async function enrolGuestIdentity(options) {
|
|
8996
9031
|
const token = options.token?.trim() ?? (options.tokenPath ? readFileSync4(options.tokenPath, "utf8").trim() : "");
|
|
@@ -9343,7 +9378,7 @@ async function writeAndCloseProcessInput(input, value) {
|
|
|
9343
9378
|
}
|
|
9344
9379
|
|
|
9345
9380
|
// src/version.ts
|
|
9346
|
-
var VERSION2 = "0.1.
|
|
9381
|
+
var VERSION2 = "0.1.87";
|
|
9347
9382
|
|
|
9348
9383
|
// src/ssh-bootstrap.ts
|
|
9349
9384
|
class SshBootstrapError extends Error {
|
|
@@ -10062,7 +10097,7 @@ import {
|
|
|
10062
10097
|
unlinkSync as unlinkSync5,
|
|
10063
10098
|
writeFileSync as writeFileSync6
|
|
10064
10099
|
} from "fs";
|
|
10065
|
-
import { dirname as
|
|
10100
|
+
import { dirname as dirname5, isAbsolute as isAbsolute3, join as join4 } from "path";
|
|
10066
10101
|
import { isIP as isIP2 } from "net";
|
|
10067
10102
|
|
|
10068
10103
|
// src/compute.ts
|
|
@@ -10611,7 +10646,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
10611
10646
|
}
|
|
10612
10647
|
const service = `forgezero-guest@${name}.service`;
|
|
10613
10648
|
const unitPath = join4(profile.unitDir, service);
|
|
10614
|
-
mkdirSync5(
|
|
10649
|
+
mkdirSync5(dirname5(unitPath), { recursive: true });
|
|
10615
10650
|
writeFileSync6(unitPath, guestUnit(spec), { mode: 420 });
|
|
10616
10651
|
await checked2(exec, ["systemctl", "daemon-reload"]);
|
|
10617
10652
|
if (prior?.phase === "running") {
|
|
@@ -11579,7 +11614,7 @@ function requestLifecycleAction(claim, socketPath = DEFAULT_LIFECYCLE_HELPER_SOC
|
|
|
11579
11614
|
}
|
|
11580
11615
|
|
|
11581
11616
|
// src/warp-config.ts
|
|
11582
|
-
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";
|
|
11583
11618
|
var xml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11584
11619
|
function renderWarpMdm(options) {
|
|
11585
11620
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(options.organization))
|
|
@@ -11605,14 +11640,14 @@ function materializeWarpMdm(options) {
|
|
|
11605
11640
|
const next = `${runtimePath}.next`;
|
|
11606
11641
|
writeFileSync8(next, renderWarpMdm(options), { mode: 384 });
|
|
11607
11642
|
chmodSync11(next, 384);
|
|
11608
|
-
|
|
11643
|
+
renameSync6(next, runtimePath);
|
|
11609
11644
|
try {
|
|
11610
11645
|
unlinkSync9(servicePath);
|
|
11611
11646
|
} catch (cause) {
|
|
11612
11647
|
if (cause.code !== "ENOENT")
|
|
11613
11648
|
throw cause;
|
|
11614
11649
|
}
|
|
11615
|
-
|
|
11650
|
+
symlinkSync3(runtimePath, servicePath);
|
|
11616
11651
|
return { runtimePath, servicePath };
|
|
11617
11652
|
}
|
|
11618
11653
|
|
|
@@ -11669,14 +11704,15 @@ import {
|
|
|
11669
11704
|
mkdirSync as mkdirSync8,
|
|
11670
11705
|
openSync as openSync2,
|
|
11671
11706
|
readFileSync as readFileSync9,
|
|
11672
|
-
readlinkSync,
|
|
11673
|
-
renameSync as
|
|
11707
|
+
readlinkSync as readlinkSync2,
|
|
11708
|
+
renameSync as renameSync7,
|
|
11674
11709
|
rmSync as rmSync5,
|
|
11675
|
-
symlinkSync as
|
|
11710
|
+
symlinkSync as symlinkSync4,
|
|
11676
11711
|
writeFileSync as writeFileSync9
|
|
11677
11712
|
} from "fs";
|
|
11678
|
-
import { dirname as
|
|
11713
|
+
import { dirname as dirname6, join as join7, resolve as resolve2 } from "path";
|
|
11679
11714
|
var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
|
|
11715
|
+
var DEFAULT_AGENT_CANDIDATE_LINK = `${DEFAULT_AGENT_RELEASE_ROOT}/candidate`;
|
|
11680
11716
|
var DEFAULT_AGENT_UPDATE_SOCKET = "/run/forgezero-update/helper.sock";
|
|
11681
11717
|
var MAX_AGENT_TARBALL_BYTES = 32 * 1024 * 1024;
|
|
11682
11718
|
var VERSION3 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
@@ -11697,7 +11733,7 @@ var syncReleaseDirectory = (directory) => {
|
|
|
11697
11733
|
join7(directory, "dist", "fz-git-ssh.js"),
|
|
11698
11734
|
join7(directory, "dist"),
|
|
11699
11735
|
directory,
|
|
11700
|
-
|
|
11736
|
+
dirname6(directory)
|
|
11701
11737
|
])
|
|
11702
11738
|
syncPath(path2);
|
|
11703
11739
|
};
|
|
@@ -11819,19 +11855,19 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
11819
11855
|
args: ["-xOzf", archive, member]
|
|
11820
11856
|
}, `agent update extraction of ${member}`);
|
|
11821
11857
|
const destination = join7(unpacked, relative);
|
|
11822
|
-
mkdirSync8(
|
|
11858
|
+
mkdirSync8(dirname6(destination), { recursive: true, mode: 448 });
|
|
11823
11859
|
writeFileSync9(destination, extracted.output, { mode: 384, flag: "wx" });
|
|
11824
11860
|
}
|
|
11825
11861
|
await validateReleaseDirectory(unpacked, release, run2);
|
|
11826
11862
|
if (!existsSync12(finalDirectory)) {
|
|
11827
|
-
|
|
11863
|
+
renameSync7(unpacked, finalDirectory);
|
|
11828
11864
|
syncReleaseDirectory(finalDirectory);
|
|
11829
11865
|
} else
|
|
11830
11866
|
await validateReleaseDirectory(finalDirectory, release, run2);
|
|
11831
11867
|
if (!existsSync12(currentLink)) {
|
|
11832
11868
|
throw new Error("agent update requires an active immutable release to roll back to");
|
|
11833
11869
|
}
|
|
11834
|
-
const previousTarget =
|
|
11870
|
+
const previousTarget = readlinkSync2(currentLink);
|
|
11835
11871
|
if (previousTarget !== join7("versions", options.currentVersion)) {
|
|
11836
11872
|
throw new Error("agent update current release does not match the running version");
|
|
11837
11873
|
}
|
|
@@ -11851,43 +11887,116 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
11851
11887
|
}
|
|
11852
11888
|
}
|
|
11853
11889
|
function selectAgentRelease(staged) {
|
|
11854
|
-
const next = join7(
|
|
11890
|
+
const next = join7(dirname6(staged.currentLink), `.current.${randomUUID2()}.next`);
|
|
11855
11891
|
try {
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
syncPath(
|
|
11892
|
+
symlinkSync4(staged.nextTarget, next);
|
|
11893
|
+
renameSync7(next, staged.currentLink);
|
|
11894
|
+
syncPath(dirname6(staged.currentLink));
|
|
11859
11895
|
} finally {
|
|
11860
11896
|
rmSync5(next, { force: true });
|
|
11861
11897
|
}
|
|
11862
11898
|
}
|
|
11863
11899
|
function restoreAgentRelease(staged) {
|
|
11864
|
-
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`;
|
|
11865
11912
|
try {
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
syncPath(
|
|
11913
|
+
symlinkSync4(staged.nextTarget, next);
|
|
11914
|
+
renameSync7(next, link);
|
|
11915
|
+
syncPath(dirname6(link));
|
|
11869
11916
|
} finally {
|
|
11870
11917
|
rmSync5(next, { force: true });
|
|
11871
11918
|
}
|
|
11872
11919
|
}
|
|
11920
|
+
function clearAgentCandidate(root = DEFAULT_AGENT_RELEASE_ROOT) {
|
|
11921
|
+
rmSync5(join7(resolve2(root), "candidate"), { force: true });
|
|
11922
|
+
}
|
|
11873
11923
|
|
|
11874
11924
|
// src/agent-update-helper.ts
|
|
11875
|
-
import { randomUUID as
|
|
11925
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
11876
11926
|
import {
|
|
11877
|
-
chmodSync as
|
|
11927
|
+
chmodSync as chmodSync14,
|
|
11878
11928
|
closeSync as closeSync3,
|
|
11879
|
-
existsSync as
|
|
11929
|
+
existsSync as existsSync14,
|
|
11880
11930
|
fsyncSync as fsyncSync2,
|
|
11881
11931
|
mkdirSync as mkdirSync9,
|
|
11882
11932
|
openSync as openSync3,
|
|
11883
11933
|
readFileSync as readFileSync10,
|
|
11884
|
-
renameSync as
|
|
11885
|
-
rmSync as
|
|
11886
|
-
unlinkSync as
|
|
11934
|
+
renameSync as renameSync9,
|
|
11935
|
+
rmSync as rmSync7,
|
|
11936
|
+
unlinkSync as unlinkSync11,
|
|
11887
11937
|
writeFileSync as writeFileSync10
|
|
11888
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";
|
|
11889
11946
|
import { connect as connect5, createServer as createServer6 } from "net";
|
|
11890
|
-
|
|
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
|
|
11891
12000
|
var AGENT_UPDATE_GROUP = "forgezero-update";
|
|
11892
12001
|
var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-helper.service";
|
|
11893
12002
|
var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
|
|
@@ -11982,13 +12091,13 @@ function validateJournal(value, root) {
|
|
|
11982
12091
|
return journal;
|
|
11983
12092
|
}
|
|
11984
12093
|
function readJournal(path2, root) {
|
|
11985
|
-
if (!
|
|
12094
|
+
if (!existsSync14(path2))
|
|
11986
12095
|
return;
|
|
11987
12096
|
return validateJournal(JSON.parse(readFileSync10(path2, "utf8")), root);
|
|
11988
12097
|
}
|
|
11989
12098
|
function writeAtomic(path2, value, mode) {
|
|
11990
|
-
mkdirSync9(
|
|
11991
|
-
const next = `${path2}.${
|
|
12099
|
+
mkdirSync9(dirname8(path2), { recursive: true, mode: 493 });
|
|
12100
|
+
const next = `${path2}.${randomUUID4()}.next`;
|
|
11992
12101
|
let file;
|
|
11993
12102
|
try {
|
|
11994
12103
|
file = openSync3(next, "wx", mode);
|
|
@@ -11997,8 +12106,8 @@ function writeAtomic(path2, value, mode) {
|
|
|
11997
12106
|
fsyncSync2(file);
|
|
11998
12107
|
closeSync3(file);
|
|
11999
12108
|
file = undefined;
|
|
12000
|
-
|
|
12001
|
-
const directory = openSync3(
|
|
12109
|
+
renameSync9(next, path2);
|
|
12110
|
+
const directory = openSync3(dirname8(path2), "r");
|
|
12002
12111
|
try {
|
|
12003
12112
|
fsyncSync2(directory);
|
|
12004
12113
|
} finally {
|
|
@@ -12007,7 +12116,7 @@ function writeAtomic(path2, value, mode) {
|
|
|
12007
12116
|
} finally {
|
|
12008
12117
|
if (file !== undefined)
|
|
12009
12118
|
closeSync3(file);
|
|
12010
|
-
|
|
12119
|
+
rmSync7(next, { force: true });
|
|
12011
12120
|
}
|
|
12012
12121
|
}
|
|
12013
12122
|
var publicReceipt = (journal) => {
|
|
@@ -12040,7 +12149,7 @@ function writeUpdateState(journalPath, receiptPath, journal) {
|
|
|
12040
12149
|
}
|
|
12041
12150
|
function readAgentUpdateReceipt(path2 = AGENT_UPDATE_RECEIPT) {
|
|
12042
12151
|
try {
|
|
12043
|
-
if (!
|
|
12152
|
+
if (!existsSync14(path2))
|
|
12044
12153
|
return;
|
|
12045
12154
|
return validateReceipt(JSON.parse(readFileSync10(path2, "utf8")));
|
|
12046
12155
|
} catch {
|
|
@@ -12080,10 +12189,32 @@ var restartAgent = async (target2, run2) => {
|
|
|
12080
12189
|
throw new Error(`systemd could not restart ${service}`);
|
|
12081
12190
|
}
|
|
12082
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
|
+
});
|
|
12083
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"]);
|
|
12084
12215
|
function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
12085
12216
|
return new Promise((resolve4) => {
|
|
12086
|
-
const socket =
|
|
12217
|
+
const socket = connect6(socketPath);
|
|
12087
12218
|
let settled = false;
|
|
12088
12219
|
let buffer = "";
|
|
12089
12220
|
const finish = (value) => {
|
|
@@ -12116,12 +12247,13 @@ function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
|
12116
12247
|
async function activateAgentRelease(staged, options = {}) {
|
|
12117
12248
|
const run2 = options.run ?? runCommand;
|
|
12118
12249
|
const target2 = options.target ?? "compute";
|
|
12119
|
-
const
|
|
12250
|
+
const paths = socketPaths(options.publicSocketPath);
|
|
12251
|
+
const probe = options.probe ?? (target2 === "compute" ? () => probeAgentSocket(paths.active) : targetProbe(target2, run2));
|
|
12120
12252
|
const now = options.now ?? Date.now;
|
|
12121
12253
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12122
12254
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12123
|
-
const previous = readJournal(journalPath,
|
|
12124
|
-
const attemptId = options.attemptId ??
|
|
12255
|
+
const previous = readJournal(journalPath, dirname8(staged.currentLink));
|
|
12256
|
+
const attemptId = options.attemptId ?? randomUUID4();
|
|
12125
12257
|
if (!ATTEMPT_ID.test(attemptId))
|
|
12126
12258
|
throw new Error("Agent update attempt ID is invalid");
|
|
12127
12259
|
const startedAtTs = now();
|
|
@@ -12143,11 +12275,18 @@ async function activateAgentRelease(staged, options = {}) {
|
|
|
12143
12275
|
writeUpdateState(journalPath, receiptPath, journal);
|
|
12144
12276
|
let selectionAttempted = false;
|
|
12145
12277
|
try {
|
|
12278
|
+
if (!options.candidatePrepared)
|
|
12279
|
+
await startCandidate(staged, target2, run2);
|
|
12280
|
+
if (target2 === "compute")
|
|
12281
|
+
switchAgentSocketRoute(paths.route, paths.candidate);
|
|
12146
12282
|
selectionAttempted = true;
|
|
12147
12283
|
selectAgentRelease(staged);
|
|
12148
12284
|
await restartAgent(target2, run2);
|
|
12149
12285
|
if (!await probe())
|
|
12150
|
-
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);
|
|
12151
12290
|
journal = { ...journal, outcome: "active", updatedAtTs: now(), rollbackHealthy: undefined };
|
|
12152
12291
|
writeUpdateState(journalPath, receiptPath, journal);
|
|
12153
12292
|
run2({
|
|
@@ -12165,10 +12304,13 @@ async function activateAgentRelease(staged, options = {}) {
|
|
|
12165
12304
|
restored = true;
|
|
12166
12305
|
await restartAgent(target2, run2);
|
|
12167
12306
|
rollbackHealthy = await probe();
|
|
12307
|
+
if (rollbackHealthy && target2 === "compute")
|
|
12308
|
+
switchAgentSocketRoute(paths.route, paths.active);
|
|
12168
12309
|
} catch {
|
|
12169
12310
|
rollbackHealthy = false;
|
|
12170
12311
|
}
|
|
12171
12312
|
}
|
|
12313
|
+
await stopCandidate(staged, target2, run2).catch(() => {});
|
|
12172
12314
|
const failures = failureCount + 1;
|
|
12173
12315
|
const updatedAtTs = now();
|
|
12174
12316
|
journal = {
|
|
@@ -12188,28 +12330,38 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
12188
12330
|
const root = resolve3(options.root ?? DEFAULT_AGENT_RELEASE_ROOT);
|
|
12189
12331
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12190
12332
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12333
|
+
const run2 = options.run ?? runCommand;
|
|
12191
12334
|
const journal = readJournal(journalPath, root);
|
|
12192
|
-
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);
|
|
12193
12340
|
return;
|
|
12341
|
+
}
|
|
12194
12342
|
if (journal.outcome !== "activating") {
|
|
12343
|
+
await stopCandidate(stagedFromJournal(journal, root), journal.target, run2).catch(() => {});
|
|
12195
12344
|
writeAtomic(receiptPath, publicReceipt(journal), 416);
|
|
12196
12345
|
return publicReceipt(journal);
|
|
12197
12346
|
}
|
|
12198
12347
|
const staged = stagedFromJournal(journal, root);
|
|
12199
|
-
if (!
|
|
12348
|
+
if (!existsSync14(join8(root, journal.previousTarget))) {
|
|
12200
12349
|
throw new Error("Agent update rollback release is missing");
|
|
12201
12350
|
}
|
|
12202
|
-
const
|
|
12203
|
-
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));
|
|
12204
12353
|
restoreAgentRelease(staged);
|
|
12205
12354
|
let rollbackHealthy = false;
|
|
12206
12355
|
let failureMessage = "activation was interrupted before its health verdict became durable";
|
|
12207
12356
|
try {
|
|
12208
12357
|
await restartAgent(journal.target, run2);
|
|
12209
12358
|
rollbackHealthy = await probe();
|
|
12359
|
+
if (rollbackHealthy && journal.target === "compute")
|
|
12360
|
+
switchAgentSocketRoute(paths.route, paths.active);
|
|
12210
12361
|
} catch (cause) {
|
|
12211
12362
|
failureMessage = `${failureMessage}; ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
12212
12363
|
}
|
|
12364
|
+
await stopCandidate(staged, journal.target, run2).catch(() => {});
|
|
12213
12365
|
const failures = journal.failureCount + 1;
|
|
12214
12366
|
const updatedAtTs = (options.now ?? Date.now)();
|
|
12215
12367
|
const recovered = {
|
|
@@ -12226,15 +12378,23 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
12226
12378
|
}
|
|
12227
12379
|
function startAgentUpdateHelper(options = {}) {
|
|
12228
12380
|
const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
|
|
12229
|
-
if (
|
|
12230
|
-
|
|
12231
|
-
mkdirSync9(
|
|
12232
|
-
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
12381
|
+
if (existsSync14(socketPath))
|
|
12382
|
+
unlinkSync11(socketPath);
|
|
12383
|
+
mkdirSync9(dirname8(socketPath), { recursive: true, mode: 488 });
|
|
12233
12384
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12234
12385
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12235
12386
|
const releaseRoot = options.root ?? DEFAULT_AGENT_RELEASE_ROOT;
|
|
12236
|
-
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
|
+
}));
|
|
12237
12396
|
let busy = true;
|
|
12397
|
+
let pending;
|
|
12238
12398
|
let blocked;
|
|
12239
12399
|
(options.recover ?? (() => recoverInterruptedAgentUpdate({
|
|
12240
12400
|
root: releaseRoot,
|
|
@@ -12246,7 +12406,7 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12246
12406
|
}).finally(() => {
|
|
12247
12407
|
busy = false;
|
|
12248
12408
|
});
|
|
12249
|
-
const server =
|
|
12409
|
+
const server = createServer7((socket) => {
|
|
12250
12410
|
let buffer = "";
|
|
12251
12411
|
socket.on("data", (chunk) => {
|
|
12252
12412
|
buffer += chunk.toString("utf8");
|
|
@@ -12265,16 +12425,49 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12265
12425
|
Promise.resolve().then(() => JSON.parse(line)).then(async (request) => {
|
|
12266
12426
|
if (blocked)
|
|
12267
12427
|
throw new Error(`update journal needs operator recovery: ${blocked}`);
|
|
12268
|
-
if (busy)
|
|
12269
|
-
throw new Error("another Agent update or recovery is already active");
|
|
12270
|
-
if (request.op !== "apply")
|
|
12271
|
-
throw new Error("unknown update operation");
|
|
12272
12428
|
if (request.target !== "compute" && request.target !== "metal") {
|
|
12273
12429
|
throw new Error("agent update target is invalid");
|
|
12274
12430
|
}
|
|
12275
|
-
const attemptId = request.attemptId ??
|
|
12431
|
+
const attemptId = request.attemptId ?? randomUUID4();
|
|
12276
12432
|
if (!ATTEMPT_ID.test(attemptId))
|
|
12277
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");
|
|
12278
12471
|
const prior = readJournal(journalPath, releaseRoot);
|
|
12279
12472
|
const now = (options.now ?? Date.now)();
|
|
12280
12473
|
if (prior?.targetVersion === request.release.version && (prior.outcome === "rolled-back" || prior.outcome === "failed") && (prior.retryAfterTs ?? 0) > now)
|
|
@@ -12285,17 +12478,23 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12285
12478
|
currentVersion: request.currentVersion,
|
|
12286
12479
|
root: releaseRoot
|
|
12287
12480
|
});
|
|
12288
|
-
|
|
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 };
|
|
12289
12494
|
socket.end(`${JSON.stringify(response)}
|
|
12290
12495
|
`);
|
|
12291
|
-
setTimer(() => void activate(staged, request.target, attemptId).catch((cause) => {
|
|
12292
|
-
blocked = cause instanceof Error ? cause.message : String(cause);
|
|
12293
|
-
}).finally(() => {
|
|
12294
|
-
busy = false;
|
|
12295
|
-
}), 100);
|
|
12296
|
-
ownsBusy = false;
|
|
12297
12496
|
}).catch((cause) => {
|
|
12298
|
-
if (ownsBusy)
|
|
12497
|
+
if (ownsBusy && !pending)
|
|
12299
12498
|
busy = false;
|
|
12300
12499
|
const response = {
|
|
12301
12500
|
ok: false,
|
|
@@ -12307,12 +12506,12 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12307
12506
|
});
|
|
12308
12507
|
socket.on("error", () => socket.destroy());
|
|
12309
12508
|
});
|
|
12310
|
-
server.listen(socketPath, () =>
|
|
12509
|
+
server.listen(socketPath, () => chmodSync14(socketPath, 432));
|
|
12311
12510
|
return server;
|
|
12312
12511
|
}
|
|
12313
12512
|
function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
|
|
12314
12513
|
return new Promise((resolve4, reject) => {
|
|
12315
|
-
const socket =
|
|
12514
|
+
const socket = connect6(socketPath, () => socket.write(`${JSON.stringify(request)}
|
|
12316
12515
|
`));
|
|
12317
12516
|
let buffer = "";
|
|
12318
12517
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -12337,7 +12536,7 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
|
|
|
12337
12536
|
}
|
|
12338
12537
|
|
|
12339
12538
|
// src/agent-heartbeat.ts
|
|
12340
|
-
import { existsSync as
|
|
12539
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11, statfsSync as statfsSync2 } from "fs";
|
|
12341
12540
|
import { cpus, freemem, loadavg, totalmem } from "os";
|
|
12342
12541
|
function readAgentHostMetrics() {
|
|
12343
12542
|
const filesystem = statfsSync2("/");
|
|
@@ -12382,7 +12581,7 @@ function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = rea
|
|
|
12382
12581
|
runtimeCapabilities: {
|
|
12383
12582
|
native: true,
|
|
12384
12583
|
ociRunc: ubuntuX64,
|
|
12385
|
-
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")
|
|
12386
12585
|
},
|
|
12387
12586
|
capacity: {
|
|
12388
12587
|
logicalCpu: Math.max(1, metrics.logicalCpu),
|
|
@@ -12434,18 +12633,24 @@ async function heartbeatAgentOnce(options) {
|
|
|
12434
12633
|
});
|
|
12435
12634
|
return response;
|
|
12436
12635
|
}
|
|
12437
|
-
let
|
|
12636
|
+
let candidatePrepared = false;
|
|
12637
|
+
let drained = false;
|
|
12438
12638
|
try {
|
|
12439
|
-
|
|
12440
|
-
|
|
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
|
+
}));
|
|
12441
12646
|
const apply = async () => {
|
|
12442
|
-
const
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
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);
|
|
12449
12654
|
if (!applied.ok)
|
|
12450
12655
|
throw new AgentUpdateRefusedError(`agent update refused: ${applied.error.message}`);
|
|
12451
12656
|
if (applied.attemptId !== desired.attemptId) {
|
|
@@ -12458,9 +12663,18 @@ async function heartbeatAgentOnce(options) {
|
|
|
12458
12663
|
} else {
|
|
12459
12664
|
await apply();
|
|
12460
12665
|
}
|
|
12461
|
-
options.onEvent?.("update-
|
|
12666
|
+
options.onEvent?.("update-active", { from: observation.version, to: release.version });
|
|
12462
12667
|
} catch (cause) {
|
|
12463
|
-
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)
|
|
12464
12678
|
await options.recoverUpdate?.(cause);
|
|
12465
12679
|
throw cause;
|
|
12466
12680
|
}
|
|
@@ -12501,14 +12715,14 @@ function startAgentHeartbeat(options) {
|
|
|
12501
12715
|
}
|
|
12502
12716
|
|
|
12503
12717
|
// src/software-helper.ts
|
|
12504
|
-
import { chmodSync as
|
|
12505
|
-
import { connect as
|
|
12506
|
-
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";
|
|
12507
12721
|
|
|
12508
12722
|
// src/service-supervisor.ts
|
|
12509
12723
|
import { createHash as createHash9 } from "crypto";
|
|
12510
|
-
import { existsSync as
|
|
12511
|
-
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";
|
|
12512
12726
|
var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
|
|
12513
12727
|
var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
|
|
12514
12728
|
var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
|
|
@@ -12518,17 +12732,17 @@ var statePath = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
|
|
|
12518
12732
|
var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
|
|
12519
12733
|
var defaultHost = {
|
|
12520
12734
|
write(path2, content, mode) {
|
|
12521
|
-
mkdirSync10(
|
|
12735
|
+
mkdirSync10(dirname9(path2), { recursive: true, mode: 493 });
|
|
12522
12736
|
const next = `${path2}.next`;
|
|
12523
12737
|
writeFileSync11(next, content, { mode });
|
|
12524
|
-
|
|
12738
|
+
renameSync10(next, path2);
|
|
12525
12739
|
},
|
|
12526
12740
|
read: (path2) => readFileSync12(path2, "utf8"),
|
|
12527
|
-
exists:
|
|
12528
|
-
list: (path2) =>
|
|
12741
|
+
exists: existsSync16,
|
|
12742
|
+
list: (path2) => existsSync16(path2) ? readdirSync2(path2) : [],
|
|
12529
12743
|
realpath: realpathSync2,
|
|
12530
12744
|
mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
|
|
12531
|
-
remove: (path2) =>
|
|
12745
|
+
remove: (path2) => rmSync8(path2, { force: true }),
|
|
12532
12746
|
async exec(argv2) {
|
|
12533
12747
|
const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
|
|
12534
12748
|
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
@@ -12724,20 +12938,20 @@ async function activateSupervisedService(request, host = defaultHost) {
|
|
|
12724
12938
|
|
|
12725
12939
|
// src/container-supervisor.ts
|
|
12726
12940
|
import { createHash as createHash10 } from "crypto";
|
|
12727
|
-
import { existsSync as
|
|
12728
|
-
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";
|
|
12729
12943
|
var defaultHost2 = {
|
|
12730
12944
|
realpath: realpathSync3,
|
|
12731
|
-
exists:
|
|
12945
|
+
exists: existsSync17,
|
|
12732
12946
|
read: (path2) => readFileSync13(path2, "utf8"),
|
|
12733
12947
|
write(path2, content, mode) {
|
|
12734
|
-
mkdirSync11(
|
|
12948
|
+
mkdirSync11(dirname10(path2), { recursive: true, mode: 493 });
|
|
12735
12949
|
const next = `${path2}.next`;
|
|
12736
12950
|
writeFileSync12(next, content, { mode });
|
|
12737
|
-
|
|
12951
|
+
renameSync11(next, path2);
|
|
12738
12952
|
},
|
|
12739
|
-
remove: (path2) =>
|
|
12740
|
-
list: (path2) =>
|
|
12953
|
+
remove: (path2) => rmSync9(path2, { force: true }),
|
|
12954
|
+
list: (path2) => existsSync17(path2) ? readdirSync3(path2) : [],
|
|
12741
12955
|
mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
|
|
12742
12956
|
async exec(argv2) {
|
|
12743
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" } });
|
|
@@ -13026,9 +13240,9 @@ async function activateContainer(requestValue, host = defaultHost2) {
|
|
|
13026
13240
|
|
|
13027
13241
|
// src/deployment-connectivity.ts
|
|
13028
13242
|
import { createHash as createHash11 } from "crypto";
|
|
13029
|
-
import { mkdirSync as mkdirSync12, renameSync as
|
|
13243
|
+
import { mkdirSync as mkdirSync12, renameSync as renameSync12, writeFileSync as writeFileSync13 } from "fs";
|
|
13030
13244
|
import { createConnection as createConnection2 } from "net";
|
|
13031
|
-
import { dirname as
|
|
13245
|
+
import { dirname as dirname11 } from "path";
|
|
13032
13246
|
var TOKEN2 = /^[A-Za-z0-9._-]{40,16384}$/;
|
|
13033
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;
|
|
13034
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])?$/;
|
|
@@ -13041,7 +13255,7 @@ var checked7 = async (host, argv2, label) => {
|
|
|
13041
13255
|
return result.output.trim();
|
|
13042
13256
|
};
|
|
13043
13257
|
async function sealWithSystemd(name, path2, value) {
|
|
13044
|
-
mkdirSync12(
|
|
13258
|
+
mkdirSync12(dirname11(path2), { recursive: true, mode: 448 });
|
|
13045
13259
|
const next = `${path2}.next`;
|
|
13046
13260
|
const child = Bun.spawn(["/usr/bin/systemd-creds", "encrypt", `--name=${name}`, "-", next], {
|
|
13047
13261
|
stdin: "pipe",
|
|
@@ -13057,15 +13271,15 @@ async function sealWithSystemd(name, path2, value) {
|
|
|
13057
13271
|
]);
|
|
13058
13272
|
if (exitCode !== 0)
|
|
13059
13273
|
throw new Error(`systemd credential sealing failed: ${`${stdout}${stderr}`.replaceAll(value, "[REDACTED]").slice(0, 512)}`);
|
|
13060
|
-
|
|
13274
|
+
renameSync12(next, path2);
|
|
13061
13275
|
}
|
|
13062
13276
|
var defaultHost3 = {
|
|
13063
13277
|
seal: sealWithSystemd,
|
|
13064
13278
|
write(path2, content, mode) {
|
|
13065
|
-
mkdirSync12(
|
|
13279
|
+
mkdirSync12(dirname11(path2), { recursive: true, mode: 493 });
|
|
13066
13280
|
const next = `${path2}.next`;
|
|
13067
13281
|
writeFileSync13(next, content, { mode });
|
|
13068
|
-
|
|
13282
|
+
renameSync12(next, path2);
|
|
13069
13283
|
},
|
|
13070
13284
|
async exec(argv2) {
|
|
13071
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" } });
|
|
@@ -13304,9 +13518,9 @@ var MAX_REQUEST_BYTES6 = 128 * 1024;
|
|
|
13304
13518
|
var MAX_PENDING_REQUESTS = 128;
|
|
13305
13519
|
function startSoftwareHelper(options = {}) {
|
|
13306
13520
|
const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
|
|
13307
|
-
if (
|
|
13308
|
-
|
|
13309
|
-
mkdirSync13(
|
|
13521
|
+
if (existsSync18(socketPath))
|
|
13522
|
+
unlinkSync12(socketPath);
|
|
13523
|
+
mkdirSync13(dirname12(socketPath), { recursive: true, mode: 488 });
|
|
13310
13524
|
const ensure = options.ensure ?? ensureSoftwareRequirements;
|
|
13311
13525
|
const activate = options.activate ?? activateSupervisedService;
|
|
13312
13526
|
const buildContainer = options.buildContainer ?? buildContainerImage;
|
|
@@ -13314,7 +13528,7 @@ function startSoftwareHelper(options = {}) {
|
|
|
13314
13528
|
const applyConnectivity = options.applyConnectivity ?? applyDeploymentConnectivity;
|
|
13315
13529
|
let tail = Promise.resolve();
|
|
13316
13530
|
let pending = 0;
|
|
13317
|
-
const server =
|
|
13531
|
+
const server = createServer8((socket) => {
|
|
13318
13532
|
let buffer = "";
|
|
13319
13533
|
socket.on("data", (chunk) => {
|
|
13320
13534
|
buffer += chunk.toString("utf8");
|
|
@@ -13393,12 +13607,12 @@ function startSoftwareHelper(options = {}) {
|
|
|
13393
13607
|
});
|
|
13394
13608
|
socket.on("error", () => socket.destroy());
|
|
13395
13609
|
});
|
|
13396
|
-
server.listen(socketPath, () =>
|
|
13610
|
+
server.listen(socketPath, () => chmodSync15(socketPath, 432));
|
|
13397
13611
|
return server;
|
|
13398
13612
|
}
|
|
13399
13613
|
function requestContainer(op, request, socketPath, timeoutMs) {
|
|
13400
13614
|
return new Promise((resolve6, reject) => {
|
|
13401
|
-
const socket =
|
|
13615
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op, request })}
|
|
13402
13616
|
`));
|
|
13403
13617
|
let buffer = "";
|
|
13404
13618
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -13436,7 +13650,7 @@ function requestDeploymentConnectivity(request, socketPath = DEFAULT_SOFTWARE_HE
|
|
|
13436
13650
|
}
|
|
13437
13651
|
function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 10 * 60000) {
|
|
13438
13652
|
return new Promise((resolve6, reject) => {
|
|
13439
|
-
const socket =
|
|
13653
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
|
|
13440
13654
|
`));
|
|
13441
13655
|
let buffer = "";
|
|
13442
13656
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -13465,7 +13679,7 @@ function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_
|
|
|
13465
13679
|
function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
|
|
13466
13680
|
validateSoftwareRequirements(requirements);
|
|
13467
13681
|
return new Promise((resolve6, reject) => {
|
|
13468
|
-
const socket =
|
|
13682
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op: "ensure", requirements })}
|
|
13469
13683
|
`));
|
|
13470
13684
|
let buffer = "";
|
|
13471
13685
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -14425,19 +14639,19 @@ var METAL_SYSTEMD_CREDENTIALS = {
|
|
|
14425
14639
|
|
|
14426
14640
|
// src/platform-bootstrap-runtime.ts
|
|
14427
14641
|
import {
|
|
14428
|
-
chmodSync as
|
|
14642
|
+
chmodSync as chmodSync16,
|
|
14429
14643
|
chownSync,
|
|
14430
14644
|
copyFileSync as copyFileSync2,
|
|
14431
|
-
existsSync as
|
|
14432
|
-
lstatSync as
|
|
14645
|
+
existsSync as existsSync19,
|
|
14646
|
+
lstatSync as lstatSync4,
|
|
14433
14647
|
mkdirSync as mkdirSync14,
|
|
14434
14648
|
readFileSync as readFileSync14,
|
|
14435
14649
|
readdirSync as readdirSync4,
|
|
14436
14650
|
realpathSync as realpathSync5,
|
|
14437
|
-
renameSync as
|
|
14438
|
-
rmSync as
|
|
14651
|
+
renameSync as renameSync13,
|
|
14652
|
+
rmSync as rmSync10,
|
|
14439
14653
|
statSync as statSync4,
|
|
14440
|
-
symlinkSync as
|
|
14654
|
+
symlinkSync as symlinkSync6,
|
|
14441
14655
|
writeFileSync as writeFileSync14
|
|
14442
14656
|
} from "fs";
|
|
14443
14657
|
import { join as join10 } from "path";
|
|
@@ -14492,21 +14706,21 @@ var platformExec = async (argv2) => {
|
|
|
14492
14706
|
};
|
|
14493
14707
|
var replaceLink = (path2, target2) => {
|
|
14494
14708
|
const pending = `${path2}.next`;
|
|
14495
|
-
|
|
14709
|
+
rmSync10(pending, { force: true });
|
|
14496
14710
|
if (!target2) {
|
|
14497
|
-
|
|
14711
|
+
rmSync10(path2, { force: true });
|
|
14498
14712
|
return;
|
|
14499
14713
|
}
|
|
14500
|
-
|
|
14501
|
-
|
|
14714
|
+
symlinkSync6(target2, pending);
|
|
14715
|
+
renameSync13(pending, path2);
|
|
14502
14716
|
};
|
|
14503
14717
|
var secureRelease = (path2, uid, gid) => {
|
|
14504
14718
|
const visit = (current2) => {
|
|
14505
|
-
const metadata =
|
|
14719
|
+
const metadata = lstatSync4(current2);
|
|
14506
14720
|
if (metadata.isSymbolicLink())
|
|
14507
14721
|
throw new Error("release contains a symbolic link");
|
|
14508
14722
|
chownSync(current2, uid, gid);
|
|
14509
|
-
|
|
14723
|
+
chmodSync16(current2, metadata.isDirectory() ? 365 : 292);
|
|
14510
14724
|
if (metadata.isDirectory())
|
|
14511
14725
|
for (const name of readdirSync4(current2))
|
|
14512
14726
|
visit(join10(current2, name));
|
|
@@ -14531,7 +14745,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14531
14745
|
const slots = join10(normalized.root, "slots");
|
|
14532
14746
|
mkdirSync14(slots, { recursive: true, mode: 493 });
|
|
14533
14747
|
const slotFile = join10(normalized.root, ".forge-slot");
|
|
14534
|
-
const previousSlot =
|
|
14748
|
+
const previousSlot = existsSync19(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
|
|
14535
14749
|
const target2 = previousSlot === "blue" ? "green" : "blue";
|
|
14536
14750
|
const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
|
|
14537
14751
|
const targetLink = join10(slots, target2);
|
|
@@ -14571,25 +14785,25 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14571
14785
|
}
|
|
14572
14786
|
const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
|
|
14573
14787
|
const backup = `${upstream}.forgezero-backup`;
|
|
14574
|
-
if (
|
|
14788
|
+
if (existsSync19(upstream))
|
|
14575
14789
|
copyFileSync2(upstream, backup);
|
|
14576
14790
|
else
|
|
14577
|
-
|
|
14791
|
+
rmSync10(backup, { force: true });
|
|
14578
14792
|
writeFileSync14(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
|
|
14579
14793
|
`, { mode: 420 });
|
|
14580
14794
|
const test = await exec(["/usr/sbin/nginx", "-t"]);
|
|
14581
14795
|
const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
|
|
14582
14796
|
if (reload.exitCode !== 0) {
|
|
14583
|
-
if (
|
|
14584
|
-
|
|
14797
|
+
if (existsSync19(backup))
|
|
14798
|
+
renameSync13(backup, upstream);
|
|
14585
14799
|
else
|
|
14586
|
-
|
|
14800
|
+
rmSync10(upstream, { force: true });
|
|
14587
14801
|
await exec(["/usr/sbin/nginx", "-t"]);
|
|
14588
14802
|
await exec(["/usr/sbin/nginx", "-s", "reload"]);
|
|
14589
14803
|
await stopTarget();
|
|
14590
14804
|
throw new Error("nginx refused the promoted upstream");
|
|
14591
14805
|
}
|
|
14592
|
-
|
|
14806
|
+
rmSync10(backup, { force: true });
|
|
14593
14807
|
writeFileSync14(slotFile, `${target2}
|
|
14594
14808
|
`, { mode: 420 });
|
|
14595
14809
|
if (previousSlot && previousSlot !== target2) {
|
|
@@ -14599,12 +14813,12 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14599
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);
|
|
14600
14814
|
for (const path2 of old)
|
|
14601
14815
|
if (path2 !== release)
|
|
14602
|
-
|
|
14816
|
+
rmSync10(path2, { recursive: true, force: true });
|
|
14603
14817
|
return { release, slot: target2 };
|
|
14604
14818
|
}
|
|
14605
14819
|
|
|
14606
14820
|
// src/recovery-host.ts
|
|
14607
|
-
import { existsSync as
|
|
14821
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, statSync as statSync5 } from "fs";
|
|
14608
14822
|
import { hostname } from "os";
|
|
14609
14823
|
import { join as join11 } from "path";
|
|
14610
14824
|
var execute2 = async (argv2) => {
|
|
@@ -14630,7 +14844,7 @@ var exactState = (path2, expected) => {
|
|
|
14630
14844
|
throw new Error(`state mismatch: ${expected}`);
|
|
14631
14845
|
};
|
|
14632
14846
|
var nonEmpty = (path2) => {
|
|
14633
|
-
if (!
|
|
14847
|
+
if (!existsSync20(path2) || !statSync5(path2).isFile() || statSync5(path2).size < 1)
|
|
14634
14848
|
throw new Error(`required file missing: ${path2}`);
|
|
14635
14849
|
};
|
|
14636
14850
|
var option = (args, name) => {
|
|
@@ -14726,20 +14940,20 @@ async function runRecoveryHost(args, run2 = execute2) {
|
|
|
14726
14940
|
}
|
|
14727
14941
|
|
|
14728
14942
|
// src/platform-fleet-verification.ts
|
|
14729
|
-
import { lstatSync as
|
|
14943
|
+
import { lstatSync as lstatSync7 } from "fs";
|
|
14730
14944
|
|
|
14731
14945
|
// src/bootstrap.ts
|
|
14732
14946
|
import {
|
|
14733
|
-
chmodSync as
|
|
14734
|
-
existsSync as
|
|
14735
|
-
lstatSync as
|
|
14947
|
+
chmodSync as chmodSync18,
|
|
14948
|
+
existsSync as existsSync22,
|
|
14949
|
+
lstatSync as lstatSync6,
|
|
14736
14950
|
mkdirSync as mkdirSync16,
|
|
14737
14951
|
readFileSync as readFileSync17,
|
|
14738
|
-
renameSync as
|
|
14739
|
-
rmSync as
|
|
14952
|
+
renameSync as renameSync15,
|
|
14953
|
+
rmSync as rmSync12,
|
|
14740
14954
|
writeFileSync as writeFileSync16
|
|
14741
14955
|
} from "fs";
|
|
14742
|
-
import { dirname as
|
|
14956
|
+
import { dirname as dirname14 } from "path";
|
|
14743
14957
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14744
14958
|
|
|
14745
14959
|
// src/provision.ts
|
|
@@ -14796,6 +15010,7 @@ var LIFECYCLE_GROUP = "forgezero-lifecycle";
|
|
|
14796
15010
|
var DEPLOYMENT_RUNNER_UNIT_PATH = "/etc/systemd/system/forgezero-deploy-runner.service";
|
|
14797
15011
|
var AGENT_SOCKET_UNIT_PATH = "/etc/systemd/system/forgezero-agent.socket";
|
|
14798
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";
|
|
14799
15014
|
var DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
|
|
14800
15015
|
var ENROLMENT_UNIT_PATH = "/etc/systemd/system/forgezero-agent-enrol.service";
|
|
14801
15016
|
var LIFECYCLE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-lifecycle-helper.service";
|
|
@@ -14907,6 +15122,7 @@ Type=simple
|
|
|
14907
15122
|
User=root
|
|
14908
15123
|
Group=${AGENT_UPDATE_GROUP}
|
|
14909
15124
|
Environment=FZ_AGENT_UPDATE_SOCKET=${DEFAULT_AGENT_UPDATE_SOCKET}
|
|
15125
|
+
Environment=FZ_AGENT_PUBLIC_SOCKET=${systemdPath(options.socketPath, "agent socket")}
|
|
14910
15126
|
ExecStart=${bin} update-helper
|
|
14911
15127
|
Restart=always
|
|
14912
15128
|
RestartSec=2
|
|
@@ -14951,13 +15167,11 @@ WantedBy=sockets.target
|
|
|
14951
15167
|
`;
|
|
14952
15168
|
}
|
|
14953
15169
|
function agentSocketProxyUnit(options) {
|
|
14954
|
-
const backend =
|
|
15170
|
+
const backend = agentRoutingSocketPath(options.socketPath);
|
|
14955
15171
|
const user = options.user ?? "forgezero";
|
|
14956
15172
|
return `[Unit]
|
|
14957
15173
|
Description=ForgeZero application Vault socket proxy
|
|
14958
15174
|
Documentation=https://www.forgezero.net/docs/agent
|
|
14959
|
-
Requires=forgezero-agent.service
|
|
14960
|
-
After=forgezero-agent.service
|
|
14961
15175
|
|
|
14962
15176
|
[Service]
|
|
14963
15177
|
User=${user}
|
|
@@ -14978,12 +15192,26 @@ RestrictAddressFamilies=AF_UNIX
|
|
|
14978
15192
|
`;
|
|
14979
15193
|
}
|
|
14980
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) {
|
|
14981
15202
|
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
14982
15203
|
const backend = `${socket}.backend`;
|
|
14983
15204
|
if (Buffer.byteLength(backend) > 100)
|
|
14984
15205
|
throw new Error("agent socket path is too long for a Unix socket");
|
|
14985
15206
|
return backend;
|
|
14986
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
|
+
}
|
|
14987
15215
|
var systemdPath = (value, label) => {
|
|
14988
15216
|
if (!value || !/^\/[A-Za-z0-9._@/-]+$/.test(value))
|
|
14989
15217
|
throw new Error(`invalid ${label} path`);
|
|
@@ -15267,7 +15495,11 @@ function agentUnit(options) {
|
|
|
15267
15495
|
}
|
|
15268
15496
|
const environment = [
|
|
15269
15497
|
"NODE_ENV=production",
|
|
15270
|
-
`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,
|
|
15271
15503
|
`FZ_CONTROL_SOCKET=${controlSocketPath}`,
|
|
15272
15504
|
`FZ_SEED_CREDENTIAL=agent-seed`,
|
|
15273
15505
|
`FZ_AGENT_MODE=${options.mode}`,
|
|
@@ -15404,6 +15636,26 @@ ${deploymentWrites}
|
|
|
15404
15636
|
WantedBy=multi-user.target
|
|
15405
15637
|
`;
|
|
15406
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
|
+
}
|
|
15407
15659
|
var renderOperation = (operation) => {
|
|
15408
15660
|
if (operation.kind === "commands")
|
|
15409
15661
|
return operation.commands.map(({ argv: argv2 }) => argv2.join(" ")).join(`
|
|
@@ -15539,6 +15791,7 @@ function planProvision(options) {
|
|
|
15539
15791
|
auxiliaryUnits: [
|
|
15540
15792
|
{ path: AGENT_SOCKET_UNIT_PATH, unit: agentSocketUnit(options) },
|
|
15541
15793
|
{ path: AGENT_SOCKET_PROXY_UNIT_PATH, unit: agentSocketProxyUnit(options) },
|
|
15794
|
+
{ path: AGENT_CANDIDATE_UNIT_PATH, unit: agentCandidateUnit(options) },
|
|
15542
15795
|
{ path: AGENT_UPDATE_HELPER_UNIT_PATH, unit: agentUpdateHelperUnit(options) },
|
|
15543
15796
|
...options.enforceEgress ? [
|
|
15544
15797
|
{ path: AGENT_EGRESS_UNIT_PATH, unit: agentEgressUnit(options) }
|
|
@@ -15670,19 +15923,19 @@ function planProvision(options) {
|
|
|
15670
15923
|
// src/cli/agent-install.ts
|
|
15671
15924
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
15672
15925
|
import {
|
|
15673
|
-
chmodSync as
|
|
15926
|
+
chmodSync as chmodSync17,
|
|
15674
15927
|
copyFileSync as copyFileSync3,
|
|
15675
|
-
existsSync as
|
|
15676
|
-
lstatSync as
|
|
15928
|
+
existsSync as existsSync21,
|
|
15929
|
+
lstatSync as lstatSync5,
|
|
15677
15930
|
mkdirSync as mkdirSync15,
|
|
15678
15931
|
readFileSync as readFileSync16,
|
|
15679
15932
|
realpathSync as realpathSync6,
|
|
15680
|
-
renameSync as
|
|
15681
|
-
rmSync as
|
|
15682
|
-
symlinkSync as
|
|
15933
|
+
renameSync as renameSync14,
|
|
15934
|
+
rmSync as rmSync11,
|
|
15935
|
+
symlinkSync as symlinkSync7,
|
|
15683
15936
|
writeFileSync as writeFileSync15
|
|
15684
15937
|
} from "fs";
|
|
15685
|
-
import { dirname as
|
|
15938
|
+
import { dirname as dirname13 } from "path";
|
|
15686
15939
|
async function readCapabilities(run2) {
|
|
15687
15940
|
const answers = {};
|
|
15688
15941
|
const checks = Object.entries(CAPABILITY_CHECKS);
|
|
@@ -15740,51 +15993,51 @@ var runProvisionOperation = async (operation) => {
|
|
|
15740
15993
|
if (operation.kind === "install-runtime") {
|
|
15741
15994
|
const release = `/opt/forgezero/agent/versions/${operation.version}`;
|
|
15742
15995
|
mkdirSync15(`${release}/dist`, { recursive: true, mode: 493 });
|
|
15743
|
-
mkdirSync15(
|
|
15996
|
+
mkdirSync15(dirname13(operation.binary), { recursive: true, mode: 493 });
|
|
15744
15997
|
copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
|
|
15745
|
-
|
|
15746
|
-
const gitSshSource = `${
|
|
15747
|
-
if (!
|
|
15998
|
+
chmodSync17(`${release}/dist/fz-agent.js`, 493);
|
|
15999
|
+
const gitSshSource = `${dirname13(operation.source)}/fz-git-ssh.js`;
|
|
16000
|
+
if (!existsSync21(gitSshSource))
|
|
15748
16001
|
return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
|
|
15749
16002
|
copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
|
|
15750
|
-
|
|
16003
|
+
chmodSync17(`${release}/dist/fz-git-ssh.js`, 493);
|
|
15751
16004
|
const pending = "/opt/forgezero/agent/current.next";
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
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);
|
|
15757
16010
|
const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
|
|
15758
|
-
|
|
15759
|
-
|
|
16011
|
+
rmSync11(gitSshBinary, { force: true });
|
|
16012
|
+
symlinkSync7("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
|
|
15760
16013
|
return { stdout: "", exitCode: 0 };
|
|
15761
16014
|
}
|
|
15762
16015
|
if (operation.kind === "ensure-seed") {
|
|
15763
|
-
if (
|
|
16016
|
+
if (existsSync21(operation.credential) && lstatSync5(operation.credential).size > 0)
|
|
15764
16017
|
return { stdout: "", exitCode: 0 };
|
|
15765
16018
|
const seed = randomBytes6(32).toString("base64url");
|
|
15766
16019
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
|
|
15767
16020
|
if (result.exitCode === 0)
|
|
15768
|
-
|
|
16021
|
+
chmodSync17(operation.credential, 256);
|
|
15769
16022
|
return result;
|
|
15770
16023
|
}
|
|
15771
16024
|
if (operation.kind === "ensure-git-identity") {
|
|
15772
16025
|
const key = "/run/forgezero-git-deploy-key";
|
|
15773
16026
|
const publicKey = `${key}.pub`;
|
|
15774
16027
|
try {
|
|
15775
|
-
if (!
|
|
15776
|
-
|
|
15777
|
-
|
|
16028
|
+
if (!existsSync21(operation.credential) || lstatSync5(operation.credential).size < 1) {
|
|
16029
|
+
rmSync11(key, { force: true });
|
|
16030
|
+
rmSync11(publicKey, { force: true });
|
|
15778
16031
|
let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
|
|
15779
16032
|
if (result.exitCode !== 0)
|
|
15780
16033
|
return result;
|
|
15781
16034
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=git-deploy-key", key, operation.credential]);
|
|
15782
16035
|
if (result.exitCode !== 0)
|
|
15783
16036
|
return result;
|
|
15784
|
-
|
|
16037
|
+
chmodSync17(operation.credential, 256);
|
|
15785
16038
|
}
|
|
15786
|
-
if (!
|
|
15787
|
-
if (!
|
|
16039
|
+
if (!existsSync21(operation.publicKey) || lstatSync5(operation.publicKey).size < 1) {
|
|
16040
|
+
if (!existsSync21(key)) {
|
|
15788
16041
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
|
|
15789
16042
|
if (decrypted.exitCode !== 0)
|
|
15790
16043
|
return decrypted;
|
|
@@ -15797,25 +16050,25 @@ var runProvisionOperation = async (operation) => {
|
|
|
15797
16050
|
}
|
|
15798
16051
|
return { stdout: "", exitCode: 0 };
|
|
15799
16052
|
} finally {
|
|
15800
|
-
|
|
15801
|
-
|
|
16053
|
+
rmSync11(key, { force: true });
|
|
16054
|
+
rmSync11(publicKey, { force: true });
|
|
15802
16055
|
}
|
|
15803
16056
|
}
|
|
15804
16057
|
if (operation.kind === "ensure-bootstrap-ssh-identity") {
|
|
15805
16058
|
const key = "/run/forgezero-bootstrap-ssh-key";
|
|
15806
16059
|
const generatedPublicKey = `${key}.pub`;
|
|
15807
16060
|
try {
|
|
15808
|
-
if (!
|
|
15809
|
-
|
|
15810
|
-
|
|
16061
|
+
if (!existsSync21(operation.credential) || lstatSync5(operation.credential).size < 1) {
|
|
16062
|
+
rmSync11(key, { force: true });
|
|
16063
|
+
rmSync11(generatedPublicKey, { force: true });
|
|
15811
16064
|
let result;
|
|
15812
16065
|
if (operation.source) {
|
|
15813
|
-
const source =
|
|
16066
|
+
const source = existsSync21(operation.source) ? lstatSync5(operation.source) : undefined;
|
|
15814
16067
|
if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
|
|
15815
16068
|
return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
|
|
15816
16069
|
}
|
|
15817
16070
|
copyFileSync3(operation.source, key);
|
|
15818
|
-
|
|
16071
|
+
chmodSync17(key, 384);
|
|
15819
16072
|
} else {
|
|
15820
16073
|
result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-bootstrap-runner", "-f", key]);
|
|
15821
16074
|
if (result.exitCode !== 0)
|
|
@@ -15828,48 +16081,48 @@ var runProvisionOperation = async (operation) => {
|
|
|
15828
16081
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
|
|
15829
16082
|
if (result.exitCode !== 0)
|
|
15830
16083
|
return result;
|
|
15831
|
-
|
|
16084
|
+
chmodSync17(operation.credential, 256);
|
|
15832
16085
|
}
|
|
15833
|
-
if (!
|
|
15834
|
-
if (!
|
|
16086
|
+
if (!existsSync21(operation.publicKey) || lstatSync5(operation.publicKey).size < 1) {
|
|
16087
|
+
if (!existsSync21(key)) {
|
|
15835
16088
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
|
|
15836
16089
|
if (decrypted.exitCode !== 0)
|
|
15837
16090
|
return decrypted;
|
|
15838
|
-
|
|
16091
|
+
chmodSync17(key, 384);
|
|
15839
16092
|
}
|
|
15840
16093
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
15841
16094
|
if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
|
|
15842
16095
|
return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
|
|
15843
16096
|
}
|
|
15844
|
-
mkdirSync15(
|
|
16097
|
+
mkdirSync15(dirname13(operation.publicKey), { recursive: true, mode: 493 });
|
|
15845
16098
|
writeFileSync15(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
|
|
15846
16099
|
`, { mode: 292 });
|
|
15847
|
-
|
|
16100
|
+
chmodSync17(operation.publicKey, 292);
|
|
15848
16101
|
}
|
|
15849
16102
|
if (operation.source)
|
|
15850
|
-
|
|
16103
|
+
rmSync11(operation.source, { force: true });
|
|
15851
16104
|
return { stdout: "", exitCode: 0 };
|
|
15852
16105
|
} finally {
|
|
15853
|
-
|
|
15854
|
-
|
|
16106
|
+
rmSync11(key, { force: true });
|
|
16107
|
+
rmSync11(generatedPublicKey, { force: true });
|
|
15855
16108
|
}
|
|
15856
16109
|
}
|
|
15857
16110
|
if (operation.kind === "ensure-enrolment") {
|
|
15858
|
-
if (
|
|
16111
|
+
if (existsSync21(operation.state) && lstatSync5(operation.state).size > 0 || existsSync21(operation.credential) && lstatSync5(operation.credential).size > 0)
|
|
15859
16112
|
return { stdout: "", exitCode: 0 };
|
|
15860
|
-
if (!
|
|
16113
|
+
if (!existsSync21(operation.source))
|
|
15861
16114
|
return { stdout: "enrolment source is missing", exitCode: 1 };
|
|
15862
16115
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
|
|
15863
16116
|
if (result.exitCode === 0) {
|
|
15864
|
-
|
|
15865
|
-
|
|
16117
|
+
chmodSync17(operation.credential, 256);
|
|
16118
|
+
rmSync11(operation.source, { force: true });
|
|
15866
16119
|
}
|
|
15867
16120
|
return result;
|
|
15868
16121
|
}
|
|
15869
16122
|
if (operation.kind === "wait-socket") {
|
|
15870
16123
|
for (let attempt = 0;attempt < operation.attempts; attempt += 1) {
|
|
15871
16124
|
try {
|
|
15872
|
-
if (
|
|
16125
|
+
if (lstatSync5(operation.path).isSocket())
|
|
15873
16126
|
return { stdout: "", exitCode: 0 };
|
|
15874
16127
|
} catch {}
|
|
15875
16128
|
await Bun.sleep(operation.intervalMs);
|
|
@@ -15877,7 +16130,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
15877
16130
|
return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
|
|
15878
16131
|
}
|
|
15879
16132
|
if (operation.kind === "verify-file")
|
|
15880
|
-
return
|
|
16133
|
+
return existsSync21(operation.path) && lstatSync5(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
|
|
15881
16134
|
if (operation.kind === "verify-egress") {
|
|
15882
16135
|
const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
|
|
15883
16136
|
if (active.exitCode !== 0)
|
|
@@ -15911,7 +16164,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
15911
16164
|
const key = "/run/cloudflare-warp-key.gpg";
|
|
15912
16165
|
writeFileSync15(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
|
|
15913
16166
|
let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
|
|
15914
|
-
|
|
16167
|
+
rmSync11(key, { force: true });
|
|
15915
16168
|
if (result.exitCode !== 0)
|
|
15916
16169
|
return result;
|
|
15917
16170
|
const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
|
|
@@ -15932,7 +16185,7 @@ async function localRunner(operation) {
|
|
|
15932
16185
|
if (capability.kind === "version")
|
|
15933
16186
|
return fixed(capability.argv);
|
|
15934
16187
|
try {
|
|
15935
|
-
const metadata =
|
|
16188
|
+
const metadata = lstatSync5(capability.path);
|
|
15936
16189
|
const present = capability.nodeType === "directory" ? metadata.isDirectory() : true;
|
|
15937
16190
|
return { stdout: present ? `yes
|
|
15938
16191
|
` : `no
|
|
@@ -16230,19 +16483,19 @@ function localBootstrapHost() {
|
|
|
16230
16483
|
};
|
|
16231
16484
|
return {
|
|
16232
16485
|
uid: () => process.getuid?.() ?? -1,
|
|
16233
|
-
exists:
|
|
16486
|
+
exists: existsSync22,
|
|
16234
16487
|
read: (path2) => readFileSync17(path2, "utf8"),
|
|
16235
16488
|
write(path2, content, mode) {
|
|
16236
|
-
mkdirSync16(
|
|
16489
|
+
mkdirSync16(dirname14(path2), { recursive: true, mode: 493 });
|
|
16237
16490
|
const temporary = `${path2}.next.${process.pid}`;
|
|
16238
16491
|
writeFileSync16(temporary, content, { mode });
|
|
16239
|
-
|
|
16240
|
-
|
|
16492
|
+
chmodSync18(temporary, mode);
|
|
16493
|
+
renameSync15(temporary, path2);
|
|
16241
16494
|
},
|
|
16242
16495
|
mkdir: (path2, mode) => mkdirSync16(path2, { recursive: true, mode }),
|
|
16243
|
-
remove: (path2) =>
|
|
16496
|
+
remove: (path2) => rmSync12(path2, { force: true }),
|
|
16244
16497
|
inspect(path2) {
|
|
16245
|
-
const value =
|
|
16498
|
+
const value = lstatSync6(path2);
|
|
16246
16499
|
return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
|
|
16247
16500
|
},
|
|
16248
16501
|
exec: execute3,
|
|
@@ -16263,9 +16516,9 @@ function localBootstrapHost() {
|
|
|
16263
16516
|
},
|
|
16264
16517
|
async installAgent(config, phase) {
|
|
16265
16518
|
const capabilities = await readCapabilities(localRunner);
|
|
16266
|
-
const hasBinding =
|
|
16267
|
-
const hasEnrolCredential =
|
|
16268
|
-
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) ? {
|
|
16269
16522
|
path: config.bootstrapBundle.bundleFile,
|
|
16270
16523
|
manifestPath: config.bootstrapBundle.manifestFile,
|
|
16271
16524
|
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync17(config.bootstrapBundle.manifestFile, "utf8")))
|
|
@@ -16281,7 +16534,7 @@ function localBootstrapHost() {
|
|
|
16281
16534
|
databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
|
|
16282
16535
|
databasePorts: [8529]
|
|
16283
16536
|
};
|
|
16284
|
-
mkdirSync16(
|
|
16537
|
+
mkdirSync16(dirname14(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
|
|
16285
16538
|
writeFileSync16(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
|
|
16286
16539
|
`, { mode: 256 });
|
|
16287
16540
|
}
|
|
@@ -16292,7 +16545,7 @@ function localBootstrapHost() {
|
|
|
16292
16545
|
initialBundle
|
|
16293
16546
|
});
|
|
16294
16547
|
for (const unit3 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
|
|
16295
|
-
mkdirSync16(
|
|
16548
|
+
mkdirSync16(dirname14(unit3.path), { recursive: true, mode: 493 });
|
|
16296
16549
|
writeFileSync16(unit3.path, unit3.unit, { mode: 420 });
|
|
16297
16550
|
}
|
|
16298
16551
|
await applyPlan(plan, localRunner);
|
|
@@ -16307,7 +16560,7 @@ var localRuntime = () => ({
|
|
|
16307
16560
|
bootstrapStatus: () => bootstrapStatus(),
|
|
16308
16561
|
characterDevice(path2) {
|
|
16309
16562
|
try {
|
|
16310
|
-
return
|
|
16563
|
+
return lstatSync7(path2).isCharacterDevice();
|
|
16311
16564
|
} catch {
|
|
16312
16565
|
return false;
|
|
16313
16566
|
}
|
|
@@ -16346,9 +16599,9 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
|
|
|
16346
16599
|
|
|
16347
16600
|
// src/community-rehearsal-host.ts
|
|
16348
16601
|
import { createHash as createHash12 } from "crypto";
|
|
16349
|
-
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";
|
|
16350
16603
|
import { isIP as isIP5 } from "net";
|
|
16351
|
-
import { dirname as
|
|
16604
|
+
import { dirname as dirname15 } from "path";
|
|
16352
16605
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
16353
16606
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
16354
16607
|
var COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
@@ -16562,19 +16815,19 @@ async function exec(argv2, stdin) {
|
|
|
16562
16815
|
async function applyOperations(operations) {
|
|
16563
16816
|
for (const operation of operations) {
|
|
16564
16817
|
if (operation.kind === "remove-tree")
|
|
16565
|
-
|
|
16818
|
+
rmSync13(operation.path, { recursive: true, force: true });
|
|
16566
16819
|
else if (operation.kind === "write") {
|
|
16567
|
-
mkdirSync17(
|
|
16820
|
+
mkdirSync17(dirname15(operation.path), { recursive: true });
|
|
16568
16821
|
writeFileSync17(operation.path, operation.content, { mode: operation.mode });
|
|
16569
16822
|
} else if (operation.kind === "unlink") {
|
|
16570
16823
|
try {
|
|
16571
|
-
|
|
16824
|
+
unlinkSync13(operation.path);
|
|
16572
16825
|
} catch (cause) {
|
|
16573
16826
|
if (cause.code !== "ENOENT")
|
|
16574
16827
|
throw cause;
|
|
16575
16828
|
}
|
|
16576
16829
|
} else if (operation.kind === "symlink")
|
|
16577
|
-
|
|
16830
|
+
symlinkSync8(operation.target, operation.path);
|
|
16578
16831
|
else {
|
|
16579
16832
|
const result = await exec(operation.argv, operation.stdin);
|
|
16580
16833
|
if (!(operation.accepted ?? [0]).includes(result.exitCode))
|
|
@@ -16616,7 +16869,7 @@ async function runCommunityRehearsalHost(request) {
|
|
|
16616
16869
|
return { ok: true, action: request.action, node: node.name };
|
|
16617
16870
|
}
|
|
16618
16871
|
if (request.action === "prepare") {
|
|
16619
|
-
const metadata =
|
|
16872
|
+
const metadata = lstatSync8(request.archivePath);
|
|
16620
16873
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash12("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
|
16621
16874
|
throw new Error("community rehearsal archive is not the declared bounded release");
|
|
16622
16875
|
}
|
|
@@ -16666,9 +16919,9 @@ async function runCommunityRehearsalHost(request) {
|
|
|
16666
16919
|
}
|
|
16667
16920
|
|
|
16668
16921
|
// src/supervised-app.ts
|
|
16669
|
-
import { lstatSync as
|
|
16922
|
+
import { lstatSync as lstatSync9, readFileSync as readFileSync19 } from "fs";
|
|
16670
16923
|
function readConfig(path2) {
|
|
16671
|
-
const stat =
|
|
16924
|
+
const stat = lstatSync9(path2);
|
|
16672
16925
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== 0 || (stat.mode & 18) !== 0 || stat.size > 128 * 1024) {
|
|
16673
16926
|
throw new Error("supervised app config is unsafe");
|
|
16674
16927
|
}
|
|
@@ -16851,17 +17104,17 @@ function agentCommandArgs(args, command3) {
|
|
|
16851
17104
|
return args.slice(index + 1);
|
|
16852
17105
|
}
|
|
16853
17106
|
function loadOrCreateSeed(path2) {
|
|
16854
|
-
if (
|
|
17107
|
+
if (existsSync23(path2)) {
|
|
16855
17108
|
const seed2 = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
16856
17109
|
if (seed2.length < 32) {
|
|
16857
17110
|
throw new Error(`agent: the seed at ${path2} is too short to derive a key from.`);
|
|
16858
17111
|
}
|
|
16859
17112
|
return seed2;
|
|
16860
17113
|
}
|
|
16861
|
-
mkdirSync18(
|
|
17114
|
+
mkdirSync18(dirname16(path2), { recursive: true });
|
|
16862
17115
|
const seed = new Uint8Array(randomBytes7(32));
|
|
16863
17116
|
writeFileSync18(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
|
|
16864
|
-
|
|
17117
|
+
chmodSync19(path2, 384);
|
|
16865
17118
|
return seed;
|
|
16866
17119
|
}
|
|
16867
17120
|
var DEFAULT_SOCKET_PATH = DEFAULT_SOCKET;
|
|
@@ -16890,7 +17143,7 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
|
|
|
16890
17143
|
if (!directory)
|
|
16891
17144
|
throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the node identity.");
|
|
16892
17145
|
const path2 = `${directory}/${name}`;
|
|
16893
|
-
if (!
|
|
17146
|
+
if (!existsSync23(path2))
|
|
16894
17147
|
throw new Error(`agent: the systemd credential ${name} is missing at ${path2}.`);
|
|
16895
17148
|
const seed = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
16896
17149
|
if (seed.length < 32)
|
|
@@ -17052,7 +17305,7 @@ if (import.meta.main) {
|
|
|
17052
17305
|
if (args.length !== 1)
|
|
17053
17306
|
throw new Error("project-release-check accepts no coordinates");
|
|
17054
17307
|
for (const relative of ["src/index.ts", "bun.lock", "src/generated/shared/contract-manifest.json"]) {
|
|
17055
|
-
const stat =
|
|
17308
|
+
const stat = lstatSync10(join12(process.cwd(), relative));
|
|
17056
17309
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1) {
|
|
17057
17310
|
throw new Error(`project release is missing ${relative}`);
|
|
17058
17311
|
}
|
|
@@ -17391,9 +17644,9 @@ if (import.meta.main) {
|
|
|
17391
17644
|
vcpu: cpus2().length,
|
|
17392
17645
|
memoryGib: Math.max(1, Math.floor(totalmem2() / 1024 ** 3)),
|
|
17393
17646
|
diskGib: Math.max(1, Math.floor(Number(filesystem.blocks) * Number(filesystem.bsize) / 1024 ** 3)),
|
|
17394
|
-
kvm:
|
|
17395
|
-
snpHost:
|
|
17396
|
-
helper:
|
|
17647
|
+
kvm: existsSync23("/dev/kvm"),
|
|
17648
|
+
snpHost: existsSync23("/dev/sev"),
|
|
17649
|
+
helper: existsSync23(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
17397
17650
|
}
|
|
17398
17651
|
};
|
|
17399
17652
|
const envelope = signRequest(keys2, keys2.ed25519.publicKey, {
|
|
@@ -17420,6 +17673,19 @@ if (import.meta.main) {
|
|
|
17420
17673
|
const keys2 = deriveKeysFromSeed(seed);
|
|
17421
17674
|
seed.fill(0);
|
|
17422
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
|
+
}
|
|
17423
17689
|
const pull = startProvisioningPull({
|
|
17424
17690
|
apiUrl: process.env.FZ_API,
|
|
17425
17691
|
nodeKey: nodeKey2,
|
|
@@ -17428,9 +17694,9 @@ if (import.meta.main) {
|
|
|
17428
17694
|
metalHostname: process.env.FZ_METAL_HOSTNAME,
|
|
17429
17695
|
run: (claim) => requestMetalProvision(claim, process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET),
|
|
17430
17696
|
metalPreflight: () => ({
|
|
17431
|
-
snpHost:
|
|
17432
|
-
kvm:
|
|
17433
|
-
helper:
|
|
17697
|
+
snpHost: existsSync23("/dev/sev"),
|
|
17698
|
+
kvm: existsSync23("/dev/kvm"),
|
|
17699
|
+
helper: existsSync23(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
17434
17700
|
}),
|
|
17435
17701
|
onEvent: (event, detail) => console.log(`[metal-agent] ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
|
|
17436
17702
|
});
|
|
@@ -17513,7 +17779,11 @@ if (import.meta.main) {
|
|
|
17513
17779
|
const telemetry = new AgentTelemetryRuntime(createAgentTelemetry(resolveAgentTelemetryConfig()));
|
|
17514
17780
|
telemetry.event("agent.started");
|
|
17515
17781
|
telemetry.setDraining(false);
|
|
17516
|
-
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;
|
|
17517
17787
|
const running = runAgent({
|
|
17518
17788
|
socketPath: process.env.FZ_SOCKET_PATH ?? DEFAULT_SOCKET_PATH,
|
|
17519
17789
|
seedCredential: process.env.FZ_SEED_CREDENTIAL,
|
|
@@ -17528,8 +17798,8 @@ if (import.meta.main) {
|
|
|
17528
17798
|
const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
|
|
17529
17799
|
let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
|
|
17530
17800
|
const enrolmentCredential = process.env.FZ_ENROL_TOKEN_CREDENTIAL;
|
|
17531
|
-
const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY &&
|
|
17532
|
-
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));
|
|
17533
17803
|
if (!binding && (enrolmentCredentialAvailable || legacyEnrolmentFileAvailable) && process.env.FZ_API) {
|
|
17534
17804
|
binding = await enrolGuestIdentity({
|
|
17535
17805
|
apiUrl: process.env.FZ_API,
|
|
@@ -17549,6 +17819,7 @@ if (import.meta.main) {
|
|
|
17549
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;
|
|
17550
17820
|
let secretCache;
|
|
17551
17821
|
let vaultSync;
|
|
17822
|
+
let initialVaultState = "unbound";
|
|
17552
17823
|
if (binding && attestationSource && nodeApiUrl) {
|
|
17553
17824
|
const result = await telemetry.observe("attestation.refresh", () => attestNodeOnce({
|
|
17554
17825
|
apiUrl: nodeApiUrl,
|
|
@@ -17566,6 +17837,7 @@ if (import.meta.main) {
|
|
|
17566
17837
|
projectKey: binding.projectKey
|
|
17567
17838
|
});
|
|
17568
17839
|
const initialVault = await telemetry.observe("vault.sync", () => initializeVaultReplica(secretCache), (result) => result.state === "ready" ? "success" : "failed");
|
|
17840
|
+
initialVaultState = initialVault.state;
|
|
17569
17841
|
running.setVault(secretCache, binding.projectKey);
|
|
17570
17842
|
vaultSync = startNodeVaultSync(secretCache, {
|
|
17571
17843
|
telemetry,
|
|
@@ -17579,6 +17851,31 @@ if (import.meta.main) {
|
|
|
17579
17851
|
console.error("[agent] vault replica unavailable; authenticated refresh remains active and only explicit " + "same-name systemd deployment fallbacks are available");
|
|
17580
17852
|
}
|
|
17581
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
|
+
}
|
|
17582
17879
|
const attestationLoop = attestationSource && nodeApiUrl ? startNodeAttestation({
|
|
17583
17880
|
apiUrl: nodeApiUrl,
|
|
17584
17881
|
nodeKey,
|
|
@@ -17607,7 +17904,7 @@ if (import.meta.main) {
|
|
|
17607
17904
|
const bootstrapEnabled = process.env.FZ_BOOTSTRAP_PULL === "true";
|
|
17608
17905
|
const bootstrapCredential = process.env.FZ_BOOTSTRAP_SSH_KEY_CREDENTIAL;
|
|
17609
17906
|
const bootstrapKeyPath = bootstrapCredential && process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/${bootstrapCredential}` : undefined;
|
|
17610
|
-
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)) {
|
|
17611
17908
|
throw new Error("agent: bootstrap runner requires enrolment, API, local SSH credential and target OTLP coordinate");
|
|
17612
17909
|
}
|
|
17613
17910
|
const bootstrapPull = bootstrapEnabled ? startSshBootstrapPull({
|