@forgezero/agent 0.1.63 → 0.1.65
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/README.md +13 -9
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.d.ts +17 -1
- package/dist/bootstrap.js +115 -68
- package/dist/cli/agent-install.d.ts +9 -0
- package/dist/deployment.d.ts +11 -0
- package/dist/fz-agent.js +120 -70
- package/dist/fz.js +391 -103
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.d.ts +42 -0
- package/dist/operator-bootstrap.js +294 -75
- package/dist/platform-fleet-verification.js +92 -66
- package/dist/provision.js +27 -26
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/metal-bootstrap.js
CHANGED
|
@@ -292,7 +292,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// src/version.ts
|
|
295
|
-
var VERSION = "0.1.
|
|
295
|
+
var VERSION = "0.1.65";
|
|
296
296
|
|
|
297
297
|
// src/otel-collector.ts
|
|
298
298
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -25,6 +25,34 @@ export interface OperatorMetalBootstrapRequest {
|
|
|
25
25
|
sshPublicKeyFiles: string[];
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
export interface OperatorPlatformBootstrapFleetRequest {
|
|
29
|
+
kind: 'platform-fleet-remote';
|
|
30
|
+
requests: readonly [string, string, string];
|
|
31
|
+
}
|
|
32
|
+
export interface OperatorPlatformBootstrapFleetPlan {
|
|
33
|
+
kind: 'platform-fleet-remote';
|
|
34
|
+
mode: OperatorPlatformBootstrapMode;
|
|
35
|
+
mutation: boolean;
|
|
36
|
+
environment: PlatformGenesisEnvironment;
|
|
37
|
+
nodes: readonly {
|
|
38
|
+
computeReference: string;
|
|
39
|
+
address: string;
|
|
40
|
+
via?: string;
|
|
41
|
+
}[];
|
|
42
|
+
secretInputs: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
export interface OperatorPlatformBootstrapFleetOutcome {
|
|
45
|
+
computeReference: string;
|
|
46
|
+
address: string;
|
|
47
|
+
ok: boolean;
|
|
48
|
+
output?: string;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface OperatorPlatformBootstrapFleetResult {
|
|
52
|
+
plan: OperatorPlatformBootstrapFleetPlan;
|
|
53
|
+
ok: boolean;
|
|
54
|
+
outcomes: readonly OperatorPlatformBootstrapFleetOutcome[];
|
|
55
|
+
}
|
|
28
56
|
export type OperatorPlatformBootstrapMode = 'apply' | 'status';
|
|
29
57
|
export type OperatorMetalBootstrapMode = 'apply' | 'genesis' | 'rehearsal' | 'rehearsal-cleanup' | 'status';
|
|
30
58
|
export interface OperatorPlatformBootstrapPlan {
|
|
@@ -68,10 +96,16 @@ export interface OperatorPlatformBootstrapOptions {
|
|
|
68
96
|
fzAgentPath?: string;
|
|
69
97
|
fzGitSshPath?: string;
|
|
70
98
|
}
|
|
99
|
+
export declare function createOperatorSshHop(input: Omit<OperatorSshHop, 'hostKeySha256'>): OperatorSshHop;
|
|
71
100
|
export declare function readOperatorPlatformBootstrapRequest(path: string): OperatorPlatformBootstrapRequest;
|
|
101
|
+
export declare function writeOperatorPlatformBootstrapRequest(path: string, request: OperatorPlatformBootstrapRequest): string;
|
|
102
|
+
export declare function readOperatorPlatformBootstrapFleetRequest(path: string): OperatorPlatformBootstrapFleetRequest;
|
|
103
|
+
export declare function writeOperatorPlatformBootstrapFleetRequest(path: string, requestFiles: readonly [string, string, string]): string;
|
|
72
104
|
export declare function readOperatorMetalBootstrapRequest(path: string): OperatorMetalBootstrapRequest;
|
|
105
|
+
export declare function writeOperatorMetalBootstrapRequest(path: string, request: OperatorMetalBootstrapRequest): string;
|
|
73
106
|
export declare function planOperatorMetalBootstrap(request: OperatorMetalBootstrapRequest, mode: OperatorMetalBootstrapMode): OperatorMetalBootstrapPlan;
|
|
74
107
|
export declare function planOperatorPlatformBootstrap(request: OperatorPlatformBootstrapRequest, mode: OperatorPlatformBootstrapMode): OperatorPlatformBootstrapPlan;
|
|
108
|
+
export declare function planOperatorPlatformFleetBootstrap(fleet: OperatorPlatformBootstrapFleetRequest, mode: OperatorPlatformBootstrapMode): OperatorPlatformBootstrapFleetPlan;
|
|
75
109
|
/**
|
|
76
110
|
* Run one reviewed operator action. No private SSH key, credential value or
|
|
77
111
|
* management token is placed in argv; OpenSSH selects the matching private key
|
|
@@ -83,6 +117,14 @@ export declare function applyOperatorPlatformBootstrap(request: OperatorPlatform
|
|
|
83
117
|
plan: OperatorPlatformBootstrapPlan;
|
|
84
118
|
output: string;
|
|
85
119
|
}>;
|
|
120
|
+
/**
|
|
121
|
+
* Run the exact three-node genesis fleet from one attended secret collection.
|
|
122
|
+
* Every request is fully validated before the first SSH connection. Nodes run
|
|
123
|
+
* concurrently because the three-member Agency cannot form sequentially.
|
|
124
|
+
*/
|
|
125
|
+
export declare function applyOperatorPlatformFleetBootstrap(fleet: OperatorPlatformBootstrapFleetRequest, mode: OperatorPlatformBootstrapMode, options?: OperatorPlatformBootstrapOptions & {
|
|
126
|
+
secrets?: PlatformBootstrapSecrets;
|
|
127
|
+
}): Promise<OperatorPlatformBootstrapFleetResult>;
|
|
86
128
|
/**
|
|
87
129
|
* Install/bootstrap one blank metal host or provision its exact three-node
|
|
88
130
|
* platform seed through the same pinned SSH transport used for guest bootstrap.
|
|
@@ -1396,7 +1396,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1396
1396
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1397
1397
|
|
|
1398
1398
|
// src/version.ts
|
|
1399
|
-
var VERSION = "0.1.
|
|
1399
|
+
var VERSION = "0.1.65";
|
|
1400
1400
|
|
|
1401
1401
|
// src/software.ts
|
|
1402
1402
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -2045,17 +2045,16 @@ function agentUnit(options) {
|
|
|
2045
2045
|
if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
|
|
2046
2046
|
throw new Error("migration pull and lifecycle profile must be supplied together");
|
|
2047
2047
|
}
|
|
2048
|
-
const
|
|
2049
|
-
if (
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
|
|
2048
|
+
const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
|
|
2049
|
+
if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath)) {
|
|
2050
|
+
throw new Error("bootstrap SSH credential and public key paths must be supplied together");
|
|
2051
|
+
}
|
|
2052
|
+
const bootstrapEnabled = Boolean(options.pullBootstrap);
|
|
2053
|
+
if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
|
|
2054
|
+
throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
|
|
2056
2055
|
}
|
|
2057
2056
|
const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
|
|
2058
|
-
const bootstrapSshCredentialPath =
|
|
2057
|
+
const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
|
|
2059
2058
|
const warpValues = [
|
|
2060
2059
|
options.warpOrganization,
|
|
2061
2060
|
options.warpClientIdCredentialPath,
|
|
@@ -2294,15 +2293,13 @@ function planProvision(options) {
|
|
|
2294
2293
|
if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
|
|
2295
2294
|
throw new Error("migration pull and lifecycle profile must be supplied together");
|
|
2296
2295
|
}
|
|
2297
|
-
const
|
|
2298
|
-
if (
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
].some(Boolean) && !bootstrapEnabled) {
|
|
2305
|
-
throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
|
|
2296
|
+
const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
|
|
2297
|
+
if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath) || options.bootstrapSshSourcePath && !bootstrapIdentityEnabled) {
|
|
2298
|
+
throw new Error("bootstrap SSH credential and public key paths must be supplied together");
|
|
2299
|
+
}
|
|
2300
|
+
const bootstrapEnabled = Boolean(options.pullBootstrap);
|
|
2301
|
+
if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
|
|
2302
|
+
throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
|
|
2306
2303
|
}
|
|
2307
2304
|
const warpValues = [
|
|
2308
2305
|
options.warpOrganization,
|
|
@@ -2312,13 +2309,13 @@ function planProvision(options) {
|
|
|
2312
2309
|
const warpEnabled = warpValues.every(Boolean);
|
|
2313
2310
|
if (warpValues.some(Boolean) && !warpEnabled)
|
|
2314
2311
|
throw new Error("WARP configuration must be supplied together");
|
|
2315
|
-
const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath
|
|
2316
|
-
if (
|
|
2317
|
-
throw new Error("direct enrolment credential
|
|
2312
|
+
const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath);
|
|
2313
|
+
if (options.enrolTokenCredentialPath && !options.enrolStatePath || options.enrolTokenSourcePath && (!options.enrolTokenCredentialPath || !options.enrolStatePath)) {
|
|
2314
|
+
throw new Error("direct enrolment credential requires its durable state path");
|
|
2318
2315
|
}
|
|
2319
2316
|
const enrolTokenSourcePath = options.enrolTokenSourcePath ? systemdPath(options.enrolTokenSourcePath, "enrolment source") : undefined;
|
|
2320
2317
|
const enrolTokenCredentialPath = enrolmentEnabled ? systemdPath(options.enrolTokenCredentialPath, "enrolment credential") : undefined;
|
|
2321
|
-
const enrolStatePath =
|
|
2318
|
+
const enrolStatePath = options.enrolStatePath ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
|
|
2322
2319
|
const enrolStateDir = enrolStatePath?.replace(/\/[^/]+$/, "");
|
|
2323
2320
|
const sourceBinPath = options.sourceBinPath ? systemdPath(options.sourceBinPath, "agent source binary") : undefined;
|
|
2324
2321
|
const binPath = options.binPath ? systemdPath(options.binPath, "agent binary") : undefined;
|
|
@@ -2330,8 +2327,8 @@ function planProvision(options) {
|
|
|
2330
2327
|
const gitPublicKeyDir = gitPublicKeyPath?.replace(/\/[^/]+$/, "");
|
|
2331
2328
|
const lifecycleProfilePath = lifecycleEnabled ? systemdPath(options.lifecycleProfilePath, "lifecycle profile") : undefined;
|
|
2332
2329
|
const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
|
|
2333
|
-
const bootstrapSshCredentialPath =
|
|
2334
|
-
const bootstrapSshPublicKeyPath =
|
|
2330
|
+
const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
|
|
2331
|
+
const bootstrapSshPublicKeyPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
|
|
2335
2332
|
const bootstrapSshSourcePath = options.bootstrapSshSourcePath ? systemdPath(options.bootstrapSshSourcePath, "bootstrap SSH private-key source") : undefined;
|
|
2336
2333
|
const bootstrapSshPublicKeyDir = bootstrapSshPublicKeyPath?.replace(/\/[^/]+$/, "");
|
|
2337
2334
|
const warpClientIdCredentialPath = warpEnabled ? systemdPath(options.warpClientIdCredentialPath, "WARP client-id credential") : undefined;
|
|
@@ -2431,7 +2428,7 @@ function planProvision(options) {
|
|
|
2431
2428
|
step("Git deploy identity directory", { kind: "directories", directories: [{ path: gitPublicKeyDir, mode: 493, owner: "root", group: "root" }] }),
|
|
2432
2429
|
step("unique encrypted Git deploy identity", { kind: "ensure-git-identity", credential: gitCredentialPath, publicKey: gitPublicKeyPath })
|
|
2433
2430
|
] : [],
|
|
2434
|
-
...
|
|
2431
|
+
...bootstrapIdentityEnabled ? [
|
|
2435
2432
|
step("bootstrap SSH public identity directory", { kind: "directories", directories: [
|
|
2436
2433
|
{ path: bootstrapSshPublicKeyDir, mode: 493, owner: "root", group: "root" }
|
|
2437
2434
|
] }),
|
|
@@ -2467,6 +2464,10 @@ function planProvision(options) {
|
|
|
2467
2464
|
{ argv: ["/usr/bin/rm", "-f", "/etc/systemd/system/forgezero-deploy-runner.socket"] },
|
|
2468
2465
|
{ argv: ["/usr/bin/systemctl", "daemon-reload"] }
|
|
2469
2466
|
] })] : [],
|
|
2467
|
+
...enrolStatePath && !enrolmentEnabled ? [step("retire consumed enrolment unit", { kind: "commands", commands: [
|
|
2468
|
+
{ argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-agent-enrol.service"], acceptedExitCodes: [0, 1, 5] },
|
|
2469
|
+
{ argv: ["/usr/bin/rm", "-f", ENROLMENT_UNIT_PATH] }
|
|
2470
|
+
] })] : [],
|
|
2470
2471
|
step("enable and converge services", { kind: "commands", commands: [
|
|
2471
2472
|
{ argv: ["/usr/bin/systemctl", "enable", ...enabledUnits] },
|
|
2472
2473
|
{ argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
|
|
@@ -4181,7 +4182,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4181
4182
|
if (connectorCapabilities?.meshConnectorToken && !host.exists(WARP_CONNECTOR_CREDENTIAL)) {
|
|
4182
4183
|
await seal(host, "CF_WARP_CONNECTOR_TOKEN", WARP_CONNECTOR_CREDENTIAL, connectorCapabilities.meshConnectorToken);
|
|
4183
4184
|
}
|
|
4184
|
-
|
|
4185
|
+
if (alreadyEnrolled) {
|
|
4186
|
+
await host.installAgent(config, "bound");
|
|
4187
|
+
} else if (config.kind === "platform") {
|
|
4188
|
+
if (bootstrapManifest)
|
|
4189
|
+
await host.installAgent(config, "bootstrap");
|
|
4190
|
+
} else {
|
|
4191
|
+
await host.installAgent(config, "enrol");
|
|
4192
|
+
await host.installAgent(config, "bound");
|
|
4193
|
+
}
|
|
4185
4194
|
if (config.kind === "platform" && config.firewall.enabled) {
|
|
4186
4195
|
await host.ensureSoftware(plan.software.filter(({ id }) => id === "ufw"));
|
|
4187
4196
|
} else
|
|
@@ -4331,7 +4340,19 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4331
4340
|
`, 384);
|
|
4332
4341
|
host.remove(config.bootstrapBundle.bundleFile);
|
|
4333
4342
|
host.remove(config.bootstrapBundle.manifestFile);
|
|
4334
|
-
|
|
4343
|
+
}
|
|
4344
|
+
if (!alreadyEnrolled) {
|
|
4345
|
+
await checked3(host, [
|
|
4346
|
+
"curl",
|
|
4347
|
+
"--fail",
|
|
4348
|
+
"--silent",
|
|
4349
|
+
"--show-error",
|
|
4350
|
+
"--max-time",
|
|
4351
|
+
"10",
|
|
4352
|
+
`http://127.0.0.1:3000${runtime.healthPath}`
|
|
4353
|
+
], "release-one API health");
|
|
4354
|
+
await host.installAgent(config, "enrol");
|
|
4355
|
+
await host.installAgent(config, "bound");
|
|
4335
4356
|
}
|
|
4336
4357
|
}
|
|
4337
4358
|
if (config.cloudflareHandoff) {
|
|
@@ -4500,6 +4521,65 @@ function readBootstrapConfig(path) {
|
|
|
4500
4521
|
}
|
|
4501
4522
|
return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync5(path, "utf8"))));
|
|
4502
4523
|
}
|
|
4524
|
+
function planBootstrapAgentInstall(config, phase, context) {
|
|
4525
|
+
const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
|
|
4526
|
+
if (phase === "bootstrap") {
|
|
4527
|
+
if (config.kind !== "platform" || hasBinding || !initialBundle) {
|
|
4528
|
+
throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
|
|
4529
|
+
}
|
|
4530
|
+
} else if (phase === "enrol") {
|
|
4531
|
+
if (hasBinding || !hasEnrolCredential) {
|
|
4532
|
+
throw new Error("Agent enrolment requires one unbound host and its sealed one-use credential");
|
|
4533
|
+
}
|
|
4534
|
+
} else if (!hasBinding) {
|
|
4535
|
+
throw new Error("bound Agent convergence requires durable enrolment state");
|
|
4536
|
+
}
|
|
4537
|
+
const bound = phase === "bound";
|
|
4538
|
+
const enrolling = phase === "enrol";
|
|
4539
|
+
const authenticated = enrolling || bound;
|
|
4540
|
+
const bootstrapRunner = platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner);
|
|
4541
|
+
const options = {
|
|
4542
|
+
capabilities,
|
|
4543
|
+
socketPath: DEFAULT_SOCKET2,
|
|
4544
|
+
seedPath: "/var/lib/forgezero/node.seed",
|
|
4545
|
+
controlSocketPath: CONTROL_SOCKET,
|
|
4546
|
+
repository: phase === "bootstrap" ? initialBundle.path : bound && config.kind === "enrolled-compute" ? config.repository : undefined,
|
|
4547
|
+
branch: phase === "bootstrap" ? initialBundle.manifest.branch : bound && config.kind === "enrolled-compute" ? config.branch : undefined,
|
|
4548
|
+
bootstrapBundlePath: phase === "bootstrap" ? initialBundle.path : undefined,
|
|
4549
|
+
bootstrapBundleManifestPath: phase === "bootstrap" ? initialBundle.manifestPath : undefined,
|
|
4550
|
+
profile: config.profile,
|
|
4551
|
+
deployRoot: config.deployRoot ?? "/opt/forgezero",
|
|
4552
|
+
deploymentCredentials: config.deploymentCredentials,
|
|
4553
|
+
publicApiUrl: config.apiUrl,
|
|
4554
|
+
gitCredentialPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
|
|
4555
|
+
gitPublicKeyPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
|
|
4556
|
+
generateGitIdentity: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey === true,
|
|
4557
|
+
pullDeployments: bound,
|
|
4558
|
+
pullMigrations: bound && config.kind === "platform",
|
|
4559
|
+
pullBootstrap: bound && bootstrapRunner,
|
|
4560
|
+
bootstrapSshCredentialPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
|
|
4561
|
+
bootstrapSshSourcePath: enrolling && config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
|
|
4562
|
+
bootstrapSshPublicKeyPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
|
|
4563
|
+
bootstrapTargetTelemetryEndpoint: bound && bootstrapRunner ? platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined : undefined,
|
|
4564
|
+
lifecycleProfilePath: bound && config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
|
|
4565
|
+
enforceEgress: true,
|
|
4566
|
+
nodeHostname: config.nodeHostname,
|
|
4567
|
+
telemetryEndpoint: config.telemetryEndpoint,
|
|
4568
|
+
binPath: "/usr/local/lib/forgezero/agent/fz-agent",
|
|
4569
|
+
sourceBinPath: PACKAGED_AGENT_BIN,
|
|
4570
|
+
...enrolling ? {
|
|
4571
|
+
enrolTokenCredentialPath: ENROL_CREDENTIAL,
|
|
4572
|
+
enrolStatePath: "/var/lib/forgezero/enrolment.json"
|
|
4573
|
+
} : bound ? { enrolStatePath: "/var/lib/forgezero/enrolment.json" } : {},
|
|
4574
|
+
...authenticated ? {
|
|
4575
|
+
apiUrl: config.apiUrl,
|
|
4576
|
+
project: config.kind === "enrolled-compute" ? config.realm : "platform",
|
|
4577
|
+
environment: config.kind === "enrolled-compute" ? undefined : config.environment,
|
|
4578
|
+
nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
|
|
4579
|
+
} : {}
|
|
4580
|
+
};
|
|
4581
|
+
return planInstall(options);
|
|
4582
|
+
}
|
|
4503
4583
|
function localBootstrapHost() {
|
|
4504
4584
|
const execute = async (argv, options = {}) => {
|
|
4505
4585
|
const child = Bun.spawn([...argv], { stdin: options.stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
|
|
@@ -4547,10 +4627,10 @@ function localBootstrapHost() {
|
|
|
4547
4627
|
throw new Error(`Agent software requirements failed: ${result.output.trim()}`);
|
|
4548
4628
|
return result;
|
|
4549
4629
|
},
|
|
4550
|
-
async installAgent(config) {
|
|
4630
|
+
async installAgent(config, phase) {
|
|
4551
4631
|
const capabilities = await readCapabilities(localRunner);
|
|
4552
|
-
const deployRoot = config.deployRoot ?? "/opt/forgezero";
|
|
4553
4632
|
const hasBinding = existsSync5("/var/lib/forgezero/enrolment.json");
|
|
4633
|
+
const hasEnrolCredential = existsSync5(ENROL_CREDENTIAL);
|
|
4554
4634
|
const initialBundle = config.kind === "platform" && !existsSync5(BOOTSTRAP_RELEASE_EVIDENCE) ? {
|
|
4555
4635
|
path: config.bootstrapBundle.bundleFile,
|
|
4556
4636
|
manifestPath: config.bootstrapBundle.manifestFile,
|
|
@@ -4571,45 +4651,11 @@ function localBootstrapHost() {
|
|
|
4571
4651
|
writeFileSync5(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
|
|
4572
4652
|
`, { mode: 256 });
|
|
4573
4653
|
}
|
|
4574
|
-
const plan =
|
|
4654
|
+
const plan = planBootstrapAgentInstall(config, phase, {
|
|
4575
4655
|
capabilities,
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
repository: initialBundle?.path ?? (config.kind === "enrolled-compute" ? config.repository : undefined),
|
|
4580
|
-
branch: initialBundle?.manifest.branch ?? (config.kind === "enrolled-compute" ? config.branch : undefined),
|
|
4581
|
-
bootstrapBundlePath: initialBundle?.path,
|
|
4582
|
-
bootstrapBundleManifestPath: initialBundle?.manifestPath,
|
|
4583
|
-
profile: config.kind === "platform" ? config.profile : config.profile,
|
|
4584
|
-
deployRoot,
|
|
4585
|
-
deploymentCredentials: config.deploymentCredentials,
|
|
4586
|
-
publicApiUrl: config.apiUrl,
|
|
4587
|
-
gitCredentialPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
|
|
4588
|
-
gitPublicKeyPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
|
|
4589
|
-
generateGitIdentity: config.kind === "enrolled-compute" && config.gitDeployKey === true,
|
|
4590
|
-
pullDeployments: hasBinding,
|
|
4591
|
-
pullMigrations: config.kind === "platform" && hasBinding,
|
|
4592
|
-
pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
|
|
4593
|
-
bootstrapSshCredentialPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
|
|
4594
|
-
bootstrapSshSourcePath: config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
|
|
4595
|
-
bootstrapSshPublicKeyPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
|
|
4596
|
-
bootstrapTargetTelemetryEndpoint: platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined,
|
|
4597
|
-
lifecycleProfilePath: config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
|
|
4598
|
-
enforceEgress: true,
|
|
4599
|
-
nodeHostname: config.nodeHostname,
|
|
4600
|
-
telemetryEndpoint: config.telemetryEndpoint,
|
|
4601
|
-
binPath: "/usr/local/lib/forgezero/agent/fz-agent",
|
|
4602
|
-
sourceBinPath: PACKAGED_AGENT_BIN,
|
|
4603
|
-
...existsSync5(ENROL_CREDENTIAL) || hasBinding ? {
|
|
4604
|
-
enrolTokenCredentialPath: existsSync5(ENROL_CREDENTIAL) ? ENROL_CREDENTIAL : undefined,
|
|
4605
|
-
enrolStatePath: "/var/lib/forgezero/enrolment.json"
|
|
4606
|
-
} : {},
|
|
4607
|
-
...hasBinding ? {
|
|
4608
|
-
apiUrl: config.apiUrl,
|
|
4609
|
-
project: config.kind === "enrolled-compute" ? config.realm : "platform",
|
|
4610
|
-
environment: config.kind === "enrolled-compute" ? undefined : config.environment,
|
|
4611
|
-
nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
|
|
4612
|
-
} : {}
|
|
4656
|
+
hasBinding,
|
|
4657
|
+
hasEnrolCredential,
|
|
4658
|
+
initialBundle
|
|
4613
4659
|
});
|
|
4614
4660
|
for (const unit of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
|
|
4615
4661
|
mkdirSync5(dirname6(unit.path), { recursive: true, mode: 493 });
|
|
@@ -5525,10 +5571,10 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
5525
5571
|
|
|
5526
5572
|
// src/operator-bootstrap.ts
|
|
5527
5573
|
import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
|
|
5528
|
-
import { lstatSync as lstatSync5, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
5574
|
+
import { chmodSync as chmodSync5, lstatSync as lstatSync5, mkdirSync as mkdirSync8, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
5529
5575
|
import { isIP as isIP6 } from "net";
|
|
5530
5576
|
import { tmpdir } from "os";
|
|
5531
|
-
import { basename, join as join6 } from "path";
|
|
5577
|
+
import { basename, dirname as dirname9, isAbsolute as isAbsolute4, join as join6, resolve as resolve6 } from "path";
|
|
5532
5578
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5533
5579
|
|
|
5534
5580
|
// src/platform-genesis.ts
|
|
@@ -5663,6 +5709,25 @@ var exactKeys3 = (value, keys, label) => {
|
|
|
5663
5709
|
throw new Error(`${label} contains unknown fields: ${unknown.join(", ")}`);
|
|
5664
5710
|
return record;
|
|
5665
5711
|
};
|
|
5712
|
+
var canonicalOutputPath = (path, label) => {
|
|
5713
|
+
if (!isAbsolute4(path) || resolve6(path) !== path || /[\r\n\0]/.test(path)) {
|
|
5714
|
+
throw new Error(`${label} must be a canonical absolute path`);
|
|
5715
|
+
}
|
|
5716
|
+
return path;
|
|
5717
|
+
};
|
|
5718
|
+
var writeOwnerJson2 = (path, value, label) => {
|
|
5719
|
+
canonicalOutputPath(path, label);
|
|
5720
|
+
mkdirSync8(dirname9(path), { recursive: true, mode: 448 });
|
|
5721
|
+
const parent = lstatSync5(dirname9(path));
|
|
5722
|
+
const uid = process.getuid?.() ?? parent.uid;
|
|
5723
|
+
if (!parent.isDirectory() || parent.isSymbolicLink() || parent.uid !== uid || (parent.mode & 63) !== 0) {
|
|
5724
|
+
throw new Error(`${label} parent must be an owner-only directory`);
|
|
5725
|
+
}
|
|
5726
|
+
writeFileSync8(path, `${JSON.stringify(value, null, 2)}
|
|
5727
|
+
`, { mode: 384, flag: "wx" });
|
|
5728
|
+
chmodSync5(path, 384);
|
|
5729
|
+
return path;
|
|
5730
|
+
};
|
|
5666
5731
|
var ownerFile = (path, limit, label) => {
|
|
5667
5732
|
if (!path.startsWith("/") || /[\r\n]/.test(path))
|
|
5668
5733
|
throw new Error(`${label} path must be absolute`);
|
|
@@ -5721,8 +5786,10 @@ var validateHop = (value, label) => {
|
|
|
5721
5786
|
}
|
|
5722
5787
|
return hop;
|
|
5723
5788
|
};
|
|
5724
|
-
function
|
|
5725
|
-
|
|
5789
|
+
function createOperatorSshHop(input) {
|
|
5790
|
+
return validateHop({ ...input, hostKeySha256: fingerprint(input.hostKey) }, "operator SSH hop");
|
|
5791
|
+
}
|
|
5792
|
+
var validatePlatformRequestValue = (value) => {
|
|
5726
5793
|
const root = exactKeys3(value, ["kind", "target", "platformConfigFile"], "operator bootstrap request");
|
|
5727
5794
|
if (root.kind !== "platform-remote")
|
|
5728
5795
|
throw new Error("operator bootstrap kind must be platform-remote");
|
|
@@ -5741,6 +5808,7 @@ function readOperatorPlatformBootstrapRequest(path) {
|
|
|
5741
5808
|
socketPath(targetValue.agentSocket);
|
|
5742
5809
|
if (typeof root.platformConfigFile !== "string")
|
|
5743
5810
|
throw new Error("platformConfigFile must be an absolute path");
|
|
5811
|
+
canonicalOutputPath(root.platformConfigFile, "platformConfigFile");
|
|
5744
5812
|
const config = readBootstrapConfig(root.platformConfigFile);
|
|
5745
5813
|
if (config.kind !== "platform")
|
|
5746
5814
|
throw new Error("operator bootstrap requires a platform config");
|
|
@@ -5754,9 +5822,39 @@ function readOperatorPlatformBootstrapRequest(path) {
|
|
|
5754
5822
|
...targetValue.jump === undefined ? {} : { jump: validateHop(targetValue.jump, "operator bootstrap jump") }
|
|
5755
5823
|
}
|
|
5756
5824
|
};
|
|
5757
|
-
}
|
|
5758
|
-
function
|
|
5759
|
-
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator
|
|
5825
|
+
};
|
|
5826
|
+
function readOperatorPlatformBootstrapRequest(path) {
|
|
5827
|
+
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator bootstrap request")));
|
|
5828
|
+
return validatePlatformRequestValue(value);
|
|
5829
|
+
}
|
|
5830
|
+
function writeOperatorPlatformBootstrapRequest(path, request) {
|
|
5831
|
+
validatePlatformRequestValue(request);
|
|
5832
|
+
return writeOwnerJson2(path, request, "operator bootstrap request");
|
|
5833
|
+
}
|
|
5834
|
+
function readOperatorPlatformBootstrapFleetRequest(path) {
|
|
5835
|
+
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator fleet request")));
|
|
5836
|
+
const root = exactKeys3(value, ["kind", "requests"], "operator fleet request");
|
|
5837
|
+
if (root.kind !== "platform-fleet-remote")
|
|
5838
|
+
throw new Error("operator fleet kind must be platform-fleet-remote");
|
|
5839
|
+
if (!Array.isArray(root.requests) || root.requests.length !== 3 || root.requests.some((entry) => typeof entry !== "string")) {
|
|
5840
|
+
throw new Error("operator fleet requires exactly three request files");
|
|
5841
|
+
}
|
|
5842
|
+
const requests = root.requests.map((entry) => canonicalOutputPath(entry, "operator fleet request file"));
|
|
5843
|
+
if (new Set(requests).size !== 3)
|
|
5844
|
+
throw new Error("operator fleet request files must be unique");
|
|
5845
|
+
for (const request of requests)
|
|
5846
|
+
readOperatorPlatformBootstrapRequest(request);
|
|
5847
|
+
return { kind: "platform-fleet-remote", requests };
|
|
5848
|
+
}
|
|
5849
|
+
function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles) {
|
|
5850
|
+
const request = { kind: "platform-fleet-remote", requests: requestFiles };
|
|
5851
|
+
if (new Set(requestFiles).size !== 3)
|
|
5852
|
+
throw new Error("operator fleet request files must be unique");
|
|
5853
|
+
for (const file of requestFiles)
|
|
5854
|
+
readOperatorPlatformBootstrapRequest(file);
|
|
5855
|
+
return writeOwnerJson2(path, request, "operator fleet request");
|
|
5856
|
+
}
|
|
5857
|
+
var validateMetalRequestValue = (value) => {
|
|
5760
5858
|
const root = exactKeys3(value, ["kind", "target", "metalConfigFile", "genesis"], "operator metal request");
|
|
5761
5859
|
if (root.kind !== "metal-remote")
|
|
5762
5860
|
throw new Error("operator metal bootstrap kind must be metal-remote");
|
|
@@ -5775,6 +5873,7 @@ function readOperatorMetalBootstrapRequest(path) {
|
|
|
5775
5873
|
socketPath(targetValue.agentSocket);
|
|
5776
5874
|
if (typeof root.metalConfigFile !== "string")
|
|
5777
5875
|
throw new Error("metalConfigFile must be an absolute path");
|
|
5876
|
+
canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
|
|
5778
5877
|
readMetalBootstrapConfig(root.metalConfigFile);
|
|
5779
5878
|
const genesis = exactKeys3(root.genesis, ["environment", "sshPublicKeyFiles"], "operator metal genesis");
|
|
5780
5879
|
if (typeof genesis.environment !== "string" || !PLATFORM_GENESIS_ENVIRONMENTS.includes(genesis.environment)) {
|
|
@@ -5798,6 +5897,14 @@ function readOperatorMetalBootstrapRequest(path) {
|
|
|
5798
5897
|
sshPublicKeyFiles: genesis.sshPublicKeyFiles
|
|
5799
5898
|
}
|
|
5800
5899
|
};
|
|
5900
|
+
};
|
|
5901
|
+
function readOperatorMetalBootstrapRequest(path) {
|
|
5902
|
+
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal request")));
|
|
5903
|
+
return validateMetalRequestValue(value);
|
|
5904
|
+
}
|
|
5905
|
+
function writeOperatorMetalBootstrapRequest(path, request) {
|
|
5906
|
+
validateMetalRequestValue(request);
|
|
5907
|
+
return writeOwnerJson2(path, request, "operator metal request");
|
|
5801
5908
|
}
|
|
5802
5909
|
function planOperatorMetalBootstrap(request, mode) {
|
|
5803
5910
|
const config = readMetalBootstrapConfig(request.metalConfigFile);
|
|
@@ -5857,6 +5964,87 @@ function planOperatorPlatformBootstrap(request, mode) {
|
|
|
5857
5964
|
secretInputs: mode === "apply" ? [...attendedSecretNames(config), ...secretSources(config).map(([name]) => name)] : []
|
|
5858
5965
|
};
|
|
5859
5966
|
}
|
|
5967
|
+
var fleetConfigIdentity = (config) => {
|
|
5968
|
+
const normalized = structuredClone(config);
|
|
5969
|
+
delete normalized.computeReference;
|
|
5970
|
+
delete normalized.nodeHostname;
|
|
5971
|
+
delete normalized.cloudflareHandoff;
|
|
5972
|
+
delete normalized.database.role;
|
|
5973
|
+
delete normalized.database.address;
|
|
5974
|
+
delete normalized.database.master;
|
|
5975
|
+
const environment = normalized.runtime.environment;
|
|
5976
|
+
delete environment.databaseRole;
|
|
5977
|
+
delete environment.databaseAddress;
|
|
5978
|
+
delete environment.databaseMaster;
|
|
5979
|
+
delete environment.nodeHostname;
|
|
5980
|
+
delete environment.seedSyncPeers;
|
|
5981
|
+
delete environment.custodianEmail;
|
|
5982
|
+
return JSON.stringify(normalized);
|
|
5983
|
+
};
|
|
5984
|
+
function validatedPlatformFleet(fleet) {
|
|
5985
|
+
const requests = fleet.requests.map(readOperatorPlatformBootstrapRequest);
|
|
5986
|
+
const configs = requests.map((request) => {
|
|
5987
|
+
const config = readBootstrapConfig(request.platformConfigFile);
|
|
5988
|
+
if (config.kind !== "platform")
|
|
5989
|
+
throw new Error("operator fleet requires platform configs");
|
|
5990
|
+
return config;
|
|
5991
|
+
});
|
|
5992
|
+
const environment = configs[0].environment;
|
|
5993
|
+
const expected = PLATFORM_GENESIS_FLEETS[environment];
|
|
5994
|
+
if (!expected || expected.length !== 3)
|
|
5995
|
+
throw new Error("operator fleet environment is invalid");
|
|
5996
|
+
const identity = fleetConfigIdentity(configs[0]);
|
|
5997
|
+
const operatorIdentity = `${requests[0].target.identityPublicKeyFile}\x00${requests[0].target.agentSocket}`;
|
|
5998
|
+
const jumpIdentity = JSON.stringify(requests[0].target.jump ?? null);
|
|
5999
|
+
const coordinators = expected.map((guest) => `http://${guest.address}:8529`);
|
|
6000
|
+
const nodeHostnames = new Set;
|
|
6001
|
+
for (let index = 0;index < 3; index += 1) {
|
|
6002
|
+
const config = configs[index];
|
|
6003
|
+
const request = requests[index];
|
|
6004
|
+
const guest = expected[index];
|
|
6005
|
+
const role = index === 0 ? "master" : "joiner";
|
|
6006
|
+
if (config.environment !== environment || fleetConfigIdentity(config) !== identity) {
|
|
6007
|
+
throw new Error("operator fleet platform configs do not share one immutable identity");
|
|
6008
|
+
}
|
|
6009
|
+
if (config.computeReference !== guest.name || config.database.address !== guest.address || config.database.role !== role || config.database.agency !== "member" || (role === "master" ? config.database.master !== undefined : config.database.master !== expected[0].address) || config.database.coordinators.join(",") !== coordinators.join(",") || config.enrolment.source !== "genesis-derived" || config.cloudflareHandoff?.nodeName !== guest.name) {
|
|
6010
|
+
throw new Error(`operator fleet ${guest.name} does not match the exact genesis topology`);
|
|
6011
|
+
}
|
|
6012
|
+
if (request.target.address !== guest.address || request.target.port !== 22 || request.target.user !== "forgezero") {
|
|
6013
|
+
throw new Error(`operator fleet ${guest.name} SSH target must be forgezero@${guest.address}:22`);
|
|
6014
|
+
}
|
|
6015
|
+
if (`${request.target.identityPublicKeyFile}\x00${request.target.agentSocket}` !== operatorIdentity) {
|
|
6016
|
+
throw new Error("operator fleet must use one caller-approved SSH identity and agent socket");
|
|
6017
|
+
}
|
|
6018
|
+
if (JSON.stringify(request.target.jump ?? null) !== jumpIdentity) {
|
|
6019
|
+
throw new Error("operator fleet must use one pinned Metal jump or no jump on every node");
|
|
6020
|
+
}
|
|
6021
|
+
if (nodeHostnames.has(config.nodeHostname))
|
|
6022
|
+
throw new Error("operator fleet node hostnames must be unique");
|
|
6023
|
+
nodeHostnames.add(config.nodeHostname);
|
|
6024
|
+
}
|
|
6025
|
+
return { requests, configs, environment };
|
|
6026
|
+
}
|
|
6027
|
+
function planOperatorPlatformFleetBootstrap(fleet, mode) {
|
|
6028
|
+
const { requests, configs, environment } = validatedPlatformFleet(fleet);
|
|
6029
|
+
const secretInputs = mode === "apply" ? attendedSecretNames(configs[0]) : [];
|
|
6030
|
+
for (const config of configs.slice(1)) {
|
|
6031
|
+
if (JSON.stringify(attendedSecretNames(config)) !== JSON.stringify(secretInputs)) {
|
|
6032
|
+
throw new Error("operator fleet configs do not share one attended secret schema");
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
return {
|
|
6036
|
+
kind: "platform-fleet-remote",
|
|
6037
|
+
mode,
|
|
6038
|
+
mutation: mode !== "status",
|
|
6039
|
+
environment,
|
|
6040
|
+
nodes: requests.map((request, index) => ({
|
|
6041
|
+
computeReference: configs[index].computeReference,
|
|
6042
|
+
address: request.target.address,
|
|
6043
|
+
...request.target.jump ? { via: request.target.jump.address } : {}
|
|
6044
|
+
})),
|
|
6045
|
+
secretInputs
|
|
6046
|
+
};
|
|
6047
|
+
}
|
|
5860
6048
|
var defaultExec3 = async (argv, options = {}) => {
|
|
5861
6049
|
const child = Bun.spawn([...argv], {
|
|
5862
6050
|
stdin: options.stdin === undefined ? "ignore" : "pipe",
|
|
@@ -6099,6 +6287,30 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
|
|
|
6099
6287
|
rmSync6(directory, { recursive: true, force: true });
|
|
6100
6288
|
}
|
|
6101
6289
|
}
|
|
6290
|
+
async function applyOperatorPlatformFleetBootstrap(fleet, mode, options = {}) {
|
|
6291
|
+
const plan = planOperatorPlatformFleetBootstrap(fleet, mode);
|
|
6292
|
+
const { requests, configs } = validatedPlatformFleet(fleet);
|
|
6293
|
+
const secrets = mode === "apply" ? validatePlatformBootstrapSecrets(configs[0], options.secrets) : undefined;
|
|
6294
|
+
if (secrets) {
|
|
6295
|
+
for (const config of configs.slice(1))
|
|
6296
|
+
validatePlatformBootstrapSecrets(config, secrets);
|
|
6297
|
+
}
|
|
6298
|
+
const settled = await Promise.allSettled(requests.map((request) => applyOperatorPlatformBootstrap(request, mode, { ...options, secrets })));
|
|
6299
|
+
const outcomes = settled.map((result, index) => {
|
|
6300
|
+
const config = configs[index];
|
|
6301
|
+
const address = requests[index].target.address;
|
|
6302
|
+
if (result.status === "fulfilled")
|
|
6303
|
+
return {
|
|
6304
|
+
computeReference: config.computeReference,
|
|
6305
|
+
address,
|
|
6306
|
+
ok: true,
|
|
6307
|
+
output: result.value.output.slice(0, 65536)
|
|
6308
|
+
};
|
|
6309
|
+
const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
6310
|
+
return { computeReference: config.computeReference, address, ok: false, error: message.slice(0, 4096) };
|
|
6311
|
+
});
|
|
6312
|
+
return { plan, ok: outcomes.every(({ ok }) => ok), outcomes };
|
|
6313
|
+
}
|
|
6102
6314
|
async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
6103
6315
|
const plan = planOperatorMetalBootstrap(request, mode);
|
|
6104
6316
|
publicIdentity(request.target.identityPublicKeyFile);
|
|
@@ -6153,10 +6365,17 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
|
6153
6365
|
}
|
|
6154
6366
|
}
|
|
6155
6367
|
export {
|
|
6368
|
+
writeOperatorPlatformBootstrapRequest,
|
|
6369
|
+
writeOperatorPlatformBootstrapFleetRequest,
|
|
6370
|
+
writeOperatorMetalBootstrapRequest,
|
|
6156
6371
|
readOperatorPlatformBootstrapRequest,
|
|
6372
|
+
readOperatorPlatformBootstrapFleetRequest,
|
|
6157
6373
|
readOperatorMetalBootstrapRequest,
|
|
6374
|
+
planOperatorPlatformFleetBootstrap,
|
|
6158
6375
|
planOperatorPlatformBootstrap,
|
|
6159
6376
|
planOperatorMetalBootstrap,
|
|
6377
|
+
createOperatorSshHop,
|
|
6378
|
+
applyOperatorPlatformFleetBootstrap,
|
|
6160
6379
|
applyOperatorPlatformBootstrap,
|
|
6161
6380
|
applyOperatorMetalBootstrap
|
|
6162
6381
|
};
|