@forgezero/agent 0.1.125 → 0.1.128

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.
@@ -917,7 +917,7 @@ async function postSignedNode(options, path, body) {
917
917
  }
918
918
 
919
919
  // src/version.ts
920
- var VERSION3 = "0.1.125";
920
+ var VERSION3 = "0.1.128";
921
921
 
922
922
  // src/agent-heartbeat.ts
923
923
  function readAgentHostMetrics() {
@@ -123,7 +123,9 @@ export interface EnrolledComputeBootstrapSecrets {
123
123
  }
124
124
  export type BootstrapSecrets = PlatformBootstrapSecrets | EnrolledComputeBootstrapSecrets;
125
125
  export declare function validateEnrolledComputeBootstrapSecrets(config: EnrolledComputeActivationConfig, input: unknown): EnrolledComputeBootstrapSecrets;
126
- export declare function validatePlatformBootstrapSecrets(config: PlatformBootstrapConfig, input: unknown): PlatformBootstrapSecrets;
126
+ export declare function validatePlatformBootstrapSecrets(config: PlatformBootstrapConfig, input: unknown, options?: {
127
+ allowStoredCloudflareCredentials?: boolean;
128
+ }): PlatformBootstrapSecrets;
127
129
  export interface BootstrapStep {
128
130
  id: string;
129
131
  label: string;
package/dist/bootstrap.js CHANGED
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.125";
1463
+ var VERSION = "0.1.128";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3805,7 +3805,7 @@ function validateEnrolledComputeBootstrapSecrets(config, input) {
3805
3805
  ...cloudflareConfigured ? validateCloudflareBootstrapSecretPair(source) : {}
3806
3806
  };
3807
3807
  }
3808
- function validatePlatformBootstrapSecrets(config, input) {
3808
+ function validatePlatformBootstrapSecrets(config, input, options = {}) {
3809
3809
  if (!input || typeof input !== "object" || Array.isArray(input))
3810
3810
  throw new Error("bootstrap credential input must be an object");
3811
3811
  const source = input;
@@ -3833,7 +3833,8 @@ function validatePlatformBootstrapSecrets(config, input) {
3833
3833
  throw new Error("backup credential is malformed");
3834
3834
  }
3835
3835
  const cloudflareConfigured = Boolean(config.cloudflareHandoff || config.runtime.environment.cloudflare);
3836
- if (cloudflareConfigured !== Boolean(cloudflareTunnelToken && cloudflareApiToken)) {
3836
+ const cloudflareCredentialsSupplied = Boolean(cloudflareTunnelToken || cloudflareApiToken);
3837
+ if (!cloudflareConfigured && cloudflareCredentialsSupplied || cloudflareConfigured && !cloudflareCredentialsSupplied && !options.allowStoredCloudflareCredentials || Boolean(cloudflareTunnelToken) !== Boolean(cloudflareApiToken)) {
3837
3838
  throw new Error("Cloudflare configuration requires attended CF_TUNNEL_TOKEN and CF_API_TOKEN together");
3838
3839
  }
3839
3840
  if (cloudflareTunnelToken)
@@ -4759,7 +4760,9 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4759
4760
  const platformPrivate = config.kind === "platform" ? (() => {
4760
4761
  if (!secrets)
4761
4762
  throw new Error("platform apply requires attended credentials on stdin");
4762
- const checked4 = validatePlatformBootstrapSecrets(config, secrets);
4763
+ const checked4 = validatePlatformBootstrapSecrets(config, secrets, {
4764
+ allowStoredCloudflareCredentials: Boolean(installed?.cloudflare && config.cloudflareHandoff && !cloudflare)
4765
+ });
4763
4766
  return {
4764
4767
  root: checked4.clusterBootstrapCode,
4765
4768
  email: checked4.emailSecret,
package/dist/fz-agent.js CHANGED
@@ -4049,7 +4049,7 @@ function combineKEMS(realSeedLen, realMsgLen, expandSeed, combiner, ...kems) {
4049
4049
  var x25519kem = /* @__PURE__ */ ecdhKem(x25519);
4050
4050
  var ml_kem768_x25519 = /* @__PURE__ */ (() => combineKEMS(32, 32, expandSeedXof(shake256), (pk, ct, ss) => sha3_256(concatBytes2(ss[0], ss[1], ct[1], pk[1], asciiToBytes("\\.//^\\"))), ml_kem768, x25519kem))();
4051
4051
 
4052
- // ../../node_modules/.bun/@forgezero+runtime@0.1.17+cd212ec15d36da0a/node_modules/@forgezero/runtime/dist/identity.js
4052
+ // ../runtime/dist/identity.js
4053
4053
  var ENCODER = new TextEncoder;
4054
4054
  var b64 = toBase64Url;
4055
4055
  var un64 = fromBase64Url;
@@ -4196,7 +4196,7 @@ function signRequest(keys, nodeKey, args) {
4196
4196
  };
4197
4197
  }
4198
4198
 
4199
- // ../../node_modules/.bun/@forgezero+vault@0.1.22+3f759277d50bc01a/node_modules/@forgezero/vault/dist/index.js
4199
+ // ../vault/dist/index.js
4200
4200
  var DEFAULT_SOCKET = "/run/forgezero/vault.sock";
4201
4201
 
4202
4202
  // src/socket.ts
@@ -4692,7 +4692,7 @@ import { chmodSync as chmodSync4, existsSync as existsSync4, lstatSync as lstatS
4692
4692
  import { createHash as createHash5, randomUUID } from "crypto";
4693
4693
  import { dirname as dirname3, isAbsolute as isAbsolute2, join as join2 } from "path";
4694
4694
 
4695
- // ../../node_modules/.bun/@forgezero+runtime@0.1.17+cd212ec15d36da0a/node_modules/@forgezero/runtime/dist/queue.js
4695
+ // ../runtime/dist/queue.js
4696
4696
  class QueueStoppedError extends Error {
4697
4697
  constructor() {
4698
4698
  super("queue: stopped before this task could run");
@@ -9640,7 +9640,7 @@ async function writeAndCloseProcessInput(input, value) {
9640
9640
  }
9641
9641
 
9642
9642
  // src/version.ts
9643
- var VERSION2 = "0.1.125";
9643
+ var VERSION2 = "0.1.128";
9644
9644
 
9645
9645
  // src/ssh-bootstrap.ts
9646
9646
  class SshBootstrapError extends Error {
@@ -9655,7 +9655,7 @@ var defaultExec = async (argv2, options = {}) => {
9655
9655
  stdin: options.stdin === undefined ? "ignore" : "pipe",
9656
9656
  stdout: "pipe",
9657
9657
  stderr: "pipe",
9658
- env: { PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin", LANG: "C", LC_ALL: "C" }
9658
+ env: { PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin", LANG: "C", LC_ALL: "C", ...options.env }
9659
9659
  });
9660
9660
  if (options.stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
9661
9661
  await writeAndCloseProcessInput(child.stdin, options.stdin);
@@ -9682,7 +9682,7 @@ var validateClaim = (claim) => {
9682
9682
  }
9683
9683
  if (operatingSystem?.id !== "ubuntu" || !["24.04", "26.04"].includes(operatingSystem.version) || operatingSystem.architecture !== "x64")
9684
9684
  throw new SshBootstrapError("CLAIM_INVALID");
9685
- if (!claim.target.credential || claim.target.credential.source !== "runner" && claim.target.credential.source !== "vault" || claim.target.credential.source === "vault" && (typeof claim.target.credential.privateKey !== "string" || claim.target.credential.privateKey.length > 32768 || !claim.target.credential.privateKey.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----") || !claim.target.credential.privateKey.trimEnd().endsWith("-----END OPENSSH PRIVATE KEY-----"))) {
9685
+ if (!/^ssh-(?:ed25519|rsa|ecdsa-[^\s]+) [A-Za-z0-9+/]+={0,3}(?:\s+.*)?$/.test(claim.runnerSshPublicKey) || !claim.target.credential || claim.target.credential.source !== "runner" && claim.target.credential.source !== "vault" || claim.target.credential.source === "vault" && (claim.target.credential.kind === "private-key" && (typeof claim.target.credential.privateKey !== "string" || claim.target.credential.privateKey.length > 32768 || !claim.target.credential.privateKey.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----") || !claim.target.credential.privateKey.trimEnd().endsWith("-----END OPENSSH PRIVATE KEY-----")) || claim.target.credential.kind === "password" && (typeof claim.target.credential.password !== "string" || claim.target.credential.password.length < 1 || claim.target.credential.password.length > 4096))) {
9686
9686
  throw new SshBootstrapError("CLAIM_INVALID");
9687
9687
  }
9688
9688
  };
@@ -9693,14 +9693,7 @@ var target = (claim, pinned) => {
9693
9693
  var sshOptions = (claim, pinned, knownHosts, keyPath) => [
9694
9694
  "-F",
9695
9695
  "/dev/null",
9696
- "-o",
9697
- "BatchMode=yes",
9698
- "-o",
9699
- "IdentitiesOnly=yes",
9700
- "-o",
9701
- `IdentityFile=${keyPath}`,
9702
- "-o",
9703
- "IdentityAgent=none",
9696
+ ...claim.target.credential.source === "vault" && claim.target.credential.kind === "password" ? ["-o", "BatchMode=no", "-o", "IdentitiesOnly=yes", "-o", "PubkeyAuthentication=no", "-o", "PreferredAuthentications=password,keyboard-interactive", "-o", "NumberOfPasswordPrompts=1"] : ["-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", "-o", `IdentityFile=${keyPath}`, "-o", "IdentityAgent=none"],
9704
9697
  "-o",
9705
9698
  `UserKnownHostsFile=${knownHosts}`,
9706
9699
  "-o",
@@ -9787,18 +9780,30 @@ async function executeSshBootstrap(claim, options) {
9787
9780
  const api = new URL(options.platformApiUrl);
9788
9781
  if (api.protocol !== "https:" || api.username || api.password || api.search || api.hash)
9789
9782
  throw new SshBootstrapError("TARGET_API_INVALID");
9790
- const exec = options.exec ?? defaultExec;
9783
+ const baseExec = options.exec ?? defaultExec;
9791
9784
  const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-ssh-bootstrap-"));
9792
9785
  chmodSync7(directory, 448);
9786
+ let exec = baseExec;
9793
9787
  let sshKeyPath = options.sshKeyPath;
9794
9788
  let remoteDirectory = "";
9795
9789
  let pinned;
9796
9790
  try {
9797
- if (claim.target.credential.source === "vault") {
9791
+ if (claim.target.credential.source === "vault" && claim.target.credential.kind === "private-key") {
9798
9792
  sshKeyPath = join3(directory, "identity");
9799
9793
  writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
9800
9794
  `, { mode: 384, flag: "wx" });
9801
9795
  }
9796
+ if (claim.target.credential.source === "vault" && claim.target.credential.kind === "password") {
9797
+ const askpass = join3(directory, "askpass");
9798
+ writeFileSync5(askpass, `#!/usr/bin/env bun
9799
+ process.stdout.write(process.env.FZ_SSH_PASSWORD ?? "")
9800
+ `, { mode: 448, flag: "wx" });
9801
+ const password = claim.target.credential.password;
9802
+ exec = (argv2, command = {}) => baseExec(argv2, {
9803
+ ...command,
9804
+ env: { ...command.env, DISPLAY: "forgezero:0", SSH_ASKPASS: askpass, SSH_ASKPASS_REQUIRE: "force", FZ_SSH_PASSWORD: password }
9805
+ });
9806
+ }
9802
9807
  const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
9803
9808
  const resolved = await resolvePinnedGitTarget(`ssh://${claim.target.user}@${sourceHost}:${claim.target.port}/`, options.resolveHost);
9804
9809
  if (!resolved || resolved.protocol !== "ssh")
@@ -9809,6 +9814,21 @@ async function executeSshBootstrap(claim, options) {
9809
9814
  remoteDirectory = await checked(exec, ["ssh", ...ssh, target(claim, pinned), "--", "mktemp", "-d", "/tmp/forgezero-bootstrap.XXXXXXXX"], "SSH_UNREACHABLE");
9810
9815
  if (!/^\/tmp\/forgezero-bootstrap\.[A-Za-z0-9]{8}$/.test(remoteDirectory))
9811
9816
  throw new SshBootstrapError("REMOTE_PATH_INVALID");
9817
+ const destination = target(claim, pinned);
9818
+ const passwd = await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/getent", "passwd", claim.target.user]);
9819
+ const home = passwd.split(":")[5] ?? "";
9820
+ if (!home.startsWith("/") || /[\r\n]/.test(home))
9821
+ throw new SshBootstrapError("SSH_IDENTITY_INSTALL_FAILED");
9822
+ await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-d", "-m", "0700", `${home}/.ssh`]);
9823
+ const existing = await remote(exec, ssh, destination, "SSH_IDENTITY_READ_FAILED", ["/usr/bin/cat", `${home}/.ssh/authorized_keys`]).catch(() => "");
9824
+ const authorized = [...new Set([...existing.split(`
9825
+ `).map((line) => line.trim()).filter(Boolean), claim.runnerSshPublicKey])].join(`
9826
+ `) + `
9827
+ `;
9828
+ const authorizedPath = join3(directory, "authorized_keys");
9829
+ writeFileSync5(authorizedPath, authorized, { mode: 384, flag: "wx" });
9830
+ await checked(exec, ["scp", ...scpOptions(claim, pinned, knownHosts, sshKeyPath), authorizedPath, `${destination}:${remoteDirectory}/authorized_keys`], "SSH_IDENTITY_INSTALL_FAILED");
9831
+ await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-m", "0600", `${remoteDirectory}/authorized_keys`, `${home}/.ssh/authorized_keys`]);
9812
9832
  const config = {
9813
9833
  kind: "enrolled-compute",
9814
9834
  operatingSystem: claim.target.operatingSystem,
@@ -9844,7 +9864,6 @@ async function executeSshBootstrap(claim, options) {
9844
9864
  join3(directory, name),
9845
9865
  `${target(claim, pinned)}:${remoteDirectory}/${name}`
9846
9866
  ], "SCP_FAILED", { secret: name === "enrol.token" });
9847
- const destination = target(claim, pinned);
9848
9867
  const version = await remote(exec, ssh, destination, "REMOTE_BUN_CHECK_FAILED", ["/usr/local/bin/bun", "--version"]).catch(() => "");
9849
9868
  if (version !== PINNED_BUN_VERSION) {
9850
9869
  await remote(exec, ssh, destination, "REMOTE_PREREQUISITES_FAILED", [
@@ -10093,6 +10112,7 @@ var validateMetalClaim = (claim) => {
10093
10112
  realm: claim.tenantKey,
10094
10113
  projectKey: claim.projectKey,
10095
10114
  environmentKey: claim.environmentKey,
10115
+ runnerSshPublicKey: claim.runnerSshPublicKey,
10096
10116
  target: claim.target,
10097
10117
  enrolmentToken: `fze_${"A".repeat(43)}`
10098
10118
  });
@@ -10104,18 +10124,30 @@ async function executeMetalAdmission(claim, options) {
10104
10124
  if (api.protocol !== "https:" || api.username || api.password || api.search || api.hash || telemetry.protocol !== "https:" || telemetry.username || telemetry.password || telemetry.search || telemetry.hash) {
10105
10125
  throw new SshBootstrapError("TARGET_COORDINATE_INVALID");
10106
10126
  }
10107
- const exec = options.exec ?? defaultExec;
10127
+ const baseExec = options.exec ?? defaultExec;
10108
10128
  const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-metal-admission-"));
10109
10129
  chmodSync7(directory, 448);
10130
+ let exec = baseExec;
10110
10131
  let sshKeyPath = options.sshKeyPath;
10111
10132
  let remoteDirectory = "";
10112
10133
  let pinned;
10113
10134
  try {
10114
- if (claim.target.credential.source === "vault") {
10135
+ if (claim.target.credential.source === "vault" && claim.target.credential.kind === "private-key") {
10115
10136
  sshKeyPath = join3(directory, "identity");
10116
10137
  writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
10117
10138
  `, { mode: 384, flag: "wx" });
10118
10139
  }
10140
+ if (claim.target.credential.source === "vault" && claim.target.credential.kind === "password") {
10141
+ const askpass = join3(directory, "askpass");
10142
+ writeFileSync5(askpass, `#!/usr/bin/env bun
10143
+ process.stdout.write(process.env.FZ_SSH_PASSWORD ?? "")
10144
+ `, { mode: 448, flag: "wx" });
10145
+ const password = claim.target.credential.password;
10146
+ exec = (argv2, command = {}) => baseExec(argv2, {
10147
+ ...command,
10148
+ env: { ...command.env, DISPLAY: "forgezero:0", SSH_ASKPASS: askpass, SSH_ASKPASS_REQUIRE: "force", FZ_SSH_PASSWORD: password }
10149
+ });
10150
+ }
10119
10151
  const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
10120
10152
  const resolved = await resolvePinnedGitTarget(`ssh://${claim.target.user}@${sourceHost}:${claim.target.port}/`, options.resolveHost);
10121
10153
  if (!resolved || resolved.protocol !== "ssh")
@@ -10127,6 +10159,20 @@ async function executeMetalAdmission(claim, options) {
10127
10159
  remoteDirectory = await checked(exec, ["ssh", ...ssh, destination, "--", "mktemp", "-d", "/tmp/forgezero-metal-admission.XXXXXXXX"], "SSH_UNREACHABLE");
10128
10160
  if (!/^\/tmp\/forgezero-metal-admission\.[A-Za-z0-9]{8}$/.test(remoteDirectory))
10129
10161
  throw new SshBootstrapError("REMOTE_PATH_INVALID");
10162
+ const passwd = await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/getent", "passwd", claim.target.user]);
10163
+ const home = passwd.split(":")[5] ?? "";
10164
+ if (!home.startsWith("/") || /[\r\n]/.test(home))
10165
+ throw new SshBootstrapError("SSH_IDENTITY_INSTALL_FAILED");
10166
+ await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-d", "-m", "0700", `${home}/.ssh`]);
10167
+ const existing = await remote(exec, ssh, destination, "SSH_IDENTITY_READ_FAILED", ["/usr/bin/cat", `${home}/.ssh/authorized_keys`]).catch(() => "");
10168
+ const authorized = [...new Set([...existing.split(`
10169
+ `).map((line) => line.trim()).filter(Boolean), claim.runnerSshPublicKey])].join(`
10170
+ `) + `
10171
+ `;
10172
+ const authorizedPath = join3(directory, "authorized_keys");
10173
+ writeFileSync5(authorizedPath, authorized, { mode: 384, flag: "wx" });
10174
+ await checked(exec, ["scp", ...scpOptions(claim, pinned, knownHosts, sshKeyPath), authorizedPath, `${destination}:${remoteDirectory}/authorized_keys`], "SSH_IDENTITY_INSTALL_FAILED");
10175
+ await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-m", "0600", `${remoteDirectory}/authorized_keys`, `${home}/.ssh/authorized_keys`]);
10130
10176
  const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
10131
10177
  const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
10132
10178
  for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
@@ -11363,7 +11409,7 @@ function requestDeploymentCommand(input, socketPath = DEFAULT_DEPLOYMENT_RUNNER_
11363
11409
  // src/snp-attestation.ts
11364
11410
  import { existsSync as existsSync10 } from "fs";
11365
11411
 
11366
- // ../../node_modules/.bun/@forgezero+runtime@0.1.17+cd212ec15d36da0a/node_modules/@forgezero/runtime/dist/snp.js
11412
+ // ../runtime/dist/snp.js
11367
11413
  var REPORT_BYTES = 1184;
11368
11414
 
11369
11415
  // src/snp-attestation.ts
@@ -15302,6 +15348,26 @@ var nonEmpty = (path2) => {
15302
15348
  if (!existsSync20(path2) || !statSync5(path2).isFile() || statSync5(path2).size < 1)
15303
15349
  throw new Error(`required file missing: ${path2}`);
15304
15350
  };
15351
+ function validateRecoveryBootstrapState(value, expected) {
15352
+ if (!value || typeof value !== "object" || Array.isArray(value))
15353
+ throw new Error("bootstrap state is malformed");
15354
+ const state = value;
15355
+ if (state.format !== 2 || state.kind !== "platform" || state.environment !== expected.profile || state.databaseRole !== expected.role || state.profile !== expected.software || state.apiUrl !== expected.apiOrigin || state.nodeHostname !== expected.edgeHostname || expected.databaseAddress !== undefined && state.databaseAddress !== expected.databaseAddress)
15356
+ throw new Error("bootstrap state does not match reviewed recovery coordinates");
15357
+ }
15358
+ var exactBootstrapState = (expected) => {
15359
+ const path2 = "/var/lib/forgezero/bootstrap.json";
15360
+ nonEmpty(path2);
15361
+ if (statSync5(path2).size > 64 * 1024)
15362
+ throw new Error("bootstrap state is oversized");
15363
+ let value;
15364
+ try {
15365
+ value = JSON.parse(readFileSync15(path2, "utf8"));
15366
+ } catch {
15367
+ throw new Error("bootstrap state is malformed");
15368
+ }
15369
+ validateRecoveryBootstrapState(value, expected);
15370
+ };
15305
15371
  var option = (args, name) => {
15306
15372
  const value = args.find((arg) => arg.startsWith(`--${name}=`))?.slice(name.length + 3);
15307
15373
  if (!value || /[\0\r\n]/.test(value))
@@ -15325,15 +15391,11 @@ async function runRecoveryHost(args, run2 = execute2) {
15325
15391
  const apiOrigin = option(args, "api-origin");
15326
15392
  const edgeHostname = option(args, "edge-hostname");
15327
15393
  const databaseAddress = args.find((arg) => arg.startsWith("--db-address="))?.slice(13);
15328
- exactState("/opt/forgezero/.forge-state", `profile=${profile}`);
15329
- exactState("/opt/forgezero/.forge-state", `db_role=${role}`);
15330
- exactState("/opt/forgezero/.forge-state", `software_profile=${software}`);
15331
- if (databaseAddress)
15332
- exactState("/opt/forgezero/.forge-state", `db_address=${databaseAddress}`);
15333
- exactState("/opt/forgezero/shared/.env", "ARANGO_DB=fz");
15334
- exactState("/opt/forgezero/shared/.env", "FZ_DATABASE_MODE=platform");
15335
- exactState("/opt/forgezero/shared/.env", `API_ORIGIN=${apiOrigin}`);
15336
- exactState("/opt/forgezero/shared/.env", `FZ_NODE_HOSTNAME=${edgeHostname}`);
15394
+ exactBootstrapState({ profile, role, software, apiOrigin, edgeHostname, ...databaseAddress ? { databaseAddress } : {} });
15395
+ exactState("/opt/forgezero/shared/.env", 'ARANGO_DB="fz"');
15396
+ exactState("/opt/forgezero/shared/.env", 'FZ_DATABASE_MODE="platform"');
15397
+ exactState("/opt/forgezero/shared/.env", `API_ORIGIN="${apiOrigin}"`);
15398
+ exactState("/opt/forgezero/shared/.env", `FZ_NODE_HOSTNAME="${edgeHostname}"`);
15337
15399
  nonEmpty("/etc/forgezero/creds/arangodb-jwt.cred");
15338
15400
  const hasDatabase = role !== "none";
15339
15401
  if (hasDatabase) {
@@ -15377,7 +15439,7 @@ async function runRecoveryHost(args, run2 = execute2) {
15377
15439
  throw new Error("maintenance accepts only fz db argv");
15378
15440
  const slot = activeSlot();
15379
15441
  const properties = [
15380
- "--property=User=forgezero",
15442
+ "--property=User=forgezero-api",
15381
15443
  `--property=WorkingDirectory=/opt/forgezero/slots/${slot}`,
15382
15444
  "--property=Environment=NODE_ENV=production",
15383
15445
  "--property=EnvironmentFile=/opt/forgezero/shared/.env",