@forgezero/agent 0.1.55 → 0.1.57
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.js +1 -1
- package/dist/cli/cloudflare-bootstrap.d.ts +17 -1
- package/dist/community-rehearsal-host.d.ts +7 -1
- package/dist/community-rehearsal-host.js +27 -1
- package/dist/fz-agent.js +25 -1
- package/dist/fz.js +130 -14
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.d.ts +1 -1
- package/dist/operator-bootstrap.js +46 -5
- package/dist/platform-fleet-verification.js +1 -1
- package/dist/platform-genesis.d.ts +9 -0
- package/dist/provision.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { finalizeCloudflareBootstrapAcceptance, verifyCloudflareBootstrapAcceptance, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapEvidence, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapFinalizeRequest, type CloudflareBootstrapPhaseRunner } from '../cloudflare-bootstrap';
|
|
1
|
+
import { finalizeCloudflareBootstrapAcceptance, verifyCloudflareBootstrapAcceptance, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapEvidence, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapFinalizeRequest, type CloudflareBootstrapPhaseRunner, type CloudflareBootstrapTokenFiles } from '../cloudflare-bootstrap';
|
|
2
|
+
export interface CloudflareBootstrapCommandConfig {
|
|
3
|
+
format: 1;
|
|
4
|
+
kind: 'forgezero-cloudflare-bootstrap-request';
|
|
5
|
+
checkpointPath: string;
|
|
6
|
+
coordinates: AttendedCloudflareBootstrapRequest['coordinates'];
|
|
7
|
+
tokenFiles: CloudflareBootstrapTokenFiles;
|
|
8
|
+
}
|
|
2
9
|
export interface CloudflareBootstrapCommandDependencies {
|
|
3
10
|
run?: CloudflareBootstrapPhaseRunner;
|
|
4
11
|
write?: (text: string) => void;
|
|
@@ -9,6 +16,15 @@ export interface CloudflareBootstrapFinalizeConfig {
|
|
|
9
16
|
checkpointPath: string;
|
|
10
17
|
acceptancePath: string;
|
|
11
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Construct the exact reviewed JSON shape used by both the interactive CLI and
|
|
21
|
+
* the strict file reader. Token values are deliberately not accepted here.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createCloudflareBootstrapCommandConfig(input: {
|
|
24
|
+
checkpointPath: string;
|
|
25
|
+
coordinates: AttendedCloudflareBootstrapRequest['coordinates'];
|
|
26
|
+
tokenFiles: CloudflareBootstrapTokenFiles;
|
|
27
|
+
}): CloudflareBootstrapCommandConfig;
|
|
12
28
|
/**
|
|
13
29
|
* Read reviewed non-secret coordinates and token-file paths for the attended
|
|
14
30
|
* operator phase. Raw token values and a persisted mode are not schema fields.
|
|
@@ -2,6 +2,7 @@ import { type SoftwareExec } from './software';
|
|
|
2
2
|
export declare const COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
3
3
|
export declare const COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
4
4
|
export declare const COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
5
|
+
export declare const COMMUNITY_CREDENTIAL_PARENT = "/etc/forgezero-rehearsal";
|
|
5
6
|
export declare const COMMUNITY_CREDENTIAL_ROOT = "/etc/forgezero-rehearsal/creds";
|
|
6
7
|
export type CommunityRehearsalApiOperation = 'health' | 'init' | 'write' | 'read' | 'query' | 'cluster';
|
|
7
8
|
export type CommunityRehearsalHostRequest = {
|
|
@@ -13,6 +14,9 @@ export type CommunityRehearsalHostRequest = {
|
|
|
13
14
|
archivePath: string;
|
|
14
15
|
archiveSha256: string;
|
|
15
16
|
jwt: string;
|
|
17
|
+
} | {
|
|
18
|
+
action: 'cleanup';
|
|
19
|
+
node: string;
|
|
16
20
|
} | {
|
|
17
21
|
action: 'database-enable' | 'api-enable' | 'database-ready' | 'starter-ready' | 'api-ready' | 'database-status' | 'api-status';
|
|
18
22
|
node: string;
|
|
@@ -25,7 +29,7 @@ export type CommunityRehearsalHostRequest = {
|
|
|
25
29
|
};
|
|
26
30
|
export type CommunityHostOperation = {
|
|
27
31
|
kind: 'remove-tree';
|
|
28
|
-
path: typeof COMMUNITY_DATABASE_ROOT;
|
|
32
|
+
path: typeof COMMUNITY_DATABASE_ROOT | typeof COMMUNITY_REHEARSAL_ROOT | typeof COMMUNITY_CREDENTIAL_PARENT;
|
|
29
33
|
} | {
|
|
30
34
|
kind: 'write';
|
|
31
35
|
path: string;
|
|
@@ -49,5 +53,7 @@ export declare function parseCommunityRehearsalHostRequest(value: unknown): Comm
|
|
|
49
53
|
export declare function planCommunityRehearsalPrepare(request: Extract<CommunityRehearsalHostRequest, {
|
|
50
54
|
action: 'prepare';
|
|
51
55
|
}>): CommunityHostOperation[];
|
|
56
|
+
/** Remove only the dedicated rehearsal units, credentials, release and data. */
|
|
57
|
+
export declare function planCommunityRehearsalCleanup(): CommunityHostOperation[];
|
|
52
58
|
/** Execute only the dedicated four-node rehearsal operation selected by a typed request. */
|
|
53
59
|
export declare function runCommunityRehearsalHost(request: CommunityRehearsalHostRequest): Promise<Record<string, unknown>>;
|
|
@@ -285,6 +285,7 @@ import { dirname } from "node:path";
|
|
|
285
285
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
286
286
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
287
287
|
var COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
288
|
+
var COMMUNITY_CREDENTIAL_PARENT = "/etc/forgezero-rehearsal";
|
|
288
289
|
var COMMUNITY_CREDENTIAL_ROOT = "/etc/forgezero-rehearsal/creds";
|
|
289
290
|
var NODES = new Map([
|
|
290
291
|
["dev-fz-n1", { address: "10.42.0.21", agency: true }],
|
|
@@ -326,6 +327,12 @@ function parseCommunityRehearsalHostRequest(value) {
|
|
|
326
327
|
}
|
|
327
328
|
return row;
|
|
328
329
|
}
|
|
330
|
+
if (action === "cleanup") {
|
|
331
|
+
if (Object.keys(row).some((key) => !["action", "node"].includes(key))) {
|
|
332
|
+
throw new Error("community rehearsal cleanup has unknown fields");
|
|
333
|
+
}
|
|
334
|
+
return { action, node: node.name };
|
|
335
|
+
}
|
|
329
336
|
if (["database-enable", "api-enable", "database-ready", "starter-ready", "api-ready", "database-status", "api-status"].includes(String(action))) {
|
|
330
337
|
if (Object.keys(row).some((key) => !["action", "node"].includes(key)))
|
|
331
338
|
throw new Error("community rehearsal host action has unknown fields");
|
|
@@ -441,6 +448,19 @@ function planCommunityRehearsalPrepare(request) {
|
|
|
441
448
|
{ kind: "unlink", path: request.archivePath }
|
|
442
449
|
];
|
|
443
450
|
}
|
|
451
|
+
function planCommunityRehearsalCleanup() {
|
|
452
|
+
return [
|
|
453
|
+
{ kind: "exec", argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-community-api.service"], accepted: [0, 1, 5] },
|
|
454
|
+
{ kind: "exec", argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-rehearsal-db.service"], accepted: [0, 1, 5] },
|
|
455
|
+
{ kind: "unlink", path: "/etc/systemd/system/forgezero-community-api.service" },
|
|
456
|
+
{ kind: "unlink", path: "/etc/systemd/system/forgezero-rehearsal-db.service" },
|
|
457
|
+
{ kind: "remove-tree", path: COMMUNITY_DATABASE_ROOT },
|
|
458
|
+
{ kind: "remove-tree", path: COMMUNITY_REHEARSAL_ROOT },
|
|
459
|
+
{ kind: "remove-tree", path: COMMUNITY_CREDENTIAL_PARENT },
|
|
460
|
+
{ kind: "exec", argv: ["/usr/bin/systemctl", "daemon-reload"] },
|
|
461
|
+
{ kind: "exec", argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-community-api.service", "forgezero-rehearsal-db.service"], accepted: [0, 1, 5] }
|
|
462
|
+
];
|
|
463
|
+
}
|
|
444
464
|
async function exec(argv, stdin) {
|
|
445
465
|
const child = Bun.spawn([...argv], { stdin: stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
|
|
446
466
|
if (stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
|
|
@@ -503,6 +523,10 @@ async function runCommunityRehearsalHost(request) {
|
|
|
503
523
|
if ((process.getuid?.() ?? -1) !== 0)
|
|
504
524
|
throw new Error("community-rehearsal must run as root");
|
|
505
525
|
const node = exactNode(request.node);
|
|
526
|
+
if (request.action === "cleanup") {
|
|
527
|
+
await applyOperations(planCommunityRehearsalCleanup());
|
|
528
|
+
return { ok: true, action: request.action, node: node.name };
|
|
529
|
+
}
|
|
506
530
|
if (request.action === "prepare") {
|
|
507
531
|
const metadata = lstatSync(request.archivePath);
|
|
508
532
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash2("sha256").update(readFileSync2(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
|
@@ -554,10 +578,12 @@ async function runCommunityRehearsalHost(request) {
|
|
|
554
578
|
export {
|
|
555
579
|
runCommunityRehearsalHost,
|
|
556
580
|
planCommunityRehearsalPrepare,
|
|
581
|
+
planCommunityRehearsalCleanup,
|
|
557
582
|
parseCommunityRehearsalHostRequest,
|
|
558
583
|
ensureCommunityRehearsalArango,
|
|
559
584
|
COMMUNITY_REHEARSAL_VERSION,
|
|
560
585
|
COMMUNITY_REHEARSAL_ROOT,
|
|
561
586
|
COMMUNITY_DATABASE_ROOT,
|
|
562
|
-
COMMUNITY_CREDENTIAL_ROOT
|
|
587
|
+
COMMUNITY_CREDENTIAL_ROOT,
|
|
588
|
+
COMMUNITY_CREDENTIAL_PARENT
|
|
563
589
|
};
|
package/dist/fz-agent.js
CHANGED
|
@@ -8448,7 +8448,7 @@ function assertSupportedGuestImage(imageKey) {
|
|
|
8448
8448
|
}
|
|
8449
8449
|
|
|
8450
8450
|
// src/version.ts
|
|
8451
|
-
var VERSION2 = "0.1.
|
|
8451
|
+
var VERSION2 = "0.1.57";
|
|
8452
8452
|
|
|
8453
8453
|
// src/ssh-bootstrap.ts
|
|
8454
8454
|
class SshBootstrapError extends Error {
|
|
@@ -15003,6 +15003,7 @@ import { dirname as dirname11 } from "path";
|
|
|
15003
15003
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
15004
15004
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
15005
15005
|
var COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
|
|
15006
|
+
var COMMUNITY_CREDENTIAL_PARENT = "/etc/forgezero-rehearsal";
|
|
15006
15007
|
var COMMUNITY_CREDENTIAL_ROOT = "/etc/forgezero-rehearsal/creds";
|
|
15007
15008
|
var NODES = new Map([
|
|
15008
15009
|
["dev-fz-n1", { address: "10.42.0.21", agency: true }],
|
|
@@ -15044,6 +15045,12 @@ function parseCommunityRehearsalHostRequest(value) {
|
|
|
15044
15045
|
}
|
|
15045
15046
|
return row2;
|
|
15046
15047
|
}
|
|
15048
|
+
if (action === "cleanup") {
|
|
15049
|
+
if (Object.keys(row2).some((key) => !["action", "node"].includes(key))) {
|
|
15050
|
+
throw new Error("community rehearsal cleanup has unknown fields");
|
|
15051
|
+
}
|
|
15052
|
+
return { action, node: node.name };
|
|
15053
|
+
}
|
|
15047
15054
|
if (["database-enable", "api-enable", "database-ready", "starter-ready", "api-ready", "database-status", "api-status"].includes(String(action))) {
|
|
15048
15055
|
if (Object.keys(row2).some((key) => !["action", "node"].includes(key)))
|
|
15049
15056
|
throw new Error("community rehearsal host action has unknown fields");
|
|
@@ -15159,6 +15166,19 @@ function planCommunityRehearsalPrepare(request) {
|
|
|
15159
15166
|
{ kind: "unlink", path: request.archivePath }
|
|
15160
15167
|
];
|
|
15161
15168
|
}
|
|
15169
|
+
function planCommunityRehearsalCleanup() {
|
|
15170
|
+
return [
|
|
15171
|
+
{ kind: "exec", argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-community-api.service"], accepted: [0, 1, 5] },
|
|
15172
|
+
{ kind: "exec", argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-rehearsal-db.service"], accepted: [0, 1, 5] },
|
|
15173
|
+
{ kind: "unlink", path: "/etc/systemd/system/forgezero-community-api.service" },
|
|
15174
|
+
{ kind: "unlink", path: "/etc/systemd/system/forgezero-rehearsal-db.service" },
|
|
15175
|
+
{ kind: "remove-tree", path: COMMUNITY_DATABASE_ROOT },
|
|
15176
|
+
{ kind: "remove-tree", path: COMMUNITY_REHEARSAL_ROOT },
|
|
15177
|
+
{ kind: "remove-tree", path: COMMUNITY_CREDENTIAL_PARENT },
|
|
15178
|
+
{ kind: "exec", argv: ["/usr/bin/systemctl", "daemon-reload"] },
|
|
15179
|
+
{ kind: "exec", argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-community-api.service", "forgezero-rehearsal-db.service"], accepted: [0, 1, 5] }
|
|
15180
|
+
];
|
|
15181
|
+
}
|
|
15162
15182
|
async function exec(argv2, stdin) {
|
|
15163
15183
|
const child = Bun.spawn([...argv2], { stdin: stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
|
|
15164
15184
|
if (stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
|
|
@@ -15221,6 +15241,10 @@ async function runCommunityRehearsalHost(request) {
|
|
|
15221
15241
|
if ((process.getuid?.() ?? -1) !== 0)
|
|
15222
15242
|
throw new Error("community-rehearsal must run as root");
|
|
15223
15243
|
const node = exactNode(request.node);
|
|
15244
|
+
if (request.action === "cleanup") {
|
|
15245
|
+
await applyOperations(planCommunityRehearsalCleanup());
|
|
15246
|
+
return { ok: true, action: request.action, node: node.name };
|
|
15247
|
+
}
|
|
15224
15248
|
if (request.action === "prepare") {
|
|
15225
15249
|
const metadata = lstatSync6(request.archivePath);
|
|
15226
15250
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash10("sha256").update(readFileSync17(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
package/dist/fz.js
CHANGED
|
@@ -4830,7 +4830,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
4830
4830
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
4831
4831
|
|
|
4832
4832
|
// src/version.ts
|
|
4833
|
-
var VERSION2 = "0.1.
|
|
4833
|
+
var VERSION2 = "0.1.57";
|
|
4834
4834
|
|
|
4835
4835
|
// src/software.ts
|
|
4836
4836
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -14347,6 +14347,26 @@ var record2 = (value, label) => {
|
|
|
14347
14347
|
throw new Error(`${label} must be an object`);
|
|
14348
14348
|
return value;
|
|
14349
14349
|
};
|
|
14350
|
+
function createCloudflareBootstrapCommandConfig(input) {
|
|
14351
|
+
if (typeof input.checkpointPath !== "string" || !input.checkpointPath.trim()) {
|
|
14352
|
+
throw new Error("Cloudflare bootstrap checkpointPath is required");
|
|
14353
|
+
}
|
|
14354
|
+
for (const key of ["tunnelTokenFile", "apiTokenFile"]) {
|
|
14355
|
+
if (typeof input.tokenFiles?.[key] !== "string" || !input.tokenFiles[key].trim()) {
|
|
14356
|
+
throw new Error(`Cloudflare bootstrap tokenFiles.${key} is required and must be a non-empty file path`);
|
|
14357
|
+
}
|
|
14358
|
+
}
|
|
14359
|
+
return {
|
|
14360
|
+
format: 1,
|
|
14361
|
+
kind: "forgezero-cloudflare-bootstrap-request",
|
|
14362
|
+
checkpointPath: input.checkpointPath.trim(),
|
|
14363
|
+
coordinates: validateCloudflareBootstrapCoordinates(input.coordinates),
|
|
14364
|
+
tokenFiles: {
|
|
14365
|
+
tunnelTokenFile: input.tokenFiles.tunnelTokenFile.trim(),
|
|
14366
|
+
apiTokenFile: input.tokenFiles.apiTokenFile.trim()
|
|
14367
|
+
}
|
|
14368
|
+
};
|
|
14369
|
+
}
|
|
14350
14370
|
function readOwnerConfig(path) {
|
|
14351
14371
|
const absolute2 = resolve6(path);
|
|
14352
14372
|
let descriptor;
|
|
@@ -15413,6 +15433,12 @@ var PLATFORM_GENESIS_FLEETS = {
|
|
|
15413
15433
|
...developmentShape
|
|
15414
15434
|
}))
|
|
15415
15435
|
};
|
|
15436
|
+
var DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST = {
|
|
15437
|
+
name: "dev-fz-n4",
|
|
15438
|
+
address: "10.42.0.24",
|
|
15439
|
+
cpuPoolKey: "node7",
|
|
15440
|
+
...developmentShape
|
|
15441
|
+
};
|
|
15416
15442
|
var publicKey = (value) => {
|
|
15417
15443
|
const normalized = value.trim();
|
|
15418
15444
|
if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(normalized)) {
|
|
@@ -15451,6 +15477,36 @@ function platformGenesisClaims(environment, sshPublicKeys) {
|
|
|
15451
15477
|
}
|
|
15452
15478
|
}));
|
|
15453
15479
|
}
|
|
15480
|
+
function platformCommunityRehearsalClaims(sshPublicKeys) {
|
|
15481
|
+
const keys = [...new Set(sshPublicKeys.map(publicKey))];
|
|
15482
|
+
if (keys.length === 0)
|
|
15483
|
+
throw new Error("platform genesis requires at least one operator SSH public key");
|
|
15484
|
+
const seed = platformGenesisClaims("development", keys);
|
|
15485
|
+
const guest = DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST;
|
|
15486
|
+
return [...seed, {
|
|
15487
|
+
computeKey: `platform:${guest.name}`,
|
|
15488
|
+
claimToken: "offline-platform-genesis",
|
|
15489
|
+
claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
|
|
15490
|
+
attempt: 1,
|
|
15491
|
+
action: "create",
|
|
15492
|
+
bootstrap: { kind: "platform-genesis" },
|
|
15493
|
+
access: { sshUser: "forgezero", sshPublicKeys: keys },
|
|
15494
|
+
spec: {
|
|
15495
|
+
reference: `platform:${guest.name}`,
|
|
15496
|
+
imageKey: SUPPORTED_GUEST_IMAGE.key,
|
|
15497
|
+
guestName: guest.name,
|
|
15498
|
+
guestAddress: guest.address,
|
|
15499
|
+
cpuPoolKey: guest.cpuPoolKey,
|
|
15500
|
+
physicalCores: guest.physicalCores,
|
|
15501
|
+
vcpu: guest.vcpu,
|
|
15502
|
+
memoryGib: guest.memoryGib,
|
|
15503
|
+
diskGib: guest.diskGib,
|
|
15504
|
+
egressGuaranteedMbps: guest.egressGuaranteedMbps,
|
|
15505
|
+
egressBurstMbps: guest.egressBurstMbps,
|
|
15506
|
+
confidential: true
|
|
15507
|
+
}
|
|
15508
|
+
}];
|
|
15509
|
+
}
|
|
15454
15510
|
|
|
15455
15511
|
// src/operator-bootstrap.ts
|
|
15456
15512
|
var REQUEST_LIMIT = 64 * 1024;
|
|
@@ -15604,13 +15660,17 @@ function readOperatorMetalBootstrapRequest(path) {
|
|
|
15604
15660
|
}
|
|
15605
15661
|
function planOperatorMetalBootstrap(request, mode) {
|
|
15606
15662
|
const config = readMetalBootstrapConfig(request.metalConfigFile);
|
|
15607
|
-
|
|
15663
|
+
if (mode === "rehearsal" && request.genesis.environment !== "development") {
|
|
15664
|
+
throw new Error("the disposable four-node Community rehearsal is development-only");
|
|
15665
|
+
}
|
|
15666
|
+
const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
|
|
15667
|
+
const claims = mode === "rehearsal" ? platformCommunityRehearsalClaims(keys) : platformGenesisClaims(request.genesis.environment, keys);
|
|
15608
15668
|
return {
|
|
15609
15669
|
kind: "metal-remote",
|
|
15610
15670
|
mode,
|
|
15611
15671
|
mutation: mode !== "status",
|
|
15612
15672
|
target: { address: request.target.address, port: request.target.port, user: request.target.user },
|
|
15613
|
-
steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "genesis" ? ["verify pinned SSH transport", "send three fixed platform-genesis claims to the constrained Metal helper"] : [
|
|
15673
|
+
steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "genesis" || mode === "rehearsal" ? ["verify pinned SSH transport", mode === "rehearsal" ? "send the fixed three-node seed plus ordinary n4 Community rehearsal claim to the constrained Metal helper" : "send three fixed platform-genesis claims to the constrained Metal helper"] : [
|
|
15614
15674
|
"verify pinned SSH transport and caller-approved SSH agent",
|
|
15615
15675
|
"copy pinned Bun and packaged fz artifacts to private staging",
|
|
15616
15676
|
"stage the strict owner-only metal config and optional seed handoff",
|
|
@@ -15905,10 +15965,11 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
|
15905
15965
|
const output2 = await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/local/bin/fz", "bootstrap", "status"], "remote metal status");
|
|
15906
15966
|
return { plan, output: output2 };
|
|
15907
15967
|
}
|
|
15908
|
-
if (mode === "genesis") {
|
|
15968
|
+
if (mode === "genesis" || mode === "rehearsal") {
|
|
15909
15969
|
const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
|
|
15910
15970
|
const outputs = [];
|
|
15911
|
-
|
|
15971
|
+
const claims = mode === "rehearsal" ? platformCommunityRehearsalClaims(keys) : platformGenesisClaims(request.genesis.environment, keys);
|
|
15972
|
+
for (const claim of claims) {
|
|
15912
15973
|
outputs.push(await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/local/bin/fz-agent", "metal-apply", "--claim=-"], `provision ${claim.spec.guestName}`, false, `${JSON.stringify(claim)}
|
|
15913
15974
|
`));
|
|
15914
15975
|
}
|
|
@@ -17077,6 +17138,55 @@ function writeBootstrapConfig(path, config) {
|
|
|
17077
17138
|
`, { mode: 384, flag: "wx" });
|
|
17078
17139
|
return path;
|
|
17079
17140
|
}
|
|
17141
|
+
function interactiveCloudflareBootstrap() {
|
|
17142
|
+
if (!process.stdin.isTTY)
|
|
17143
|
+
throw new Error("interactive Cloudflare config generation requires a terminal");
|
|
17144
|
+
const nodeCount = bootstrapNumber("Number of public API nodes", "3");
|
|
17145
|
+
if (nodeCount < 1 || nodeCount > 32)
|
|
17146
|
+
throw new Error("Number of public API nodes must be between 1 and 32");
|
|
17147
|
+
const realtimeEnabled = bootstrapAnswer("Configure existing Worker realtime fan-out? (yes/no)", "yes") === "yes";
|
|
17148
|
+
const meshEnabled = bootstrapAnswer("Configure private Mesh/WARP routes? (yes/no)", "no") === "yes";
|
|
17149
|
+
const meshDevicePolicyId = meshEnabled ? bootstrapAnswer("Existing Mesh device policy id") : undefined;
|
|
17150
|
+
const nodes = Array.from({ length: nodeCount }, (_, index) => {
|
|
17151
|
+
const ordinal = index + 1;
|
|
17152
|
+
const nodeName = bootstrapAnswer(`Node ${ordinal} inventory name`);
|
|
17153
|
+
const nodeMesh = meshEnabled && bootstrapAnswer(`Configure Mesh routes for ${nodeName}? (yes/no)`, "yes") === "yes";
|
|
17154
|
+
return {
|
|
17155
|
+
nodeName,
|
|
17156
|
+
hostname: bootstrapAnswer(`${nodeName} public hostname`),
|
|
17157
|
+
service: bootstrapAnswer(`${nodeName} loopback API service`, "http://127.0.0.1:3000"),
|
|
17158
|
+
tunnelName: bootstrapAnswer(`${nodeName} Tunnel name`, nodeName),
|
|
17159
|
+
...nodeMesh ? {
|
|
17160
|
+
mesh: {
|
|
17161
|
+
connectorName: bootstrapAnswer(`${nodeName} Mesh connector name`, `${nodeName}-mesh`),
|
|
17162
|
+
routes: bootstrapList(`${nodeName} private CIDR routes`),
|
|
17163
|
+
highAvailability: bootstrapAnswer(`${nodeName} shares a highly available Mesh connector? (yes/no)`, "no") === "yes"
|
|
17164
|
+
}
|
|
17165
|
+
} : {}
|
|
17166
|
+
};
|
|
17167
|
+
});
|
|
17168
|
+
return createCloudflareBootstrapCommandConfig({
|
|
17169
|
+
checkpointPath: bootstrapAnswer("Owner-only resumable checkpoint path", "./cloudflare-handoff.json"),
|
|
17170
|
+
coordinates: {
|
|
17171
|
+
accountId: bootstrapAnswer("Cloudflare account id"),
|
|
17172
|
+
zoneId: bootstrapAnswer("Cloudflare zone id"),
|
|
17173
|
+
kvNamespaceId: bootstrapAnswer("Existing Worker-bound KV namespace id"),
|
|
17174
|
+
...meshDevicePolicyId ? { meshDevicePolicyId } : {},
|
|
17175
|
+
...realtimeEnabled ? {
|
|
17176
|
+
realtime: {
|
|
17177
|
+
workerScriptName: bootstrapAnswer("Existing Worker script name"),
|
|
17178
|
+
endpoint: bootstrapAnswer("Stable public Worker HTTPS endpoint"),
|
|
17179
|
+
producer: bootstrapAnswer("Realtime producer identity", "platform-api")
|
|
17180
|
+
}
|
|
17181
|
+
} : {},
|
|
17182
|
+
nodes
|
|
17183
|
+
},
|
|
17184
|
+
tokenFiles: {
|
|
17185
|
+
tunnelTokenFile: bootstrapAnswer("Owner-only CF_TUNNEL_TOKEN file", "./CF_TUNNEL_TOKEN"),
|
|
17186
|
+
apiTokenFile: bootstrapAnswer("Owner-only CF_API_TOKEN file", "./CF_API_TOKEN")
|
|
17187
|
+
}
|
|
17188
|
+
});
|
|
17189
|
+
}
|
|
17080
17190
|
function genesisOutputDirectory(path) {
|
|
17081
17191
|
if (!isAbsolute5(path) || resolve9(path) !== path)
|
|
17082
17192
|
throw new Error("--output must be a canonical absolute directory");
|
|
@@ -17249,13 +17359,19 @@ async function cmdBootstrap(options, args) {
|
|
|
17249
17359
|
const operation = args[0] ?? "status";
|
|
17250
17360
|
if (operation === "config") {
|
|
17251
17361
|
const kind = args[1];
|
|
17252
|
-
if (kind !== "metal" && kind !== "platform" || args[2] !== undefined || !options.outputPath) {
|
|
17253
|
-
throw new Error("Usage: fz bootstrap config metal --output <absolute-file> | fz bootstrap config platform --output <absolute-owner-only-directory>");
|
|
17362
|
+
if (kind !== "metal" && kind !== "platform" && kind !== "cloudflare" || args[2] !== undefined || !options.outputPath) {
|
|
17363
|
+
throw new Error("Usage: fz bootstrap config metal|cloudflare --output <absolute-file> | fz bootstrap config platform --output <absolute-owner-only-directory>");
|
|
17254
17364
|
}
|
|
17255
17365
|
if (kind === "platform") {
|
|
17256
17366
|
out.line(JSON.stringify({ kind, ...writePlatformGenesisFleet(options.outputPath), mode: "0600" }, null, 2));
|
|
17257
17367
|
return 0;
|
|
17258
17368
|
}
|
|
17369
|
+
if (kind === "cloudflare") {
|
|
17370
|
+
const path = writeBootstrapConfig(options.outputPath, interactiveCloudflareBootstrap());
|
|
17371
|
+
readCloudflareBootstrapCommandConfig(path, "plan");
|
|
17372
|
+
out.line(JSON.stringify({ kind, path, mode: "0600" }, null, 2));
|
|
17373
|
+
return 0;
|
|
17374
|
+
}
|
|
17259
17375
|
const config2 = interactiveMetalBootstrap();
|
|
17260
17376
|
out.line(JSON.stringify({ kind, path: writeBootstrapConfig(options.outputPath, config2), mode: "0600" }, null, 2));
|
|
17261
17377
|
return 0;
|
|
@@ -17279,8 +17395,8 @@ async function cmdBootstrap(options, args) {
|
|
|
17279
17395
|
}
|
|
17280
17396
|
if (operation === "metal" && args[1] === "remote") {
|
|
17281
17397
|
const mode = args[2];
|
|
17282
|
-
if (!mode || !["apply", "genesis", "status"].includes(mode) || args[3] !== undefined) {
|
|
17283
|
-
throw new Error("Usage: fz bootstrap metal remote <apply|genesis|status> --bootstrap-config <owner-only-request.json> [--apply]");
|
|
17398
|
+
if (!mode || !["apply", "genesis", "rehearsal", "status"].includes(mode) || args[3] !== undefined) {
|
|
17399
|
+
throw new Error("Usage: fz bootstrap metal remote <apply|genesis|rehearsal|status> --bootstrap-config <owner-only-request.json> [--apply]");
|
|
17284
17400
|
}
|
|
17285
17401
|
if (!options.bootstrapConfigPath)
|
|
17286
17402
|
throw new Error("remote metal bootstrap requires --bootstrap-config");
|
|
@@ -17391,7 +17507,7 @@ async function cmdBootstrap(options, args) {
|
|
|
17391
17507
|
return 0;
|
|
17392
17508
|
}
|
|
17393
17509
|
if (!["platform", "repair"].includes(operation)) {
|
|
17394
|
-
throw new Error("Usage: fz bootstrap config <metal|platform>|platform [prepare|remote <prepare|apply|status>|cloudflare [verify|finalize]]|metal [remote <apply|genesis|status>]|status|repair [--bootstrap-config <path>] [--apply]");
|
|
17510
|
+
throw new Error("Usage: fz bootstrap config <metal|platform|cloudflare>|platform [prepare|remote <prepare|apply|status>|cloudflare [verify|finalize]]|metal [remote <apply|genesis|rehearsal|status>]|status|repair [--bootstrap-config <path>] [--apply]");
|
|
17395
17511
|
}
|
|
17396
17512
|
const config = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : operation === "repair" ? (() => {
|
|
17397
17513
|
throw new Error("repair requires --bootstrap-config so immutable coordinates are revalidated");
|
|
@@ -17956,9 +18072,9 @@ function usage() {
|
|
|
17956
18072
|
Run typed bootstrap from the operator laptop through
|
|
17957
18073
|
a pinned host and caller-approved SSH agent
|
|
17958
18074
|
fz bootstrap platform Install/repair a typed elastic platform compute
|
|
17959
|
-
fz bootstrap config <metal|platform>
|
|
17960
|
-
Write a validated owner-only metal file or
|
|
17961
|
-
three-node platform genesis directory; never apply locally
|
|
18075
|
+
fz bootstrap config <metal|platform|cloudflare>
|
|
18076
|
+
Write a validated owner-only metal/Cloudflare file or
|
|
18077
|
+
strict three-node platform genesis directory; never apply locally
|
|
17962
18078
|
fz bootstrap platform cloudflare
|
|
17963
18079
|
Plan/apply attended Tunnel and DNS reconciliation
|
|
17964
18080
|
using supplied management and KV/Worker runtime token files
|
|
@@ -17969,7 +18085,7 @@ function usage() {
|
|
|
17969
18085
|
Persist secret-free public-node acceptance, then remove
|
|
17970
18086
|
the capability-bearing checkpoint and node handoffs
|
|
17971
18087
|
fz bootstrap metal Plan/install an identity-only physical provisioner
|
|
17972
|
-
fz bootstrap metal remote <apply|genesis|status>
|
|
18088
|
+
fz bootstrap metal remote <apply|genesis|rehearsal|status>
|
|
17973
18089
|
Install/bootstrap one pinned blank metal target, then
|
|
17974
18090
|
provision the exact selected three-node genesis seed
|
|
17975
18091
|
fz bootstrap status Verify persisted profile and supervised units
|
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.57";
|
|
296
296
|
|
|
297
297
|
// src/otel-collector.ts
|
|
298
298
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -25,7 +25,7 @@ export interface OperatorMetalBootstrapRequest {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
export type OperatorPlatformBootstrapMode = 'prepare' | 'apply' | 'status';
|
|
28
|
-
export type OperatorMetalBootstrapMode = 'apply' | 'genesis' | 'status';
|
|
28
|
+
export type OperatorMetalBootstrapMode = 'apply' | 'genesis' | 'rehearsal' | 'status';
|
|
29
29
|
export interface OperatorPlatformBootstrapPlan {
|
|
30
30
|
kind: 'platform-remote';
|
|
31
31
|
mode: OperatorPlatformBootstrapMode;
|
|
@@ -1207,7 +1207,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1207
1207
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1208
1208
|
|
|
1209
1209
|
// src/version.ts
|
|
1210
|
-
var VERSION = "0.1.
|
|
1210
|
+
var VERSION = "0.1.57";
|
|
1211
1211
|
|
|
1212
1212
|
// src/software.ts
|
|
1213
1213
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -5273,6 +5273,12 @@ var PLATFORM_GENESIS_FLEETS = {
|
|
|
5273
5273
|
...developmentShape
|
|
5274
5274
|
}))
|
|
5275
5275
|
};
|
|
5276
|
+
var DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST = {
|
|
5277
|
+
name: "dev-fz-n4",
|
|
5278
|
+
address: "10.42.0.24",
|
|
5279
|
+
cpuPoolKey: "node7",
|
|
5280
|
+
...developmentShape
|
|
5281
|
+
};
|
|
5276
5282
|
var publicKey = (value) => {
|
|
5277
5283
|
const normalized = value.trim();
|
|
5278
5284
|
if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(normalized)) {
|
|
@@ -5311,6 +5317,36 @@ function platformGenesisClaims(environment, sshPublicKeys) {
|
|
|
5311
5317
|
}
|
|
5312
5318
|
}));
|
|
5313
5319
|
}
|
|
5320
|
+
function platformCommunityRehearsalClaims(sshPublicKeys) {
|
|
5321
|
+
const keys = [...new Set(sshPublicKeys.map(publicKey))];
|
|
5322
|
+
if (keys.length === 0)
|
|
5323
|
+
throw new Error("platform genesis requires at least one operator SSH public key");
|
|
5324
|
+
const seed = platformGenesisClaims("development", keys);
|
|
5325
|
+
const guest = DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST;
|
|
5326
|
+
return [...seed, {
|
|
5327
|
+
computeKey: `platform:${guest.name}`,
|
|
5328
|
+
claimToken: "offline-platform-genesis",
|
|
5329
|
+
claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
|
|
5330
|
+
attempt: 1,
|
|
5331
|
+
action: "create",
|
|
5332
|
+
bootstrap: { kind: "platform-genesis" },
|
|
5333
|
+
access: { sshUser: "forgezero", sshPublicKeys: keys },
|
|
5334
|
+
spec: {
|
|
5335
|
+
reference: `platform:${guest.name}`,
|
|
5336
|
+
imageKey: SUPPORTED_GUEST_IMAGE.key,
|
|
5337
|
+
guestName: guest.name,
|
|
5338
|
+
guestAddress: guest.address,
|
|
5339
|
+
cpuPoolKey: guest.cpuPoolKey,
|
|
5340
|
+
physicalCores: guest.physicalCores,
|
|
5341
|
+
vcpu: guest.vcpu,
|
|
5342
|
+
memoryGib: guest.memoryGib,
|
|
5343
|
+
diskGib: guest.diskGib,
|
|
5344
|
+
egressGuaranteedMbps: guest.egressGuaranteedMbps,
|
|
5345
|
+
egressBurstMbps: guest.egressBurstMbps,
|
|
5346
|
+
confidential: true
|
|
5347
|
+
}
|
|
5348
|
+
}];
|
|
5349
|
+
}
|
|
5314
5350
|
|
|
5315
5351
|
// src/operator-bootstrap.ts
|
|
5316
5352
|
var REQUEST_LIMIT = 64 * 1024;
|
|
@@ -5464,13 +5500,17 @@ function readOperatorMetalBootstrapRequest(path) {
|
|
|
5464
5500
|
}
|
|
5465
5501
|
function planOperatorMetalBootstrap(request, mode) {
|
|
5466
5502
|
const config = readMetalBootstrapConfig(request.metalConfigFile);
|
|
5467
|
-
|
|
5503
|
+
if (mode === "rehearsal" && request.genesis.environment !== "development") {
|
|
5504
|
+
throw new Error("the disposable four-node Community rehearsal is development-only");
|
|
5505
|
+
}
|
|
5506
|
+
const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
|
|
5507
|
+
const claims = mode === "rehearsal" ? platformCommunityRehearsalClaims(keys) : platformGenesisClaims(request.genesis.environment, keys);
|
|
5468
5508
|
return {
|
|
5469
5509
|
kind: "metal-remote",
|
|
5470
5510
|
mode,
|
|
5471
5511
|
mutation: mode !== "status",
|
|
5472
5512
|
target: { address: request.target.address, port: request.target.port, user: request.target.user },
|
|
5473
|
-
steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "genesis" ? ["verify pinned SSH transport", "send three fixed platform-genesis claims to the constrained Metal helper"] : [
|
|
5513
|
+
steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "genesis" || mode === "rehearsal" ? ["verify pinned SSH transport", mode === "rehearsal" ? "send the fixed three-node seed plus ordinary n4 Community rehearsal claim to the constrained Metal helper" : "send three fixed platform-genesis claims to the constrained Metal helper"] : [
|
|
5474
5514
|
"verify pinned SSH transport and caller-approved SSH agent",
|
|
5475
5515
|
"copy pinned Bun and packaged fz artifacts to private staging",
|
|
5476
5516
|
"stage the strict owner-only metal config and optional seed handoff",
|
|
@@ -5765,10 +5805,11 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
|
5765
5805
|
const output2 = await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/local/bin/fz", "bootstrap", "status"], "remote metal status");
|
|
5766
5806
|
return { plan, output: output2 };
|
|
5767
5807
|
}
|
|
5768
|
-
if (mode === "genesis") {
|
|
5808
|
+
if (mode === "genesis" || mode === "rehearsal") {
|
|
5769
5809
|
const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
|
|
5770
5810
|
const outputs = [];
|
|
5771
|
-
|
|
5811
|
+
const claims = mode === "rehearsal" ? platformCommunityRehearsalClaims(keys) : platformGenesisClaims(request.genesis.environment, keys);
|
|
5812
|
+
for (const claim of claims) {
|
|
5772
5813
|
outputs.push(await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/local/bin/fz-agent", "metal-apply", "--claim=-"], `provision ${claim.spec.guestName}`, false, `${JSON.stringify(claim)}
|
|
5773
5814
|
`));
|
|
5774
5815
|
}
|
|
@@ -2086,7 +2086,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2086
2086
|
}
|
|
2087
2087
|
|
|
2088
2088
|
// src/version.ts
|
|
2089
|
-
var VERSION3 = "0.1.
|
|
2089
|
+
var VERSION3 = "0.1.57";
|
|
2090
2090
|
|
|
2091
2091
|
// src/egress-policy.ts
|
|
2092
2092
|
import { realpathSync as realpathSync3 } from "node:fs";
|
|
@@ -14,4 +14,13 @@ export interface PlatformGenesisGuest {
|
|
|
14
14
|
}
|
|
15
15
|
/** Package-owned provisioning coordinates for the exact three-node platform seed. */
|
|
16
16
|
export declare const PLATFORM_GENESIS_FLEETS: Record<PlatformGenesisEnvironment, readonly PlatformGenesisGuest[]>;
|
|
17
|
+
/**
|
|
18
|
+
* The disposable Community acceptance fleet adds one ordinary post-genesis
|
|
19
|
+
* Coordinator/DBServer host. It is deliberately not part of the three-member
|
|
20
|
+
* platform seed and never acquires Agency authority from this provisioning
|
|
21
|
+
* claim. The rehearsal controller installs that database role later.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST: PlatformGenesisGuest;
|
|
17
24
|
export declare function platformGenesisClaims(environment: PlatformGenesisEnvironment, sshPublicKeys: readonly string[]): readonly RemoteProvisionClaim[];
|
|
25
|
+
/** Exact disposable four-host development fleet: three seed guests plus n4. */
|
|
26
|
+
export declare function platformCommunityRehearsalClaims(sshPublicKeys: readonly string[]): readonly RemoteProvisionClaim[];
|
package/dist/provision.js
CHANGED
|
@@ -2086,7 +2086,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2086
2086
|
}
|
|
2087
2087
|
|
|
2088
2088
|
// src/version.ts
|
|
2089
|
-
var VERSION3 = "0.1.
|
|
2089
|
+
var VERSION3 = "0.1.57";
|
|
2090
2090
|
|
|
2091
2091
|
// src/egress-policy.ts
|
|
2092
2092
|
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.57";
|