@forgezero/agent 0.1.31 → 0.1.32
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-heartbeat.d.ts +10 -2
- package/dist/agent-heartbeat.js +396 -54
- package/dist/agent-update-helper.d.ts +41 -2
- package/dist/agent-update-helper.js +360 -43
- package/dist/agent-update.d.ts +1 -0
- package/dist/agent-update.js +34 -2
- package/dist/fz-agent.js +418 -73
- package/dist/fz.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/provision.js +363 -49
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/fz-agent.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
6
|
-
import { readFileSync as
|
|
7
|
-
import { dirname as dirname7, join as
|
|
6
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync13, mkdirSync as mkdirSync9, chmodSync as chmodSync12 } from "fs";
|
|
7
|
+
import { dirname as dirname7, join as join6 } from "path";
|
|
8
8
|
|
|
9
9
|
// ../access/dist/security.js
|
|
10
10
|
var HEX = Array.from({ length: 256 }, (_, index) => index.toString(16).padStart(2, "0"));
|
|
@@ -7581,8 +7581,11 @@ function materializeWarpMdm(options) {
|
|
|
7581
7581
|
import { createHash as createHash3, timingSafeEqual, randomUUID as randomUUID2 } from "crypto";
|
|
7582
7582
|
import {
|
|
7583
7583
|
chmodSync as chmodSync9,
|
|
7584
|
+
closeSync,
|
|
7584
7585
|
existsSync as existsSync10,
|
|
7586
|
+
fsyncSync,
|
|
7585
7587
|
mkdirSync as mkdirSync6,
|
|
7588
|
+
openSync,
|
|
7586
7589
|
readFileSync as readFileSync6,
|
|
7587
7590
|
readlinkSync,
|
|
7588
7591
|
renameSync as renameSync4,
|
|
@@ -7596,6 +7599,25 @@ var DEFAULT_AGENT_UPDATE_SOCKET = "/run/forgezero-update/helper.sock";
|
|
|
7596
7599
|
var MAX_AGENT_TARBALL_BYTES = 32 * 1024 * 1024;
|
|
7597
7600
|
var VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
7598
7601
|
var REGISTRY = "registry.npmjs.org";
|
|
7602
|
+
var syncPath = (path) => {
|
|
7603
|
+
const descriptor = openSync(path, "r");
|
|
7604
|
+
try {
|
|
7605
|
+
fsyncSync(descriptor);
|
|
7606
|
+
} finally {
|
|
7607
|
+
closeSync(descriptor);
|
|
7608
|
+
}
|
|
7609
|
+
};
|
|
7610
|
+
var syncReleaseDirectory = (directory) => {
|
|
7611
|
+
for (const path of [
|
|
7612
|
+
join4(directory, "package.json"),
|
|
7613
|
+
join4(directory, "dist", "fz-agent.js"),
|
|
7614
|
+
join4(directory, "dist", "fz.js"),
|
|
7615
|
+
join4(directory, "dist"),
|
|
7616
|
+
directory,
|
|
7617
|
+
dirname4(directory)
|
|
7618
|
+
])
|
|
7619
|
+
syncPath(path);
|
|
7620
|
+
};
|
|
7599
7621
|
function validateAgentRelease(release) {
|
|
7600
7622
|
if (release?.package !== "@forgezero/agent")
|
|
7601
7623
|
throw new Error("agent update package is fixed");
|
|
@@ -7714,16 +7736,24 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
7714
7736
|
]
|
|
7715
7737
|
}, "agent update extraction");
|
|
7716
7738
|
await validateReleaseDirectory(unpacked, release, run);
|
|
7717
|
-
if (!existsSync10(finalDirectory))
|
|
7739
|
+
if (!existsSync10(finalDirectory)) {
|
|
7718
7740
|
renameSync4(unpacked, finalDirectory);
|
|
7719
|
-
|
|
7741
|
+
syncReleaseDirectory(finalDirectory);
|
|
7742
|
+
} else
|
|
7720
7743
|
await validateReleaseDirectory(finalDirectory, release, run);
|
|
7721
7744
|
if (!existsSync10(currentLink)) {
|
|
7722
7745
|
throw new Error("agent update requires an active immutable release to roll back to");
|
|
7723
7746
|
}
|
|
7724
7747
|
const previousTarget = readlinkSync(currentLink);
|
|
7748
|
+
if (previousTarget !== join4("versions", options.currentVersion)) {
|
|
7749
|
+
throw new Error("agent update current release does not match the running version");
|
|
7750
|
+
}
|
|
7751
|
+
if (!existsSync10(join4(root, previousTarget))) {
|
|
7752
|
+
throw new Error("agent update rollback release is missing");
|
|
7753
|
+
}
|
|
7725
7754
|
return {
|
|
7726
7755
|
version: release.version,
|
|
7756
|
+
fromVersion: options.currentVersion,
|
|
7727
7757
|
directory: finalDirectory,
|
|
7728
7758
|
previousTarget,
|
|
7729
7759
|
nextTarget: join4("versions", release.version),
|
|
@@ -7738,6 +7768,7 @@ function selectAgentRelease(staged) {
|
|
|
7738
7768
|
try {
|
|
7739
7769
|
symlinkSync2(staged.nextTarget, next);
|
|
7740
7770
|
renameSync4(next, staged.currentLink);
|
|
7771
|
+
syncPath(dirname4(staged.currentLink));
|
|
7741
7772
|
} finally {
|
|
7742
7773
|
rmSync2(next, { force: true });
|
|
7743
7774
|
}
|
|
@@ -7747,24 +7778,187 @@ function restoreAgentRelease(staged) {
|
|
|
7747
7778
|
try {
|
|
7748
7779
|
symlinkSync2(staged.previousTarget, next);
|
|
7749
7780
|
renameSync4(next, staged.currentLink);
|
|
7781
|
+
syncPath(dirname4(staged.currentLink));
|
|
7750
7782
|
} finally {
|
|
7751
7783
|
rmSync2(next, { force: true });
|
|
7752
7784
|
}
|
|
7753
7785
|
}
|
|
7754
7786
|
|
|
7755
7787
|
// src/agent-update-helper.ts
|
|
7756
|
-
import {
|
|
7788
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
7789
|
+
import {
|
|
7790
|
+
chmodSync as chmodSync10,
|
|
7791
|
+
closeSync as closeSync2,
|
|
7792
|
+
existsSync as existsSync11,
|
|
7793
|
+
fsyncSync as fsyncSync2,
|
|
7794
|
+
mkdirSync as mkdirSync7,
|
|
7795
|
+
openSync as openSync2,
|
|
7796
|
+
readFileSync as readFileSync7,
|
|
7797
|
+
renameSync as renameSync5,
|
|
7798
|
+
rmSync as rmSync3,
|
|
7799
|
+
unlinkSync as unlinkSync9,
|
|
7800
|
+
writeFileSync as writeFileSync7
|
|
7801
|
+
} from "fs";
|
|
7757
7802
|
import { connect as connect5, createServer as createServer6 } from "net";
|
|
7758
|
-
import { dirname as dirname5 } from "path";
|
|
7803
|
+
import { dirname as dirname5, join as join5, resolve as resolve3 } from "path";
|
|
7759
7804
|
var AGENT_UPDATE_GROUP = "forgezero-update";
|
|
7760
7805
|
var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-helper.service";
|
|
7761
|
-
var
|
|
7806
|
+
var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
|
|
7807
|
+
var AGENT_UPDATE_RECEIPT = "/var/lib/forgezero/agent-update-receipt.json";
|
|
7762
7808
|
var MAX_REQUEST_BYTES5 = 8 * 1024;
|
|
7763
7809
|
var COMPUTE_HELPER_UNITS = [
|
|
7764
7810
|
"forgezero-deploy-runner.service",
|
|
7765
7811
|
"forgezero-lifecycle-helper.service",
|
|
7766
7812
|
"forgezero-software-helper.service"
|
|
7767
7813
|
];
|
|
7814
|
+
var VERSION2 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
7815
|
+
var ATTEMPT_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
7816
|
+
var REASON_CODE = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
7817
|
+
var MAX_REASON_BYTES = 512;
|
|
7818
|
+
var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
7819
|
+
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
7820
|
+
var boundedMessage = (value) => {
|
|
7821
|
+
let message = value.replace(/[\r\n]+/g, " ").trim();
|
|
7822
|
+
while (Buffer.byteLength(message, "utf8") > MAX_REASON_BYTES)
|
|
7823
|
+
message = message.slice(0, -1);
|
|
7824
|
+
return message;
|
|
7825
|
+
};
|
|
7826
|
+
var reason = (code, message) => ({
|
|
7827
|
+
code: REASON_CODE.test(code) ? code : "UPDATE_FAILED",
|
|
7828
|
+
message: boundedMessage(message) || "Agent update failed"
|
|
7829
|
+
});
|
|
7830
|
+
var validTime = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
7831
|
+
function validateReceipt(value) {
|
|
7832
|
+
if (!value || typeof value !== "object")
|
|
7833
|
+
throw new Error("Agent update receipt is malformed");
|
|
7834
|
+
const receipt = value;
|
|
7835
|
+
if (!receipt.attemptId || !ATTEMPT_ID.test(receipt.attemptId))
|
|
7836
|
+
throw new Error("Agent update attempt ID is invalid");
|
|
7837
|
+
if (!receipt.fromVersion || !VERSION2.test(receipt.fromVersion))
|
|
7838
|
+
throw new Error("Agent update source version is invalid");
|
|
7839
|
+
if (!receipt.targetVersion || !VERSION2.test(receipt.targetVersion))
|
|
7840
|
+
throw new Error("Agent update target version is invalid");
|
|
7841
|
+
if (!["activating", "active", "rolled-back", "failed"].includes(receipt.outcome ?? "")) {
|
|
7842
|
+
throw new Error("Agent update outcome is invalid");
|
|
7843
|
+
}
|
|
7844
|
+
if (!validTime(receipt.startedAtTs) || !validTime(receipt.updatedAtTs)) {
|
|
7845
|
+
throw new Error("Agent update timestamps are invalid");
|
|
7846
|
+
}
|
|
7847
|
+
if (receipt.retryAfterTs !== undefined && !validTime(receipt.retryAfterTs)) {
|
|
7848
|
+
throw new Error("Agent update retry timestamp is invalid");
|
|
7849
|
+
}
|
|
7850
|
+
if (receipt.rollbackHealthy !== undefined && typeof receipt.rollbackHealthy !== "boolean") {
|
|
7851
|
+
throw new Error("Agent update rollback health is invalid");
|
|
7852
|
+
}
|
|
7853
|
+
if (receipt.reason && (!REASON_CODE.test(receipt.reason.code) || typeof receipt.reason.message !== "string" || Buffer.byteLength(receipt.reason.message, "utf8") > MAX_REASON_BYTES))
|
|
7854
|
+
throw new Error("Agent update failure reason is invalid");
|
|
7855
|
+
return {
|
|
7856
|
+
attemptId: receipt.attemptId,
|
|
7857
|
+
fromVersion: receipt.fromVersion,
|
|
7858
|
+
targetVersion: receipt.targetVersion,
|
|
7859
|
+
outcome: receipt.outcome,
|
|
7860
|
+
startedAtTs: receipt.startedAtTs,
|
|
7861
|
+
updatedAtTs: receipt.updatedAtTs,
|
|
7862
|
+
...receipt.retryAfterTs === undefined ? {} : { retryAfterTs: receipt.retryAfterTs },
|
|
7863
|
+
...receipt.rollbackHealthy === undefined ? {} : { rollbackHealthy: receipt.rollbackHealthy },
|
|
7864
|
+
...receipt.reason === undefined ? {} : { reason: receipt.reason }
|
|
7865
|
+
};
|
|
7866
|
+
}
|
|
7867
|
+
function validateJournal(value, root) {
|
|
7868
|
+
if (!value || typeof value !== "object")
|
|
7869
|
+
throw new Error("Agent update journal is malformed");
|
|
7870
|
+
const legacy = value;
|
|
7871
|
+
if (legacy.schemaVersion === undefined) {
|
|
7872
|
+
if (typeof legacy.version === "string" && VERSION2.test(legacy.version) && legacy.outcome === "active" && validTime(legacy.updatedAtTs) && Object.keys(value).every((key) => ["version", "outcome", "updatedAtTs"].includes(key)))
|
|
7873
|
+
return;
|
|
7874
|
+
throw new Error("Agent update legacy receipt is malformed");
|
|
7875
|
+
}
|
|
7876
|
+
const journal = value;
|
|
7877
|
+
const receipt = validateReceipt(journal);
|
|
7878
|
+
if (journal.schemaVersion !== 1)
|
|
7879
|
+
throw new Error("Agent update journal schema is unsupported");
|
|
7880
|
+
if (journal.target !== "compute" && journal.target !== "metal")
|
|
7881
|
+
throw new Error("Agent update target is invalid");
|
|
7882
|
+
if (!Number.isSafeInteger(journal.failureCount) || (journal.failureCount ?? -1) < 0) {
|
|
7883
|
+
throw new Error("Agent update failure count is invalid");
|
|
7884
|
+
}
|
|
7885
|
+
const releaseRoot = resolve3(root);
|
|
7886
|
+
if (journal.currentLink !== join5(releaseRoot, "current"))
|
|
7887
|
+
throw new Error("Agent update current link is invalid");
|
|
7888
|
+
if (journal.previousTarget !== join5("versions", receipt.fromVersion)) {
|
|
7889
|
+
throw new Error("Agent update rollback target is invalid");
|
|
7890
|
+
}
|
|
7891
|
+
if (journal.nextTarget !== join5("versions", receipt.targetVersion)) {
|
|
7892
|
+
throw new Error("Agent update next target is invalid");
|
|
7893
|
+
}
|
|
7894
|
+
return journal;
|
|
7895
|
+
}
|
|
7896
|
+
function readJournal(path, root) {
|
|
7897
|
+
if (!existsSync11(path))
|
|
7898
|
+
return;
|
|
7899
|
+
return validateJournal(JSON.parse(readFileSync7(path, "utf8")), root);
|
|
7900
|
+
}
|
|
7901
|
+
function writeAtomic(path, value, mode) {
|
|
7902
|
+
mkdirSync7(dirname5(path), { recursive: true, mode: 493 });
|
|
7903
|
+
const next = `${path}.${randomUUID3()}.next`;
|
|
7904
|
+
let file;
|
|
7905
|
+
try {
|
|
7906
|
+
file = openSync2(next, "wx", mode);
|
|
7907
|
+
writeFileSync7(file, `${JSON.stringify(value)}
|
|
7908
|
+
`);
|
|
7909
|
+
fsyncSync2(file);
|
|
7910
|
+
closeSync2(file);
|
|
7911
|
+
file = undefined;
|
|
7912
|
+
renameSync5(next, path);
|
|
7913
|
+
const directory = openSync2(dirname5(path), "r");
|
|
7914
|
+
try {
|
|
7915
|
+
fsyncSync2(directory);
|
|
7916
|
+
} finally {
|
|
7917
|
+
closeSync2(directory);
|
|
7918
|
+
}
|
|
7919
|
+
} finally {
|
|
7920
|
+
if (file !== undefined)
|
|
7921
|
+
closeSync2(file);
|
|
7922
|
+
rmSync3(next, { force: true });
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7925
|
+
var publicReceipt = (journal) => {
|
|
7926
|
+
const {
|
|
7927
|
+
attemptId,
|
|
7928
|
+
fromVersion,
|
|
7929
|
+
targetVersion,
|
|
7930
|
+
outcome,
|
|
7931
|
+
startedAtTs,
|
|
7932
|
+
updatedAtTs,
|
|
7933
|
+
retryAfterTs,
|
|
7934
|
+
rollbackHealthy,
|
|
7935
|
+
reason: failureReason
|
|
7936
|
+
} = journal;
|
|
7937
|
+
return {
|
|
7938
|
+
attemptId,
|
|
7939
|
+
fromVersion,
|
|
7940
|
+
targetVersion,
|
|
7941
|
+
outcome,
|
|
7942
|
+
startedAtTs,
|
|
7943
|
+
updatedAtTs,
|
|
7944
|
+
...retryAfterTs === undefined ? {} : { retryAfterTs },
|
|
7945
|
+
...rollbackHealthy === undefined ? {} : { rollbackHealthy },
|
|
7946
|
+
...failureReason === undefined ? {} : { reason: failureReason }
|
|
7947
|
+
};
|
|
7948
|
+
};
|
|
7949
|
+
function writeUpdateState(journalPath, receiptPath, journal) {
|
|
7950
|
+
writeAtomic(journalPath, journal, 384);
|
|
7951
|
+
writeAtomic(receiptPath, publicReceipt(journal), 416);
|
|
7952
|
+
}
|
|
7953
|
+
function readAgentUpdateReceipt(path = AGENT_UPDATE_RECEIPT) {
|
|
7954
|
+
try {
|
|
7955
|
+
if (!existsSync11(path))
|
|
7956
|
+
return;
|
|
7957
|
+
return validateReceipt(JSON.parse(readFileSync7(path, "utf8")));
|
|
7958
|
+
} catch {
|
|
7959
|
+
return;
|
|
7960
|
+
}
|
|
7961
|
+
}
|
|
7768
7962
|
var runCommand = async (input) => {
|
|
7769
7963
|
const child = Bun.spawn([input.command, ...input.args], {
|
|
7770
7964
|
cwd: input.cwd,
|
|
@@ -7780,8 +7974,27 @@ var runCommand = async (input) => {
|
|
|
7780
7974
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
7781
7975
|
};
|
|
7782
7976
|
var runOk = async (run, command2, args) => (await run({ command: command2, args })).exitCode === 0;
|
|
7977
|
+
var retryAfter = (now, failures) => now + Math.min(UPDATE_RETRY_MAX_MS, UPDATE_RETRY_BASE_MS * 2 ** Math.min(16, Math.max(0, failures - 1)));
|
|
7978
|
+
var stagedFromJournal = (journal, root) => ({
|
|
7979
|
+
version: journal.targetVersion,
|
|
7980
|
+
fromVersion: journal.fromVersion,
|
|
7981
|
+
directory: join5(resolve3(root), journal.nextTarget),
|
|
7982
|
+
previousTarget: journal.previousTarget,
|
|
7983
|
+
nextTarget: journal.nextTarget,
|
|
7984
|
+
currentLink: journal.currentLink
|
|
7985
|
+
});
|
|
7986
|
+
var restartAgent = async (target, run) => {
|
|
7987
|
+
const helpers = target === "compute" ? COMPUTE_HELPER_UNITS : ["forgezero-metal-helper.service"];
|
|
7988
|
+
for (const unit of helpers)
|
|
7989
|
+
await run({ command: "/usr/bin/systemctl", args: ["try-restart", unit] });
|
|
7990
|
+
const service = target === "compute" ? "forgezero-agent.service" : "forgezero-metal-agent.service";
|
|
7991
|
+
if (!await runOk(run, "/usr/bin/systemctl", ["restart", service])) {
|
|
7992
|
+
throw new Error(`systemd could not restart ${service}`);
|
|
7993
|
+
}
|
|
7994
|
+
};
|
|
7995
|
+
var targetProbe = (target, run) => target === "compute" ? () => probeAgentSocket() : async () => await runOk(run, "/usr/bin/systemctl", ["is-active", "--quiet", "forgezero-metal-agent.service"]) && await runOk(run, "/usr/bin/systemctl", ["is-active", "--quiet", "forgezero-metal-helper.service"]);
|
|
7783
7996
|
function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
7784
|
-
return new Promise((
|
|
7997
|
+
return new Promise((resolve4) => {
|
|
7785
7998
|
const socket = connect5(socketPath);
|
|
7786
7999
|
let settled = false;
|
|
7787
8000
|
let buffer = "";
|
|
@@ -7791,7 +8004,7 @@ function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
|
7791
8004
|
settled = true;
|
|
7792
8005
|
clearTimeout(timer);
|
|
7793
8006
|
socket.destroy();
|
|
7794
|
-
|
|
8007
|
+
resolve4(value);
|
|
7795
8008
|
};
|
|
7796
8009
|
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
7797
8010
|
socket.on("connect", () => socket.write(`{"op":"identity"}
|
|
@@ -7815,55 +8028,136 @@ function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
|
|
|
7815
8028
|
async function activateAgentRelease(staged, options = {}) {
|
|
7816
8029
|
const run = options.run ?? runCommand;
|
|
7817
8030
|
const target = options.target ?? "compute";
|
|
7818
|
-
const probe = options.probe ?? (target
|
|
7819
|
-
const
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
8031
|
+
const probe = options.probe ?? targetProbe(target, run);
|
|
8032
|
+
const now = options.now ?? Date.now;
|
|
8033
|
+
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
8034
|
+
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
8035
|
+
const previous = readJournal(journalPath, dirname5(staged.currentLink));
|
|
8036
|
+
const attemptId = options.attemptId ?? randomUUID3();
|
|
8037
|
+
if (!ATTEMPT_ID.test(attemptId))
|
|
8038
|
+
throw new Error("Agent update attempt ID is invalid");
|
|
8039
|
+
const startedAtTs = now();
|
|
8040
|
+
const failureCount = previous?.targetVersion === staged.version ? previous.failureCount : 0;
|
|
8041
|
+
let journal = {
|
|
8042
|
+
schemaVersion: 1,
|
|
8043
|
+
attemptId,
|
|
8044
|
+
target,
|
|
8045
|
+
fromVersion: staged.fromVersion,
|
|
8046
|
+
targetVersion: staged.version,
|
|
8047
|
+
outcome: "activating",
|
|
8048
|
+
startedAtTs,
|
|
8049
|
+
updatedAtTs: startedAtTs,
|
|
8050
|
+
currentLink: staged.currentLink,
|
|
8051
|
+
previousTarget: staged.previousTarget,
|
|
8052
|
+
nextTarget: staged.nextTarget,
|
|
8053
|
+
failureCount
|
|
7828
8054
|
};
|
|
7829
|
-
|
|
8055
|
+
writeUpdateState(journalPath, receiptPath, journal);
|
|
8056
|
+
let selectionAttempted = false;
|
|
7830
8057
|
try {
|
|
8058
|
+
selectionAttempted = true;
|
|
7831
8059
|
selectAgentRelease(staged);
|
|
7832
|
-
|
|
7833
|
-
await restart();
|
|
8060
|
+
await restartAgent(target, run);
|
|
7834
8061
|
if (!await probe())
|
|
7835
8062
|
throw new Error("the replacement Agent did not answer its retained Vault socket");
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
const next = `${receipt}.next`;
|
|
7839
|
-
writeFileSync7(next, JSON.stringify({
|
|
7840
|
-
version: staged.version,
|
|
7841
|
-
outcome: "active",
|
|
7842
|
-
updatedAtTs: (options.now ?? Date.now)()
|
|
7843
|
-
}) + `
|
|
7844
|
-
`, { mode: 420 });
|
|
7845
|
-
renameSync5(next, receipt);
|
|
8063
|
+
journal = { ...journal, outcome: "active", updatedAtTs: now(), rollbackHealthy: undefined };
|
|
8064
|
+
writeUpdateState(journalPath, receiptPath, journal);
|
|
7846
8065
|
run({
|
|
7847
8066
|
command: "/usr/bin/systemctl",
|
|
7848
8067
|
args: ["try-restart", "--no-block", "forgezero-agent-update-helper.service"]
|
|
7849
8068
|
});
|
|
7850
8069
|
return { ok: true, version: staged.version };
|
|
7851
8070
|
} catch (cause) {
|
|
7852
|
-
const
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
8071
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
8072
|
+
let rollbackHealthy = false;
|
|
8073
|
+
let restored = false;
|
|
8074
|
+
if (selectionAttempted) {
|
|
8075
|
+
try {
|
|
8076
|
+
restoreAgentRelease(staged);
|
|
8077
|
+
restored = true;
|
|
8078
|
+
await restartAgent(target, run);
|
|
8079
|
+
rollbackHealthy = await probe();
|
|
8080
|
+
} catch {
|
|
8081
|
+
rollbackHealthy = false;
|
|
8082
|
+
}
|
|
7856
8083
|
}
|
|
7857
|
-
|
|
8084
|
+
const failures = failureCount + 1;
|
|
8085
|
+
const updatedAtTs = now();
|
|
8086
|
+
journal = {
|
|
8087
|
+
...journal,
|
|
8088
|
+
outcome: rollbackHealthy ? "rolled-back" : "failed",
|
|
8089
|
+
updatedAtTs,
|
|
8090
|
+
retryAfterTs: retryAfter(updatedAtTs, failures),
|
|
8091
|
+
rollbackHealthy,
|
|
8092
|
+
reason: reason(rollbackHealthy ? "REPLACEMENT_UNHEALTHY" : "ROLLBACK_UNHEALTHY", rollbackHealthy ? message : `${message}; the restored Agent did not pass its health probe`),
|
|
8093
|
+
failureCount: failures
|
|
8094
|
+
};
|
|
8095
|
+
writeUpdateState(journalPath, receiptPath, journal);
|
|
8096
|
+
return { ok: false, rolledBack: restored, rollbackHealthy, reason: message };
|
|
7858
8097
|
}
|
|
7859
8098
|
}
|
|
8099
|
+
async function recoverInterruptedAgentUpdate(options = {}) {
|
|
8100
|
+
const root = resolve3(options.root ?? DEFAULT_AGENT_RELEASE_ROOT);
|
|
8101
|
+
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
8102
|
+
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
8103
|
+
const journal = readJournal(journalPath, root);
|
|
8104
|
+
if (!journal)
|
|
8105
|
+
return;
|
|
8106
|
+
if (journal.outcome !== "activating") {
|
|
8107
|
+
writeAtomic(receiptPath, publicReceipt(journal), 416);
|
|
8108
|
+
return publicReceipt(journal);
|
|
8109
|
+
}
|
|
8110
|
+
const staged = stagedFromJournal(journal, root);
|
|
8111
|
+
if (!existsSync11(join5(root, journal.previousTarget))) {
|
|
8112
|
+
throw new Error("Agent update rollback release is missing");
|
|
8113
|
+
}
|
|
8114
|
+
const run = options.run ?? runCommand;
|
|
8115
|
+
const probe = options.probe ?? targetProbe(journal.target, run);
|
|
8116
|
+
restoreAgentRelease(staged);
|
|
8117
|
+
let rollbackHealthy = false;
|
|
8118
|
+
let failureMessage = "activation was interrupted before its health verdict became durable";
|
|
8119
|
+
try {
|
|
8120
|
+
await restartAgent(journal.target, run);
|
|
8121
|
+
rollbackHealthy = await probe();
|
|
8122
|
+
} catch (cause) {
|
|
8123
|
+
failureMessage = `${failureMessage}; ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
8124
|
+
}
|
|
8125
|
+
const failures = journal.failureCount + 1;
|
|
8126
|
+
const updatedAtTs = (options.now ?? Date.now)();
|
|
8127
|
+
const recovered = {
|
|
8128
|
+
...journal,
|
|
8129
|
+
outcome: rollbackHealthy ? "rolled-back" : "failed",
|
|
8130
|
+
updatedAtTs,
|
|
8131
|
+
retryAfterTs: retryAfter(updatedAtTs, failures),
|
|
8132
|
+
rollbackHealthy,
|
|
8133
|
+
reason: reason(rollbackHealthy ? "ACTIVATION_INTERRUPTED" : "ROLLBACK_UNHEALTHY", failureMessage),
|
|
8134
|
+
failureCount: failures
|
|
8135
|
+
};
|
|
8136
|
+
writeUpdateState(journalPath, receiptPath, recovered);
|
|
8137
|
+
return readAgentUpdateReceipt(receiptPath);
|
|
8138
|
+
}
|
|
7860
8139
|
function startAgentUpdateHelper(options = {}) {
|
|
7861
8140
|
const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
|
|
7862
8141
|
if (existsSync11(socketPath))
|
|
7863
8142
|
unlinkSync9(socketPath);
|
|
7864
8143
|
mkdirSync7(dirname5(socketPath), { recursive: true, mode: 488 });
|
|
7865
8144
|
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
7866
|
-
const
|
|
8145
|
+
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
8146
|
+
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
8147
|
+
const releaseRoot = options.root ?? DEFAULT_AGENT_RELEASE_ROOT;
|
|
8148
|
+
const activate = options.activate ?? ((staged, target, attemptId) => activateAgentRelease(staged, { target, attemptId, journalPath, receiptPath, now: options.now }));
|
|
8149
|
+
let busy = true;
|
|
8150
|
+
let blocked;
|
|
8151
|
+
(options.recover ?? (() => recoverInterruptedAgentUpdate({
|
|
8152
|
+
root: releaseRoot,
|
|
8153
|
+
journalPath,
|
|
8154
|
+
receiptPath,
|
|
8155
|
+
now: options.now
|
|
8156
|
+
})))().catch((cause) => {
|
|
8157
|
+
blocked = cause instanceof Error ? cause.message : String(cause);
|
|
8158
|
+
}).finally(() => {
|
|
8159
|
+
busy = false;
|
|
8160
|
+
});
|
|
7867
8161
|
const server = createServer6((socket) => {
|
|
7868
8162
|
let buffer = "";
|
|
7869
8163
|
socket.on("data", (chunk) => {
|
|
@@ -7879,22 +8173,42 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
7879
8173
|
return;
|
|
7880
8174
|
const line = buffer.slice(0, newline);
|
|
7881
8175
|
buffer = "";
|
|
8176
|
+
let ownsBusy = false;
|
|
7882
8177
|
Promise.resolve().then(() => JSON.parse(line)).then(async (request) => {
|
|
8178
|
+
if (blocked)
|
|
8179
|
+
throw new Error(`update journal needs operator recovery: ${blocked}`);
|
|
8180
|
+
if (busy)
|
|
8181
|
+
throw new Error("another Agent update or recovery is already active");
|
|
7883
8182
|
if (request.op !== "apply")
|
|
7884
8183
|
throw new Error("unknown update operation");
|
|
7885
8184
|
if (request.target !== "compute" && request.target !== "metal") {
|
|
7886
8185
|
throw new Error("agent update target is invalid");
|
|
7887
8186
|
}
|
|
8187
|
+
const attemptId = request.attemptId ?? randomUUID3();
|
|
8188
|
+
if (!ATTEMPT_ID.test(attemptId))
|
|
8189
|
+
throw new Error("Agent update attempt ID is invalid");
|
|
8190
|
+
const prior = readJournal(journalPath, releaseRoot);
|
|
8191
|
+
const now = (options.now ?? Date.now)();
|
|
8192
|
+
if (prior?.targetVersion === request.release.version && (prior.outcome === "rolled-back" || prior.outcome === "failed") && (prior.retryAfterTs ?? 0) > now)
|
|
8193
|
+
throw new Error(`Agent update ${request.release.version} is quarantined until ${prior.retryAfterTs}`);
|
|
8194
|
+
busy = true;
|
|
8195
|
+
ownsBusy = true;
|
|
7888
8196
|
const staged = await stageAgentRelease(request.release, {
|
|
7889
8197
|
currentVersion: request.currentVersion,
|
|
7890
|
-
root:
|
|
8198
|
+
root: releaseRoot
|
|
7891
8199
|
});
|
|
7892
|
-
const response = { ok: true, status: "staged", version: staged.version };
|
|
8200
|
+
const response = { ok: true, status: "staged", version: staged.version, attemptId };
|
|
7893
8201
|
socket.end(`${JSON.stringify(response)}
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
8202
|
+
`);
|
|
8203
|
+
setTimer(() => void activate(staged, request.target, attemptId).catch((cause) => {
|
|
8204
|
+
blocked = cause instanceof Error ? cause.message : String(cause);
|
|
8205
|
+
}).finally(() => {
|
|
8206
|
+
busy = false;
|
|
8207
|
+
}), 100);
|
|
8208
|
+
ownsBusy = false;
|
|
7897
8209
|
}).catch((cause) => {
|
|
8210
|
+
if (ownsBusy)
|
|
8211
|
+
busy = false;
|
|
7898
8212
|
const response = {
|
|
7899
8213
|
ok: false,
|
|
7900
8214
|
error: { code: "UPDATE_REFUSED", message: cause instanceof Error ? cause.message : String(cause) }
|
|
@@ -7909,7 +8223,7 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
7909
8223
|
return server;
|
|
7910
8224
|
}
|
|
7911
8225
|
function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
|
|
7912
|
-
return new Promise((
|
|
8226
|
+
return new Promise((resolve4, reject) => {
|
|
7913
8227
|
const socket = connect5(socketPath, () => socket.write(`${JSON.stringify(request)}
|
|
7914
8228
|
`));
|
|
7915
8229
|
let buffer = "";
|
|
@@ -7925,7 +8239,7 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
|
|
|
7925
8239
|
return;
|
|
7926
8240
|
socket.end();
|
|
7927
8241
|
try {
|
|
7928
|
-
|
|
8242
|
+
resolve4(JSON.parse(buffer.slice(0, newline)));
|
|
7929
8243
|
} catch (cause) {
|
|
7930
8244
|
reject(cause);
|
|
7931
8245
|
}
|
|
@@ -7935,14 +8249,14 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
|
|
|
7935
8249
|
}
|
|
7936
8250
|
|
|
7937
8251
|
// src/agent-heartbeat.ts
|
|
7938
|
-
import { readFileSync as
|
|
8252
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
7939
8253
|
|
|
7940
8254
|
// src/version.ts
|
|
7941
|
-
var
|
|
8255
|
+
var VERSION3 = "0.1.32";
|
|
7942
8256
|
|
|
7943
8257
|
// src/agent-heartbeat.ts
|
|
7944
8258
|
var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
|
|
7945
|
-
function observeAgentHost(version =
|
|
8259
|
+
function observeAgentHost(version = VERSION3, mode = "enrolled", osRelease = readFileSync8("/etc/os-release", "utf8"), architecture = process.arch) {
|
|
7946
8260
|
const values = Object.fromEntries(osRelease.split(`
|
|
7947
8261
|
`).flatMap((line) => {
|
|
7948
8262
|
const separator = line.indexOf("=");
|
|
@@ -7956,23 +8270,51 @@ function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = rea
|
|
|
7956
8270
|
};
|
|
7957
8271
|
}
|
|
7958
8272
|
async function heartbeatAgentOnce(options) {
|
|
7959
|
-
|
|
8273
|
+
let observation = (options.observation ?? (() => observeAgentHost(options.version ?? VERSION3, options.mode)))();
|
|
8274
|
+
const update = observation.update ?? readAgentUpdateReceipt(options.receiptPath ?? AGENT_UPDATE_RECEIPT);
|
|
8275
|
+
if (update)
|
|
8276
|
+
observation = { ...observation, update };
|
|
7960
8277
|
const response = await postSignedNode(options, "v1/node/heartbeat", observation);
|
|
7961
|
-
|
|
7962
|
-
|
|
8278
|
+
const desired = response.desiredAgentUpdate ?? (response.desiredAgentRelease ? {
|
|
8279
|
+
attemptId: `legacy:${response.desiredAgentRelease.version}`,
|
|
8280
|
+
leaseExpiresAtTs: Number.MAX_SAFE_INTEGER,
|
|
8281
|
+
release: response.desiredAgentRelease
|
|
8282
|
+
} : undefined);
|
|
8283
|
+
if (desired) {
|
|
8284
|
+
const release = validateAgentRelease(desired.release);
|
|
8285
|
+
const now = (options.now ?? Date.now)();
|
|
8286
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(desired.attemptId)) {
|
|
8287
|
+
throw new Error("agent update attempt ID is invalid");
|
|
8288
|
+
}
|
|
8289
|
+
if (!Number.isSafeInteger(desired.leaseExpiresAtTs) || desired.leaseExpiresAtTs <= now) {
|
|
8290
|
+
options.onEvent?.("update-lease-expired", { attemptId: desired.attemptId, to: release.version });
|
|
8291
|
+
return response;
|
|
8292
|
+
}
|
|
7963
8293
|
if (compareVersions(release.version, observation.version) > 0) {
|
|
8294
|
+
if (update?.targetVersion === release.version && (update.outcome === "rolled-back" || update.outcome === "failed") && (update.retryAfterTs ?? 0) > now) {
|
|
8295
|
+
options.onEvent?.("update-quarantined", {
|
|
8296
|
+
attemptId: desired.attemptId,
|
|
8297
|
+
to: release.version,
|
|
8298
|
+
retryAfterTs: update.retryAfterTs
|
|
8299
|
+
});
|
|
8300
|
+
return response;
|
|
8301
|
+
}
|
|
7964
8302
|
let prepared = false;
|
|
7965
8303
|
try {
|
|
7966
8304
|
await options.prepareUpdate?.(release);
|
|
7967
8305
|
prepared = true;
|
|
7968
|
-
const applied = await (options.applyUpdate ?? ((next, current) => requestAgentUpdate({
|
|
8306
|
+
const applied = await (options.applyUpdate ?? ((next, current, attemptId) => requestAgentUpdate({
|
|
7969
8307
|
op: "apply",
|
|
7970
8308
|
target: options.updateTarget ?? "compute",
|
|
7971
8309
|
release: next,
|
|
7972
|
-
currentVersion: current
|
|
7973
|
-
|
|
8310
|
+
currentVersion: current,
|
|
8311
|
+
attemptId
|
|
8312
|
+
})))(release, observation.version, desired.attemptId);
|
|
7974
8313
|
if (!applied.ok)
|
|
7975
8314
|
throw new Error(`agent update refused: ${applied.error.message}`);
|
|
8315
|
+
if (applied.attemptId !== desired.attemptId) {
|
|
8316
|
+
throw new Error("agent update helper returned the wrong rollout attempt");
|
|
8317
|
+
}
|
|
7976
8318
|
options.onEvent?.("update-staged", { from: observation.version, to: release.version });
|
|
7977
8319
|
} catch (cause) {
|
|
7978
8320
|
if (prepared)
|
|
@@ -8092,7 +8434,7 @@ function startSoftwareHelper(options = {}) {
|
|
|
8092
8434
|
}
|
|
8093
8435
|
function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
|
|
8094
8436
|
validateSoftwareRequirements(requirements);
|
|
8095
|
-
return new Promise((
|
|
8437
|
+
return new Promise((resolve4, reject) => {
|
|
8096
8438
|
const socket = connect6(socketPath, () => socket.write(`${JSON.stringify({ op: "ensure", requirements })}
|
|
8097
8439
|
`));
|
|
8098
8440
|
let buffer = "";
|
|
@@ -8111,7 +8453,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
8111
8453
|
const response = JSON.parse(buffer.slice(0, newline));
|
|
8112
8454
|
if (!response.ok || !response.results)
|
|
8113
8455
|
throw new Error(response.error?.message ?? "software helper refused the request");
|
|
8114
|
-
|
|
8456
|
+
resolve4(response.results);
|
|
8115
8457
|
} catch (cause) {
|
|
8116
8458
|
reject(cause);
|
|
8117
8459
|
}
|
|
@@ -8123,7 +8465,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
8123
8465
|
// src/index.ts
|
|
8124
8466
|
function loadOrCreateSeed(path) {
|
|
8125
8467
|
if (existsSync13(path)) {
|
|
8126
|
-
const seed2 = new Uint8Array(Buffer.from(
|
|
8468
|
+
const seed2 = new Uint8Array(Buffer.from(readFileSync9(path, "utf8").trim(), "base64url"));
|
|
8127
8469
|
if (seed2.length < 32) {
|
|
8128
8470
|
throw new Error(`agent: the seed at ${path} is too short to derive a key from.`);
|
|
8129
8471
|
}
|
|
@@ -8156,7 +8498,7 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
|
|
|
8156
8498
|
const path = `${directory}/${name}`;
|
|
8157
8499
|
if (!existsSync13(path))
|
|
8158
8500
|
throw new Error(`agent: the systemd credential ${name} is missing at ${path}.`);
|
|
8159
|
-
const seed = new Uint8Array(Buffer.from(
|
|
8501
|
+
const seed = new Uint8Array(Buffer.from(readFileSync9(path, "utf8").trim(), "base64url"));
|
|
8160
8502
|
if (seed.length < 32)
|
|
8161
8503
|
throw new Error(`agent: the systemd credential ${name} is too short to derive a key from.`);
|
|
8162
8504
|
return seed;
|
|
@@ -8166,7 +8508,7 @@ function loadTextCredential(name, directory = process.env.CREDENTIALS_DIRECTORY)
|
|
|
8166
8508
|
throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the credential.");
|
|
8167
8509
|
if (!/^[A-Za-z0-9_.-]+$/.test(name))
|
|
8168
8510
|
throw new Error("agent: invalid systemd credential name.");
|
|
8169
|
-
const value =
|
|
8511
|
+
const value = readFileSync9(`${directory}/${name}`, "utf8").trim();
|
|
8170
8512
|
if (!value)
|
|
8171
8513
|
throw new Error(`agent: systemd credential ${name} is empty.`);
|
|
8172
8514
|
return value;
|
|
@@ -8216,7 +8558,7 @@ if (import.meta.main) {
|
|
|
8216
8558
|
const args = process.argv.slice(2);
|
|
8217
8559
|
if (args.includes("--help") || args.includes("-h")) {
|
|
8218
8560
|
console.log([
|
|
8219
|
-
`fz-agent ${
|
|
8561
|
+
`fz-agent ${VERSION3}`,
|
|
8220
8562
|
"",
|
|
8221
8563
|
"Runs inside managed compute and answers secret requests over a local socket.",
|
|
8222
8564
|
"It holds no configuration of its own \u2014 everything comes from the",
|
|
@@ -8244,7 +8586,7 @@ if (import.meta.main) {
|
|
|
8244
8586
|
process.exit(0);
|
|
8245
8587
|
}
|
|
8246
8588
|
if (args.includes("--version") || args.includes("-v")) {
|
|
8247
|
-
console.log(
|
|
8589
|
+
console.log(VERSION3);
|
|
8248
8590
|
process.exit(0);
|
|
8249
8591
|
}
|
|
8250
8592
|
const command2 = args.find((arg) => !arg.startsWith("-"));
|
|
@@ -8274,7 +8616,7 @@ if (import.meta.main) {
|
|
|
8274
8616
|
keys: keys2,
|
|
8275
8617
|
label: process.env.FZ_NODE_LABEL,
|
|
8276
8618
|
edgeHostname: process.env.FZ_NODE_HOSTNAME,
|
|
8277
|
-
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ?
|
|
8619
|
+
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync9(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
8278
8620
|
privateNetworkAttachment: privateNetworkAttachmentFromEnvironment()
|
|
8279
8621
|
});
|
|
8280
8622
|
console.log(`[agent] enrolled ${binding2.computeReference} in project ${binding2.projectKey}/${binding2.environmentKey}`);
|
|
@@ -8284,7 +8626,7 @@ if (import.meta.main) {
|
|
|
8284
8626
|
const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
|
|
8285
8627
|
if (!profilePath)
|
|
8286
8628
|
throw new Error("metal-helper requires --profile=/absolute/path.json");
|
|
8287
|
-
const profile2 = JSON.parse(
|
|
8629
|
+
const profile2 = JSON.parse(readFileSync9(profilePath, "utf8"));
|
|
8288
8630
|
const helper = startMetalHelper({
|
|
8289
8631
|
profile: profile2,
|
|
8290
8632
|
socketPath: process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET
|
|
@@ -8356,7 +8698,7 @@ if (import.meta.main) {
|
|
|
8356
8698
|
if (stopping)
|
|
8357
8699
|
return;
|
|
8358
8700
|
stopping = true;
|
|
8359
|
-
await new Promise((
|
|
8701
|
+
await new Promise((resolve4) => helper.close(() => resolve4()));
|
|
8360
8702
|
process.exit(0);
|
|
8361
8703
|
};
|
|
8362
8704
|
process.on("SIGTERM", () => void stop());
|
|
@@ -8375,7 +8717,7 @@ if (import.meta.main) {
|
|
|
8375
8717
|
if (stopping)
|
|
8376
8718
|
return;
|
|
8377
8719
|
stopping = true;
|
|
8378
|
-
await new Promise((
|
|
8720
|
+
await new Promise((resolve4) => helper.close(() => resolve4()));
|
|
8379
8721
|
process.exit(0);
|
|
8380
8722
|
};
|
|
8381
8723
|
process.on("SIGTERM", () => void stop());
|
|
@@ -8402,7 +8744,7 @@ if (import.meta.main) {
|
|
|
8402
8744
|
const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
|
|
8403
8745
|
if (!profilePath)
|
|
8404
8746
|
throw new Error("metal-isolation requires --profile=/absolute/path.json");
|
|
8405
|
-
const profile2 = JSON.parse(
|
|
8747
|
+
const profile2 = JSON.parse(readFileSync9(profilePath, "utf8"));
|
|
8406
8748
|
await applyMetalIsolation(profile2);
|
|
8407
8749
|
console.log("[metal-isolation] host and guest cgroup boundaries active");
|
|
8408
8750
|
process.exit(0);
|
|
@@ -8447,7 +8789,7 @@ if (import.meta.main) {
|
|
|
8447
8789
|
if (claimArg !== "-" && !claimArg.startsWith("/")) {
|
|
8448
8790
|
throw new Error("metal-apply claim path must be absolute");
|
|
8449
8791
|
}
|
|
8450
|
-
const raw =
|
|
8792
|
+
const raw = readFileSync9(claimArg === "-" ? "/dev/stdin" : claimArg, "utf8");
|
|
8451
8793
|
if (Buffer.byteLength(raw) > 32 * 1024)
|
|
8452
8794
|
throw new Error("metal-apply claim exceeds 32 KiB");
|
|
8453
8795
|
const claim = JSON.parse(raw);
|
|
@@ -8512,7 +8854,7 @@ if (import.meta.main) {
|
|
|
8512
8854
|
const deadline = Math.max(1, Number(process.env.FZ_DRAIN_DEADLINE_MS ?? 120000));
|
|
8513
8855
|
const drained = await Promise.race([
|
|
8514
8856
|
Promise.all([pull.stop(), heartbeat.stop()]).then(() => true),
|
|
8515
|
-
new Promise((
|
|
8857
|
+
new Promise((resolve4) => setTimeout(() => resolve4(false), deadline))
|
|
8516
8858
|
]);
|
|
8517
8859
|
console.log(`[metal-agent] ${signal}: ${drained ? "drained" : "deadline reached; claim left fenced for recovery"}`);
|
|
8518
8860
|
process.exit(drained ? 0 : 1);
|
|
@@ -8556,7 +8898,7 @@ if (import.meta.main) {
|
|
|
8556
8898
|
record: (entry) => console.log(`[agent] ${entry.op} ${entry.outcome}${entry.detail ? ` ${entry.detail}` : ""}`)
|
|
8557
8899
|
});
|
|
8558
8900
|
const { nodeKey, keys, server } = running;
|
|
8559
|
-
console.log(`[agent] ${
|
|
8901
|
+
console.log(`[agent] ${VERSION3} signing as ${nodeKey}`);
|
|
8560
8902
|
const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
|
|
8561
8903
|
let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
|
|
8562
8904
|
const enrolmentCredential = process.env.FZ_ENROL_TOKEN_CREDENTIAL;
|
|
@@ -8572,7 +8914,7 @@ if (import.meta.main) {
|
|
|
8572
8914
|
keys,
|
|
8573
8915
|
label: process.env.FZ_NODE_LABEL,
|
|
8574
8916
|
edgeHostname: process.env.FZ_NODE_HOSTNAME,
|
|
8575
|
-
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ?
|
|
8917
|
+
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync9(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
8576
8918
|
privateNetworkAttachment: privateNetworkAttachmentFromEnvironment()
|
|
8577
8919
|
});
|
|
8578
8920
|
console.log(`[agent] enrolled ${binding.computeReference} in project ${binding.projectKey}/${binding.environmentKey}`);
|
|
@@ -8661,7 +9003,7 @@ if (import.meta.main) {
|
|
|
8661
9003
|
return [name, process.env[name]];
|
|
8662
9004
|
})),
|
|
8663
9005
|
gitCredentialPath: process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/git-deploy-key` : undefined,
|
|
8664
|
-
knownHostsPath: source.knownHosts ?
|
|
9006
|
+
knownHostsPath: source.knownHosts ? join6(root, "cache", `known-hosts-${key}`) : undefined,
|
|
8665
9007
|
knownHostsContent: source.knownHosts,
|
|
8666
9008
|
cache: deploymentSecrets,
|
|
8667
9009
|
ensureSoftware: (requirements) => requestSoftware(requirements, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
|
|
@@ -8736,7 +9078,7 @@ if (import.meta.main) {
|
|
|
8736
9078
|
const deadlineMs = Math.max(1, Number(process.env.FZ_DRAIN_DEADLINE_MS ?? 30000));
|
|
8737
9079
|
const deadline = Date.now() + deadlineMs;
|
|
8738
9080
|
const remaining = () => Math.max(1, deadline - Date.now());
|
|
8739
|
-
const controlClosed = control ? await settleWithin(new Promise((
|
|
9081
|
+
const controlClosed = control ? await settleWithin(new Promise((resolve4) => control.close(() => resolve4())), remaining()) : true;
|
|
8740
9082
|
const pullDrain = pull?.stop() ?? Promise.resolve();
|
|
8741
9083
|
const vaultDrain = vaultSync?.stop() ?? Promise.resolve();
|
|
8742
9084
|
const attestationDrain = attestationLoop?.stop() ?? Promise.resolve();
|
|
@@ -8840,6 +9182,8 @@ export {
|
|
|
8840
9182
|
requestAgentUpdate,
|
|
8841
9183
|
renderWarpMdm,
|
|
8842
9184
|
removeMetalGuest,
|
|
9185
|
+
recoverInterruptedAgentUpdate,
|
|
9186
|
+
readAgentUpdateReceipt,
|
|
8843
9187
|
pullProvisioningOnce,
|
|
8844
9188
|
pullMigrationOnce,
|
|
8845
9189
|
pullDeploymentOnce,
|
|
@@ -8877,7 +9221,7 @@ export {
|
|
|
8877
9221
|
allocateCpuPool,
|
|
8878
9222
|
allocateAddress,
|
|
8879
9223
|
activateAgentRelease,
|
|
8880
|
-
|
|
9224
|
+
VERSION3 as VERSION,
|
|
8881
9225
|
SUPPORTED_GUEST_IMAGE,
|
|
8882
9226
|
SOFTWARE_HELPER_UNIT_PATH,
|
|
8883
9227
|
SOFTWARE_HELPER_GROUP,
|
|
@@ -8898,6 +9242,7 @@ export {
|
|
|
8898
9242
|
DEFAULT_AGENT_RELEASE_ROOT,
|
|
8899
9243
|
CacheError,
|
|
8900
9244
|
AGENT_UPDATE_RECEIPT,
|
|
9245
|
+
AGENT_UPDATE_JOURNAL,
|
|
8901
9246
|
AGENT_UPDATE_HELPER_UNIT_PATH,
|
|
8902
9247
|
AGENT_UPDATE_GROUP
|
|
8903
9248
|
};
|