@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
|
@@ -1251,7 +1251,7 @@ async function sha256File(path) {
|
|
|
1251
1251
|
});
|
|
1252
1252
|
return hash.digest("hex");
|
|
1253
1253
|
}
|
|
1254
|
-
var
|
|
1254
|
+
var bootstrapBundleBranch = (value) => {
|
|
1255
1255
|
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("-")) {
|
|
1256
1256
|
throw new Error("bootstrap bundle branch is malformed");
|
|
1257
1257
|
}
|
|
@@ -1268,7 +1268,7 @@ function parseBootstrapBundleManifest(value) {
|
|
|
1268
1268
|
if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
|
|
1269
1269
|
throw new Error("bootstrap bundle manifest format is unsupported");
|
|
1270
1270
|
}
|
|
1271
|
-
|
|
1271
|
+
bootstrapBundleBranch(source.branch);
|
|
1272
1272
|
if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
|
|
1273
1273
|
throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
|
|
1274
1274
|
}
|
|
@@ -1313,7 +1313,7 @@ async function buildBootstrapBundle(input, exec = run) {
|
|
|
1313
1313
|
const repositoryRoot = resolve2(input.repositoryRoot);
|
|
1314
1314
|
const outputPath = resolve2(input.outputPath);
|
|
1315
1315
|
const manifestPath = `${outputPath}.json`;
|
|
1316
|
-
const branch =
|
|
1316
|
+
const branch = bootstrapBundleBranch(input.branch);
|
|
1317
1317
|
if (!isAbsolute(input.outputPath) || outputPath !== input.outputPath) {
|
|
1318
1318
|
throw new Error("bootstrap bundle output must be a canonical absolute path");
|
|
1319
1319
|
}
|
|
@@ -1396,11 +1396,37 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1396
1396
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1397
1397
|
|
|
1398
1398
|
// src/version.ts
|
|
1399
|
-
var VERSION = "0.1.
|
|
1399
|
+
var VERSION = "0.1.67";
|
|
1400
1400
|
|
|
1401
1401
|
// src/software.ts
|
|
1402
1402
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
1403
1403
|
var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
|
|
1404
|
+
var OS_CATALOG = [
|
|
1405
|
+
{
|
|
1406
|
+
id: "ubuntu",
|
|
1407
|
+
version: "26.04",
|
|
1408
|
+
architecture: "x64",
|
|
1409
|
+
status: "active",
|
|
1410
|
+
imageKey: "ubuntu-resolute-20260731",
|
|
1411
|
+
imageLabel: "Ubuntu 26.04 LTS Resolute",
|
|
1412
|
+
imageVersion: "2026-07-31",
|
|
1413
|
+
imageUrl: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img",
|
|
1414
|
+
imageSha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05",
|
|
1415
|
+
confidentialModes: ["sev-snp"]
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
id: "ubuntu",
|
|
1419
|
+
version: "24.04",
|
|
1420
|
+
architecture: "x64",
|
|
1421
|
+
status: "active",
|
|
1422
|
+
imageKey: "ubuntu-noble-20260705",
|
|
1423
|
+
imageLabel: "Ubuntu 24.04 LTS Noble",
|
|
1424
|
+
imageVersion: "2026-07-05",
|
|
1425
|
+
imageUrl: "https://cloud-images.ubuntu.com/releases/noble/release-20260705/ubuntu-24.04-server-cloudimg-amd64.img",
|
|
1426
|
+
imageSha256: "ffe6203da54deeb6db5d2a98a83f9ec8e55f149d3f7ba622e1abe5fa966ee3d6",
|
|
1427
|
+
confidentialModes: []
|
|
1428
|
+
}
|
|
1429
|
+
];
|
|
1404
1430
|
var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
|
|
1405
1431
|
"data-root": "/var/lib/docker",
|
|
1406
1432
|
"storage-driver": "overlay2",
|
|
@@ -2786,6 +2812,143 @@ async function applyPlan(plan, run2) {
|
|
|
2786
2812
|
return transcript;
|
|
2787
2813
|
}
|
|
2788
2814
|
|
|
2815
|
+
// src/ubuntu.ts
|
|
2816
|
+
var current = OS_CATALOG[0];
|
|
2817
|
+
var SUPPORTED_GUEST_IMAGE = Object.freeze({
|
|
2818
|
+
key: current.imageKey,
|
|
2819
|
+
family: `${current.id}-${current.version}`,
|
|
2820
|
+
version: current.imageVersion,
|
|
2821
|
+
label: current.imageLabel,
|
|
2822
|
+
url: current.imageUrl,
|
|
2823
|
+
sha256: current.imageSha256
|
|
2824
|
+
});
|
|
2825
|
+
var SUPPORTED_GUEST_IMAGES = Object.freeze(OS_CATALOG.filter(({ status }) => status === "active").map(({ id, imageKey, imageLabel, imageVersion, version, confidentialModes }) => ({
|
|
2826
|
+
key: imageKey,
|
|
2827
|
+
label: imageLabel,
|
|
2828
|
+
family: `${id}-${version}`,
|
|
2829
|
+
version: imageVersion,
|
|
2830
|
+
confidential: confidentialModes.some((mode) => mode === "sev-snp")
|
|
2831
|
+
})));
|
|
2832
|
+
function assertSupportedGuestImage(imageKey, confidential = false) {
|
|
2833
|
+
const selected = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
|
|
2834
|
+
if (!selected) {
|
|
2835
|
+
throw new Error(`unsupported guest image ${imageKey}; supported images: ${SUPPORTED_GUEST_IMAGES.map(({ key }) => key).join(", ")}`);
|
|
2836
|
+
}
|
|
2837
|
+
if (confidential && !selected.confidentialModes.some((mode) => mode === "sev-snp")) {
|
|
2838
|
+
throw new Error(`guest image ${imageKey} is not approved for SEV-SNP`);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
// src/platform-genesis.ts
|
|
2843
|
+
var SAFE_NAME = /^[a-z][a-z0-9-]{0,62}$/;
|
|
2844
|
+
var SAFE_POOL = /^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/;
|
|
2845
|
+
var PRIVATE_V4 = /^(?:10\.(?:\d{1,3}\.){2}\d{1,3}|192\.168\.(?:\d{1,3})\.(?:\d{1,3})|172\.(?:1[6-9]|2\d|3[01])\.(?:\d{1,3})\.(?:\d{1,3}))$/;
|
|
2846
|
+
var integer = (value, minimum, maximum, label) => {
|
|
2847
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
2848
|
+
throw new Error(`platform genesis ${label} is outside the supported range`);
|
|
2849
|
+
}
|
|
2850
|
+
return value;
|
|
2851
|
+
};
|
|
2852
|
+
function validatePlatformGenesisGuests(guests, expectedCount = 3) {
|
|
2853
|
+
if (!Array.isArray(guests) || guests.length !== expectedCount) {
|
|
2854
|
+
throw new Error(`platform genesis requires exactly ${expectedCount} reviewed guest requests`);
|
|
2855
|
+
}
|
|
2856
|
+
const names = new Set;
|
|
2857
|
+
const addresses = new Set;
|
|
2858
|
+
const pools = new Set;
|
|
2859
|
+
return guests.map((input) => {
|
|
2860
|
+
if (!input || typeof input !== "object" || !SAFE_NAME.test(input.name)) {
|
|
2861
|
+
throw new Error("platform genesis guest name is malformed");
|
|
2862
|
+
}
|
|
2863
|
+
if (!PRIVATE_V4.test(input.address))
|
|
2864
|
+
throw new Error("platform genesis guest address must be private IPv4");
|
|
2865
|
+
if (typeof input.confidential !== "boolean")
|
|
2866
|
+
throw new Error("platform genesis confidential requirement is missing");
|
|
2867
|
+
assertSupportedGuestImage(input.imageKey, input.confidential);
|
|
2868
|
+
if (input.cpuPoolKey !== undefined && !SAFE_POOL.test(input.cpuPoolKey)) {
|
|
2869
|
+
throw new Error("platform genesis CPU pool key is malformed");
|
|
2870
|
+
}
|
|
2871
|
+
if (names.has(input.name) || addresses.has(input.address) || input.cpuPoolKey !== undefined && pools.has(input.cpuPoolKey)) {
|
|
2872
|
+
throw new Error("platform genesis guest names, addresses and explicit CPU pools must be unique");
|
|
2873
|
+
}
|
|
2874
|
+
names.add(input.name);
|
|
2875
|
+
addresses.add(input.address);
|
|
2876
|
+
if (input.cpuPoolKey !== undefined)
|
|
2877
|
+
pools.add(input.cpuPoolKey);
|
|
2878
|
+
const physicalCores = integer(input.physicalCores, 1, 256, "physical core count");
|
|
2879
|
+
const vcpu = integer(input.vcpu, 1, 512, "vCPU count");
|
|
2880
|
+
if (vcpu < physicalCores)
|
|
2881
|
+
throw new Error("platform genesis vCPU count cannot be smaller than physical cores");
|
|
2882
|
+
const memoryGib = integer(input.memoryGib, 1, 8192, "memory");
|
|
2883
|
+
const diskGib = integer(input.diskGib, 8, 65536, "disk");
|
|
2884
|
+
const egressGuaranteedMbps = integer(input.egressGuaranteedMbps, 0, 1e6, "guaranteed egress");
|
|
2885
|
+
const egressBurstMbps = integer(input.egressBurstMbps, egressGuaranteedMbps, 1e6, "burst egress");
|
|
2886
|
+
return {
|
|
2887
|
+
name: input.name,
|
|
2888
|
+
address: input.address,
|
|
2889
|
+
imageKey: input.imageKey,
|
|
2890
|
+
...input.cpuPoolKey ? { cpuPoolKey: input.cpuPoolKey } : {},
|
|
2891
|
+
physicalCores,
|
|
2892
|
+
vcpu,
|
|
2893
|
+
memoryGib,
|
|
2894
|
+
diskGib,
|
|
2895
|
+
egressGuaranteedMbps,
|
|
2896
|
+
egressBurstMbps,
|
|
2897
|
+
confidential: input.confidential
|
|
2898
|
+
};
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
var publicKey = (value) => {
|
|
2902
|
+
const normalized = value.trim();
|
|
2903
|
+
if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(normalized)) {
|
|
2904
|
+
throw new Error("platform genesis SSH public key is malformed");
|
|
2905
|
+
}
|
|
2906
|
+
return normalized;
|
|
2907
|
+
};
|
|
2908
|
+
var claimFor = (guest, keys, action) => {
|
|
2909
|
+
const common = {
|
|
2910
|
+
computeKey: `platform:${guest.name}`,
|
|
2911
|
+
claimToken: "offline-platform-genesis",
|
|
2912
|
+
claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
|
|
2913
|
+
attempt: 1,
|
|
2914
|
+
bootstrap: { kind: "platform-genesis" },
|
|
2915
|
+
spec: {
|
|
2916
|
+
reference: `platform:${guest.name}`,
|
|
2917
|
+
imageKey: guest.imageKey,
|
|
2918
|
+
guestName: guest.name,
|
|
2919
|
+
guestAddress: guest.address,
|
|
2920
|
+
...guest.cpuPoolKey ? { cpuPoolKey: guest.cpuPoolKey } : {},
|
|
2921
|
+
physicalCores: guest.physicalCores,
|
|
2922
|
+
vcpu: guest.vcpu,
|
|
2923
|
+
memoryGib: guest.memoryGib,
|
|
2924
|
+
diskGib: guest.diskGib,
|
|
2925
|
+
egressGuaranteedMbps: guest.egressGuaranteedMbps,
|
|
2926
|
+
egressBurstMbps: guest.egressBurstMbps,
|
|
2927
|
+
confidential: guest.confidential
|
|
2928
|
+
}
|
|
2929
|
+
};
|
|
2930
|
+
if (action === "delete")
|
|
2931
|
+
return { ...common, action: "delete" };
|
|
2932
|
+
return { ...common, action: "create", access: { sshUser: "forgezero", sshPublicKeys: [...keys] } };
|
|
2933
|
+
};
|
|
2934
|
+
function platformGenesisClaims(guests, sshPublicKeys) {
|
|
2935
|
+
const reviewed = validatePlatformGenesisGuests(guests);
|
|
2936
|
+
const keys = [...new Set(sshPublicKeys.map(publicKey))];
|
|
2937
|
+
if (keys.length === 0)
|
|
2938
|
+
throw new Error("platform genesis requires at least one operator SSH public key");
|
|
2939
|
+
return reviewed.map((guest) => claimFor(guest, keys, "create"));
|
|
2940
|
+
}
|
|
2941
|
+
function platformCommunityRehearsalClaims(seedGuests, rehearsalGuest, sshPublicKeys) {
|
|
2942
|
+
const reviewed = validatePlatformGenesisGuests([...seedGuests, rehearsalGuest], 4);
|
|
2943
|
+
const keys = [...new Set(sshPublicKeys.map(publicKey))];
|
|
2944
|
+
if (keys.length === 0)
|
|
2945
|
+
throw new Error("platform genesis requires at least one operator SSH public key");
|
|
2946
|
+
return reviewed.map((guest) => claimFor(guest, keys, "create"));
|
|
2947
|
+
}
|
|
2948
|
+
function platformCommunityRehearsalDeletionClaims(seedGuests, rehearsalGuest) {
|
|
2949
|
+
return validatePlatformGenesisGuests([...seedGuests, rehearsalGuest], 4).map((guest) => claimFor(guest, [], "delete"));
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2789
2952
|
// src/platform-bootstrap-runtime.ts
|
|
2790
2953
|
var safeAtom = (name, value) => {
|
|
2791
2954
|
if (!value || /[\0\r\n]/.test(value))
|
|
@@ -2817,6 +2980,50 @@ var httpsOrigin = (name, raw) => {
|
|
|
2817
2980
|
}
|
|
2818
2981
|
return value.origin;
|
|
2819
2982
|
};
|
|
2983
|
+
function validatePlatformInitialInventory(value) {
|
|
2984
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
|
|
2985
|
+
throw new Error("initial platform inventory coordinates are invalid");
|
|
2986
|
+
}
|
|
2987
|
+
safeAtom("initialInventory.region.label", value.region.label);
|
|
2988
|
+
if (value.region.city !== undefined)
|
|
2989
|
+
safeAtom("initialInventory.region.city", value.region.city);
|
|
2990
|
+
const computes = validatePlatformGenesisGuests(value.computes.map((compute) => {
|
|
2991
|
+
if (!compute || typeof compute !== "object" || Array.isArray(compute) || Object.keys(compute).some((key) => ![
|
|
2992
|
+
"name",
|
|
2993
|
+
"address",
|
|
2994
|
+
"imageKey",
|
|
2995
|
+
"cpuPoolKey",
|
|
2996
|
+
"physicalCores",
|
|
2997
|
+
"vcpu",
|
|
2998
|
+
"memoryGib",
|
|
2999
|
+
"diskGib",
|
|
3000
|
+
"egressGuaranteedMbps",
|
|
3001
|
+
"egressBurstMbps",
|
|
3002
|
+
"confidential",
|
|
3003
|
+
"databaseRole",
|
|
3004
|
+
"databaseAgency"
|
|
3005
|
+
].includes(key)))
|
|
3006
|
+
throw new Error("initial platform compute contains unknown fields");
|
|
3007
|
+
return compute;
|
|
3008
|
+
}));
|
|
3009
|
+
for (let index = 0;index < computes.length; index += 1) {
|
|
3010
|
+
const declared = value.computes[index];
|
|
3011
|
+
const expectedRole = index === 0 ? "master" : "joiner";
|
|
3012
|
+
if (declared.databaseRole !== expectedRole || declared.databaseAgency !== "member" || declared.confidential !== true) {
|
|
3013
|
+
throw new Error("initial platform inventory requires one master, two joiners, three Agency members and confidential compute");
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
return {
|
|
3017
|
+
metalHostname: value.metalHostname,
|
|
3018
|
+
region: { ...value.region },
|
|
3019
|
+
computes: value.computes.map((compute, index) => ({
|
|
3020
|
+
...computes[index],
|
|
3021
|
+
databaseRole: compute.databaseRole,
|
|
3022
|
+
databaseAgency: compute.databaseAgency,
|
|
3023
|
+
confidential: true
|
|
3024
|
+
}))
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
2820
3027
|
var systemdValue = (name, raw) => {
|
|
2821
3028
|
if (/[\0\r\n]/.test(raw))
|
|
2822
3029
|
throw new Error(`${name} must be single-line.`);
|
|
@@ -2902,12 +3109,14 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
2902
3109
|
throw new Error("realtime.producer is invalid.");
|
|
2903
3110
|
}
|
|
2904
3111
|
}
|
|
3112
|
+
const initialInventory = input.initialInventory ? validatePlatformInitialInventory(input.initialInventory) : undefined;
|
|
2905
3113
|
return {
|
|
2906
3114
|
...input,
|
|
2907
3115
|
databaseCoordinators: coordinators,
|
|
2908
3116
|
appOrigin: httpsOrigin("appOrigin", input.appOrigin),
|
|
2909
3117
|
apiOrigin: httpsOrigin("apiOrigin", input.apiOrigin),
|
|
2910
|
-
agentOtlpEndpoint: httpsOrigin("agentOtlpEndpoint", input.agentOtlpEndpoint)
|
|
3118
|
+
agentOtlpEndpoint: httpsOrigin("agentOtlpEndpoint", input.agentOtlpEndpoint),
|
|
3119
|
+
...initialInventory ? { initialInventory } : {}
|
|
2911
3120
|
};
|
|
2912
3121
|
}
|
|
2913
3122
|
function renderPlatformSharedEnvironment(input) {
|
|
@@ -2970,7 +3179,8 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
2970
3179
|
FZ_CF_TUNNEL_SERVICE: value.cloudflare?.tunnelService ?? "",
|
|
2971
3180
|
FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
|
|
2972
3181
|
FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
|
|
2973
|
-
FZ_REALTIME_PRODUCER: value.realtime?.producer ?? ""
|
|
3182
|
+
FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
|
|
3183
|
+
FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
|
|
2974
3184
|
};
|
|
2975
3185
|
return `# Generated by fz bootstrap platform. Non-secret coordinates only.
|
|
2976
3186
|
` + Object.entries(entries).map(([key, entry]) => `${key}=${systemdValue(key, entry)}`).join(`
|
|
@@ -3485,6 +3695,12 @@ function validateBootstrapConfig(value) {
|
|
|
3485
3695
|
throw new Error("Cloudflare Mesh/WARP requires a node-specific Cloudflare handoff");
|
|
3486
3696
|
}
|
|
3487
3697
|
if (value.kind === "enrolled-compute") {
|
|
3698
|
+
const os = OS_CATALOG.find((entry) => entry.status === "active" && entry.id === value.operatingSystem?.id && entry.version === value.operatingSystem?.version && entry.architecture === value.operatingSystem?.architecture);
|
|
3699
|
+
if (!os)
|
|
3700
|
+
throw new Error("enrolled compute operating system is not in the active typed OS catalog");
|
|
3701
|
+
if (value.operatingSystem.version === "24.04" && (value.installCloudflared || value.installWarp)) {
|
|
3702
|
+
throw new Error("Cloudflare Tunnel and WARP require the reviewed Ubuntu 26.04 strategy");
|
|
3703
|
+
}
|
|
3488
3704
|
if (value.gitDeployKey !== undefined && typeof value.gitDeployKey !== "boolean") {
|
|
3489
3705
|
throw new Error("gitDeployKey must be boolean");
|
|
3490
3706
|
}
|
|
@@ -3516,6 +3732,7 @@ function validateBootstrapConfig(value) {
|
|
|
3516
3732
|
if (!value.bootstrapBundle || ![value.bootstrapBundle.bundleFile, value.bootstrapBundle.manifestFile].every((path) => typeof path === "string" && path.startsWith("/") && !/[\r\n\0:]/.test(path))) {
|
|
3517
3733
|
throw new Error("platform bootstrap requires absolute bundle and manifest paths");
|
|
3518
3734
|
}
|
|
3735
|
+
bootstrapBundleBranch(value.bootstrapBundle.branch);
|
|
3519
3736
|
let api;
|
|
3520
3737
|
try {
|
|
3521
3738
|
api = new URL(value.apiUrl);
|
|
@@ -3570,9 +3787,19 @@ function validateBootstrapConfig(value) {
|
|
|
3570
3787
|
}
|
|
3571
3788
|
function planBootstrap(input, initialized = false) {
|
|
3572
3789
|
const config = validateBootstrapConfig(structuredClone(input));
|
|
3790
|
+
const enrolledOsVersion = config.kind === "enrolled-compute" ? `ubuntu-${config.operatingSystem.version}` : undefined;
|
|
3791
|
+
const enrolledSoftware = config.kind === "enrolled-compute" ? [...config.software ?? []] : [];
|
|
3792
|
+
if (config.kind === "enrolled-compute") {
|
|
3793
|
+
if (!enrolledSoftware.some(({ id }) => id === "bun"))
|
|
3794
|
+
enrolledSoftware.push({ id: "bun", version: "1.3.14" });
|
|
3795
|
+
if (!enrolledSoftware.some(({ id }) => id === "git"))
|
|
3796
|
+
enrolledSoftware.push({ id: "git", version: enrolledOsVersion });
|
|
3797
|
+
if (!enrolledSoftware.some(({ id }) => id === "ufw"))
|
|
3798
|
+
enrolledSoftware.push({ id: "ufw", version: enrolledOsVersion });
|
|
3799
|
+
}
|
|
3573
3800
|
const software = config.kind === "enrolled-compute" ? [
|
|
3574
|
-
...
|
|
3575
|
-
...config.bootstrapRunner && !
|
|
3801
|
+
...enrolledSoftware,
|
|
3802
|
+
...config.bootstrapRunner && !enrolledSoftware.some(({ id }) => id === "openssh-client") ? [{ id: "openssh-client", version: enrolledOsVersion }] : [],
|
|
3576
3803
|
...config.installCloudflared ? [{ id: "cloudflared", version: "2026.7.3" }] : [],
|
|
3577
3804
|
...config.installWarp ? [{ id: "cloudflare-warp", version: "2026.6.822.0-min" }] : []
|
|
3578
3805
|
] : [
|
|
@@ -3807,9 +4034,8 @@ async function verifyBootstrapBundleOnHost(host, config, verifyGit = false) {
|
|
|
3807
4034
|
throw new Error("bootstrap bundle manifest is not valid JSON");
|
|
3808
4035
|
}
|
|
3809
4036
|
const manifest = parseBootstrapBundleManifest(parsed);
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
throw new Error("bootstrap bundle manifest disagrees with the selected platform environment");
|
|
4037
|
+
if (manifest.branch !== config.bootstrapBundle.branch || bundleMetadata && bundleMetadata.size !== manifest.bytes) {
|
|
4038
|
+
throw new Error("bootstrap bundle manifest disagrees with the reviewed bootstrap bundle coordinates");
|
|
3813
4039
|
}
|
|
3814
4040
|
const digest = (await checked3(host, ["/usr/bin/sha256sum", config.bootstrapBundle.bundleFile], "bootstrap bundle digest")).trim().split(/\s+/)[0];
|
|
3815
4041
|
if (digest !== manifest.sha256)
|
|
@@ -3823,6 +4049,7 @@ function bootstrapIdentity(config) {
|
|
|
3823
4049
|
if (config.kind === "enrolled-compute")
|
|
3824
4050
|
return {
|
|
3825
4051
|
kind: config.kind,
|
|
4052
|
+
operatingSystem: config.operatingSystem,
|
|
3826
4053
|
apiUrl: config.apiUrl,
|
|
3827
4054
|
realm: config.realm,
|
|
3828
4055
|
nodeHostname: config.nodeHostname,
|
|
@@ -4436,7 +4663,7 @@ function strictBootstrapDocument(value) {
|
|
|
4436
4663
|
"bootstrapRunner"
|
|
4437
4664
|
], "bootstrap config");
|
|
4438
4665
|
if (root.kind === "platform") {
|
|
4439
|
-
exactKeys(root.bootstrapBundle, ["bundleFile", "manifestFile"], "bootstrap bundle config");
|
|
4666
|
+
exactKeys(root.bootstrapBundle, ["bundleFile", "manifestFile", "branch"], "bootstrap bundle config");
|
|
4440
4667
|
exactKeys(root.firewall, ["enabled", "sshPort", "privateCidrs"], "firewall config");
|
|
4441
4668
|
if (root.cloudflareHandoff !== undefined)
|
|
4442
4669
|
exactKeys(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
|
|
@@ -4483,9 +4710,32 @@ function strictBootstrapDocument(value) {
|
|
|
4483
4710
|
"otlpTraceSampleRatio",
|
|
4484
4711
|
"backup",
|
|
4485
4712
|
"cloudflare",
|
|
4486
|
-
"realtime"
|
|
4713
|
+
"realtime",
|
|
4714
|
+
"initialInventory"
|
|
4487
4715
|
], "runtime environment");
|
|
4488
4716
|
const environment = runtime.environment;
|
|
4717
|
+
if (environment.initialInventory !== undefined) {
|
|
4718
|
+
const inventory = exactKeys(environment.initialInventory, ["metalHostname", "region", "computes"], "initial inventory");
|
|
4719
|
+
exactKeys(inventory.region, ["key", "label", "country", "city", "confidentialCapable"], "initial inventory region");
|
|
4720
|
+
if (!Array.isArray(inventory.computes))
|
|
4721
|
+
throw new Error("initial inventory computes must be an array");
|
|
4722
|
+
for (const compute of inventory.computes)
|
|
4723
|
+
exactKeys(compute, [
|
|
4724
|
+
"name",
|
|
4725
|
+
"address",
|
|
4726
|
+
"imageKey",
|
|
4727
|
+
"cpuPoolKey",
|
|
4728
|
+
"physicalCores",
|
|
4729
|
+
"vcpu",
|
|
4730
|
+
"memoryGib",
|
|
4731
|
+
"diskGib",
|
|
4732
|
+
"egressGuaranteedMbps",
|
|
4733
|
+
"egressBurstMbps",
|
|
4734
|
+
"confidential",
|
|
4735
|
+
"databaseRole",
|
|
4736
|
+
"databaseAgency"
|
|
4737
|
+
], "initial inventory compute");
|
|
4738
|
+
}
|
|
4489
4739
|
if (environment.email !== undefined) {
|
|
4490
4740
|
const email = exactKeys(environment.email, ["provider", "host", "port", "user", "from", "eu"], "email config");
|
|
4491
4741
|
if (email.provider === "smtp")
|
|
@@ -4502,6 +4752,7 @@ function strictBootstrapDocument(value) {
|
|
|
4502
4752
|
if (environment.realtime !== undefined)
|
|
4503
4753
|
exactKeys(environment.realtime, ["workerScriptName", "endpoint", "producer"], "realtime runtime config");
|
|
4504
4754
|
} else if (root.kind === "enrolled-compute") {
|
|
4755
|
+
exactKeys(root.operatingSystem, ["id", "version", "architecture"], "enrolled compute operating system");
|
|
4505
4756
|
if (root.cloudflareHandoff !== undefined)
|
|
4506
4757
|
exactKeys(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
|
|
4507
4758
|
if (root.bootstrapRunner !== undefined)
|
|
@@ -4694,19 +4945,7 @@ import { join as join4 } from "path";
|
|
|
4694
4945
|
// src/metal-provision.ts
|
|
4695
4946
|
import { dirname as dirname7, isAbsolute as isAbsolute2, join as join3 } from "path";
|
|
4696
4947
|
import { isIP as isIP4 } from "net";
|
|
4697
|
-
|
|
4698
|
-
// src/ubuntu.ts
|
|
4699
|
-
var SUPPORTED_GUEST_IMAGE = Object.freeze({
|
|
4700
|
-
key: "ubuntu-resolute-20260731",
|
|
4701
|
-
family: "ubuntu-26.04",
|
|
4702
|
-
version: "2026-07-31",
|
|
4703
|
-
label: "Ubuntu 26.04 LTS Resolute",
|
|
4704
|
-
url: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img",
|
|
4705
|
-
sha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05"
|
|
4706
|
-
});
|
|
4707
|
-
|
|
4708
|
-
// src/metal-provision.ts
|
|
4709
|
-
var SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
|
|
4948
|
+
var SAFE_NAME2 = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
|
|
4710
4949
|
var DEVICE = /^[a-zA-Z][a-zA-Z0-9_.-]{0,14}$/;
|
|
4711
4950
|
var IPV4_PREFIX = /^(?:25[0-5]|2[0-4]\d|1?\d?\d)\.(?:25[0-5]|2[0-4]\d|1?\d?\d)\.(?:25[0-5]|2[0-4]\d|1?\d?\d)$/;
|
|
4712
4951
|
var LINUX_LIST = /^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$/;
|
|
@@ -4732,7 +4971,7 @@ function membersOfLinuxList(value, label) {
|
|
|
4732
4971
|
return members;
|
|
4733
4972
|
}
|
|
4734
4973
|
function validateMetalProfile(profile) {
|
|
4735
|
-
if (!
|
|
4974
|
+
if (!SAFE_NAME2.test(profile.volumeGroup))
|
|
4736
4975
|
throw new MetalProvisionError("invalid volume group");
|
|
4737
4976
|
if (!DEVICE.test(profile.bridge))
|
|
4738
4977
|
throw new MetalProvisionError("invalid bridge");
|
|
@@ -4754,9 +4993,16 @@ function validateMetalProfile(profile) {
|
|
|
4754
4993
|
if (telemetryEndpoint.protocol !== "https:" || telemetryEndpoint.username || telemetryEndpoint.password || telemetryEndpoint.search || telemetryEndpoint.hash || isIP4(telemetryEndpoint.hostname) !== 0 || !telemetryEndpoint.hostname.includes(".") || telemetryEndpoint.hostname === "localhost" || telemetryEndpoint.hostname.endsWith(".local"))
|
|
4755
4994
|
throw new MetalProvisionError("Agent telemetry endpoint must be a public HTTPS DNS coordinate without credentials, query or fragment");
|
|
4756
4995
|
const imageKeys = Object.keys(profile.images);
|
|
4757
|
-
if (imageKeys.length
|
|
4758
|
-
throw new MetalProvisionError(
|
|
4996
|
+
if (imageKeys.length === 0)
|
|
4997
|
+
throw new MetalProvisionError("metal profile requires at least one pinned guest image");
|
|
4998
|
+
for (const imageKey of imageKeys) {
|
|
4999
|
+
const catalog = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
|
|
5000
|
+
if (!catalog || profile.images[imageKey]?.sha256 !== catalog.imageSha256) {
|
|
5001
|
+
throw new MetalProvisionError(`metal profile image ${imageKey} does not match the pinned OS catalog`);
|
|
5002
|
+
}
|
|
4759
5003
|
}
|
|
5004
|
+
if (profile.confidential && !imageKeys.some((key) => OS_CATALOG.find((entry) => entry.imageKey === key)?.confidentialModes.some((mode) => mode === "sev-snp")))
|
|
5005
|
+
throw new MetalProvisionError("confidential metal profile requires a pinned SEV-SNP guest image");
|
|
4760
5006
|
if (!Array.isArray(profile.cpuPools) || profile.cpuPools.length === 0) {
|
|
4761
5007
|
throw new MetalProvisionError("at least one exclusive CPU pool is required");
|
|
4762
5008
|
}
|
|
@@ -4765,7 +5011,7 @@ function validateMetalProfile(profile) {
|
|
|
4765
5011
|
const assignedMemory = new Set;
|
|
4766
5012
|
let poolsWithMemory = 0;
|
|
4767
5013
|
for (const pool of profile.cpuPools) {
|
|
4768
|
-
if (!
|
|
5014
|
+
if (!SAFE_NAME2.test(pool.key) || keys.has(pool.key))
|
|
4769
5015
|
throw new MetalProvisionError("invalid or duplicate CPU pool key");
|
|
4770
5016
|
keys.add(pool.key);
|
|
4771
5017
|
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
@@ -5007,10 +5253,9 @@ function validateMetalBootstrapConfig(config) {
|
|
|
5007
5253
|
if (config.profile.stateDir !== "/etc/forgezero/metal-guests" || config.profile.seedDir !== "/var/lib/forgezero/seed" || config.profile.unitDir !== UNIT_DIRECTORY || config.profile.legacyStateDir !== undefined) {
|
|
5008
5254
|
throw new MetalBootstrapError("metal bootstrap uses fixed state, seed, and systemd unit directories");
|
|
5009
5255
|
}
|
|
5010
|
-
const image
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
}
|
|
5256
|
+
for (const image of Object.values(config.profile.images))
|
|
5257
|
+
if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve5(image.path) !== image.path || /[\0\r\n]/.test(image.path))
|
|
5258
|
+
throw new MetalBootstrapError("every pinned guest image must use the fixed image directory");
|
|
5014
5259
|
if (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION) {
|
|
5015
5260
|
throw new MetalBootstrapError("metal guest runtime must use the package-owned Bun and Agent release coordinates");
|
|
5016
5261
|
}
|
|
@@ -5312,9 +5557,11 @@ var preflight = async (config, exec) => {
|
|
|
5312
5557
|
throw new MetalBootstrapError("/dev/sev is required by the confidential profile");
|
|
5313
5558
|
await runChecked(exec, ["/usr/bin/qemu-system-x86_64", "-object", "sev-snp-guest,help"]);
|
|
5314
5559
|
}
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5560
|
+
for (const image of Object.values(config.profile.images)) {
|
|
5561
|
+
const digest = (await runChecked(exec, ["/usr/bin/sha256sum", image.path])).stdout.split(/\s+/)[0];
|
|
5562
|
+
if (digest !== image.sha256)
|
|
5563
|
+
throw new MetalBootstrapError("pinned guest image digest mismatch");
|
|
5564
|
+
}
|
|
5318
5565
|
};
|
|
5319
5566
|
var assertSupportedMetalHost = () => {
|
|
5320
5567
|
if (process.platform !== "linux" || process.arch !== "x64") {
|
|
@@ -5326,34 +5573,38 @@ var assertSupportedMetalHost = () => {
|
|
|
5326
5573
|
}
|
|
5327
5574
|
};
|
|
5328
5575
|
var ensurePinnedGuestImage = async (config, exec) => {
|
|
5329
|
-
const image
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
"--fail",
|
|
5338
|
-
"--location",
|
|
5339
|
-
"--proto",
|
|
5340
|
-
"=https",
|
|
5341
|
-
"--tlsv1.2",
|
|
5342
|
-
"--output",
|
|
5343
|
-
temporary,
|
|
5344
|
-
SUPPORTED_GUEST_IMAGE.url
|
|
5345
|
-
]);
|
|
5346
|
-
const digest = (await runChecked(exec, ["/usr/bin/sha256sum", temporary])).stdout.split(/\s+/)[0];
|
|
5347
|
-
if (digest !== image.sha256)
|
|
5348
|
-
throw new MetalBootstrapError("downloaded guest image digest mismatch");
|
|
5349
|
-
chmodSync4(temporary, 292);
|
|
5350
|
-
chownSync(temporary, 0, 0);
|
|
5351
|
-
renameSync6(temporary, image.path);
|
|
5352
|
-
} catch (cause) {
|
|
5576
|
+
for (const [imageKey, image] of Object.entries(config.profile.images)) {
|
|
5577
|
+
if (existsSync6(image.path))
|
|
5578
|
+
continue;
|
|
5579
|
+
const catalog = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
|
|
5580
|
+
if (!catalog)
|
|
5581
|
+
throw new MetalBootstrapError(`guest image ${imageKey} is not active`);
|
|
5582
|
+
mkdirSync7(dirname8(image.path), { recursive: true, mode: 493 });
|
|
5583
|
+
const temporary = `${image.path}.next-${process.pid}`;
|
|
5353
5584
|
try {
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5585
|
+
await runChecked(exec, [
|
|
5586
|
+
"/usr/bin/curl",
|
|
5587
|
+
"--fail",
|
|
5588
|
+
"--location",
|
|
5589
|
+
"--proto",
|
|
5590
|
+
"=https",
|
|
5591
|
+
"--tlsv1.2",
|
|
5592
|
+
"--output",
|
|
5593
|
+
temporary,
|
|
5594
|
+
catalog.imageUrl
|
|
5595
|
+
]);
|
|
5596
|
+
const digest = (await runChecked(exec, ["/usr/bin/sha256sum", temporary])).stdout.split(/\s+/)[0];
|
|
5597
|
+
if (digest !== image.sha256)
|
|
5598
|
+
throw new MetalBootstrapError("downloaded guest image digest mismatch");
|
|
5599
|
+
chmodSync4(temporary, 292);
|
|
5600
|
+
chownSync(temporary, 0, 0);
|
|
5601
|
+
renameSync6(temporary, image.path);
|
|
5602
|
+
} catch (cause) {
|
|
5603
|
+
try {
|
|
5604
|
+
unlinkSync(temporary);
|
|
5605
|
+
} catch {}
|
|
5606
|
+
throw cause;
|
|
5607
|
+
}
|
|
5357
5608
|
}
|
|
5358
5609
|
};
|
|
5359
5610
|
async function applyMetalBootstrap(config, options) {
|
|
@@ -5513,10 +5764,17 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
5513
5764
|
])).exitCode !== 0) {
|
|
5514
5765
|
problems.push("local OTLP metrics receiver did not accept a proof request");
|
|
5515
5766
|
}
|
|
5516
|
-
|
|
5517
|
-
|
|
5767
|
+
imageVerified = true;
|
|
5768
|
+
for (const image of Object.values(profile.images)) {
|
|
5769
|
+
if (!existsSync6(image.path)) {
|
|
5770
|
+
imageVerified = false;
|
|
5771
|
+
break;
|
|
5772
|
+
}
|
|
5518
5773
|
const digest = (await exec(["/usr/bin/sha256sum", image.path])).stdout.split(/\s+/)[0];
|
|
5519
|
-
|
|
5774
|
+
if (digest !== image.sha256) {
|
|
5775
|
+
imageVerified = false;
|
|
5776
|
+
break;
|
|
5777
|
+
}
|
|
5520
5778
|
}
|
|
5521
5779
|
if (!imageVerified)
|
|
5522
5780
|
problems.push("pinned guest image is absent or has the wrong digest");
|
|
@@ -5576,126 +5834,6 @@ import { isIP as isIP6 } from "net";
|
|
|
5576
5834
|
import { tmpdir } from "os";
|
|
5577
5835
|
import { basename, dirname as dirname9, isAbsolute as isAbsolute4, join as join6, resolve as resolve6 } from "path";
|
|
5578
5836
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5579
|
-
|
|
5580
|
-
// src/platform-genesis.ts
|
|
5581
|
-
var PLATFORM_GENESIS_ENVIRONMENTS = ["production", "development"];
|
|
5582
|
-
var productionShape = {
|
|
5583
|
-
physicalCores: 8,
|
|
5584
|
-
vcpu: 16,
|
|
5585
|
-
memoryGib: 24,
|
|
5586
|
-
diskGib: 200,
|
|
5587
|
-
egressGuaranteedMbps: 1000,
|
|
5588
|
-
egressBurstMbps: 2000
|
|
5589
|
-
};
|
|
5590
|
-
var developmentShape = {
|
|
5591
|
-
physicalCores: 2,
|
|
5592
|
-
vcpu: 4,
|
|
5593
|
-
memoryGib: 8,
|
|
5594
|
-
diskGib: 64,
|
|
5595
|
-
egressGuaranteedMbps: 100,
|
|
5596
|
-
egressBurstMbps: 250
|
|
5597
|
-
};
|
|
5598
|
-
var PLATFORM_GENESIS_FLEETS = {
|
|
5599
|
-
production: [1, 2, 3].map((node) => ({
|
|
5600
|
-
name: `fz-n${node}`,
|
|
5601
|
-
address: `10.42.0.${10 + node}`,
|
|
5602
|
-
cpuPoolKey: `node${node}`,
|
|
5603
|
-
...productionShape
|
|
5604
|
-
})),
|
|
5605
|
-
development: [1, 2, 3].map((node) => ({
|
|
5606
|
-
name: `dev-fz-n${node}`,
|
|
5607
|
-
address: `10.42.0.${20 + node}`,
|
|
5608
|
-
cpuPoolKey: `node${node + 3}`,
|
|
5609
|
-
...developmentShape
|
|
5610
|
-
}))
|
|
5611
|
-
};
|
|
5612
|
-
var DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST = {
|
|
5613
|
-
name: "dev-fz-n4",
|
|
5614
|
-
address: "10.42.0.24",
|
|
5615
|
-
cpuPoolKey: "node7",
|
|
5616
|
-
...developmentShape
|
|
5617
|
-
};
|
|
5618
|
-
var publicKey = (value) => {
|
|
5619
|
-
const normalized = value.trim();
|
|
5620
|
-
if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(normalized)) {
|
|
5621
|
-
throw new Error("platform genesis SSH public key is malformed");
|
|
5622
|
-
}
|
|
5623
|
-
return normalized;
|
|
5624
|
-
};
|
|
5625
|
-
function platformGenesisClaims(environment, sshPublicKeys) {
|
|
5626
|
-
if (!PLATFORM_GENESIS_ENVIRONMENTS.includes(environment)) {
|
|
5627
|
-
throw new Error("platform genesis environment must be production or development");
|
|
5628
|
-
}
|
|
5629
|
-
const keys = [...new Set(sshPublicKeys.map(publicKey))];
|
|
5630
|
-
if (keys.length === 0)
|
|
5631
|
-
throw new Error("platform genesis requires at least one operator SSH public key");
|
|
5632
|
-
return PLATFORM_GENESIS_FLEETS[environment].map((guest) => ({
|
|
5633
|
-
computeKey: `platform:${guest.name}`,
|
|
5634
|
-
claimToken: "offline-platform-genesis",
|
|
5635
|
-
claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
|
|
5636
|
-
attempt: 1,
|
|
5637
|
-
action: "create",
|
|
5638
|
-
bootstrap: { kind: "platform-genesis" },
|
|
5639
|
-
access: { sshUser: "forgezero", sshPublicKeys: keys },
|
|
5640
|
-
spec: {
|
|
5641
|
-
reference: `platform:${guest.name}`,
|
|
5642
|
-
imageKey: SUPPORTED_GUEST_IMAGE.key,
|
|
5643
|
-
guestName: guest.name,
|
|
5644
|
-
guestAddress: guest.address,
|
|
5645
|
-
cpuPoolKey: guest.cpuPoolKey,
|
|
5646
|
-
physicalCores: guest.physicalCores,
|
|
5647
|
-
vcpu: guest.vcpu,
|
|
5648
|
-
memoryGib: guest.memoryGib,
|
|
5649
|
-
diskGib: guest.diskGib,
|
|
5650
|
-
egressGuaranteedMbps: guest.egressGuaranteedMbps,
|
|
5651
|
-
egressBurstMbps: guest.egressBurstMbps,
|
|
5652
|
-
confidential: true
|
|
5653
|
-
}
|
|
5654
|
-
}));
|
|
5655
|
-
}
|
|
5656
|
-
function platformCommunityRehearsalClaims(sshPublicKeys) {
|
|
5657
|
-
const keys = [...new Set(sshPublicKeys.map(publicKey))];
|
|
5658
|
-
if (keys.length === 0)
|
|
5659
|
-
throw new Error("platform genesis requires at least one operator SSH public key");
|
|
5660
|
-
const seed = platformGenesisClaims("development", keys);
|
|
5661
|
-
const guest = DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST;
|
|
5662
|
-
return [...seed, {
|
|
5663
|
-
computeKey: `platform:${guest.name}`,
|
|
5664
|
-
claimToken: "offline-platform-genesis",
|
|
5665
|
-
claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
|
|
5666
|
-
attempt: 1,
|
|
5667
|
-
action: "create",
|
|
5668
|
-
bootstrap: { kind: "platform-genesis" },
|
|
5669
|
-
access: { sshUser: "forgezero", sshPublicKeys: keys },
|
|
5670
|
-
spec: {
|
|
5671
|
-
reference: `platform:${guest.name}`,
|
|
5672
|
-
imageKey: SUPPORTED_GUEST_IMAGE.key,
|
|
5673
|
-
guestName: guest.name,
|
|
5674
|
-
guestAddress: guest.address,
|
|
5675
|
-
cpuPoolKey: guest.cpuPoolKey,
|
|
5676
|
-
physicalCores: guest.physicalCores,
|
|
5677
|
-
vcpu: guest.vcpu,
|
|
5678
|
-
memoryGib: guest.memoryGib,
|
|
5679
|
-
diskGib: guest.diskGib,
|
|
5680
|
-
egressGuaranteedMbps: guest.egressGuaranteedMbps,
|
|
5681
|
-
egressBurstMbps: guest.egressBurstMbps,
|
|
5682
|
-
confidential: true
|
|
5683
|
-
}
|
|
5684
|
-
}];
|
|
5685
|
-
}
|
|
5686
|
-
function platformCommunityRehearsalDeletionClaims(sshPublicKeys) {
|
|
5687
|
-
return platformCommunityRehearsalClaims(sshPublicKeys).map((claim) => ({
|
|
5688
|
-
computeKey: claim.computeKey,
|
|
5689
|
-
claimToken: claim.claimToken,
|
|
5690
|
-
claimExpiresAtTs: claim.claimExpiresAtTs,
|
|
5691
|
-
attempt: claim.attempt,
|
|
5692
|
-
action: "delete",
|
|
5693
|
-
bootstrap: { kind: "platform-genesis" },
|
|
5694
|
-
spec: claim.spec
|
|
5695
|
-
}));
|
|
5696
|
-
}
|
|
5697
|
-
|
|
5698
|
-
// src/operator-bootstrap.ts
|
|
5699
5837
|
var REQUEST_LIMIT = 64 * 1024;
|
|
5700
5838
|
var SECRET_LIMIT = 256 * 1024;
|
|
5701
5839
|
var REMOTE_STAGE = "/run/forgezero-operator-bootstrap";
|
|
@@ -5875,10 +6013,44 @@ var validateMetalRequestValue = (value) => {
|
|
|
5875
6013
|
throw new Error("metalConfigFile must be an absolute path");
|
|
5876
6014
|
canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
|
|
5877
6015
|
readMetalBootstrapConfig(root.metalConfigFile);
|
|
5878
|
-
const genesis = exactKeys3(root.genesis, ["
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
6016
|
+
const genesis = exactKeys3(root.genesis, ["nodes", "rehearsalNode", "sshPublicKeyFiles"], "operator metal genesis");
|
|
6017
|
+
const guest = (value2, label) => {
|
|
6018
|
+
const row = exactKeys3(value2, [
|
|
6019
|
+
"name",
|
|
6020
|
+
"address",
|
|
6021
|
+
"imageKey",
|
|
6022
|
+
"cpuPoolKey",
|
|
6023
|
+
"physicalCores",
|
|
6024
|
+
"vcpu",
|
|
6025
|
+
"memoryGib",
|
|
6026
|
+
"diskGib",
|
|
6027
|
+
"egressGuaranteedMbps",
|
|
6028
|
+
"egressBurstMbps",
|
|
6029
|
+
"confidential"
|
|
6030
|
+
], label);
|
|
6031
|
+
if (typeof row.name !== "string" || typeof row.address !== "string" || typeof row.imageKey !== "string" || row.cpuPoolKey !== undefined && typeof row.cpuPoolKey !== "string" || !["physicalCores", "vcpu", "memoryGib", "diskGib", "egressGuaranteedMbps", "egressBurstMbps"].every((key) => typeof row[key] === "number") || typeof row.confidential !== "boolean") {
|
|
6032
|
+
throw new Error(`${label} has invalid resource coordinates`);
|
|
6033
|
+
}
|
|
6034
|
+
return {
|
|
6035
|
+
name: row.name,
|
|
6036
|
+
address: row.address,
|
|
6037
|
+
imageKey: row.imageKey,
|
|
6038
|
+
...row.cpuPoolKey ? { cpuPoolKey: row.cpuPoolKey } : {},
|
|
6039
|
+
physicalCores: row.physicalCores,
|
|
6040
|
+
vcpu: row.vcpu,
|
|
6041
|
+
memoryGib: row.memoryGib,
|
|
6042
|
+
diskGib: row.diskGib,
|
|
6043
|
+
egressGuaranteedMbps: row.egressGuaranteedMbps,
|
|
6044
|
+
egressBurstMbps: row.egressBurstMbps,
|
|
6045
|
+
confidential: row.confidential
|
|
6046
|
+
};
|
|
6047
|
+
};
|
|
6048
|
+
if (!Array.isArray(genesis.nodes))
|
|
6049
|
+
throw new Error("operator metal genesis nodes must be an array");
|
|
6050
|
+
const nodes = validatePlatformGenesisGuests(genesis.nodes.map((row, index) => guest(row, `operator metal genesis node ${index + 1}`)));
|
|
6051
|
+
const rehearsalNode = genesis.rehearsalNode === undefined ? undefined : guest(genesis.rehearsalNode, "operator metal rehearsal node");
|
|
6052
|
+
if (rehearsalNode)
|
|
6053
|
+
validatePlatformGenesisGuests([...nodes, rehearsalNode], 4);
|
|
5882
6054
|
if (!Array.isArray(genesis.sshPublicKeyFiles) || genesis.sshPublicKeyFiles.length === 0 || genesis.sshPublicKeyFiles.some((entry) => typeof entry !== "string")) {
|
|
5883
6055
|
throw new Error("operator metal genesis requires SSH public-key files");
|
|
5884
6056
|
}
|
|
@@ -5893,7 +6065,8 @@ var validateMetalRequestValue = (value) => {
|
|
|
5893
6065
|
agentSocket: targetValue.agentSocket
|
|
5894
6066
|
},
|
|
5895
6067
|
genesis: {
|
|
5896
|
-
|
|
6068
|
+
nodes,
|
|
6069
|
+
...rehearsalNode ? { rehearsalNode } : {},
|
|
5897
6070
|
sshPublicKeyFiles: genesis.sshPublicKeyFiles
|
|
5898
6071
|
}
|
|
5899
6072
|
};
|
|
@@ -5908,17 +6081,17 @@ function writeOperatorMetalBootstrapRequest(path, request) {
|
|
|
5908
6081
|
}
|
|
5909
6082
|
function planOperatorMetalBootstrap(request, mode) {
|
|
5910
6083
|
const config = readMetalBootstrapConfig(request.metalConfigFile);
|
|
5911
|
-
if ((mode === "rehearsal" || mode === "rehearsal-cleanup") && request.genesis.environment !== "development") {
|
|
5912
|
-
throw new Error("the disposable four-node Community rehearsal is development-only");
|
|
5913
|
-
}
|
|
5914
6084
|
const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
|
|
5915
|
-
|
|
6085
|
+
if ((mode === "rehearsal" || mode === "rehearsal-cleanup") && !request.genesis.rehearsalNode) {
|
|
6086
|
+
throw new Error("the reviewed metal request has no rehearsal node");
|
|
6087
|
+
}
|
|
6088
|
+
const claims = mode === "rehearsal" || mode === "rehearsal-cleanup" ? mode === "rehearsal-cleanup" ? platformCommunityRehearsalDeletionClaims(request.genesis.nodes, request.genesis.rehearsalNode) : platformCommunityRehearsalClaims(request.genesis.nodes, request.genesis.rehearsalNode, keys) : platformGenesisClaims(request.genesis.nodes, keys);
|
|
5916
6089
|
return {
|
|
5917
6090
|
kind: "metal-remote",
|
|
5918
6091
|
mode,
|
|
5919
6092
|
mutation: mode !== "status",
|
|
5920
6093
|
target: { address: request.target.address, port: request.target.port, user: request.target.user },
|
|
5921
|
-
steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "genesis" || mode === "rehearsal" || mode === "rehearsal-cleanup" ? ["verify pinned SSH transport", mode === "rehearsal" ? "send the
|
|
6094
|
+
steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "genesis" || mode === "rehearsal" || mode === "rehearsal-cleanup" ? ["verify pinned SSH transport", mode === "rehearsal" ? "send the three reviewed seed claims plus the reviewed fourth Community rehearsal claim to the constrained Metal helper" : mode === "rehearsal-cleanup" ? "delete the exact four reviewed disposable guests and their owned disks, units, seed data and inventory" : "send the three reviewed platform-genesis claims to the constrained Metal helper"] : [
|
|
5922
6095
|
"verify pinned SSH transport and caller-approved SSH agent",
|
|
5923
6096
|
"copy pinned Bun and packaged fz artifacts to private staging",
|
|
5924
6097
|
"stage the strict owner-only metal config and optional seed handoff",
|
|
@@ -5990,9 +6163,7 @@ function validatedPlatformFleet(fleet) {
|
|
|
5990
6163
|
return config;
|
|
5991
6164
|
});
|
|
5992
6165
|
const environment = configs[0].environment;
|
|
5993
|
-
const expected =
|
|
5994
|
-
if (!expected || expected.length !== 3)
|
|
5995
|
-
throw new Error("operator fleet environment is invalid");
|
|
6166
|
+
const expected = configs.map((config) => ({ name: config.computeReference, address: config.database.address }));
|
|
5996
6167
|
const identity = fleetConfigIdentity(configs[0]);
|
|
5997
6168
|
const operatorIdentity = `${requests[0].target.identityPublicKeyFile}\x00${requests[0].target.agentSocket}`;
|
|
5998
6169
|
const jumpIdentity = JSON.stringify(requests[0].target.jump ?? null);
|
|
@@ -6024,6 +6195,20 @@ function validatedPlatformFleet(fleet) {
|
|
|
6024
6195
|
}
|
|
6025
6196
|
return { requests, configs, environment };
|
|
6026
6197
|
}
|
|
6198
|
+
function operatorPlatformFleetCoordinates(fleet) {
|
|
6199
|
+
const { requests, configs, environment } = validatedPlatformFleet(fleet);
|
|
6200
|
+
return {
|
|
6201
|
+
environment,
|
|
6202
|
+
appOrigin: configs[0].runtime.environment.appOrigin,
|
|
6203
|
+
apiOrigin: configs[0].runtime.environment.apiOrigin,
|
|
6204
|
+
nodes: configs.map((config, index) => ({
|
|
6205
|
+
computeReference: config.computeReference,
|
|
6206
|
+
address: requests[index].target.address,
|
|
6207
|
+
nodeHostname: config.nodeHostname,
|
|
6208
|
+
databaseRole: config.database.role
|
|
6209
|
+
}))
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6027
6212
|
function planOperatorPlatformFleetBootstrap(fleet, mode) {
|
|
6028
6213
|
const { requests, configs, environment } = validatedPlatformFleet(fleet);
|
|
6029
6214
|
const secretInputs = mode === "apply" ? attendedSecretNames(configs[0]) : [];
|
|
@@ -6163,13 +6348,17 @@ async function stageConfig(config, directory) {
|
|
|
6163
6348
|
rewritten.cloudflareHandoff.handoffFile = remotePath;
|
|
6164
6349
|
}
|
|
6165
6350
|
const bundle = await readBootstrapBundle(config.bootstrapBundle.bundleFile, config.bootstrapBundle.manifestFile);
|
|
6351
|
+
if (bundle.manifest.branch !== config.bootstrapBundle.branch) {
|
|
6352
|
+
throw new Error("reviewed bootstrap config branch does not match the bundle manifest");
|
|
6353
|
+
}
|
|
6166
6354
|
const bundleFiles = [
|
|
6167
6355
|
{ source: bundle.bundlePath, name: "bootstrap-api.bundle" },
|
|
6168
6356
|
{ source: bundle.manifestPath, name: "bootstrap-api.bundle.json" }
|
|
6169
6357
|
];
|
|
6170
6358
|
rewritten.bootstrapBundle = {
|
|
6171
6359
|
bundleFile: `${REMOTE_STAGE}/bootstrap-api.bundle`,
|
|
6172
|
-
manifestFile: `${REMOTE_STAGE}/bootstrap-api.bundle.json
|
|
6360
|
+
manifestFile: `${REMOTE_STAGE}/bootstrap-api.bundle.json`,
|
|
6361
|
+
branch: bundle.manifest.branch
|
|
6173
6362
|
};
|
|
6174
6363
|
const path = join6(directory, "platform-config.json");
|
|
6175
6364
|
writeFileSync8(path, `${JSON.stringify(rewritten, null, 2)}
|
|
@@ -6311,6 +6500,38 @@ async function applyOperatorPlatformFleetBootstrap(fleet, mode, options = {}) {
|
|
|
6311
6500
|
});
|
|
6312
6501
|
return { plan, ok: outcomes.every(({ ok }) => ok), outcomes };
|
|
6313
6502
|
}
|
|
6503
|
+
async function verifyOperatorPlatformFleet(fleet, expectedVersion, options = {}) {
|
|
6504
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(expectedVersion)) {
|
|
6505
|
+
throw new Error("expected Agent version is malformed");
|
|
6506
|
+
}
|
|
6507
|
+
const plan = planOperatorPlatformFleetBootstrap(fleet, "status");
|
|
6508
|
+
const { requests, configs } = validatedPlatformFleet(fleet);
|
|
6509
|
+
const exec = options.exec ?? defaultExec3;
|
|
6510
|
+
const settled = await Promise.allSettled(requests.map(async (request) => {
|
|
6511
|
+
publicIdentity(request.target.identityPublicKeyFile);
|
|
6512
|
+
socketPath(request.target.agentSocket);
|
|
6513
|
+
const directory = mkdtempSync(join6(tmpdir(), "forgezero-operator-verify-"));
|
|
6514
|
+
try {
|
|
6515
|
+
const knownHosts = writeKnownHosts(request, directory);
|
|
6516
|
+
return await remote(exec, request, knownHosts, [
|
|
6517
|
+
"/usr/bin/sudo",
|
|
6518
|
+
"-n",
|
|
6519
|
+
"/usr/local/lib/forgezero/agent/fz-agent",
|
|
6520
|
+
"platform-fleet-verify",
|
|
6521
|
+
`--expected-version=${expectedVersion}`
|
|
6522
|
+
], "remote platform fleet verification");
|
|
6523
|
+
} finally {
|
|
6524
|
+
rmSync6(directory, { recursive: true, force: true });
|
|
6525
|
+
}
|
|
6526
|
+
}));
|
|
6527
|
+
const outcomes = settled.map((result, index) => result.status === "fulfilled" ? { computeReference: configs[index].computeReference, address: requests[index].target.address, ok: true, output: result.value.slice(0, 65536) } : {
|
|
6528
|
+
computeReference: configs[index].computeReference,
|
|
6529
|
+
address: requests[index].target.address,
|
|
6530
|
+
ok: false,
|
|
6531
|
+
error: (result.reason instanceof Error ? result.reason.message : String(result.reason)).slice(0, 4096)
|
|
6532
|
+
});
|
|
6533
|
+
return { plan, ok: outcomes.every(({ ok }) => ok), outcomes };
|
|
6534
|
+
}
|
|
6314
6535
|
async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
6315
6536
|
const plan = planOperatorMetalBootstrap(request, mode);
|
|
6316
6537
|
publicIdentity(request.target.identityPublicKeyFile);
|
|
@@ -6327,13 +6548,16 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
|
6327
6548
|
}
|
|
6328
6549
|
if (mode === "genesis" || mode === "rehearsal" || mode === "rehearsal-cleanup") {
|
|
6329
6550
|
const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
|
|
6551
|
+
if ((mode === "rehearsal" || mode === "rehearsal-cleanup") && !request.genesis.rehearsalNode) {
|
|
6552
|
+
throw new Error("the reviewed metal request has no rehearsal node");
|
|
6553
|
+
}
|
|
6330
6554
|
const outputs = [];
|
|
6331
|
-
const claims = mode === "rehearsal" || mode === "rehearsal-cleanup" ? mode === "rehearsal-cleanup" ? platformCommunityRehearsalDeletionClaims(
|
|
6555
|
+
const claims = mode === "rehearsal" || mode === "rehearsal-cleanup" ? mode === "rehearsal-cleanup" ? platformCommunityRehearsalDeletionClaims(request.genesis.nodes, request.genesis.rehearsalNode) : platformCommunityRehearsalClaims(request.genesis.nodes, request.genesis.rehearsalNode, keys) : platformGenesisClaims(request.genesis.nodes, keys);
|
|
6332
6556
|
for (const claim of claims) {
|
|
6333
6557
|
outputs.push(await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/local/bin/fz-agent", "metal-apply", "--claim=-"], `provision ${claim.spec.guestName}`, false, `${JSON.stringify(claim)}
|
|
6334
6558
|
`));
|
|
6335
6559
|
}
|
|
6336
|
-
return { plan, output: JSON.stringify({
|
|
6560
|
+
return { plan, output: JSON.stringify({ nodes: plan.genesisNodes, outputs }) };
|
|
6337
6561
|
}
|
|
6338
6562
|
const config = readMetalBootstrapConfig(request.metalConfigFile);
|
|
6339
6563
|
const staged = stageMetalConfig(config, directory);
|
|
@@ -6368,12 +6592,14 @@ export {
|
|
|
6368
6592
|
writeOperatorPlatformBootstrapRequest,
|
|
6369
6593
|
writeOperatorPlatformBootstrapFleetRequest,
|
|
6370
6594
|
writeOperatorMetalBootstrapRequest,
|
|
6595
|
+
verifyOperatorPlatformFleet,
|
|
6371
6596
|
readOperatorPlatformBootstrapRequest,
|
|
6372
6597
|
readOperatorPlatformBootstrapFleetRequest,
|
|
6373
6598
|
readOperatorMetalBootstrapRequest,
|
|
6374
6599
|
planOperatorPlatformFleetBootstrap,
|
|
6375
6600
|
planOperatorPlatformBootstrap,
|
|
6376
6601
|
planOperatorMetalBootstrap,
|
|
6602
|
+
operatorPlatformFleetCoordinates,
|
|
6377
6603
|
createOperatorSshHop,
|
|
6378
6604
|
applyOperatorPlatformFleetBootstrap,
|
|
6379
6605
|
applyOperatorPlatformBootstrap,
|