@forgezero/agent 0.1.101 → 0.1.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.d.ts +6 -2
- package/dist/bootstrap.js +72 -6
- package/dist/fz-agent.js +1 -1
- package/dist/fz.js +172 -18
- package/dist/metal-bootstrap.d.ts +8 -0
- package/dist/metal-bootstrap.js +10 -3
- package/dist/operator-bootstrap.d.ts +10 -0
- package/dist/operator-bootstrap.js +127 -9
- package/dist/platform-bootstrap-runtime.d.ts +21 -1
- package/dist/platform-bootstrap-runtime.js +19 -1
- package/dist/platform-fleet-verification.js +21 -3
- package/dist/platform-genesis-config.d.ts +1 -1
- package/dist/platform-launch-profile.d.ts +5 -0
- package/dist/provision.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/fz.js
CHANGED
|
@@ -4841,7 +4841,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
4841
4841
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
4842
4842
|
|
|
4843
4843
|
// src/version.ts
|
|
4844
|
-
var VERSION2 = "0.1.
|
|
4844
|
+
var VERSION2 = "0.1.103";
|
|
4845
4845
|
|
|
4846
4846
|
// src/software.ts
|
|
4847
4847
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -12058,7 +12058,7 @@ function removeSession(api, realm, path = defaultSessionPath()) {
|
|
|
12058
12058
|
}
|
|
12059
12059
|
|
|
12060
12060
|
// src/bootstrap.ts
|
|
12061
|
-
import { createHash as createHash4, createHmac as createHmac2, randomBytes as randomBytes7 } from "crypto";
|
|
12061
|
+
import { createHash as createHash4, createHmac as createHmac2, createPrivateKey, randomBytes as randomBytes7 } from "crypto";
|
|
12062
12062
|
import {
|
|
12063
12063
|
chmodSync as chmodSync4,
|
|
12064
12064
|
existsSync as existsSync9,
|
|
@@ -12239,10 +12239,16 @@ var httpsOrigin = (name, raw) => {
|
|
|
12239
12239
|
};
|
|
12240
12240
|
var agentTelemetryOrigin = (raw) => raw === "http://127.0.0.1:4318" ? raw : httpsOrigin("agentOtlpEndpoint", raw);
|
|
12241
12241
|
function validatePlatformInitialInventory(value) {
|
|
12242
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes", "attestation", "deployment"].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)) {
|
|
12242
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "metalIdentity", "region", "computes", "attestation", "deployment"].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)) {
|
|
12243
12243
|
throw new Error("initial platform inventory coordinates are invalid");
|
|
12244
12244
|
}
|
|
12245
12245
|
safeAtom("initialInventory.region.label", value.region.label);
|
|
12246
|
+
if (value.metalIdentity !== undefined) {
|
|
12247
|
+
const identity = value.metalIdentity;
|
|
12248
|
+
if (!identity || typeof identity !== "object" || Array.isArray(identity) || Object.keys(identity).some((key) => !["nodeKey", "publicKeys"].includes(key)) || !/^[A-Za-z0-9_-]{43}$/.test(identity.nodeKey) || !identity.publicKeys || identity.publicKeys.ed25519 !== identity.nodeKey || !/^[A-Za-z0-9_-]{1,8192}$/.test(identity.publicKeys.mlDsa) || Object.keys(identity.publicKeys).some((key) => !["ed25519", "mlDsa"].includes(key))) {
|
|
12249
|
+
throw new Error("initial platform Metal identity is invalid");
|
|
12250
|
+
}
|
|
12251
|
+
}
|
|
12246
12252
|
if (value.region.city !== undefined)
|
|
12247
12253
|
safeAtom("initialInventory.region.city", value.region.city);
|
|
12248
12254
|
if (value.deployment !== undefined && (!value.deployment || typeof value.deployment !== "object" || Array.isArray(value.deployment) || Object.keys(value.deployment).some((key) => !["source", "branch", "revision", "bundleSha256"].includes(key)) || value.deployment.source !== "bootstrap-bundle" || !["dev", "main"].includes(value.deployment.branch) || !/^[a-f0-9]{40}$/.test(value.deployment.revision) || !/^[a-f0-9]{64}$/.test(value.deployment.bundleSha256)))
|
|
@@ -12277,6 +12283,7 @@ function validatePlatformInitialInventory(value) {
|
|
|
12277
12283
|
}
|
|
12278
12284
|
return {
|
|
12279
12285
|
metalHostname: value.metalHostname,
|
|
12286
|
+
...value.metalIdentity ? { metalIdentity: structuredClone(value.metalIdentity) } : {},
|
|
12280
12287
|
region: { ...value.region },
|
|
12281
12288
|
computes: value.computes.map((compute, index) => ({
|
|
12282
12289
|
...computes[index],
|
|
@@ -12330,6 +12337,11 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
12330
12337
|
} else if (input.email !== undefined) {
|
|
12331
12338
|
throw new Error("Bootstrap email provider must be smtp or jetemail.");
|
|
12332
12339
|
}
|
|
12340
|
+
if (input.githubApp) {
|
|
12341
|
+
if (!/^(?:Iv1\.[A-Fa-f0-9]{16}|Ov23li[A-Za-z0-9]{14,})$/.test(input.githubApp.clientId) || !/^[1-9][0-9]{0,19}$/.test(input.githubApp.appId) || !/^[a-z0-9][a-z0-9-]{0,99}$/.test(input.githubApp.slug)) {
|
|
12342
|
+
throw new Error("GitHub App client id, app id or slug is malformed.");
|
|
12343
|
+
}
|
|
12344
|
+
}
|
|
12333
12345
|
boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
|
|
12334
12346
|
if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
|
|
12335
12347
|
throw new Error("seedSyncMembers must contain 3 to 64 unique physical host identities.");
|
|
@@ -12460,6 +12472,9 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
12460
12472
|
FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
|
|
12461
12473
|
FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
|
|
12462
12474
|
FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
|
|
12475
|
+
FZ_GITHUB_CLIENT_ID: value.githubApp?.clientId ?? "",
|
|
12476
|
+
FZ_GITHUB_APP_ID: value.githubApp?.appId ?? "",
|
|
12477
|
+
FZ_GITHUB_APP_SLUG: value.githubApp?.slug ?? "",
|
|
12463
12478
|
FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
|
|
12464
12479
|
};
|
|
12465
12480
|
return `# Generated by fz bootstrap platform. Non-secret coordinates only.
|
|
@@ -12471,6 +12486,9 @@ function platformApiCredentialSpecs(options) {
|
|
|
12471
12486
|
const optional = [
|
|
12472
12487
|
["fz_smtp.password", options.emailProvider === "smtp"],
|
|
12473
12488
|
["fz_jetemail.apiKey", options.emailProvider === "jetemail"],
|
|
12489
|
+
["fz_github.clientSecret", options.githubApp],
|
|
12490
|
+
["fz_github.privateKey", options.githubApp],
|
|
12491
|
+
["fz_github.webhookSecret", options.githubApp],
|
|
12474
12492
|
["CF_API_TOKEN", options.cloudflareKv],
|
|
12475
12493
|
["CF_TUNNEL_TOKEN", options.cloudflareKv],
|
|
12476
12494
|
["REALTIME_PUBLISH_SECRET", options.realtime],
|
|
@@ -14102,7 +14120,17 @@ function validatePlatformBootstrapSecrets(config, input) {
|
|
|
14102
14120
|
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
14103
14121
|
throw new Error("bootstrap credential input must be an object");
|
|
14104
14122
|
const source = input;
|
|
14105
|
-
const allowed = [
|
|
14123
|
+
const allowed = [
|
|
14124
|
+
"clusterBootstrapCode",
|
|
14125
|
+
"emailSecret",
|
|
14126
|
+
"enrolmentToken",
|
|
14127
|
+
"backupS3Secret",
|
|
14128
|
+
"cloudflareTunnelToken",
|
|
14129
|
+
"cloudflareApiToken",
|
|
14130
|
+
"githubClientSecret",
|
|
14131
|
+
"githubPrivateKeyBase64",
|
|
14132
|
+
"githubWebhookSecret"
|
|
14133
|
+
];
|
|
14106
14134
|
const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
|
|
14107
14135
|
if (unknown.length)
|
|
14108
14136
|
throw new Error(`bootstrap credential input contains unsupported field ${unknown[0]}`);
|
|
@@ -14112,6 +14140,9 @@ function validatePlatformBootstrapSecrets(config, input) {
|
|
|
14112
14140
|
const backupS3Secret = typeof source.backupS3Secret === "string" ? source.backupS3Secret.trim() : undefined;
|
|
14113
14141
|
const cloudflareTunnelToken = typeof source.cloudflareTunnelToken === "string" ? source.cloudflareTunnelToken.trim() : undefined;
|
|
14114
14142
|
const cloudflareApiToken = typeof source.cloudflareApiToken === "string" ? source.cloudflareApiToken.trim() : undefined;
|
|
14143
|
+
const githubClientSecret = typeof source.githubClientSecret === "string" ? source.githubClientSecret.trim() : undefined;
|
|
14144
|
+
const githubPrivateKeyBase64 = typeof source.githubPrivateKeyBase64 === "string" ? source.githubPrivateKeyBase64.trim() : undefined;
|
|
14145
|
+
const githubWebhookSecret = typeof source.githubWebhookSecret === "string" ? source.githubWebhookSecret.trim() : undefined;
|
|
14115
14146
|
if (!/^[a-f0-9]{64}$/i.test(clusterBootstrapCode))
|
|
14116
14147
|
throw new Error("cluster bootstrap code must contain exactly 64 hexadecimal characters");
|
|
14117
14148
|
if (!emailSecret || emailSecret.length > 16384 || /[\r\n\0]/.test(emailSecret))
|
|
@@ -14131,12 +14162,29 @@ function validatePlatformBootstrapSecrets(config, input) {
|
|
|
14131
14162
|
}
|
|
14132
14163
|
if (cloudflareTunnelToken)
|
|
14133
14164
|
validateCloudflareBootstrapSecretPair({ cloudflareTunnelToken, cloudflareApiToken });
|
|
14165
|
+
const githubConfigured = Boolean(config.runtime.environment.githubApp);
|
|
14166
|
+
if (githubConfigured !== Boolean(githubClientSecret && githubPrivateKeyBase64 && githubWebhookSecret)) {
|
|
14167
|
+
throw new Error("GitHub App coordinates require client secret, private key and webhook secret together");
|
|
14168
|
+
}
|
|
14169
|
+
if (githubConfigured) {
|
|
14170
|
+
if (githubClientSecret.length < 20 || githubClientSecret.length > 512 || /[\r\n\0]/.test(githubClientSecret) || githubWebhookSecret.length < 32 || githubWebhookSecret.length > 512 || /[\r\n\0]/.test(githubWebhookSecret)) {
|
|
14171
|
+
throw new Error("GitHub App client or webhook secret is malformed");
|
|
14172
|
+
}
|
|
14173
|
+
try {
|
|
14174
|
+
const pem = Buffer.from(githubPrivateKeyBase64, "base64").toString("utf8");
|
|
14175
|
+
if (createPrivateKey(pem).asymmetricKeyType !== "rsa")
|
|
14176
|
+
throw new Error("not RSA");
|
|
14177
|
+
} catch {
|
|
14178
|
+
throw new Error("GitHub App private key must be a base64-encoded RSA private key");
|
|
14179
|
+
}
|
|
14180
|
+
}
|
|
14134
14181
|
return {
|
|
14135
14182
|
clusterBootstrapCode,
|
|
14136
14183
|
emailSecret,
|
|
14137
14184
|
...enrolmentToken ? { enrolmentToken } : {},
|
|
14138
14185
|
...backupS3Secret ? { backupS3Secret } : {},
|
|
14139
|
-
...cloudflareTunnelToken ? { cloudflareTunnelToken, cloudflareApiToken } : {}
|
|
14186
|
+
...cloudflareTunnelToken ? { cloudflareTunnelToken, cloudflareApiToken } : {},
|
|
14187
|
+
...githubConfigured ? { githubClientSecret, githubPrivateKeyBase64, githubWebhookSecret } : {}
|
|
14140
14188
|
};
|
|
14141
14189
|
}
|
|
14142
14190
|
var platformBootstrapRunner = (config) => config.kind === "platform" && config.database.role === "master";
|
|
@@ -14688,6 +14736,17 @@ function approvedPlatformRepairIdentityDigests(config) {
|
|
|
14688
14736
|
delete beforeAttestation.runtime.environment.initialInventory.attestation;
|
|
14689
14737
|
prior.push(beforeAttestation);
|
|
14690
14738
|
}
|
|
14739
|
+
const metalIdentity = config.runtime.environment.initialInventory?.metalIdentity;
|
|
14740
|
+
if (metalIdentity && config.enrolment.source === "genesis-derived") {
|
|
14741
|
+
const beforeMetalIdentity = structuredClone(config);
|
|
14742
|
+
delete beforeMetalIdentity.runtime.environment.initialInventory.metalIdentity;
|
|
14743
|
+
prior.push(beforeMetalIdentity);
|
|
14744
|
+
}
|
|
14745
|
+
if (config.database.role === "joiner" && config.enrolment.source === "genesis-derived" && config.runtime.environment.custodianEmail) {
|
|
14746
|
+
const beforeReplicaCustodianEmail = structuredClone(config);
|
|
14747
|
+
delete beforeReplicaCustodianEmail.runtime.environment.custodianEmail;
|
|
14748
|
+
prior.push(beforeReplicaCustodianEmail);
|
|
14749
|
+
}
|
|
14691
14750
|
const telemetryMigration = config.environment === "development" && config.telemetryEndpoint === LOCAL_DEBUG_OTEL_EXPORT && config.runtime.environment.agentOtlpEndpoint === "http://127.0.0.1:4318";
|
|
14692
14751
|
if (telemetryMigration) {
|
|
14693
14752
|
const beforeTelemetry = structuredClone(config);
|
|
@@ -15040,7 +15099,10 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
15040
15099
|
enrolmentToken: checked4.enrolmentToken,
|
|
15041
15100
|
backup: checked4.backupS3Secret,
|
|
15042
15101
|
cloudflareTunnelToken: checked4.cloudflareTunnelToken,
|
|
15043
|
-
cloudflareApiToken: checked4.cloudflareApiToken
|
|
15102
|
+
cloudflareApiToken: checked4.cloudflareApiToken,
|
|
15103
|
+
githubClientSecret: checked4.githubClientSecret,
|
|
15104
|
+
githubPrivateKey: checked4.githubPrivateKeyBase64 ? Buffer.from(checked4.githubPrivateKeyBase64, "base64").toString("utf8") : undefined,
|
|
15105
|
+
githubWebhookSecret: checked4.githubWebhookSecret
|
|
15044
15106
|
};
|
|
15045
15107
|
})() : undefined;
|
|
15046
15108
|
const enrolledPrivate = config.kind === "enrolled-compute" ? validateEnrolledComputeBootstrapSecrets(config, secrets) : undefined;
|
|
@@ -15138,6 +15200,9 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
15138
15200
|
const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
|
|
15139
15201
|
for (const [name, source] of Object.entries({
|
|
15140
15202
|
...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
|
|
15203
|
+
"fz_github.clientSecret": platformPrivate.githubClientSecret,
|
|
15204
|
+
"fz_github.privateKey": platformPrivate.githubPrivateKey,
|
|
15205
|
+
"fz_github.webhookSecret": platformPrivate.githubWebhookSecret,
|
|
15141
15206
|
"backup.s3.secretAccessKey": platformPrivate.backup
|
|
15142
15207
|
})) {
|
|
15143
15208
|
if (source) {
|
|
@@ -15154,6 +15219,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
15154
15219
|
const credentials = platformApiCredentialSpecs({
|
|
15155
15220
|
emailProvider: runtime.environment.email?.provider,
|
|
15156
15221
|
cloudflareKv: cloudflareConfigured,
|
|
15222
|
+
githubApp: Boolean(runtime.environment.githubApp),
|
|
15157
15223
|
realtime: Boolean(runtime.environment.realtime)
|
|
15158
15224
|
});
|
|
15159
15225
|
const units = renderPlatformApiUnits({
|
|
@@ -16151,7 +16217,7 @@ var SUPPORTED_BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dc
|
|
|
16151
16217
|
|
|
16152
16218
|
class MetalBootstrapError extends Error {
|
|
16153
16219
|
}
|
|
16154
|
-
function
|
|
16220
|
+
function parseMetalPublicIdentity(value) {
|
|
16155
16221
|
let parsed;
|
|
16156
16222
|
try {
|
|
16157
16223
|
parsed = JSON.parse(value);
|
|
@@ -16166,7 +16232,13 @@ function normalizeMetalPublicIdentity(value) {
|
|
|
16166
16232
|
if (Object.keys(identity).some((key) => !["nodeKey", "publicKeys"].includes(key)) || typeof identity.nodeKey !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(identity.nodeKey) || !keys || Object.keys(keys).some((key) => !["ed25519", "mlDsa"].includes(key)) || keys.ed25519 !== identity.nodeKey || typeof keys.mlDsa !== "string" || !/^[A-Za-z0-9_-]{1,8192}$/.test(keys.mlDsa)) {
|
|
16167
16233
|
throw new MetalBootstrapError("metal public identity proof was invalid");
|
|
16168
16234
|
}
|
|
16169
|
-
return
|
|
16235
|
+
return {
|
|
16236
|
+
nodeKey: identity.nodeKey,
|
|
16237
|
+
publicKeys: { ed25519: keys.ed25519, mlDsa: keys.mlDsa }
|
|
16238
|
+
};
|
|
16239
|
+
}
|
|
16240
|
+
function normalizeMetalPublicIdentity(value) {
|
|
16241
|
+
return JSON.stringify(parseMetalPublicIdentity(value));
|
|
16170
16242
|
}
|
|
16171
16243
|
var defaultExec2 = async (argv2, stdin) => {
|
|
16172
16244
|
const child = Bun.spawn([...argv2], {
|
|
@@ -17056,7 +17128,7 @@ function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles, cloudfla
|
|
|
17056
17128
|
return writeOwnerJson2(path, request, "operator fleet request");
|
|
17057
17129
|
}
|
|
17058
17130
|
var validateMetalRequestValue = (value, options = {}) => {
|
|
17059
|
-
const root = exactKeys5(value, ["kind", "target", "metalConfigFile", "genesis"], "operator metal request");
|
|
17131
|
+
const root = exactKeys5(value, ["kind", "target", "metalConfigFile", "metalIdentityEvidenceFile", "genesis"], "operator metal request");
|
|
17060
17132
|
if (root.kind !== "metal-remote")
|
|
17061
17133
|
throw new Error("operator metal bootstrap kind must be metal-remote");
|
|
17062
17134
|
const targetValue = exactKeys5(root.target, ["address", "port", "user", "hostKey", "hostKeySha256", "identityPublicKeyFile", "agentSocket"], "operator metal target");
|
|
@@ -17075,6 +17147,12 @@ var validateMetalRequestValue = (value, options = {}) => {
|
|
|
17075
17147
|
if (typeof root.metalConfigFile !== "string")
|
|
17076
17148
|
throw new Error("metalConfigFile must be an absolute path");
|
|
17077
17149
|
canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
|
|
17150
|
+
if (root.metalIdentityEvidenceFile !== undefined && typeof root.metalIdentityEvidenceFile !== "string") {
|
|
17151
|
+
throw new Error("metalIdentityEvidenceFile must be an absolute path");
|
|
17152
|
+
}
|
|
17153
|
+
if (typeof root.metalIdentityEvidenceFile === "string") {
|
|
17154
|
+
canonicalOutputPath(root.metalIdentityEvidenceFile, "metalIdentityEvidenceFile");
|
|
17155
|
+
}
|
|
17078
17156
|
if (options.validateMetalConfig !== false)
|
|
17079
17157
|
readMetalBootstrapConfig(root.metalConfigFile);
|
|
17080
17158
|
const genesis = exactKeys5(root.genesis, ["nodes", "rehearsalNode", "sshPublicKeyFiles"], "operator metal genesis");
|
|
@@ -17123,6 +17201,7 @@ var validateMetalRequestValue = (value, options = {}) => {
|
|
|
17123
17201
|
return {
|
|
17124
17202
|
kind: "metal-remote",
|
|
17125
17203
|
metalConfigFile: root.metalConfigFile,
|
|
17204
|
+
...typeof root.metalIdentityEvidenceFile === "string" ? { metalIdentityEvidenceFile: root.metalIdentityEvidenceFile } : {},
|
|
17126
17205
|
target: {
|
|
17127
17206
|
...target,
|
|
17128
17207
|
identityPublicKeyFile: targetValue.identityPublicKeyFile,
|
|
@@ -17143,6 +17222,25 @@ function writeOperatorMetalBootstrapRequest(path, request) {
|
|
|
17143
17222
|
validateMetalRequestValue(request);
|
|
17144
17223
|
return writeOwnerJson2(path, request, "operator metal request");
|
|
17145
17224
|
}
|
|
17225
|
+
function readOperatorMetalIdentityEvidence(path, request) {
|
|
17226
|
+
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal identity evidence")));
|
|
17227
|
+
const root = exactKeys5(value, ["format", "kind", "metalHostname", "metalHostKeySha256", "nodeKey", "publicKeys"], "operator metal identity evidence");
|
|
17228
|
+
if (root.format !== 1 || root.kind !== "forgezero-operator-metal-identity" || typeof root.metalHostname !== "string" || typeof root.metalHostKeySha256 !== "string" || typeof root.nodeKey !== "string" || !root.publicKeys || typeof root.publicKeys !== "object") {
|
|
17229
|
+
throw new Error("operator metal identity evidence is malformed");
|
|
17230
|
+
}
|
|
17231
|
+
const identity = parseMetalPublicIdentity(JSON.stringify({ nodeKey: root.nodeKey, publicKeys: root.publicKeys }));
|
|
17232
|
+
const config = readMetalBootstrapConfig(request.metalConfigFile, { allowHistoricalRelease: true });
|
|
17233
|
+
if (root.metalHostname !== config.metalHostname || root.metalHostKeySha256 !== request.target.hostKeySha256) {
|
|
17234
|
+
throw new Error("operator metal identity evidence does not match the reviewed Metal target");
|
|
17235
|
+
}
|
|
17236
|
+
return {
|
|
17237
|
+
format: 1,
|
|
17238
|
+
kind: "forgezero-operator-metal-identity",
|
|
17239
|
+
metalHostname: root.metalHostname,
|
|
17240
|
+
metalHostKeySha256: root.metalHostKeySha256,
|
|
17241
|
+
...identity
|
|
17242
|
+
};
|
|
17243
|
+
}
|
|
17146
17244
|
var validateGuestHostKeyEvidenceValue = (value, request) => {
|
|
17147
17245
|
const root = exactKeys5(value, ["format", "kind", "metalHostKeySha256", "nodes"], "operator guest host-key evidence");
|
|
17148
17246
|
if (root.format !== 1 || root.kind !== "forgezero-operator-guest-host-keys" || typeof root.metalHostKeySha256 !== "string" || !Array.isArray(root.nodes)) {
|
|
@@ -17828,6 +17926,25 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
|
17828
17926
|
`${REMOTE_STAGE}/metal-config.json`,
|
|
17829
17927
|
"--apply"
|
|
17830
17928
|
], "remote typed metal bootstrap");
|
|
17929
|
+
if (request.metalIdentityEvidenceFile) {
|
|
17930
|
+
let applied;
|
|
17931
|
+
try {
|
|
17932
|
+
applied = JSON.parse(output);
|
|
17933
|
+
} catch {
|
|
17934
|
+
throw new Error("remote Metal bootstrap did not return identity evidence");
|
|
17935
|
+
}
|
|
17936
|
+
const publicIdentity2 = applied?.publicIdentity;
|
|
17937
|
+
if (typeof publicIdentity2 !== "string")
|
|
17938
|
+
throw new Error("remote Metal bootstrap omitted its public identity");
|
|
17939
|
+
const identity = parseMetalPublicIdentity(publicIdentity2);
|
|
17940
|
+
writeOwnerJson2(request.metalIdentityEvidenceFile, {
|
|
17941
|
+
format: 1,
|
|
17942
|
+
kind: "forgezero-operator-metal-identity",
|
|
17943
|
+
metalHostname: config.metalHostname,
|
|
17944
|
+
metalHostKeySha256: request.target.hostKeySha256,
|
|
17945
|
+
...identity
|
|
17946
|
+
}, "operator metal identity evidence");
|
|
17947
|
+
}
|
|
17831
17948
|
return { plan, output };
|
|
17832
17949
|
} finally {
|
|
17833
17950
|
if (knownHosts) {
|
|
@@ -17881,7 +17998,7 @@ async function applyAttendedPlatformBootstrap(fleetRequest, cloudflareRequest, a
|
|
|
17881
17998
|
}
|
|
17882
17999
|
|
|
17883
18000
|
// src/platform-genesis-config.ts
|
|
17884
|
-
function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname, region, deployment, attestation) {
|
|
18001
|
+
function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname, region, deployment, attestation, metalIdentity) {
|
|
17885
18002
|
const fleet = validatePlatformGenesisGuests(guests);
|
|
17886
18003
|
if (!/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(metalHostname)) {
|
|
17887
18004
|
throw new Error("platform genesis requires the reviewed metal hostname");
|
|
@@ -17897,6 +18014,7 @@ function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname,
|
|
|
17897
18014
|
const coordinators = fleet.map((guest) => `http://${guest.address}:8529`);
|
|
17898
18015
|
const initialInventory = {
|
|
17899
18016
|
metalHostname,
|
|
18017
|
+
...metalIdentity ? { metalIdentity: structuredClone(metalIdentity) } : {},
|
|
17900
18018
|
region: {
|
|
17901
18019
|
key: template.runtime.environment.nodeRegion,
|
|
17902
18020
|
label: region.label,
|
|
@@ -17946,7 +18064,7 @@ function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname,
|
|
|
17946
18064
|
seedSyncPeers: fleet.filter((_, peer) => peer !== index).flatMap((peer) => [config.runtime.bluePort, config.runtime.greenPort].map((port) => `ws://${peer.address}:${port}/_internal/forgezero/seed-mesh/v2`)),
|
|
17947
18065
|
seedSyncMembers: nodes.map(({ nodeHostname }) => nodeHostname),
|
|
17948
18066
|
initialInventory,
|
|
17949
|
-
custodianEmail:
|
|
18067
|
+
custodianEmail: template.runtime.environment.custodianEmail
|
|
17950
18068
|
};
|
|
17951
18069
|
return validateBootstrapConfig(config);
|
|
17952
18070
|
});
|
|
@@ -18060,6 +18178,7 @@ function forgeZeroLaunchTemplate(profile, guests, bundle, owner) {
|
|
|
18060
18178
|
otlpTraceSampleRatio: 0.1,
|
|
18061
18179
|
custodianEmail: owner.custodianEmail,
|
|
18062
18180
|
email: owner.email,
|
|
18181
|
+
...owner.githubApp ? { githubApp: owner.githubApp } : {},
|
|
18063
18182
|
deployProfile: profile.environment
|
|
18064
18183
|
},
|
|
18065
18184
|
serviceUser: "forgezero-api",
|
|
@@ -19262,13 +19381,20 @@ function readPlatformLaunchEnvironment(path) {
|
|
|
19262
19381
|
if (smtpPort !== undefined && (!Number.isInteger(smtpPort) || smtpPort < 1 || smtpPort > 65535)) {
|
|
19263
19382
|
throw new Error("FZ_SMTP_PORT must be an integer from 1 through 65535");
|
|
19264
19383
|
}
|
|
19384
|
+
const githubEnabled = (values.get("FZ_GITHUB_ENABLED") ?? "false").trim().toLowerCase() === "true";
|
|
19385
|
+
const githubApp = githubEnabled ? {
|
|
19386
|
+
clientId: requiredValue("FZ_GITHUB_CLIENT_ID"),
|
|
19387
|
+
appId: requiredValue("FZ_GITHUB_APP_ID"),
|
|
19388
|
+
slug: requiredValue("FZ_GITHUB_APP_SLUG")
|
|
19389
|
+
} : undefined;
|
|
19265
19390
|
const owner = provider === "jetemail" ? {
|
|
19266
19391
|
custodianEmail: requiredValue("FZ_BOOTSTRAP_CUSTODIAN_EMAIL"),
|
|
19267
19392
|
email: {
|
|
19268
19393
|
provider,
|
|
19269
19394
|
from: requiredValue("FZ_EMAIL_FROM"),
|
|
19270
19395
|
eu: (values.get("FZ_JETEMAIL_EU") ?? "false").trim().toLowerCase() === "true"
|
|
19271
|
-
}
|
|
19396
|
+
},
|
|
19397
|
+
...githubApp ? { githubApp } : {}
|
|
19272
19398
|
} : {
|
|
19273
19399
|
custodianEmail: requiredValue("FZ_BOOTSTRAP_CUSTODIAN_EMAIL"),
|
|
19274
19400
|
email: {
|
|
@@ -19277,13 +19403,19 @@ function readPlatformLaunchEnvironment(path) {
|
|
|
19277
19403
|
host: requiredValue("FZ_SMTP_HOST"),
|
|
19278
19404
|
port: smtpPort,
|
|
19279
19405
|
user: requiredValue("FZ_SMTP_USER")
|
|
19280
|
-
}
|
|
19406
|
+
},
|
|
19407
|
+
...githubApp ? { githubApp } : {}
|
|
19281
19408
|
};
|
|
19282
19409
|
return {
|
|
19283
19410
|
owner,
|
|
19284
19411
|
emailSecret: requiredValue(provider === "jetemail" ? "FZ_JETEMAIL_API_KEY" : "FZ_SMTP_PASSWORD"),
|
|
19285
19412
|
cloudflareTunnelToken: requiredValue("CF_TUNNEL_TOKEN"),
|
|
19286
|
-
cloudflareApiToken: requiredValue("CF_API_TOKEN")
|
|
19413
|
+
cloudflareApiToken: requiredValue("CF_API_TOKEN"),
|
|
19414
|
+
...githubEnabled ? {
|
|
19415
|
+
githubClientSecret: requiredValue("FZ_GITHUB_CLIENT_SECRET"),
|
|
19416
|
+
githubPrivateKeyBase64: requiredValue("FZ_GITHUB_PRIVATE_KEY_BASE64"),
|
|
19417
|
+
githubWebhookSecret: requiredValue("FZ_GITHUB_WEBHOOK_SECRET")
|
|
19418
|
+
} : {}
|
|
19287
19419
|
};
|
|
19288
19420
|
}
|
|
19289
19421
|
async function promptPlatformBootstrapSecrets(config, generatedClusterBootstrapCode) {
|
|
@@ -19295,6 +19427,11 @@ async function promptPlatformBootstrapSecrets(config, generatedClusterBootstrapC
|
|
|
19295
19427
|
...config.cloudflareHandoff || config.runtime.environment.cloudflare ? {
|
|
19296
19428
|
cloudflareTunnelToken: await bootstrapSecret("CF_TUNNEL_TOKEN"),
|
|
19297
19429
|
cloudflareApiToken: await bootstrapSecret("CF_API_TOKEN")
|
|
19430
|
+
} : {},
|
|
19431
|
+
...config.runtime.environment.githubApp ? {
|
|
19432
|
+
githubClientSecret: await bootstrapSecret("GitHub App client secret"),
|
|
19433
|
+
githubPrivateKeyBase64: await bootstrapSecret("GitHub App RSA private key (base64)"),
|
|
19434
|
+
githubWebhookSecret: await bootstrapSecret("GitHub App webhook secret")
|
|
19298
19435
|
} : {}
|
|
19299
19436
|
});
|
|
19300
19437
|
}
|
|
@@ -19476,6 +19613,7 @@ function writeMetalOperatorFiles(directory) {
|
|
|
19476
19613
|
const remoteRequest = writeOperatorMetalBootstrapRequest(join13(output, "metal-remote.json"), {
|
|
19477
19614
|
kind: "metal-remote",
|
|
19478
19615
|
metalConfigFile: metalConfig,
|
|
19616
|
+
metalIdentityEvidenceFile: join13(output, "metal-identity.json"),
|
|
19479
19617
|
target: { ...target, ...identity },
|
|
19480
19618
|
genesis: { ...interactiveGenesisGuests(), sshPublicKeyFiles: [identity.identityPublicKeyFile] }
|
|
19481
19619
|
});
|
|
@@ -19496,6 +19634,10 @@ function writePlatformGenesisFleet(directory, preset) {
|
|
|
19496
19634
|
const output = genesisOutputDirectory(directory);
|
|
19497
19635
|
const metalRequestFile = preset?.metalRequestFile ?? bootstrapAnswer("Owner-only metal remote request containing the reviewed genesis guests");
|
|
19498
19636
|
const metalRequest = readOperatorMetalBootstrapRequest(metalRequestFile, { validateMetalConfig: false });
|
|
19637
|
+
if (!metalRequest.metalIdentityEvidenceFile) {
|
|
19638
|
+
throw new Error("the reviewed Metal request predates automatic identity seeding; rerun Metal apply with a generated request");
|
|
19639
|
+
}
|
|
19640
|
+
const metalIdentity = readOperatorMetalIdentityEvidence(metalRequest.metalIdentityEvidenceFile, metalRequest);
|
|
19499
19641
|
const guestHostKeysFile = preset?.guestHostKeysFile ?? bootstrapAnswer("Owner-only guest host-key evidence file");
|
|
19500
19642
|
const guestHostKeys = readOperatorGuestHostKeyEvidence(guestHostKeysFile, metalRequest);
|
|
19501
19643
|
const fleet = metalRequest.genesis.nodes;
|
|
@@ -19540,7 +19682,7 @@ function writePlatformGenesisFleet(directory, preset) {
|
|
|
19540
19682
|
branch: preset.profile.branch,
|
|
19541
19683
|
revision: preset.bundle.manifest.revision,
|
|
19542
19684
|
bundleSha256: preset.bundle.manifest.sha256
|
|
19543
|
-
} : undefined, preset?.profile.attestation).map((config) => {
|
|
19685
|
+
} : undefined, preset?.profile.attestation, { nodeKey: metalIdentity.nodeKey, publicKeys: { ...metalIdentity.publicKeys } }).map((config) => {
|
|
19544
19686
|
const path = `${output}/${config.computeReference}-platform.json`;
|
|
19545
19687
|
if (preset)
|
|
19546
19688
|
writeDerivedLaunchFile(path, (temporary) => writeBootstrapConfig(temporary, config));
|
|
@@ -19594,10 +19736,16 @@ function forgeZeroLaunchOwnerInput() {
|
|
|
19594
19736
|
throw new Error("Bootstrap email provider must be smtp or jetemail");
|
|
19595
19737
|
}
|
|
19596
19738
|
const from = bootstrapAnswer("Bootstrap email From address");
|
|
19739
|
+
const githubApp = bootstrapBoolean("Configure the platform GitHub App now?", "no") ? {
|
|
19740
|
+
clientId: bootstrapAnswer("GitHub App client ID"),
|
|
19741
|
+
appId: bootstrapAnswer("GitHub App ID"),
|
|
19742
|
+
slug: bootstrapAnswer("GitHub App slug")
|
|
19743
|
+
} : undefined;
|
|
19597
19744
|
if (provider === "jetemail") {
|
|
19598
19745
|
return {
|
|
19599
19746
|
custodianEmail,
|
|
19600
|
-
email: { provider, from, eu: bootstrapBoolean("Use JetEmail EU processing?", "no") }
|
|
19747
|
+
email: { provider, from, eu: bootstrapBoolean("Use JetEmail EU processing?", "no") },
|
|
19748
|
+
...githubApp ? { githubApp } : {}
|
|
19601
19749
|
};
|
|
19602
19750
|
}
|
|
19603
19751
|
return {
|
|
@@ -19608,7 +19756,8 @@ function forgeZeroLaunchOwnerInput() {
|
|
|
19608
19756
|
host: bootstrapAnswer("SMTP host"),
|
|
19609
19757
|
port: bootstrapNumber("SMTP port", "587"),
|
|
19610
19758
|
user: bootstrapAnswer("SMTP user")
|
|
19611
|
-
}
|
|
19759
|
+
},
|
|
19760
|
+
...githubApp ? { githubApp } : {}
|
|
19612
19761
|
};
|
|
19613
19762
|
}
|
|
19614
19763
|
async function prepareForgeZeroPlatformLaunch(environment, outputDirectory, projectRoot, owner) {
|
|
@@ -19793,7 +19942,12 @@ async function runAttendedPlatformFleet(fleet, apply, generatedClusterBootstrapC
|
|
|
19793
19942
|
clusterBootstrapCode: generatedClusterBootstrapCode,
|
|
19794
19943
|
emailSecret: environmentInput.emailSecret,
|
|
19795
19944
|
cloudflareTunnelToken: environmentInput.cloudflareTunnelToken,
|
|
19796
|
-
cloudflareApiToken: environmentInput.cloudflareApiToken
|
|
19945
|
+
cloudflareApiToken: environmentInput.cloudflareApiToken,
|
|
19946
|
+
...environmentInput.githubClientSecret ? {
|
|
19947
|
+
githubClientSecret: environmentInput.githubClientSecret,
|
|
19948
|
+
githubPrivateKeyBase64: environmentInput.githubPrivateKeyBase64,
|
|
19949
|
+
githubWebhookSecret: environmentInput.githubWebhookSecret
|
|
19950
|
+
} : {}
|
|
19797
19951
|
}) : await promptPlatformBootstrapSecrets(firstConfig, generatedClusterBootstrapCode);
|
|
19798
19952
|
const cloudflare = await resolveCloudflareBootstrapCommandConfig(fleet.cloudflareConfigFile, {
|
|
19799
19953
|
tunnelToken: secrets.cloudflareTunnelToken,
|
|
@@ -43,6 +43,14 @@ export interface MetalBootstrapApplyOptions {
|
|
|
43
43
|
}
|
|
44
44
|
export declare class MetalBootstrapError extends Error {
|
|
45
45
|
}
|
|
46
|
+
export interface MetalPublicIdentity {
|
|
47
|
+
nodeKey: string;
|
|
48
|
+
publicKeys: {
|
|
49
|
+
ed25519: string;
|
|
50
|
+
mlDsa: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export declare function parseMetalPublicIdentity(value: string): MetalPublicIdentity;
|
|
46
54
|
export declare function normalizeMetalPublicIdentity(value: string): string;
|
|
47
55
|
export interface MetalBootstrapValidationOptions {
|
|
48
56
|
/**
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
// src/version.ts
|
|
369
|
-
var VERSION = "0.1.
|
|
369
|
+
var VERSION = "0.1.103";
|
|
370
370
|
|
|
371
371
|
// src/otel-collector.ts
|
|
372
372
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -543,7 +543,7 @@ var SUPPORTED_BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dc
|
|
|
543
543
|
|
|
544
544
|
class MetalBootstrapError extends Error {
|
|
545
545
|
}
|
|
546
|
-
function
|
|
546
|
+
function parseMetalPublicIdentity(value) {
|
|
547
547
|
let parsed;
|
|
548
548
|
try {
|
|
549
549
|
parsed = JSON.parse(value);
|
|
@@ -558,7 +558,13 @@ function normalizeMetalPublicIdentity(value) {
|
|
|
558
558
|
if (Object.keys(identity).some((key) => !["nodeKey", "publicKeys"].includes(key)) || typeof identity.nodeKey !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(identity.nodeKey) || !keys || Object.keys(keys).some((key) => !["ed25519", "mlDsa"].includes(key)) || keys.ed25519 !== identity.nodeKey || typeof keys.mlDsa !== "string" || !/^[A-Za-z0-9_-]{1,8192}$/.test(keys.mlDsa)) {
|
|
559
559
|
throw new MetalBootstrapError("metal public identity proof was invalid");
|
|
560
560
|
}
|
|
561
|
-
return
|
|
561
|
+
return {
|
|
562
|
+
nodeKey: identity.nodeKey,
|
|
563
|
+
publicKeys: { ed25519: keys.ed25519, mlDsa: keys.mlDsa }
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function normalizeMetalPublicIdentity(value) {
|
|
567
|
+
return JSON.stringify(parseMetalPublicIdentity(value));
|
|
562
568
|
}
|
|
563
569
|
var defaultExec2 = async (argv, stdin) => {
|
|
564
570
|
const child = Bun.spawn([...argv], {
|
|
@@ -1258,6 +1264,7 @@ export {
|
|
|
1258
1264
|
renderMetalUnits,
|
|
1259
1265
|
readMetalBootstrapConfig,
|
|
1260
1266
|
planMetalBootstrap,
|
|
1267
|
+
parseMetalPublicIdentity,
|
|
1261
1268
|
normalizeMetalPublicIdentity,
|
|
1262
1269
|
metalBootstrapStatus,
|
|
1263
1270
|
applyMetalBootstrap,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type PlatformBootstrapConfig, type PlatformBootstrapSecrets } from './bootstrap';
|
|
2
|
+
import { type MetalPublicIdentity } from './metal-bootstrap';
|
|
2
3
|
import { type PlatformGenesisGuest } from './platform-genesis';
|
|
3
4
|
export interface OperatorSshHop {
|
|
4
5
|
address: string;
|
|
@@ -20,12 +21,20 @@ export interface OperatorMetalBootstrapRequest {
|
|
|
20
21
|
kind: 'metal-remote';
|
|
21
22
|
target: OperatorPlatformBootstrapRequest['target'];
|
|
22
23
|
metalConfigFile: string;
|
|
24
|
+
/** Secret-free, pinned-host identity evidence written automatically after apply. */
|
|
25
|
+
metalIdentityEvidenceFile?: string;
|
|
23
26
|
genesis: {
|
|
24
27
|
nodes: readonly [PlatformGenesisGuest, PlatformGenesisGuest, PlatformGenesisGuest];
|
|
25
28
|
rehearsalNode?: PlatformGenesisGuest;
|
|
26
29
|
sshPublicKeyFiles: string[];
|
|
27
30
|
};
|
|
28
31
|
}
|
|
32
|
+
export interface OperatorMetalIdentityEvidence extends MetalPublicIdentity {
|
|
33
|
+
format: 1;
|
|
34
|
+
kind: 'forgezero-operator-metal-identity';
|
|
35
|
+
metalHostname: string;
|
|
36
|
+
metalHostKeySha256: string;
|
|
37
|
+
}
|
|
29
38
|
export interface OperatorGuestHostKeyEvidence {
|
|
30
39
|
format: 1;
|
|
31
40
|
kind: 'forgezero-operator-guest-host-keys';
|
|
@@ -139,6 +148,7 @@ export declare function readOperatorMetalBootstrapRequest(path: string, options?
|
|
|
139
148
|
validateMetalConfig?: boolean;
|
|
140
149
|
}): OperatorMetalBootstrapRequest;
|
|
141
150
|
export declare function writeOperatorMetalBootstrapRequest(path: string, request: OperatorMetalBootstrapRequest): string;
|
|
151
|
+
export declare function readOperatorMetalIdentityEvidence(path: string, request: OperatorMetalBootstrapRequest): OperatorMetalIdentityEvidence;
|
|
142
152
|
export declare function readOperatorGuestHostKeyEvidence(path: string, request?: OperatorMetalBootstrapRequest): OperatorGuestHostKeyEvidence;
|
|
143
153
|
export declare function writeOperatorGuestHostKeyEvidence(path: string, evidence: OperatorGuestHostKeyEvidence, request?: OperatorMetalBootstrapRequest): string;
|
|
144
154
|
export declare function planOperatorMetalBootstrap(request: OperatorMetalBootstrapRequest, mode: OperatorMetalBootstrapMode): OperatorMetalBootstrapPlan;
|