@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/fz.js CHANGED
@@ -4634,7 +4634,7 @@ var VaultError, runtimeEnvironment = () => typeof process !== "undefined" && pro
4634
4634
  } catch {
4635
4635
  return;
4636
4636
  }
4637
- }, VERSION = "0.1.19";
4637
+ }, VERSION = "0.1.20";
4638
4638
  var init_dist = __esm(() => {
4639
4639
  init_identity();
4640
4640
  VaultError = class VaultError extends Error {
@@ -4810,9 +4810,9 @@ async function spawnWith(command, env, report = () => {}, options = {}) {
4810
4810
  }
4811
4811
 
4812
4812
  // src/cli/index.ts
4813
- import { existsSync as existsSync13, lstatSync as lstatSync10, mkdirSync as mkdirSync13, readFileSync as readFileSync16, renameSync as renameSync10, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
4813
+ import { existsSync as existsSync13, lstatSync as lstatSync11, mkdirSync as mkdirSync13, readFileSync as readFileSync17, renameSync as renameSync10, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
4814
4814
  import { randomBytes as randomBytes10 } from "crypto";
4815
- import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join13, resolve as resolve12 } from "path";
4815
+ import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join13, resolve as resolve13 } from "path";
4816
4816
 
4817
4817
  // src/process-input.ts
4818
4818
  async function writeAndCloseProcessInput(input, value) {
@@ -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.103";
4844
+ var VERSION2 = "0.1.108";
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, createPrivateKey, randomBytes as randomBytes7 } from "crypto";
12061
+ import { createHash as createHash4, createHmac as createHmac2, randomBytes as randomBytes7 } from "crypto";
12062
12062
  import {
12063
12063
  chmodSync as chmodSync4,
12064
12064
  existsSync as existsSync9,
@@ -12337,10 +12337,8 @@ function validatePlatformSharedEnvironment(input) {
12337
12337
  } else if (input.email !== undefined) {
12338
12338
  throw new Error("Bootstrap email provider must be smtp or jetemail.");
12339
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
- }
12340
+ if (!input.githubOAuth || !/^(?:Iv1\.[A-Fa-f0-9]{16}|Ov23li[A-Za-z0-9]{14,})$/.test(input.githubOAuth.clientId)) {
12341
+ throw new Error("GitHub OAuth client id is malformed.");
12344
12342
  }
12345
12343
  boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
12346
12344
  if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
@@ -12472,9 +12470,7 @@ function renderPlatformSharedEnvironment(input) {
12472
12470
  FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
12473
12471
  FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
12474
12472
  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 ?? "",
12473
+ FZ_GITHUB_OAUTH_CLIENT_ID: value.githubOAuth.clientId,
12478
12474
  FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
12479
12475
  };
12480
12476
  return `# Generated by fz bootstrap platform. Non-secret coordinates only.
@@ -12486,9 +12482,7 @@ function platformApiCredentialSpecs(options) {
12486
12482
  const optional = [
12487
12483
  ["fz_smtp.password", options.emailProvider === "smtp"],
12488
12484
  ["fz_jetemail.apiKey", options.emailProvider === "jetemail"],
12489
- ["fz_github.clientSecret", options.githubApp],
12490
- ["fz_github.privateKey", options.githubApp],
12491
- ["fz_github.webhookSecret", options.githubApp],
12485
+ ["fz_oauth.github.clientSecret", options.githubOAuth],
12492
12486
  ["CF_API_TOKEN", options.cloudflareKv],
12493
12487
  ["CF_TUNNEL_TOKEN", options.cloudflareKv],
12494
12488
  ["REALTIME_PUBLISH_SECRET", options.realtime],
@@ -12496,6 +12490,7 @@ function platformApiCredentialSpecs(options) {
12496
12490
  ];
12497
12491
  return [
12498
12492
  { name: "arangodb-jwt", encryptedPath: "/etc/forgezero/creds/arangodb-jwt.cred", required: true },
12493
+ { name: "arangodb-root-password", encryptedPath: "/etc/forgezero/creds/arangodb-root-password.cred", required: true },
12499
12494
  { name: "seed-sync-root", encryptedPath: "/etc/forgezero/creds/seed-sync-root.cred", required: true },
12500
12495
  ...optional.filter(([, present]) => present).map(([name]) => ({
12501
12496
  name,
@@ -14079,6 +14074,46 @@ async function buildBootstrapBundle(input, exec = run) {
14079
14074
  throw cause;
14080
14075
  }
14081
14076
  }
14077
+ async function refreshBootstrapBundle(input, exec = run) {
14078
+ const outputPath = resolve6(input.outputPath);
14079
+ const manifestPath = `${outputPath}.json`;
14080
+ if (existsSync8(outputPath) !== existsSync8(manifestPath)) {
14081
+ throw new Error("bootstrap bundle and manifest must either both exist or both be absent");
14082
+ }
14083
+ const candidatePath = `${outputPath}.candidate.${process.pid}.${randomBytes6(6).toString("hex")}`;
14084
+ const candidate = await buildBootstrapBundle({ ...input, outputPath: candidatePath }, exec);
14085
+ try {
14086
+ if (!existsSync8(outputPath)) {
14087
+ renameSync7(candidate.bundlePath, outputPath);
14088
+ renameSync7(candidate.manifestPath, manifestPath);
14089
+ return readBootstrapBundle(outputPath);
14090
+ }
14091
+ const current2 = await readBootstrapBundle(outputPath);
14092
+ if (current2.manifest.branch === candidate.manifest.branch && current2.manifest.revision === candidate.manifest.revision) {
14093
+ rmSync5(candidate.bundlePath, { force: true });
14094
+ rmSync5(candidate.manifestPath, { force: true });
14095
+ return current2;
14096
+ }
14097
+ const archivePath = `${outputPath}.before-${current2.manifest.revision.slice(0, 7)}-${Date.now()}`;
14098
+ const archiveManifestPath = `${archivePath}.json`;
14099
+ renameSync7(outputPath, archivePath);
14100
+ renameSync7(manifestPath, archiveManifestPath);
14101
+ try {
14102
+ renameSync7(candidate.bundlePath, outputPath);
14103
+ renameSync7(candidate.manifestPath, manifestPath);
14104
+ return readBootstrapBundle(outputPath);
14105
+ } catch (cause) {
14106
+ rmSync5(outputPath, { force: true });
14107
+ rmSync5(manifestPath, { force: true });
14108
+ renameSync7(archivePath, outputPath);
14109
+ renameSync7(archiveManifestPath, manifestPath);
14110
+ throw cause;
14111
+ }
14112
+ } finally {
14113
+ rmSync5(candidate.bundlePath, { force: true });
14114
+ rmSync5(candidate.manifestPath, { force: true });
14115
+ }
14116
+ }
14082
14117
 
14083
14118
  // src/bootstrap.ts
14084
14119
  var PLATFORM_BOOTSTRAP_PROFILES = [
@@ -14127,9 +14162,7 @@ function validatePlatformBootstrapSecrets(config, input) {
14127
14162
  "backupS3Secret",
14128
14163
  "cloudflareTunnelToken",
14129
14164
  "cloudflareApiToken",
14130
- "githubClientSecret",
14131
- "githubPrivateKeyBase64",
14132
- "githubWebhookSecret"
14165
+ "githubOAuthClientSecret"
14133
14166
  ];
14134
14167
  const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
14135
14168
  if (unknown.length)
@@ -14140,9 +14173,7 @@ function validatePlatformBootstrapSecrets(config, input) {
14140
14173
  const backupS3Secret = typeof source.backupS3Secret === "string" ? source.backupS3Secret.trim() : undefined;
14141
14174
  const cloudflareTunnelToken = typeof source.cloudflareTunnelToken === "string" ? source.cloudflareTunnelToken.trim() : undefined;
14142
14175
  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;
14176
+ const githubOAuthClientSecret = typeof source.githubOAuthClientSecret === "string" ? source.githubOAuthClientSecret.trim() : "";
14146
14177
  if (!/^[a-f0-9]{64}$/i.test(clusterBootstrapCode))
14147
14178
  throw new Error("cluster bootstrap code must contain exactly 64 hexadecimal characters");
14148
14179
  if (!emailSecret || emailSecret.length > 16384 || /[\r\n\0]/.test(emailSecret))
@@ -14162,21 +14193,8 @@ function validatePlatformBootstrapSecrets(config, input) {
14162
14193
  }
14163
14194
  if (cloudflareTunnelToken)
14164
14195
  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
- }
14196
+ if (githubOAuthClientSecret.length < 20 || githubOAuthClientSecret.length > 512 || /[\r\n\0]/.test(githubOAuthClientSecret)) {
14197
+ throw new Error("GitHub OAuth client secret is malformed");
14180
14198
  }
14181
14199
  return {
14182
14200
  clusterBootstrapCode,
@@ -14184,7 +14202,7 @@ function validatePlatformBootstrapSecrets(config, input) {
14184
14202
  ...enrolmentToken ? { enrolmentToken } : {},
14185
14203
  ...backupS3Secret ? { backupS3Secret } : {},
14186
14204
  ...cloudflareTunnelToken ? { cloudflareTunnelToken, cloudflareApiToken } : {},
14187
- ...githubConfigured ? { githubClientSecret, githubPrivateKeyBase64, githubWebhookSecret } : {}
14205
+ githubOAuthClientSecret
14188
14206
  };
14189
14207
  }
14190
14208
  var platformBootstrapRunner = (config) => config.kind === "platform" && config.database.role === "master";
@@ -14199,6 +14217,7 @@ var STATE_PATH = BOOTSTRAP_STATE_PATH;
14199
14217
  var INTENT_PATH = "/var/lib/forgezero/bootstrap.intent.json";
14200
14218
  var CREDS = "/etc/forgezero/creds";
14201
14219
  var JWT_CREDENTIAL = `${CREDS}/arangodb-jwt.cred`;
14220
+ var ARANGO_ROOT_CREDENTIAL = `${CREDS}/arangodb-root-password.cred`;
14202
14221
  var ENROL_CREDENTIAL = `${CREDS}/enrol-token.cred`;
14203
14222
  var TUNNEL_CREDENTIAL = `${CREDS}/CF_TUNNEL_CONNECTOR_TOKEN.cred`;
14204
14223
  var CF_API_CREDENTIAL = `${CREDS}/CF_API_TOKEN.cred`;
@@ -14484,7 +14503,7 @@ WantedBy=multi-user.target
14484
14503
  function databaseVerifyUnit(config) {
14485
14504
  const { address } = config.database;
14486
14505
  return `[Unit]
14487
- Description=Verify ForgeZero ArangoDB Community 3.11.14 writable Coordinator
14506
+ Description=Secure and verify ForgeZero ArangoDB Community 3.11.14 writable Coordinator
14488
14507
  Requires=forgezero-db.service
14489
14508
  After=forgezero-db.service
14490
14509
  PartOf=forgezero-db.service
@@ -14494,7 +14513,9 @@ Type=oneshot
14494
14513
  User=arangodb
14495
14514
  Group=arangodb
14496
14515
  LoadCredentialEncrypted=arangodb-jwt:${JWT_CREDENTIAL}
14497
- 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));'
14516
+ LoadCredentialEncrypted=arangodb-root-password:${ARANGO_ROOT_CREDENTIAL}
14517
+ 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);'
14518
+ ExecStart=/usr/local/bin/fz-agent database-auth-verify --endpoint=http://${unitEscape(address)}:8529
14498
14519
  RemainAfterExit=yes
14499
14520
  TimeoutStartSec=200
14500
14521
  NoNewPrivileges=true
@@ -14961,6 +14982,11 @@ async function bootstrapStatus(host = localBootstrapHost()) {
14961
14982
  if (nginx.exitCode !== 0)
14962
14983
  problems.push("nginx configuration is invalid");
14963
14984
  if (state.databaseRole !== "none") {
14985
+ for (const credential of [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL]) {
14986
+ services[credential] = host.exists(credential);
14987
+ if (!services[credential])
14988
+ problems.push(`${credential} is missing`);
14989
+ }
14964
14990
  const unitPath = "/etc/systemd/system/forgezero-db.service";
14965
14991
  const expectsNoAgency = state.databaseAgency === "none";
14966
14992
  const unitHasNoAgency = host.exists(unitPath) && host.read(unitPath).includes("--cluster.start-agent=false");
@@ -15100,9 +15126,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15100
15126
  backup: checked4.backupS3Secret,
15101
15127
  cloudflareTunnelToken: checked4.cloudflareTunnelToken,
15102
15128
  cloudflareApiToken: checked4.cloudflareApiToken,
15103
- githubClientSecret: checked4.githubClientSecret,
15104
- githubPrivateKey: checked4.githubPrivateKeyBase64 ? Buffer.from(checked4.githubPrivateKeyBase64, "base64").toString("utf8") : undefined,
15105
- githubWebhookSecret: checked4.githubWebhookSecret
15129
+ githubOAuthClientSecret: checked4.githubOAuthClientSecret
15106
15130
  };
15107
15131
  })() : undefined;
