@forgezero/agent 0.1.68 → 0.1.69
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/attended-platform-bootstrap.d.ts +22 -0
- package/dist/bootstrap.js +2 -1
- package/dist/fz-agent.js +1 -1
- package/dist/fz.js +158 -26
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.d.ts +8 -1
- package/dist/operator-bootstrap.js +26 -5
- package/dist/platform-fleet-verification.js +1 -1
- package/dist/provision.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/agent-heartbeat.js
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { finalizeCloudflareBootstrapAcceptance, runAttendedCloudflareBootstrap, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapEvidence } from './cloudflare-bootstrap';
|
|
2
|
+
import { applyOperatorPlatformFleetBootstrap, type OperatorPlatformBootstrapFleetRequest, type OperatorPlatformBootstrapFleetResult } from './operator-bootstrap';
|
|
3
|
+
import type { PlatformBootstrapSecrets } from './bootstrap';
|
|
4
|
+
export interface AttendedPlatformBootstrapResult {
|
|
5
|
+
kind: 'forgezero-attended-platform-bootstrap';
|
|
6
|
+
ok: boolean;
|
|
7
|
+
edge: CloudflareBootstrapEvidence;
|
|
8
|
+
fleet: OperatorPlatformBootstrapFleetResult;
|
|
9
|
+
acceptance?: CloudflareBootstrapAcceptanceEvidence;
|
|
10
|
+
inviteUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AttendedPlatformBootstrapDependencies {
|
|
13
|
+
cloudflare?: typeof runAttendedCloudflareBootstrap;
|
|
14
|
+
fleet?: typeof applyOperatorPlatformFleetBootstrap;
|
|
15
|
+
finalize?: typeof finalizeCloudflareBootstrapAcceptance;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The sole attended genesis composition boundary. All non-secret coordinates
|
|
19
|
+
* are checked before the first Cloudflare or SSH mutation. Secret values exist
|
|
20
|
+
* only in this call and the authenticated SSH stdin used by the fleet runner.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyAttendedPlatformBootstrap(fleetRequest: OperatorPlatformBootstrapFleetRequest, cloudflareRequest: AttendedCloudflareBootstrapRequest, acceptancePath: string, secrets: PlatformBootstrapSecrets, dependencies?: AttendedPlatformBootstrapDependencies): Promise<AttendedPlatformBootstrapResult>;
|
package/dist/bootstrap.js
CHANGED
|
@@ -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.69";
|
|
1400
1400
|
|
|
1401
1401
|
// src/software.ts
|
|
1402
1402
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -4380,6 +4380,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4380
4380
|
}
|
|
4381
4381
|
bindBootstrapIntent(host, config);
|
|
4382
4382
|
const plan = planBootstrap(config, host.exists(STATE_PATH));
|
|
4383
|
+
await checked3(host, ["hostnamectl", "set-hostname", "--static", config.nodeHostname], "compute hostname");
|
|
4383
4384
|
const alreadyEnrolled = host.exists("/var/lib/forgezero/enrolment.json");
|
|
4384
4385
|
host.mkdir(CREDS, 448);
|
|
4385
4386
|
host.mkdir("/var/lib/forgezero", 448);
|
package/dist/fz-agent.js
CHANGED
|
@@ -8588,7 +8588,7 @@ import { join as join3 } from "path";
|
|
|
8588
8588
|
import { fileURLToPath } from "url";
|
|
8589
8589
|
|
|
8590
8590
|
// src/version.ts
|
|
8591
|
-
var VERSION2 = "0.1.
|
|
8591
|
+
var VERSION2 = "0.1.69";
|
|
8592
8592
|
|
|
8593
8593
|
// src/ssh-bootstrap.ts
|
|
8594
8594
|
class SshBootstrapError extends Error {
|
package/dist/fz.js
CHANGED
|
@@ -4829,7 +4829,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
4829
4829
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
4830
4830
|
|
|
4831
4831
|
// src/version.ts
|
|
4832
|
-
var VERSION2 = "0.1.
|
|
4832
|
+
var VERSION2 = "0.1.69";
|
|
4833
4833
|
|
|
4834
4834
|
// src/software.ts
|
|
4835
4835
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -14399,6 +14399,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
14399
14399
|
}
|
|
14400
14400
|
bindBootstrapIntent(host, config);
|
|
14401
14401
|
const plan = planBootstrap(config, host.exists(STATE_PATH));
|
|
14402
|
+
await checked3(host, ["hostnamectl", "set-hostname", "--static", config.nodeHostname], "compute hostname");
|
|
14402
14403
|
const alreadyEnrolled = host.exists("/var/lib/forgezero/enrolment.json");
|
|
14403
14404
|
host.mkdir(CREDS, 448);
|
|
14404
14405
|
host.mkdir("/var/lib/forgezero", 448);
|
|
@@ -15076,6 +15077,14 @@ async function runCloudflareBootstrapFinalizeCommand(configPath, dependencies =
|
|
|
15076
15077
|
return evidence;
|
|
15077
15078
|
}
|
|
15078
15079
|
|
|
15080
|
+
// src/operator-bootstrap.ts
|
|
15081
|
+
import { createHash as createHash6, randomBytes as randomBytes9 } from "crypto";
|
|
15082
|
+
import { chmodSync as chmodSync6, lstatSync as lstatSync7, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
15083
|
+
import { isIP as isIP6 } from "net";
|
|
15084
|
+
import { tmpdir } from "os";
|
|
15085
|
+
import { basename as basename2, dirname as dirname13, isAbsolute as isAbsolute5, join as join10, resolve as resolve9 } from "path";
|
|
15086
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15087
|
+
|
|
15079
15088
|
// src/metal-bootstrap.ts
|
|
15080
15089
|
import { createHash as createHash5, randomBytes as randomBytes8 } from "crypto";
|
|
15081
15090
|
import {
|
|
@@ -15986,12 +15995,6 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
15986
15995
|
}
|
|
15987
15996
|
|
|
15988
15997
|
// src/operator-bootstrap.ts
|
|
15989
|
-
import { createHash as createHash6, randomBytes as randomBytes9 } from "crypto";
|
|
15990
|
-
import { chmodSync as chmodSync6, lstatSync as lstatSync7, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
15991
|
-
import { isIP as isIP6 } from "net";
|
|
15992
|
-
import { tmpdir } from "os";
|
|
15993
|
-
import { basename as basename2, dirname as dirname13, isAbsolute as isAbsolute5, join as join10, resolve as resolve9 } from "path";
|
|
15994
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15995
15998
|
var REQUEST_LIMIT = 64 * 1024;
|
|
15996
15999
|
var SECRET_LIMIT = 256 * 1024;
|
|
15997
16000
|
var REMOTE_STAGE = "/run/forgezero-operator-bootstrap";
|
|
@@ -16129,7 +16132,12 @@ function writeOperatorPlatformBootstrapRequest(path, request) {
|
|
|
16129
16132
|
}
|
|
16130
16133
|
function readOperatorPlatformBootstrapFleetRequest(path) {
|
|
16131
16134
|
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator fleet request")));
|
|
16132
|
-
const root = exactKeys5(value, [
|
|
16135
|
+
const root = exactKeys5(value, [
|
|
16136
|
+
"kind",
|
|
16137
|
+
"requests",
|
|
16138
|
+
"cloudflareConfigFile",
|
|
16139
|
+
"cloudflareAcceptanceFile"
|
|
16140
|
+
], "operator fleet request");
|
|
16133
16141
|
if (root.kind !== "platform-fleet-remote")
|
|
16134
16142
|
throw new Error("operator fleet kind must be platform-fleet-remote");
|
|
16135
16143
|
if (!Array.isArray(root.requests) || root.requests.length !== 3 || root.requests.some((entry) => typeof entry !== "string")) {
|
|
@@ -16140,10 +16148,25 @@ function readOperatorPlatformBootstrapFleetRequest(path) {
|
|
|
16140
16148
|
throw new Error("operator fleet request files must be unique");
|
|
16141
16149
|
for (const request of requests)
|
|
16142
16150
|
readOperatorPlatformBootstrapRequest(request);
|
|
16143
|
-
|
|
16151
|
+
if (typeof root.cloudflareConfigFile !== "string" || typeof root.cloudflareAcceptanceFile !== "string") {
|
|
16152
|
+
throw new Error("operator fleet requires Cloudflare coordinates and an acceptance evidence path");
|
|
16153
|
+
}
|
|
16154
|
+
const cloudflareConfigFile = canonicalOutputPath(root.cloudflareConfigFile, "operator fleet Cloudflare config");
|
|
16155
|
+
const cloudflareAcceptanceFile = canonicalOutputPath(root.cloudflareAcceptanceFile, "operator fleet Cloudflare acceptance evidence");
|
|
16156
|
+
return {
|
|
16157
|
+
kind: "platform-fleet-remote",
|
|
16158
|
+
requests,
|
|
16159
|
+
cloudflareConfigFile,
|
|
16160
|
+
cloudflareAcceptanceFile
|
|
16161
|
+
};
|
|
16144
16162
|
}
|
|
16145
|
-
function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles) {
|
|
16146
|
-
const request = {
|
|
16163
|
+
function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles, cloudflare) {
|
|
16164
|
+
const request = {
|
|
16165
|
+
kind: "platform-fleet-remote",
|
|
16166
|
+
requests: requestFiles,
|
|
16167
|
+
cloudflareConfigFile: canonicalOutputPath(cloudflare.configFile, "operator fleet Cloudflare config"),
|
|
16168
|
+
cloudflareAcceptanceFile: canonicalOutputPath(cloudflare.acceptanceFile, "operator fleet Cloudflare acceptance evidence")
|
|
16169
|
+
};
|
|
16147
16170
|
if (new Set(requestFiles).size !== 3)
|
|
16148
16171
|
throw new Error("operator fleet request files must be unique");
|
|
16149
16172
|
for (const file of requestFiles)
|
|
@@ -16396,6 +16419,20 @@ function validatedPlatformFleet(fleet) {
|
|
|
16396
16419
|
}
|
|
16397
16420
|
return { requests, configs, environment };
|
|
16398
16421
|
}
|
|
16422
|
+
function operatorPlatformFleetCoordinates(fleet) {
|
|
16423
|
+
const { requests, configs, environment } = validatedPlatformFleet(fleet);
|
|
16424
|
+
return {
|
|
16425
|
+
environment,
|
|
16426
|
+
appOrigin: configs[0].runtime.environment.appOrigin,
|
|
16427
|
+
apiOrigin: configs[0].runtime.environment.apiOrigin,
|
|
16428
|
+
nodes: configs.map((config, index) => ({
|
|
16429
|
+
computeReference: config.computeReference,
|
|
16430
|
+
address: requests[index].target.address,
|
|
16431
|
+
nodeHostname: config.nodeHostname,
|
|
16432
|
+
databaseRole: config.database.role
|
|
16433
|
+
}))
|
|
16434
|
+
};
|
|
16435
|
+
}
|
|
16399
16436
|
function planOperatorPlatformFleetBootstrap(fleet, mode) {
|
|
16400
16437
|
const { requests, configs, environment } = validatedPlatformFleet(fleet);
|
|
16401
16438
|
const secretInputs = mode === "apply" ? attendedSecretNames(configs[0]) : [];
|
|
@@ -16797,6 +16834,47 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
|
16797
16834
|
}
|
|
16798
16835
|
}
|
|
16799
16836
|
|
|
16837
|
+
// src/attended-platform-bootstrap.ts
|
|
16838
|
+
var invitationFrom = (fleet, master) => {
|
|
16839
|
+
const output = fleet.outcomes.find(({ computeReference }) => computeReference === master)?.output;
|
|
16840
|
+
try {
|
|
16841
|
+
const parsed = JSON.parse(output ?? "");
|
|
16842
|
+
if (typeof parsed.launch?.inviteUrl === "string")
|
|
16843
|
+
return parsed.launch.inviteUrl;
|
|
16844
|
+
} catch {}
|
|
16845
|
+
throw new Error("platform bootstrap completed without the initial custodian invitation");
|
|
16846
|
+
};
|
|
16847
|
+
async function applyAttendedPlatformBootstrap(fleetRequest, cloudflareRequest, acceptancePath, secrets, dependencies = {}) {
|
|
16848
|
+
if (cloudflareRequest.mode !== "apply")
|
|
16849
|
+
throw new Error("attended platform bootstrap requires Cloudflare apply mode");
|
|
16850
|
+
if (!secrets.cloudflareTunnelToken || !secrets.cloudflareApiToken) {
|
|
16851
|
+
throw new Error("attended platform bootstrap requires both Cloudflare tokens");
|
|
16852
|
+
}
|
|
16853
|
+
const fleetCoordinates = operatorPlatformFleetCoordinates(fleetRequest);
|
|
16854
|
+
if (cloudflareRequest.coordinates.nodes.length !== fleetCoordinates.nodes.length || fleetCoordinates.nodes.some((node, index) => {
|
|
16855
|
+
const edge2 = cloudflareRequest.coordinates.nodes[index];
|
|
16856
|
+
return edge2?.nodeName !== node.computeReference || edge2.hostname !== node.nodeHostname || edge2.service !== "http://127.0.0.1:3000";
|
|
16857
|
+
})) {
|
|
16858
|
+
throw new Error("Cloudflare coordinates do not match the exact reviewed platform fleet");
|
|
16859
|
+
}
|
|
16860
|
+
const edge = await (dependencies.cloudflare ?? runAttendedCloudflareBootstrap)({
|
|
16861
|
+
...cloudflareRequest,
|
|
16862
|
+
tokens: {
|
|
16863
|
+
tunnelToken: secrets.cloudflareTunnelToken,
|
|
16864
|
+
apiToken: secrets.cloudflareApiToken
|
|
16865
|
+
}
|
|
16866
|
+
});
|
|
16867
|
+
const fleet = await (dependencies.fleet ?? applyOperatorPlatformFleetBootstrap)(fleetRequest, "apply", { secrets });
|
|
16868
|
+
if (!fleet.ok)
|
|
16869
|
+
return { kind: "forgezero-attended-platform-bootstrap", ok: false, edge, fleet };
|
|
16870
|
+
const inviteUrl = invitationFrom(fleet, fleetCoordinates.nodes[0].computeReference);
|
|
16871
|
+
const acceptance = await (dependencies.finalize ?? finalizeCloudflareBootstrapAcceptance)({
|
|
16872
|
+
checkpointPath: edge.checkpointFile,
|
|
16873
|
+
acceptancePath
|
|
16874
|
+
});
|
|
16875
|
+
return { kind: "forgezero-attended-platform-bootstrap", ok: true, edge, fleet, acceptance, inviteUrl };
|
|
16876
|
+
}
|
|
16877
|
+
|
|
16800
16878
|
// src/platform-genesis-config.ts
|
|
16801
16879
|
function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname, region) {
|
|
16802
16880
|
const fleet = validatePlatformGenesisGuests(guests);
|
|
@@ -18102,7 +18180,8 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18102
18180
|
const guestHostKeys = readOperatorGuestHostKeyEvidence(guestHostKeysFile, metalRequest);
|
|
18103
18181
|
const fleet = metalRequest.genesis.nodes;
|
|
18104
18182
|
const metalHostname = readMetalBootstrapConfig(metalRequest.metalConfigFile).metalHostname;
|
|
18105
|
-
const
|
|
18183
|
+
const checkpointPath = join12(output, "cloudflare-handoff.json");
|
|
18184
|
+
const template = interactiveBootstrap("platform", fleet, checkpointPath);
|
|
18106
18185
|
const region = {
|
|
18107
18186
|
label: bootstrapAnswer(`Region ${template.runtime.environment.nodeRegion} display label`),
|
|
18108
18187
|
country: bootstrapAnswer("Region ISO 3166-1 alpha-2 country code").toUpperCase(),
|
|
@@ -18110,11 +18189,32 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18110
18189
|
};
|
|
18111
18190
|
const nodes = fleet.map((guest, index) => index === 0 ? {
|
|
18112
18191
|
nodeHostname: template.nodeHostname,
|
|
18113
|
-
cloudflareHandoffFile:
|
|
18192
|
+
cloudflareHandoffFile: cloudflareHostHandoffPath(checkpointPath, guest.name)
|
|
18114
18193
|
} : {
|
|
18115
18194
|
nodeHostname: bootstrapAnswer(`${guest.name} public node hostname`),
|
|
18116
|
-
cloudflareHandoffFile:
|
|
18195
|
+
cloudflareHandoffFile: cloudflareHostHandoffPath(checkpointPath, guest.name)
|
|
18117
18196
|
});
|
|
18197
|
+
const realtimeEnabled = bootstrapBoolean("Configure existing Worker realtime fan-out?", "yes");
|
|
18198
|
+
const cloudflareConfig = writeBootstrapConfig(join12(output, "cloudflare.json"), createCloudflareBootstrapCommandConfig({
|
|
18199
|
+
checkpointPath,
|
|
18200
|
+
coordinates: {
|
|
18201
|
+
accountId: bootstrapAnswer("Cloudflare account id"),
|
|
18202
|
+
zoneId: bootstrapAnswer("Cloudflare zone id"),
|
|
18203
|
+
kvNamespaceId: bootstrapAnswer("Existing Worker-bound KV namespace id"),
|
|
18204
|
+
...realtimeEnabled ? { realtime: {
|
|
18205
|
+
workerScriptName: bootstrapAnswer("Existing Worker script name"),
|
|
18206
|
+
endpoint: bootstrapAnswer("Stable public Worker HTTPS endpoint"),
|
|
18207
|
+
producer: bootstrapAnswer("Realtime producer identity", "platform-api")
|
|
18208
|
+
} } : {},
|
|
18209
|
+
nodes: fleet.map((guest, index) => ({
|
|
18210
|
+
nodeName: guest.name,
|
|
18211
|
+
hostname: nodes[index].nodeHostname,
|
|
18212
|
+
service: "http://127.0.0.1:3000",
|
|
18213
|
+
tunnelName: bootstrapAnswer(`${guest.name} Tunnel name`, guest.name)
|
|
18214
|
+
}))
|
|
18215
|
+
}
|
|
18216
|
+
}));
|
|
18217
|
+
const cloudflareAcceptance = join12(output, "cloudflare-acceptance.json");
|
|
18118
18218
|
const configs = platformGenesisBootstrapConfigs(template, fleet, nodes, metalHostname, region).map((config) => {
|
|
18119
18219
|
const path = `${output}/${config.computeReference}-platform.json`;
|
|
18120
18220
|
writeBootstrapConfig(path, config);
|
|
@@ -18144,10 +18244,10 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18144
18244
|
}
|
|
18145
18245
|
});
|
|
18146
18246
|
});
|
|
18147
|
-
const fleetRequest = writeOperatorPlatformBootstrapFleetRequest(join12(output, "platform-fleet-remote.json"), requests);
|
|
18148
|
-
return { configs, requests, fleetRequest };
|
|
18247
|
+
const fleetRequest = writeOperatorPlatformBootstrapFleetRequest(join12(output, "platform-fleet-remote.json"), requests, { configFile: cloudflareConfig, acceptanceFile: cloudflareAcceptance });
|
|
18248
|
+
return { configs, requests, fleetRequest, cloudflareConfig, cloudflareAcceptance };
|
|
18149
18249
|
}
|
|
18150
|
-
function interactiveBootstrap(kind, genesisGuests) {
|
|
18250
|
+
function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint) {
|
|
18151
18251
|
if (!process.stdin.isTTY)
|
|
18152
18252
|
throw new Error("non-interactive bootstrap requires --bootstrap-config <private-json-file>");
|
|
18153
18253
|
const environment = bootstrapAnswer("Environment (production/development)", "production");
|
|
@@ -18187,7 +18287,7 @@ function interactiveBootstrap(kind, genesisGuests) {
|
|
|
18187
18287
|
const backupRegion = backupEndpoint ? bootstrapAnswer("Backup S3 region") : undefined;
|
|
18188
18288
|
const backupBucket = backupEndpoint ? bootstrapAnswer("Backup S3 bucket") : undefined;
|
|
18189
18289
|
const backupAccessKeyId = backupEndpoint ? bootstrapAnswer("Backup S3 access-key id") : undefined;
|
|
18190
|
-
const cloudflareHandoffFile =
|
|
18290
|
+
const cloudflareHandoffFile = genesis ? cloudflareHostHandoffPath(genesisCloudflareCheckpoint, computeReference) : bootstrapAnswer("Node-specific Cloudflare host handoff (blank for no public edge)", "");
|
|
18191
18291
|
const cloudflareNodeName = cloudflareHandoffFile ? bootstrapAnswer("Cloudflare handoff node name", computeReference) : undefined;
|
|
18192
18292
|
const databaseNetworkMode = "private-lan";
|
|
18193
18293
|
const enrolmentSource = genesis ? "genesis-derived" : bootstrapAnswer("Enrolment source (genesis-derived/api-token)", "genesis-derived");
|
|
@@ -18293,19 +18393,49 @@ async function cmdBootstrap(options, args) {
|
|
|
18293
18393
|
throw new Error("remote platform fleet bootstrap requires --bootstrap-config");
|
|
18294
18394
|
const fleet = readOperatorPlatformBootstrapFleetRequest(options.bootstrapConfigPath);
|
|
18295
18395
|
const plan2 = planOperatorPlatformFleetBootstrap(fleet, mode);
|
|
18396
|
+
const cloudflare = readCloudflareBootstrapCommandConfig(fleet.cloudflareConfigFile, options.apply && mode === "apply" ? "apply" : "plan");
|
|
18397
|
+
const coordinates = operatorPlatformFleetCoordinates(fleet);
|
|
18398
|
+
if (cloudflare.coordinates.nodes.length !== coordinates.nodes.length || coordinates.nodes.some((node, index) => {
|
|
18399
|
+
const edge = cloudflare.coordinates.nodes[index];
|
|
18400
|
+
return edge?.nodeName !== node.computeReference || edge.hostname !== node.nodeHostname || edge.service !== "http://127.0.0.1:3000";
|
|
18401
|
+
})) {
|
|
18402
|
+
throw new Error("Cloudflare coordinates do not match the exact reviewed platform fleet");
|
|
18403
|
+
}
|
|
18296
18404
|
if (!options.apply) {
|
|
18297
|
-
out.line(JSON.stringify(
|
|
18298
|
-
|
|
18405
|
+
out.line(JSON.stringify({
|
|
18406
|
+
kind: "forgezero-attended-platform-bootstrap-plan",
|
|
18407
|
+
mutation: true,
|
|
18408
|
+
cloudflare: {
|
|
18409
|
+
checkpointFile: cloudflare.checkpointPath,
|
|
18410
|
+
nodes: cloudflare.coordinates.nodes.map(({ nodeName, hostname: hostname2 }) => ({ nodeName, hostname: hostname2 }))
|
|
18411
|
+
},
|
|
18412
|
+
fleet: plan2,
|
|
18413
|
+
acceptanceFile: fleet.cloudflareAcceptanceFile,
|
|
18414
|
+
completion: ["public ingress verified", "initial custodian invitation printed"]
|
|
18415
|
+
}, null, 2));
|
|
18416
|
+
out.step("Review the complete Tunnel, hostname, bundle, credential and three-node plan, then repeat with --apply.");
|
|
18299
18417
|
return 0;
|
|
18300
18418
|
}
|
|
18301
18419
|
const first = readOperatorPlatformBootstrapRequest(fleet.requests[0]);
|
|
18302
18420
|
const firstConfig = readBootstrapConfig(first.platformConfigFile);
|
|
18303
18421
|
if (firstConfig.kind !== "platform")
|
|
18304
18422
|
throw new Error("remote platform fleet requires platform configs");
|
|
18423
|
+
if (mode === "status") {
|
|
18424
|
+
const result3 = await applyOperatorPlatformFleetBootstrap(fleet, mode);
|
|
18425
|
+
out.line(JSON.stringify(result3, null, 2));
|
|
18426
|
+
return result3.ok ? 0 : 1;
|
|
18427
|
+
}
|
|
18305
18428
|
const secrets2 = mode === "apply" ? await promptPlatformBootstrapSecrets(firstConfig) : undefined;
|
|
18306
|
-
|
|
18429
|
+
if (!secrets2?.cloudflareTunnelToken || !secrets2.cloudflareApiToken) {
|
|
18430
|
+
throw new Error("attended platform bootstrap requires both Cloudflare tokens");
|
|
18431
|
+
}
|
|
18432
|
+
const result2 = await applyAttendedPlatformBootstrap(fleet, cloudflare, fleet.cloudflareAcceptanceFile, secrets2);
|
|
18307
18433
|
out.line(JSON.stringify(result2, null, 2));
|
|
18308
|
-
|
|
18434
|
+
if (!result2.ok)
|
|
18435
|
+
return 1;
|
|
18436
|
+
out.line();
|
|
18437
|
+
out.step(`Initial custodian invitation: ${result2.inviteUrl}`);
|
|
18438
|
+
return 0;
|
|
18309
18439
|
}
|
|
18310
18440
|
if (operation === "platform" && args[1] === "remote") {
|
|
18311
18441
|
const mode = args[2];
|
|
@@ -19032,14 +19162,16 @@ function usage() {
|
|
|
19032
19162
|
Run typed bootstrap from the operator laptop through
|
|
19033
19163
|
a pinned host; apply copies and verifies that bundle
|
|
19034
19164
|
fz bootstrap platform fleet <apply|status>
|
|
19035
|
-
|
|
19036
|
-
|
|
19165
|
+
One attended genesis operation: reconcile public
|
|
19166
|
+
hostnames/Tunnels, seal credentials, deploy the bundle,
|
|
19167
|
+
run all three nodes, verify ingress and print the invite
|
|
19037
19168
|
fz bootstrap platform Install/repair a typed elastic platform compute
|
|
19038
19169
|
fz bootstrap config <metal|platform|cloudflare>
|
|
19039
19170
|
Write validated owner-only metal config+remote request,
|
|
19040
|
-
Cloudflare file, or complete three-node
|
|
19171
|
+
low-level Cloudflare file, or the complete three-node
|
|
19172
|
+
platform + Cloudflare request set
|
|
19041
19173
|
fz bootstrap platform cloudflare
|
|
19042
|
-
|
|
19174
|
+
Low-level resume/diagnostic Tunnel and DNS reconciliation
|
|
19043
19175
|
using hidden management and KV/Worker runtime token prompts
|
|
19044
19176
|
fz bootstrap platform cloudflare verify
|
|
19045
19177
|
From the operator laptop, prove every ordinary public
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -354,7 +354,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
// src/version.ts
|
|
357
|
-
var VERSION = "0.1.
|
|
357
|
+
var VERSION = "0.1.69";
|
|
358
358
|
|
|
359
359
|
// src/otel-collector.ts
|
|
360
360
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -40,6 +40,10 @@ export interface OperatorGuestHostKeyEvidence {
|
|
|
40
40
|
export interface OperatorPlatformBootstrapFleetRequest {
|
|
41
41
|
kind: 'platform-fleet-remote';
|
|
42
42
|
requests: readonly [string, string, string];
|
|
43
|
+
/** Reviewed non-secret coordinates for the attended Tunnel/DNS/KV/DO phase. */
|
|
44
|
+
cloudflareConfigFile: string;
|
|
45
|
+
/** Secret-free evidence retained after checkpoint and host handoffs are consumed. */
|
|
46
|
+
cloudflareAcceptanceFile: string;
|
|
43
47
|
}
|
|
44
48
|
export interface OperatorPlatformBootstrapFleetPlan {
|
|
45
49
|
kind: 'platform-fleet-remote';
|
|
@@ -123,7 +127,10 @@ export declare function createOperatorSshHop(input: Omit<OperatorSshHop, 'hostKe
|
|
|
123
127
|
export declare function readOperatorPlatformBootstrapRequest(path: string): OperatorPlatformBootstrapRequest;
|
|
124
128
|
export declare function writeOperatorPlatformBootstrapRequest(path: string, request: OperatorPlatformBootstrapRequest): string;
|
|
125
129
|
export declare function readOperatorPlatformBootstrapFleetRequest(path: string): OperatorPlatformBootstrapFleetRequest;
|
|
126
|
-
export declare function writeOperatorPlatformBootstrapFleetRequest(path: string, requestFiles: readonly [string, string, string]
|
|
130
|
+
export declare function writeOperatorPlatformBootstrapFleetRequest(path: string, requestFiles: readonly [string, string, string], cloudflare: {
|
|
131
|
+
configFile: string;
|
|
132
|
+
acceptanceFile: string;
|
|
133
|
+
}): string;
|
|
127
134
|
export declare function readOperatorMetalBootstrapRequest(path: string): OperatorMetalBootstrapRequest;
|
|
128
135
|
export declare function writeOperatorMetalBootstrapRequest(path: string, request: OperatorMetalBootstrapRequest): string;
|
|
129
136
|
export declare function readOperatorGuestHostKeyEvidence(path: string, request?: OperatorMetalBootstrapRequest): OperatorGuestHostKeyEvidence;
|
|
@@ -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.69";
|
|
1400
1400
|
|
|
1401
1401
|
// src/software.ts
|
|
1402
1402
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -4380,6 +4380,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4380
4380
|
}
|
|
4381
4381
|
bindBootstrapIntent(host, config);
|
|
4382
4382
|
const plan = planBootstrap(config, host.exists(STATE_PATH));
|
|
4383
|
+
await checked3(host, ["hostnamectl", "set-hostname", "--static", config.nodeHostname], "compute hostname");
|
|
4383
4384
|
const alreadyEnrolled = host.exists("/var/lib/forgezero/enrolment.json");
|
|
4384
4385
|
host.mkdir(CREDS, 448);
|
|
4385
4386
|
host.mkdir("/var/lib/forgezero", 448);
|
|
@@ -5971,7 +5972,12 @@ function writeOperatorPlatformBootstrapRequest(path, request) {
|
|
|
5971
5972
|
}
|
|
5972
5973
|
function readOperatorPlatformBootstrapFleetRequest(path) {
|
|
5973
5974
|
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator fleet request")));
|
|
5974
|
-
const root = exactKeys3(value, [
|
|
5975
|
+
const root = exactKeys3(value, [
|
|
5976
|
+
"kind",
|
|
5977
|
+
"requests",
|
|
5978
|
+
"cloudflareConfigFile",
|
|
5979
|
+
"cloudflareAcceptanceFile"
|
|
5980
|
+
], "operator fleet request");
|
|
5975
5981
|
if (root.kind !== "platform-fleet-remote")
|
|
5976
5982
|
throw new Error("operator fleet kind must be platform-fleet-remote");
|
|
5977
5983
|
if (!Array.isArray(root.requests) || root.requests.length !== 3 || root.requests.some((entry) => typeof entry !== "string")) {
|
|
@@ -5982,10 +5988,25 @@ function readOperatorPlatformBootstrapFleetRequest(path) {
|
|
|
5982
5988
|
throw new Error("operator fleet request files must be unique");
|
|
5983
5989
|
for (const request of requests)
|
|
5984
5990
|
readOperatorPlatformBootstrapRequest(request);
|
|
5985
|
-
|
|
5991
|
+
if (typeof root.cloudflareConfigFile !== "string" || typeof root.cloudflareAcceptanceFile !== "string") {
|
|
5992
|
+
throw new Error("operator fleet requires Cloudflare coordinates and an acceptance evidence path");
|
|
5993
|
+
}
|
|
5994
|
+
const cloudflareConfigFile = canonicalOutputPath(root.cloudflareConfigFile, "operator fleet Cloudflare config");
|
|
5995
|
+
const cloudflareAcceptanceFile = canonicalOutputPath(root.cloudflareAcceptanceFile, "operator fleet Cloudflare acceptance evidence");
|
|
5996
|
+
return {
|
|
5997
|
+
kind: "platform-fleet-remote",
|
|
5998
|
+
requests,
|
|
5999
|
+
cloudflareConfigFile,
|
|
6000
|
+
cloudflareAcceptanceFile
|
|
6001
|
+
};
|
|
5986
6002
|
}
|
|
5987
|
-
function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles) {
|
|
5988
|
-
const request = {
|
|
6003
|
+
function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles, cloudflare) {
|
|
6004
|
+
const request = {
|
|
6005
|
+
kind: "platform-fleet-remote",
|
|
6006
|
+
requests: requestFiles,
|
|
6007
|
+
cloudflareConfigFile: canonicalOutputPath(cloudflare.configFile, "operator fleet Cloudflare config"),
|
|
6008
|
+
cloudflareAcceptanceFile: canonicalOutputPath(cloudflare.acceptanceFile, "operator fleet Cloudflare acceptance evidence")
|
|
6009
|
+
};
|
|
5989
6010
|
if (new Set(requestFiles).size !== 3)
|
|
5990
6011
|
throw new Error("operator fleet request files must be unique");
|
|
5991
6012
|
for (const file of requestFiles)
|
|
@@ -2125,7 +2125,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2125
2125
|
}
|
|
2126
2126
|
|
|
2127
2127
|
// src/version.ts
|
|
2128
|
-
var VERSION3 = "0.1.
|
|
2128
|
+
var VERSION3 = "0.1.69";
|
|
2129
2129
|
|
|
2130
2130
|
// src/egress-policy.ts
|
|
2131
2131
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/provision.js
CHANGED
|
@@ -2125,7 +2125,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2125
2125
|
}
|
|
2126
2126
|
|
|
2127
2127
|
// src/version.ts
|
|
2128
|
-
var VERSION3 = "0.1.
|
|
2128
|
+
var VERSION3 = "0.1.69";
|
|
2129
2129
|
|
|
2130
2130
|
// src/egress-policy.ts
|
|
2131
2131
|
import { realpathSync as realpathSync3 } from "node:fs";
|
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.69";
|