@forgezero/agent 0.1.79 → 0.1.80
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 +57 -6
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +21 -6
- package/dist/deploy-compiler.js +50 -2
- package/dist/deploy-plan-runner.js +50 -2
- package/dist/deploy-plan.js +50 -2
- package/dist/deploy.d.ts +37 -1
- package/dist/deploy.js +1 -0
- package/dist/deployment-connectivity.d.ts +26 -0
- package/dist/deployment-connectivity.js +128 -1
- package/dist/deployment-targets.js +15 -1
- package/dist/deployment-topology.d.ts +46 -0
- package/dist/deployment-topology.js +113 -0
- package/dist/deployment.d.ts +40 -0
- package/dist/fz-agent.js +368 -14
- package/dist/fz.js +75 -12
- package/dist/index.d.ts +1 -0
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +21 -6
- package/dist/platform-bootstrap-runtime.d.ts +4 -1
- package/dist/platform-bootstrap-runtime.js +18 -4
- package/dist/platform-fleet-verification.js +147 -6
- package/dist/provision.js +129 -2
- package/dist/software-helper.js +128 -1
- package/dist/version.d.ts +1 -1
- package/package.json +5 -1
package/dist/fz-agent.js
CHANGED
|
@@ -6384,7 +6384,7 @@ function reference(value, where) {
|
|
|
6384
6384
|
const row2 = object(value, where);
|
|
6385
6385
|
exact3(row2, ["$ref"], where);
|
|
6386
6386
|
const coordinate = boundedString(row2.$ref, `${where}.$ref`, 256);
|
|
6387
|
-
if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62}|execution\.(targetName|slot|releaseExecutor|runtime|cpuCores|memoryMiB|storageGiB))$/.test(coordinate)) {
|
|
6387
|
+
if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62}|execution\.(targetName|slot|releaseExecutor|runtime|cpuCores|memoryMiB|storageGiB|site|topologyRole|topologyTransport|localRelayAddress))$/.test(coordinate)) {
|
|
6388
6388
|
fail(where, "contains an unsupported reference.");
|
|
6389
6389
|
}
|
|
6390
6390
|
return { $ref: coordinate };
|
|
@@ -6816,6 +6816,12 @@ function validateStep(value, where, targets, context) {
|
|
|
6816
6816
|
if (!targets.has(String(scope.target)))
|
|
6817
6817
|
fail(`${where}.scope.target`, "must name an existing target.");
|
|
6818
6818
|
integer(scope.size, `${where}.scope.size`, 1, 32);
|
|
6819
|
+
} else if (scope.kind === "topology-role") {
|
|
6820
|
+
exact3(scope, ["kind", "target", "role"], `${where}.scope`);
|
|
6821
|
+
if (!targets.has(String(scope.target)))
|
|
6822
|
+
fail(`${where}.scope.target`, "must name an existing target.");
|
|
6823
|
+
if (!["relay", "standby", "member"].includes(String(scope.role)))
|
|
6824
|
+
fail(`${where}.scope.role`, "is unsupported.");
|
|
6819
6825
|
} else
|
|
6820
6826
|
fail(`${where}.scope.kind`, "is unsupported.");
|
|
6821
6827
|
if (row2.timeoutMs !== undefined)
|
|
@@ -6970,7 +6976,7 @@ function compileDeployment(sourceValue) {
|
|
|
6970
6976
|
const targetDefinitions = new Map;
|
|
6971
6977
|
for (const [targetName, value] of targetEntries) {
|
|
6972
6978
|
const target = object(value, `deployment.spec.targets.${targetName}`);
|
|
6973
|
-
exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "placement"], `deployment.spec.targets.${targetName}`);
|
|
6979
|
+
exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "topology", "placement"], `deployment.spec.targets.${targetName}`);
|
|
6974
6980
|
if (target.kind !== "compute")
|
|
6975
6981
|
fail(`deployment.spec.targets.${targetName}.kind`, "must be compute.");
|
|
6976
6982
|
const selector = object(target.selector, `deployment.spec.targets.${targetName}.selector`);
|
|
@@ -7092,6 +7098,45 @@ function compileDeployment(sourceValue) {
|
|
|
7092
7098
|
}
|
|
7093
7099
|
}
|
|
7094
7100
|
}
|
|
7101
|
+
if (target.topology !== undefined) {
|
|
7102
|
+
const topology = object(target.topology, `deployment.spec.targets.${targetName}.topology`);
|
|
7103
|
+
exact3(topology, ["groupBy", "relays", "crossMetal"], `deployment.spec.targets.${targetName}.topology`);
|
|
7104
|
+
if (topology.groupBy !== "metal")
|
|
7105
|
+
fail(`deployment.spec.targets.${targetName}.topology.groupBy`, "must be metal.");
|
|
7106
|
+
const relays = object(topology.relays, `deployment.spec.targets.${targetName}.topology.relays`);
|
|
7107
|
+
exact3(relays, ["activePerMetal", "standbyPerMetal", "healthPort", "routedTcpPorts"], `deployment.spec.targets.${targetName}.topology.relays`);
|
|
7108
|
+
if (relays.activePerMetal !== 1 || ![0, 1].includes(Number(relays.standbyPerMetal)))
|
|
7109
|
+
fail(`deployment.spec.targets.${targetName}.topology.relays`, "requires one active relay and zero or one standby per metal.");
|
|
7110
|
+
integer(relays.healthPort, `deployment.spec.targets.${targetName}.topology.relays.healthPort`, 1, 65535);
|
|
7111
|
+
if (!Array.isArray(relays.routedTcpPorts) || relays.routedTcpPorts.length < 1 || relays.routedTcpPorts.length > 64 || new Set(relays.routedTcpPorts).size !== relays.routedTcpPorts.length) {
|
|
7112
|
+
fail(`deployment.spec.targets.${targetName}.topology.relays.routedTcpPorts`, "must contain 1 to 64 unique ports.");
|
|
7113
|
+
}
|
|
7114
|
+
for (const [index, port] of relays.routedTcpPorts.entries()) {
|
|
7115
|
+
integer(port, `deployment.spec.targets.${targetName}.topology.relays.routedTcpPorts.${index}`, 1, 65535);
|
|
7116
|
+
}
|
|
7117
|
+
if (!relays.routedTcpPorts.includes(relays.healthPort)) {
|
|
7118
|
+
fail(`deployment.spec.targets.${targetName}.topology.relays`, "routedTcpPorts must include healthPort.");
|
|
7119
|
+
}
|
|
7120
|
+
const crossMetal = object(topology.crossMetal, `deployment.spec.targets.${targetName}.topology.crossMetal`);
|
|
7121
|
+
if (crossMetal.mode === "private-lan")
|
|
7122
|
+
exact3(crossMetal, ["mode"], `deployment.spec.targets.${targetName}.topology.crossMetal`);
|
|
7123
|
+
else if (crossMetal.mode === "cloudflare-warp" || crossMetal.mode === "auto") {
|
|
7124
|
+
exact3(crossMetal, crossMetal.mode === "auto" ? ["mode", "directPrivateMaximumMetals", "credential", "network"] : ["mode", "credential", "network"], `deployment.spec.targets.${targetName}.topology.crossMetal`);
|
|
7125
|
+
if (crossMetal.mode === "auto" && crossMetal.directPrivateMaximumMetals !== 2)
|
|
7126
|
+
fail(`deployment.spec.targets.${targetName}.topology.crossMetal.directPrivateMaximumMetals`, "must be 2.");
|
|
7127
|
+
if (!credentials[named(crossMetal.credential, `deployment.spec.targets.${targetName}.topology.crossMetal.credential`)])
|
|
7128
|
+
fail(`deployment.spec.targets.${targetName}.topology.crossMetal.credential`, "must name a declared credential.");
|
|
7129
|
+
if (typeof crossMetal.network === "string")
|
|
7130
|
+
named(crossMetal.network, `deployment.spec.targets.${targetName}.topology.crossMetal.network`);
|
|
7131
|
+
else
|
|
7132
|
+
reference(crossMetal.network, `deployment.spec.targets.${targetName}.topology.crossMetal.network`);
|
|
7133
|
+
} else
|
|
7134
|
+
fail(`deployment.spec.targets.${targetName}.topology.crossMetal.mode`, "is unsupported.");
|
|
7135
|
+
if (allocation.sharing !== "exclusive")
|
|
7136
|
+
fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "must be exclusive for a per-metal relay topology.");
|
|
7137
|
+
if (minimum < 1 + Number(relays.standbyPerMetal))
|
|
7138
|
+
fail(`deployment.spec.targets.${targetName}.cardinality.minimum`, "must allow the declared relay and standby.");
|
|
7139
|
+
}
|
|
7095
7140
|
if (target.placement !== undefined)
|
|
7096
7141
|
jsonValue(target.placement, `deployment.spec.targets.${targetName}.placement`);
|
|
7097
7142
|
targetDefinitions.set(targetName, target);
|
|
@@ -7115,6 +7160,9 @@ function compileDeployment(sourceValue) {
|
|
|
7115
7160
|
if (target.connectivity?.private?.mode === "cloudflare-warp" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
|
|
7116
7161
|
fail(`deployment.spec.targets.${targetName}.connectivity.private`, "requires one network.private provider requirement.");
|
|
7117
7162
|
}
|
|
7163
|
+
if (target.topology && target.topology.crossMetal.mode !== "private-lan" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
|
|
7164
|
+
fail(`deployment.spec.targets.${targetName}.topology.crossMetal`, "requires one network.private provider requirement.");
|
|
7165
|
+
}
|
|
7118
7166
|
}
|
|
7119
7167
|
const components = object(spec.components, "deployment.spec.components");
|
|
7120
7168
|
const componentEntries = Object.entries(components);
|
|
@@ -7372,6 +7420,19 @@ function resolveDeploymentTargets(plan, supplied = {}) {
|
|
|
7372
7420
|
} : { mode: "disabled" } } : {}
|
|
7373
7421
|
};
|
|
7374
7422
|
}
|
|
7423
|
+
let topology;
|
|
7424
|
+
if (target.topology) {
|
|
7425
|
+
const crossMetal = target.topology.crossMetal;
|
|
7426
|
+
topology = {
|
|
7427
|
+
groupBy: "metal",
|
|
7428
|
+
relays: { ...target.topology.relays },
|
|
7429
|
+
crossMetal: crossMetal.mode === "private-lan" ? { mode: "private-lan" } : {
|
|
7430
|
+
...crossMetal,
|
|
7431
|
+
credential: credentialSource(crossMetal.credential, `target ${name} topology WARP credential`),
|
|
7432
|
+
network: stringCoordinate(crossMetal.network, `target ${name} topology WARP network`)
|
|
7433
|
+
}
|
|
7434
|
+
};
|
|
7435
|
+
}
|
|
7375
7436
|
return {
|
|
7376
7437
|
name,
|
|
7377
7438
|
profiles: [...target.selector.profiles],
|
|
@@ -7390,7 +7451,8 @@ function resolveDeploymentTargets(plan, supplied = {}) {
|
|
|
7390
7451
|
}
|
|
7391
7452
|
},
|
|
7392
7453
|
...target.provisioning ? { provisioning: { ...target.provisioning } } : {},
|
|
7393
|
-
...connectivity ? { connectivity } : {}
|
|
7454
|
+
...connectivity ? { connectivity } : {},
|
|
7455
|
+
...topology ? { topology } : {}
|
|
7394
7456
|
};
|
|
7395
7457
|
});
|
|
7396
7458
|
}
|
|
@@ -8096,12 +8158,19 @@ function createDeploymentManager(options) {
|
|
|
8096
8158
|
if (!selected || mismatches.length > 0) {
|
|
8097
8159
|
throw new DeploymentError("PIPELINE_FAILED", `the control-plane target assignment does not match this exact deployment plan (${mismatches.join(", ")})`);
|
|
8098
8160
|
}
|
|
8099
|
-
const
|
|
8161
|
+
const assignedTopology = assigned.topology;
|
|
8162
|
+
const selectedTopology = selected.topology;
|
|
8163
|
+
const topologyMatches = selectedTopology ? Boolean(assignedTopology && /^[A-Za-z0-9.-]{1,253}$/.test(assignedTopology.site) && /^[A-Za-z0-9.-]{1,253}$/.test(assignedTopology.nodeIdentity) && /^[A-Za-z0-9._:-]{1,256}$/.test(assignedTopology.localRelayReference) && /^sha256:[a-f0-9]{64}$/.test(assignedTopology.generation) && /^(?:\d{1,3}\.){3}0\/24$/.test(assignedTopology.siteCidr) && assignedTopology.localRelayAddress.length <= 64 && assignedTopology.localPeerAddresses.length <= 1023 && assignedTopology.localPeerAddresses.every((address) => typeof address === "string" && address.length <= 64) && assignedTopology.localPeerIdentities.length === assignedTopology.localPeerAddresses.length && assignedTopology.localPeerIdentities.every((identity) => /^[A-Za-z0-9.-]{1,253}$/.test(identity)) && assignedTopology.remoteRelayAddresses.length <= 1023 && assignedTopology.remoteRelayAddresses.every((address) => typeof address === "string" && address.length <= 64) && assignedTopology.remoteRelayIdentities.length === assignedTopology.remoteRelayAddresses.length && assignedTopology.remoteRelayIdentities.every((identity) => /^[A-Za-z0-9.-]{1,253}$/.test(identity)) && assignedTopology.memberIdentities.length >= 1 && assignedTopology.memberIdentities.length <= 1024 && new Set(assignedTopology.memberIdentities).size === assignedTopology.memberIdentities.length && assignedTopology.memberIdentities.includes(assignedTopology.nodeIdentity) && assignedTopology.memberIdentities.every((identity) => /^[A-Za-z0-9.-]{1,253}$/.test(identity)) && (assignedTopology.role === "member" ? assignedTopology.remoteRelayAddresses.length === 0 : assignedTopology.remoteSiteCidrs.length === assignedTopology.remoteRelayAddresses.length) && assignedTopology.remoteSiteCidrs.every((cidr) => typeof cidr === "string" && /^(?:\d{1,3}\.){3}0\/24$/.test(cidr)) && Number.isSafeInteger(assignedTopology.healthPort) && assignedTopology.healthPort >= 1 && assignedTopology.healthPort <= 65535 && assignedTopology.routedTcpPorts.length >= 1 && assignedTopology.routedTcpPorts.length <= 64 && new Set(assignedTopology.routedTcpPorts).size === assignedTopology.routedTcpPorts.length && assignedTopology.routedTcpPorts.includes(assignedTopology.healthPort) && assignedTopology.routedTcpPorts.every((port) => Number.isSafeInteger(port) && port >= 1 && port <= 65535) && (assignedTopology.role !== "member" || assignedTopology.transport === "private-lan" && assignedTopology.warpRequired === false && assignedTopology.remoteRelayAddresses.length === 0 && assignedTopology.localPeerAddresses.length === 1) && (assignedTopology.warpRequired ? assignedTopology.role !== "member" && assignedTopology.transport === "cloudflare-warp" && selectedTopology.crossMetal.mode !== "private-lan" : assignedTopology.transport === "private-lan")) : assignedTopology === undefined;
|
|
8164
|
+
const expectedPrivate = selectedTopology ? assignedTopology?.warpRequired ? selectedTopology.crossMetal.mode === "private-lan" ? undefined : {
|
|
8165
|
+
mode: "cloudflare-warp",
|
|
8166
|
+
credential: selectedTopology.crossMetal.credential,
|
|
8167
|
+
network: selectedTopology.crossMetal.network
|
|
8168
|
+
} : { mode: "private-lan" } : selected.connectivity?.private;
|
|
8100
8169
|
const expectedPublic = selected.connectivity?.public;
|
|
8101
8170
|
const assignedPrivate = assigned.connectivity?.private;
|
|
8102
8171
|
const assignedPublic = assigned.connectivity?.public;
|
|
8103
8172
|
const publicMatches = expectedPublic?.mode === "cloudflare-tunnel" ? assignedPublic?.mode === "cloudflare-tunnel" && assignedPublic.credential === expectedPublic.credential && assignedPublic.service.protocol === expectedPublic.service.protocol && assignedPublic.service.port === expectedPublic.service.port && assignedPublic.hostname === (expectedPublic.hostname.mode === "static" ? `${expectedPublic.hostname.label}.${expectedPublic.zone}` : `${expectedPublic.hostname.prefix}${expectedPublic.hostname.startAt + assigned.slot}.${expectedPublic.zone}`) : expectedPublic?.mode === "disabled" ? assignedPublic?.mode === "disabled" : assignedPublic === undefined;
|
|
8104
|
-
if (JSON.stringify(assignedPrivate) !== JSON.stringify(expectedPrivate) || !publicMatches) {
|
|
8173
|
+
if (!topologyMatches || JSON.stringify(assignedPrivate) !== JSON.stringify(expectedPrivate) || !publicMatches) {
|
|
8105
8174
|
throw new DeploymentError("PIPELINE_FAILED", "the control-plane connectivity assignment does not match this target slot");
|
|
8106
8175
|
}
|
|
8107
8176
|
targetNames = new Set([assigned.targetName]);
|
|
@@ -8118,7 +8187,24 @@ function createDeploymentManager(options) {
|
|
|
8118
8187
|
FZ_TARGET_CPU_CORES: String(assigned.resources.cpuCores),
|
|
8119
8188
|
FZ_TARGET_MEMORY_MIB: String(assigned.resources.memoryMiB),
|
|
8120
8189
|
FZ_TARGET_STORAGE_GIB: String(assigned.resources.storageGiB),
|
|
8121
|
-
FZ_TARGET_EXECUTION: assigned.execution
|
|
8190
|
+
FZ_TARGET_EXECUTION: assigned.execution,
|
|
8191
|
+
...assigned.topology ? {
|
|
8192
|
+
FZ_TOPOLOGY_SITE: assigned.topology.site,
|
|
8193
|
+
FZ_TOPOLOGY_NODE_IDENTITY: assigned.topology.nodeIdentity,
|
|
8194
|
+
FZ_TOPOLOGY_SITE_CIDR: assigned.topology.siteCidr,
|
|
8195
|
+
FZ_TOPOLOGY_ROLE: assigned.topology.role,
|
|
8196
|
+
FZ_TOPOLOGY_TRANSPORT: assigned.topology.transport,
|
|
8197
|
+
FZ_TOPOLOGY_LOCAL_RELAY: assigned.topology.localRelayAddress,
|
|
8198
|
+
FZ_TOPOLOGY_LOCAL_PEERS: assigned.topology.localPeerAddresses.join(","),
|
|
8199
|
+
FZ_TOPOLOGY_LOCAL_PEER_IDENTITIES: assigned.topology.localPeerIdentities.join(","),
|
|
8200
|
+
FZ_TOPOLOGY_REMOTE_RELAYS: assigned.topology.remoteRelayAddresses.join(","),
|
|
8201
|
+
FZ_TOPOLOGY_REMOTE_RELAY_IDENTITIES: assigned.topology.remoteRelayIdentities.join(","),
|
|
8202
|
+
FZ_TOPOLOGY_REMOTE_CIDRS: assigned.topology.remoteSiteCidrs.join(","),
|
|
8203
|
+
FZ_TOPOLOGY_MEMBERS: assigned.topology.memberIdentities.join(","),
|
|
8204
|
+
FZ_TOPOLOGY_HEALTH_PORT: String(assigned.topology.healthPort),
|
|
8205
|
+
FZ_TOPOLOGY_ROUTED_TCP_PORTS: assigned.topology.routedTcpPorts.join(","),
|
|
8206
|
+
FZ_TOPOLOGY_GENERATION: assigned.topology.generation
|
|
8207
|
+
} : {}
|
|
8122
8208
|
});
|
|
8123
8209
|
for (const targetName of targetNames) {
|
|
8124
8210
|
const capacity2 = resolvedTargets.find(({ name }) => name === targetName)?.allocation.resources;
|
|
@@ -8174,11 +8260,15 @@ function createDeploymentManager(options) {
|
|
|
8174
8260
|
return { changed: true };
|
|
8175
8261
|
},
|
|
8176
8262
|
"forgezero.network/ensure@1": async ({ resolved }) => {
|
|
8263
|
+
if (!assigned && bootstrapBundle && typeof resolved.target === "string" && targetNames.has(resolved.target)) {
|
|
8264
|
+
return { changed: false, evidence: { target: resolved.target, authority: "attended-bootstrap" } };
|
|
8265
|
+
}
|
|
8177
8266
|
if (!assigned || typeof resolved.target !== "string" || resolved.target !== assigned.targetName) {
|
|
8178
8267
|
throw new DeploymentActionError("refused", "network.ensure must target this exact control-plane assignment");
|
|
8179
8268
|
}
|
|
8180
|
-
if (!assigned.connectivity)
|
|
8181
|
-
return { changed: false, evidence: { target: assigned.targetName } };
|
|
8269
|
+
if (!assigned.connectivity) {
|
|
8270
|
+
return { changed: false, evidence: { target: assigned.targetName, authority: "none" } };
|
|
8271
|
+
}
|
|
8182
8272
|
if (!options.ensureSoftware || !options.applyConnectivity) {
|
|
8183
8273
|
throw new DeploymentActionError("provider-unavailable", "the supervised connectivity helper is unavailable");
|
|
8184
8274
|
}
|
|
@@ -8187,12 +8277,31 @@ function createDeploymentManager(options) {
|
|
|
8187
8277
|
requirements.push({ id: "cloudflared", version: "latest" });
|
|
8188
8278
|
if (assigned.connectivity.private?.mode === "cloudflare-warp")
|
|
8189
8279
|
requirements.push({ id: "cloudflare-warp", version: "latest" });
|
|
8280
|
+
if (assigned.topology)
|
|
8281
|
+
requirements.push({ id: "ufw", version: "latest" });
|
|
8190
8282
|
if (requirements.length > 0)
|
|
8191
8283
|
await options.ensureSoftware(requirements);
|
|
8192
8284
|
const evidence = await options.applyConnectivity({
|
|
8193
8285
|
key: `${options.key}:${assigned.targetName}:${assigned.slot}`,
|
|
8194
8286
|
intent: assigned.connectivity,
|
|
8195
|
-
capabilities: request.connectivityCapabilities ?? {}
|
|
8287
|
+
capabilities: request.connectivityCapabilities ?? {},
|
|
8288
|
+
...assigned.topology ? { topology: {
|
|
8289
|
+
site: assigned.topology.site,
|
|
8290
|
+
nodeIdentity: assigned.topology.nodeIdentity,
|
|
8291
|
+
siteCidr: assigned.topology.siteCidr,
|
|
8292
|
+
role: assigned.topology.role,
|
|
8293
|
+
transport: assigned.topology.transport,
|
|
8294
|
+
localRelayAddress: assigned.topology.localRelayAddress,
|
|
8295
|
+
localPeerAddresses: assigned.topology.localPeerAddresses,
|
|
8296
|
+
localPeerIdentities: assigned.topology.localPeerIdentities,
|
|
8297
|
+
remoteRelayAddresses: assigned.topology.remoteRelayAddresses,
|
|
8298
|
+
remoteRelayIdentities: assigned.topology.remoteRelayIdentities,
|
|
8299
|
+
remoteSiteCidrs: assigned.topology.remoteSiteCidrs,
|
|
8300
|
+
memberIdentities: assigned.topology.memberIdentities,
|
|
8301
|
+
healthPort: assigned.topology.healthPort,
|
|
8302
|
+
routedTcpPorts: assigned.topology.routedTcpPorts,
|
|
8303
|
+
generation: assigned.topology.generation
|
|
8304
|
+
} } : {}
|
|
8196
8305
|
});
|
|
8197
8306
|
return { changed: true, evidence: structuredClone(evidence) };
|
|
8198
8307
|
},
|
|
@@ -8301,7 +8410,13 @@ function createDeploymentManager(options) {
|
|
|
8301
8410
|
runtime: assigned.execution,
|
|
8302
8411
|
cpuCores: assigned.resources.cpuCores,
|
|
8303
8412
|
memoryMiB: assigned.resources.memoryMiB,
|
|
8304
|
-
storageGiB: assigned.resources.storageGiB
|
|
8413
|
+
storageGiB: assigned.resources.storageGiB,
|
|
8414
|
+
...assigned.topology ? {
|
|
8415
|
+
site: assigned.topology.site,
|
|
8416
|
+
topologyRole: assigned.topology.role,
|
|
8417
|
+
topologyTransport: assigned.topology.transport,
|
|
8418
|
+
localRelayAddress: assigned.topology.localRelayAddress
|
|
8419
|
+
} : {}
|
|
8305
8420
|
} : {
|
|
8306
8421
|
targetName: matchingTargetNames[0],
|
|
8307
8422
|
slot: 0,
|
|
@@ -8311,7 +8426,7 @@ function createDeploymentManager(options) {
|
|
|
8311
8426
|
memoryMiB: resolvedTargets[0].allocation.resources.memoryMiB,
|
|
8312
8427
|
storageGiB: resolvedTargets[0].allocation.resources.storageGiB
|
|
8313
8428
|
},
|
|
8314
|
-
shouldRun: (step2) => step2.scope.kind === "release-executor" || step2.scope.kind === "elected-one" ? request.releaseExecutor === true : targetNames.has(step2.scope.target)
|
|
8429
|
+
shouldRun: (step2) => step2.scope.kind === "release-executor" || step2.scope.kind === "elected-one" ? request.releaseExecutor === true : step2.scope.kind === "topology-role" ? targetNames.has(step2.scope.target) && assigned?.topology?.role === step2.scope.role : targetNames.has(step2.scope.target)
|
|
8315
8430
|
});
|
|
8316
8431
|
const phase = {
|
|
8317
8432
|
pipeline: `${plan.name}:deploy`,
|
|
@@ -8751,7 +8866,8 @@ async function pullDeploymentOnce(options) {
|
|
|
8751
8866
|
labels: { ...target.labels },
|
|
8752
8867
|
allocation: { ...target.allocation, resources: { ...target.allocation.resources } },
|
|
8753
8868
|
...target.provisioning ? { provisioning: { ...target.provisioning } } : {},
|
|
8754
|
-
...target.connectivity ? { connectivity: structuredClone(target.connectivity) } : {}
|
|
8869
|
+
...target.connectivity ? { connectivity: structuredClone(target.connectivity) } : {},
|
|
8870
|
+
...target.topology ? { topology: structuredClone(target.topology) } : {}
|
|
8755
8871
|
})) } : {},
|
|
8756
8872
|
...result.capacity ? { capacity: {
|
|
8757
8873
|
recommendedConcurrency: result.capacity.recommendedConcurrency,
|
|
@@ -9215,7 +9331,7 @@ async function writeAndCloseProcessInput(input, value) {
|
|
|
9215
9331
|
}
|
|
9216
9332
|
|
|
9217
9333
|
// src/version.ts
|
|
9218
|
-
var VERSION2 = "0.1.
|
|
9334
|
+
var VERSION2 = "0.1.80";
|
|
9219
9335
|
|
|
9220
9336
|
// src/ssh-bootstrap.ts
|
|
9221
9337
|
class SshBootstrapError extends Error {
|
|
@@ -12860,6 +12976,7 @@ async function activateContainer(requestValue, host = defaultHost2) {
|
|
|
12860
12976
|
// src/deployment-connectivity.ts
|
|
12861
12977
|
import { createHash as createHash11 } from "crypto";
|
|
12862
12978
|
import { mkdirSync as mkdirSync12, renameSync as renameSync10, writeFileSync as writeFileSync13 } from "fs";
|
|
12979
|
+
import { createConnection as createConnection2 } from "net";
|
|
12863
12980
|
import { dirname as dirname9 } from "path";
|
|
12864
12981
|
var TOKEN2 = /^[A-Za-z0-9._-]{40,16384}$/;
|
|
12865
12982
|
var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
|
|
@@ -12904,6 +13021,21 @@ var defaultHost3 = {
|
|
|
12904
13021
|
const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
|
|
12905
13022
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
12906
13023
|
},
|
|
13024
|
+
probe: (address, port, timeoutMs) => new Promise((resolve6) => {
|
|
13025
|
+
const socket = createConnection2({ host: address, port });
|
|
13026
|
+
let settled = false;
|
|
13027
|
+
const finish = (value) => {
|
|
13028
|
+
if (settled)
|
|
13029
|
+
return;
|
|
13030
|
+
settled = true;
|
|
13031
|
+
socket.destroy();
|
|
13032
|
+
resolve6(value);
|
|
13033
|
+
};
|
|
13034
|
+
socket.setTimeout(timeoutMs);
|
|
13035
|
+
socket.once("connect", () => finish(true));
|
|
13036
|
+
socket.once("timeout", () => finish(false));
|
|
13037
|
+
socket.once("error", () => finish(false));
|
|
13038
|
+
}),
|
|
12907
13039
|
sleep: (ms) => Bun.sleep(ms)
|
|
12908
13040
|
};
|
|
12909
13041
|
function validate2(request) {
|
|
@@ -12918,7 +13050,7 @@ function validate2(request) {
|
|
|
12918
13050
|
} else if (request.capabilities.public)
|
|
12919
13051
|
throw new Error("unexpected public deployment capability");
|
|
12920
13052
|
if (privateIntent?.mode === "cloudflare-warp") {
|
|
12921
|
-
if (!NAME3.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !TOKEN2.test(request.capabilities.private.connectorToken)) {
|
|
13053
|
+
if (!NAME3.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !UUID.test(request.capabilities.private.siteRouteId) || !TOKEN2.test(request.capabilities.private.connectorToken)) {
|
|
12922
13054
|
throw new Error("private deployment connectivity is malformed");
|
|
12923
13055
|
}
|
|
12924
13056
|
} else if (request.capabilities.private)
|
|
@@ -12928,6 +13060,22 @@ async function applyDeploymentConnectivity(request, host = defaultHost3) {
|
|
|
12928
13060
|
validate2(request);
|
|
12929
13061
|
const id2 = idFor3(request.key);
|
|
12930
13062
|
const evidence = { key: request.key };
|
|
13063
|
+
const topology = request.topology;
|
|
13064
|
+
if (topology) {
|
|
13065
|
+
const values = [topology.localRelayAddress, ...topology.localPeerAddresses, ...topology.remoteRelayAddresses];
|
|
13066
|
+
const identities = [
|
|
13067
|
+
topology.nodeIdentity,
|
|
13068
|
+
...topology.localPeerIdentities,
|
|
13069
|
+
...topology.remoteRelayIdentities,
|
|
13070
|
+
...topology.memberIdentities
|
|
13071
|
+
];
|
|
13072
|
+
if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) => !/^(?:\d{1,3}\.){3}\d{1,3}$/.test(value)) || identities.some((value) => !/^[A-Za-z0-9.-]{1,253}$/.test(value)) || topology.localPeerIdentities.length !== topology.localPeerAddresses.length || topology.remoteRelayIdentities.length !== topology.remoteRelayAddresses.length || topology.memberIdentities.length < 1 || topology.memberIdentities.length > 1024 || new Set(topology.memberIdentities).size !== topology.memberIdentities.length || !topology.memberIdentities.includes(topology.nodeIdentity) || !/^sha256:[a-f0-9]{64}$/.test(topology.generation) || (topology.role === "member" ? topology.remoteRelayAddresses.length !== 0 : topology.remoteSiteCidrs.length !== topology.remoteRelayAddresses.length) || topology.remoteSiteCidrs.some((value) => !/^(?:\d{1,3}\.){3}0\/24$/.test(value)) || new Set(topology.remoteSiteCidrs).size !== topology.remoteSiteCidrs.length || !Number.isSafeInteger(topology.healthPort) || topology.healthPort < 1 || topology.healthPort > 65535 || topology.routedTcpPorts.length < 1 || topology.routedTcpPorts.length > 64 || new Set(topology.routedTcpPorts).size !== topology.routedTcpPorts.length || !topology.routedTcpPorts.includes(topology.healthPort) || topology.routedTcpPorts.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65535) || topology.role === "member" && topology.transport !== "private-lan") {
|
|
13073
|
+
throw new Error("deployment topology is malformed");
|
|
13074
|
+
}
|
|
13075
|
+
if (request.intent.private?.mode === "cloudflare-warp" !== (topology.transport === "cloudflare-warp")) {
|
|
13076
|
+
throw new Error("deployment topology transport differs from private connectivity intent");
|
|
13077
|
+
}
|
|
13078
|
+
}
|
|
12931
13079
|
if (request.intent.public?.mode === "cloudflare-tunnel") {
|
|
12932
13080
|
const capability = request.capabilities.public;
|
|
12933
13081
|
const credentialName = `FZ_TUNNEL_${id2.toUpperCase()}`;
|
|
@@ -12965,6 +13113,12 @@ WantedBy=multi-user.target
|
|
|
12965
13113
|
const capability = request.capabilities.private;
|
|
12966
13114
|
const credentialPath = "/etc/forgezero/creds/CF_WARP_CONNECTOR_TOKEN.cred";
|
|
12967
13115
|
const unit2 = "forgezero-deployment-mesh.service";
|
|
13116
|
+
const forwarding = "/etc/sysctl.d/99-forgezero-mesh.conf";
|
|
13117
|
+
host.write(forwarding, `net.ipv4.ip_forward = 1
|
|
13118
|
+
net.ipv6.conf.all.forwarding = 1
|
|
13119
|
+
net.ipv6.conf.all.accept_ra = 2
|
|
13120
|
+
`, 420);
|
|
13121
|
+
await checked7(host, ["/usr/sbin/sysctl", "-p", forwarding], "Mesh subnet forwarding");
|
|
12968
13122
|
await host.seal("CF_WARP_CONNECTOR_TOKEN", credentialPath, capability.connectorToken);
|
|
12969
13123
|
host.write(`/etc/systemd/system/${unit2}`, `[Unit]
|
|
12970
13124
|
Description=ForgeZero deployment Mesh/WARP connector
|
|
@@ -12999,6 +13153,95 @@ WantedBy=multi-user.target
|
|
|
12999
13153
|
if (!evidence.private)
|
|
13000
13154
|
throw new Error("WARP connector readiness failed");
|
|
13001
13155
|
}
|
|
13156
|
+
if (topology) {
|
|
13157
|
+
const syncAddresses = topology.role === "relay" ? [...topology.localPeerAddresses, ...topology.remoteRelayAddresses] : [topology.localRelayAddress];
|
|
13158
|
+
const topologyEpoch = `topology-${topology.generation.slice("sha256:".length, "sha256:".length + 48)}`;
|
|
13159
|
+
host.write("/etc/forgezero/deployment-topology.env", [
|
|
13160
|
+
`FZ_TOPOLOGY_NODE_IDENTITY=${topology.nodeIdentity}`,
|
|
13161
|
+
`FZ_TOPOLOGY_PEER_ADDRESSES=${[...new Set(syncAddresses)].join(",")}`,
|
|
13162
|
+
`FZ_TOPOLOGY_MEMBERS=${topology.memberIdentities.join(",")}`,
|
|
13163
|
+
`FZ_TOPOLOGY_EPOCH=${topologyEpoch}`,
|
|
13164
|
+
`FZ_TOPOLOGY_SITE=${topology.site}`,
|
|
13165
|
+
`FZ_TOPOLOGY_ROLE=${topology.role}`,
|
|
13166
|
+
`FZ_TOPOLOGY_TRANSPORT=${topology.transport}`,
|
|
13167
|
+
""
|
|
13168
|
+
].join(`
|
|
13169
|
+
`), 384);
|
|
13170
|
+
const routeUnit = "forgezero-deployment-topology.service";
|
|
13171
|
+
const routeUnitPath = `/etc/systemd/system/${routeUnit}`;
|
|
13172
|
+
const routePairs = topology.role === "member" ? topology.remoteSiteCidrs.map((network) => ({ network, via: topology.localRelayAddress })) : topology.transport === "private-lan" ? topology.remoteSiteCidrs.map((network, index) => ({ network, via: topology.remoteRelayAddresses[index] })) : [];
|
|
13173
|
+
for (const { via } of routePairs) {
|
|
13174
|
+
await checked7(host, ["/usr/sbin/ip", "route", "get", via], `private route gateway ${via}`);
|
|
13175
|
+
}
|
|
13176
|
+
await host.exec(["/usr/bin/systemctl", "disable", "--now", routeUnit]);
|
|
13177
|
+
const forwarding = topology.role !== "member" && topology.remoteSiteCidrs.length > 0 ? `ExecStart=/usr/sbin/sysctl -w net.ipv4.ip_forward=1
|
|
13178
|
+
` : "";
|
|
13179
|
+
const starts = routePairs.map(({ network, via }) => `ExecStart=/usr/sbin/ip route replace ${network} via ${via}`).join(`
|
|
13180
|
+
`);
|
|
13181
|
+
const stops = routePairs.map(({ network, via }) => `ExecStop=-/usr/sbin/ip route del ${network} via ${via}`).join(`
|
|
13182
|
+
`);
|
|
13183
|
+
const noOp = !forwarding && !starts ? `ExecStart=/usr/bin/true
|
|
13184
|
+
` : "";
|
|
13185
|
+
host.write(routeUnitPath, `[Unit]
|
|
13186
|
+
Description=ForgeZero fenced deployment site routing
|
|
13187
|
+
After=network-online.target${topology.transport === "cloudflare-warp" ? " forgezero-deployment-mesh.service" : ""}
|
|
13188
|
+
Wants=network-online.target
|
|
13189
|
+
|
|
13190
|
+
[Service]
|
|
13191
|
+
Type=oneshot
|
|
13192
|
+
RemainAfterExit=yes
|
|
13193
|
+
${forwarding}${noOp}${starts}${starts ? `
|
|
13194
|
+
` : ""}${stops}${stops ? `
|
|
13195
|
+
` : ""}
|
|
13196
|
+
[Install]
|
|
13197
|
+
WantedBy=multi-user.target
|
|
13198
|
+
`, 420);
|
|
13199
|
+
for (const source of [...new Set([topology.siteCidr, ...topology.remoteSiteCidrs])]) {
|
|
13200
|
+
for (const port of topology.routedTcpPorts) {
|
|
13201
|
+
await checked7(host, ["/usr/sbin/ufw", "allow", "from", source, "to", "any", "port", String(port), "proto", "tcp"], `private service ${source}:${port}`);
|
|
13202
|
+
}
|
|
13203
|
+
}
|
|
13204
|
+
if (topology.role !== "member")
|
|
13205
|
+
for (const remoteCidr of topology.remoteSiteCidrs) {
|
|
13206
|
+
for (const port of topology.routedTcpPorts) {
|
|
13207
|
+
await checked7(host, [
|
|
13208
|
+
"/usr/sbin/ufw",
|
|
13209
|
+
"route",
|
|
13210
|
+
"allow",
|
|
13211
|
+
"proto",
|
|
13212
|
+
"tcp",
|
|
13213
|
+
"from",
|
|
13214
|
+
topology.siteCidr,
|
|
13215
|
+
"to",
|
|
13216
|
+
remoteCidr,
|
|
13217
|
+
"port",
|
|
13218
|
+
String(port)
|
|
13219
|
+
], `private routed service ${topology.siteCidr}->${remoteCidr}:${port}`);
|
|
13220
|
+
}
|
|
13221
|
+
}
|
|
13222
|
+
await checked7(host, ["/usr/bin/systemctl", "daemon-reload"], "topology daemon reload");
|
|
13223
|
+
await checked7(host, ["/usr/bin/systemctl", "enable", "--now", routeUnit], "topology routes");
|
|
13224
|
+
const probes = topology.role === "member" ? [topology.localRelayAddress] : topology.remoteRelayAddresses;
|
|
13225
|
+
for (const address of [...new Set(probes)]) {
|
|
13226
|
+
await checked7(host, ["/usr/sbin/ip", "route", "get", address], `private route ${address}`);
|
|
13227
|
+
let ready = false;
|
|
13228
|
+
for (let attempt = 0;attempt < 10; attempt += 1) {
|
|
13229
|
+
if (await host.probe(address, topology.healthPort, 2000)) {
|
|
13230
|
+
ready = true;
|
|
13231
|
+
break;
|
|
13232
|
+
}
|
|
13233
|
+
await host.sleep(1000);
|
|
13234
|
+
}
|
|
13235
|
+
if (!ready)
|
|
13236
|
+
throw new Error(`private topology relay ${address}:${topology.healthPort} is unreachable`);
|
|
13237
|
+
}
|
|
13238
|
+
evidence.topology = {
|
|
13239
|
+
site: topology.site,
|
|
13240
|
+
siteCidr: topology.siteCidr,
|
|
13241
|
+
role: topology.role,
|
|
13242
|
+
probed: [...new Set(probes)]
|
|
13243
|
+
};
|
|
13244
|
+
}
|
|
13002
13245
|
return evidence;
|
|
13003
13246
|
}
|
|
13004
13247
|
|
|
@@ -16439,6 +16682,115 @@ async function readBoundedStdin(maxBytes) {
|
|
|
16439
16682
|
throw new Error(`stdin request exceeds ${maxBytes} bytes`);
|
|
16440
16683
|
return Buffer.from(stdout).toString("utf8");
|
|
16441
16684
|
}
|
|
16685
|
+
// src/deployment-topology.ts
|
|
16686
|
+
import { isIP as isIP6 } from "net";
|
|
16687
|
+
|
|
16688
|
+
class DeploymentTopologyError extends Error {
|
|
16689
|
+
constructor(message) {
|
|
16690
|
+
super(message);
|
|
16691
|
+
this.name = "DeploymentTopologyError";
|
|
16692
|
+
}
|
|
16693
|
+
}
|
|
16694
|
+
var SAFE = /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/;
|
|
16695
|
+
var PRIVATE_V4 = (value) => {
|
|
16696
|
+
const parts = value.split(".").map(Number);
|
|
16697
|
+
return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) && (parts[0] === 10 || parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31 || parts[0] === 192 && parts[1] === 168);
|
|
16698
|
+
};
|
|
16699
|
+
var validSiteCidr = (value) => {
|
|
16700
|
+
const match = value.match(/^((?:\d{1,3}\.){3})0\/24$/);
|
|
16701
|
+
return Boolean(match && PRIVATE_V4(`${match[1]}1`));
|
|
16702
|
+
};
|
|
16703
|
+
function planDeploymentTopology(args) {
|
|
16704
|
+
const topology = args.target.topology;
|
|
16705
|
+
if (!topology)
|
|
16706
|
+
throw new DeploymentTopologyError(`target ${args.target.name} has no per-metal topology`);
|
|
16707
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(args.target.name) || !/^[A-Za-z0-9._:-]{1,128}$/.test(args.generation)) {
|
|
16708
|
+
throw new DeploymentTopologyError("topology identity is invalid");
|
|
16709
|
+
}
|
|
16710
|
+
if (args.placements.length < 1 || args.placements.length > 1024)
|
|
16711
|
+
throw new DeploymentTopologyError("topology requires 1 to 1024 placements");
|
|
16712
|
+
const references = new Set;
|
|
16713
|
+
const slots = new Set;
|
|
16714
|
+
for (const placement of args.placements) {
|
|
16715
|
+
if (!SAFE.test(placement.computeReference) || !SAFE.test(placement.nodeIdentity) || !SAFE.test(placement.metalHostname) || isIP6(placement.privateAddress) !== 4 || !PRIVATE_V4(placement.privateAddress) || !validSiteCidr(placement.siteCidr) || !placement.privateAddress.startsWith(placement.siteCidr.slice(0, -4)) || !Number.isSafeInteger(placement.slot) || placement.slot < 0 || placement.slot > 1e6 || references.has(placement.computeReference) || slots.has(placement.slot)) {
|
|
16716
|
+
throw new DeploymentTopologyError("topology placement is malformed or duplicated");
|
|
16717
|
+
}
|
|
16718
|
+
references.add(placement.computeReference);
|
|
16719
|
+
slots.add(placement.slot);
|
|
16720
|
+
}
|
|
16721
|
+
const sites = new Map;
|
|
16722
|
+
for (const placement of args.placements) {
|
|
16723
|
+
const members2 = sites.get(placement.metalHostname) ?? [];
|
|
16724
|
+
members2.push({ ...placement });
|
|
16725
|
+
sites.set(placement.metalHostname, members2);
|
|
16726
|
+
}
|
|
16727
|
+
for (const members2 of sites.values())
|
|
16728
|
+
members2.sort((left, right) => left.slot - right.slot || left.computeReference.localeCompare(right.computeReference));
|
|
16729
|
+
const siteCidrs = new Map;
|
|
16730
|
+
for (const [site, members2] of sites) {
|
|
16731
|
+
const cidrs = new Set(members2.map(({ siteCidr }) => siteCidr));
|
|
16732
|
+
if (cidrs.size !== 1)
|
|
16733
|
+
throw new DeploymentTopologyError(`metal ${site} has inconsistent private CIDRs`);
|
|
16734
|
+
siteCidrs.set(site, members2[0].siteCidr);
|
|
16735
|
+
}
|
|
16736
|
+
if (new Set(siteCidrs.values()).size !== siteCidrs.size) {
|
|
16737
|
+
throw new DeploymentTopologyError("deployment metals require non-overlapping private CIDRs");
|
|
16738
|
+
}
|
|
16739
|
+
const required = 1 + topology.relays.standbyPerMetal;
|
|
16740
|
+
for (const [site, members2] of sites)
|
|
16741
|
+
if (members2.length < required) {
|
|
16742
|
+
throw new DeploymentTopologyError(`metal ${site} cannot satisfy its relay and standby topology`);
|
|
16743
|
+
}
|
|
16744
|
+
const siteNames = [...sites.keys()].sort();
|
|
16745
|
+
let crossTransport;
|
|
16746
|
+
if (siteNames.length <= 2)
|
|
16747
|
+
crossTransport = "private-lan";
|
|
16748
|
+
else {
|
|
16749
|
+
if (topology.crossMetal.mode === "private-lan") {
|
|
16750
|
+
throw new DeploymentTopologyError("three or more metals require the declared Mesh/WARP authority");
|
|
16751
|
+
}
|
|
16752
|
+
crossTransport = "cloudflare-warp";
|
|
16753
|
+
}
|
|
16754
|
+
const active = new Map(siteNames.map((site) => [site, sites.get(site)[0]]));
|
|
16755
|
+
const memberIdentities = [...args.placements].sort((left, right) => left.slot - right.slot || left.computeReference.localeCompare(right.computeReference)).map(({ nodeIdentity }) => nodeIdentity);
|
|
16756
|
+
if (new Set(memberIdentities).size !== memberIdentities.length) {
|
|
16757
|
+
throw new DeploymentTopologyError("deployment node identities must be unique");
|
|
16758
|
+
}
|
|
16759
|
+
if (!Number.isSafeInteger(topology.relays.healthPort) || topology.relays.healthPort < 1 || topology.relays.healthPort > 65535 || !Array.isArray(topology.relays.routedTcpPorts) || topology.relays.routedTcpPorts.length < 1 || topology.relays.routedTcpPorts.length > 64 || new Set(topology.relays.routedTcpPorts).size !== topology.relays.routedTcpPorts.length || !topology.relays.routedTcpPorts.includes(topology.relays.healthPort) || topology.relays.routedTcpPorts.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65535)) {
|
|
16760
|
+
throw new DeploymentTopologyError("relay health and routed TCP ports are malformed");
|
|
16761
|
+
}
|
|
16762
|
+
return siteNames.flatMap((site) => {
|
|
16763
|
+
const members2 = sites.get(site);
|
|
16764
|
+
const relay = active.get(site);
|
|
16765
|
+
const remoteRelayAddresses = siteNames.filter((other) => other !== site).map((other) => active.get(other).privateAddress);
|
|
16766
|
+
const remoteRelayIdentities = siteNames.filter((other) => other !== site).map((other) => active.get(other).nodeIdentity);
|
|
16767
|
+
const remoteSiteCidrs = siteNames.filter((other) => other !== site).map((other) => siteCidrs.get(other));
|
|
16768
|
+
return members2.map((placement, index) => {
|
|
16769
|
+
const role = index === 0 ? "relay" : index === 1 && topology.relays.standbyPerMetal === 1 ? "standby" : "member";
|
|
16770
|
+
const crossSite = role !== "member" && siteNames.length > 1;
|
|
16771
|
+
const localPeers = role === "relay" ? members2.filter(({ privateAddress }) => privateAddress !== placement.privateAddress) : [relay];
|
|
16772
|
+
const localPeerAddresses = role === "relay" ? localPeers.map(({ privateAddress }) => privateAddress) : [relay.privateAddress];
|
|
16773
|
+
const localPeerIdentities = role === "relay" ? localPeers.map(({ nodeIdentity }) => nodeIdentity) : [relay.nodeIdentity];
|
|
16774
|
+
return {
|
|
16775
|
+
...placement,
|
|
16776
|
+
role,
|
|
16777
|
+
transport: crossSite ? crossTransport : "private-lan",
|
|
16778
|
+
localRelayReference: relay.computeReference,
|
|
16779
|
+
localRelayAddress: relay.privateAddress,
|
|
16780
|
+
localPeerAddresses,
|
|
16781
|
+
localPeerIdentities,
|
|
16782
|
+
remoteRelayAddresses: crossSite ? remoteRelayAddresses : [],
|
|
16783
|
+
remoteRelayIdentities: crossSite ? remoteRelayIdentities : [],
|
|
16784
|
+
remoteSiteCidrs: siteNames.length > 1 ? remoteSiteCidrs : [],
|
|
16785
|
+
healthPort: topology.relays.healthPort,
|
|
16786
|
+
routedTcpPorts: [...topology.relays.routedTcpPorts],
|
|
16787
|
+
memberIdentities,
|
|
16788
|
+
warpRequired: crossSite && crossTransport === "cloudflare-warp",
|
|
16789
|
+
generation: args.generation
|
|
16790
|
+
};
|
|
16791
|
+
});
|
|
16792
|
+
});
|
|
16793
|
+
}
|
|
16442
16794
|
|
|
16443
16795
|
// src/index.ts
|
|
16444
16796
|
function agentCommandArgs(args, command3) {
|
|
@@ -17522,6 +17874,7 @@ export {
|
|
|
17522
17874
|
projectVaultCoordinate,
|
|
17523
17875
|
projectVaultCacheKey,
|
|
17524
17876
|
probeAgentSocket,
|
|
17877
|
+
planDeploymentTopology,
|
|
17525
17878
|
observeSoftwareHost,
|
|
17526
17879
|
observeAgentHost,
|
|
17527
17880
|
normalizeEgressTcpPorts,
|
|
@@ -17577,6 +17930,7 @@ export {
|
|
|
17577
17930
|
OS_CATALOG,
|
|
17578
17931
|
METAL_SYSTEMD_CREDENTIALS,
|
|
17579
17932
|
MAX_AGENT_TARBALL_BYTES,
|
|
17933
|
+
DeploymentTopologyError,
|
|
17580
17934
|
DeploymentInputError,
|
|
17581
17935
|
DeploymentError,
|
|
17582
17936
|
DEFAULT_SOFTWARE_HELPER_SOCKET,
|