15108
15132
  const enrolledPrivate = config.kind === "enrolled-compute" ? validateEnrolledComputeBootstrapSecrets(config, secrets) : undefined;
@@ -15195,14 +15219,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15195
15219
  if (!host.exists(JWT_CREDENTIAL)) {
15196
15220
  await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
15197
15221
  }
15222
+ if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
15223
+ await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
15224
+ }
15198
15225
  await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
15199
15226
  await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
15200
15227
  const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
15201
15228
  for (const [name, source] of Object.entries({
15202
15229
  ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
15203
- "fz_github.clientSecret": platformPrivate.githubClientSecret,
15204
- "fz_github.privateKey": platformPrivate.githubPrivateKey,
15205
- "fz_github.webhookSecret": platformPrivate.githubWebhookSecret,
15230
+ "fz_oauth.github.clientSecret": platformPrivate.githubOAuthClientSecret,
15206
15231
  "backup.s3.secretAccessKey": platformPrivate.backup
15207
15232
  })) {
15208
15233
  if (source) {
@@ -15219,7 +15244,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15219
15244
  const credentials = platformApiCredentialSpecs({
15220
15245
  emailProvider: runtime.environment.email?.provider,
15221
15246
  cloudflareKv: cloudflareConfigured,
15222
- githubApp: Boolean(runtime.environment.githubApp),
15247
+ githubOAuth: true,
15223
15248
  realtime: Boolean(runtime.environment.realtime)
15224
15249
  });
15225
15250
  const units = renderPlatformApiUnits({
@@ -15467,6 +15492,7 @@ function strictBootstrapDocument(value) {
15467
15492
  "agentOtlpEndpoint",
15468
15493
  "custodianEmail",
15469
15494
  "email",
15495
+ "githubOAuth",
15470
15496
  "deployProfile",
15471
15497
  "otlpFlushIntervalMs",
15472
15498
  "otlpTraceSampleRatio",
@@ -15477,8 +15503,13 @@ function strictBootstrapDocument(value) {
15477
15503
  "databaseReadPreferredCoordinators"
15478
15504
  ], "runtime environment");
15479
15505
  const environment = runtime.environment;
15506
+ exactKeys2(environment.githubOAuth, ["clientId"], "GitHub OAuth config");
15480
15507
  if (environment.initialInventory !== undefined) {
15481
- const inventory = exactKeys2(environment.initialInventory, ["metalHostname", "region", "computes", "attestation", "deployment"], "initial inventory");
15508
+ const inventory = exactKeys2(environment.initialInventory, ["metalHostname", "metalIdentity", "region", "computes", "attestation", "deployment"], "initial inventory");
15509
+ if (inventory.metalIdentity !== undefined) {
15510
+ const identity = exactKeys2(inventory.metalIdentity, ["nodeKey", "publicKeys"], "initial Metal identity");
15511
+ exactKeys2(identity.publicKeys, ["ed25519", "mlDsa"], "initial Metal public keys");
15512
+ }
15482
15513
  exactKeys2(inventory.region, ["key", "label", "country", "city", "confidentialCapable"], "initial inventory region");
15483
15514
  if (inventory.attestation !== undefined) {
15484
15515
  const attestation = exactKeys2(inventory.attestation, ["measurement", "tcbFloor"], "initial attestation evidence");
@@ -18178,7 +18209,7 @@ function forgeZeroLaunchTemplate(profile, guests, bundle, owner) {
18178
18209
  otlpTraceSampleRatio: 0.1,
18179
18210
  custodianEmail: owner.custodianEmail,
18180
18211
  email: owner.email,
18181
- ...owner.githubApp ? { githubApp: owner.githubApp } : {},
18212
+ githubOAuth: owner.githubOAuth,
18182
18213
  deployProfile: profile.environment
18183
18214
  },
18184
18215
  serviceUser: "forgezero-api",
@@ -18535,6 +18566,52 @@ function planAppBuild(input) {
18535
18566
  };
18536
18567
  }
18537
18568
 
18569
+ // src/platform-launch-env.ts
18570
+ import { lstatSync as lstatSync10, readFileSync as readFileSync16 } from "fs";
18571
+ import { resolve as resolve12 } from "path";
18572
+ function readPlatformLaunchEnvironment(path) {
18573
+ const absolute2 = resolve12(path);
18574
+ const stat2 = lstatSync10(absolute2);
18575
+ if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.nlink !== 1 || typeof process.getuid === "function" && stat2.uid !== process.getuid() || (stat2.mode & 63) !== 0) {
18576
+ throw new Error(`bootstrap secret environment must be one owner-only regular file: ${absolute2}`);
18577
+ }
18578
+ const values = new Map;
18579
+ for (const [index, raw] of readFileSync16(absolute2, "utf8").split(/\r?\n/).entries()) {
18580
+ const line = raw.trim();
18581
+ if (!line || line.startsWith("#"))
18582
+ continue;
18583
+ const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
18584
+ if (!match)
18585
+ throw new Error(`invalid environment assignment at ${absolute2}:${index + 1}`);
18586
+ if (values.has(match[1]))
18587
+ throw new Error(`duplicate environment assignment ${match[1]} at ${absolute2}:${index + 1}`);
18588
+ let value = match[2].trim();
18589
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
18590
+ value = value.slice(1, -1);
18591
+ }
18592
+ values.set(match[1], value);
18593
+ }
18594
+ const requiredValue = (name) => {
18595
+ const value = values.get(name)?.trim();
18596
+ if (!value)
18597
+ throw new Error(`bootstrap secret environment is missing ${name}`);
18598
+ return value;
18599
+ };
18600
+ const githubOAuth = { clientId: requiredValue("FZ_GITHUB_OAUTH_CLIENT_ID") };
18601
+ const owner = {
18602
+ custodianEmail: "info@axxra.in",
18603
+ email: { provider: "jetemail", from: "noreply@forgezero.net", eu: false },
18604
+ githubOAuth
18605
+ };
18606
+ return {
18607
+ owner,
18608
+ emailSecret: requiredValue("FZ_JETEMAIL_API_KEY"),
18609
+ cloudflareTunnelToken: requiredValue("CF_TUNNEL_TOKEN"),
18610
+ cloudflareApiToken: requiredValue("CF_API_TOKEN"),
18611
+ githubOAuthClientSecret: requiredValue("FZ_GITHUB_OAUTH_CLIENT_SECRET")
18612
+ };
18613
+ }
18614
+
18538
18615
  // src/cli/index.ts
18539
18616
  var DEFAULT_MODE = (THRESHOLD_MODES.find((mode) => mode.threshold === 1 && mode.total === 1) ?? THRESHOLD_MODES[0]).id;
18540
18617
  var PACKAGED_AGENT_BIN2 = operatorPackagedAgentPath();
@@ -18847,7 +18924,7 @@ async function api(options, path, init) {
18847
18924
  return { status: response.status, body };
18848
18925
  }
18849
18926
  function sleep(ms) {
18850
- return new Promise((resolve13) => setTimeout(resolve13, ms));
18927
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
18851
18928
  }
18852
18929
  function browserCommand(url) {
18853
18930
  if (process.platform === "darwin")
@@ -18932,7 +19009,7 @@ function requireSession() {
18932
19009
  function readOwnerOnlySecret(path, label) {
18933
19010
  let metadata;
18934
19011
  try {
18935
- metadata = lstatSync10(path);
19012
+ metadata = lstatSync11(path);
18936
19013
  } catch {
18937
19014
  throw new Error(`The ${label} file ${path} is unreadable.`);
18938
19015
  }
@@ -18946,7 +19023,7 @@ function readOwnerOnlySecret(path, label) {
18946
19023
  if (uid !== undefined && uid !== 0 && metadata.uid !== uid) {
18947
19024
  throw new Error(`The ${label} file ${path} is not owned by the current user.`);
18948
19025
  }
18949
- const value = readFileSync16(path, "utf8").trim();
19026
+ const value = readFileSync17(path, "utf8").trim();
18950
19027
  if (!value)
18951
19028
  throw new Error(`The ${label} file ${path} is empty.`);
18952
19029
  return value;
@@ -19127,7 +19204,7 @@ async function cmdApi(options, args) {
19127
19204
  let body = undefined;
19128
19205
  if (options.data !== undefined && options.dataFile)
19129
19206
  throw new Error("Use only one of --data or --data-file.");
19130
- const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync16(options.dataFile, "utf8") : options.data;
19207
+ const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync17(options.dataFile, "utf8") : options.data;
19131
19208
  if (encoded !== undefined)
19132
19209
  body = JSON.parse(encoded);
19133
19210
  const result = await api(options, `${url.pathname}${url.search}`, { method, body });
@@ -19306,7 +19383,7 @@ async function cmdAgent(options, args) {
19306
19383
  out.line();
19307
19384
  out.line(" Compatibility SSH deploy public key:");
19308
19385
  out.line();
19309
- out.line(` ${readFileSync16(gitIdentity.gitPublicKeyPath, "utf8").trim()}`);
19386
+ out.line(` ${readFileSync17(gitIdentity.gitPublicKeyPath, "utf8").trim()}`);
19310
19387
  out.line();
19311
19388
  }
19312
19389
  return 0;
@@ -19346,78 +19423,6 @@ async function bootstrapSecret(question) {
19346
19423
  throw new Error(`${question} was not provided`);
19347
19424
  return value;
19348
19425
  }
19349
- function readPlatformLaunchEnvironment(path) {
19350
- const absolute2 = resolve12(path);
19351
- const stat2 = lstatSync10(absolute2);
19352
- if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.nlink !== 1 || typeof process.getuid === "function" && stat2.uid !== process.getuid() || (stat2.mode & 63) !== 0) {
19353
- throw new Error(`bootstrap secret environment must be one owner-only regular file: ${absolute2}`);
19354
- }
19355
- const values = new Map;
19356
- for (const [index, raw] of readFileSync16(absolute2, "utf8").split(/\r?\n/).entries()) {
19357
- const line = raw.trim();
19358
- if (!line || line.startsWith("#"))
19359
- continue;
19360
- const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
19361
- if (!match)
19362
- throw new Error(`invalid environment assignment at ${absolute2}:${index + 1}`);
19363
- if (values.has(match[1]))
19364
- throw new Error(`duplicate environment assignment ${match[1]} at ${absolute2}:${index + 1}`);
19365
- let value = match[2].trim();
19366
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
19367
- value = value.slice(1, -1);
19368
- }
19369
- values.set(match[1], value);
19370
- }
19371
- const requiredValue = (name) => {
19372
- const value = values.get(name)?.trim();
19373
- if (!value)
19374
- throw new Error(`bootstrap secret environment is missing ${name}`);
19375
- return value;
19376
- };
19377
- const provider = requiredValue("FZ_EMAIL_PROVIDER");
19378
- if (provider !== "smtp" && provider !== "jetemail")
19379
- throw new Error("FZ_EMAIL_PROVIDER must be smtp or jetemail");
19380
- const smtpPort = provider === "smtp" ? Number(requiredValue("FZ_SMTP_PORT")) : undefined;
19381
- if (smtpPort !== undefined && (!Number.isInteger(smtpPort) || smtpPort < 1 || smtpPort > 65535)) {
19382
- throw new Error("FZ_SMTP_PORT must be an integer from 1 through 65535");
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;
19390
- const owner = provider === "jetemail" ? {
19391
- custodianEmail: requiredValue("FZ_BOOTSTRAP_CUSTODIAN_EMAIL"),
19392
- email: {
19393
- provider,
19394
- from: requiredValue("FZ_EMAIL_FROM"),
19395
- eu: (values.get("FZ_JETEMAIL_EU") ?? "false").trim().toLowerCase() === "true"
19396
- },
19397
- ...githubApp ? { githubApp } : {}
19398
- } : {
19399
- custodianEmail: requiredValue("FZ_BOOTSTRAP_CUSTODIAN_EMAIL"),
19400
- email: {
19401
- provider,
19402
- from: requiredValue("FZ_EMAIL_FROM"),
19403
- host: requiredValue("FZ_SMTP_HOST"),
19404
- port: smtpPort,
19405
- user: requiredValue("FZ_SMTP_USER")
19406
- },
19407
- ...githubApp ? { githubApp } : {}
19408
- };
19409
- return {
19410
- owner,
19411
- emailSecret: requiredValue(provider === "jetemail" ? "FZ_JETEMAIL_API_KEY" : "FZ_SMTP_PASSWORD"),
19412
- cloudflareTunnelToken: requiredValue("CF_TUNNEL_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
- } : {}
19419
- };
19420
- }
19421
19426
  async function promptPlatformBootstrapSecrets(config, generatedClusterBootstrapCode) {
19422
19427
  return validatePlatformBootstrapSecrets(config, {
19423
19428
  clusterBootstrapCode: generatedClusterBootstrapCode ?? await bootstrapSecret("Shared 64-hex cluster bootstrap code"),
@@ -19428,11 +19433,7 @@ async function promptPlatformBootstrapSecrets(config, generatedClusterBootstrapC
19428
19433
  cloudflareTunnelToken: await bootstrapSecret("CF_TUNNEL_TOKEN"),
19429
19434
  cloudflareApiToken: await bootstrapSecret("CF_API_TOKEN")
19430
19435
  } : {},
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")
19435
- } : {}
19436
+ githubOAuthClientSecret: await bootstrapSecret("GitHub OAuth client secret")
19436
19437
  });
19437
19438
  }
19438
19439
  var bootstrapList = (question, fallback) => bootstrapAnswer(question, fallback).split(",").map((value) => value.trim()).filter(Boolean);
@@ -19493,7 +19494,7 @@ function interactiveMetalBootstrap() {
19493
19494
  });
19494
19495
  }
19495
19496
  function writeBootstrapConfig(path, config) {
19496
- if (!isAbsolute7(path) || resolve12(path) !== path)
19497
+ if (!isAbsolute7(path) || resolve13(path) !== path)
19497
19498
  throw new Error("--output must be a canonical absolute path");
19498
19499
  mkdirSync13(dirname14(path), { recursive: true, mode: 448 });
19499
19500
  writeFileSync13(path, `${JSON.stringify(config, null, 2)}
@@ -19557,11 +19558,11 @@ async function interactiveCloudflareBootstrap() {
19557
19558
  });
19558
19559
  }
19559
19560
  function genesisOutputDirectory(path) {
19560
- if (!isAbsolute7(path) || resolve12(path) !== path)
19561
+ if (!isAbsolute7(path) || resolve13(path) !== path)
19561
19562
  throw new Error("--output must be a canonical absolute directory");
19562
19563
  if (!existsSync13(path))
19563
19564
  mkdirSync13(path, { recursive: true, mode: 448 });
19564
- const metadata = lstatSync10(path);
19565
+ const metadata = lstatSync11(path);
19565
19566
  const uid = process.getuid?.();
19566
19567
  if (!metadata.isDirectory() || metadata.isSymbolicLink() || (metadata.mode & 63) !== 0 || uid !== undefined && uid !== 0 && metadata.uid !== uid) {
19567
19568
  throw new Error("platform genesis output must be an owner-only directory owned by the current user");
@@ -19730,34 +19731,11 @@ function selectedForgeZeroLaunchEnvironment(value) {
19730
19731
  return value;
19731
19732
  }
19732
19733
  function forgeZeroLaunchOwnerInput() {
19733
- const custodianEmail = bootstrapAnswer("First custodian email");
19734
- const provider = bootstrapAnswer("Bootstrap email provider (smtp/jetemail)", "jetemail");
19735
- if (provider !== "smtp" && provider !== "jetemail") {
19736
- throw new Error("Bootstrap email provider must be smtp or jetemail");
19737
- }
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;
19744
- if (provider === "jetemail") {
19745
- return {
19746
- custodianEmail,
19747
- email: { provider, from, eu: bootstrapBoolean("Use JetEmail EU processing?", "no") },
19748
- ...githubApp ? { githubApp } : {}
19749
- };
19750
- }
19734
+ const githubOAuth = { clientId: bootstrapAnswer("GitHub OAuth client ID") };
19751
19735
  return {
19752
- custodianEmail,
19753
- email: {
19754
- provider,
19755
- from,
19756
- host: bootstrapAnswer("SMTP host"),
19757
- port: bootstrapNumber("SMTP port", "587"),
19758
- user: bootstrapAnswer("SMTP user")
19759
- },
19760
- ...githubApp ? { githubApp } : {}
19736
+ custodianEmail: "info@axxra.in",
19737
+ email: { provider: "jetemail", from: "noreply@forgezero.net", eu: false },
19738
+ githubOAuth
19761
19739
  };
19762
19740
  }
19763
19741
  async function prepareForgeZeroPlatformLaunch(environment, outputDirectory, projectRoot, owner) {
@@ -19772,14 +19750,11 @@ async function prepareForgeZeroPlatformLaunch(environment, outputDirectory, proj
19772
19750
  if (!existsSync13(path))
19773
19751
  throw new Error(`${label} is missing at ${path}`);
19774
19752
  }
19775
- const root = resolve12(projectRoot);
19753
+ const root = resolve13(projectRoot);
19776
19754
  const nestedApi = join13(root, "api");
19777
19755
  const repositoryRoot = existsSync13(join13(nestedApi, "package.json")) ? nestedApi : root;
19778
19756
  const bundlePath = join13(base, "api.bundle");
19779
- const bundle = existsSync13(bundlePath) || existsSync13(`${bundlePath}.json`) ? await readBootstrapBundle(bundlePath) : await buildBootstrapBundle({ repositoryRoot, outputPath: bundlePath, branch: profile.branch });
19780
- if (bundle.manifest.branch !== profile.branch) {
19781
- throw new Error(`existing bootstrap bundle is for ${bundle.manifest.branch}, expected ${profile.branch}`);
19782
- }
19757
+ const bundle = await refreshBootstrapBundle({ repositoryRoot, outputPath: bundlePath, branch: profile.branch });
19783
19758
  return writePlatformGenesisFleet(outputDirectory, {
19784
19759
  profile,
19785
19760
  metalRequestFile,
@@ -19829,6 +19804,7 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
19829
19804
  const backupRegion = backupEndpoint ? bootstrapAnswer("Backup S3 region") : undefined;
19830
19805
  const backupBucket = backupEndpoint ? bootstrapAnswer("Backup S3 bucket") : undefined;
19831
19806
  const backupAccessKeyId = backupEndpoint ? bootstrapAnswer("Backup S3 access-key id") : undefined;
19807
+ const githubOAuthClientId = bootstrapAnswer("GitHub OAuth client ID");
19832
19808
  const cloudflareHandoffFile = genesis ? cloudflareHostHandoffPath(genesisCloudflareCheckpoint, computeReference) : bootstrapAnswer("Node-specific Cloudflare host handoff (blank for no public edge)", "");
19833
19809
  const cloudflareNodeName = cloudflareHandoffFile ? genesis ? computeReference : bootstrapAnswer("Cloudflare handoff node name", computeReference) : undefined;
19834
19810
  const databaseNetworkMode = "private-lan";
@@ -19883,6 +19859,7 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
19883
19859
  otlpTraceSampleRatio: 0.1,
19884
19860
  custodianEmail: role === "master" ? bootstrapAnswer("First custodian email") : undefined,
19885
19861
  email: emailProvider === "smtp" ? { provider: "smtp", host: smtpHost, port: smtpPort, user: smtpUser, from: emailFrom } : { provider: "jetemail", from: emailFrom, eu: jetemailEu },
19862
+ githubOAuth: { clientId: githubOAuthClientId },
19886
19863
  ...backupEndpoint && backupRegion && backupBucket && backupAccessKeyId ? {
19887
19864
  backup: { endpoint: backupEndpoint, region: backupRegion, bucket: backupBucket, accessKeyId: backupAccessKeyId }
19888
19865
  } : {},
@@ -19943,11 +19920,7 @@ async function runAttendedPlatformFleet(fleet, apply, generatedClusterBootstrapC
19943
19920
  emailSecret: environmentInput.emailSecret,
19944
19921
  cloudflareTunnelToken: environmentInput.cloudflareTunnelToken,
19945
19922
  cloudflareApiToken: environmentInput.cloudflareApiToken,
19946
- ...environmentInput.githubClientSecret ? {
19947
- githubClientSecret: environmentInput.githubClientSecret,
19948
- githubPrivateKeyBase64: environmentInput.githubPrivateKeyBase64,
19949
- githubWebhookSecret: environmentInput.githubWebhookSecret
19950
- } : {}
19923
+ githubOAuthClientSecret: environmentInput.githubOAuthClientSecret
19951
19924
  }) : await promptPlatformBootstrapSecrets(firstConfig, generatedClusterBootstrapCode);
19952
19925
  const cloudflare = await resolveCloudflareBootstrapCommandConfig(fleet.cloudflareConfigFile, {
19953
19926
  tunnelToken: secrets.cloudflareTunnelToken,
@@ -20261,7 +20234,7 @@ async function cmdUnlock(options) {
20261
20234
  if (options.key || options.userExplicit) {
20262
20235
  throw new Error("SSH/user unlock coordinates are not accepted. SSH keys cannot open a WebAuthn-PRF custody envelope; use --phrase-file or --phrase-stdin.");
20263
20236
  }
20264
- const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync16(0, "utf8").trim() : "";
20237
+ const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync17(0, "utf8").trim() : "";
20265
20238
  const phrase = phraseText ? phraseText.split(/\s+/) : [];
20266
20239
  if (phrase.length !== 24) {
20267
20240
  throw new Error(`Recovery phrase must contain exactly 24 words; received ${phrase.length}. Use --phrase-file or --phrase-stdin.`);
@@ -20494,7 +20467,7 @@ function projectFromCheckout(options) {
20494
20467
  try {
20495
20468
  return loadConfig({
20496
20469
  cwd: options.projectRoot,
20497
- readFile: (path) => existsSync13(path) ? readFileSync16(path, "utf8") : undefined
20470
+ readFile: (path) => existsSync13(path) ? readFileSync17(path, "utf8") : undefined
20498
20471
  }).config.project;
20499
20472
  } catch (cause) {
20500
20473
  throw new Error(`No --project was given and the checkout has no usable .fz/config.json: ${cause instanceof Error ? cause.message : String(cause)}`);
@@ -20561,7 +20534,7 @@ async function cmdDeploy(options, args) {
20561
20534
  throw new Error("typed runtime initialization selects its tested software and attestation from --runtime/--isolation; do not combine --software or --attestation");
20562
20535
  }
20563
20536
  initializeTypeScriptDeployment(options.projectRoot, {
20564
- name: options.projectName ?? basename3(resolve12(options.projectRoot)),
20537
+ name: options.projectName ?? basename3(resolve13(options.projectRoot)),
20565
20538
  force: options.force,
20566
20539
  runtime: options.deployRuntime,
20567
20540
  operatingSystem: options.deployOperatingSystem,
@@ -20616,7 +20589,7 @@ async function cmdDeploy(options, args) {
20616
20589
  return problems.length === 0 ? 0 : 1;
20617
20590
  }
20618
20591
  if (operation === "check" || operation === "sync") {
20619
- if (existsSync13(resolve12(options.projectRoot, DEPLOY_SOURCE_FILE))) {
20592
+ if (existsSync13(resolve13(options.projectRoot, DEPLOY_SOURCE_FILE))) {
20620
20593
  const expected = await compileDeploymentProject(options.projectRoot, { write: false });
20621
20594
  const actual = inspectCompiledDeployment(options.projectRoot);
20622
20595
  const current2 = canonicalJson(expected.plan) === canonicalJson(actual.plan);
@@ -20692,7 +20665,7 @@ async function cmdDeploy(options, args) {
20692
20665
  branch: options.branch,
20693
20666
  cloneUrl: required(options.cloneUrl, "--clone-url"),
20694
20667
  sourceAuth,
20695
- ...options.knownHostsFile ? { knownHosts: readFileSync16(options.knownHostsFile, "utf8") } : {},
20668
+ ...options.knownHostsFile ? { knownHosts: readFileSync17(options.knownHostsFile, "utf8") } : {},
20696
20669
  projectKey
20697
20670
  } });
20698
20671
  const pipelineKey2 = String(created.pipelineKey);
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
366
366
  }
367
367
 
368
368
  // src/version.ts
369
- var VERSION = "0.1.103";
369
+ var VERSION = "0.1.108";
370
370
 
371
371
  // src/otel-collector.ts
372
372
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";