@forgezero/agent 0.1.70 → 0.1.72
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 +5 -2
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +30 -10
- package/dist/cli/cloudflare-bootstrap.d.ts +37 -1
- package/dist/cloudflare-bootstrap.d.ts +7 -0
- package/dist/cloudflare-bootstrap.js +30 -9
- package/dist/fz-agent.js +1 -1
- package/dist/fz.js +212 -48
- package/dist/metal-bootstrap.d.ts +10 -2
- package/dist/metal-bootstrap.js +10 -5
- package/dist/operator-bootstrap.d.ts +4 -1
- package/dist/operator-bootstrap.js +57 -24
- 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/README.md
CHANGED
|
@@ -644,7 +644,7 @@ import type {
|
|
|
644
644
|
<a id="forgezero-agent-cloudflare-bootstrap"></a>
|
|
645
645
|
## @forgezero/agent/cloudflare-bootstrap
|
|
646
646
|
|
|
647
|
-
Attended two-token Cloudflare Tunnel/DNS reconciliation and per-node connector/KV runtime handoff for existing KV resources. This entry exposes
|
|
647
|
+
Attended two-token Cloudflare Tunnel/DNS reconciliation and per-node connector/KV runtime handoff for existing KV resources. This entry exposes 14 named value exports and 17 named type exports. The generated import block lists one name per line for scanning and copying; keep only the names used by your file.
|
|
648
648
|
|
|
649
649
|
```text
|
|
650
650
|
import {
|
|
@@ -659,6 +659,7 @@ import {
|
|
|
659
659
|
runAttendedCloudflareBootstrap,
|
|
660
660
|
validateCloudflareBootstrapCoordinates,
|
|
661
661
|
validateCloudflareBootstrapTokens,
|
|
662
|
+
validateCloudflareBootstrapTopology,
|
|
662
663
|
verifyCloudflareBootstrapAcceptance,
|
|
663
664
|
writeOwnerBootstrapOutput,
|
|
664
665
|
} from '@forgezero/agent/cloudflare-bootstrap';
|
|
@@ -676,6 +677,7 @@ import type {
|
|
|
676
677
|
CloudflareBootstrapPhaseRunner,
|
|
677
678
|
CloudflareBootstrapPlan,
|
|
678
679
|
CloudflareBootstrapTokens,
|
|
680
|
+
CloudflareBootstrapTopology,
|
|
679
681
|
CloudflareConnectorHandoff,
|
|
680
682
|
CloudflareHostHandoff,
|
|
681
683
|
CloudflareMeshCoordinates,
|
|
@@ -1023,7 +1025,7 @@ import type {
|
|
|
1023
1025
|
<a id="forgezero-agent-metal-bootstrap"></a>
|
|
1024
1026
|
## @forgezero/agent/metal-bootstrap
|
|
1025
1027
|
|
|
1026
|
-
Physical-metal bootstrap planning, application and status evidence. This entry exposes 10 named value exports and
|
|
1028
|
+
Physical-metal bootstrap planning, application and status evidence. This entry exposes 10 named value exports and 7 named type exports. The generated import block lists one name per line for scanning and copying; keep only the names used by your file.
|
|
1027
1029
|
|
|
1028
1030
|
```text
|
|
1029
1031
|
import {
|
|
@@ -1046,6 +1048,7 @@ import type {
|
|
|
1046
1048
|
MetalBootstrapPlan,
|
|
1047
1049
|
MetalBootstrapResult,
|
|
1048
1050
|
MetalBootstrapStatus,
|
|
1051
|
+
MetalBootstrapValidationOptions,
|
|
1049
1052
|
} from '@forgezero/agent/metal-bootstrap';
|
|
1050
1053
|
```
|
|
1051
1054
|
|
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.js
CHANGED
|
@@ -450,20 +450,17 @@ var privateMeshCidr = (value) => {
|
|
|
450
450
|
}
|
|
451
451
|
return `${address}/${prefix}`;
|
|
452
452
|
};
|
|
453
|
-
function
|
|
453
|
+
function validateCloudflareBootstrapTopology(input) {
|
|
454
454
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
455
|
-
throw new Error("Cloudflare bootstrap
|
|
455
|
+
throw new Error("Cloudflare bootstrap topology must be an object");
|
|
456
456
|
}
|
|
457
457
|
const unsupported = Object.keys(input).filter((key) => ![
|
|
458
|
-
"accountId",
|
|
459
|
-
"zoneId",
|
|
460
|
-
"kvNamespaceId",
|
|
461
458
|
"meshDevicePolicyId",
|
|
462
459
|
"realtime",
|
|
463
460
|
"nodes"
|
|
464
461
|
].includes(key));
|
|
465
462
|
if (unsupported.length)
|
|
466
|
-
throw new Error(`Cloudflare bootstrap
|
|
463
|
+
throw new Error(`Cloudflare bootstrap topology contains unsupported field ${unsupported[0]}`);
|
|
467
464
|
const nodeInputs = input.nodes;
|
|
468
465
|
if (!Array.isArray(nodeInputs))
|
|
469
466
|
throw new Error("Cloudflare bootstrap requires an explicit nodes array");
|
|
@@ -570,9 +567,6 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
570
567
|
realtime = { workerScriptName, endpoint: endpoint2.origin, producer };
|
|
571
568
|
}
|
|
572
569
|
return {
|
|
573
|
-
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
574
|
-
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
575
|
-
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
576
570
|
...input.meshDevicePolicyId ? {
|
|
577
571
|
meshDevicePolicyId: validateName(input.meshDevicePolicyId, "Cloudflare Mesh device policy id")
|
|
578
572
|
} : {},
|
|
@@ -580,6 +574,32 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
580
574
|
nodes
|
|
581
575
|
};
|
|
582
576
|
}
|
|
577
|
+
function validateCloudflareBootstrapCoordinates(input) {
|
|
578
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
579
|
+
throw new Error("Cloudflare bootstrap coordinates must be an object");
|
|
580
|
+
}
|
|
581
|
+
const unsupported = Object.keys(input).filter((key) => ![
|
|
582
|
+
"accountId",
|
|
583
|
+
"zoneId",
|
|
584
|
+
"kvNamespaceId",
|
|
585
|
+
"meshDevicePolicyId",
|
|
586
|
+
"realtime",
|
|
587
|
+
"nodes"
|
|
588
|
+
].includes(key));
|
|
589
|
+
if (unsupported.length)
|
|
590
|
+
throw new Error(`Cloudflare bootstrap coordinates contain unsupported field ${unsupported[0]}`);
|
|
591
|
+
const topology = validateCloudflareBootstrapTopology({
|
|
592
|
+
...input.meshDevicePolicyId ? { meshDevicePolicyId: input.meshDevicePolicyId } : {},
|
|
593
|
+
...input.realtime ? { realtime: input.realtime } : {},
|
|
594
|
+
nodes: input.nodes
|
|
595
|
+
});
|
|
596
|
+
return {
|
|
597
|
+
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
598
|
+
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
599
|
+
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
600
|
+
...topology
|
|
601
|
+
};
|
|
602
|
+
}
|
|
583
603
|
function planCloudflareBootstrap(input, outputPath) {
|
|
584
604
|
const coordinates = validateCloudflareBootstrapCoordinates(input);
|
|
585
605
|
return {
|
|
@@ -1396,7 +1416,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1396
1416
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1397
1417
|
|
|
1398
1418
|
// src/version.ts
|
|
1399
|
-
var VERSION = "0.1.
|
|
1419
|
+
var VERSION = "0.1.72";
|
|
1400
1420
|
|
|
1401
1421
|
// src/software.ts
|
|
1402
1422
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { finalizeCloudflareBootstrapAcceptance, verifyCloudflareBootstrapAcceptance, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapEvidence, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapFinalizeRequest, type CloudflareBootstrapPhaseRunner, type CloudflareBootstrapTokens } from '../cloudflare-bootstrap';
|
|
1
|
+
import { finalizeCloudflareBootstrapAcceptance, verifyCloudflareBootstrapAcceptance, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapEvidence, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapFinalizeRequest, type CloudflareBootstrapPhaseRunner, type CloudflareBootstrapTokens, type CloudflareBootstrapNodeCoordinates } from '../cloudflare-bootstrap';
|
|
2
2
|
import { discoverCloudflareBootstrapResources, type CloudflareBootstrapDiscovery } from '../cloudflare-edge';
|
|
3
3
|
export interface CloudflareBootstrapCommandConfig {
|
|
4
4
|
format: 1;
|
|
@@ -6,6 +6,29 @@ export interface CloudflareBootstrapCommandConfig {
|
|
|
6
6
|
checkpointPath: string;
|
|
7
7
|
coordinates: AttendedCloudflareBootstrapRequest['coordinates'];
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Human-reviewable discovery coordinates used by the integrated platform
|
|
11
|
+
* launch. Cloudflare ids are deliberately absent: the two hidden tokens resolve
|
|
12
|
+
* and bind them immediately before apply.
|
|
13
|
+
*/
|
|
14
|
+
export interface CloudflareBootstrapDiscoveryCommandConfig {
|
|
15
|
+
format: 1;
|
|
16
|
+
kind: 'forgezero-cloudflare-bootstrap-discovery-request';
|
|
17
|
+
checkpointPath: string;
|
|
18
|
+
discovery: {
|
|
19
|
+
zoneName: string;
|
|
20
|
+
kvNamespaceTitle: string;
|
|
21
|
+
workerScriptName?: string;
|
|
22
|
+
endpoint?: string;
|
|
23
|
+
producer?: string;
|
|
24
|
+
};
|
|
25
|
+
nodes: readonly CloudflareBootstrapNodeCoordinates[];
|
|
26
|
+
}
|
|
27
|
+
export interface CloudflareBootstrapCommandPlan {
|
|
28
|
+
checkpointPath: string;
|
|
29
|
+
nodes: readonly CloudflareBootstrapNodeCoordinates[];
|
|
30
|
+
discovery?: CloudflareBootstrapDiscoveryCommandConfig['discovery'];
|
|
31
|
+
}
|
|
9
32
|
export interface CloudflareBootstrapCommandDependencies {
|
|
10
33
|
run?: CloudflareBootstrapPhaseRunner;
|
|
11
34
|
write?: (text: string) => void;
|
|
@@ -33,11 +56,24 @@ export declare function createCloudflareBootstrapCommandConfig(input: {
|
|
|
33
56
|
checkpointPath: string;
|
|
34
57
|
coordinates: AttendedCloudflareBootstrapRequest['coordinates'];
|
|
35
58
|
}): CloudflareBootstrapCommandConfig;
|
|
59
|
+
/** Create the secret-free, name-based request used by one-command genesis. */
|
|
60
|
+
export declare function createCloudflareBootstrapDiscoveryCommandConfig(input: {
|
|
61
|
+
checkpointPath: string;
|
|
62
|
+
discovery: CloudflareBootstrapDiscoveryCommandConfig['discovery'];
|
|
63
|
+
nodes: readonly CloudflareBootstrapNodeCoordinates[];
|
|
64
|
+
}): CloudflareBootstrapDiscoveryCommandConfig;
|
|
36
65
|
/**
|
|
37
66
|
* Read reviewed non-secret coordinates for the attended operator phase. Raw
|
|
38
67
|
* token values and a persisted mode are not schema fields.
|
|
39
68
|
*/
|
|
40
69
|
export declare function readCloudflareBootstrapCommandConfig(path: string, mode: 'plan' | 'apply'): AttendedCloudflareBootstrapRequest;
|
|
70
|
+
export declare function readCloudflareBootstrapDiscoveryCommandConfig(path: string): CloudflareBootstrapDiscoveryCommandConfig;
|
|
71
|
+
/** Read either supported request shape without contacting Cloudflare. */
|
|
72
|
+
export declare function readCloudflareBootstrapCommandPlan(path: string): CloudflareBootstrapCommandPlan;
|
|
73
|
+
/** Resolve name selectors with hidden credentials immediately before apply. */
|
|
74
|
+
export declare function resolveCloudflareBootstrapCommandConfig(path: string, tokens: CloudflareBootstrapTokens, dependencies?: {
|
|
75
|
+
discover?: typeof discoverCloudflareBootstrapCommandResources;
|
|
76
|
+
}): Promise<AttendedCloudflareBootstrapRequest>;
|
|
41
77
|
export declare function runCloudflareBootstrapCommand(configPath: string, apply: boolean, tokens?: CloudflareBootstrapTokens, dependencies?: CloudflareBootstrapCommandDependencies): Promise<CloudflareBootstrapEvidence>;
|
|
42
78
|
export declare function runCloudflareBootstrapVerificationCommand(checkpointPath: string, dependencies?: {
|
|
43
79
|
verify?: typeof verifyCloudflareBootstrapAcceptance;
|
|
@@ -115,6 +115,13 @@ export interface CloudflareBootstrapDependencies {
|
|
|
115
115
|
}
|
|
116
116
|
export type CloudflareBootstrapPhaseRunner = (request: AttendedCloudflareBootstrapRequest) => Promise<CloudflareBootstrapEvidence>;
|
|
117
117
|
export declare function validateCloudflareBootstrapTokens(input: unknown): CloudflareBootstrapTokens;
|
|
118
|
+
export interface CloudflareBootstrapTopology {
|
|
119
|
+
meshDevicePolicyId?: string;
|
|
120
|
+
realtime?: CloudflareRealtimeCoordinates;
|
|
121
|
+
nodes: CloudflareBootstrapNodeCoordinates[];
|
|
122
|
+
}
|
|
123
|
+
/** Validate the fleet/realtime shape without inventing resource identifiers. */
|
|
124
|
+
export declare function validateCloudflareBootstrapTopology(input: CloudflareBootstrapTopology): CloudflareBootstrapTopology;
|
|
118
125
|
export declare function validateCloudflareBootstrapCoordinates(input: CloudflareBootstrapCoordinates): CloudflareBootstrapCoordinates;
|
|
119
126
|
export declare function planCloudflareBootstrap(input: CloudflareBootstrapCoordinates, outputPath: string): CloudflareBootstrapPlan;
|
|
120
127
|
export interface CloudflareConnectorHandoff {
|
|
@@ -450,20 +450,17 @@ var privateMeshCidr = (value) => {
|
|
|
450
450
|
}
|
|
451
451
|
return `${address}/${prefix}`;
|
|
452
452
|
};
|
|
453
|
-
function
|
|
453
|
+
function validateCloudflareBootstrapTopology(input) {
|
|
454
454
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
455
|
-
throw new Error("Cloudflare bootstrap
|
|
455
|
+
throw new Error("Cloudflare bootstrap topology must be an object");
|
|
456
456
|
}
|
|
457
457
|
const unsupported = Object.keys(input).filter((key) => ![
|
|
458
|
-
"accountId",
|
|
459
|
-
"zoneId",
|
|
460
|
-
"kvNamespaceId",
|
|
461
458
|
"meshDevicePolicyId",
|
|
462
459
|
"realtime",
|
|
463
460
|
"nodes"
|
|
464
461
|
].includes(key));
|
|
465
462
|
if (unsupported.length)
|
|
466
|
-
throw new Error(`Cloudflare bootstrap
|
|
463
|
+
throw new Error(`Cloudflare bootstrap topology contains unsupported field ${unsupported[0]}`);
|
|
467
464
|
const nodeInputs = input.nodes;
|
|
468
465
|
if (!Array.isArray(nodeInputs))
|
|
469
466
|
throw new Error("Cloudflare bootstrap requires an explicit nodes array");
|
|
@@ -570,9 +567,6 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
570
567
|
realtime = { workerScriptName, endpoint: endpoint2.origin, producer };
|
|
571
568
|
}
|
|
572
569
|
return {
|
|
573
|
-
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
574
|
-
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
575
|
-
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
576
570
|
...input.meshDevicePolicyId ? {
|
|
577
571
|
meshDevicePolicyId: validateName(input.meshDevicePolicyId, "Cloudflare Mesh device policy id")
|
|
578
572
|
} : {},
|
|
@@ -580,6 +574,32 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
580
574
|
nodes
|
|
581
575
|
};
|
|
582
576
|
}
|
|
577
|
+
function validateCloudflareBootstrapCoordinates(input) {
|
|
578
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
579
|
+
throw new Error("Cloudflare bootstrap coordinates must be an object");
|
|
580
|
+
}
|
|
581
|
+
const unsupported = Object.keys(input).filter((key) => ![
|
|
582
|
+
"accountId",
|
|
583
|
+
"zoneId",
|
|
584
|
+
"kvNamespaceId",
|
|
585
|
+
"meshDevicePolicyId",
|
|
586
|
+
"realtime",
|
|
587
|
+
"nodes"
|
|
588
|
+
].includes(key));
|
|
589
|
+
if (unsupported.length)
|
|
590
|
+
throw new Error(`Cloudflare bootstrap coordinates contain unsupported field ${unsupported[0]}`);
|
|
591
|
+
const topology = validateCloudflareBootstrapTopology({
|
|
592
|
+
...input.meshDevicePolicyId ? { meshDevicePolicyId: input.meshDevicePolicyId } : {},
|
|
593
|
+
...input.realtime ? { realtime: input.realtime } : {},
|
|
594
|
+
nodes: input.nodes
|
|
595
|
+
});
|
|
596
|
+
return {
|
|
597
|
+
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
598
|
+
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
599
|
+
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
600
|
+
...topology
|
|
601
|
+
};
|
|
602
|
+
}
|
|
583
603
|
function planCloudflareBootstrap(input, outputPath) {
|
|
584
604
|
const coordinates = validateCloudflareBootstrapCoordinates(input);
|
|
585
605
|
return {
|
|
@@ -1211,6 +1231,7 @@ async function finalizeCloudflareBootstrapAcceptance(request, fetcher = fetch) {
|
|
|
1211
1231
|
export {
|
|
1212
1232
|
writeOwnerBootstrapOutput,
|
|
1213
1233
|
verifyCloudflareBootstrapAcceptance,
|
|
1234
|
+
validateCloudflareBootstrapTopology,
|
|
1214
1235
|
validateCloudflareBootstrapTokens,
|
|
1215
1236
|
validateCloudflareBootstrapCoordinates,
|
|
1216
1237
|
runAttendedCloudflareBootstrap,
|
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.72";
|
|
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.72";
|
|
4833
4833
|
|
|
4834
4834
|
// src/software.ts
|
|
4835
4835
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -12551,20 +12551,17 @@ var privateMeshCidr = (value) => {
|
|
|
12551
12551
|
}
|
|
12552
12552
|
return `${address}/${prefix}`;
|
|
12553
12553
|
};
|
|
12554
|
-
function
|
|
12554
|
+
function validateCloudflareBootstrapTopology(input) {
|
|
12555
12555
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
12556
|
-
throw new Error("Cloudflare bootstrap
|
|
12556
|
+
throw new Error("Cloudflare bootstrap topology must be an object");
|
|
12557
12557
|
}
|
|
12558
12558
|
const unsupported = Object.keys(input).filter((key) => ![
|
|
12559
|
-
"accountId",
|
|
12560
|
-
"zoneId",
|
|
12561
|
-
"kvNamespaceId",
|
|
12562
12559
|
"meshDevicePolicyId",
|
|
12563
12560
|
"realtime",
|
|
12564
12561
|
"nodes"
|
|
12565
12562
|
].includes(key));
|
|
12566
12563
|
if (unsupported.length)
|
|
12567
|
-
throw new Error(`Cloudflare bootstrap
|
|
12564
|
+
throw new Error(`Cloudflare bootstrap topology contains unsupported field ${unsupported[0]}`);
|
|
12568
12565
|
const nodeInputs = input.nodes;
|
|
12569
12566
|
if (!Array.isArray(nodeInputs))
|
|
12570
12567
|
throw new Error("Cloudflare bootstrap requires an explicit nodes array");
|
|
@@ -12671,9 +12668,6 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
12671
12668
|
realtime = { workerScriptName, endpoint: endpoint2.origin, producer };
|
|
12672
12669
|
}
|
|
12673
12670
|
return {
|
|
12674
|
-
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
12675
|
-
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
12676
|
-
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
12677
12671
|
...input.meshDevicePolicyId ? {
|
|
12678
12672
|
meshDevicePolicyId: validateName(input.meshDevicePolicyId, "Cloudflare Mesh device policy id")
|
|
12679
12673
|
} : {},
|
|
@@ -12681,6 +12675,32 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
12681
12675
|
nodes
|
|
12682
12676
|
};
|
|
12683
12677
|
}
|
|
12678
|
+
function validateCloudflareBootstrapCoordinates(input) {
|
|
12679
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
12680
|
+
throw new Error("Cloudflare bootstrap coordinates must be an object");
|
|
12681
|
+
}
|
|
12682
|
+
const unsupported = Object.keys(input).filter((key) => ![
|
|
12683
|
+
"accountId",
|
|
12684
|
+
"zoneId",
|
|
12685
|
+
"kvNamespaceId",
|
|
12686
|
+
"meshDevicePolicyId",
|
|
12687
|
+
"realtime",
|
|
12688
|
+
"nodes"
|
|
12689
|
+
].includes(key));
|
|
12690
|
+
if (unsupported.length)
|
|
12691
|
+
throw new Error(`Cloudflare bootstrap coordinates contain unsupported field ${unsupported[0]}`);
|
|
12692
|
+
const topology = validateCloudflareBootstrapTopology({
|
|
12693
|
+
...input.meshDevicePolicyId ? { meshDevicePolicyId: input.meshDevicePolicyId } : {},
|
|
12694
|
+
...input.realtime ? { realtime: input.realtime } : {},
|
|
12695
|
+
nodes: input.nodes
|
|
12696
|
+
});
|
|
12697
|
+
return {
|
|
12698
|
+
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
12699
|
+
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
12700
|
+
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
12701
|
+
...topology
|
|
12702
|
+
};
|
|
12703
|
+
}
|
|
12684
12704
|
function planCloudflareBootstrap(input, outputPath) {
|
|
12685
12705
|
const coordinates = validateCloudflareBootstrapCoordinates(input);
|
|
12686
12706
|
return {
|
|
@@ -14970,6 +14990,56 @@ function createCloudflareBootstrapCommandConfig(input) {
|
|
|
14970
14990
|
coordinates: validateCloudflareBootstrapCoordinates(input.coordinates)
|
|
14971
14991
|
};
|
|
14972
14992
|
}
|
|
14993
|
+
var normalizeDiscovery = (input) => {
|
|
14994
|
+
const zoneName = input.zoneName.trim().toLowerCase().replace(/\.$/, "");
|
|
14995
|
+
const kvNamespaceTitle = input.kvNamespaceTitle.trim();
|
|
14996
|
+
if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(zoneName)) {
|
|
14997
|
+
throw new Error("Cloudflare zone name is invalid");
|
|
14998
|
+
}
|
|
14999
|
+
if (!kvNamespaceTitle || kvNamespaceTitle.length > 512) {
|
|
15000
|
+
throw new Error("Cloudflare KV namespace title is invalid");
|
|
15001
|
+
}
|
|
15002
|
+
const workerScriptName = input.workerScriptName?.trim();
|
|
15003
|
+
const endpoint2 = input.endpoint?.trim();
|
|
15004
|
+
const producer = input.producer?.trim();
|
|
15005
|
+
if ([workerScriptName, endpoint2, producer].filter((value) => value !== undefined).length !== 0 && (!workerScriptName || !endpoint2 || !producer)) {
|
|
15006
|
+
throw new Error("Cloudflare realtime discovery requires Worker name, endpoint, and producer together");
|
|
15007
|
+
}
|
|
15008
|
+
return {
|
|
15009
|
+
zoneName,
|
|
15010
|
+
kvNamespaceTitle,
|
|
15011
|
+
...workerScriptName ? { workerScriptName, endpoint: endpoint2, producer } : {}
|
|
15012
|
+
};
|
|
15013
|
+
};
|
|
15014
|
+
function createCloudflareBootstrapDiscoveryCommandConfig(input) {
|
|
15015
|
+
if (typeof input.checkpointPath !== "string" || !input.checkpointPath.trim()) {
|
|
15016
|
+
throw new Error("Cloudflare bootstrap checkpointPath is required");
|
|
15017
|
+
}
|
|
15018
|
+
const discovery = normalizeDiscovery(input.discovery);
|
|
15019
|
+
const normalized = validateCloudflareBootstrapTopology({
|
|
15020
|
+
...discovery.workerScriptName ? { realtime: {
|
|
15021
|
+
workerScriptName: discovery.workerScriptName,
|
|
15022
|
+
endpoint: discovery.endpoint,
|
|
15023
|
+
producer: discovery.producer
|
|
15024
|
+
} } : {},
|
|
15025
|
+
nodes: [...input.nodes]
|
|
15026
|
+
});
|
|
15027
|
+
return {
|
|
15028
|
+
format: 1,
|
|
15029
|
+
kind: "forgezero-cloudflare-bootstrap-discovery-request",
|
|
15030
|
+
checkpointPath: input.checkpointPath.trim(),
|
|
15031
|
+
discovery: {
|
|
15032
|
+
zoneName: discovery.zoneName,
|
|
15033
|
+
kvNamespaceTitle: discovery.kvNamespaceTitle,
|
|
15034
|
+
...normalized.realtime ? {
|
|
15035
|
+
workerScriptName: normalized.realtime.workerScriptName,
|
|
15036
|
+
endpoint: normalized.realtime.endpoint,
|
|
15037
|
+
producer: normalized.realtime.producer
|
|
15038
|
+
} : {}
|
|
15039
|
+
},
|
|
15040
|
+
nodes: normalized.nodes
|
|
15041
|
+
};
|
|
15042
|
+
}
|
|
14973
15043
|
function readOwnerConfig(path) {
|
|
14974
15044
|
const absolute2 = resolve7(path);
|
|
14975
15045
|
let descriptor;
|
|
@@ -15037,6 +15107,79 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
|
|
|
15037
15107
|
checkpointPath: resolve7(baseDirectory, input.checkpointPath)
|
|
15038
15108
|
};
|
|
15039
15109
|
}
|
|
15110
|
+
function readCloudflareBootstrapDiscoveryCommandConfig(path) {
|
|
15111
|
+
const input = record2(readOwnerConfig(path), "Cloudflare bootstrap discovery config");
|
|
15112
|
+
const baseDirectory = dirname10(resolve7(path));
|
|
15113
|
+
exactKeys3(input, ["format", "kind", "checkpointPath", "discovery", "nodes"], "Cloudflare bootstrap discovery config");
|
|
15114
|
+
if (input.format !== 1 || input.kind !== "forgezero-cloudflare-bootstrap-discovery-request") {
|
|
15115
|
+
throw new Error("Cloudflare bootstrap discovery config format/kind is invalid");
|
|
15116
|
+
}
|
|
15117
|
+
if (typeof input.checkpointPath !== "string" || !input.checkpointPath.trim()) {
|
|
15118
|
+
throw new Error("Cloudflare bootstrap checkpointPath is required");
|
|
15119
|
+
}
|
|
15120
|
+
const discovery = record2(input.discovery, "Cloudflare bootstrap discovery selectors");
|
|
15121
|
+
exactKeys3(discovery, [
|
|
15122
|
+
"zoneName",
|
|
15123
|
+
"kvNamespaceTitle",
|
|
15124
|
+
"workerScriptName",
|
|
15125
|
+
"endpoint",
|
|
15126
|
+
"producer"
|
|
15127
|
+
], "Cloudflare bootstrap discovery selectors");
|
|
15128
|
+
if (!Array.isArray(input.nodes))
|
|
15129
|
+
throw new Error("Cloudflare bootstrap discovery nodes must be an array");
|
|
15130
|
+
for (const node of input.nodes) {
|
|
15131
|
+
exactKeys3(record2(node, "Cloudflare bootstrap node"), [
|
|
15132
|
+
"nodeName",
|
|
15133
|
+
"hostname",
|
|
15134
|
+
"service",
|
|
15135
|
+
"tunnelName",
|
|
15136
|
+
"mesh"
|
|
15137
|
+
], "Cloudflare bootstrap node");
|
|
15138
|
+
}
|
|
15139
|
+
const created = createCloudflareBootstrapDiscoveryCommandConfig({
|
|
15140
|
+
checkpointPath: resolve7(baseDirectory, input.checkpointPath),
|
|
15141
|
+
discovery,
|
|
15142
|
+
nodes: input.nodes
|
|
15143
|
+
});
|
|
15144
|
+
return created;
|
|
15145
|
+
}
|
|
15146
|
+
function readCloudflareBootstrapCommandPlan(path) {
|
|
15147
|
+
const input = record2(readOwnerConfig(path), "Cloudflare bootstrap config");
|
|
15148
|
+
if (input.kind === "forgezero-cloudflare-bootstrap-discovery-request") {
|
|
15149
|
+
const config = readCloudflareBootstrapDiscoveryCommandConfig(path);
|
|
15150
|
+
return { checkpointPath: config.checkpointPath, nodes: config.nodes, discovery: config.discovery };
|
|
15151
|
+
}
|
|
15152
|
+
const request = readCloudflareBootstrapCommandConfig(path, "plan");
|
|
15153
|
+
return { checkpointPath: request.checkpointPath, nodes: request.coordinates.nodes };
|
|
15154
|
+
}
|
|
15155
|
+
async function resolveCloudflareBootstrapCommandConfig(path, tokens, dependencies = {}) {
|
|
15156
|
+
const input = record2(readOwnerConfig(path), "Cloudflare bootstrap config");
|
|
15157
|
+
if (input.kind !== "forgezero-cloudflare-bootstrap-discovery-request") {
|
|
15158
|
+
const request = readCloudflareBootstrapCommandConfig(path, "apply");
|
|
15159
|
+
request.tokens = tokens;
|
|
15160
|
+
return request;
|
|
15161
|
+
}
|
|
15162
|
+
const config = readCloudflareBootstrapDiscoveryCommandConfig(path);
|
|
15163
|
+
const coordinates = await (dependencies.discover ?? discoverCloudflareBootstrapCommandResources)({
|
|
15164
|
+
zoneName: config.discovery.zoneName,
|
|
15165
|
+
kvNamespaceTitle: config.discovery.kvNamespaceTitle,
|
|
15166
|
+
...config.discovery.workerScriptName ? { workerScriptName: config.discovery.workerScriptName } : {},
|
|
15167
|
+
tokens
|
|
15168
|
+
});
|
|
15169
|
+
return {
|
|
15170
|
+
mode: "apply",
|
|
15171
|
+
checkpointPath: config.checkpointPath,
|
|
15172
|
+
coordinates: validateCloudflareBootstrapCoordinates({
|
|
15173
|
+
...coordinates,
|
|
15174
|
+
...config.discovery.workerScriptName ? { realtime: {
|
|
15175
|
+
workerScriptName: config.discovery.workerScriptName,
|
|
15176
|
+
endpoint: config.discovery.endpoint,
|
|
15177
|
+
producer: config.discovery.producer
|
|
15178
|
+
} } : {},
|
|
15179
|
+
nodes: [...config.nodes]
|
|
15180
|
+
})
|
|
15181
|
+
};
|
|
15182
|
+
}
|
|
15040
15183
|
async function runCloudflareBootstrapCommand(configPath, apply, tokens, dependencies = {}) {
|
|
15041
15184
|
const request = readCloudflareBootstrapCommandConfig(configPath, apply ? "apply" : "plan");
|
|
15042
15185
|
if (apply)
|
|
@@ -15379,7 +15522,7 @@ var validUnit = (unit) => {
|
|
|
15379
15522
|
throw new MetalBootstrapError("the OTLP collector must be independently supervised");
|
|
15380
15523
|
}
|
|
15381
15524
|
};
|
|
15382
|
-
function validateMetalBootstrapConfig(config) {
|
|
15525
|
+
function validateMetalBootstrapConfig(config, options = {}) {
|
|
15383
15526
|
if (!config || typeof config !== "object")
|
|
15384
15527
|
throw new MetalBootstrapError("metal bootstrap config must be an object");
|
|
15385
15528
|
exactKeys4(config, ["kind", "metalHostname", "profile", "hostTelemetryEndpoint", "hostTelemetryUnit", "agentSeedFile"], "metal bootstrap config");
|
|
@@ -15423,7 +15566,7 @@ function validateMetalBootstrapConfig(config) {
|
|
|
15423
15566
|
for (const image of Object.values(config.profile.images))
|
|
15424
15567
|
if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve8(image.path) !== image.path || /[\0\r\n]/.test(image.path))
|
|
15425
15568
|
throw new MetalBootstrapError("every pinned guest image must use the fixed image directory");
|
|
15426
|
-
if (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION2) {
|
|
15569
|
+
if (!options.allowHistoricalRelease && (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION2)) {
|
|
15427
15570
|
throw new MetalBootstrapError("metal guest runtime must use the package-owned Bun and Agent release coordinates");
|
|
15428
15571
|
}
|
|
15429
15572
|
let api;
|
|
@@ -15469,7 +15612,7 @@ function validateOwnerOnlyPath(path, requireRootOwner) {
|
|
|
15469
15612
|
throw new MetalBootstrapError(requireRootOwner ? "private bootstrap file must be root-owned" : "private bootstrap file has an unexpected owner");
|
|
15470
15613
|
}
|
|
15471
15614
|
}
|
|
15472
|
-
function readMetalBootstrapConfig(path) {
|
|
15615
|
+
function readMetalBootstrapConfig(path, options = {}) {
|
|
15473
15616
|
validateOwnerOnlyPath(path, false);
|
|
15474
15617
|
const metadata = statSync(path);
|
|
15475
15618
|
if (metadata.size < 2 || metadata.size > MAX_CONFIG_BYTES)
|
|
@@ -15480,7 +15623,7 @@ function readMetalBootstrapConfig(path) {
|
|
|
15480
15623
|
} catch {
|
|
15481
15624
|
throw new MetalBootstrapError("metal bootstrap config is not valid JSON");
|
|
15482
15625
|
}
|
|
15483
|
-
return validateMetalBootstrapConfig(parsed);
|
|
15626
|
+
return validateMetalBootstrapConfig(parsed, options);
|
|
15484
15627
|
}
|
|
15485
15628
|
function planMetalBootstrap(config) {
|
|
15486
15629
|
validateMetalBootstrapConfig(config);
|
|
@@ -15981,6 +16124,10 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
15981
16124
|
problems.push("metal helper socket is not ready");
|
|
15982
16125
|
if (!updateSocketReady)
|
|
15983
16126
|
problems.push("Agent update helper socket is not ready");
|
|
16127
|
+
const versionResult = await exec([AGENT_PATH, "--version"]);
|
|
16128
|
+
const installedAgentVersion = versionResult.exitCode === 0 && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(versionResult.stdout.trim()) ? versionResult.stdout.trim() : undefined;
|
|
16129
|
+
if (!installedAgentVersion)
|
|
16130
|
+
problems.push("installed Metal Agent version is unavailable or malformed");
|
|
15984
16131
|
return {
|
|
15985
16132
|
initialized: existsSync10(STATE_PATH2),
|
|
15986
16133
|
profileValid,
|
|
@@ -15990,6 +16137,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
15990
16137
|
helperSocketReady,
|
|
15991
16138
|
updateSocketReady,
|
|
15992
16139
|
metalHostname,
|
|
16140
|
+
installedAgentVersion,
|
|
15993
16141
|
problems
|
|
15994
16142
|
};
|
|
15995
16143
|
}
|
|
@@ -16173,7 +16321,7 @@ function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles, cloudfla
|
|
|
16173
16321
|
readOperatorPlatformBootstrapRequest(file);
|
|
16174
16322
|
return writeOwnerJson2(path, request, "operator fleet request");
|
|
16175
16323
|
}
|
|
16176
|
-
var validateMetalRequestValue = (value) => {
|
|
16324
|
+
var validateMetalRequestValue = (value, options = {}) => {
|
|
16177
16325
|
const root = exactKeys5(value, ["kind", "target", "metalConfigFile", "genesis"], "operator metal request");
|
|
16178
16326
|
if (root.kind !== "metal-remote")
|
|
16179
16327
|
throw new Error("operator metal bootstrap kind must be metal-remote");
|
|
@@ -16193,7 +16341,8 @@ var validateMetalRequestValue = (value) => {
|
|
|
16193
16341
|
if (typeof root.metalConfigFile !== "string")
|
|
16194
16342
|
throw new Error("metalConfigFile must be an absolute path");
|
|
16195
16343
|
canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
|
|
16196
|
-
|
|
16344
|
+
if (options.validateMetalConfig !== false)
|
|
16345
|
+
readMetalBootstrapConfig(root.metalConfigFile);
|
|
16197
16346
|
const genesis = exactKeys5(root.genesis, ["nodes", "rehearsalNode", "sshPublicKeyFiles"], "operator metal genesis");
|
|
16198
16347
|
const guest = (value2, label) => {
|
|
16199
16348
|
const row2 = exactKeys5(value2, [
|
|
@@ -16252,9 +16401,9 @@ var validateMetalRequestValue = (value) => {
|
|
|
16252
16401
|
}
|
|
16253
16402
|
};
|
|
16254
16403
|
};
|
|
16255
|
-
function readOperatorMetalBootstrapRequest(path) {
|
|
16404
|
+
function readOperatorMetalBootstrapRequest(path, options = {}) {
|
|
16256
16405
|
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal request")));
|
|
16257
|
-
return validateMetalRequestValue(value);
|
|
16406
|
+
return validateMetalRequestValue(value, options);
|
|
16258
16407
|
}
|
|
16259
16408
|
function writeOperatorMetalBootstrapRequest(path, request) {
|
|
16260
16409
|
validateMetalRequestValue(request);
|
|
@@ -16303,7 +16452,7 @@ function writeOperatorGuestHostKeyEvidence(path, evidence, request) {
|
|
|
16303
16452
|
return writeOwnerJson2(path, validateGuestHostKeyEvidenceValue(evidence, request), "operator guest host-key evidence");
|
|
16304
16453
|
}
|
|
16305
16454
|
function planOperatorMetalBootstrap(request, mode) {
|
|
16306
|
-
const config = readMetalBootstrapConfig(request.metalConfigFile);
|
|
16455
|
+
const config = mode === "apply" ? readMetalBootstrapConfig(request.metalConfigFile) : undefined;
|
|
16307
16456
|
const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
|
|
16308
16457
|
if ((mode === "rehearsal" || mode === "rehearsal-cleanup") && !request.genesis.rehearsalNode) {
|
|
16309
16458
|
throw new Error("the reviewed metal request has no rehearsal node");
|
|
@@ -16323,7 +16472,7 @@ function planOperatorMetalBootstrap(request, mode) {
|
|
|
16323
16472
|
"remove transient local and remote staging data"
|
|
16324
16473
|
],
|
|
16325
16474
|
genesisNodes,
|
|
16326
|
-
secretInputs: config
|
|
16475
|
+
secretInputs: config?.agentSeedFile ? ["metal-agent-seed"] : []
|
|
16327
16476
|
};
|
|
16328
16477
|
}
|
|
16329
16478
|
var secretSources = (config) => [
|
|
@@ -16475,7 +16624,10 @@ var defaultExec3 = async (argv2, options = {}) => {
|
|
|
16475
16624
|
new Response(child.stderr).text(),
|
|
16476
16625
|
child.exited
|
|
16477
16626
|
]);
|
|
16478
|
-
return {
|
|
16627
|
+
return {
|
|
16628
|
+
exitCode,
|
|
16629
|
+
output: options.secret ? options.safeStdout && exitCode === 0 ? stdout.slice(0, 65536) : "" : `${stdout}${stderr}`.slice(0, 65536)
|
|
16630
|
+
};
|
|
16479
16631
|
};
|
|
16480
16632
|
var checked5 = async (exec, argv2, label, options) => {
|
|
16481
16633
|
const result = await exec(argv2, options);
|
|
@@ -16529,9 +16681,13 @@ var safeRemoteArg = (value) => {
|
|
|
16529
16681
|
if (!/^[A-Za-z0-9_+.,:@%/=\/-]{1,1024}$/.test(value))
|
|
16530
16682
|
throw new Error(`unsafe remote argument: ${value}`);
|
|
16531
16683
|
};
|
|
16532
|
-
var remote = async (exec, request, knownHosts, argv2, label, secret = false, stdin) => {
|
|
16684
|
+
var remote = async (exec, request, knownHosts, argv2, label, secret = false, stdin, safeStdout = false) => {
|
|
16533
16685
|
argv2.forEach(safeRemoteArg);
|
|
16534
|
-
return checked5(exec, ["ssh", ...sshOptions(request, knownHosts), destination2(request.target), "--", ...argv2], label, {
|
|
16686
|
+
return checked5(exec, ["ssh", ...sshOptions(request, knownHosts), destination2(request.target), "--", ...argv2], label, {
|
|
16687
|
+
secret,
|
|
16688
|
+
stdin,
|
|
16689
|
+
safeStdout
|
|
16690
|
+
});
|
|
16535
16691
|
};
|
|
16536
16692
|
var remoteRegularFileExists = async (exec, request, knownHosts, path) => {
|
|
16537
16693
|
safeRemoteArg(path);
|
|
@@ -16736,7 +16892,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
|
|
|
16736
16892
|
const command = ["/usr/bin/sudo", "-n", "/usr/local/bin/fz", "bootstrap", "platform", "credentials-stdin"];
|
|
16737
16893
|
command.push("--bootstrap-config", `${REMOTE_STAGE}/platform-config.json`, "--apply");
|
|
16738
16894
|
const output = await remote(exec, request, knownHosts, command, "remote typed bootstrap", mode === "apply", secrets ? `${JSON.stringify(secrets)}
|
|
16739
|
-
` : undefined);
|
|
16895
|
+
` : undefined, mode === "apply");
|
|
16740
16896
|
return { plan, output };
|
|
16741
16897
|
} finally {
|
|
16742
16898
|
if (knownHosts) {
|
|
@@ -18175,11 +18331,13 @@ function writeMetalOperatorFiles(directory) {
|
|
|
18175
18331
|
function writePlatformGenesisFleet(directory) {
|
|
18176
18332
|
const output = genesisOutputDirectory(directory);
|
|
18177
18333
|
const metalRequestFile = bootstrapAnswer("Owner-only metal remote request containing the reviewed genesis guests");
|
|
18178
|
-
const metalRequest = readOperatorMetalBootstrapRequest(metalRequestFile);
|
|
18334
|
+
const metalRequest = readOperatorMetalBootstrapRequest(metalRequestFile, { validateMetalConfig: false });
|
|
18179
18335
|
const guestHostKeysFile = bootstrapAnswer("Owner-only guest host-key evidence file");
|
|
18180
18336
|
const guestHostKeys = readOperatorGuestHostKeyEvidence(guestHostKeysFile, metalRequest);
|
|
18181
18337
|
const fleet = metalRequest.genesis.nodes;
|
|
18182
|
-
const metalHostname = readMetalBootstrapConfig(metalRequest.metalConfigFile
|
|
18338
|
+
const metalHostname = readMetalBootstrapConfig(metalRequest.metalConfigFile, {
|
|
18339
|
+
allowHistoricalRelease: true
|
|
18340
|
+
}).metalHostname;
|
|
18183
18341
|
const checkpointPath = join12(output, "cloudflare-handoff.json");
|
|
18184
18342
|
const template = interactiveBootstrap("platform", fleet, checkpointPath);
|
|
18185
18343
|
const region = {
|
|
@@ -18195,24 +18353,23 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18195
18353
|
cloudflareHandoffFile: cloudflareHostHandoffPath(checkpointPath, guest.name)
|
|
18196
18354
|
});
|
|
18197
18355
|
const realtimeEnabled = bootstrapBoolean("Configure existing Worker realtime fan-out?", "yes");
|
|
18198
|
-
const cloudflareConfig = writeBootstrapConfig(join12(output, "cloudflare.json"),
|
|
18356
|
+
const cloudflareConfig = writeBootstrapConfig(join12(output, "cloudflare.json"), createCloudflareBootstrapDiscoveryCommandConfig({
|
|
18199
18357
|
checkpointPath,
|
|
18200
|
-
|
|
18201
|
-
|
|
18202
|
-
|
|
18203
|
-
|
|
18204
|
-
...realtimeEnabled ? { realtime: {
|
|
18358
|
+
discovery: {
|
|
18359
|
+
zoneName: bootstrapAnswer("Cloudflare DNS zone name", "forgezero.net"),
|
|
18360
|
+
kvNamespaceTitle: bootstrapAnswer("Existing Worker-bound KV namespace title"),
|
|
18361
|
+
...realtimeEnabled ? {
|
|
18205
18362
|
workerScriptName: bootstrapAnswer("Existing Worker script name"),
|
|
18206
18363
|
endpoint: bootstrapAnswer("Stable public Worker HTTPS endpoint"),
|
|
18207
18364
|
producer: bootstrapAnswer("Realtime producer identity", "platform-api")
|
|
18208
|
-
}
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
|
|
18212
|
-
|
|
18213
|
-
|
|
18214
|
-
})
|
|
18215
|
-
}
|
|
18365
|
+
} : {}
|
|
18366
|
+
},
|
|
18367
|
+
nodes: fleet.map((guest, index) => ({
|
|
18368
|
+
nodeName: guest.name,
|
|
18369
|
+
hostname: nodes[index].nodeHostname,
|
|
18370
|
+
service: "http://127.0.0.1:3000",
|
|
18371
|
+
tunnelName: bootstrapAnswer(`${guest.name} Tunnel name`, guest.name)
|
|
18372
|
+
}))
|
|
18216
18373
|
}));
|
|
18217
18374
|
const cloudflareAcceptance = join12(output, "cloudflare-acceptance.json");
|
|
18218
18375
|
const configs = platformGenesisBootstrapConfigs(template, fleet, nodes, metalHostname, region).map((config) => {
|
|
@@ -18288,7 +18445,7 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
|
|
|
18288
18445
|
const backupBucket = backupEndpoint ? bootstrapAnswer("Backup S3 bucket") : undefined;
|
|
18289
18446
|
const backupAccessKeyId = backupEndpoint ? bootstrapAnswer("Backup S3 access-key id") : undefined;
|
|
18290
18447
|
const cloudflareHandoffFile = genesis ? cloudflareHostHandoffPath(genesisCloudflareCheckpoint, computeReference) : bootstrapAnswer("Node-specific Cloudflare host handoff (blank for no public edge)", "");
|
|
18291
|
-
const cloudflareNodeName = cloudflareHandoffFile ? bootstrapAnswer("Cloudflare handoff node name", computeReference) : undefined;
|
|
18448
|
+
const cloudflareNodeName = cloudflareHandoffFile ? genesis ? computeReference : bootstrapAnswer("Cloudflare handoff node name", computeReference) : undefined;
|
|
18292
18449
|
const databaseNetworkMode = "private-lan";
|
|
18293
18450
|
const enrolmentSource = genesis ? "genesis-derived" : bootstrapAnswer("Enrolment source (genesis-derived/api-token)", "genesis-derived");
|
|
18294
18451
|
return {
|
|
@@ -18365,10 +18522,10 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
|
|
|
18365
18522
|
}
|
|
18366
18523
|
async function runAttendedPlatformFleet(fleet, apply) {
|
|
18367
18524
|
const plan = planOperatorPlatformFleetBootstrap(fleet, "apply");
|
|
18368
|
-
const
|
|
18525
|
+
const cloudflarePlan = readCloudflareBootstrapCommandPlan(fleet.cloudflareConfigFile);
|
|
18369
18526
|
const coordinates = operatorPlatformFleetCoordinates(fleet);
|
|
18370
|
-
if (
|
|
18371
|
-
const edge =
|
|
18527
|
+
if (cloudflarePlan.nodes.length !== coordinates.nodes.length || coordinates.nodes.some((node, index) => {
|
|
18528
|
+
const edge = cloudflarePlan.nodes[index];
|
|
18372
18529
|
return edge?.nodeName !== node.computeReference || edge.hostname !== node.nodeHostname || edge.service !== "http://127.0.0.1:3000";
|
|
18373
18530
|
})) {
|
|
18374
18531
|
throw new Error("Cloudflare coordinates do not match the exact reviewed platform fleet");
|
|
@@ -18377,8 +18534,9 @@ async function runAttendedPlatformFleet(fleet, apply) {
|
|
|
18377
18534
|
kind: "forgezero-attended-platform-bootstrap-plan",
|
|
18378
18535
|
mutation: true,
|
|
18379
18536
|
cloudflare: {
|
|
18380
|
-
checkpointFile:
|
|
18381
|
-
|
|
18537
|
+
checkpointFile: cloudflarePlan.checkpointPath,
|
|
18538
|
+
...cloudflarePlan.discovery ? { discovery: cloudflarePlan.discovery } : {},
|
|
18539
|
+
nodes: cloudflarePlan.nodes.map(({ nodeName, hostname: hostname2 }) => ({ nodeName, hostname: hostname2 }))
|
|
18382
18540
|
},
|
|
18383
18541
|
fleet: plan,
|
|
18384
18542
|
acceptanceFile: fleet.cloudflareAcceptanceFile,
|
|
@@ -18386,7 +18544,7 @@ async function runAttendedPlatformFleet(fleet, apply) {
|
|
|
18386
18544
|
};
|
|
18387
18545
|
out.line(JSON.stringify(attendedPlan, null, 2));
|
|
18388
18546
|
if (!apply) {
|
|
18389
|
-
out.step("Review the
|
|
18547
|
+
out.step("Review the generated plan, then apply its platform-fleet-remote.json with fz bootstrap platform fleet apply --apply.");
|
|
18390
18548
|
return 0;
|
|
18391
18549
|
}
|
|
18392
18550
|
const first = readOperatorPlatformBootstrapRequest(fleet.requests[0]);
|
|
@@ -18394,6 +18552,10 @@ async function runAttendedPlatformFleet(fleet, apply) {
|
|
|
18394
18552
|
if (firstConfig.kind !== "platform")
|
|
18395
18553
|
throw new Error("remote platform fleet requires platform configs");
|
|
18396
18554
|
const secrets = await promptPlatformBootstrapSecrets(firstConfig);
|
|
18555
|
+
const cloudflare = await resolveCloudflareBootstrapCommandConfig(fleet.cloudflareConfigFile, {
|
|
18556
|
+
tunnelToken: secrets.cloudflareTunnelToken,
|
|
18557
|
+
apiToken: secrets.cloudflareApiToken
|
|
18558
|
+
});
|
|
18397
18559
|
const result = await applyAttendedPlatformBootstrap(fleet, cloudflare, fleet.cloudflareAcceptanceFile, secrets);
|
|
18398
18560
|
out.line(JSON.stringify(result, null, 2));
|
|
18399
18561
|
if (!result.ok)
|
|
@@ -18492,7 +18654,9 @@ async function cmdBootstrap(options, args) {
|
|
|
18492
18654
|
if ((mode === "host-keys" || mode === "rehearsal-host-keys") && !options.outputPath) {
|
|
18493
18655
|
throw new Error(`${mode} requires --output <absolute-owner-only-evidence.json>`);
|
|
18494
18656
|
}
|
|
18495
|
-
const request = readOperatorMetalBootstrapRequest(options.bootstrapConfigPath
|
|
18657
|
+
const request = readOperatorMetalBootstrapRequest(options.bootstrapConfigPath, {
|
|
18658
|
+
validateMetalConfig: mode === "apply"
|
|
18659
|
+
});
|
|
18496
18660
|
const plan2 = planOperatorMetalBootstrap(request, mode);
|
|
18497
18661
|
if (!options.apply) {
|
|
18498
18662
|
out.line(JSON.stringify(plan2, null, 2));
|
|
@@ -28,6 +28,7 @@ export interface MetalBootstrapStatus {
|
|
|
28
28
|
helperSocketReady: boolean;
|
|
29
29
|
updateSocketReady: boolean;
|
|
30
30
|
metalHostname?: string;
|
|
31
|
+
installedAgentVersion?: string;
|
|
31
32
|
problems: string[];
|
|
32
33
|
}
|
|
33
34
|
export interface MetalBootstrapResult extends MetalBootstrapStatus {
|
|
@@ -43,10 +44,17 @@ export interface MetalBootstrapApplyOptions {
|
|
|
43
44
|
export declare class MetalBootstrapError extends Error {
|
|
44
45
|
}
|
|
45
46
|
export declare function normalizeMetalPublicIdentity(value: string): string;
|
|
46
|
-
export
|
|
47
|
+
export interface MetalBootstrapValidationOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Read identity/topology from a config that was already applied by an older
|
|
50
|
+
* Agent release. New installs and repairs must never set this flag.
|
|
51
|
+
*/
|
|
52
|
+
allowHistoricalRelease?: boolean;
|
|
53
|
+
}
|
|
54
|
+
export declare function validateMetalBootstrapConfig(config: MetalBootstrapConfig, options?: MetalBootstrapValidationOptions): MetalBootstrapConfig;
|
|
47
55
|
/** Reject symlinks and group/world access before parsing coordinates or entropy. */
|
|
48
56
|
export declare function validateOwnerOnlyPath(path: string, requireRootOwner: boolean): void;
|
|
49
|
-
export declare function readMetalBootstrapConfig(path: string): MetalBootstrapConfig;
|
|
57
|
+
export declare function readMetalBootstrapConfig(path: string, options?: MetalBootstrapValidationOptions): MetalBootstrapConfig;
|
|
50
58
|
export declare function planMetalBootstrap(config: MetalBootstrapConfig): MetalBootstrapPlan;
|
|
51
59
|
export declare function renderMetalUnits(config: MetalBootstrapConfig): Record<string, string>;
|
|
52
60
|
export declare function applyMetalBootstrap(config: MetalBootstrapConfig, options: MetalBootstrapApplyOptions): Promise<MetalBootstrapResult>;
|
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.72";
|
|
358
358
|
|
|
359
359
|
// src/otel-collector.ts
|
|
360
360
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -579,7 +579,7 @@ var validUnit = (unit) => {
|
|
|
579
579
|
throw new MetalBootstrapError("the OTLP collector must be independently supervised");
|
|
580
580
|
}
|
|
581
581
|
};
|
|
582
|
-
function validateMetalBootstrapConfig(config) {
|
|
582
|
+
function validateMetalBootstrapConfig(config, options = {}) {
|
|
583
583
|
if (!config || typeof config !== "object")
|
|
584
584
|
throw new MetalBootstrapError("metal bootstrap config must be an object");
|
|
585
585
|
exactKeys(config, ["kind", "metalHostname", "profile", "hostTelemetryEndpoint", "hostTelemetryUnit", "agentSeedFile"], "metal bootstrap config");
|
|
@@ -623,7 +623,7 @@ function validateMetalBootstrapConfig(config) {
|
|
|
623
623
|
for (const image of Object.values(config.profile.images))
|
|
624
624
|
if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve(image.path) !== image.path || /[\0\r\n]/.test(image.path))
|
|
625
625
|
throw new MetalBootstrapError("every pinned guest image must use the fixed image directory");
|
|
626
|
-
if (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION) {
|
|
626
|
+
if (!options.allowHistoricalRelease && (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION)) {
|
|
627
627
|
throw new MetalBootstrapError("metal guest runtime must use the package-owned Bun and Agent release coordinates");
|
|
628
628
|
}
|
|
629
629
|
let api;
|
|
@@ -669,7 +669,7 @@ function validateOwnerOnlyPath(path, requireRootOwner) {
|
|
|
669
669
|
throw new MetalBootstrapError(requireRootOwner ? "private bootstrap file must be root-owned" : "private bootstrap file has an unexpected owner");
|
|
670
670
|
}
|
|
671
671
|
}
|
|
672
|
-
function readMetalBootstrapConfig(path) {
|
|
672
|
+
function readMetalBootstrapConfig(path, options = {}) {
|
|
673
673
|
validateOwnerOnlyPath(path, false);
|
|
674
674
|
const metadata = statSync(path);
|
|
675
675
|
if (metadata.size < 2 || metadata.size > MAX_CONFIG_BYTES)
|
|
@@ -680,7 +680,7 @@ function readMetalBootstrapConfig(path) {
|
|
|
680
680
|
} catch {
|
|
681
681
|
throw new MetalBootstrapError("metal bootstrap config is not valid JSON");
|
|
682
682
|
}
|
|
683
|
-
return validateMetalBootstrapConfig(parsed);
|
|
683
|
+
return validateMetalBootstrapConfig(parsed, options);
|
|
684
684
|
}
|
|
685
685
|
function planMetalBootstrap(config) {
|
|
686
686
|
validateMetalBootstrapConfig(config);
|
|
@@ -1181,6 +1181,10 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
1181
1181
|
problems.push("metal helper socket is not ready");
|
|
1182
1182
|
if (!updateSocketReady)
|
|
1183
1183
|
problems.push("Agent update helper socket is not ready");
|
|
1184
|
+
const versionResult = await exec([AGENT_PATH, "--version"]);
|
|
1185
|
+
const installedAgentVersion = versionResult.exitCode === 0 && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(versionResult.stdout.trim()) ? versionResult.stdout.trim() : undefined;
|
|
1186
|
+
if (!installedAgentVersion)
|
|
1187
|
+
problems.push("installed Metal Agent version is unavailable or malformed");
|
|
1184
1188
|
return {
|
|
1185
1189
|
initialized: existsSync(STATE_PATH),
|
|
1186
1190
|
profileValid,
|
|
@@ -1190,6 +1194,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
1190
1194
|
helperSocketReady,
|
|
1191
1195
|
updateSocketReady,
|
|
1192
1196
|
metalHostname,
|
|
1197
|
+
installedAgentVersion,
|
|
1193
1198
|
problems
|
|
1194
1199
|
};
|
|
1195
1200
|
}
|
|
@@ -115,6 +115,7 @@ export interface OperatorCommandResult {
|
|
|
115
115
|
export type OperatorCommand = (argv: readonly string[], options?: {
|
|
116
116
|
stdin?: string;
|
|
117
117
|
secret?: boolean;
|
|
118
|
+
safeStdout?: boolean;
|
|
118
119
|
}) => Promise<OperatorCommandResult>;
|
|
119
120
|
export interface OperatorPlatformBootstrapOptions {
|
|
120
121
|
exec?: OperatorCommand;
|
|
@@ -131,7 +132,9 @@ export declare function writeOperatorPlatformBootstrapFleetRequest(path: string,
|
|
|
131
132
|
configFile: string;
|
|
132
133
|
acceptanceFile: string;
|
|
133
134
|
}): string;
|
|
134
|
-
export declare function readOperatorMetalBootstrapRequest(path: string
|
|
135
|
+
export declare function readOperatorMetalBootstrapRequest(path: string, options?: {
|
|
136
|
+
validateMetalConfig?: boolean;
|
|
137
|
+
}): OperatorMetalBootstrapRequest;
|
|
135
138
|
export declare function writeOperatorMetalBootstrapRequest(path: string, request: OperatorMetalBootstrapRequest): string;
|
|
136
139
|
export declare function readOperatorGuestHostKeyEvidence(path: string, request?: OperatorMetalBootstrapRequest): OperatorGuestHostKeyEvidence;
|
|
137
140
|
export declare function writeOperatorGuestHostKeyEvidence(path: string, evidence: OperatorGuestHostKeyEvidence, request?: OperatorMetalBootstrapRequest): string;
|
|
@@ -450,20 +450,17 @@ var privateMeshCidr = (value) => {
|
|
|
450
450
|
}
|
|
451
451
|
return `${address}/${prefix}`;
|
|
452
452
|
};
|
|
453
|
-
function
|
|
453
|
+
function validateCloudflareBootstrapTopology(input) {
|
|
454
454
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
455
|
-
throw new Error("Cloudflare bootstrap
|
|
455
|
+
throw new Error("Cloudflare bootstrap topology must be an object");
|
|
456
456
|
}
|
|
457
457
|
const unsupported = Object.keys(input).filter((key) => ![
|
|
458
|
-
"accountId",
|
|
459
|
-
"zoneId",
|
|
460
|
-
"kvNamespaceId",
|
|
461
458
|
"meshDevicePolicyId",
|
|
462
459
|
"realtime",
|
|
463
460
|
"nodes"
|
|
464
461
|
].includes(key));
|
|
465
462
|
if (unsupported.length)
|
|
466
|
-
throw new Error(`Cloudflare bootstrap
|
|
463
|
+
throw new Error(`Cloudflare bootstrap topology contains unsupported field ${unsupported[0]}`);
|
|
467
464
|
const nodeInputs = input.nodes;
|
|
468
465
|
if (!Array.isArray(nodeInputs))
|
|
469
466
|
throw new Error("Cloudflare bootstrap requires an explicit nodes array");
|
|
@@ -570,9 +567,6 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
570
567
|
realtime = { workerScriptName, endpoint: endpoint2.origin, producer };
|
|
571
568
|
}
|
|
572
569
|
return {
|
|
573
|
-
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
574
|
-
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
575
|
-
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
576
570
|
...input.meshDevicePolicyId ? {
|
|
577
571
|
meshDevicePolicyId: validateName(input.meshDevicePolicyId, "Cloudflare Mesh device policy id")
|
|
578
572
|
} : {},
|
|
@@ -580,6 +574,32 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
580
574
|
nodes
|
|
581
575
|
};
|
|
582
576
|
}
|
|
577
|
+
function validateCloudflareBootstrapCoordinates(input) {
|
|
578
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
579
|
+
throw new Error("Cloudflare bootstrap coordinates must be an object");
|
|
580
|
+
}
|
|
581
|
+
const unsupported = Object.keys(input).filter((key) => ![
|
|
582
|
+
"accountId",
|
|
583
|
+
"zoneId",
|
|
584
|
+
"kvNamespaceId",
|
|
585
|
+
"meshDevicePolicyId",
|
|
586
|
+
"realtime",
|
|
587
|
+
"nodes"
|
|
588
|
+
].includes(key));
|
|
589
|
+
if (unsupported.length)
|
|
590
|
+
throw new Error(`Cloudflare bootstrap coordinates contain unsupported field ${unsupported[0]}`);
|
|
591
|
+
const topology = validateCloudflareBootstrapTopology({
|
|
592
|
+
...input.meshDevicePolicyId ? { meshDevicePolicyId: input.meshDevicePolicyId } : {},
|
|
593
|
+
...input.realtime ? { realtime: input.realtime } : {},
|
|
594
|
+
nodes: input.nodes
|
|
595
|
+
});
|
|
596
|
+
return {
|
|
597
|
+
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
598
|
+
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
599
|
+
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
600
|
+
...topology
|
|
601
|
+
};
|
|
602
|
+
}
|
|
583
603
|
function planCloudflareBootstrap(input, outputPath) {
|
|
584
604
|
const coordinates = validateCloudflareBootstrapCoordinates(input);
|
|
585
605
|
return {
|
|
@@ -1396,7 +1416,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1396
1416
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1397
1417
|
|
|
1398
1418
|
// src/version.ts
|
|
1399
|
-
var VERSION = "0.1.
|
|
1419
|
+
var VERSION = "0.1.72";
|
|
1400
1420
|
|
|
1401
1421
|
// src/software.ts
|
|
1402
1422
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -5213,7 +5233,7 @@ var validUnit = (unit) => {
|
|
|
5213
5233
|
throw new MetalBootstrapError("the OTLP collector must be independently supervised");
|
|
5214
5234
|
}
|
|
5215
5235
|
};
|
|
5216
|
-
function validateMetalBootstrapConfig(config) {
|
|
5236
|
+
function validateMetalBootstrapConfig(config, options = {}) {
|
|
5217
5237
|
if (!config || typeof config !== "object")
|
|
5218
5238
|
throw new MetalBootstrapError("metal bootstrap config must be an object");
|
|
5219
5239
|
exactKeys2(config, ["kind", "metalHostname", "profile", "hostTelemetryEndpoint", "hostTelemetryUnit", "agentSeedFile"], "metal bootstrap config");
|
|
@@ -5257,7 +5277,7 @@ function validateMetalBootstrapConfig(config) {
|
|
|
5257
5277
|
for (const image of Object.values(config.profile.images))
|
|
5258
5278
|
if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve5(image.path) !== image.path || /[\0\r\n]/.test(image.path))
|
|
5259
5279
|
throw new MetalBootstrapError("every pinned guest image must use the fixed image directory");
|
|
5260
|
-
if (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION) {
|
|
5280
|
+
if (!options.allowHistoricalRelease && (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION)) {
|
|
5261
5281
|
throw new MetalBootstrapError("metal guest runtime must use the package-owned Bun and Agent release coordinates");
|
|
5262
5282
|
}
|
|
5263
5283
|
let api;
|
|
@@ -5303,7 +5323,7 @@ function validateOwnerOnlyPath(path, requireRootOwner) {
|
|
|
5303
5323
|
throw new MetalBootstrapError(requireRootOwner ? "private bootstrap file must be root-owned" : "private bootstrap file has an unexpected owner");
|
|
5304
5324
|
}
|
|
5305
5325
|
}
|
|
5306
|
-
function readMetalBootstrapConfig(path) {
|
|
5326
|
+
function readMetalBootstrapConfig(path, options = {}) {
|
|
5307
5327
|
validateOwnerOnlyPath(path, false);
|
|
5308
5328
|
const metadata = statSync(path);
|
|
5309
5329
|
if (metadata.size < 2 || metadata.size > MAX_CONFIG_BYTES)
|
|
@@ -5314,7 +5334,7 @@ function readMetalBootstrapConfig(path) {
|
|
|
5314
5334
|
} catch {
|
|
5315
5335
|
throw new MetalBootstrapError("metal bootstrap config is not valid JSON");
|
|
5316
5336
|
}
|
|
5317
|
-
return validateMetalBootstrapConfig(parsed);
|
|
5337
|
+
return validateMetalBootstrapConfig(parsed, options);
|
|
5318
5338
|
}
|
|
5319
5339
|
function planMetalBootstrap(config) {
|
|
5320
5340
|
validateMetalBootstrapConfig(config);
|
|
@@ -5815,6 +5835,10 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
5815
5835
|
problems.push("metal helper socket is not ready");
|
|
5816
5836
|
if (!updateSocketReady)
|
|
5817
5837
|
problems.push("Agent update helper socket is not ready");
|
|
5838
|
+
const versionResult = await exec([AGENT_PATH, "--version"]);
|
|
5839
|
+
const installedAgentVersion = versionResult.exitCode === 0 && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(versionResult.stdout.trim()) ? versionResult.stdout.trim() : undefined;
|
|
5840
|
+
if (!installedAgentVersion)
|
|
5841
|
+
problems.push("installed Metal Agent version is unavailable or malformed");
|
|
5818
5842
|
return {
|
|
5819
5843
|
initialized: existsSync6(STATE_PATH2),
|
|
5820
5844
|
profileValid,
|
|
@@ -5824,6 +5848,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
5824
5848
|
helperSocketReady,
|
|
5825
5849
|
updateSocketReady,
|
|
5826
5850
|
metalHostname,
|
|
5851
|
+
installedAgentVersion,
|
|
5827
5852
|
problems
|
|
5828
5853
|
};
|
|
5829
5854
|
}
|
|
@@ -6013,7 +6038,7 @@ function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles, cloudfla
|
|
|
6013
6038
|
readOperatorPlatformBootstrapRequest(file);
|
|
6014
6039
|
return writeOwnerJson2(path, request, "operator fleet request");
|
|
6015
6040
|
}
|
|
6016
|
-
var validateMetalRequestValue = (value) => {
|
|
6041
|
+
var validateMetalRequestValue = (value, options = {}) => {
|
|
6017
6042
|
const root = exactKeys3(value, ["kind", "target", "metalConfigFile", "genesis"], "operator metal request");
|
|
6018
6043
|
if (root.kind !== "metal-remote")
|
|
6019
6044
|
throw new Error("operator metal bootstrap kind must be metal-remote");
|
|
@@ -6033,7 +6058,8 @@ var validateMetalRequestValue = (value) => {
|
|
|
6033
6058
|
if (typeof root.metalConfigFile !== "string")
|
|
6034
6059
|
throw new Error("metalConfigFile must be an absolute path");
|
|
6035
6060
|
canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
|
|
6036
|
-
|
|
6061
|
+
if (options.validateMetalConfig !== false)
|
|
6062
|
+
readMetalBootstrapConfig(root.metalConfigFile);
|
|
6037
6063
|
const genesis = exactKeys3(root.genesis, ["nodes", "rehearsalNode", "sshPublicKeyFiles"], "operator metal genesis");
|
|
6038
6064
|
const guest = (value2, label) => {
|
|
6039
6065
|
const row = exactKeys3(value2, [
|
|
@@ -6092,9 +6118,9 @@ var validateMetalRequestValue = (value) => {
|
|
|
6092
6118
|
}
|
|
6093
6119
|
};
|
|
6094
6120
|
};
|
|
6095
|
-
function readOperatorMetalBootstrapRequest(path) {
|
|
6121
|
+
function readOperatorMetalBootstrapRequest(path, options = {}) {
|
|
6096
6122
|
const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal request")));
|
|
6097
|
-
return validateMetalRequestValue(value);
|
|
6123
|
+
return validateMetalRequestValue(value, options);
|
|
6098
6124
|
}
|
|
6099
6125
|
function writeOperatorMetalBootstrapRequest(path, request) {
|
|
6100
6126
|
validateMetalRequestValue(request);
|
|
@@ -6143,7 +6169,7 @@ function writeOperatorGuestHostKeyEvidence(path, evidence, request) {
|
|
|
6143
6169
|
return writeOwnerJson2(path, validateGuestHostKeyEvidenceValue(evidence, request), "operator guest host-key evidence");
|
|
6144
6170
|
}
|
|
6145
6171
|
function planOperatorMetalBootstrap(request, mode) {
|
|
6146
|
-
const config = readMetalBootstrapConfig(request.metalConfigFile);
|
|
6172
|
+
const config = mode === "apply" ? readMetalBootstrapConfig(request.metalConfigFile) : undefined;
|
|
6147
6173
|
const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
|
|
6148
6174
|
if ((mode === "rehearsal" || mode === "rehearsal-cleanup") && !request.genesis.rehearsalNode) {
|
|
6149
6175
|
throw new Error("the reviewed metal request has no rehearsal node");
|
|
@@ -6163,7 +6189,7 @@ function planOperatorMetalBootstrap(request, mode) {
|
|
|
6163
6189
|
"remove transient local and remote staging data"
|
|
6164
6190
|
],
|
|
6165
6191
|
genesisNodes,
|
|
6166
|
-
secretInputs: config
|
|
6192
|
+
secretInputs: config?.agentSeedFile ? ["metal-agent-seed"] : []
|
|
6167
6193
|
};
|
|
6168
6194
|
}
|
|
6169
6195
|
var secretSources = (config) => [
|
|
@@ -6315,7 +6341,10 @@ var defaultExec3 = async (argv, options = {}) => {
|
|
|
6315
6341
|
new Response(child.stderr).text(),
|
|
6316
6342
|
child.exited
|
|
6317
6343
|
]);
|
|
6318
|
-
return {
|
|
6344
|
+
return {
|
|
6345
|
+
exitCode,
|
|
6346
|
+
output: options.secret ? options.safeStdout && exitCode === 0 ? stdout.slice(0, 65536) : "" : `${stdout}${stderr}`.slice(0, 65536)
|
|
6347
|
+
};
|
|
6319
6348
|
};
|
|
6320
6349
|
var checked5 = async (exec, argv, label, options) => {
|
|
6321
6350
|
const result = await exec(argv, options);
|
|
@@ -6369,9 +6398,13 @@ var safeRemoteArg = (value) => {
|
|
|
6369
6398
|
if (!/^[A-Za-z0-9_+.,:@%/=\/-]{1,1024}$/.test(value))
|
|
6370
6399
|
throw new Error(`unsafe remote argument: ${value}`);
|
|
6371
6400
|
};
|
|
6372
|
-
var remote = async (exec, request, knownHosts, argv, label, secret = false, stdin) => {
|
|
6401
|
+
var remote = async (exec, request, knownHosts, argv, label, secret = false, stdin, safeStdout = false) => {
|
|
6373
6402
|
argv.forEach(safeRemoteArg);
|
|
6374
|
-
return checked5(exec, ["ssh", ...sshOptions(request, knownHosts), destination2(request.target), "--", ...argv], label, {
|
|
6403
|
+
return checked5(exec, ["ssh", ...sshOptions(request, knownHosts), destination2(request.target), "--", ...argv], label, {
|
|
6404
|
+
secret,
|
|
6405
|
+
stdin,
|
|
6406
|
+
safeStdout
|
|
6407
|
+
});
|
|
6375
6408
|
};
|
|
6376
6409
|
var remoteRegularFileExists = async (exec, request, knownHosts, path) => {
|
|
6377
6410
|
safeRemoteArg(path);
|
|
@@ -6639,7 +6672,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
|
|
|
6639
6672
|
const command = ["/usr/bin/sudo", "-n", "/usr/local/bin/fz", "bootstrap", "platform", "credentials-stdin"];
|
|
6640
6673
|
command.push("--bootstrap-config", `${REMOTE_STAGE}/platform-config.json`, "--apply");
|
|
6641
6674
|
const output = await remote(exec, request, knownHosts, command, "remote typed bootstrap", mode === "apply", secrets ? `${JSON.stringify(secrets)}
|
|
6642
|
-
` : undefined);
|
|
6675
|
+
` : undefined, mode === "apply");
|
|
6643
6676
|
return { plan, output };
|
|
6644
6677
|
} finally {
|
|
6645
6678
|
if (knownHosts) {
|
|
@@ -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.72";
|
|
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.72";
|
|
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.72";
|