@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.
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.d.ts +3 -1
- package/dist/bootstrap.js +7 -4
- package/dist/fz-agent.js +92 -30
- package/dist/fz.js +7554 -7326
- package/dist/host-maintenance.js +1 -1
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.d.ts +1 -1
- package/dist/operator-bootstrap.js +32 -12
- package/dist/platform-fleet-verification.js +1 -1
- package/dist/provision.js +1 -1
- package/dist/recovery-host.d.ts +9 -0
- package/dist/recovery-host.js +27 -10
- package/dist/ssh-bootstrap.d.ts +8 -0
- package/dist/version.d.ts +1 -1
- package/package.json +3 -3
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.d.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
15329
|
-
exactState("/opt/forgezero/.
|
|
15330
|
-
exactState("/opt/forgezero/.
|
|
15331
|
-
|
|
15332
|
-
|
|
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",
|