@forgezero/agent 0.1.103 → 0.1.108
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-bundle.d.ts +6 -0
- package/dist/bootstrap-bundle.js +41 -0
- package/dist/bootstrap.d.ts +1 -4
- package/dist/bootstrap.js +75 -44
- package/dist/database-auth-verify.d.ts +6 -0
- package/dist/deployment-connectivity.d.ts +21 -0
- package/dist/deployment-connectivity.js +117 -21
- package/dist/deployment-pull.d.ts +1 -1
- package/dist/deployment-topology.d.ts +2 -0
- package/dist/deployment-topology.js +2 -0
- package/dist/deployment.d.ts +18 -1
- package/dist/fz-agent.js +251 -34
- package/dist/fz.js +148 -175
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +75 -44
- package/dist/platform-bootstrap-runtime.d.ts +4 -6
- package/dist/platform-bootstrap-runtime.js +5 -10
- package/dist/platform-fleet-verification.js +134 -37
- package/dist/platform-launch-env.d.ts +17 -0
- package/dist/platform-launch-profile.d.ts +2 -3
- package/dist/provision.js +120 -24
- package/dist/software-helper.js +117 -21
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1384,9 +1384,49 @@ async function buildBootstrapBundle(input, exec = run) {
|
|
|
1384
1384
|
throw cause;
|
|
1385
1385
|
}
|
|
1386
1386
|
}
|
|
1387
|
+
async function refreshBootstrapBundle(input, exec = run) {
|
|
1388
|
+
const outputPath = resolve2(input.outputPath);
|
|
1389
|
+
const manifestPath = `${outputPath}.json`;
|
|
1390
|
+
if (existsSync(outputPath) !== existsSync(manifestPath)) {
|
|
1391
|
+
throw new Error("bootstrap bundle and manifest must either both exist or both be absent");
|
|
1392
|
+
}
|
|
1393
|
+
const candidatePath = `${outputPath}.candidate.${process.pid}.${randomBytes(6).toString("hex")}`;
|
|
1394
|
+
const candidate = await buildBootstrapBundle({ ...input, outputPath: candidatePath }, exec);
|
|
1395
|
+
try {
|
|
1396
|
+
if (!existsSync(outputPath)) {
|
|
1397
|
+
renameSync(candidate.bundlePath, outputPath);
|
|
1398
|
+
renameSync(candidate.manifestPath, manifestPath);
|
|
1399
|
+
return readBootstrapBundle(outputPath);
|
|
1400
|
+
}
|
|
1401
|
+
const current = await readBootstrapBundle(outputPath);
|
|
1402
|
+
if (current.manifest.branch === candidate.manifest.branch && current.manifest.revision === candidate.manifest.revision) {
|
|
1403
|
+
rmSync(candidate.bundlePath, { force: true });
|
|
1404
|
+
rmSync(candidate.manifestPath, { force: true });
|
|
1405
|
+
return current;
|
|
1406
|
+
}
|
|
1407
|
+
const archivePath = `${outputPath}.before-${current.manifest.revision.slice(0, 7)}-${Date.now()}`;
|
|
1408
|
+
const archiveManifestPath = `${archivePath}.json`;
|
|
1409
|
+
renameSync(outputPath, archivePath);
|
|
1410
|
+
renameSync(manifestPath, archiveManifestPath);
|
|
1411
|
+
try {
|
|
1412
|
+
renameSync(candidate.bundlePath, outputPath);
|
|
1413
|
+
renameSync(candidate.manifestPath, manifestPath);
|
|
1414
|
+
return readBootstrapBundle(outputPath);
|
|
1415
|
+
} catch (cause) {
|
|
1416
|
+
rmSync(outputPath, { force: true });
|
|
1417
|
+
rmSync(manifestPath, { force: true });
|
|
1418
|
+
renameSync(archivePath, outputPath);
|
|
1419
|
+
renameSync(archiveManifestPath, manifestPath);
|
|
1420
|
+
throw cause;
|
|
1421
|
+
}
|
|
1422
|
+
} finally {
|
|
1423
|
+
rmSync(candidate.bundlePath, { force: true });
|
|
1424
|
+
rmSync(candidate.manifestPath, { force: true });
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1387
1427
|
|
|
1388
1428
|
// src/bootstrap.ts
|
|
1389
|
-
import { createHash as createHash2, createHmac as createHmac2,
|
|
1429
|
+
import { createHash as createHash2, createHmac as createHmac2, randomBytes as randomBytes3 } from "crypto";
|
|
1390
1430
|
import {
|
|
1391
1431
|
chmodSync as chmodSync3,
|
|
1392
1432
|
existsSync as existsSync5,
|
|
@@ -1420,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1420
1460
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1421
1461
|
|
|
1422
1462
|
// src/version.ts
|
|
1423
|
-
var VERSION = "0.1.
|
|
1463
|
+
var VERSION = "0.1.108";
|
|
1424
1464
|
|
|
1425
1465
|
// src/software.ts
|
|
1426
1466
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -3188,10 +3228,8 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
3188
3228
|
} else if (input.email !== undefined) {
|
|
3189
3229
|
throw new Error("Bootstrap email provider must be smtp or jetemail.");
|
|
3190
3230
|
}
|
|
3191
|
-
if (input.
|
|
3192
|
-
|
|
3193
|
-
throw new Error("GitHub App client id, app id or slug is malformed.");
|
|
3194
|
-
}
|
|
3231
|
+
if (!input.githubOAuth || !/^(?:Iv1\.[A-Fa-f0-9]{16}|Ov23li[A-Za-z0-9]{14,})$/.test(input.githubOAuth.clientId)) {
|
|
3232
|
+
throw new Error("GitHub OAuth client id is malformed.");
|
|
3195
3233
|
}
|
|
3196
3234
|
boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
|
|
3197
3235
|
if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
|
|
@@ -3323,9 +3361,7 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
3323
3361
|
FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
|
|
3324
3362
|
FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
|
|
3325
3363
|
FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
|
|
3326
|
-
|
|
3327
|
-
FZ_GITHUB_APP_ID: value.githubApp?.appId ?? "",
|
|
3328
|
-
FZ_GITHUB_APP_SLUG: value.githubApp?.slug ?? "",
|
|
3364
|
+
FZ_GITHUB_OAUTH_CLIENT_ID: value.githubOAuth.clientId,
|
|
3329
3365
|
FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
|
|
3330
3366
|
};
|
|
3331
3367
|
return `# Generated by fz bootstrap platform. Non-secret coordinates only.
|
|
@@ -3337,9 +3373,7 @@ function platformApiCredentialSpecs(options) {
|
|
|
3337
3373
|
const optional = [
|
|
3338
3374
|
["fz_smtp.password", options.emailProvider === "smtp"],
|
|
3339
3375
|
["fz_jetemail.apiKey", options.emailProvider === "jetemail"],
|
|
3340
|
-
["
|
|
3341
|
-
["fz_github.privateKey", options.githubApp],
|
|
3342
|
-
["fz_github.webhookSecret", options.githubApp],
|
|
3376
|
+
["fz_oauth.github.clientSecret", options.githubOAuth],
|
|
3343
3377
|
["CF_API_TOKEN", options.cloudflareKv],
|
|
3344
3378
|
["CF_TUNNEL_TOKEN", options.cloudflareKv],
|
|
3345
3379
|
["REALTIME_PUBLISH_SECRET", options.realtime],
|
|
@@ -3347,6 +3381,7 @@ function platformApiCredentialSpecs(options) {
|
|
|
3347
3381
|
];
|
|
3348
3382
|
return [
|
|
3349
3383
|
{ name: "arangodb-jwt", encryptedPath: "/etc/forgezero/creds/arangodb-jwt.cred", required: true },
|
|
3384
|
+
{ name: "arangodb-root-password", encryptedPath: "/etc/forgezero/creds/arangodb-root-password.cred", required: true },
|
|
3350
3385
|
{ name: "seed-sync-root", encryptedPath: "/etc/forgezero/creds/seed-sync-root.cred", required: true },
|
|
3351
3386
|
...optional.filter(([, present]) => present).map(([name]) => ({
|
|
3352
3387
|
name,
|
|
@@ -3742,9 +3777,7 @@ function validatePlatformBootstrapSecrets(config, input) {
|
|
|
3742
3777
|
"backupS3Secret",
|
|
3743
3778
|
"cloudflareTunnelToken",
|
|
3744
3779
|
"cloudflareApiToken",
|
|
3745
|
-
"
|
|
3746
|
-
"githubPrivateKeyBase64",
|
|
3747
|
-
"githubWebhookSecret"
|
|
3780
|
+
"githubOAuthClientSecret"
|
|
3748
3781
|
];
|
|
3749
3782
|
const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
|
|
3750
3783
|
if (unknown.length)
|
|
@@ -3755,9 +3788,7 @@ function validatePlatformBootstrapSecrets(config, input) {
|
|
|
3755
3788
|
const backupS3Secret = typeof source.backupS3Secret === "string" ? source.backupS3Secret.trim() : undefined;
|
|
3756
3789
|
const cloudflareTunnelToken = typeof source.cloudflareTunnelToken === "string" ? source.cloudflareTunnelToken.trim() : undefined;
|
|
3757
3790
|
const cloudflareApiToken = typeof source.cloudflareApiToken === "string" ? source.cloudflareApiToken.trim() : undefined;
|
|
3758
|
-
const
|
|
3759
|
-
const githubPrivateKeyBase64 = typeof source.githubPrivateKeyBase64 === "string" ? source.githubPrivateKeyBase64.trim() : undefined;
|
|
3760
|
-
const githubWebhookSecret = typeof source.githubWebhookSecret === "string" ? source.githubWebhookSecret.trim() : undefined;
|
|
3791
|
+
const githubOAuthClientSecret = typeof source.githubOAuthClientSecret === "string" ? source.githubOAuthClientSecret.trim() : "";
|
|
3761
3792
|
if (!/^[a-f0-9]{64}$/i.test(clusterBootstrapCode))
|
|
3762
3793
|
throw new Error("cluster bootstrap code must contain exactly 64 hexadecimal characters");
|
|
3763
3794
|
if (!emailSecret || emailSecret.length > 16384 || /[\r\n\0]/.test(emailSecret))
|
|
@@ -3777,21 +3808,8 @@ function validatePlatformBootstrapSecrets(config, input) {
|
|
|
3777
3808
|
}
|
|
3778
3809
|
if (cloudflareTunnelToken)
|
|
3779
3810
|
validateCloudflareBootstrapSecretPair({ cloudflareTunnelToken, cloudflareApiToken });
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
throw new Error("GitHub App coordinates require client secret, private key and webhook secret together");
|
|
3783
|
-
}
|
|
3784
|
-
if (githubConfigured) {
|
|
3785
|
-
if (githubClientSecret.length < 20 || githubClientSecret.length > 512 || /[\r\n\0]/.test(githubClientSecret) || githubWebhookSecret.length < 32 || githubWebhookSecret.length > 512 || /[\r\n\0]/.test(githubWebhookSecret)) {
|
|
3786
|
-
throw new Error("GitHub App client or webhook secret is malformed");
|
|
3787
|
-
}
|
|
3788
|
-
try {
|
|
3789
|
-
const pem = Buffer.from(githubPrivateKeyBase64, "base64").toString("utf8");
|
|
3790
|
-
if (createPrivateKey(pem).asymmetricKeyType !== "rsa")
|
|
3791
|
-
throw new Error("not RSA");
|
|
3792
|
-
} catch {
|
|
3793
|
-
throw new Error("GitHub App private key must be a base64-encoded RSA private key");
|
|
3794
|
-
}
|
|
3811
|
+
if (githubOAuthClientSecret.length < 20 || githubOAuthClientSecret.length > 512 || /[\r\n\0]/.test(githubOAuthClientSecret)) {
|
|
3812
|
+
throw new Error("GitHub OAuth client secret is malformed");
|
|
3795
3813
|
}
|
|
3796
3814
|
return {
|
|
3797
3815
|
clusterBootstrapCode,
|
|
@@ -3799,7 +3817,7 @@ function validatePlatformBootstrapSecrets(config, input) {
|
|
|
3799
3817
|
...enrolmentToken ? { enrolmentToken } : {},
|
|
3800
3818
|
...backupS3Secret ? { backupS3Secret } : {},
|
|
3801
3819
|
...cloudflareTunnelToken ? { cloudflareTunnelToken, cloudflareApiToken } : {},
|
|
3802
|
-
|
|
3820
|
+
githubOAuthClientSecret
|
|
3803
3821
|
};
|
|
3804
3822
|
}
|
|
3805
3823
|
var platformBootstrapRunner = (config) => config.kind === "platform" && config.database.role === "master";
|
|
@@ -3814,6 +3832,7 @@ var STATE_PATH = BOOTSTRAP_STATE_PATH;
|
|
|
3814
3832
|
var INTENT_PATH = "/var/lib/forgezero/bootstrap.intent.json";
|
|
3815
3833
|
var CREDS = "/etc/forgezero/creds";
|
|
3816
3834
|
var JWT_CREDENTIAL = `${CREDS}/arangodb-jwt.cred`;
|
|
3835
|
+
var ARANGO_ROOT_CREDENTIAL = `${CREDS}/arangodb-root-password.cred`;
|
|
3817
3836
|
var ENROL_CREDENTIAL = `${CREDS}/enrol-token.cred`;
|
|
3818
3837
|
var TUNNEL_CREDENTIAL = `${CREDS}/CF_TUNNEL_CONNECTOR_TOKEN.cred`;
|
|
3819
3838
|
var CF_API_CREDENTIAL = `${CREDS}/CF_API_TOKEN.cred`;
|
|
@@ -4099,7 +4118,7 @@ WantedBy=multi-user.target
|
|
|
4099
4118
|
function databaseVerifyUnit(config) {
|
|
4100
4119
|
const { address } = config.database;
|
|
4101
4120
|
return `[Unit]
|
|
4102
|
-
Description=
|
|
4121
|
+
Description=Secure and verify ForgeZero ArangoDB Community 3.11.14 writable Coordinator
|
|
4103
4122
|
Requires=forgezero-db.service
|
|
4104
4123
|
After=forgezero-db.service
|
|
4105
4124
|
PartOf=forgezero-db.service
|
|
@@ -4109,7 +4128,9 @@ Type=oneshot
|
|
|
4109
4128
|
User=arangodb
|
|
4110
4129
|
Group=arangodb
|
|
4111
4130
|
LoadCredentialEncrypted=arangodb-jwt:${JWT_CREDENTIAL}
|
|
4112
|
-
|
|
4131
|
+
LoadCredentialEncrypted=arangodb-root-password:${ARANGO_ROOT_CREDENTIAL}
|
|
4132
|
+
ExecStart=/usr/bin/arangosh --server.endpoint tcp://${unitEscape(address)}:8529 --server.jwt-secret-keyfile %d/arangodb-jwt --javascript.execute-string 'const c=require("@arangodb").db._connection;let last;let ok=false;for(let i=0;i<90;i++){try{const v=c.GET("/_api/version?details=true");const s=c.GET("/_admin/status");const m=c.GET("/_admin/server/mode");if(!v.error&&v.version==="3.11.14"&&v.details&&v.details.license==="community"&&!s.error&&s.serverInfo&&s.serverInfo.role==="COORDINATOR"&&!m.error&&m.mode==="default"){ok=true;break;}last={v,s,m};}catch(e){last=String(e);}require("internal").wait(2);}if(!ok)throw new Error("writable Community Coordinator verification failed: "+JSON.stringify(last));const password=require("fs").read("%d/arangodb-root-password").trim();if(password.length<48)throw new Error("ArangoDB root credential is malformed");require("@arangodb/users").update("root",password,true);'
|
|
4133
|
+
ExecStart=/usr/local/bin/fz-agent database-auth-verify --endpoint=http://${unitEscape(address)}:8529
|
|
4113
4134
|
RemainAfterExit=yes
|
|
4114
4135
|
TimeoutStartSec=200
|
|
4115
4136
|
NoNewPrivileges=true
|
|
@@ -4576,6 +4597,11 @@ async function bootstrapStatus(host = localBootstrapHost()) {
|
|
|
4576
4597
|
if (nginx.exitCode !== 0)
|
|
4577
4598
|
problems.push("nginx configuration is invalid");
|
|
4578
4599
|
if (state.databaseRole !== "none") {
|
|
4600
|
+
for (const credential of [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL]) {
|
|
4601
|
+
services[credential] = host.exists(credential);
|
|
4602
|
+
if (!services[credential])
|
|
4603
|
+
problems.push(`${credential} is missing`);
|
|
4604
|
+
}
|
|
4579
4605
|
const unitPath = "/etc/systemd/system/forgezero-db.service";
|
|
4580
4606
|
const expectsNoAgency = state.databaseAgency === "none";
|
|
4581
4607
|
const unitHasNoAgency = host.exists(unitPath) && host.read(unitPath).includes("--cluster.start-agent=false");
|
|
@@ -4715,9 +4741,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4715
4741
|
backup: checked4.backupS3Secret,
|
|
4716
4742
|
cloudflareTunnelToken: checked4.cloudflareTunnelToken,
|
|
4717
4743
|
cloudflareApiToken: checked4.cloudflareApiToken,
|
|
4718
|
-
|
|
4719
|
-
githubPrivateKey: checked4.githubPrivateKeyBase64 ? Buffer.from(checked4.githubPrivateKeyBase64, "base64").toString("utf8") : undefined,
|
|
4720
|
-
githubWebhookSecret: checked4.githubWebhookSecret
|
|
4744
|
+
githubOAuthClientSecret: checked4.githubOAuthClientSecret
|
|
4721
4745
|
};
|
|
4722
4746
|
})() : undefined;
|
|
4723
4747
|
const enrolledPrivate = config.kind === "enrolled-compute" ? validateEnrolledComputeBootstrapSecrets(config, secrets) : undefined;
|
|
@@ -4810,14 +4834,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4810
4834
|
if (!host.exists(JWT_CREDENTIAL)) {
|
|
4811
4835
|
await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
|
|
4812
4836
|
}
|
|
4837
|
+
if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
|
|
4838
|
+
await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
|
|
4839
|
+
}
|
|
4813
4840
|
await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
|
|
4814
4841
|
await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
|
|
4815
4842
|
const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
|
|
4816
4843
|
for (const [name, source] of Object.entries({
|
|
4817
4844
|
...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
|
|
4818
|
-
"
|
|
4819
|
-
"fz_github.privateKey": platformPrivate.githubPrivateKey,
|
|
4820
|
-
"fz_github.webhookSecret": platformPrivate.githubWebhookSecret,
|
|
4845
|
+
"fz_oauth.github.clientSecret": platformPrivate.githubOAuthClientSecret,
|
|
4821
4846
|
"backup.s3.secretAccessKey": platformPrivate.backup
|
|
4822
4847
|
})) {
|
|
4823
4848
|
if (source) {
|
|
@@ -4834,7 +4859,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4834
4859
|
const credentials = platformApiCredentialSpecs({
|
|
4835
4860
|
emailProvider: runtime.environment.email?.provider,
|
|
4836
4861
|
cloudflareKv: cloudflareConfigured,
|
|
4837
|
-
|
|
4862
|
+
githubOAuth: true,
|
|
4838
4863
|
realtime: Boolean(runtime.environment.realtime)
|
|
4839
4864
|
});
|
|
4840
4865
|
const units = renderPlatformApiUnits({
|
|
@@ -5082,6 +5107,7 @@ function strictBootstrapDocument(value) {
|
|
|
5082
5107
|
"agentOtlpEndpoint",
|
|
5083
5108
|
"custodianEmail",
|
|
5084
5109
|
"email",
|
|
5110
|
+
"githubOAuth",
|
|
5085
5111
|
"deployProfile",
|
|
5086
5112
|
"otlpFlushIntervalMs",
|
|
5087
5113
|
"otlpTraceSampleRatio",
|
|
@@ -5092,8 +5118,13 @@ function strictBootstrapDocument(value) {
|
|
|
5092
5118
|
"databaseReadPreferredCoordinators"
|
|
5093
5119
|
], "runtime environment");
|
|
5094
5120
|
const environment = runtime.environment;
|
|
5121
|
+
exactKeys(environment.githubOAuth, ["clientId"], "GitHub OAuth config");
|
|
5095
5122
|
if (environment.initialInventory !== undefined) {
|
|
5096
|
-
const inventory = exactKeys(environment.initialInventory, ["metalHostname", "region", "computes", "attestation", "deployment"], "initial inventory");
|
|
5123
|
+
const inventory = exactKeys(environment.initialInventory, ["metalHostname", "metalIdentity", "region", "computes", "attestation", "deployment"], "initial inventory");
|
|
5124
|
+
if (inventory.metalIdentity !== undefined) {
|
|
5125
|
+
const identity = exactKeys(inventory.metalIdentity, ["nodeKey", "publicKeys"], "initial Metal identity");
|
|
5126
|
+
exactKeys(identity.publicKeys, ["ed25519", "mlDsa"], "initial Metal public keys");
|
|
5127
|
+
}
|
|
5097
5128
|
exactKeys(inventory.region, ["key", "label", "country", "city", "confidentialCapable"], "initial inventory region");
|
|
5098
5129
|
if (inventory.attestation !== undefined) {
|
|
5099
5130
|
const attestation = exactKeys(inventory.attestation, ["measurement", "tcbFloor"], "initial attestation evidence");
|
|
@@ -73,10 +73,8 @@ export type PlatformBootstrapEmail = {
|
|
|
73
73
|
* webhook secret are attended inputs sealed as systemd credentials and later
|
|
74
74
|
* imported into the unlocked platform Vault.
|
|
75
75
|
*/
|
|
76
|
-
export interface
|
|
76
|
+
export interface PlatformBootstrapGitHubOAuth {
|
|
77
77
|
clientId: string;
|
|
78
|
-
appId: string;
|
|
79
|
-
slug: string;
|
|
80
78
|
}
|
|
81
79
|
export interface PlatformSharedEnvironment {
|
|
82
80
|
softwareProfile: PlatformSoftwareProfile;
|
|
@@ -109,7 +107,7 @@ export interface PlatformSharedEnvironment {
|
|
|
109
107
|
otlpTraceSampleRatio: number;
|
|
110
108
|
custodianEmail?: string;
|
|
111
109
|
email?: PlatformBootstrapEmail;
|
|
112
|
-
|
|
110
|
+
githubOAuth: PlatformBootstrapGitHubOAuth;
|
|
113
111
|
backup?: {
|
|
114
112
|
endpoint: string;
|
|
115
113
|
region: string;
|
|
@@ -137,13 +135,13 @@ export declare function validatePlatformSharedEnvironment(input: PlatformSharedE
|
|
|
137
135
|
/** Render only non-secret runtime coordinates. Passwords/tokens have no field in this contract. */
|
|
138
136
|
export declare function renderPlatformSharedEnvironment(input: PlatformSharedEnvironment): string;
|
|
139
137
|
export interface SystemdCredentialSpec {
|
|
140
|
-
name: 'arangodb-jwt' | 'seed-sync-root' | 'fz_smtp.password' | 'fz_jetemail.apiKey' | '
|
|
138
|
+
name: 'arangodb-jwt' | 'arangodb-root-password' | 'seed-sync-root' | 'fz_smtp.password' | 'fz_jetemail.apiKey' | 'fz_oauth.github.clientSecret' | 'CF_API_TOKEN' | 'CF_TUNNEL_TOKEN' | 'REALTIME_PUBLISH_SECRET' | 'REALTIME_TICKET_SECRET';
|
|
141
139
|
encryptedPath: string;
|
|
142
140
|
required: boolean;
|
|
143
141
|
}
|
|
144
142
|
export declare function platformApiCredentialSpecs(options: {
|
|
145
143
|
emailProvider?: PlatformBootstrapEmail['provider'];
|
|
146
|
-
|
|
144
|
+
githubOAuth: boolean;
|
|
147
145
|
cloudflareKv: boolean;
|
|
148
146
|
realtime: boolean;
|
|
149
147
|
}): SystemdCredentialSpec[];
|
|
@@ -828,10 +828,8 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
828
828
|
} else if (input.email !== undefined) {
|
|
829
829
|
throw new Error("Bootstrap email provider must be smtp or jetemail.");
|
|
830
830
|
}
|
|
831
|
-
if (input.
|
|
832
|
-
|
|
833
|
-
throw new Error("GitHub App client id, app id or slug is malformed.");
|
|
834
|
-
}
|
|
831
|
+
if (!input.githubOAuth || !/^(?:Iv1\.[A-Fa-f0-9]{16}|Ov23li[A-Za-z0-9]{14,})$/.test(input.githubOAuth.clientId)) {
|
|
832
|
+
throw new Error("GitHub OAuth client id is malformed.");
|
|
835
833
|
}
|
|
836
834
|
boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
|
|
837
835
|
if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
|
|
@@ -963,9 +961,7 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
963
961
|
FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
|
|
964
962
|
FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
|
|
965
963
|
FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
|
|
966
|
-
|
|
967
|
-
FZ_GITHUB_APP_ID: value.githubApp?.appId ?? "",
|
|
968
|
-
FZ_GITHUB_APP_SLUG: value.githubApp?.slug ?? "",
|
|
964
|
+
FZ_GITHUB_OAUTH_CLIENT_ID: value.githubOAuth.clientId,
|
|
969
965
|
FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
|
|
970
966
|
};
|
|
971
967
|
return `# Generated by fz bootstrap platform. Non-secret coordinates only.
|
|
@@ -977,9 +973,7 @@ function platformApiCredentialSpecs(options) {
|
|
|
977
973
|
const optional = [
|
|
978
974
|
["fz_smtp.password", options.emailProvider === "smtp"],
|
|
979
975
|
["fz_jetemail.apiKey", options.emailProvider === "jetemail"],
|
|
980
|
-
["
|
|
981
|
-
["fz_github.privateKey", options.githubApp],
|
|
982
|
-
["fz_github.webhookSecret", options.githubApp],
|
|
976
|
+
["fz_oauth.github.clientSecret", options.githubOAuth],
|
|
983
977
|
["CF_API_TOKEN", options.cloudflareKv],
|
|
984
978
|
["CF_TUNNEL_TOKEN", options.cloudflareKv],
|
|
985
979
|
["REALTIME_PUBLISH_SECRET", options.realtime],
|
|
@@ -987,6 +981,7 @@ function platformApiCredentialSpecs(options) {
|
|
|
987
981
|
];
|
|
988
982
|
return [
|
|
989
983
|
{ name: "arangodb-jwt", encryptedPath: "/etc/forgezero/creds/arangodb-jwt.cred", required: true },
|
|
984
|
+
{ name: "arangodb-root-password", encryptedPath: "/etc/forgezero/creds/arangodb-root-password.cred", required: true },
|
|
990
985
|
{ name: "seed-sync-root", encryptedPath: "/etc/forgezero/creds/seed-sync-root.cred", required: true },
|
|
991
986
|
...optional.filter(([, present]) => present).map(([name]) => ({
|
|
992
987
|
name,
|
|
@@ -1869,7 +1869,7 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
|
|
|
1869
1869
|
// src/deployment-connectivity.ts
|
|
1870
1870
|
import { createHash as createHash3 } from "node:crypto";
|
|
1871
1871
|
import { mkdirSync as mkdirSync4, renameSync as renameSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1872
|
-
import { createConnection } from "node:net";
|
|
1872
|
+
import { createConnection, isIP } from "node:net";
|
|
1873
1873
|
import { dirname as dirname5 } from "node:path";
|
|
1874
1874
|
|
|
1875
1875
|
// src/process-input.ts
|
|
@@ -1956,21 +1956,99 @@ function validate(request) {
|
|
|
1956
1956
|
}
|
|
1957
1957
|
} else if (request.capabilities.private)
|
|
1958
1958
|
throw new Error("unexpected private deployment capability");
|
|
1959
|
+
if (request.firewallPolicy) {
|
|
1960
|
+
const policy = request.firewallPolicy;
|
|
1961
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(policy.generation) || !Array.isArray(policy.rules) || policy.rules.length > 256) {
|
|
1962
|
+
throw new Error("deployment firewall policy is malformed");
|
|
1963
|
+
}
|
|
1964
|
+
let expanded = 0;
|
|
1965
|
+
for (const rule of policy.rules) {
|
|
1966
|
+
expanded += rule.sourceAddresses.length;
|
|
1967
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(rule.ruleKey) || !["allow", "deny"].includes(rule.action) || !["tcp", "udp"].includes(rule.protocol) || rule.sourceAddresses.length < 1 || rule.sourceAddresses.length > 1024 || new Set(rule.sourceAddresses).size !== rule.sourceAddresses.length || rule.sourceAddresses.some((address) => isIP(address) !== 4) || !Number.isSafeInteger(rule.portFrom) || rule.portFrom < 1 || rule.portFrom > 65535 || !Number.isSafeInteger(rule.portTo) || rule.portTo < rule.portFrom || rule.portTo > 65535 || !Number.isSafeInteger(rule.priority) || rule.priority < 0 || rule.priority > 1e6) {
|
|
1968
|
+
throw new Error("deployment firewall policy is malformed");
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
if (expanded > 2048)
|
|
1972
|
+
throw new Error("deployment firewall policy expands beyond its rule limit");
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
async function replaceTaggedUfwRules(host, comment) {
|
|
1976
|
+
const status = await checked2(host, ["/usr/sbin/ufw", "status", "numbered"], "firewall inventory");
|
|
1977
|
+
const numbers = status.split(`
|
|
1978
|
+
`).flatMap((line) => {
|
|
1979
|
+
if (!line.includes(comment))
|
|
1980
|
+
return [];
|
|
1981
|
+
const match = line.match(/^\s*\[\s*(\d{1,6})\]/);
|
|
1982
|
+
return match ? [Number(match[1])] : [];
|
|
1983
|
+
}).filter((value) => Number.isSafeInteger(value) && value > 0).sort((left, right) => right - left);
|
|
1984
|
+
for (const number of numbers)
|
|
1985
|
+
await checked2(host, ["/usr/sbin/ufw", "--force", "delete", String(number)], `stale firewall rule ${number}`);
|
|
1959
1986
|
}
|
|
1960
1987
|
async function applyDeploymentConnectivity(request, host = defaultHost) {
|
|
1961
1988
|
validate(request);
|
|
1962
1989
|
const id = idFor(request.key);
|
|
1963
1990
|
const evidence = { key: request.key };
|
|
1991
|
+
if (request.firewallPolicy) {
|
|
1992
|
+
const policy = request.firewallPolicy;
|
|
1993
|
+
const comment = `fz-policy-${id}`;
|
|
1994
|
+
await replaceTaggedUfwRules(host, comment);
|
|
1995
|
+
const ordered = [...policy.rules].sort((left, right) => left.priority - right.priority || (left.action === right.action ? left.ruleKey.localeCompare(right.ruleKey) : left.action === "deny" ? -1 : 1));
|
|
1996
|
+
const commands = [];
|
|
1997
|
+
for (const rule of ordered)
|
|
1998
|
+
for (const source of rule.sourceAddresses)
|
|
1999
|
+
commands.push([
|
|
2000
|
+
"/usr/sbin/ufw",
|
|
2001
|
+
"insert",
|
|
2002
|
+
"1",
|
|
2003
|
+
rule.action,
|
|
2004
|
+
"from",
|
|
2005
|
+
source,
|
|
2006
|
+
"to",
|
|
2007
|
+
"any",
|
|
2008
|
+
"port",
|
|
2009
|
+
rule.portFrom === rule.portTo ? String(rule.portFrom) : `${rule.portFrom}:${rule.portTo}`,
|
|
2010
|
+
"proto",
|
|
2011
|
+
rule.protocol,
|
|
2012
|
+
"comment",
|
|
2013
|
+
comment
|
|
2014
|
+
]);
|
|
2015
|
+
const ranges = new Map;
|
|
2016
|
+
for (const rule of ordered)
|
|
2017
|
+
ranges.set(`${rule.protocol}:${rule.portFrom}:${rule.portTo}`, rule);
|
|
2018
|
+
for (const range of ranges.values())
|
|
2019
|
+
commands.push([
|
|
2020
|
+
"/usr/sbin/ufw",
|
|
2021
|
+
"insert",
|
|
2022
|
+
"1",
|
|
2023
|
+
"deny",
|
|
2024
|
+
"to",
|
|
2025
|
+
"any",
|
|
2026
|
+
"port",
|
|
2027
|
+
range.portFrom === range.portTo ? String(range.portFrom) : `${range.portFrom}:${range.portTo}`,
|
|
2028
|
+
"proto",
|
|
2029
|
+
range.protocol,
|
|
2030
|
+
"comment",
|
|
2031
|
+
comment
|
|
2032
|
+
]);
|
|
2033
|
+
for (const command2 of commands.toReversed())
|
|
2034
|
+
await checked2(host, command2, "deployment firewall rule");
|
|
2035
|
+
evidence.firewall = { generation: policy.generation, rules: commands.length, active: true };
|
|
2036
|
+
}
|
|
1964
2037
|
const topology = request.topology;
|
|
1965
2038
|
if (topology) {
|
|
1966
|
-
const values = [
|
|
2039
|
+
const values = [
|
|
2040
|
+
topology.localRelayAddress,
|
|
2041
|
+
...topology.localPeerAddresses,
|
|
2042
|
+
...topology.remoteRelayAddresses,
|
|
2043
|
+
...topology.remoteMemberAddresses
|
|
2044
|
+
];
|
|
1967
2045
|
const identities = [
|
|
1968
2046
|
topology.nodeIdentity,
|
|
1969
2047
|
...topology.localPeerIdentities,
|
|
1970
2048
|
...topology.remoteRelayIdentities,
|
|
1971
2049
|
...topology.memberIdentities
|
|
1972
2050
|
];
|
|
1973
|
-
if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) =>
|
|
2051
|
+
if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) => isIP(value) !== 4) || identities.some((value) => !/^[A-Za-z0-9.-]{1,253}$/.test(value)) || topology.localPeerIdentities.length !== topology.localPeerAddresses.length || topology.remoteRelayIdentities.length !== topology.remoteRelayAddresses.length || topology.remoteMemberAddresses.length > 1024 || new Set(topology.remoteMemberAddresses).size !== topology.remoteMemberAddresses.length || topology.memberIdentities.length < 1 || topology.memberIdentities.length > 1024 || new Set(topology.memberIdentities).size !== topology.memberIdentities.length || !topology.memberIdentities.includes(topology.nodeIdentity) || !/^sha256:[a-f0-9]{64}$/.test(topology.generation) || (topology.role === "member" ? topology.remoteRelayAddresses.length !== 0 : topology.remoteSiteCidrs.length !== topology.remoteRelayAddresses.length) || topology.remoteSiteCidrs.some((value) => !/^(?:\d{1,3}\.){3}0\/24$/.test(value)) || new Set(topology.remoteSiteCidrs).size !== topology.remoteSiteCidrs.length || !Number.isSafeInteger(topology.healthPort) || topology.healthPort < 1 || topology.healthPort > 65535 || topology.routedTcpPorts.length < 1 || topology.routedTcpPorts.length > 64 || new Set(topology.routedTcpPorts).size !== topology.routedTcpPorts.length || !topology.routedTcpPorts.includes(topology.healthPort) || topology.routedTcpPorts.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65535) || topology.role === "member" && topology.transport !== "private-lan") {
|
|
1974
2052
|
throw new Error("deployment topology is malformed");
|
|
1975
2053
|
}
|
|
1976
2054
|
if (request.intent.private?.mode === "cloudflare-warp" !== (topology.transport === "cloudflare-warp")) {
|
|
@@ -2097,28 +2175,46 @@ ${forwarding}${noOp}${starts}${starts ? `
|
|
|
2097
2175
|
[Install]
|
|
2098
2176
|
WantedBy=multi-user.target
|
|
2099
2177
|
`, 420);
|
|
2100
|
-
|
|
2178
|
+
const firewallComment = `fz-topology-${id}`;
|
|
2179
|
+
await replaceTaggedUfwRules(host, firewallComment);
|
|
2180
|
+
for (const source of [...new Set([...topology.localPeerAddresses, ...topology.remoteMemberAddresses])]) {
|
|
2101
2181
|
for (const port of topology.routedTcpPorts) {
|
|
2102
|
-
await checked2(host, [
|
|
2182
|
+
await checked2(host, [
|
|
2183
|
+
"/usr/sbin/ufw",
|
|
2184
|
+
"allow",
|
|
2185
|
+
"from",
|
|
2186
|
+
source,
|
|
2187
|
+
"to",
|
|
2188
|
+
"any",
|
|
2189
|
+
"port",
|
|
2190
|
+
String(port),
|
|
2191
|
+
"proto",
|
|
2192
|
+
"tcp",
|
|
2193
|
+
"comment",
|
|
2194
|
+
firewallComment
|
|
2195
|
+
], `private service ${source}:${port}`);
|
|
2103
2196
|
}
|
|
2104
2197
|
}
|
|
2105
2198
|
if (topology.role !== "member")
|
|
2106
|
-
for (const
|
|
2107
|
-
for (const
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2199
|
+
for (const remoteAddress of topology.remoteMemberAddresses) {
|
|
2200
|
+
for (const source of topology.localPeerAddresses)
|
|
2201
|
+
for (const port of topology.routedTcpPorts) {
|
|
2202
|
+
await checked2(host, [
|
|
2203
|
+
"/usr/sbin/ufw",
|
|
2204
|
+
"route",
|
|
2205
|
+
"allow",
|
|
2206
|
+
"proto",
|
|
2207
|
+
"tcp",
|
|
2208
|
+
"from",
|
|
2209
|
+
source,
|
|
2210
|
+
"to",
|
|
2211
|
+
remoteAddress,
|
|
2212
|
+
"port",
|
|
2213
|
+
String(port),
|
|
2214
|
+
"comment",
|
|
2215
|
+
firewallComment
|
|
2216
|
+
], `private routed service ${source}->${remoteAddress}:${port}`);
|
|
2217
|
+
}
|
|
2122
2218
|
}
|
|
2123
2219
|
await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "topology daemon reload");
|
|
2124
2220
|
await checked2(host, ["/usr/bin/systemctl", "enable", "--now", routeUnit], "topology routes");
|
|
@@ -2908,7 +3004,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2908
3004
|
}
|
|
2909
3005
|
|
|
2910
3006
|
// src/version.ts
|
|
2911
|
-
var VERSION3 = "0.1.
|
|
3007
|
+
var VERSION3 = "0.1.108";
|
|
2912
3008
|
|
|
2913
3009
|
// src/egress-policy.ts
|
|
2914
3010
|
import { realpathSync as realpathSync3 } from "node:fs";
|
|
@@ -2971,7 +3067,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
2971
3067
|
}
|
|
2972
3068
|
|
|
2973
3069
|
// src/provision.ts
|
|
2974
|
-
import { isIP } from "node:net";
|
|
3070
|
+
import { isIP as isIP2 } from "node:net";
|
|
2975
3071
|
function atLeast(version, floor) {
|
|
2976
3072
|
const parse = (value) => (value.trim().replace(/^v/, "").match(/\d+/g) ?? []).slice(0, 3).map(Number);
|
|
2977
3073
|
const got = parse(version);
|
|
@@ -3451,7 +3547,7 @@ function agentUnit(options) {
|
|
|
3451
3547
|
throw new Error("compute telemetry endpoint must be an absolute collector URL");
|
|
3452
3548
|
}
|
|
3453
3549
|
const localCollector = endpoint.protocol === "http:" && endpoint.hostname === "127.0.0.1" && endpoint.port === "4318" && endpoint.pathname === "/";
|
|
3454
|
-
const publicCollector = endpoint.protocol === "https:" && !endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash &&
|
|
3550
|
+
const publicCollector = endpoint.protocol === "https:" && !endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash && isIP2(endpoint.hostname) === 0 && endpoint.hostname.includes(".") && endpoint.hostname !== "localhost" && !endpoint.hostname.endsWith(".local");
|
|
3455
3551
|
if (!localCollector && !publicCollector || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
|
|
3456
3552
|
throw new Error("compute telemetry endpoint must be the supervised loopback collector or credential-free public HTTPS");
|
|
3457
3553
|
}
|
|
@@ -4240,10 +4336,8 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
4240
4336
|
} else if (input.email !== undefined) {
|
|
4241
4337
|
throw new Error("Bootstrap email provider must be smtp or jetemail.");
|
|
4242
4338
|
}
|
|
4243
|
-
if (input.
|
|
4244
|
-
|
|
4245
|
-
throw new Error("GitHub App client id, app id or slug is malformed.");
|
|
4246
|
-
}
|
|
4339
|
+
if (!input.githubOAuth || !/^(?:Iv1\.[A-Fa-f0-9]{16}|Ov23li[A-Za-z0-9]{14,})$/.test(input.githubOAuth.clientId)) {
|
|
4340
|
+
throw new Error("GitHub OAuth client id is malformed.");
|
|
4247
4341
|
}
|
|
4248
4342
|
boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
|
|
4249
4343
|
if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
|
|
@@ -4375,9 +4469,7 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
4375
4469
|
FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
|
|
4376
4470
|
FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
|
|
4377
4471
|
FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
|
|
4378
|
-
|
|
4379
|
-
FZ_GITHUB_APP_ID: value.githubApp?.appId ?? "",
|
|
4380
|
-
FZ_GITHUB_APP_SLUG: value.githubApp?.slug ?? "",
|
|
4472
|
+
FZ_GITHUB_OAUTH_CLIENT_ID: value.githubOAuth.clientId,
|
|
4381
4473
|
FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
|
|
4382
4474
|
};
|
|
4383
4475
|
return `# Generated by fz bootstrap platform. Non-secret coordinates only.
|
|
@@ -4389,9 +4481,7 @@ function platformApiCredentialSpecs(options) {
|
|
|
4389
4481
|
const optional = [
|
|
4390
4482
|
["fz_smtp.password", options.emailProvider === "smtp"],
|
|
4391
4483
|
["fz_jetemail.apiKey", options.emailProvider === "jetemail"],
|
|
4392
|
-
["
|
|
4393
|
-
["fz_github.privateKey", options.githubApp],
|
|
4394
|
-
["fz_github.webhookSecret", options.githubApp],
|
|
4484
|
+
["fz_oauth.github.clientSecret", options.githubOAuth],
|
|
4395
4485
|
["CF_API_TOKEN", options.cloudflareKv],
|
|
4396
4486
|
["CF_TUNNEL_TOKEN", options.cloudflareKv],
|
|
4397
4487
|
["REALTIME_PUBLISH_SECRET", options.realtime],
|
|
@@ -4399,6 +4489,7 @@ function platformApiCredentialSpecs(options) {
|
|
|
4399
4489
|
];
|
|
4400
4490
|
return [
|
|
4401
4491
|
{ name: "arangodb-jwt", encryptedPath: "/etc/forgezero/creds/arangodb-jwt.cred", required: true },
|
|
4492
|
+
{ name: "arangodb-root-password", encryptedPath: "/etc/forgezero/creds/arangodb-root-password.cred", required: true },
|
|
4402
4493
|
{ name: "seed-sync-root", encryptedPath: "/etc/forgezero/creds/seed-sync-root.cred", required: true },
|
|
4403
4494
|
...optional.filter(([, present]) => present).map(([name]) => ({
|
|
4404
4495
|
name,
|
|
@@ -4745,7 +4836,7 @@ function planLocalOtlpProof(endpoint, collectorUnit) {
|
|
|
4745
4836
|
import { lstatSync as lstatSync5 } from "node:fs";
|
|
4746
4837
|
|
|
4747
4838
|
// src/bootstrap.ts
|
|
4748
|
-
import { createHash as createHash7, createHmac as createHmac2,
|
|
4839
|
+
import { createHash as createHash7, createHmac as createHmac2, randomBytes as randomBytes3 } from "node:crypto";
|
|
4749
4840
|
import {
|
|
4750
4841
|
chmodSync as chmodSync9,
|
|
4751
4842
|
existsSync as existsSync11,
|
|
@@ -5078,10 +5169,10 @@ import { constants } from "node:fs";
|
|
|
5078
5169
|
import { createHmac, randomUUID as randomUUID4 } from "node:crypto";
|
|
5079
5170
|
import { chmod, lstat, mkdir, open, readdir, rename, rmdir, stat, unlink } from "node:fs/promises";
|
|
5080
5171
|
import { dirname as dirname10, join as join6, resolve as resolve5 } from "node:path";
|
|
5081
|
-
import { isIP as
|
|
5172
|
+
import { isIP as isIP4 } from "node:net";
|
|
5082
5173
|
|
|
5083
5174
|
// src/cloudflare-edge.ts
|
|
5084
|
-
import { isIP as
|
|
5175
|
+
import { isIP as isIP3 } from "node:net";
|
|
5085
5176
|
|
|
5086
5177
|
// src/otel-collector.ts
|
|
5087
5178
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -5146,6 +5237,7 @@ var BOOTSTRAP_STATE_PATH = "/var/lib/forgezero/bootstrap.json";
|
|
|
5146
5237
|
var STATE_PATH = BOOTSTRAP_STATE_PATH;
|
|
5147
5238
|
var CREDS = "/etc/forgezero/creds";
|
|
5148
5239
|
var JWT_CREDENTIAL = `${CREDS}/arangodb-jwt.cred`;
|
|
5240
|
+
var ARANGO_ROOT_CREDENTIAL = `${CREDS}/arangodb-root-password.cred`;
|
|
5149
5241
|
var ENROL_CREDENTIAL = `${CREDS}/enrol-token.cred`;
|
|
5150
5242
|
var TUNNEL_CREDENTIAL = `${CREDS}/CF_TUNNEL_CONNECTOR_TOKEN.cred`;
|
|
5151
5243
|
var CF_API_CREDENTIAL = `${CREDS}/CF_API_TOKEN.cred`;
|
|
@@ -5307,6 +5399,11 @@ async function bootstrapStatus(host = localBootstrapHost()) {
|
|
|
5307
5399
|
if (nginx3.exitCode !== 0)
|
|
5308
5400
|
problems.push("nginx configuration is invalid");
|
|
5309
5401
|
if (state.databaseRole !== "none") {
|
|
5402
|
+
for (const credential of [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL]) {
|
|
5403
|
+
services[credential] = host.exists(credential);
|
|
5404
|
+
if (!services[credential])
|
|
5405
|
+
problems.push(`${credential} is missing`);
|
|
5406
|
+
}
|
|
5310
5407
|
const unitPath = "/etc/systemd/system/forgezero-db.service";
|
|
5311
5408
|
const expectsNoAgency = state.databaseAgency === "none";
|
|
5312
5409
|
const unitHasNoAgency = host.exists(unitPath) && host.read(unitPath).includes("--cluster.start-agent=false");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ForgeZeroLaunchOwnerInput } from './platform-launch-profile';
|
|
2
|
+
export interface PlatformLaunchEnvironmentInput {
|
|
3
|
+
owner: ForgeZeroLaunchOwnerInput;
|
|
4
|
+
emailSecret: string;
|
|
5
|
+
cloudflareTunnelToken: string;
|
|
6
|
+
cloudflareApiToken: string;
|
|
7
|
+
githubOAuthClientSecret: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Read the development-only attended-launch adapter.
|
|
11
|
+
*
|
|
12
|
+
* The file may contain the API's ordinary non-secret environment as well; this
|
|
13
|
+
* reader selects only the fixed bootstrap coordinates below. Secret values are
|
|
14
|
+
* returned to the caller for immediate systemd-creds sealing and are never
|
|
15
|
+
* copied into the API environment rendered on a target.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readPlatformLaunchEnvironment(path: string): PlatformLaunchEnvironmentInput;
|