@forgezero/agent 0.1.64 → 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/cli/agent-install.d.ts +9 -0
- 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 +501 -258
- 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/agent-heartbeat.js
CHANGED
|
@@ -26,6 +26,7 @@ export interface BootstrapBundleCommandResult {
|
|
|
26
26
|
}
|
|
27
27
|
export type BootstrapBundleCommand = (argv: readonly string[]) => Promise<BootstrapBundleCommandResult>;
|
|
28
28
|
export declare function sha256File(path: string): Promise<string>;
|
|
29
|
+
export declare const bootstrapBundleBranch: (value: unknown) => string;
|
|
29
30
|
export declare function parseBootstrapBundleManifest(value: unknown): BootstrapBundleManifest;
|
|
30
31
|
export declare function readBootstrapBundle(bundlePath: string, manifestPath?: string): Promise<BootstrapBundleBuildResult>;
|
|
31
32
|
export declare function buildBootstrapBundle(input: BootstrapBundleBuildInput, exec?: BootstrapBundleCommand): Promise<BootstrapBundleBuildResult>;
|
package/dist/bootstrap-bundle.js
CHANGED
|
@@ -41,7 +41,7 @@ async function sha256File(path) {
|
|
|
41
41
|
});
|
|
42
42
|
return hash.digest("hex");
|
|
43
43
|
}
|
|
44
|
-
var
|
|
44
|
+
var bootstrapBundleBranch = (value) => {
|
|
45
45
|
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("-")) {
|
|
46
46
|
throw new Error("bootstrap bundle branch is malformed");
|
|
47
47
|
}
|
|
@@ -58,7 +58,7 @@ function parseBootstrapBundleManifest(value) {
|
|
|
58
58
|
if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
|
|
59
59
|
throw new Error("bootstrap bundle manifest format is unsupported");
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
bootstrapBundleBranch(source.branch);
|
|
62
62
|
if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
|
|
63
63
|
throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
|
|
64
64
|
}
|
|
@@ -103,7 +103,7 @@ async function buildBootstrapBundle(input, exec = run) {
|
|
|
103
103
|
const repositoryRoot = resolve(input.repositoryRoot);
|
|
104
104
|
const outputPath = resolve(input.outputPath);
|
|
105
105
|
const manifestPath = `${outputPath}.json`;
|
|
106
|
-
const branch =
|
|
106
|
+
const branch = bootstrapBundleBranch(input.branch);
|
|
107
107
|
if (!isAbsolute(input.outputPath) || outputPath !== input.outputPath) {
|
|
108
108
|
throw new Error("bootstrap bundle output must be a canonical absolute path");
|
|
109
109
|
}
|
|
@@ -159,6 +159,7 @@ export {
|
|
|
159
159
|
readBootstrapBundle,
|
|
160
160
|
parseBootstrapBundleManifest,
|
|
161
161
|
buildBootstrapBundle,
|
|
162
|
+
bootstrapBundleBranch,
|
|
162
163
|
MAX_BOOTSTRAP_BUNDLE_BYTES,
|
|
163
164
|
BOOTSTRAP_BUNDLE_KIND,
|
|
164
165
|
BOOTSTRAP_BUNDLE_FORMAT
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface PlatformBootstrapConfig {
|
|
|
13
13
|
kind: 'platform';
|
|
14
14
|
environment: BootstrapEnvironment;
|
|
15
15
|
profile: PlatformBootstrapProfile;
|
|
16
|
-
/** API-owned compute reference. Initial seed references
|
|
16
|
+
/** API-owned compute reference. Initial seed references come from the reviewed generic guest requests. */
|
|
17
17
|
computeReference: string;
|
|
18
18
|
nodeHostname: string;
|
|
19
19
|
apiUrl: string;
|
|
@@ -21,6 +21,7 @@ export interface PlatformBootstrapConfig {
|
|
|
21
21
|
bootstrapBundle: {
|
|
22
22
|
bundleFile: string;
|
|
23
23
|
manifestFile: string;
|
|
24
|
+
branch: string;
|
|
24
25
|
};
|
|
25
26
|
deployRoot?: string;
|
|
26
27
|
telemetryEndpoint: string;
|
|
@@ -70,6 +71,11 @@ export interface PlatformBootstrapConfig {
|
|
|
70
71
|
}
|
|
71
72
|
export interface EnrolledComputeActivationConfig {
|
|
72
73
|
kind: 'enrolled-compute';
|
|
74
|
+
operatingSystem: {
|
|
75
|
+
id: 'ubuntu';
|
|
76
|
+
version: '24.04' | '26.04';
|
|
77
|
+
architecture: 'x64';
|
|
78
|
+
};
|
|
73
79
|
apiUrl: string;
|
|
74
80
|
realm: string;
|
|
75
81
|
nodeHostname: string;
|
package/dist/bootstrap.js
CHANGED
|
@@ -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)
|
|
@@ -84,6 +84,15 @@ export interface InstallOptions {
|
|
|
84
84
|
nodeHostname?: string;
|
|
85
85
|
telemetryEndpoint?: string;
|
|
86
86
|
}
|
|
87
|
+
export type GitIdentityInstallOptions = Pick<InstallOptions, 'gitCredentialPath' | 'gitPublicKeyPath' | 'generateGitIdentity'>;
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the optional compatibility Git identity from the explicit install
|
|
90
|
+
* environment. An ordinary Agent install must not load a credential merely
|
|
91
|
+
* because the CLI knows its conventional pathname: systemd treats every
|
|
92
|
+
* LoadCredentialEncrypted entry as required and refuses to start when that
|
|
93
|
+
* file does not exist.
|
|
94
|
+
*/
|
|
95
|
+
export declare function resolveGitIdentityInstallOptions(environment: Readonly<Record<string, string | undefined>>): GitIdentityInstallOptions;
|
|
87
96
|
export declare function planInstall(options: InstallOptions): ProvisionPlan;
|
|
88
97
|
/** Execute the same ordered plan the control plane executes over SSH. */
|
|
89
98
|
export declare function applyPlan(plan: ProvisionPlan, run: (operation: CapabilityOperation | ProvisionOperation) => Promise<{
|
|
@@ -4,12 +4,20 @@ export declare const COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/commu
|
|
|
4
4
|
export declare const COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
5
5
|
export declare const COMMUNITY_CREDENTIAL_PARENT = "/etc/forgezero-rehearsal";
|
|
6
6
|
export declare const COMMUNITY_CREDENTIAL_ROOT = "/etc/forgezero-rehearsal/creds";
|
|
7
|
+
export interface CommunityRehearsalNode {
|
|
8
|
+
name: string;
|
|
9
|
+
address: string;
|
|
10
|
+
agency: boolean;
|
|
11
|
+
starter: boolean;
|
|
12
|
+
readPreferred: boolean;
|
|
13
|
+
}
|
|
7
14
|
export type CommunityRehearsalApiOperation = 'health' | 'init' | 'write' | 'read' | 'query' | 'cluster';
|
|
8
15
|
export type CommunityRehearsalHostRequest = {
|
|
9
16
|
action: 'prepare';
|
|
10
17
|
node: string;
|
|
11
18
|
address: string;
|
|
12
19
|
agency: boolean;
|
|
20
|
+
topology: readonly CommunityRehearsalNode[];
|
|
13
21
|
release: string;
|
|
14
22
|
archivePath: string;
|
|
15
23
|
archiveSha256: string;
|
|
@@ -17,12 +25,15 @@ export type CommunityRehearsalHostRequest = {
|
|
|
17
25
|
} | {
|
|
18
26
|
action: 'cleanup';
|
|
19
27
|
node: string;
|
|
28
|
+
topology: readonly CommunityRehearsalNode[];
|
|
20
29
|
} | {
|
|
21
30
|
action: 'database-enable' | 'api-enable' | 'database-ready' | 'starter-ready' | 'api-ready' | 'database-status' | 'api-status';
|
|
22
31
|
node: string;
|
|
32
|
+
topology: readonly CommunityRehearsalNode[];
|
|
23
33
|
} | {
|
|
24
34
|
action: 'api';
|
|
25
35
|
node: string;
|
|
36
|
+
topology: readonly CommunityRehearsalNode[];
|
|
26
37
|
operation: CommunityRehearsalApiOperation;
|
|
27
38
|
key?: string;
|
|
28
39
|
value?: string;
|
|
@@ -49,6 +60,7 @@ export type CommunityHostOperation = {
|
|
|
49
60
|
accepted?: readonly number[];
|
|
50
61
|
};
|
|
51
62
|
export declare function ensureCommunityRehearsalArango(execute?: SoftwareExec): Promise<void>;
|
|
63
|
+
export declare const validateCommunityRehearsalTopology: (value: unknown) => readonly CommunityRehearsalNode[];
|
|
52
64
|
export declare function parseCommunityRehearsalHostRequest(value: unknown): CommunityRehearsalHostRequest;
|
|
53
65
|
export declare function planCommunityRehearsalPrepare(request: Extract<CommunityRehearsalHostRequest, {
|
|
54
66
|
action: 'prepare';
|