@forgezero/agent 0.1.65 → 0.1.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +745 -157
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap-bundle.d.ts +1 -0
- package/dist/bootstrap-bundle.js +4 -3
- package/dist/bootstrap.d.ts +7 -1
- package/dist/bootstrap.js +264 -13
- package/dist/community-rehearsal-host.d.ts +12 -0
- package/dist/community-rehearsal-host.js +99 -36
- package/dist/definition.js +41 -4
- package/dist/deploy-compiler.js +4 -4
- package/dist/deploy-file.js +41 -4
- package/dist/deploy-plan-runner.js +4 -4
- package/dist/deploy-plan.js +4 -4
- package/dist/fz-agent.js +165 -76
- package/dist/fz.js +487 -251
- package/dist/index.d.ts +1 -1
- package/dist/metal-bootstrap.js +120 -46
- package/dist/metal-helper-socket.js +406 -65
- package/dist/metal-provision.js +402 -61
- package/dist/operator-bootstrap.d.ts +20 -4
- package/dist/operator-bootstrap.js +429 -203
- package/dist/platform-bootstrap-runtime.d.ts +33 -0
- package/dist/platform-bootstrap-runtime.js +551 -44
- package/dist/platform-fleet-verification.js +242 -18
- package/dist/platform-genesis-config.d.ts +7 -1
- package/dist/platform-genesis.d.ts +16 -17
- package/dist/platform-genesis.js +464 -0
- package/dist/provision.js +42 -5
- package/dist/software-helper.js +41 -4
- package/dist/software.d.ts +8 -2
- package/dist/software.js +41 -4
- package/dist/ssh-bootstrap.d.ts +5 -0
- package/dist/ubuntu.d.ts +15 -14
- package/dist/ubuntu.js +345 -9
- package/dist/version.d.ts +1 -1
- package/package.json +8 -4
package/dist/fz-agent.js
CHANGED
|
@@ -4970,7 +4970,30 @@ var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cb
|
|
|
4970
4970
|
var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
|
|
4971
4971
|
var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
|
|
4972
4972
|
var OS_CATALOG = [
|
|
4973
|
-
{
|
|
4973
|
+
{
|
|
4974
|
+
id: "ubuntu",
|
|
4975
|
+
version: "26.04",
|
|
4976
|
+
architecture: "x64",
|
|
4977
|
+
status: "active",
|
|
4978
|
+
imageKey: "ubuntu-resolute-20260731",
|
|
4979
|
+
imageLabel: "Ubuntu 26.04 LTS Resolute",
|
|
4980
|
+
imageVersion: "2026-07-31",
|
|
4981
|
+
imageUrl: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img",
|
|
4982
|
+
imageSha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05",
|
|
4983
|
+
confidentialModes: ["sev-snp"]
|
|
4984
|
+
},
|
|
4985
|
+
{
|
|
4986
|
+
id: "ubuntu",
|
|
4987
|
+
version: "24.04",
|
|
4988
|
+
architecture: "x64",
|
|
4989
|
+
status: "active",
|
|
4990
|
+
imageKey: "ubuntu-noble-20260705",
|
|
4991
|
+
imageLabel: "Ubuntu 24.04 LTS Noble",
|
|
4992
|
+
imageVersion: "2026-07-05",
|
|
4993
|
+
imageUrl: "https://cloud-images.ubuntu.com/releases/noble/release-20260705/ubuntu-24.04-server-cloudimg-amd64.img",
|
|
4994
|
+
imageSha256: "ffe6203da54deeb6db5d2a98a83f9ec8e55f149d3f7ba622e1abe5fa966ee3d6",
|
|
4995
|
+
confidentialModes: []
|
|
4996
|
+
}
|
|
4974
4997
|
];
|
|
4975
4998
|
var SOFTWARE_CATALOG = [
|
|
4976
4999
|
{ id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
@@ -4981,7 +5004,12 @@ var SOFTWARE_CATALOG = [
|
|
|
4981
5004
|
{ id: "cloudflare-warp", version: "2026.6.822.0-min", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4982
5005
|
{ id: "ufw", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4983
5006
|
{ id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4984
|
-
{ id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
|
|
5007
|
+
{ id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
5008
|
+
{ id: "docker", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
5009
|
+
{ id: "nginx", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
5010
|
+
{ id: "ufw", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
5011
|
+
{ id: "openssh-client", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
5012
|
+
{ id: "git", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
|
|
4985
5013
|
];
|
|
4986
5014
|
var UBUNTU_2604_X64 = [
|
|
4987
5015
|
{ requirement: { id: "bun", version: "1.3.14" } },
|
|
@@ -4994,6 +5022,15 @@ var UBUNTU_2604_X64 = [
|
|
|
4994
5022
|
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } },
|
|
4995
5023
|
{ requirement: { id: "git", version: "ubuntu-26.04" } }
|
|
4996
5024
|
];
|
|
5025
|
+
var UBUNTU_2404_X64 = [
|
|
5026
|
+
{ requirement: { id: "bun", version: "1.3.14" } },
|
|
5027
|
+
{ requirement: { id: "docker", version: "ubuntu-24.04" } },
|
|
5028
|
+
{ requirement: { id: "nginx", version: "ubuntu-24.04" } },
|
|
5029
|
+
{ requirement: { id: "ufw", version: "ubuntu-24.04" } },
|
|
5030
|
+
{ requirement: { id: "openssh-client", version: "ubuntu-24.04" } },
|
|
5031
|
+
{ requirement: { id: "git", version: "ubuntu-24.04" } }
|
|
5032
|
+
];
|
|
5033
|
+
var strategiesFor = (observation) => observation.os.id === "ubuntu" && observation.architecture === "x64" ? observation.os.versionId === "26.04" ? UBUNTU_2604_X64 : observation.os.versionId === "24.04" ? UBUNTU_2404_X64 : undefined : undefined;
|
|
4997
5034
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
4998
5035
|
var DOCKER_DAEMON_PATH = "/etc/docker/daemon.json";
|
|
4999
5036
|
var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
|
|
@@ -5040,7 +5077,7 @@ var aptInstall = async (name) => {
|
|
|
5040
5077
|
};
|
|
5041
5078
|
async function executeSoftwareOperation(operation) {
|
|
5042
5079
|
const { software, version } = operation;
|
|
5043
|
-
if (!UBUNTU_2604_X64.some(({ requirement }) => requirement.id === software && requirement.version === version)) {
|
|
5080
|
+
if (![...UBUNTU_2604_X64, ...UBUNTU_2404_X64].some(({ requirement }) => requirement.id === software && requirement.version === version)) {
|
|
5044
5081
|
return { exitCode: 2, output: "unsupported software operation" };
|
|
5045
5082
|
}
|
|
5046
5083
|
if (operation.kind === "check") {
|
|
@@ -5210,7 +5247,7 @@ async function ensureSoftwareRequirements(requirementsInput, options) {
|
|
|
5210
5247
|
}
|
|
5211
5248
|
const results = [];
|
|
5212
5249
|
for (const requirement of requirements) {
|
|
5213
|
-
const strategy =
|
|
5250
|
+
const strategy = strategiesFor(observation)?.find(({ requirement: candidate }) => candidate.id === requirement.id && candidate.version === requirement.version);
|
|
5214
5251
|
if (!strategy)
|
|
5215
5252
|
throw new Error(`unsupported software requirement: ${requirement.id}@${requirement.version}`);
|
|
5216
5253
|
const before = await options.exec({ kind: "check", software: requirement.id, version: requirement.version });
|
|
@@ -6015,13 +6052,13 @@ function validateBuiltInProvider(requirement2, where) {
|
|
|
6015
6052
|
return;
|
|
6016
6053
|
const expected = {
|
|
6017
6054
|
"forgezero.bun": { capability: "runtime.javascript", versions: ["1.3.14"], keys: ["installScope"] },
|
|
6018
|
-
"forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
|
|
6019
|
-
"forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
|
|
6055
|
+
"forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
|
|
6056
|
+
"forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
|
|
6020
6057
|
"forgezero.arangodb": { capability: "database.arangodb", versions: ["3.11.14"], keys: ["installScope", "runtime", "dataPath", "bind"] },
|
|
6021
6058
|
"forgezero.cloudflared": { capability: "network.public-tunnel", versions: ["2026.7.3"], keys: ["installScope", "mode"] },
|
|
6022
6059
|
"forgezero.warp": { capability: "network.private", versions: ["2026.6.822.0-min"], keys: ["installScope", "mode"] },
|
|
6023
|
-
"forgezero.ufw": { capability: "security.firewall", versions: ["ubuntu-26.04"], keys: ["installScope", "defaultIncoming", "defaultOutgoing"] },
|
|
6024
|
-
"forgezero.openssh": { capability: "transport.ssh", versions: ["ubuntu-26.04"], keys: ["installScope", "mode"] },
|
|
6060
|
+
"forgezero.ufw": { capability: "security.firewall", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "defaultIncoming", "defaultOutgoing"] },
|
|
6061
|
+
"forgezero.openssh": { capability: "transport.ssh", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "mode"] },
|
|
6025
6062
|
"forgezero.systemd": { capability: "service.manager", versions: ["ubuntu-26.04"], keys: ["installScope", "credentials", "serviceSandbox"] }
|
|
6026
6063
|
};
|
|
6027
6064
|
const contract = expected[requirement2.provider];
|
|
@@ -7148,7 +7185,7 @@ async function sha256File(path2) {
|
|
|
7148
7185
|
});
|
|
7149
7186
|
return hash.digest("hex");
|
|
7150
7187
|
}
|
|
7151
|
-
var
|
|
7188
|
+
var bootstrapBundleBranch = (value) => {
|
|
7152
7189
|
if (typeof value !== "string" || !/^[A-Za-z0-9](?:[A-Za-z0-9._/-]{0,126}[A-Za-z0-9])?$/.test(value) || value.includes("..") || value.includes("//") || value.startsWith("-")) {
|
|
7153
7190
|
throw new Error("bootstrap bundle branch is malformed");
|
|
7154
7191
|
}
|
|
@@ -7165,7 +7202,7 @@ function parseBootstrapBundleManifest(value) {
|
|
|
7165
7202
|
if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
|
|
7166
7203
|
throw new Error("bootstrap bundle manifest format is unsupported");
|
|
7167
7204
|
}
|
|
7168
|
-
|
|
7205
|
+
bootstrapBundleBranch(source.branch);
|
|
7169
7206
|
if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
|
|
7170
7207
|
throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
|
|
7171
7208
|
}
|
|
@@ -8550,23 +8587,8 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
8550
8587
|
import { join as join3 } from "path";
|
|
8551
8588
|
import { fileURLToPath } from "url";
|
|
8552
8589
|
|
|
8553
|
-
// src/ubuntu.ts
|
|
8554
|
-
var SUPPORTED_GUEST_IMAGE = Object.freeze({
|
|
8555
|
-
key: "ubuntu-resolute-20260731",
|
|
8556
|
-
family: "ubuntu-26.04",
|
|
8557
|
-
version: "2026-07-31",
|
|
8558
|
-
label: "Ubuntu 26.04 LTS Resolute",
|
|
8559
|
-
url: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img",
|
|
8560
|
-
sha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05"
|
|
8561
|
-
});
|
|
8562
|
-
function assertSupportedGuestImage(imageKey) {
|
|
8563
|
-
if (imageKey !== SUPPORTED_GUEST_IMAGE.key) {
|
|
8564
|
-
throw new Error(`unsupported guest image ${imageKey}; ForgeZero currently supports only ${SUPPORTED_GUEST_IMAGE.key}`);
|
|
8565
|
-
}
|
|
8566
|
-
}
|
|
8567
|
-
|
|
8568
8590
|
// src/version.ts
|
|
8569
|
-
var VERSION2 = "0.1.
|
|
8591
|
+
var VERSION2 = "0.1.67";
|
|
8570
8592
|
|
|
8571
8593
|
// src/ssh-bootstrap.ts
|
|
8572
8594
|
class SshBootstrapError extends Error {
|
|
@@ -8603,10 +8625,12 @@ var checked = async (exec, argv2, code, options) => {
|
|
|
8603
8625
|
var validateClaim = (claim) => {
|
|
8604
8626
|
if (!/^[A-Za-z0-9_-]{1,64}$/.test(claim.computeKey) || !/^[A-Za-z0-9_-]{16,128}$/.test(claim.claimToken) || !/^fze_[A-Za-z0-9_-]{32,128}$/.test(claim.enrolmentToken) || !/^[a-z0-9][a-z0-9_-]{0,62}$/.test(claim.realm) || !/^[A-Za-z0-9_-]{1,64}$/.test(claim.projectKey) || !/^[A-Za-z0-9_-]{1,64}$/.test(claim.environmentKey))
|
|
8605
8627
|
throw new SshBootstrapError("CLAIM_INVALID");
|
|
8606
|
-
const { host, port, user, hostKeySha256, nodeHostname } = claim.target;
|
|
8628
|
+
const { host, port, user, hostKeySha256, nodeHostname, operatingSystem } = claim.target;
|
|
8607
8629
|
if (!host || host.length > 253 || /[\s/@]/.test(host) || !Number.isSafeInteger(port) || port < 1 || port > 65535 || !/^[a-z_][a-z0-9_-]{0,31}$/.test(user) || !/^SHA256:[A-Za-z0-9+/]{43}$/.test(hostKeySha256) || !/^[a-z0-9](?:[a-z0-9.-]{1,251}[a-z0-9])$/.test(nodeHostname) || !nodeHostname.includes(".")) {
|
|
8608
8630
|
throw new SshBootstrapError("CLAIM_INVALID");
|
|
8609
8631
|
}
|
|
8632
|
+
if (operatingSystem?.id !== "ubuntu" || !["24.04", "26.04"].includes(operatingSystem.version) || operatingSystem.architecture !== "x64")
|
|
8633
|
+
throw new SshBootstrapError("CLAIM_INVALID");
|
|
8610
8634
|
if (!claim.target.credential || claim.target.credential.source !== "runner" && claim.target.credential.source !== "vault" || claim.target.credential.source === "vault" && (typeof claim.target.credential.privateKey !== "string" || claim.target.credential.privateKey.length > 32768 || !claim.target.credential.privateKey.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----") || !claim.target.credential.privateKey.trimEnd().endsWith("-----END OPENSSH PRIVATE KEY-----"))) {
|
|
8611
8635
|
throw new SshBootstrapError("CLAIM_INVALID");
|
|
8612
8636
|
}
|
|
@@ -8736,6 +8760,7 @@ async function executeSshBootstrap(claim, options) {
|
|
|
8736
8760
|
throw new SshBootstrapError("REMOTE_PATH_INVALID");
|
|
8737
8761
|
const config = {
|
|
8738
8762
|
kind: "enrolled-compute",
|
|
8763
|
+
operatingSystem: claim.target.operatingSystem,
|
|
8739
8764
|
apiUrl: api.origin,
|
|
8740
8765
|
realm: claim.realm,
|
|
8741
8766
|
nodeHostname: claim.target.nodeHostname,
|
|
@@ -9071,10 +9096,10 @@ async function executeMetalAdmission(claim, options) {
|
|
|
9071
9096
|
unitDir: "/etc/systemd/system",
|
|
9072
9097
|
apiUrl: api.origin,
|
|
9073
9098
|
agentTelemetryEndpoint: telemetry.origin,
|
|
9074
|
-
images: {
|
|
9075
|
-
|
|
9076
|
-
sha256:
|
|
9077
|
-
|
|
9099
|
+
images: Object.fromEntries(OS_CATALOG.filter(({ status }) => status === "active").map((image) => [
|
|
9100
|
+
image.imageKey,
|
|
9101
|
+
{ path: `/var/lib/forgezero/images/${image.imageKey}.img`, sha256: image.imageSha256 }
|
|
9102
|
+
])),
|
|
9078
9103
|
bunVersion: PINNED_BUN_VERSION,
|
|
9079
9104
|
bunReleaseSha256: BUN_RELEASE_SHA256,
|
|
9080
9105
|
agentVersion: VERSION2
|
|
@@ -9384,6 +9409,36 @@ function shapeEgressUnitDirectives(tap, guaranteedMbps, burstMbps) {
|
|
|
9384
9409
|
];
|
|
9385
9410
|
}
|
|
9386
9411
|
|
|
9412
|
+
// src/ubuntu.ts
|
|
9413
|
+
var current = OS_CATALOG[0];
|
|
9414
|
+
var SUPPORTED_GUEST_IMAGE = Object.freeze({
|
|
9415
|
+
key: current.imageKey,
|
|
9416
|
+
family: `${current.id}-${current.version}`,
|
|
9417
|
+
version: current.imageVersion,
|
|
9418
|
+
label: current.imageLabel,
|
|
9419
|
+
url: current.imageUrl,
|
|
9420
|
+
sha256: current.imageSha256
|
|
9421
|
+
});
|
|
9422
|
+
var SUPPORTED_GUEST_IMAGES = Object.freeze(OS_CATALOG.filter(({ status }) => status === "active").map(({ id: id2, imageKey, imageLabel, imageVersion, version, confidentialModes }) => ({
|
|
9423
|
+
key: imageKey,
|
|
9424
|
+
label: imageLabel,
|
|
9425
|
+
family: `${id2}-${version}`,
|
|
9426
|
+
version: imageVersion,
|
|
9427
|
+
confidential: confidentialModes.some((mode) => mode === "sev-snp")
|
|
9428
|
+
})));
|
|
9429
|
+
function supportedGuestImage(imageKey) {
|
|
9430
|
+
return SUPPORTED_GUEST_IMAGES.find(({ key }) => key === imageKey);
|
|
9431
|
+
}
|
|
9432
|
+
function assertSupportedGuestImage(imageKey, confidential = false) {
|
|
9433
|
+
const selected = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
|
|
9434
|
+
if (!selected) {
|
|
9435
|
+
throw new Error(`unsupported guest image ${imageKey}; supported images: ${SUPPORTED_GUEST_IMAGES.map(({ key }) => key).join(", ")}`);
|
|
9436
|
+
}
|
|
9437
|
+
if (confidential && !selected.confidentialModes.some((mode) => mode === "sev-snp")) {
|
|
9438
|
+
throw new Error(`guest image ${imageKey} is not approved for SEV-SNP`);
|
|
9439
|
+
}
|
|
9440
|
+
}
|
|
9441
|
+
|
|
9387
9442
|
// src/metal-provision.ts
|
|
9388
9443
|
var SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
|
|
9389
9444
|
var DEVICE = /^[a-zA-Z][a-zA-Z0-9_.-]{0,14}$/;
|
|
@@ -9443,9 +9498,16 @@ function validateMetalProfile(profile) {
|
|
|
9443
9498
|
if (telemetryEndpoint.protocol !== "https:" || telemetryEndpoint.username || telemetryEndpoint.password || telemetryEndpoint.search || telemetryEndpoint.hash || isIP2(telemetryEndpoint.hostname) !== 0 || !telemetryEndpoint.hostname.includes(".") || telemetryEndpoint.hostname === "localhost" || telemetryEndpoint.hostname.endsWith(".local"))
|
|
9444
9499
|
throw new MetalProvisionError("Agent telemetry endpoint must be a public HTTPS DNS coordinate without credentials, query or fragment");
|
|
9445
9500
|
const imageKeys = Object.keys(profile.images);
|
|
9446
|
-
if (imageKeys.length
|
|
9447
|
-
throw new MetalProvisionError(
|
|
9501
|
+
if (imageKeys.length === 0)
|
|
9502
|
+
throw new MetalProvisionError("metal profile requires at least one pinned guest image");
|
|
9503
|
+
for (const imageKey of imageKeys) {
|
|
9504
|
+
const catalog = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
|
|
9505
|
+
if (!catalog || profile.images[imageKey]?.sha256 !== catalog.imageSha256) {
|
|
9506
|
+
throw new MetalProvisionError(`metal profile image ${imageKey} does not match the pinned OS catalog`);
|
|
9507
|
+
}
|
|
9448
9508
|
}
|
|
9509
|
+
if (profile.confidential && !imageKeys.some((key) => OS_CATALOG.find((entry) => entry.imageKey === key)?.confidentialModes.some((mode) => mode === "sev-snp")))
|
|
9510
|
+
throw new MetalProvisionError("confidential metal profile requires a pinned SEV-SNP guest image");
|
|
9449
9511
|
if (!Array.isArray(profile.cpuPools) || profile.cpuPools.length === 0) {
|
|
9450
9512
|
throw new MetalProvisionError("at least one exclusive CPU pool is required");
|
|
9451
9513
|
}
|
|
@@ -9664,7 +9726,7 @@ var checked2 = async (exec, argv2) => {
|
|
|
9664
9726
|
};
|
|
9665
9727
|
async function provisionMetalGuest(profile, claim, exec) {
|
|
9666
9728
|
validateMetalProfile(profile);
|
|
9667
|
-
assertSupportedGuestImage(claim.spec.imageKey);
|
|
9729
|
+
assertSupportedGuestImage(claim.spec.imageKey, claim.spec.confidential);
|
|
9668
9730
|
if (!claim.computeKey || !claim.spec.reference || !SAFE_NAME.test(claim.spec.imageKey) || claim.spec.guestName !== undefined && !SAFE_NAME.test(claim.spec.guestName) || claim.spec.cpuPoolKey !== undefined && !SAFE_NAME.test(claim.spec.cpuPoolKey) || !Number.isInteger(claim.spec.physicalCores) || claim.spec.physicalCores < 1 || claim.spec.physicalCores > 256 || !Number.isInteger(claim.spec.vcpu) || claim.spec.vcpu < 1 || claim.spec.vcpu > 512 || !Number.isInteger(claim.spec.memoryGib) || claim.spec.memoryGib < 1 || claim.spec.memoryGib > 8192 || !Number.isInteger(claim.spec.diskGib) || claim.spec.diskGib < 8 || claim.spec.diskGib > 65536 || !Number.isInteger(claim.spec.egressGuaranteedMbps) || claim.spec.egressGuaranteedMbps < 0 || !Number.isInteger(claim.spec.egressBurstMbps) || claim.spec.egressBurstMbps < claim.spec.egressGuaranteedMbps)
|
|
9669
9731
|
throw new MetalProvisionError("invalid compute claim");
|
|
9670
9732
|
const image = profile.images[claim.spec.imageKey];
|
|
@@ -11544,11 +11606,11 @@ async function heartbeatAgentOnce(options) {
|
|
|
11544
11606
|
await options.prepareUpdate?.(release);
|
|
11545
11607
|
prepared = true;
|
|
11546
11608
|
const apply = async () => {
|
|
11547
|
-
const applied = await (options.applyUpdate ?? ((next,
|
|
11609
|
+
const applied = await (options.applyUpdate ?? ((next, current2, attemptId) => requestAgentUpdate({
|
|
11548
11610
|
op: "apply",
|
|
11549
11611
|
target: options.updateTarget ?? "compute",
|
|
11550
11612
|
release: next,
|
|
11551
|
-
currentVersion:
|
|
11613
|
+
currentVersion: current2,
|
|
11552
11614
|
attemptId
|
|
11553
11615
|
})))(release, observation.version, desired.attemptId);
|
|
11554
11616
|
if (!applied.ok)
|
|
@@ -12551,8 +12613,8 @@ function applyAgentEgressPolicy(options) {
|
|
|
12551
12613
|
} : undefined;
|
|
12552
12614
|
if (loopback && !uids.includes(loopback.uid))
|
|
12553
12615
|
uids.push(loopback.uid);
|
|
12554
|
-
const
|
|
12555
|
-
const applied = command2(["/usr/sbin/nft", "--file", "-"], renderAgentEgressNft(uids,
|
|
12616
|
+
const current2 = listPolicy(command2);
|
|
12617
|
+
const applied = command2(["/usr/sbin/nft", "--file", "-"], renderAgentEgressNft(uids, current2.exitCode === 0, loopback));
|
|
12556
12618
|
if (applied.exitCode !== 0) {
|
|
12557
12619
|
throw new Error(`Could not apply Agent egress policy: ${applied.stderr.trim() || "nft refused it"}`);
|
|
12558
12620
|
}
|
|
@@ -13313,15 +13375,15 @@ var replaceLink = (path2, target2) => {
|
|
|
13313
13375
|
renameSync10(pending, path2);
|
|
13314
13376
|
};
|
|
13315
13377
|
var secureRelease = (path2, uid, gid) => {
|
|
13316
|
-
const visit = (
|
|
13317
|
-
const metadata = lstatSync3(
|
|
13378
|
+
const visit = (current2) => {
|
|
13379
|
+
const metadata = lstatSync3(current2);
|
|
13318
13380
|
if (metadata.isSymbolicLink())
|
|
13319
13381
|
throw new Error("release contains a symbolic link");
|
|
13320
|
-
chownSync(
|
|
13321
|
-
chmodSync15(
|
|
13382
|
+
chownSync(current2, uid, gid);
|
|
13383
|
+
chmodSync15(current2, metadata.isDirectory() ? 365 : 292);
|
|
13322
13384
|
if (metadata.isDirectory())
|
|
13323
|
-
for (const name of readdirSync4(
|
|
13324
|
-
visit(join10(
|
|
13385
|
+
for (const name of readdirSync4(current2))
|
|
13386
|
+
visit(join10(current2, name));
|
|
13325
13387
|
};
|
|
13326
13388
|
visit(path2);
|
|
13327
13389
|
};
|
|
@@ -15162,19 +15224,15 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
|
|
|
15162
15224
|
// src/community-rehearsal-host.ts
|
|
15163
15225
|
import { createHash as createHash11 } from "crypto";
|
|
15164
15226
|
import { lstatSync as lstatSync7, mkdirSync as mkdirSync16, readFileSync as readFileSync18, rmSync as rmSync12, symlinkSync as symlinkSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
15227
|
+
import { isIP as isIP5 } from "net";
|
|
15165
15228
|
import { dirname as dirname11 } from "path";
|
|
15166
15229
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
15167
15230
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
15168
15231
|
var COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
15169
15232
|
var COMMUNITY_CREDENTIAL_PARENT = "/etc/forgezero-rehearsal";
|
|
15170
15233
|
var COMMUNITY_CREDENTIAL_ROOT = "/etc/forgezero-rehearsal/creds";
|
|
15171
|
-
var NODES = new Map([
|
|
15172
|
-
["dev-fz-n1", { address: "10.42.0.21", agency: true }],
|
|
15173
|
-
["dev-fz-n2", { address: "10.42.0.22", agency: true }],
|
|
15174
|
-
["dev-fz-n3", { address: "10.42.0.23", agency: true }],
|
|
15175
|
-
["dev-fz-n4", { address: "10.42.0.24", agency: false }]
|
|
15176
|
-
]);
|
|
15177
15234
|
var RELEASE = /^[a-f0-9]{64}$/;
|
|
15235
|
+
var SAFE_NAME2 = /^[a-z][a-z0-9-]{0,62}$/;
|
|
15178
15236
|
async function ensureCommunityRehearsalArango(execute3 = executeSoftwareOperation) {
|
|
15179
15237
|
const requirement2 = { software: "arangodb", version: COMMUNITY_REHEARSAL_VERSION };
|
|
15180
15238
|
if ((await execute3({ kind: "check", ...requirement2 })).exitCode === 0)
|
|
@@ -15189,50 +15247,70 @@ var exactObject = (value) => {
|
|
|
15189
15247
|
throw new Error("community rehearsal request must be an object");
|
|
15190
15248
|
return value;
|
|
15191
15249
|
};
|
|
15192
|
-
var
|
|
15193
|
-
if (typeof node !== "string")
|
|
15250
|
+
var nodeName = (node) => {
|
|
15251
|
+
if (typeof node !== "string" || !SAFE_NAME2.test(node))
|
|
15194
15252
|
throw new Error("community rehearsal node is invalid");
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15253
|
+
return node;
|
|
15254
|
+
};
|
|
15255
|
+
var validateCommunityRehearsalTopology = (value) => {
|
|
15256
|
+
if (!Array.isArray(value) || value.length !== 4)
|
|
15257
|
+
throw new Error("community rehearsal requires exactly four reviewed nodes");
|
|
15258
|
+
const rows = value.map((item, index) => {
|
|
15259
|
+
const row2 = exactObject(item);
|
|
15260
|
+
if (Object.keys(row2).some((key) => !["name", "address", "agency", "starter", "readPreferred"].includes(key)) || typeof row2.name !== "string" || !SAFE_NAME2.test(row2.name) || typeof row2.address !== "string" || isIP5(row2.address) !== 4 || typeof row2.agency !== "boolean" || typeof row2.starter !== "boolean" || typeof row2.readPreferred !== "boolean") {
|
|
15261
|
+
throw new Error(`community rehearsal topology node ${index + 1} is invalid`);
|
|
15262
|
+
}
|
|
15263
|
+
return row2;
|
|
15264
|
+
});
|
|
15265
|
+
if (new Set(rows.map(({ name }) => name)).size !== rows.length || new Set(rows.map(({ address }) => address)).size !== rows.length) {
|
|
15266
|
+
throw new Error("community rehearsal node names and addresses must be unique");
|
|
15267
|
+
}
|
|
15268
|
+
if (rows.filter(({ agency }) => agency).length !== 3 || rows.filter(({ starter }) => starter).length !== 1 || !rows.find(({ starter }) => starter).agency || rows.filter(({ readPreferred }) => readPreferred).length !== 1 || rows.find(({ readPreferred }) => readPreferred).agency) {
|
|
15269
|
+
throw new Error("community rehearsal requires three Agency voters, one voter starter and one non-voting read-preferred member");
|
|
15270
|
+
}
|
|
15271
|
+
return rows;
|
|
15199
15272
|
};
|
|
15200
15273
|
function parseCommunityRehearsalHostRequest(value) {
|
|
15201
15274
|
const row2 = exactObject(value);
|
|
15202
|
-
const node =
|
|
15275
|
+
const node = nodeName(row2.node);
|
|
15276
|
+
const topology = validateCommunityRehearsalTopology(row2.topology);
|
|
15277
|
+
const expected = topology.find((entry) => entry.name === node);
|
|
15278
|
+
if (!expected)
|
|
15279
|
+
throw new Error("community rehearsal node is not present in the reviewed topology");
|
|
15203
15280
|
const action = row2.action;
|
|
15204
15281
|
if (action === "prepare") {
|
|
15205
|
-
const allowed = ["action", "node", "address", "agency", "release", "archivePath", "archiveSha256", "jwt"];
|
|
15206
|
-
if (Object.keys(row2).some((key) => !allowed.includes(key)) || row2.address !==
|
|
15282
|
+
const allowed = ["action", "node", "address", "agency", "topology", "release", "archivePath", "archiveSha256", "jwt"];
|
|
15283
|
+
if (Object.keys(row2).some((key) => !allowed.includes(key)) || row2.address !== expected.address || row2.agency !== expected.agency || typeof row2.release !== "string" || !RELEASE.test(row2.release) || row2.archiveSha256 !== row2.release || row2.archivePath !== `/tmp/forgezero-community-${row2.release}.tar.gz` || typeof row2.jwt !== "string" || !/^[a-f0-9]{64}$/.test(row2.jwt)) {
|
|
15207
15284
|
throw new Error("community rehearsal prepare request does not match the dedicated fleet contract");
|
|
15208
15285
|
}
|
|
15209
|
-
return row2;
|
|
15286
|
+
return { ...row2, topology };
|
|
15210
15287
|
}
|
|
15211
15288
|
if (action === "cleanup") {
|
|
15212
|
-
if (Object.keys(row2).some((key) => !["action", "node"].includes(key))) {
|
|
15289
|
+
if (Object.keys(row2).some((key) => !["action", "node", "topology"].includes(key))) {
|
|
15213
15290
|
throw new Error("community rehearsal cleanup has unknown fields");
|
|
15214
15291
|
}
|
|
15215
|
-
return { action, node
|
|
15292
|
+
return { action, node, topology };
|
|
15216
15293
|
}
|
|
15217
15294
|
if (["database-enable", "api-enable", "database-ready", "starter-ready", "api-ready", "database-status", "api-status"].includes(String(action))) {
|
|
15218
|
-
if (Object.keys(row2).some((key) => !["action", "node"].includes(key)))
|
|
15295
|
+
if (Object.keys(row2).some((key) => !["action", "node", "topology"].includes(key)))
|
|
15219
15296
|
throw new Error("community rehearsal host action has unknown fields");
|
|
15220
|
-
return { action, node
|
|
15297
|
+
return { action, node, topology };
|
|
15221
15298
|
}
|
|
15222
15299
|
if (action === "api") {
|
|
15223
|
-
if (Object.keys(row2).some((key) => !["action", "node", "operation", "key", "value"].includes(key)) || !["health", "init", "write", "read", "query", "cluster"].includes(String(row2.operation)) || row2.key !== undefined && (typeof row2.key !== "string" || !/^[A-Za-z0-9_.:-]{1,128}$/.test(row2.key)) || row2.value !== undefined && (typeof row2.value !== "string" || row2.value.length > 4096)) {
|
|
15300
|
+
if (Object.keys(row2).some((key) => !["action", "node", "topology", "operation", "key", "value"].includes(key)) || !["health", "init", "write", "read", "query", "cluster"].includes(String(row2.operation)) || row2.key !== undefined && (typeof row2.key !== "string" || !/^[A-Za-z0-9_.:-]{1,128}$/.test(row2.key)) || row2.value !== undefined && (typeof row2.value !== "string" || row2.value.length > 4096)) {
|
|
15224
15301
|
throw new Error("community rehearsal API request is invalid");
|
|
15225
15302
|
}
|
|
15226
15303
|
if ((row2.operation === "write" || row2.operation === "read") && typeof row2.key !== "string")
|
|
15227
15304
|
throw new Error("community rehearsal API key is required");
|
|
15228
15305
|
if ((row2.operation === "write" || row2.operation === "query") && typeof row2.value !== "string")
|
|
15229
15306
|
throw new Error("community rehearsal API value is required");
|
|
15230
|
-
return row2;
|
|
15307
|
+
return { ...row2, node, topology };
|
|
15231
15308
|
}
|
|
15232
15309
|
throw new Error("unsupported community rehearsal host action");
|
|
15233
15310
|
}
|
|
15234
|
-
var databaseUnit = (node) => {
|
|
15235
|
-
const
|
|
15311
|
+
var databaseUnit = (node, topology) => {
|
|
15312
|
+
const starter = topology.find((entry) => entry.starter);
|
|
15313
|
+
const join12 = node.starter ? "" : ` --starter.join=${starter.address}`;
|
|
15236
15314
|
const role = node.agency ? "" : " --cluster.start-agent=false --cluster.start-coordinator=true --cluster.start-dbserver=true";
|
|
15237
15315
|
return `[Unit]
|
|
15238
15316
|
Description=ForgeZero isolated Community ${COMMUNITY_REHEARSAL_VERSION} rehearsal cluster
|
|
@@ -15267,7 +15345,10 @@ LockPersonality=true
|
|
|
15267
15345
|
WantedBy=multi-user.target
|
|
15268
15346
|
`;
|
|
15269
15347
|
};
|
|
15270
|
-
var apiUnit = (node) =>
|
|
15348
|
+
var apiUnit = (node, topology) => {
|
|
15349
|
+
const urls = topology.map(({ address }) => `http://${address}:8529`).join(",");
|
|
15350
|
+
const readPreferred = topology.filter(({ readPreferred: readPreferred2 }) => readPreferred2).map(({ address }) => `http://${address}:8529`).join(",");
|
|
15351
|
+
return `[Unit]
|
|
15271
15352
|
Description=ForgeZero typed Community cluster rehearsal API
|
|
15272
15353
|
After=forgezero-rehearsal-db.service network-online.target
|
|
15273
15354
|
Requires=forgezero-rehearsal-db.service
|
|
@@ -15282,8 +15363,8 @@ Environment=HOST=127.0.0.1
|
|
|
15282
15363
|
Environment=PORT=8787
|
|
15283
15364
|
Environment=NODE_NAME=${node.name}
|
|
15284
15365
|
Environment=ARANGO_URL=http://127.0.0.1:8529
|
|
15285
|
-
Environment=ARANGO_URLS
|
|
15286
|
-
Environment=ARANGO_READ_PREFERRED_URLS
|
|
15366
|
+
Environment=ARANGO_URLS=${urls}
|
|
15367
|
+
Environment=ARANGO_READ_PREFERRED_URLS=${readPreferred}
|
|
15287
15368
|
Environment=ARANGO_READ_PREFERRED_FALLBACK=error
|
|
15288
15369
|
ExecStart=/usr/local/bin/bun src/index.ts
|
|
15289
15370
|
Restart=always
|
|
@@ -15303,8 +15384,10 @@ LockPersonality=true
|
|
|
15303
15384
|
[Install]
|
|
15304
15385
|
WantedBy=multi-user.target
|
|
15305
15386
|
`;
|
|
15387
|
+
};
|
|
15306
15388
|
function planCommunityRehearsalPrepare(request) {
|
|
15307
|
-
const
|
|
15389
|
+
const topology = validateCommunityRehearsalTopology(request.topology);
|
|
15390
|
+
const node = topology.find(({ name }) => name === request.node);
|
|
15308
15391
|
const releaseRoot = `${COMMUNITY_REHEARSAL_ROOT}/releases/${request.release}`;
|
|
15309
15392
|
return [
|
|
15310
15393
|
{ kind: "exec", argv: ["/usr/sbin/arangod", "--version"] },
|
|
@@ -15319,13 +15402,13 @@ function planCommunityRehearsalPrepare(request) {
|
|
|
15319
15402
|
` },
|
|
15320
15403
|
{ kind: "exec", argv: ["/usr/bin/chmod", "0400", `${COMMUNITY_CREDENTIAL_ROOT}/arangodb-jwt.cred.next`] },
|
|
15321
15404
|
{ kind: "exec", argv: ["/usr/bin/mv", "-f", `${COMMUNITY_CREDENTIAL_ROOT}/arangodb-jwt.cred.next`, `${COMMUNITY_CREDENTIAL_ROOT}/arangodb-jwt.cred`] },
|
|
15322
|
-
{ kind: "write", path: "/etc/systemd/system/forgezero-rehearsal-db.service", content: databaseUnit(node), mode: 420 },
|
|
15405
|
+
{ kind: "write", path: "/etc/systemd/system/forgezero-rehearsal-db.service", content: databaseUnit(node, topology), mode: 420 },
|
|
15323
15406
|
{ kind: "exec", argv: ["/usr/bin/tar", "-xzf", request.archivePath, "-C", releaseRoot] },
|
|
15324
15407
|
{ kind: "exec", argv: ["/usr/bin/chown", "-R", "forgezero:forgezero", releaseRoot] },
|
|
15325
15408
|
{ kind: "exec", argv: ["/usr/sbin/runuser", "-u", "forgezero", "--", "/usr/local/bin/bun", "install", "--cwd", releaseRoot, "--frozen-lockfile", "--production"] },
|
|
15326
15409
|
{ kind: "unlink", path: `${COMMUNITY_REHEARSAL_ROOT}/current` },
|
|
15327
15410
|
{ kind: "symlink", target: releaseRoot, path: `${COMMUNITY_REHEARSAL_ROOT}/current` },
|
|
15328
|
-
{ kind: "write", path: "/etc/systemd/system/forgezero-community-api.service", content: apiUnit(node), mode: 420 },
|
|
15411
|
+
{ kind: "write", path: "/etc/systemd/system/forgezero-community-api.service", content: apiUnit(node, topology), mode: 420 },
|
|
15329
15412
|
{ kind: "unlink", path: request.archivePath }
|
|
15330
15413
|
];
|
|
15331
15414
|
}
|
|
@@ -15403,7 +15486,10 @@ var responseFor = async (request) => {
|
|
|
15403
15486
|
async function runCommunityRehearsalHost(request) {
|
|
15404
15487
|
if ((process.getuid?.() ?? -1) !== 0)
|
|
15405
15488
|
throw new Error("community-rehearsal must run as root");
|
|
15406
|
-
const
|
|
15489
|
+
const topology = validateCommunityRehearsalTopology(request.topology);
|
|
15490
|
+
const node = topology.find((entry) => entry.name === request.node);
|
|
15491
|
+
if (!node)
|
|
15492
|
+
throw new Error("community rehearsal node is not present in the reviewed topology");
|
|
15407
15493
|
if (request.action === "cleanup") {
|
|
15408
15494
|
await applyOperations(planCommunityRehearsalCleanup());
|
|
15409
15495
|
return { ok: true, action: request.action, node: node.name };
|
|
@@ -15440,7 +15526,8 @@ async function runCommunityRehearsalHost(request) {
|
|
|
15440
15526
|
return { ok: true, action: request.action, node: node.name };
|
|
15441
15527
|
}
|
|
15442
15528
|
if (request.action === "database-ready" || request.action === "starter-ready" || request.action === "api-ready") {
|
|
15443
|
-
const
|
|
15529
|
+
const starter = topology.find((entry) => entry.starter);
|
|
15530
|
+
const url = request.action === "database-ready" ? "http://127.0.0.1:8529/_api/version" : request.action === "starter-ready" ? `http://${starter.address}:8528/version` : "http://127.0.0.1:8787/health";
|
|
15444
15531
|
const response2 = await fetch(url, { signal: AbortSignal.timeout(2000) }).catch(() => {
|
|
15445
15532
|
return;
|
|
15446
15533
|
});
|
|
@@ -16542,6 +16629,7 @@ export {
|
|
|
16542
16629
|
tenantNodeApiUrl,
|
|
16543
16630
|
systemdListenFd,
|
|
16544
16631
|
systemdAgentEgressDirectives,
|
|
16632
|
+
supportedGuestImage,
|
|
16545
16633
|
superviseAgentEgressPolicy,
|
|
16546
16634
|
startSshBootstrapPull,
|
|
16547
16635
|
startSoftwareHelper,
|
|
@@ -16631,6 +16719,7 @@ export {
|
|
|
16631
16719
|
VERSION2 as VERSION,
|
|
16632
16720
|
SYSTEMD_RESOLVED_STUB,
|
|
16633
16721
|
SYSTEMD_RESOLVED_ADDRESS,
|
|
16722
|
+
SUPPORTED_GUEST_IMAGES,
|
|
16634
16723
|
SUPPORTED_GUEST_IMAGE,
|
|
16635
16724
|
SOFTWARE_HELPER_UNIT_PATH,
|
|
16636
16725
|
SOFTWARE_HELPER_GROUP,
|