@forgezero/agent 0.1.86 → 0.1.88
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 +19 -0
- package/dist/agent-heartbeat.d.ts +3 -3
- package/dist/agent-heartbeat.js +247 -60
- package/dist/agent-update-helper.d.ts +12 -2
- package/dist/agent-update-helper.js +210 -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 +569 -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 +415 -211
- package/dist/provision.d.ts +16 -1
- package/dist/provision.js +332 -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.88";
|
|
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 && value.nodeKey === expected.nodeKey && value.bound === expected.bound && expected.vault.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,37 @@ 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, expectedNodeKey, 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({
|
|
12200
|
+
version: staged.version,
|
|
12201
|
+
nodeKey: expectedNodeKey,
|
|
12202
|
+
bound: true,
|
|
12203
|
+
vault: target2 === "compute" ? ["ready"] : ["unbound"]
|
|
12204
|
+
})) {
|
|
12205
|
+
await run2({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target2)] });
|
|
12206
|
+
clearAgentCandidate(dirname8(staged.currentLink));
|
|
12207
|
+
throw new Error("the candidate Agent did not prove its identity and durable state");
|
|
12208
|
+
}
|
|
12209
|
+
};
|
|
12210
|
+
var stopCandidate = async (staged, target2, run2) => {
|
|
12211
|
+
await run2({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target2)] });
|
|
12212
|
+
clearAgentCandidate(dirname8(staged.currentLink));
|
|
12213
|
+
};
|
|
12214
|
+
var socketPaths = (publicSocket = process.env.FZ_AGENT_PUBLIC_SOCKET ?? DEFAULT_SOCKET) => ({
|
|
12215
|
+
route: `${publicSocket}.backend`,
|
|
12216
|
+
active: `${publicSocket}.backend.active`,
|
|
12217
|
+
candidate: `${publicSocket}.backend.candidate`
|
|
12218
|
+
});
|
|
12083
12219
|
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
12220
|
function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
12085
12221
|
return new Promise((resolve4) => {
|
|
12086
|
-
const socket =
|
|
12222
|
+
const socket = connect6(socketPath);
|
|
12087
12223
|
let settled = false;
|
|
12088
12224
|
let buffer = "";
|
|
12089
12225
|
const finish = (value) => {
|
|
@@ -12116,12 +12252,13 @@ function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
|
12116
12252
|
async function activateAgentRelease(staged, options = {}) {
|
|
12117
12253
|
const run2 = options.run ?? runCommand;
|
|
12118
12254
|
const target2 = options.target ?? "compute";
|
|
12119
|
-
const
|
|
12255
|
+
const paths = socketPaths(options.publicSocketPath);
|
|
12256
|
+
const probe = options.probe ?? (target2 === "compute" ? () => probeAgentSocket(paths.active) : targetProbe(target2, run2));
|
|
12120
12257
|
const now = options.now ?? Date.now;
|
|
12121
12258
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12122
12259
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12123
|
-
const previous = readJournal(journalPath,
|
|
12124
|
-
const attemptId = options.attemptId ??
|
|
12260
|
+
const previous = readJournal(journalPath, dirname8(staged.currentLink));
|
|
12261
|
+
const attemptId = options.attemptId ?? randomUUID4();
|
|
12125
12262
|
if (!ATTEMPT_ID.test(attemptId))
|
|
12126
12263
|
throw new Error("Agent update attempt ID is invalid");
|
|
12127
12264
|
const startedAtTs = now();
|
|
@@ -12143,11 +12280,19 @@ async function activateAgentRelease(staged, options = {}) {
|
|
|
12143
12280
|
writeUpdateState(journalPath, receiptPath, journal);
|
|
12144
12281
|
let selectionAttempted = false;
|
|
12145
12282
|
try {
|
|
12283
|
+
if (!options.candidatePrepared) {
|
|
12284
|
+
throw new Error("direct activation requires a separately identity-bound candidate preparation");
|
|
12285
|
+
}
|
|
12286
|
+
if (target2 === "compute")
|
|
12287
|
+
switchAgentSocketRoute(paths.route, paths.candidate);
|
|
12146
12288
|
selectionAttempted = true;
|
|
12147
12289
|
selectAgentRelease(staged);
|
|
12148
12290
|
await restartAgent(target2, run2);
|
|
12149
12291
|
if (!await probe())
|
|
12150
|
-
throw new Error("the replacement Agent did not answer its
|
|
12292
|
+
throw new Error("the replacement Agent did not answer its active Vault backend");
|
|
12293
|
+
if (target2 === "compute")
|
|
12294
|
+
switchAgentSocketRoute(paths.route, paths.active);
|
|
12295
|
+
await stopCandidate(staged, target2, run2);
|
|
12151
12296
|
journal = { ...journal, outcome: "active", updatedAtTs: now(), rollbackHealthy: undefined };
|
|
12152
12297
|
writeUpdateState(journalPath, receiptPath, journal);
|
|
12153
12298
|
run2({
|
|
@@ -12165,10 +12310,13 @@ async function activateAgentRelease(staged, options = {}) {
|
|
|
12165
12310
|
restored = true;
|
|
12166
12311
|
await restartAgent(target2, run2);
|
|
12167
12312
|
rollbackHealthy = await probe();
|
|
12313
|
+
if (rollbackHealthy && target2 === "compute")
|
|
12314
|
+
switchAgentSocketRoute(paths.route, paths.active);
|
|
12168
12315
|
} catch {
|
|
12169
12316
|
rollbackHealthy = false;
|
|
12170
12317
|
}
|
|
12171
12318
|
}
|
|
12319
|
+
await stopCandidate(staged, target2, run2).catch(() => {});
|
|
12172
12320
|
const failures = failureCount + 1;
|
|
12173
12321
|
const updatedAtTs = now();
|
|
12174
12322
|
journal = {
|
|
@@ -12188,28 +12336,38 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
12188
12336
|
const root = resolve3(options.root ?? DEFAULT_AGENT_RELEASE_ROOT);
|
|
12189
12337
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12190
12338
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12339
|
+
const run2 = options.run ?? runCommand;
|
|
12191
12340
|
const journal = readJournal(journalPath, root);
|
|
12192
|
-
if (!journal)
|
|
12341
|
+
if (!journal) {
|
|
12342
|
+
for (const target2 of ["compute", "metal"]) {
|
|
12343
|
+
await run2({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target2)] });
|
|
12344
|
+
}
|
|
12345
|
+
clearAgentCandidate(root);
|
|
12193
12346
|
return;
|
|
12347
|
+
}
|
|
12194
12348
|
if (journal.outcome !== "activating") {
|
|
12349
|
+
await stopCandidate(stagedFromJournal(journal, root), journal.target, run2).catch(() => {});
|
|
12195
12350
|
writeAtomic(receiptPath, publicReceipt(journal), 416);
|
|
12196
12351
|
return publicReceipt(journal);
|
|
12197
12352
|
}
|
|
12198
12353
|
const staged = stagedFromJournal(journal, root);
|
|
12199
|
-
if (!
|
|
12354
|
+
if (!existsSync14(join8(root, journal.previousTarget))) {
|
|
12200
12355
|
throw new Error("Agent update rollback release is missing");
|
|
12201
12356
|
}
|
|
12202
|
-
const
|
|
12203
|
-
const probe = options.probe ?? targetProbe(journal.target, run2);
|
|
12357
|
+
const paths = socketPaths(options.publicSocketPath);
|
|
12358
|
+
const probe = options.probe ?? (journal.target === "compute" ? () => probeAgentSocket(paths.active) : targetProbe(journal.target, run2));
|
|
12204
12359
|
restoreAgentRelease(staged);
|
|
12205
12360
|
let rollbackHealthy = false;
|
|
12206
12361
|
let failureMessage = "activation was interrupted before its health verdict became durable";
|
|
12207
12362
|
try {
|
|
12208
12363
|
await restartAgent(journal.target, run2);
|
|
12209
12364
|
rollbackHealthy = await probe();
|
|
12365
|
+
if (rollbackHealthy && journal.target === "compute")
|
|
12366
|
+
switchAgentSocketRoute(paths.route, paths.active);
|
|
12210
12367
|
} catch (cause) {
|
|
12211
12368
|
failureMessage = `${failureMessage}; ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
12212
12369
|
}
|
|
12370
|
+
await stopCandidate(staged, journal.target, run2).catch(() => {});
|
|
12213
12371
|
const failures = journal.failureCount + 1;
|
|
12214
12372
|
const updatedAtTs = (options.now ?? Date.now)();
|
|
12215
12373
|
const recovered = {
|
|
@@ -12226,15 +12384,23 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
12226
12384
|
}
|
|
12227
12385
|
function startAgentUpdateHelper(options = {}) {
|
|
12228
12386
|
const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
|
|
12229
|
-
if (
|
|
12230
|
-
|
|
12231
|
-
mkdirSync9(
|
|
12232
|
-
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
12387
|
+
if (existsSync14(socketPath))
|
|
12388
|
+
unlinkSync11(socketPath);
|
|
12389
|
+
mkdirSync9(dirname8(socketPath), { recursive: true, mode: 488 });
|
|
12233
12390
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
12234
12391
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
12235
12392
|
const releaseRoot = options.root ?? DEFAULT_AGENT_RELEASE_ROOT;
|
|
12236
|
-
const
|
|
12393
|
+
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
12394
|
+
const activate = options.activate ?? ((staged, target2, attemptId) => activateAgentRelease(staged, {
|
|
12395
|
+
target: target2,
|
|
12396
|
+
attemptId,
|
|
12397
|
+
journalPath,
|
|
12398
|
+
receiptPath,
|
|
12399
|
+
now: options.now,
|
|
12400
|
+
candidatePrepared: true
|
|
12401
|
+
}));
|
|
12237
12402
|
let busy = true;
|
|
12403
|
+
let pending;
|
|
12238
12404
|
let blocked;
|
|
12239
12405
|
(options.recover ?? (() => recoverInterruptedAgentUpdate({
|
|
12240
12406
|
root: releaseRoot,
|
|
@@ -12246,7 +12412,7 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12246
12412
|
}).finally(() => {
|
|
12247
12413
|
busy = false;
|
|
12248
12414
|
});
|
|
12249
|
-
const server =
|
|
12415
|
+
const server = createServer7((socket) => {
|
|
12250
12416
|
let buffer = "";
|
|
12251
12417
|
socket.on("data", (chunk) => {
|
|
12252
12418
|
buffer += chunk.toString("utf8");
|
|
@@ -12265,16 +12431,52 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12265
12431
|
Promise.resolve().then(() => JSON.parse(line)).then(async (request) => {
|
|
12266
12432
|
if (blocked)
|
|
12267
12433
|
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
12434
|
if (request.target !== "compute" && request.target !== "metal") {
|
|
12273
12435
|
throw new Error("agent update target is invalid");
|
|
12274
12436
|
}
|
|
12275
|
-
const attemptId = request.attemptId ??
|
|
12437
|
+
const attemptId = request.attemptId ?? randomUUID4();
|
|
12276
12438
|
if (!ATTEMPT_ID.test(attemptId))
|
|
12277
12439
|
throw new Error("Agent update attempt ID is invalid");
|
|
12440
|
+
if (request.op === "commit" || request.op === "abort") {
|
|
12441
|
+
if (!pending || pending.attemptId !== attemptId || pending.target !== request.target || pending.staged.fromVersion !== request.currentVersion || pending.staged.version !== request.targetVersion) {
|
|
12442
|
+
throw new Error("Agent update handover does not match the prepared candidate");
|
|
12443
|
+
}
|
|
12444
|
+
const selected = pending;
|
|
12445
|
+
pending = undefined;
|
|
12446
|
+
if (request.op === "abort") {
|
|
12447
|
+
await stopCandidate(selected.staged, selected.target, runCommand);
|
|
12448
|
+
busy = false;
|
|
12449
|
+
const response3 = {
|
|
12450
|
+
ok: true,
|
|
12451
|
+
status: "aborted",
|
|
12452
|
+
version: selected.staged.version,
|
|
12453
|
+
attemptId
|
|
12454
|
+
};
|
|
12455
|
+
socket.end(`${JSON.stringify(response3)}
|
|
12456
|
+
`);
|
|
12457
|
+
return;
|
|
12458
|
+
}
|
|
12459
|
+
const outcome = await activate(selected.staged, selected.target, attemptId);
|
|
12460
|
+
busy = false;
|
|
12461
|
+
if (outcome?.ok === false)
|
|
12462
|
+
throw new Error(outcome.reason ?? "Agent handover failed");
|
|
12463
|
+
const response2 = {
|
|
12464
|
+
ok: true,
|
|
12465
|
+
status: "active",
|
|
12466
|
+
version: selected.staged.version,
|
|
12467
|
+
attemptId
|
|
12468
|
+
};
|
|
12469
|
+
socket.end(`${JSON.stringify(response2)}
|
|
12470
|
+
`);
|
|
12471
|
+
return;
|
|
12472
|
+
}
|
|
12473
|
+
if (request.op !== "prepare")
|
|
12474
|
+
throw new Error("unknown update operation");
|
|
12475
|
+
if (typeof request.expectedNodeKey !== "string" || request.expectedNodeKey.length < 16 || Buffer.byteLength(request.expectedNodeKey, "utf8") > 512 || /[\0\r\n]/.test(request.expectedNodeKey)) {
|
|
12476
|
+
throw new Error("Agent update expected node identity is invalid");
|
|
12477
|
+
}
|
|
12478
|
+
if (busy)
|
|
12479
|
+
throw new Error("another Agent update or recovery is already active");
|
|
12278
12480
|
const prior = readJournal(journalPath, releaseRoot);
|
|
12279
12481
|
const now = (options.now ?? Date.now)();
|
|
12280
12482
|
if (prior?.targetVersion === request.release.version && (prior.outcome === "rolled-back" || prior.outcome === "failed") && (prior.retryAfterTs ?? 0) > now)
|
|
@@ -12285,17 +12487,23 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12285
12487
|
currentVersion: request.currentVersion,
|
|
12286
12488
|
root: releaseRoot
|
|
12287
12489
|
});
|
|
12288
|
-
|
|
12490
|
+
await startCandidate(staged, request.target, request.expectedNodeKey, runCommand);
|
|
12491
|
+
pending = { staged, target: request.target, attemptId, expectedNodeKey: request.expectedNodeKey };
|
|
12492
|
+
ownsBusy = false;
|
|
12493
|
+
setTimer(() => {
|
|
12494
|
+
if (pending?.attemptId !== attemptId)
|
|
12495
|
+
return;
|
|
12496
|
+
const expired = pending;
|
|
12497
|
+
pending = undefined;
|
|
12498
|
+
stopCandidate(expired.staged, expired.target, runCommand).finally(() => {
|
|
12499
|
+
busy = false;
|
|
12500
|
+
});
|
|
12501
|
+
}, 180000);
|
|
12502
|
+
const response = { ok: true, status: "prepared", version: staged.version, attemptId };
|
|
12289
12503
|
socket.end(`${JSON.stringify(response)}
|
|
12290
12504
|
`);
|
|
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
12505
|
}).catch((cause) => {
|
|
12298
|
-
if (ownsBusy)
|
|
12506
|
+
if (ownsBusy && !pending)
|
|
12299
12507
|
busy = false;
|
|
12300
12508
|
const response = {
|
|
12301
12509
|
ok: false,
|
|
@@ -12307,12 +12515,12 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
12307
12515
|
});
|
|
12308
12516
|
socket.on("error", () => socket.destroy());
|
|
12309
12517
|
});
|
|
12310
|
-
server.listen(socketPath, () =>
|
|
12518
|
+
server.listen(socketPath, () => chmodSync14(socketPath, 432));
|
|
12311
12519
|
return server;
|
|
12312
12520
|
}
|
|
12313
12521
|
function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
|
|
12314
12522
|
return new Promise((resolve4, reject) => {
|
|
12315
|
-
const socket =
|
|
12523
|
+
const socket = connect6(socketPath, () => socket.write(`${JSON.stringify(request)}
|
|
12316
12524
|
`));
|
|
12317
12525
|
let buffer = "";
|
|
12318
12526
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -12337,7 +12545,7 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
|
|
|
12337
12545
|
}
|
|
12338
12546
|
|
|
12339
12547
|
// src/agent-heartbeat.ts
|
|
12340
|
-
import { existsSync as
|
|
12548
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11, statfsSync as statfsSync2 } from "fs";
|
|
12341
12549
|
import { cpus, freemem, loadavg, totalmem } from "os";
|
|
12342
12550
|
function readAgentHostMetrics() {
|
|
12343
12551
|
const filesystem = statfsSync2("/");
|
|
@@ -12382,7 +12590,7 @@ function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = rea
|
|
|
12382
12590
|
runtimeCapabilities: {
|
|
12383
12591
|
native: true,
|
|
12384
12592
|
ociRunc: ubuntuX64,
|
|
12385
|
-
kataQemuSnp: ubuntuX64 && osVersion === "26.04" &&
|
|
12593
|
+
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
12594
|
},
|
|
12387
12595
|
capacity: {
|
|
12388
12596
|
logicalCpu: Math.max(1, metrics.logicalCpu),
|
|
@@ -12434,18 +12642,31 @@ async function heartbeatAgentOnce(options) {
|
|
|
12434
12642
|
});
|
|
12435
12643
|
return response;
|
|
12436
12644
|
}
|
|
12437
|
-
let
|
|
12645
|
+
let candidatePrepared = false;
|
|
12646
|
+
let drained = false;
|
|
12438
12647
|
try {
|
|
12439
|
-
|
|
12440
|
-
|
|
12648
|
+
const update2 = options.applyUpdate ?? ((phase, next, current2, attemptId) => requestAgentUpdate(phase === "prepare" ? {
|
|
12649
|
+
op: "prepare",
|
|
12650
|
+
target: options.updateTarget ?? "compute",
|
|
12651
|
+
release: next,
|
|
12652
|
+
currentVersion: current2,
|
|
12653
|
+
expectedNodeKey: options.nodeKey,
|
|
12654
|
+
attemptId
|
|
12655
|
+
} : {
|
|
12656
|
+
op: phase,
|
|
12657
|
+
target: options.updateTarget ?? "compute",
|
|
12658
|
+
currentVersion: current2,
|
|
12659
|
+
targetVersion: next.version,
|
|
12660
|
+
attemptId
|
|
12661
|
+
}));
|
|
12441
12662
|
const apply = async () => {
|
|
12442
|
-
const
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12663
|
+
const prepared = await update2("prepare", release, observation.version, desired.attemptId);
|
|
12664
|
+
if (!prepared.ok)
|
|
12665
|
+
throw new AgentUpdateRefusedError(`agent update refused: ${prepared.error.message}`);
|
|
12666
|
+
candidatePrepared = true;
|
|
12667
|
+
await options.prepareUpdate?.(release);
|
|
12668
|
+
drained = true;
|
|
12669
|
+
const applied = await update2("commit", release, observation.version, desired.attemptId);
|
|
12449
12670
|
if (!applied.ok)
|
|
12450
12671
|
throw new AgentUpdateRefusedError(`agent update refused: ${applied.error.message}`);
|
|
12451
12672
|
if (applied.attemptId !== desired.attemptId) {
|
|
@@ -12458,9 +12679,18 @@ async function heartbeatAgentOnce(options) {
|
|
|
12458
12679
|
} else {
|
|
12459
12680
|
await apply();
|
|
12460
12681
|
}
|
|
12461
|
-
options.onEvent?.("update-
|
|
12682
|
+
options.onEvent?.("update-active", { from: observation.version, to: release.version });
|
|
12462
12683
|
} catch (cause) {
|
|
12463
|
-
if (
|
|
12684
|
+
if (candidatePrepared && !drained) {
|
|
12685
|
+
await (options.applyUpdate ?? ((phase, next, current2, attemptId) => requestAgentUpdate({
|
|
12686
|
+
op: phase,
|
|
12687
|
+
target: options.updateTarget ?? "compute",
|
|
12688
|
+
currentVersion: current2,
|
|
12689
|
+
targetVersion: next.version,
|
|
12690
|
+
attemptId
|
|
12691
|
+
})))("abort", release, observation.version, desired.attemptId).catch(() => {});
|
|
12692
|
+
}
|
|
12693
|
+
if (drained)
|
|
12464
12694
|
await options.recoverUpdate?.(cause);
|
|
12465
12695
|
throw cause;
|
|
12466
12696
|
}
|
|
@@ -12501,14 +12731,14 @@ function startAgentHeartbeat(options) {
|
|
|
12501
12731
|
}
|
|
12502
12732
|
|
|
12503
12733
|
// src/software-helper.ts
|
|
12504
|
-
import { chmodSync as
|
|
12505
|
-
import { connect as
|
|
12506
|
-
import { dirname as
|
|
12734
|
+
import { chmodSync as chmodSync15, existsSync as existsSync18, mkdirSync as mkdirSync13, unlinkSync as unlinkSync12 } from "fs";
|
|
12735
|
+
import { connect as connect7, createServer as createServer8 } from "net";
|
|
12736
|
+
import { dirname as dirname12 } from "path";
|
|
12507
12737
|
|
|
12508
12738
|
// src/service-supervisor.ts
|
|
12509
12739
|
import { createHash as createHash9 } from "crypto";
|
|
12510
|
-
import { existsSync as
|
|
12511
|
-
import { dirname as
|
|
12740
|
+
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";
|
|
12741
|
+
import { dirname as dirname9, join as join9, resolve as resolve4, sep as sep2 } from "path";
|
|
12512
12742
|
var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
|
|
12513
12743
|
var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
|
|
12514
12744
|
var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
|
|
@@ -12518,17 +12748,17 @@ var statePath = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
|
|
|
12518
12748
|
var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
|
|
12519
12749
|
var defaultHost = {
|
|
12520
12750
|
write(path2, content, mode) {
|
|
12521
|
-
mkdirSync10(
|
|
12751
|
+
mkdirSync10(dirname9(path2), { recursive: true, mode: 493 });
|
|
12522
12752
|
const next = `${path2}.next`;
|
|
12523
12753
|
writeFileSync11(next, content, { mode });
|
|
12524
|
-
|
|
12754
|
+
renameSync10(next, path2);
|
|
12525
12755
|
},
|
|
12526
12756
|
read: (path2) => readFileSync12(path2, "utf8"),
|
|
12527
|
-
exists:
|
|
12528
|
-
list: (path2) =>
|
|
12757
|
+
exists: existsSync16,
|
|
12758
|
+
list: (path2) => existsSync16(path2) ? readdirSync2(path2) : [],
|
|
12529
12759
|
realpath: realpathSync2,
|
|
12530
12760
|
mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
|
|
12531
|
-
remove: (path2) =>
|
|
12761
|
+
remove: (path2) => rmSync8(path2, { force: true }),
|
|
12532
12762
|
async exec(argv2) {
|
|
12533
12763
|
const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
|
|
12534
12764
|
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
@@ -12724,20 +12954,20 @@ async function activateSupervisedService(request, host = defaultHost) {
|
|
|
12724
12954
|
|
|
12725
12955
|
// src/container-supervisor.ts
|
|
12726
12956
|
import { createHash as createHash10 } from "crypto";
|
|
12727
|
-
import { existsSync as
|
|
12728
|
-
import { dirname as
|
|
12957
|
+
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";
|
|
12958
|
+
import { dirname as dirname10, resolve as resolve5, sep as sep3 } from "path";
|
|
12729
12959
|
var defaultHost2 = {
|
|
12730
12960
|
realpath: realpathSync3,
|
|
12731
|
-
exists:
|
|
12961
|
+
exists: existsSync17,
|
|
12732
12962
|
read: (path2) => readFileSync13(path2, "utf8"),
|
|
12733
12963
|
write(path2, content, mode) {
|
|
12734
|
-
mkdirSync11(
|
|
12964
|
+
mkdirSync11(dirname10(path2), { recursive: true, mode: 493 });
|
|
12735
12965
|
const next = `${path2}.next`;
|
|
12736
12966
|
writeFileSync12(next, content, { mode });
|
|
12737
|
-
|
|
12967
|
+
renameSync11(next, path2);
|
|
12738
12968
|
},
|
|
12739
|
-
remove: (path2) =>
|
|
12740
|
-
list: (path2) =>
|
|
12969
|
+
remove: (path2) => rmSync9(path2, { force: true }),
|
|
12970
|
+
list: (path2) => existsSync17(path2) ? readdirSync3(path2) : [],
|
|
12741
12971
|
mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
|
|
12742
12972
|
async exec(argv2) {
|
|
12743
12973
|
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 +13256,9 @@ async function activateContainer(requestValue, host = defaultHost2) {
|
|
|
13026
13256
|
|
|
13027
13257
|
// src/deployment-connectivity.ts
|
|
13028
13258
|
import { createHash as createHash11 } from "crypto";
|
|
13029
|
-
import { mkdirSync as mkdirSync12, renameSync as
|
|
13259
|
+
import { mkdirSync as mkdirSync12, renameSync as renameSync12, writeFileSync as writeFileSync13 } from "fs";
|
|
13030
13260
|
import { createConnection as createConnection2 } from "net";
|
|
13031
|
-
import { dirname as
|
|
13261
|
+
import { dirname as dirname11 } from "path";
|
|
13032
13262
|
var TOKEN2 = /^[A-Za-z0-9._-]{40,16384}$/;
|
|
13033
13263
|
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
13264
|
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 +13271,7 @@ var checked7 = async (host, argv2, label) => {
|
|
|
13041
13271
|
return result.output.trim();
|
|
13042
13272
|
};
|
|
13043
13273
|
async function sealWithSystemd(name, path2, value) {
|
|
13044
|
-
mkdirSync12(
|
|
13274
|
+
mkdirSync12(dirname11(path2), { recursive: true, mode: 448 });
|
|
13045
13275
|
const next = `${path2}.next`;
|
|
13046
13276
|
const child = Bun.spawn(["/usr/bin/systemd-creds", "encrypt", `--name=${name}`, "-", next], {
|
|
13047
13277
|
stdin: "pipe",
|
|
@@ -13057,15 +13287,15 @@ async function sealWithSystemd(name, path2, value) {
|
|
|
13057
13287
|
]);
|
|
13058
13288
|
if (exitCode !== 0)
|
|
13059
13289
|
throw new Error(`systemd credential sealing failed: ${`${stdout}${stderr}`.replaceAll(value, "[REDACTED]").slice(0, 512)}`);
|
|
13060
|
-
|
|
13290
|
+
renameSync12(next, path2);
|
|
13061
13291
|
}
|
|
13062
13292
|
var defaultHost3 = {
|
|
13063
13293
|
seal: sealWithSystemd,
|
|
13064
13294
|
write(path2, content, mode) {
|
|
13065
|
-
mkdirSync12(
|
|
13295
|
+
mkdirSync12(dirname11(path2), { recursive: true, mode: 493 });
|
|
13066
13296
|
const next = `${path2}.next`;
|
|
13067
13297
|
writeFileSync13(next, content, { mode });
|
|
13068
|
-
|
|
13298
|
+
renameSync12(next, path2);
|
|
13069
13299
|
},
|
|
13070
13300
|
async exec(argv2) {
|
|
13071
13301
|
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 +13534,9 @@ var MAX_REQUEST_BYTES6 = 128 * 1024;
|
|
|
13304
13534
|
var MAX_PENDING_REQUESTS = 128;
|
|
13305
13535
|
function startSoftwareHelper(options = {}) {
|
|
13306
13536
|
const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
|
|
13307
|
-
if (
|
|
13308
|
-
|
|
13309
|
-
mkdirSync13(
|
|
13537
|
+
if (existsSync18(socketPath))
|
|
13538
|
+
unlinkSync12(socketPath);
|
|
13539
|
+
mkdirSync13(dirname12(socketPath), { recursive: true, mode: 488 });
|
|
13310
13540
|
const ensure = options.ensure ?? ensureSoftwareRequirements;
|
|
13311
13541
|
const activate = options.activate ?? activateSupervisedService;
|
|
13312
13542
|
const buildContainer = options.buildContainer ?? buildContainerImage;
|
|
@@ -13314,7 +13544,7 @@ function startSoftwareHelper(options = {}) {
|
|
|
13314
13544
|
const applyConnectivity = options.applyConnectivity ?? applyDeploymentConnectivity;
|
|
13315
13545
|
let tail = Promise.resolve();
|
|
13316
13546
|
let pending = 0;
|
|
13317
|
-
const server =
|
|
13547
|
+
const server = createServer8((socket) => {
|
|
13318
13548
|
let buffer = "";
|
|
13319
13549
|
socket.on("data", (chunk) => {
|
|
13320
13550
|
buffer += chunk.toString("utf8");
|
|
@@ -13393,12 +13623,12 @@ function startSoftwareHelper(options = {}) {
|
|
|
13393
13623
|
});
|
|
13394
13624
|
socket.on("error", () => socket.destroy());
|
|
13395
13625
|
});
|
|
13396
|
-
server.listen(socketPath, () =>
|
|
13626
|
+
server.listen(socketPath, () => chmodSync15(socketPath, 432));
|
|
13397
13627
|
return server;
|
|
13398
13628
|
}
|
|
13399
13629
|
function requestContainer(op, request, socketPath, timeoutMs) {
|
|
13400
13630
|
return new Promise((resolve6, reject) => {
|
|
13401
|
-
const socket =
|
|
13631
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op, request })}
|
|
13402
13632
|
`));
|
|
13403
13633
|
let buffer = "";
|
|
13404
13634
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -13436,7 +13666,7 @@ function requestDeploymentConnectivity(request, socketPath = DEFAULT_SOFTWARE_HE
|
|
|
13436
13666
|
}
|
|
13437
13667
|
function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 10 * 60000) {
|
|
13438
13668
|
return new Promise((resolve6, reject) => {
|
|
13439
|
-
const socket =
|
|
13669
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
|
|
13440
13670
|
`));
|
|
13441
13671
|
let buffer = "";
|
|
13442
13672
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -13465,7 +13695,7 @@ function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_
|
|
|
13465
13695
|
function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
|
|
13466
13696
|
validateSoftwareRequirements(requirements);
|
|
13467
13697
|
return new Promise((resolve6, reject) => {
|
|
13468
|
-
const socket =
|
|
13698
|
+
const socket = connect7(socketPath, () => socket.write(`${JSON.stringify({ op: "ensure", requirements })}
|
|
13469
13699
|
`));
|
|
13470
13700
|
let buffer = "";
|
|
13471
13701
|
socket.setTimeout(timeoutMs, () => {
|
|
@@ -14425,19 +14655,19 @@ var METAL_SYSTEMD_CREDENTIALS = {
|
|
|
14425
14655
|
|
|
14426
14656
|
// src/platform-bootstrap-runtime.ts
|
|
14427
14657
|
import {
|
|
14428
|
-
chmodSync as
|
|
14658
|
+
chmodSync as chmodSync16,
|
|
14429
14659
|
chownSync,
|
|
14430
14660
|
copyFileSync as copyFileSync2,
|
|
14431
|
-
existsSync as
|
|
14432
|
-
lstatSync as
|
|
14661
|
+
existsSync as existsSync19,
|
|
14662
|
+
lstatSync as lstatSync4,
|
|
14433
14663
|
mkdirSync as mkdirSync14,
|
|
14434
14664
|
readFileSync as readFileSync14,
|
|
14435
14665
|
readdirSync as readdirSync4,
|
|
14436
14666
|
realpathSync as realpathSync5,
|
|
14437
|
-
renameSync as
|
|
14438
|
-
rmSync as
|
|
14667
|
+
renameSync as renameSync13,
|
|
14668
|
+
rmSync as rmSync10,
|
|
14439
14669
|
statSync as statSync4,
|
|
14440
|
-
symlinkSync as
|
|
14670
|
+
symlinkSync as symlinkSync6,
|
|
14441
14671
|
writeFileSync as writeFileSync14
|
|
14442
14672
|
} from "fs";
|
|
14443
14673
|
import { join as join10 } from "path";
|
|
@@ -14492,21 +14722,21 @@ var platformExec = async (argv2) => {
|
|
|
14492
14722
|
};
|
|
14493
14723
|
var replaceLink = (path2, target2) => {
|
|
14494
14724
|
const pending = `${path2}.next`;
|
|
14495
|
-
|
|
14725
|
+
rmSync10(pending, { force: true });
|
|
14496
14726
|
if (!target2) {
|
|
14497
|
-
|
|
14727
|
+
rmSync10(path2, { force: true });
|
|
14498
14728
|
return;
|
|
14499
14729
|
}
|
|
14500
|
-
|
|
14501
|
-
|
|
14730
|
+
symlinkSync6(target2, pending);
|
|
14731
|
+
renameSync13(pending, path2);
|
|
14502
14732
|
};
|
|
14503
14733
|
var secureRelease = (path2, uid, gid) => {
|
|
14504
14734
|
const visit = (current2) => {
|
|
14505
|
-
const metadata =
|
|
14735
|
+
const metadata = lstatSync4(current2);
|
|
14506
14736
|
if (metadata.isSymbolicLink())
|
|
14507
14737
|
throw new Error("release contains a symbolic link");
|
|
14508
14738
|
chownSync(current2, uid, gid);
|
|
14509
|
-
|
|
14739
|
+
chmodSync16(current2, metadata.isDirectory() ? 365 : 292);
|
|
14510
14740
|
if (metadata.isDirectory())
|
|
14511
14741
|
for (const name of readdirSync4(current2))
|
|
14512
14742
|
visit(join10(current2, name));
|
|
@@ -14531,7 +14761,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14531
14761
|
const slots = join10(normalized.root, "slots");
|
|
14532
14762
|
mkdirSync14(slots, { recursive: true, mode: 493 });
|
|
14533
14763
|
const slotFile = join10(normalized.root, ".forge-slot");
|
|
14534
|
-
const previousSlot =
|
|
14764
|
+
const previousSlot = existsSync19(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
|
|
14535
14765
|
const target2 = previousSlot === "blue" ? "green" : "blue";
|
|
14536
14766
|
const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
|
|
14537
14767
|
const targetLink = join10(slots, target2);
|
|
@@ -14571,25 +14801,25 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14571
14801
|
}
|
|
14572
14802
|
const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
|
|
14573
14803
|
const backup = `${upstream}.forgezero-backup`;
|
|
14574
|
-
if (
|
|
14804
|
+
if (existsSync19(upstream))
|
|
14575
14805
|
copyFileSync2(upstream, backup);
|
|
14576
14806
|
else
|
|
14577
|
-
|
|
14807
|
+
rmSync10(backup, { force: true });
|
|
14578
14808
|
writeFileSync14(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
|
|
14579
14809
|
`, { mode: 420 });
|
|
14580
14810
|
const test = await exec(["/usr/sbin/nginx", "-t"]);
|
|
14581
14811
|
const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
|
|
14582
14812
|
if (reload.exitCode !== 0) {
|
|
14583
|
-
if (
|
|
14584
|
-
|
|
14813
|
+
if (existsSync19(backup))
|
|
14814
|
+
renameSync13(backup, upstream);
|
|
14585
14815
|
else
|
|
14586
|
-
|
|
14816
|
+
rmSync10(upstream, { force: true });
|
|
14587
14817
|
await exec(["/usr/sbin/nginx", "-t"]);
|
|
14588
14818
|
await exec(["/usr/sbin/nginx", "-s", "reload"]);
|
|
14589
14819
|
await stopTarget();
|
|
14590
14820
|
throw new Error("nginx refused the promoted upstream");
|
|
14591
14821
|
}
|
|
14592
|
-
|
|
14822
|
+
rmSync10(backup, { force: true });
|
|
14593
14823
|
writeFileSync14(slotFile, `${target2}
|
|
14594
14824
|
`, { mode: 420 });
|
|
14595
14825
|
if (previousSlot && previousSlot !== target2) {
|
|
@@ -14599,12 +14829,12 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
14599
14829
|
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
14830
|
for (const path2 of old)
|
|
14601
14831
|
if (path2 !== release)
|
|
14602
|
-
|
|
14832
|
+
rmSync10(path2, { recursive: true, force: true });
|
|
14603
14833
|
return { release, slot: target2 };
|
|
14604
14834
|
}
|
|
14605
14835
|
|
|
14606
14836
|
// src/recovery-host.ts
|
|
14607
|
-
import { existsSync as
|
|
14837
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, statSync as statSync5 } from "fs";
|
|
14608
14838
|
import { hostname } from "os";
|
|
14609
14839
|
import { join as join11 } from "path";
|
|
14610
14840
|
var execute2 = async (argv2) => {
|
|
@@ -14630,7 +14860,7 @@ var exactState = (path2, expected) => {
|
|
|
14630
14860
|
throw new Error(`state mismatch: ${expected}`);
|
|
14631
14861
|
};
|
|
14632
14862
|
var nonEmpty = (path2) => {
|
|
14633
|
-
if (!
|
|
14863
|
+
if (!existsSync20(path2) || !statSync5(path2).isFile() || statSync5(path2).size < 1)
|
|
14634
14864
|
throw new Error(`required file missing: ${path2}`);
|
|
14635
14865
|
};
|
|
14636
14866
|
var option = (args, name) => {
|
|
@@ -14726,20 +14956,20 @@ async function runRecoveryHost(args, run2 = execute2) {
|
|
|
14726
14956
|
}
|
|
14727
14957
|
|
|
14728
14958
|
// src/platform-fleet-verification.ts
|
|
14729
|
-
import { lstatSync as
|
|
14959
|
+
import { lstatSync as lstatSync7 } from "fs";
|
|
14730
14960
|
|
|
14731
14961
|
// src/bootstrap.ts
|
|
14732
14962
|
import {
|
|
14733
|
-
chmodSync as
|
|
14734
|
-
existsSync as
|
|
14735
|
-
lstatSync as
|
|
14963
|
+
chmodSync as chmodSync18,
|
|
14964
|
+
existsSync as existsSync22,
|
|
14965
|
+
lstatSync as lstatSync6,
|
|
14736
14966
|
mkdirSync as mkdirSync16,
|
|
14737
14967
|
readFileSync as readFileSync17,
|
|
14738
|
-
renameSync as
|
|
14739
|
-
rmSync as
|
|
14968
|
+
renameSync as renameSync15,
|
|
14969
|
+
rmSync as rmSync12,
|
|
14740
14970
|
writeFileSync as writeFileSync16
|
|
14741
14971
|
} from "fs";
|
|
14742
|
-
import { dirname as
|
|
14972
|
+
import { dirname as dirname14 } from "path";
|
|
14743
14973
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14744
14974
|
|
|
14745
14975
|
// src/provision.ts
|
|
@@ -14796,6 +15026,7 @@ var LIFECYCLE_GROUP = "forgezero-lifecycle";
|
|
|
14796
15026
|
var DEPLOYMENT_RUNNER_UNIT_PATH = "/etc/systemd/system/forgezero-deploy-runner.service";
|
|
14797
15027
|
var AGENT_SOCKET_UNIT_PATH = "/etc/systemd/system/forgezero-agent.socket";
|
|
14798
15028
|
var AGENT_SOCKET_PROXY_UNIT_PATH = "/etc/systemd/system/forgezero-agent-proxy.service";
|
|
15029
|
+
var AGENT_CANDIDATE_UNIT_PATH = "/etc/systemd/system/forgezero-agent-candidate.service";
|
|
14799
15030
|
var DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
|
|
14800
15031
|
var ENROLMENT_UNIT_PATH = "/etc/systemd/system/forgezero-agent-enrol.service";
|
|
14801
15032
|
var LIFECYCLE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-lifecycle-helper.service";
|
|
@@ -14907,6 +15138,7 @@ Type=simple
|
|
|
14907
15138
|
User=root
|
|
14908
15139
|
Group=${AGENT_UPDATE_GROUP}
|
|
14909
15140
|
Environment=FZ_AGENT_UPDATE_SOCKET=${DEFAULT_AGENT_UPDATE_SOCKET}
|
|
15141
|
+
Environment=FZ_AGENT_PUBLIC_SOCKET=${systemdPath(options.socketPath, "agent socket")}
|
|
14910
15142
|
ExecStart=${bin} update-helper
|
|
14911
15143
|
Restart=always
|
|
14912
15144
|
RestartSec=2
|
|
@@ -14951,13 +15183,11 @@ WantedBy=sockets.target
|
|
|
14951
15183
|
`;
|
|
14952
15184
|
}
|
|
14953
15185
|
function agentSocketProxyUnit(options) {
|
|
14954
|
-
const backend =
|
|
15186
|
+
const backend = agentRoutingSocketPath(options.socketPath);
|
|
14955
15187
|
const user = options.user ?? "forgezero";
|
|
14956
15188
|
return `[Unit]
|
|
14957
15189
|
Description=ForgeZero application Vault socket proxy
|
|
14958
15190
|
Documentation=https://www.forgezero.net/docs/agent
|
|
14959
|
-
Requires=forgezero-agent.service
|
|
14960
|
-
After=forgezero-agent.service
|
|
14961
15191
|
|
|
14962
15192
|
[Service]
|
|
14963
15193
|
User=${user}
|
|
@@ -14978,12 +15208,26 @@ RestrictAddressFamilies=AF_UNIX
|
|
|
14978
15208
|
`;
|
|
14979
15209
|
}
|
|
14980
15210
|
function agentBackendSocketPath(publicSocketPath) {
|
|
15211
|
+
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
15212
|
+
const backend = `${socket}.backend.active`;
|
|
15213
|
+
if (Buffer.byteLength(backend) > 100)
|
|
15214
|
+
throw new Error("agent socket path is too long for a Unix socket");
|
|
15215
|
+
return backend;
|
|
15216
|
+
}
|
|
15217
|
+
function agentRoutingSocketPath(publicSocketPath) {
|
|
14981
15218
|
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
14982
15219
|
const backend = `${socket}.backend`;
|
|
14983
15220
|
if (Buffer.byteLength(backend) > 100)
|
|
14984
15221
|
throw new Error("agent socket path is too long for a Unix socket");
|
|
14985
15222
|
return backend;
|
|
14986
15223
|
}
|
|
15224
|
+
function agentCandidateSocketPath(publicSocketPath) {
|
|
15225
|
+
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
15226
|
+
const backend = `${socket}.backend.candidate`;
|
|
15227
|
+
if (Buffer.byteLength(backend) > 100)
|
|
15228
|
+
throw new Error("agent socket path is too long for a Unix socket");
|
|
15229
|
+
return backend;
|
|
15230
|
+
}
|
|
14987
15231
|
var systemdPath = (value, label) => {
|
|
14988
15232
|
if (!value || !/^\/[A-Za-z0-9._@/-]+$/.test(value))
|
|
14989
15233
|
throw new Error(`invalid ${label} path`);
|
|
@@ -15267,7 +15511,11 @@ function agentUnit(options) {
|
|
|
15267
15511
|
}
|
|
15268
15512
|
const environment = [
|
|
15269
15513
|
"NODE_ENV=production",
|
|
15270
|
-
`FZ_SOCKET_PATH=${agentBackendSocketPath(options.socketPath)}`,
|
|
15514
|
+
`FZ_SOCKET_PATH=${options.backendSocketPath ?? agentBackendSocketPath(options.socketPath)}`,
|
|
15515
|
+
`FZ_AGENT_PUBLIC_SOCKET=${options.socketPath}`,
|
|
15516
|
+
`FZ_AGENT_ROUTE_SOCKET=${agentRoutingSocketPath(options.socketPath)}`,
|
|
15517
|
+
options.handoverCandidate ? "FZ_AGENT_HANDOVER_CANDIDATE=true" : null,
|
|
15518
|
+
options.handoverCandidate ? `FZ_AGENT_HANDOVER_READY_SOCKET=${DEFAULT_AGENT_CANDIDATE_READY_SOCKET}` : null,
|
|
15271
15519
|
`FZ_CONTROL_SOCKET=${controlSocketPath}`,
|
|
15272
15520
|
`FZ_SEED_CREDENTIAL=agent-seed`,
|
|
15273
15521
|
`FZ_AGENT_MODE=${options.mode}`,
|
|
@@ -15404,6 +15652,26 @@ ${deploymentWrites}
|
|
|
15404
15652
|
WantedBy=multi-user.target
|
|
15405
15653
|
`;
|
|
15406
15654
|
}
|
|
15655
|
+
function agentCandidateUnit(options) {
|
|
15656
|
+
return agentUnit({
|
|
15657
|
+
...options,
|
|
15658
|
+
binPath: `${DEFAULT_AGENT_RELEASE_ROOT}/candidate/dist/fz-agent.js`,
|
|
15659
|
+
backendSocketPath: agentCandidateSocketPath(options.socketPath),
|
|
15660
|
+
handoverCandidate: true,
|
|
15661
|
+
gitCredentialPath: undefined,
|
|
15662
|
+
deploymentCredentials: {},
|
|
15663
|
+
bootstrapSshCredentialPath: undefined,
|
|
15664
|
+
bootstrapSshPublicKeyPath: undefined,
|
|
15665
|
+
pullBootstrap: false,
|
|
15666
|
+
pullDeployments: false,
|
|
15667
|
+
pullMigrations: false,
|
|
15668
|
+
lifecycleProfilePath: undefined,
|
|
15669
|
+
bootstrapTargetTelemetryEndpoint: undefined,
|
|
15670
|
+
repository: undefined,
|
|
15671
|
+
bootstrapBundlePath: undefined,
|
|
15672
|
+
bootstrapBundleManifestPath: undefined
|
|
15673
|
+
}).replace("Description=ForgeZero node agent", "Description=ForgeZero candidate node agent");
|
|
15674
|
+
}
|
|
15407
15675
|
var renderOperation = (operation) => {
|
|
15408
15676
|
if (operation.kind === "commands")
|
|
15409
15677
|
return operation.commands.map(({ argv: argv2 }) => argv2.join(" ")).join(`
|
|
@@ -15539,6 +15807,7 @@ function planProvision(options) {
|
|
|
15539
15807
|
auxiliaryUnits: [
|
|
15540
15808
|
{ path: AGENT_SOCKET_UNIT_PATH, unit: agentSocketUnit(options) },
|
|
15541
15809
|
{ path: AGENT_SOCKET_PROXY_UNIT_PATH, unit: agentSocketProxyUnit(options) },
|
|
15810
|
+
{ path: AGENT_CANDIDATE_UNIT_PATH, unit: agentCandidateUnit(options) },
|
|
15542
15811
|
{ path: AGENT_UPDATE_HELPER_UNIT_PATH, unit: agentUpdateHelperUnit(options) },
|
|
15543
15812
|
...options.enforceEgress ? [
|
|
15544
15813
|
{ path: AGENT_EGRESS_UNIT_PATH, unit: agentEgressUnit(options) }
|
|
@@ -15670,19 +15939,19 @@ function planProvision(options) {
|
|
|
15670
15939
|
// src/cli/agent-install.ts
|
|
15671
15940
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
15672
15941
|
import {
|
|
15673
|
-
chmodSync as
|
|
15942
|
+
chmodSync as chmodSync17,
|
|
15674
15943
|
copyFileSync as copyFileSync3,
|
|
15675
|
-
existsSync as
|
|
15676
|
-
lstatSync as
|
|
15944
|
+
existsSync as existsSync21,
|
|
15945
|
+
lstatSync as lstatSync5,
|
|
15677
15946
|
mkdirSync as mkdirSync15,
|
|
15678
15947
|
readFileSync as readFileSync16,
|
|
15679
15948
|
realpathSync as realpathSync6,
|
|
15680
|
-
renameSync as
|
|
15681
|
-
rmSync as
|
|
15682
|
-
symlinkSync as
|
|
15949
|
+
renameSync as renameSync14,
|
|
15950
|
+
rmSync as rmSync11,
|
|
15951
|
+
symlinkSync as symlinkSync7,
|
|
15683
15952
|
writeFileSync as writeFileSync15
|
|
15684
15953
|
} from "fs";
|
|
15685
|
-
import { dirname as
|
|
15954
|
+
import { dirname as dirname13 } from "path";
|
|
15686
15955
|
async function readCapabilities(run2) {
|
|
15687
15956
|
const answers = {};
|
|
15688
15957
|
const checks = Object.entries(CAPABILITY_CHECKS);
|
|
@@ -15740,51 +16009,51 @@ var runProvisionOperation = async (operation) => {
|
|
|
15740
16009
|
if (operation.kind === "install-runtime") {
|
|
15741
16010
|
const release = `/opt/forgezero/agent/versions/${operation.version}`;
|
|
15742
16011
|
mkdirSync15(`${release}/dist`, { recursive: true, mode: 493 });
|
|
15743
|
-
mkdirSync15(
|
|
16012
|
+
mkdirSync15(dirname13(operation.binary), { recursive: true, mode: 493 });
|
|
15744
16013
|
copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
|
|
15745
|
-
|
|
15746
|
-
const gitSshSource = `${
|
|
15747
|
-
if (!
|
|
16014
|
+
chmodSync17(`${release}/dist/fz-agent.js`, 493);
|
|
16015
|
+
const gitSshSource = `${dirname13(operation.source)}/fz-git-ssh.js`;
|
|
16016
|
+
if (!existsSync21(gitSshSource))
|
|
15748
16017
|
return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
|
|
15749
16018
|
copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
|
|
15750
|
-
|
|
16019
|
+
chmodSync17(`${release}/dist/fz-git-ssh.js`, 493);
|
|
15751
16020
|
const pending = "/opt/forgezero/agent/current.next";
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
16021
|
+
rmSync11(pending, { force: true });
|
|
16022
|
+
symlinkSync7(`versions/${operation.version}`, pending);
|
|
16023
|
+
renameSync14(pending, "/opt/forgezero/agent/current");
|
|
16024
|
+
rmSync11(operation.binary, { force: true });
|
|
16025
|
+
symlinkSync7("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
|
|
15757
16026
|
const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
|
|
15758
|
-
|
|
15759
|
-
|
|
16027
|
+
rmSync11(gitSshBinary, { force: true });
|
|
16028
|
+
symlinkSync7("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
|
|
15760
16029
|
return { stdout: "", exitCode: 0 };
|
|
15761
16030
|
}
|
|
15762
16031
|
if (operation.kind === "ensure-seed") {
|
|
15763
|
-
if (
|
|
16032
|
+
if (existsSync21(operation.credential) && lstatSync5(operation.credential).size > 0)
|
|
15764
16033
|
return { stdout: "", exitCode: 0 };
|
|
15765
16034
|
const seed = randomBytes6(32).toString("base64url");
|
|
15766
16035
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
|
|
15767
16036
|
if (result.exitCode === 0)
|
|
15768
|
-
|
|
16037
|
+
chmodSync17(operation.credential, 256);
|
|
15769
16038
|
return result;
|
|
15770
16039
|
}
|
|
15771
16040
|
if (operation.kind === "ensure-git-identity") {
|
|
15772
16041
|
const key = "/run/forgezero-git-deploy-key";
|
|
15773
16042
|
const publicKey = `${key}.pub`;
|
|
15774
16043
|
try {
|
|
15775
|
-
if (!
|
|
15776
|
-
|
|
15777
|
-
|
|
16044
|
+
if (!existsSync21(operation.credential) || lstatSync5(operation.credential).size < 1) {
|
|
16045
|
+
rmSync11(key, { force: true });
|
|
16046
|
+
rmSync11(publicKey, { force: true });
|
|
15778
16047
|
let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
|
|
15779
16048
|
if (result.exitCode !== 0)
|
|
15780
16049
|
return result;
|
|
15781
16050
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=git-deploy-key", key, operation.credential]);
|
|
15782
16051
|
if (result.exitCode !== 0)
|
|
15783
16052
|
return result;
|
|
15784
|
-
|
|
16053
|
+
chmodSync17(operation.credential, 256);
|
|
15785
16054
|
}
|
|
15786
|
-
if (!
|
|
15787
|
-
if (!
|
|
16055
|
+
if (!existsSync21(operation.publicKey) || lstatSync5(operation.publicKey).size < 1) {
|
|
16056
|
+
if (!existsSync21(key)) {
|
|
15788
16057
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
|
|
15789
16058
|
if (decrypted.exitCode !== 0)
|
|
15790
16059
|
return decrypted;
|
|
@@ -15797,25 +16066,25 @@ var runProvisionOperation = async (operation) => {
|
|
|
15797
16066
|
}
|
|
15798
16067
|
return { stdout: "", exitCode: 0 };
|
|
15799
16068
|
} finally {
|
|
15800
|
-
|
|
15801
|
-
|
|
16069
|
+
rmSync11(key, { force: true });
|
|
16070
|
+
rmSync11(publicKey, { force: true });
|
|
15802
16071
|
}
|
|
15803
16072
|
}
|
|
15804
16073
|
if (operation.kind === "ensure-bootstrap-ssh-identity") {
|
|
15805
16074
|
const key = "/run/forgezero-bootstrap-ssh-key";
|
|
15806
16075
|
const generatedPublicKey = `${key}.pub`;
|
|
15807
16076
|
try {
|
|
15808
|
-
if (!
|
|
15809
|
-
|
|
15810
|
-
|
|
16077
|
+
if (!existsSync21(operation.credential) || lstatSync5(operation.credential).size < 1) {
|
|
16078
|
+
rmSync11(key, { force: true });
|
|
16079
|
+
rmSync11(generatedPublicKey, { force: true });
|
|
15811
16080
|
let result;
|
|
15812
16081
|
if (operation.source) {
|
|
15813
|
-
const source =
|
|
16082
|
+
const source = existsSync21(operation.source) ? lstatSync5(operation.source) : undefined;
|
|
15814
16083
|
if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
|
|
15815
16084
|
return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
|
|
15816
16085
|
}
|
|
15817
16086
|
copyFileSync3(operation.source, key);
|
|
15818
|
-
|
|
16087
|
+
chmodSync17(key, 384);
|
|
15819
16088
|
} else {
|
|
15820
16089
|
result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-bootstrap-runner", "-f", key]);
|
|
15821
16090
|
if (result.exitCode !== 0)
|
|
@@ -15828,48 +16097,48 @@ var runProvisionOperation = async (operation) => {
|
|
|
15828
16097
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
|
|
15829
16098
|
if (result.exitCode !== 0)
|
|
15830
16099
|
return result;
|
|
15831
|
-
|
|
16100
|
+
chmodSync17(operation.credential, 256);
|
|
15832
16101
|
}
|
|
15833
|
-
if (!
|
|
15834
|
-
if (!
|
|
16102
|
+
if (!existsSync21(operation.publicKey) || lstatSync5(operation.publicKey).size < 1) {
|
|
16103
|
+
if (!existsSync21(key)) {
|
|
15835
16104
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
|
|
15836
16105
|
if (decrypted.exitCode !== 0)
|
|
15837
16106
|
return decrypted;
|
|
15838
|
-
|
|
16107
|
+
chmodSync17(key, 384);
|
|
15839
16108
|
}
|
|
15840
16109
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
15841
16110
|
if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
|
|
15842
16111
|
return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
|
|
15843
16112
|
}
|
|
15844
|
-
mkdirSync15(
|
|
16113
|
+
mkdirSync15(dirname13(operation.publicKey), { recursive: true, mode: 493 });
|
|
15845
16114
|
writeFileSync15(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
|
|
15846
16115
|
`, { mode: 292 });
|
|
15847
|
-
|
|
16116
|
+
chmodSync17(operation.publicKey, 292);
|
|
15848
16117
|
}
|
|
15849
16118
|
if (operation.source)
|
|
15850
|
-
|
|
16119
|
+
rmSync11(operation.source, { force: true });
|
|
15851
16120
|
return { stdout: "", exitCode: 0 };
|
|
15852
16121
|
} finally {
|
|
15853
|
-
|
|
15854
|
-
|
|
16122
|
+
rmSync11(key, { force: true });
|
|
16123
|
+
rmSync11(generatedPublicKey, { force: true });
|
|
15855
16124
|
}
|
|
15856
16125
|
}
|
|
15857
16126
|
if (operation.kind === "ensure-enrolment") {
|
|
15858
|
-
if (
|
|
16127
|
+
if (existsSync21(operation.state) && lstatSync5(operation.state).size > 0 || existsSync21(operation.credential) && lstatSync5(operation.credential).size > 0)
|
|
15859
16128
|
return { stdout: "", exitCode: 0 };
|
|
15860
|
-
if (!
|
|
16129
|
+
if (!existsSync21(operation.source))
|
|
15861
16130
|
return { stdout: "enrolment source is missing", exitCode: 1 };
|
|
15862
16131
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
|
|
15863
16132
|
if (result.exitCode === 0) {
|
|
15864
|
-
|
|
15865
|
-
|
|
16133
|
+
chmodSync17(operation.credential, 256);
|
|
16134
|
+
rmSync11(operation.source, { force: true });
|
|
15866
16135
|
}
|
|
15867
16136
|
return result;
|
|
15868
16137
|
}
|
|
15869
16138
|
if (operation.kind === "wait-socket") {
|
|
15870
16139
|
for (let attempt = 0;attempt < operation.attempts; attempt += 1) {
|
|
15871
16140
|
try {
|
|
15872
|
-
if (
|
|
16141
|
+
if (lstatSync5(operation.path).isSocket())
|
|
15873
16142
|
return { stdout: "", exitCode: 0 };
|
|
15874
16143
|
} catch {}
|
|
15875
16144
|
await Bun.sleep(operation.intervalMs);
|
|
@@ -15877,7 +16146,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
15877
16146
|
return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
|
|
15878
16147
|
}
|
|
15879
16148
|
if (operation.kind === "verify-file")
|
|
15880
|
-
return
|
|
16149
|
+
return existsSync21(operation.path) && lstatSync5(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
|
|
15881
16150
|
if (operation.kind === "verify-egress") {
|
|
15882
16151
|
const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
|
|
15883
16152
|
if (active.exitCode !== 0)
|
|
@@ -15911,7 +16180,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
15911
16180
|
const key = "/run/cloudflare-warp-key.gpg";
|
|
15912
16181
|
writeFileSync15(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
|
|
15913
16182
|
let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
|
|
15914
|
-
|
|
16183
|
+
rmSync11(key, { force: true });
|
|
15915
16184
|
if (result.exitCode !== 0)
|
|
15916
16185
|
return result;
|
|
15917
16186
|
const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
|
|
@@ -15932,7 +16201,7 @@ async function localRunner(operation) {
|
|
|
15932
16201
|
if (capability.kind === "version")
|
|
15933
16202
|
return fixed(capability.argv);
|
|
15934
16203
|
try {
|
|
15935
|
-
const metadata =
|
|
16204
|
+
const metadata = lstatSync5(capability.path);
|
|
15936
16205
|
const present = capability.nodeType === "directory" ? metadata.isDirectory() : true;
|
|
15937
16206
|
return { stdout: present ? `yes
|
|
15938
16207
|
` : `no
|
|
@@ -16230,19 +16499,19 @@ function localBootstrapHost() {
|
|
|
16230
16499
|
};
|
|
16231
16500
|
return {
|
|
16232
16501
|
uid: () => process.getuid?.() ?? -1,
|
|
16233
|
-
exists:
|
|
16502
|
+
exists: existsSync22,
|
|
16234
16503
|
read: (path2) => readFileSync17(path2, "utf8"),
|
|
16235
16504
|
write(path2, content, mode) {
|
|
16236
|
-
mkdirSync16(
|
|
16505
|
+
mkdirSync16(dirname14(path2), { recursive: true, mode: 493 });
|
|
16237
16506
|
const temporary = `${path2}.next.${process.pid}`;
|
|
16238
16507
|
writeFileSync16(temporary, content, { mode });
|
|
16239
|
-
|
|
16240
|
-
|
|
16508
|
+
chmodSync18(temporary, mode);
|
|
16509
|
+
renameSync15(temporary, path2);
|
|
16241
16510
|
},
|
|
16242
16511
|
mkdir: (path2, mode) => mkdirSync16(path2, { recursive: true, mode }),
|
|
16243
|
-
remove: (path2) =>
|
|
16512
|
+
remove: (path2) => rmSync12(path2, { force: true }),
|
|
16244
16513
|
inspect(path2) {
|
|
16245
|
-
const value =
|
|
16514
|
+
const value = lstatSync6(path2);
|
|
16246
16515
|
return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
|
|
16247
16516
|
},
|
|
16248
16517
|
exec: execute3,
|
|
@@ -16263,9 +16532,9 @@ function localBootstrapHost() {
|
|
|
16263
16532
|
},
|
|
16264
16533
|
async installAgent(config, phase) {
|
|
16265
16534
|
const capabilities = await readCapabilities(localRunner);
|
|
16266
|
-
const hasBinding =
|
|
16267
|
-
const hasEnrolCredential =
|
|
16268
|
-
const initialBundle = config.kind === "platform" && !
|
|
16535
|
+
const hasBinding = existsSync22("/var/lib/forgezero/enrolment.json");
|
|
16536
|
+
const hasEnrolCredential = existsSync22(ENROL_CREDENTIAL);
|
|
16537
|
+
const initialBundle = config.kind === "platform" && !existsSync22(BOOTSTRAP_RELEASE_EVIDENCE) ? {
|
|
16269
16538
|
path: config.bootstrapBundle.bundleFile,
|
|
16270
16539
|
manifestPath: config.bootstrapBundle.manifestFile,
|
|
16271
16540
|
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync17(config.bootstrapBundle.manifestFile, "utf8")))
|
|
@@ -16281,7 +16550,7 @@ function localBootstrapHost() {
|
|
|
16281
16550
|
databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
|
|
16282
16551
|
databasePorts: [8529]
|
|
16283
16552
|
};
|
|
16284
|
-
mkdirSync16(
|
|
16553
|
+
mkdirSync16(dirname14(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
|
|
16285
16554
|
writeFileSync16(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
|
|
16286
16555
|
`, { mode: 256 });
|
|
16287
16556
|
}
|
|
@@ -16292,7 +16561,7 @@ function localBootstrapHost() {
|
|
|
16292
16561
|
initialBundle
|
|
16293
16562
|
});
|
|
16294
16563
|
for (const unit3 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
|
|
16295
|
-
mkdirSync16(
|
|
16564
|
+
mkdirSync16(dirname14(unit3.path), { recursive: true, mode: 493 });
|
|
16296
16565
|
writeFileSync16(unit3.path, unit3.unit, { mode: 420 });
|
|
16297
16566
|
}
|
|
16298
16567
|
await applyPlan(plan, localRunner);
|
|
@@ -16307,7 +16576,7 @@ var localRuntime = () => ({
|
|
|
16307
16576
|
bootstrapStatus: () => bootstrapStatus(),
|
|
16308
16577
|
characterDevice(path2) {
|
|
16309
16578
|
try {
|
|
16310
|
-
return
|
|
16579
|
+
return lstatSync7(path2).isCharacterDevice();
|
|
16311
16580
|
} catch {
|
|
16312
16581
|
return false;
|
|
16313
16582
|
}
|
|
@@ -16346,9 +16615,9 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
|
|
|
16346
16615
|
|
|
16347
16616
|
// src/community-rehearsal-host.ts
|
|
16348
16617
|
import { createHash as createHash12 } from "crypto";
|
|
16349
|
-
import { lstatSync as
|
|
16618
|
+
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
16619
|
import { isIP as isIP5 } from "net";
|
|
16351
|
-
import { dirname as
|
|
16620
|
+
import { dirname as dirname15 } from "path";
|
|
16352
16621
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
16353
16622
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
16354
16623
|
var COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
@@ -16562,19 +16831,19 @@ async function exec(argv2, stdin) {
|
|
|
16562
16831
|
async function applyOperations(operations) {
|
|
16563
16832
|
for (const operation of operations) {
|
|
16564
16833
|
if (operation.kind === "remove-tree")
|
|
16565
|
-
|
|
16834
|
+
rmSync13(operation.path, { recursive: true, force: true });
|
|
16566
16835
|
else if (operation.kind === "write") {
|
|
16567
|
-
mkdirSync17(
|
|
16836
|
+
mkdirSync17(dirname15(operation.path), { recursive: true });
|
|
16568
16837
|
writeFileSync17(operation.path, operation.content, { mode: operation.mode });
|
|
16569
16838
|
} else if (operation.kind === "unlink") {
|
|
16570
16839
|
try {
|
|
16571
|
-
|
|
16840
|
+
unlinkSync13(operation.path);
|
|
16572
16841
|
} catch (cause) {
|
|
16573
16842
|
if (cause.code !== "ENOENT")
|
|
16574
16843
|
throw cause;
|
|
16575
16844
|
}
|
|
16576
16845
|
} else if (operation.kind === "symlink")
|
|
16577
|
-
|
|
16846
|
+
symlinkSync8(operation.target, operation.path);
|
|
16578
16847
|
else {
|
|
16579
16848
|
const result = await exec(operation.argv, operation.stdin);
|
|
16580
16849
|
if (!(operation.accepted ?? [0]).includes(result.exitCode))
|
|
@@ -16616,7 +16885,7 @@ async function runCommunityRehearsalHost(request) {
|
|
|
16616
16885
|
return { ok: true, action: request.action, node: node.name };
|
|
16617
16886
|
}
|
|
16618
16887
|
if (request.action === "prepare") {
|
|
16619
|
-
const metadata =
|
|
16888
|
+
const metadata = lstatSync8(request.archivePath);
|
|
16620
16889
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash12("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
|
16621
16890
|
throw new Error("community rehearsal archive is not the declared bounded release");
|
|
16622
16891
|
}
|
|
@@ -16666,9 +16935,9 @@ async function runCommunityRehearsalHost(request) {
|
|
|
16666
16935
|
}
|
|
16667
16936
|
|
|
16668
16937
|
// src/supervised-app.ts
|
|
16669
|
-
import { lstatSync as
|
|
16938
|
+
import { lstatSync as lstatSync9, readFileSync as readFileSync19 } from "fs";
|
|
16670
16939
|
function readConfig(path2) {
|
|
16671
|
-
const stat =
|
|
16940
|
+
const stat = lstatSync9(path2);
|
|
16672
16941
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== 0 || (stat.mode & 18) !== 0 || stat.size > 128 * 1024) {
|
|
16673
16942
|
throw new Error("supervised app config is unsafe");
|
|
16674
16943
|
}
|
|
@@ -16851,17 +17120,17 @@ function agentCommandArgs(args, command3) {
|
|
|
16851
17120
|
return args.slice(index + 1);
|
|
16852
17121
|
}
|
|
16853
17122
|
function loadOrCreateSeed(path2) {
|
|
16854
|
-
if (
|
|
17123
|
+
if (existsSync23(path2)) {
|
|
16855
17124
|
const seed2 = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
16856
17125
|
if (seed2.length < 32) {
|
|
16857
17126
|
throw new Error(`agent: the seed at ${path2} is too short to derive a key from.`);
|
|
16858
17127
|
}
|
|
16859
17128
|
return seed2;
|
|
16860
17129
|
}
|
|
16861
|
-
mkdirSync18(
|
|
17130
|
+
mkdirSync18(dirname16(path2), { recursive: true });
|
|
16862
17131
|
const seed = new Uint8Array(randomBytes7(32));
|
|
16863
17132
|
writeFileSync18(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
|
|
16864
|
-
|
|
17133
|
+
chmodSync19(path2, 384);
|
|
16865
17134
|
return seed;
|
|
16866
17135
|
}
|
|
16867
17136
|
var DEFAULT_SOCKET_PATH = DEFAULT_SOCKET;
|
|
@@ -16890,7 +17159,7 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
|
|
|
16890
17159
|
if (!directory)
|
|
16891
17160
|
throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the node identity.");
|
|
16892
17161
|
const path2 = `${directory}/${name}`;
|
|
16893
|
-
if (!
|
|
17162
|
+
if (!existsSync23(path2))
|
|
16894
17163
|
throw new Error(`agent: the systemd credential ${name} is missing at ${path2}.`);
|
|
16895
17164
|
const seed = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
16896
17165
|
if (seed.length < 32)
|
|
@@ -17052,7 +17321,7 @@ if (import.meta.main) {
|
|
|
17052
17321
|
if (args.length !== 1)
|
|
17053
17322
|
throw new Error("project-release-check accepts no coordinates");
|
|
17054
17323
|
for (const relative of ["src/index.ts", "bun.lock", "src/generated/shared/contract-manifest.json"]) {
|
|
17055
|
-
const stat =
|
|
17324
|
+
const stat = lstatSync10(join12(process.cwd(), relative));
|
|
17056
17325
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1) {
|
|
17057
17326
|
throw new Error(`project release is missing ${relative}`);
|
|
17058
17327
|
}
|
|
@@ -17391,9 +17660,9 @@ if (import.meta.main) {
|
|
|
17391
17660
|
vcpu: cpus2().length,
|
|
17392
17661
|
memoryGib: Math.max(1, Math.floor(totalmem2() / 1024 ** 3)),
|
|
17393
17662
|
diskGib: Math.max(1, Math.floor(Number(filesystem.blocks) * Number(filesystem.bsize) / 1024 ** 3)),
|
|
17394
|
-
kvm:
|
|
17395
|
-
snpHost:
|
|
17396
|
-
helper:
|
|
17663
|
+
kvm: existsSync23("/dev/kvm"),
|
|
17664
|
+
snpHost: existsSync23("/dev/sev"),
|
|
17665
|
+
helper: existsSync23(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
17397
17666
|
}
|
|
17398
17667
|
};
|
|
17399
17668
|
const envelope = signRequest(keys2, keys2.ed25519.publicKey, {
|
|
@@ -17420,6 +17689,19 @@ if (import.meta.main) {
|
|
|
17420
17689
|
const keys2 = deriveKeysFromSeed(seed);
|
|
17421
17690
|
seed.fill(0);
|
|
17422
17691
|
const nodeKey2 = process.env.FZ_NODE_KEY ?? keys2.ed25519.publicKey;
|
|
17692
|
+
if (process.env.FZ_AGENT_HANDOVER_CANDIDATE === "true") {
|
|
17693
|
+
const ready = startAgentCandidateReady({
|
|
17694
|
+
version: VERSION2,
|
|
17695
|
+
nodeKey: nodeKey2,
|
|
17696
|
+
bound: true,
|
|
17697
|
+
vault: "unbound"
|
|
17698
|
+
}, process.env.FZ_AGENT_HANDOVER_READY_SOCKET ?? DEFAULT_AGENT_CANDIDATE_READY_SOCKET);
|
|
17699
|
+
console.log(`[metal-agent] candidate ${VERSION2} ready for atomic handover`);
|
|
17700
|
+
const stop2 = () => ready.close(() => process.exit(0));
|
|
17701
|
+
process.on("SIGTERM", stop2);
|
|
17702
|
+
process.on("SIGINT", stop2);
|
|
17703
|
+
await new Promise(() => {});
|
|
17704
|
+
}
|
|
17423
17705
|
const pull = startProvisioningPull({
|
|
17424
17706
|
apiUrl: process.env.FZ_API,
|
|
17425
17707
|
nodeKey: nodeKey2,
|
|
@@ -17428,9 +17710,9 @@ if (import.meta.main) {
|
|
|
17428
17710
|
metalHostname: process.env.FZ_METAL_HOSTNAME,
|
|
17429
17711
|
run: (claim) => requestMetalProvision(claim, process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET),
|
|
17430
17712
|
metalPreflight: () => ({
|
|
17431
|
-
snpHost:
|
|
17432
|
-
kvm:
|
|
17433
|
-
helper:
|
|
17713
|
+
snpHost: existsSync23("/dev/sev"),
|
|
17714
|
+
kvm: existsSync23("/dev/kvm"),
|
|
17715
|
+
helper: existsSync23(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
17434
17716
|
}),
|
|
17435
17717
|
onEvent: (event, detail) => console.log(`[metal-agent] ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
|
|
17436
17718
|
});
|
|
@@ -17513,7 +17795,11 @@ if (import.meta.main) {
|
|
|
17513
17795
|
const telemetry = new AgentTelemetryRuntime(createAgentTelemetry(resolveAgentTelemetryConfig()));
|
|
17514
17796
|
telemetry.event("agent.started");
|
|
17515
17797
|
telemetry.setDraining(false);
|
|
17516
|
-
const
|
|
17798
|
+
const handoverCandidate = process.env.FZ_AGENT_HANDOVER_CANDIDATE === "true";
|
|
17799
|
+
if (!handoverCandidate && process.env.FZ_AGENT_ROUTE_SOCKET && process.env.FZ_SOCKET_PATH) {
|
|
17800
|
+
ensureAgentSocketRoute(process.env.FZ_AGENT_ROUTE_SOCKET, process.env.FZ_SOCKET_PATH);
|
|
17801
|
+
}
|
|
17802
|
+
const attestationSource = existsSync23("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
|
|
17517
17803
|
const running = runAgent({
|
|
17518
17804
|
socketPath: process.env.FZ_SOCKET_PATH ?? DEFAULT_SOCKET_PATH,
|
|
17519
17805
|
seedCredential: process.env.FZ_SEED_CREDENTIAL,
|
|
@@ -17528,8 +17814,8 @@ if (import.meta.main) {
|
|
|
17528
17814
|
const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
|
|
17529
17815
|
let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
|
|
17530
17816
|
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 &&
|
|
17817
|
+
const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync23(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
|
|
17818
|
+
const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync23(process.env.FZ_ENROL_TOKEN_FILE));
|
|
17533
17819
|
if (!binding && (enrolmentCredentialAvailable || legacyEnrolmentFileAvailable) && process.env.FZ_API) {
|
|
17534
17820
|
binding = await enrolGuestIdentity({
|
|
17535
17821
|
apiUrl: process.env.FZ_API,
|
|
@@ -17549,6 +17835,7 @@ if (import.meta.main) {
|
|
|
17549
17835
|
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
17836
|
let secretCache;
|
|
17551
17837
|
let vaultSync;
|
|
17838
|
+
let initialVaultState = "unbound";
|
|
17552
17839
|
if (binding && attestationSource && nodeApiUrl) {
|
|
17553
17840
|
const result = await telemetry.observe("attestation.refresh", () => attestNodeOnce({
|
|
17554
17841
|
apiUrl: nodeApiUrl,
|
|
@@ -17566,6 +17853,7 @@ if (import.meta.main) {
|
|
|
17566
17853
|
projectKey: binding.projectKey
|
|
17567
17854
|
});
|
|
17568
17855
|
const initialVault = await telemetry.observe("vault.sync", () => initializeVaultReplica(secretCache), (result) => result.state === "ready" ? "success" : "failed");
|
|
17856
|
+
initialVaultState = initialVault.state;
|
|
17569
17857
|
running.setVault(secretCache, binding.projectKey);
|
|
17570
17858
|
vaultSync = startNodeVaultSync(secretCache, {
|
|
17571
17859
|
telemetry,
|
|
@@ -17579,6 +17867,31 @@ if (import.meta.main) {
|
|
|
17579
17867
|
console.error("[agent] vault replica unavailable; authenticated refresh remains active and only explicit " + "same-name systemd deployment fallbacks are available");
|
|
17580
17868
|
}
|
|
17581
17869
|
}
|
|
17870
|
+
if (handoverCandidate) {
|
|
17871
|
+
const ready = startAgentCandidateReady({
|
|
17872
|
+
version: VERSION2,
|
|
17873
|
+
nodeKey,
|
|
17874
|
+
bound: Boolean(binding),
|
|
17875
|
+
vault: initialVaultState
|
|
17876
|
+
}, process.env.FZ_AGENT_HANDOVER_READY_SOCKET ?? DEFAULT_AGENT_CANDIDATE_READY_SOCKET);
|
|
17877
|
+
console.log(`[agent] candidate ${VERSION2} ready for atomic handover`);
|
|
17878
|
+
let stopping = false;
|
|
17879
|
+
const stop = async () => {
|
|
17880
|
+
if (stopping)
|
|
17881
|
+
return;
|
|
17882
|
+
stopping = true;
|
|
17883
|
+
await vaultSync?.stop();
|
|
17884
|
+
await Promise.all([
|
|
17885
|
+
new Promise((resolve6) => ready.close(() => resolve6())),
|
|
17886
|
+
new Promise((resolve6) => server.close(() => resolve6()))
|
|
17887
|
+
]);
|
|
17888
|
+
await telemetry.close();
|
|
17889
|
+
process.exit(0);
|
|
17890
|
+
};
|
|
17891
|
+
process.on("SIGTERM", () => void stop());
|
|
17892
|
+
process.on("SIGINT", () => void stop());
|
|
17893
|
+
await new Promise(() => {});
|
|
17894
|
+
}
|
|
17582
17895
|
const attestationLoop = attestationSource && nodeApiUrl ? startNodeAttestation({
|
|
17583
17896
|
apiUrl: nodeApiUrl,
|
|
17584
17897
|
nodeKey,
|
|
@@ -17607,7 +17920,7 @@ if (import.meta.main) {
|
|
|
17607
17920
|
const bootstrapEnabled = process.env.FZ_BOOTSTRAP_PULL === "true";
|
|
17608
17921
|
const bootstrapCredential = process.env.FZ_BOOTSTRAP_SSH_KEY_CREDENTIAL;
|
|
17609
17922
|
const bootstrapKeyPath = bootstrapCredential && process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/${bootstrapCredential}` : undefined;
|
|
17610
|
-
if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !
|
|
17923
|
+
if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync23(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
|
|
17611
17924
|
throw new Error("agent: bootstrap runner requires enrolment, API, local SSH credential and target OTLP coordinate");
|
|
17612
17925
|
}
|
|
17613
17926
|
const bootstrapPull = bootstrapEnabled ? startSshBootstrapPull({
|