@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/host-maintenance.js
CHANGED
|
@@ -62,7 +62,7 @@ function planHostMaintenance(request, runtime = localRuntime()) {
|
|
|
62
62
|
"--pipe",
|
|
63
63
|
"--collect",
|
|
64
64
|
`--unit=forgezero-${request.operation}`,
|
|
65
|
-
"--property=User=forgezero",
|
|
65
|
+
"--property=User=forgezero-api",
|
|
66
66
|
`--property=WorkingDirectory=${workingDirectory}`,
|
|
67
67
|
`--property=EnvironmentFile=${SHARED_ENV}`,
|
|
68
68
|
"--property=Environment=NODE_ENV=production",
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
// src/version.ts
|
|
369
|
-
var VERSION = "0.1.
|
|
369
|
+
var VERSION = "0.1.128";
|
|
370
370
|
|
|
371
371
|
// src/otel-collector.ts
|
|
372
372
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -89,7 +89,7 @@ export interface OperatorPlatformFleetCoordinates {
|
|
|
89
89
|
databaseRole: 'master' | 'joiner';
|
|
90
90
|
}[];
|
|
91
91
|
}
|
|
92
|
-
export type OperatorPlatformBootstrapMode = 'apply' | 'status';
|
|
92
|
+
export type OperatorPlatformBootstrapMode = 'apply' | 'repair' | 'status';
|
|
93
93
|
export type OperatorMetalBootstrapMode = 'apply' | 'genesis' | 'genesis-cleanup' | 'rehearsal' | 'rehearsal-cleanup' | 'host-keys' | 'rehearsal-host-keys' | 'status';
|
|
94
94
|
export interface OperatorPlatformBootstrapPlan {
|
|
95
95
|
kind: 'platform-remote';
|
|
@@ -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,
|
|
@@ -6713,12 +6716,12 @@ function planOperatorMetalBootstrap(request, mode) {
|
|
|
6713
6716
|
var secretSources = (config) => [
|
|
6714
6717
|
...config.cloudflareHandoff ? [["cloudflare-handoff", config.cloudflareHandoff.handoffFile]] : []
|
|
6715
6718
|
];
|
|
6716
|
-
var attendedSecretNames = (config) => [
|
|
6719
|
+
var attendedSecretNames = (config, options = {}) => [
|
|
6717
6720
|
"cluster-bootstrap-code",
|
|
6718
6721
|
config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : "fz_smtp.password",
|
|
6719
6722
|
...config.enrolment.source === "api-token" ? ["enrol-token"] : [],
|
|
6720
6723
|
...config.runtime.environment.backup ? ["backup.s3.secretAccessKey"] : [],
|
|
6721
|
-
|
|
6724
|
+
...!options.reuseBoundCloudflare && (config.cloudflareHandoff || config.runtime.environment.cloudflare) ? ["CF_TUNNEL_TOKEN", "CF_API_TOKEN"] : []
|
|
6722
6725
|
];
|
|
6723
6726
|
function planOperatorPlatformBootstrap(request, mode) {
|
|
6724
6727
|
const config = readBootstrapConfig(request.platformConfigFile);
|
|
@@ -6742,7 +6745,10 @@ function planOperatorPlatformBootstrap(request, mode) {
|
|
|
6742
6745
|
"run typed fz bootstrap platform --apply from the local bundle",
|
|
6743
6746
|
"remove transient local and remote staging data"
|
|
6744
6747
|
],
|
|
6745
|
-
secretInputs: mode === "apply"
|
|
6748
|
+
secretInputs: mode === "apply" || mode === "repair" ? [
|
|
6749
|
+
...attendedSecretNames(config, { reuseBoundCloudflare: mode === "repair" }),
|
|
6750
|
+
...mode === "repair" ? [] : secretSources(config).map(([name]) => name)
|
|
6751
|
+
] : []
|
|
6746
6752
|
};
|
|
6747
6753
|
}
|
|
6748
6754
|
var fleetConfigIdentity = (config) => {
|
|
@@ -7172,10 +7178,10 @@ async function withOperatorGuestTransport(request, evidence, callback, options =
|
|
|
7172
7178
|
rmSync6(directory, { recursive: true, force: true });
|
|
7173
7179
|
}
|
|
7174
7180
|
}
|
|
7175
|
-
async function stageConfig(config, directory) {
|
|
7181
|
+
async function stageConfig(config, directory, options = {}) {
|
|
7176
7182
|
const rewritten = structuredClone(config);
|
|
7177
7183
|
const staged = [];
|
|
7178
|
-
for (const [name, source] of secretSources(config)) {
|
|
7184
|
+
for (const [name, source] of options.reuseBoundCloudflare ? [] : secretSources(config)) {
|
|
7179
7185
|
const bytes = ownerFile(source, SECRET_LIMIT, name);
|
|
7180
7186
|
const local = join6(directory, name);
|
|
7181
7187
|
writeFileSync8(local, bytes, { mode: 384, flag: "wx" });
|
|
@@ -7184,6 +7190,9 @@ async function stageConfig(config, directory) {
|
|
|
7184
7190
|
if (name === "cloudflare-handoff")
|
|
7185
7191
|
rewritten.cloudflareHandoff.handoffFile = remotePath;
|
|
7186
7192
|
}
|
|
7193
|
+
if (options.reuseBoundCloudflare && rewritten.cloudflareHandoff) {
|
|
7194
|
+
rewritten.cloudflareHandoff.handoffFile = `${REMOTE_STAGE}/cloudflare-handoff`;
|
|
7195
|
+
}
|
|
7187
7196
|
const bundle = await readBootstrapBundle(config.bootstrapBundle.bundleFile, config.bootstrapBundle.manifestFile);
|
|
7188
7197
|
if (bundle.manifest.branch !== config.bootstrapBundle.branch) {
|
|
7189
7198
|
throw new Error("reviewed bootstrap config branch does not match the bundle manifest");
|
|
@@ -7290,8 +7299,12 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
|
|
|
7290
7299
|
const config = readBootstrapConfig(request.platformConfigFile);
|
|
7291
7300
|
if (config.kind !== "platform")
|
|
7292
7301
|
throw new Error("operator bootstrap requires a platform config");
|
|
7293
|
-
const secrets = mode === "apply" ? validatePlatformBootstrapSecrets(config, options.secrets
|
|
7294
|
-
|
|
7302
|
+
const secrets = mode === "apply" || mode === "repair" ? validatePlatformBootstrapSecrets(config, options.secrets, {
|
|
7303
|
+
allowStoredCloudflareCredentials: mode === "repair"
|
|
7304
|
+
}) : undefined;
|
|
7305
|
+
const staged = await stageConfig(config, directory, {
|
|
7306
|
+
reuseBoundCloudflare: mode === "repair"
|
|
7307
|
+
});
|
|
7295
7308
|
await installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options);
|
|
7296
7309
|
await copy(exec, request, knownHosts, staged.path, `${remoteTemp}/platform-config.json`, true);
|
|
7297
7310
|
for (const name of staged.files)
|
|
@@ -7302,7 +7315,14 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
|
|
|
7302
7315
|
await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/bin/install", "-m", "0600", `${remoteTemp}/${name}`, `${REMOTE_STAGE}/${name}`], "remote bootstrap handoff", true);
|
|
7303
7316
|
for (const { name } of staged.bundleFiles)
|
|
7304
7317
|
await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/bin/install", "-m", "0644", `${remoteTemp}/${name}`, `${REMOTE_STAGE}/${name}`], "remote bootstrap bundle", true);
|
|
7305
|
-
const command = [
|
|
7318
|
+
const command = [
|
|
7319
|
+
"/usr/bin/sudo",
|
|
7320
|
+
"-n",
|
|
7321
|
+
"/usr/local/bin/fz",
|
|
7322
|
+
"bootstrap",
|
|
7323
|
+
mode === "repair" ? "repair" : "platform",
|
|
7324
|
+
"credentials-stdin"
|
|
7325
|
+
];
|
|
7306
7326
|
command.push("--bootstrap-config", `${REMOTE_STAGE}/platform-config.json`, "--apply");
|
|
7307
7327
|
const output = await remote(exec, request, knownHosts, command, "remote typed bootstrap", mode === "apply", secrets ? `${JSON.stringify(secrets)}
|
|
7308
7328
|
` : undefined, mode === "apply");
|
|
@@ -3020,7 +3020,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
3020
3020
|
}
|
|
3021
3021
|
|
|
3022
3022
|
// src/version.ts
|
|
3023
|
-
var VERSION3 = "0.1.
|
|
3023
|
+
var VERSION3 = "0.1.128";
|
|
3024
3024
|
|
|
3025
3025
|
// src/egress-policy.ts
|
|
3026
3026
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/provision.js
CHANGED
|
@@ -3020,7 +3020,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
3020
3020
|
}
|
|
3021
3021
|
|
|
3022
3022
|
// src/version.ts
|
|
3023
|
-
var VERSION3 = "0.1.
|
|
3023
|
+
var VERSION3 = "0.1.128";
|
|
3024
3024
|
|
|
3025
3025
|
// src/egress-policy.ts
|
|
3026
3026
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/recovery-host.d.ts
CHANGED
|
@@ -4,4 +4,13 @@ export interface RecoveryHostResult {
|
|
|
4
4
|
stderr: string;
|
|
5
5
|
}
|
|
6
6
|
export type RecoveryHostExec = (argv: readonly string[]) => Promise<RecoveryHostResult>;
|
|
7
|
+
export interface RecoveryBootstrapCoordinates {
|
|
8
|
+
profile: string;
|
|
9
|
+
role: string;
|
|
10
|
+
software: string;
|
|
11
|
+
apiOrigin: string;
|
|
12
|
+
edgeHostname: string;
|
|
13
|
+
databaseAddress?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function validateRecoveryBootstrapState(value: unknown, expected: RecoveryBootstrapCoordinates): void;
|
|
7
16
|
export declare function runRecoveryHost(args: readonly string[], run?: RecoveryHostExec): Promise<void>;
|
package/dist/recovery-host.js
CHANGED
|
@@ -28,6 +28,26 @@ var nonEmpty = (path) => {
|
|
|
28
28
|
if (!existsSync(path) || !statSync(path).isFile() || statSync(path).size < 1)
|
|
29
29
|
throw new Error(`required file missing: ${path}`);
|
|
30
30
|
};
|
|
31
|
+
function validateRecoveryBootstrapState(value, expected) {
|
|
32
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
33
|
+
throw new Error("bootstrap state is malformed");
|
|
34
|
+
const state = value;
|
|
35
|
+
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)
|
|
36
|
+
throw new Error("bootstrap state does not match reviewed recovery coordinates");
|
|
37
|
+
}
|
|
38
|
+
var exactBootstrapState = (expected) => {
|
|
39
|
+
const path = "/var/lib/forgezero/bootstrap.json";
|
|
40
|
+
nonEmpty(path);
|
|
41
|
+
if (statSync(path).size > 64 * 1024)
|
|
42
|
+
throw new Error("bootstrap state is oversized");
|
|
43
|
+
let value;
|
|
44
|
+
try {
|
|
45
|
+
value = JSON.parse(readFileSync(path, "utf8"));
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error("bootstrap state is malformed");
|
|
48
|
+
}
|
|
49
|
+
validateRecoveryBootstrapState(value, expected);
|
|
50
|
+
};
|
|
31
51
|
var option = (args, name) => {
|
|
32
52
|
const value = args.find((arg) => arg.startsWith(`--${name}=`))?.slice(name.length + 3);
|
|
33
53
|
if (!value || /[\0\r\n]/.test(value))
|
|
@@ -51,15 +71,11 @@ async function runRecoveryHost(args, run = execute) {
|
|
|
51
71
|
const apiOrigin = option(args, "api-origin");
|
|
52
72
|
const edgeHostname = option(args, "edge-hostname");
|
|
53
73
|
const databaseAddress = args.find((arg) => arg.startsWith("--db-address="))?.slice(13);
|
|
54
|
-
|
|
55
|
-
exactState("/opt/forgezero/.
|
|
56
|
-
exactState("/opt/forgezero/.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
exactState("/opt/forgezero/shared/.env", "ARANGO_DB=fz");
|
|
60
|
-
exactState("/opt/forgezero/shared/.env", "FZ_DATABASE_MODE=platform");
|
|
61
|
-
exactState("/opt/forgezero/shared/.env", `API_ORIGIN=${apiOrigin}`);
|
|
62
|
-
exactState("/opt/forgezero/shared/.env", `FZ_NODE_HOSTNAME=${edgeHostname}`);
|
|
74
|
+
exactBootstrapState({ profile, role, software, apiOrigin, edgeHostname, ...databaseAddress ? { databaseAddress } : {} });
|
|
75
|
+
exactState("/opt/forgezero/shared/.env", 'ARANGO_DB="fz"');
|
|
76
|
+
exactState("/opt/forgezero/shared/.env", 'FZ_DATABASE_MODE="platform"');
|
|
77
|
+
exactState("/opt/forgezero/shared/.env", `API_ORIGIN="${apiOrigin}"`);
|
|
78
|
+
exactState("/opt/forgezero/shared/.env", `FZ_NODE_HOSTNAME="${edgeHostname}"`);
|
|
63
79
|
nonEmpty("/etc/forgezero/creds/arangodb-jwt.cred");
|
|
64
80
|
const hasDatabase = role !== "none";
|
|
65
81
|
if (hasDatabase) {
|
|
@@ -103,7 +119,7 @@ async function runRecoveryHost(args, run = execute) {
|
|
|
103
119
|
throw new Error("maintenance accepts only fz db argv");
|
|
104
120
|
const slot = activeSlot();
|
|
105
121
|
const properties = [
|
|
106
|
-
"--property=User=forgezero",
|
|
122
|
+
"--property=User=forgezero-api",
|
|
107
123
|
`--property=WorkingDirectory=/opt/forgezero/slots/${slot}`,
|
|
108
124
|
"--property=Environment=NODE_ENV=production",
|
|
109
125
|
"--property=EnvironmentFile=/opt/forgezero/shared/.env",
|
|
@@ -120,5 +136,6 @@ async function runRecoveryHost(args, run = execute) {
|
|
|
120
136
|
throw new Error("unknown recovery-host action");
|
|
121
137
|
}
|
|
122
138
|
export {
|
|
139
|
+
validateRecoveryBootstrapState,
|
|
123
140
|
runRecoveryHost
|
|
124
141
|
};
|
package/dist/ssh-bootstrap.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface RemoteSshBootstrapClaim {
|
|
|
10
10
|
realm: string;
|
|
11
11
|
projectKey: string;
|
|
12
12
|
environmentKey: string;
|
|
13
|
+
runnerSshPublicKey: string;
|
|
13
14
|
target: {
|
|
14
15
|
host: string;
|
|
15
16
|
port: number;
|
|
@@ -25,7 +26,12 @@ export interface RemoteSshBootstrapClaim {
|
|
|
25
26
|
source: 'runner';
|
|
26
27
|
} | {
|
|
27
28
|
source: 'vault';
|
|
29
|
+
kind: 'private-key';
|
|
28
30
|
privateKey: string;
|
|
31
|
+
} | {
|
|
32
|
+
source: 'vault';
|
|
33
|
+
kind: 'password';
|
|
34
|
+
password: string;
|
|
29
35
|
};
|
|
30
36
|
};
|
|
31
37
|
enrolmentToken: string;
|
|
@@ -42,6 +48,7 @@ export interface RemoteMetalAdmissionClaim {
|
|
|
42
48
|
label: string;
|
|
43
49
|
regionKey: string;
|
|
44
50
|
runnerNodeKey: string;
|
|
51
|
+
runnerSshPublicKey: string;
|
|
45
52
|
challenge: string;
|
|
46
53
|
provisioning: {
|
|
47
54
|
volumeGroup: string;
|
|
@@ -109,6 +116,7 @@ export interface BootstrapCommandResult {
|
|
|
109
116
|
export type BootstrapCommand = (argv: readonly string[], options?: {
|
|
110
117
|
stdin?: string;
|
|
111
118
|
secret?: boolean;
|
|
119
|
+
env?: Record<string, string>;
|
|
112
120
|
}) => Promise<BootstrapCommandResult>;
|
|
113
121
|
export interface SshBootstrapPullOptions {
|
|
114
122
|
apiUrl: string;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** One package version shared by both public binaries. Pinned to package.json by tests. */
|
|
2
|
-
export declare const VERSION = "0.1.
|
|
2
|
+
export declare const VERSION = "0.1.128";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgezero/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.128",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "tsc --noEmit",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"@noble/post-quantum": "^0.6.1"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@forgezero/runtime": "0.1.
|
|
20
|
-
"@forgezero/vault": "0.1.
|
|
19
|
+
"@forgezero/runtime": "0.1.19",
|
|
20
|
+
"@forgezero/vault": "0.1.25",
|
|
21
21
|
"@noble/curves": "2.2.0",
|
|
22
22
|
"@noble/hashes": "2.2.0",
|
|
23
23
|
"@noble/post-quantum": "0.6.1",
|