@forgezero/agent 0.1.73 → 0.1.75
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 +565 -61
- package/dist/agent-heartbeat.d.ts +25 -1
- package/dist/agent-heartbeat.js +45 -5
- package/dist/bootstrap.d.ts +2 -0
- package/dist/bootstrap.js +22 -4
- package/dist/community-rehearsal-host.js +208 -9
- package/dist/credential-schema.d.ts +1 -1
- package/dist/credential-schema.js +1 -1
- package/dist/definition.js +206 -7
- package/dist/deploy-actions.d.ts +7 -0
- package/dist/deploy-compiler.js +191 -10
- package/dist/deploy-file.js +206 -7
- package/dist/deploy-plan-runner.d.ts +3 -0
- package/dist/deploy-plan-runner.js +190 -9
- package/dist/deploy-plan.js +187 -8
- package/dist/deploy-providers.d.ts +19 -0
- package/dist/deploy.d.ts +89 -3
- package/dist/deploy.js +49 -2
- package/dist/deployment-connectivity.d.ts +63 -0
- package/dist/deployment-connectivity.js +148 -0
- package/dist/deployment-pull.d.ts +2 -0
- package/dist/deployment-targets.d.ts +9 -0
- package/dist/deployment-targets.js +141 -0
- package/dist/deployment.d.ts +69 -0
- package/dist/fz-agent.js +1112 -302
- package/dist/fz.js +246 -18
- package/dist/index.d.ts +1 -0
- package/dist/metal-bootstrap.js +1 -1
- package/dist/metal-helper-socket.js +210 -11
- package/dist/metal-provision.js +210 -11
- package/dist/operator-bootstrap.js +22 -4
- package/dist/platform-bootstrap-runtime.d.ts +1 -0
- package/dist/platform-bootstrap-runtime.js +216 -10
- package/dist/platform-fleet-verification.js +523 -150
- package/dist/platform-genesis.js +206 -7
- package/dist/provision.js +465 -99
- package/dist/service-supervisor.d.ts +6 -0
- package/dist/software-helper.d.ts +3 -0
- package/dist/software-helper.js +465 -98
- package/dist/software.d.ts +4 -1
- package/dist/software.js +210 -8
- package/dist/ubuntu.js +206 -7
- package/dist/version.d.ts +1 -1
- package/package.json +12 -4
package/dist/fz.js
CHANGED
|
@@ -4634,7 +4634,7 @@ var VaultError, runtimeEnvironment = () => typeof process !== "undefined" && pro
|
|
|
4634
4634
|
} catch {
|
|
4635
4635
|
return;
|
|
4636
4636
|
}
|
|
4637
|
-
}, VERSION = "0.1.
|
|
4637
|
+
}, VERSION = "0.1.15";
|
|
4638
4638
|
var init_dist = __esm(() => {
|
|
4639
4639
|
init_identity();
|
|
4640
4640
|
VaultError = class VaultError extends Error {
|
|
@@ -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.75";
|
|
4833
4833
|
|
|
4834
4834
|
// src/software.ts
|
|
4835
4835
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -4863,6 +4863,8 @@ var OS_CATALOG = [
|
|
|
4863
4863
|
var SOFTWARE_CATALOG = [
|
|
4864
4864
|
{ id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4865
4865
|
{ id: "docker", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4866
|
+
{ id: "containerd", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4867
|
+
{ id: "kata-containers", version: "4.0.0", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4866
4868
|
{ id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4867
4869
|
{ id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4868
4870
|
{ id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
@@ -4871,6 +4873,7 @@ var SOFTWARE_CATALOG = [
|
|
|
4871
4873
|
{ id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4872
4874
|
{ id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4873
4875
|
{ id: "docker", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4876
|
+
{ id: "containerd", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4874
4877
|
{ id: "nginx", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4875
4878
|
{ id: "ufw", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4876
4879
|
{ id: "openssh-client", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
@@ -4895,7 +4898,7 @@ function validateSoftwareRequirements(value, _options = {}) {
|
|
|
4895
4898
|
if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
|
|
4896
4899
|
throw new Error("software requirement contains an unknown field");
|
|
4897
4900
|
}
|
|
4898
|
-
if (!["bun", "docker", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client", "git"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
|
|
4901
|
+
if (!["bun", "docker", "containerd", "kata-containers", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client", "git"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
|
|
4899
4902
|
throw new Error("software requirement coordinate is invalid");
|
|
4900
4903
|
}
|
|
4901
4904
|
const requirement = { id: row.id, version: row.version };
|
|
@@ -10399,6 +10402,13 @@ function validateBuiltInAction(definition, context, where) {
|
|
|
10399
10402
|
throw new Error(`${where}.with.requirements must name existing requirements`);
|
|
10400
10403
|
return;
|
|
10401
10404
|
}
|
|
10405
|
+
if (definition.uses === "forgezero.network/ensure@1") {
|
|
10406
|
+
exact(withValue, ["target"], `${where}.with`);
|
|
10407
|
+
if (typeof withValue.target !== "string" || !Object.values(context.components).some((entry) => entry.target === withValue.target)) {
|
|
10408
|
+
throw new Error(`${where}.with.target must name a target used by a component`);
|
|
10409
|
+
}
|
|
10410
|
+
return;
|
|
10411
|
+
}
|
|
10402
10412
|
if (definition.uses === "forgezero.exec/argv@1") {
|
|
10403
10413
|
const allowed = ["component", "argv", "credentials"];
|
|
10404
10414
|
const unknown = Object.keys(withValue).filter((key) => !allowed.includes(key));
|
|
@@ -10454,6 +10464,8 @@ function validateBuiltInProvider(requirement2, where) {
|
|
|
10454
10464
|
const expected = {
|
|
10455
10465
|
"forgezero.bun": { capability: "runtime.javascript", versions: ["1.3.14"], keys: ["installScope"] },
|
|
10456
10466
|
"forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
|
|
10467
|
+
"forgezero.containerd": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "runtimeClass", "namespace", "snapshotter"] },
|
|
10468
|
+
"forgezero.kata": { capability: "runtime.container", versions: ["4.0.0"], keys: ["installScope", "runtimeClass", "namespace", "hypervisor", "confidentialGuest"] },
|
|
10457
10469
|
"forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
|
|
10458
10470
|
"forgezero.arangodb": { capability: "database.arangodb", versions: ["3.11.14"], keys: ["installScope", "runtime", "dataPath", "bind"] },
|
|
10459
10471
|
"forgezero.cloudflared": { capability: "network.public-tunnel", versions: ["2026.7.3"], keys: ["installScope", "mode"] },
|
|
@@ -10474,6 +10486,10 @@ function validateBuiltInProvider(requirement2, where) {
|
|
|
10474
10486
|
if (row2.storageDriver !== "overlay2" || row2.dataRoot !== "/var/lib/docker" || row2.liveRestore !== true || row2.logDriver !== "local" || row2.defaultNetwork !== "bridge" || row2.rootless !== false)
|
|
10475
10487
|
throw new Error(`${where} Docker config is unsupported by the fixed host strategy`);
|
|
10476
10488
|
}
|
|
10489
|
+
if (requirement2.provider === "forgezero.containerd" && (row2.runtimeClass !== "runc" || row2.namespace !== "forgezero" || row2.snapshotter !== "overlayfs"))
|
|
10490
|
+
throw new Error(`${where} containerd config is unsupported by the fixed host strategy`);
|
|
10491
|
+
if (requirement2.provider === "forgezero.kata" && (row2.runtimeClass !== "kata-qemu-snp" || row2.namespace !== "forgezero" || row2.hypervisor !== "qemu" || row2.confidentialGuest !== "sev-snp"))
|
|
10492
|
+
throw new Error(`${where} Kata config is unsupported by the fixed host strategy`);
|
|
10477
10493
|
if (requirement2.provider === "forgezero.nginx" && (typeof row2.websocket !== "boolean" || !Number.isInteger(row2.maximumBodyMiB) || Number(row2.maximumBodyMiB) < 1 || Number(row2.maximumBodyMiB) > 1024 || !(row2.workerProcesses === "auto" || Number.isInteger(row2.workerProcesses)) || !Number.isInteger(row2.workerConnections)))
|
|
10478
10494
|
throw new Error(`${where} Nginx config is invalid`);
|
|
10479
10495
|
if (requirement2.provider === "forgezero.arangodb" && (row2.runtime !== "native" || !absolute(row2.dataPath) || row2.bind !== "private"))
|
|
@@ -10551,7 +10567,7 @@ function reference(value, where) {
|
|
|
10551
10567
|
const row2 = object(value, where);
|
|
10552
10568
|
exact3(row2, ["$ref"], where);
|
|
10553
10569
|
const coordinate = boundedString(row2.$ref, `${where}.$ref`, 256);
|
|
10554
|
-
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})$/.test(coordinate)) {
|
|
10570
|
+
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)) {
|
|
10555
10571
|
fail2(where, "contains an unsupported reference.");
|
|
10556
10572
|
}
|
|
10557
10573
|
return { $ref: coordinate };
|
|
@@ -10791,10 +10807,9 @@ function validateNetwork(value, where) {
|
|
|
10791
10807
|
named(container.network, `${where}.container.network`);
|
|
10792
10808
|
}
|
|
10793
10809
|
}
|
|
10794
|
-
function validateComponent(value, where,
|
|
10810
|
+
function validateComponent(value, where, targets, requirements) {
|
|
10795
10811
|
const row2 = object(value, where);
|
|
10796
|
-
|
|
10797
|
-
fail2(`${where}.target`, "must name an existing target.");
|
|
10812
|
+
const selectedTarget = targets.get(String(row2.target)) ?? fail2(`${where}.target`, "must name an existing target.");
|
|
10798
10813
|
if (row2.kind === "application") {
|
|
10799
10814
|
exact3(row2, ["kind", "target", "runtime", "service", "resources", "storage", "network", "rollout"], where);
|
|
10800
10815
|
const runtime = object(row2.runtime, `${where}.runtime`);
|
|
@@ -10805,7 +10820,9 @@ function validateComponent(value, where, targetNames, requirements) {
|
|
|
10805
10820
|
exact3(runtime, ["kind", "provider", "requirement", "argv"], `${where}.runtime`);
|
|
10806
10821
|
validateArgv(runtime.argv, `${where}.runtime.argv`);
|
|
10807
10822
|
} else if (runtime.kind === "container") {
|
|
10808
|
-
exact3(runtime, ["kind", "provider", "requirement", "image", "entrypoint", "security"], `${where}.runtime`);
|
|
10823
|
+
exact3(runtime, ["kind", "provider", "requirement", "runtimeClass", "image", "entrypoint", "security"], `${where}.runtime`);
|
|
10824
|
+
if (runtime.runtimeClass !== undefined && !["runc", "kata-qemu-snp"].includes(String(runtime.runtimeClass)))
|
|
10825
|
+
fail2(`${where}.runtime.runtimeClass`, "is unsupported.");
|
|
10809
10826
|
const image = object(runtime.image, `${where}.runtime.image`);
|
|
10810
10827
|
exact3(image, ["source"], `${where}.runtime.image`);
|
|
10811
10828
|
const source = object(image.source, `${where}.runtime.image.source`);
|
|
@@ -10839,6 +10856,23 @@ function validateComponent(value, where, targetNames, requirements) {
|
|
|
10839
10856
|
fail2(`${where}.runtime.requirement`, "must provide runtime.container.");
|
|
10840
10857
|
if (runtimeRequirement && runtime.kind === "native" && !runtimeRequirement.capability.startsWith("runtime."))
|
|
10841
10858
|
fail2(`${where}.runtime.requirement`, "must provide a runtime capability.");
|
|
10859
|
+
const execution = selectedTarget.allocation.execution;
|
|
10860
|
+
if (execution === "native" && runtime.kind !== "native")
|
|
10861
|
+
fail2(`${where}.runtime.kind`, "must be native for the selected target.");
|
|
10862
|
+
if (execution !== "native" && runtime.kind !== "container")
|
|
10863
|
+
fail2(`${where}.runtime.kind`, "must be container for the selected target.");
|
|
10864
|
+
if (runtime.kind === "container" && execution === "oci-runc") {
|
|
10865
|
+
if (!["forgezero.containerd", "forgezero.docker"].includes(String(runtime.provider)))
|
|
10866
|
+
fail2(`${where}.runtime.provider`, "must be forgezero.containerd for an OCI runc target.");
|
|
10867
|
+
if (runtime.runtimeClass !== undefined && runtime.runtimeClass !== "runc")
|
|
10868
|
+
fail2(`${where}.runtime.runtimeClass`, "must be runc for the selected target.");
|
|
10869
|
+
}
|
|
10870
|
+
if (runtime.kind === "container" && execution === "oci-kata-qemu-snp") {
|
|
10871
|
+
if (runtime.provider !== "forgezero.kata")
|
|
10872
|
+
fail2(`${where}.runtime.provider`, "must be forgezero.kata for a Kata QEMU SNP target.");
|
|
10873
|
+
if (runtime.runtimeClass !== "kata-qemu-snp")
|
|
10874
|
+
fail2(`${where}.runtime.runtimeClass`, "must be kata-qemu-snp for the selected target.");
|
|
10875
|
+
}
|
|
10842
10876
|
const service = object(row2.service, `${where}.service`);
|
|
10843
10877
|
exact3(service, ["protocol", "port", "health", "websocket", "maximumConnections"], `${where}.service`);
|
|
10844
10878
|
if (service.protocol !== "http")
|
|
@@ -10870,6 +10904,18 @@ function validateComponent(value, where, targetNames, requirements) {
|
|
|
10870
10904
|
const network = object(row2.network, `${where}.network`);
|
|
10871
10905
|
if (network.container === undefined)
|
|
10872
10906
|
fail2(`${where}.network.container`, "must select bridge or reviewed host networking.");
|
|
10907
|
+
if (selectedTarget.allocation.sharing === "shared") {
|
|
10908
|
+
if (object(runtime.security, `${where}.runtime.security`).root !== "read-only")
|
|
10909
|
+
fail2(`${where}.runtime.security.root`, "must be read-only on a shared compute.");
|
|
10910
|
+
if (network.container.mode !== "bridge")
|
|
10911
|
+
fail2(`${where}.network.container.mode`, "must be bridge on a shared compute.");
|
|
10912
|
+
if (Array.isArray(row2.storage) && row2.storage.some((mount) => mount.class !== "ephemeral")) {
|
|
10913
|
+
fail2(`${where}.storage`, "may contain only size-bounded tmpfs mounts on a shared compute.");
|
|
10914
|
+
}
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
if (runtime.kind === "native" && selectedTarget.allocation.sharing !== "exclusive") {
|
|
10918
|
+
fail2(`${where}.target`, "native applications require an exclusive compute.");
|
|
10873
10919
|
}
|
|
10874
10920
|
if (row2.storage !== undefined) {
|
|
10875
10921
|
if (!Array.isArray(row2.storage) || row2.storage.length > 32)
|
|
@@ -10906,6 +10952,8 @@ function validateComponent(value, where, targetNames, requirements) {
|
|
|
10906
10952
|
fail2(`${where}.runtime.mode`, "must match the provider runtime mode.");
|
|
10907
10953
|
if (runtime.mode !== "native")
|
|
10908
10954
|
fail2(`${where}.runtime.mode`, "must be native in deployment plan v1.");
|
|
10955
|
+
if (selectedTarget.allocation.execution !== "native")
|
|
10956
|
+
fail2(`${where}.target`, "must select a native execution target for database components.");
|
|
10909
10957
|
const topology = object(row2.topology, `${where}.topology`);
|
|
10910
10958
|
if (topology.mode === "standalone")
|
|
10911
10959
|
exact3(topology, ["mode"], `${where}.topology`);
|
|
@@ -11102,15 +11150,20 @@ function compileDeployment(sourceValue) {
|
|
|
11102
11150
|
if (targetEntries.length < 1 || targetEntries.length > 64)
|
|
11103
11151
|
fail2("deployment.spec.targets", "must contain 1 to 64 targets.");
|
|
11104
11152
|
const targetNames = new Set(targetEntries.map(([targetName]) => named(targetName, "deployment.spec.targets key")));
|
|
11153
|
+
const targetDefinitions = new Map;
|
|
11105
11154
|
for (const [targetName, value] of targetEntries) {
|
|
11106
11155
|
const target = object(value, `deployment.spec.targets.${targetName}`);
|
|
11107
|
-
exact3(target, ["kind", "selector", "cardinality", "placement"], `deployment.spec.targets.${targetName}`);
|
|
11156
|
+
exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "placement"], `deployment.spec.targets.${targetName}`);
|
|
11108
11157
|
if (target.kind !== "compute")
|
|
11109
11158
|
fail2(`deployment.spec.targets.${targetName}.kind`, "must be compute.");
|
|
11110
11159
|
const selector = object(target.selector, `deployment.spec.targets.${targetName}.selector`);
|
|
11111
|
-
exact3(selector, ["profiles", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
|
|
11160
|
+
exact3(selector, ["profiles", "operatingSystem", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
|
|
11112
11161
|
if (!Array.isArray(selector.profiles) || selector.profiles.length < 1 || selector.profiles.length > 32 || selector.profiles.some((profile) => typeof profile !== "string" || !NAME3.test(profile)) || new Set(selector.profiles).size !== selector.profiles.length)
|
|
11113
11162
|
fail2(`deployment.spec.targets.${targetName}.selector.profiles`, "is invalid.");
|
|
11163
|
+
const operatingSystem = object(selector.operatingSystem, `deployment.spec.targets.${targetName}.selector.operatingSystem`);
|
|
11164
|
+
exact3(operatingSystem, ["id", "version", "architecture"], `deployment.spec.targets.${targetName}.selector.operatingSystem`);
|
|
11165
|
+
if (operatingSystem.id !== "ubuntu" || !["24.04", "26.04"].includes(String(operatingSystem.version)) || operatingSystem.architecture !== "x64")
|
|
11166
|
+
fail2(`deployment.spec.targets.${targetName}.selector.operatingSystem`, "is unsupported.");
|
|
11114
11167
|
if (selector.confidentialCompute !== undefined && !["required", "preferred", "disabled"].includes(String(selector.confidentialCompute)))
|
|
11115
11168
|
fail2(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "is unsupported.");
|
|
11116
11169
|
if (selector.labels !== undefined)
|
|
@@ -11123,8 +11176,108 @@ function compileDeployment(sourceValue) {
|
|
|
11123
11176
|
integer(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`, minimum, maximum);
|
|
11124
11177
|
else
|
|
11125
11178
|
reference(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`);
|
|
11179
|
+
const allocation = object(target.allocation, `deployment.spec.targets.${targetName}.allocation`);
|
|
11180
|
+
exact3(allocation, ["execution", "sharing", "resources", "existing"], `deployment.spec.targets.${targetName}.allocation`);
|
|
11181
|
+
if (!["native", "oci-runc", "oci-kata-qemu-snp"].includes(String(allocation.execution)))
|
|
11182
|
+
fail2(`deployment.spec.targets.${targetName}.allocation.execution`, "is unsupported.");
|
|
11183
|
+
if (!["exclusive", "shared"].includes(String(allocation.sharing)))
|
|
11184
|
+
fail2(`deployment.spec.targets.${targetName}.allocation.sharing`, "is unsupported.");
|
|
11185
|
+
if (!["prefer", "require"].includes(String(allocation.existing)))
|
|
11186
|
+
fail2(`deployment.spec.targets.${targetName}.allocation.existing`, "is unsupported.");
|
|
11187
|
+
const capacity = object(allocation.resources, `deployment.spec.targets.${targetName}.allocation.resources`);
|
|
11188
|
+
exact3(capacity, ["cpuCores", "memoryMiB", "storageGiB"], `deployment.spec.targets.${targetName}.allocation.resources`);
|
|
11189
|
+
const capacityValue = (value2, where, minimumValue, maximumValue) => {
|
|
11190
|
+
if (typeof value2 === "number")
|
|
11191
|
+
integer(value2, where, minimumValue, maximumValue);
|
|
11192
|
+
else
|
|
11193
|
+
reference(value2, where);
|
|
11194
|
+
};
|
|
11195
|
+
capacityValue(capacity.cpuCores, `deployment.spec.targets.${targetName}.allocation.resources.cpuCores`, 1, 1024);
|
|
11196
|
+
capacityValue(capacity.memoryMiB, `deployment.spec.targets.${targetName}.allocation.resources.memoryMiB`, 128, 4194304);
|
|
11197
|
+
capacityValue(capacity.storageGiB, `deployment.spec.targets.${targetName}.allocation.resources.storageGiB`, 1, 1048576);
|
|
11198
|
+
if (allocation.execution === "oci-kata-qemu-snp" && selector.confidentialCompute !== "required")
|
|
11199
|
+
fail2(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "must be required for Kata QEMU SNP.");
|
|
11200
|
+
if ((allocation.execution === "oci-kata-qemu-snp" || selector.confidentialCompute === "required") && operatingSystem.version !== "26.04")
|
|
11201
|
+
fail2(`deployment.spec.targets.${targetName}.selector.operatingSystem.version`, "must be Ubuntu 26.04 for SEV-SNP.");
|
|
11202
|
+
if (allocation.execution !== "oci-kata-qemu-snp" && selector.confidentialCompute === "required" && allocation.execution !== "native")
|
|
11203
|
+
fail2(`deployment.spec.targets.${targetName}.allocation.execution`, "cannot satisfy required confidential compute.");
|
|
11204
|
+
if (target.provisioning !== undefined) {
|
|
11205
|
+
const provisioning = object(target.provisioning, `deployment.spec.targets.${targetName}.provisioning`);
|
|
11206
|
+
exact3(provisioning, ["planKey", "regionKey", "imageKey", "environmentKey", "ownership", "metalHostname", "maxMonthlySpendMinor"], `deployment.spec.targets.${targetName}.provisioning`);
|
|
11207
|
+
for (const key of ["planKey", "regionKey", "imageKey", "environmentKey"])
|
|
11208
|
+
named(provisioning[key], `deployment.spec.targets.${targetName}.provisioning.${key}`);
|
|
11209
|
+
if (!["platform", "tenant-metal"].includes(String(provisioning.ownership)) || provisioning.ownership === "tenant-metal" !== Boolean(provisioning.metalHostname))
|
|
11210
|
+
fail2(`deployment.spec.targets.${targetName}.provisioning.ownership`, "must bind platform capacity or one tenant metal hostname.");
|
|
11211
|
+
if (provisioning.metalHostname !== undefined && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(String(provisioning.metalHostname)))
|
|
11212
|
+
fail2(`deployment.spec.targets.${targetName}.provisioning.metalHostname`, "is invalid.");
|
|
11213
|
+
if (typeof provisioning.maxMonthlySpendMinor !== "string" || !/^\d{1,18}$/.test(provisioning.maxMonthlySpendMinor))
|
|
11214
|
+
fail2(`deployment.spec.targets.${targetName}.provisioning.maxMonthlySpendMinor`, "must be a bounded decimal minor-unit amount.");
|
|
11215
|
+
}
|
|
11216
|
+
if (allocation.existing === "prefer" && target.provisioning === undefined)
|
|
11217
|
+
fail2(`deployment.spec.targets.${targetName}.provisioning`, "is required when placement may provision after reuse.");
|
|
11218
|
+
if (allocation.existing === "require" && target.provisioning !== undefined)
|
|
11219
|
+
fail2(`deployment.spec.targets.${targetName}.provisioning`, "must be omitted for reuse-only placement.");
|
|
11220
|
+
if (target.connectivity !== undefined) {
|
|
11221
|
+
const connectivity = object(target.connectivity, `deployment.spec.targets.${targetName}.connectivity`);
|
|
11222
|
+
exact3(connectivity, ["private", "public"], `deployment.spec.targets.${targetName}.connectivity`);
|
|
11223
|
+
const declaredPrivate = connectivity.private === undefined ? undefined : object(connectivity.private, `deployment.spec.targets.${targetName}.connectivity.private`);
|
|
11224
|
+
if (declaredPrivate?.mode === "cloudflare-warp" && allocation.sharing !== "exclusive")
|
|
11225
|
+
fail2(`deployment.spec.targets.${targetName}.allocation.sharing`, "must be exclusive for a Cloudflare WARP connector.");
|
|
11226
|
+
if (connectivity.private !== undefined) {
|
|
11227
|
+
const privateNetwork = declaredPrivate;
|
|
11228
|
+
if (privateNetwork.mode === "cloudflare-warp") {
|
|
11229
|
+
exact3(privateNetwork, ["mode", "credential", "network"], `deployment.spec.targets.${targetName}.connectivity.private`);
|
|
11230
|
+
if (!credentials[named(privateNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.private.credential`)])
|
|
11231
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.private.credential`, "must name a declared credential.");
|
|
11232
|
+
if (typeof privateNetwork.network === "string")
|
|
11233
|
+
named(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
|
|
11234
|
+
else
|
|
11235
|
+
reference(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
|
|
11236
|
+
} else {
|
|
11237
|
+
exact3(privateNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.private`);
|
|
11238
|
+
if (!["disabled", "private-lan"].includes(String(privateNetwork.mode)))
|
|
11239
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.private.mode`, "is unsupported.");
|
|
11240
|
+
}
|
|
11241
|
+
}
|
|
11242
|
+
if (connectivity.public !== undefined) {
|
|
11243
|
+
const publicNetwork = object(connectivity.public, `deployment.spec.targets.${targetName}.connectivity.public`);
|
|
11244
|
+
if (publicNetwork.mode === "cloudflare-tunnel") {
|
|
11245
|
+
exact3(publicNetwork, ["mode", "credential", "zone", "hostname"], `deployment.spec.targets.${targetName}.connectivity.public`);
|
|
11246
|
+
if (!credentials[named(publicNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.public.credential`)])
|
|
11247
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.public.credential`, "must name a declared credential.");
|
|
11248
|
+
if (typeof publicNetwork.zone === "string") {
|
|
11249
|
+
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(publicNetwork.zone))
|
|
11250
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.public.zone`, "is invalid.");
|
|
11251
|
+
} else
|
|
11252
|
+
reference(publicNetwork.zone, `deployment.spec.targets.${targetName}.connectivity.public.zone`);
|
|
11253
|
+
const hostname = object(publicNetwork.hostname, `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
|
|
11254
|
+
const value2 = (coordinate, where, pattern) => {
|
|
11255
|
+
if (typeof coordinate === "string") {
|
|
11256
|
+
if (!pattern.test(coordinate))
|
|
11257
|
+
fail2(where, "is invalid.");
|
|
11258
|
+
} else
|
|
11259
|
+
reference(coordinate, where);
|
|
11260
|
+
};
|
|
11261
|
+
if (hostname.mode === "static") {
|
|
11262
|
+
exact3(hostname, ["mode", "label"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
|
|
11263
|
+
value2(hostname.label, `deployment.spec.targets.${targetName}.connectivity.public.hostname.label`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
|
|
11264
|
+
} else if (hostname.mode === "indexed") {
|
|
11265
|
+
exact3(hostname, ["mode", "prefix", "startAt"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
|
|
11266
|
+
value2(hostname.prefix, `deployment.spec.targets.${targetName}.connectivity.public.hostname.prefix`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
|
|
11267
|
+
if (hostname.startAt !== undefined)
|
|
11268
|
+
integer(hostname.startAt, `deployment.spec.targets.${targetName}.connectivity.public.hostname.startAt`, 0, 1e6);
|
|
11269
|
+
} else
|
|
11270
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.public.hostname.mode`, "is unsupported.");
|
|
11271
|
+
} else {
|
|
11272
|
+
exact3(publicNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.public`);
|
|
11273
|
+
if (publicNetwork.mode !== "disabled")
|
|
11274
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.public.mode`, "is unsupported.");
|
|
11275
|
+
}
|
|
11276
|
+
}
|
|
11277
|
+
}
|
|
11126
11278
|
if (target.placement !== undefined)
|
|
11127
11279
|
jsonValue(target.placement, `deployment.spec.targets.${targetName}.placement`);
|
|
11280
|
+
targetDefinitions.set(targetName, target);
|
|
11128
11281
|
}
|
|
11129
11282
|
const requirements = object(spec.requirements ?? {}, "deployment.spec.requirements");
|
|
11130
11283
|
if (Object.keys(requirements).length > 64)
|
|
@@ -11138,13 +11291,34 @@ function compileDeployment(sourceValue) {
|
|
|
11138
11291
|
const key = `${requirementValue.provider}@${requirementValue.contract}:${requirementValue.capability}`;
|
|
11139
11292
|
requiredProviders.set(key, { provider: requirementValue.provider, contract: requirementValue.contract, capability: requirementValue.capability });
|
|
11140
11293
|
}
|
|
11294
|
+
for (const [targetName, target] of targetDefinitions) {
|
|
11295
|
+
if (target.connectivity?.public?.mode === "cloudflare-tunnel" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.public-tunnel")) {
|
|
11296
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.public`, "requires one network.public-tunnel provider requirement.");
|
|
11297
|
+
}
|
|
11298
|
+
if (target.connectivity?.private?.mode === "cloudflare-warp" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
|
|
11299
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.private`, "requires one network.private provider requirement.");
|
|
11300
|
+
}
|
|
11301
|
+
}
|
|
11141
11302
|
const components = object(spec.components, "deployment.spec.components");
|
|
11142
11303
|
const componentEntries = Object.entries(components);
|
|
11143
11304
|
if (componentEntries.length < 1 || componentEntries.length > 128)
|
|
11144
11305
|
fail2("deployment.spec.components", "must contain 1 to 128 components.");
|
|
11145
11306
|
for (const [componentName, value] of componentEntries) {
|
|
11146
11307
|
named(componentName, "deployment.spec.components key");
|
|
11147
|
-
validateComponent(value, `deployment.spec.components.${componentName}`,
|
|
11308
|
+
validateComponent(value, `deployment.spec.components.${componentName}`, targetDefinitions, requirementDefinitions);
|
|
11309
|
+
}
|
|
11310
|
+
for (const [targetName, target] of targetDefinitions) {
|
|
11311
|
+
if (target.connectivity?.public?.mode !== "cloudflare-tunnel")
|
|
11312
|
+
continue;
|
|
11313
|
+
const publicApplications = componentEntries.filter(([, component2]) => {
|
|
11314
|
+
const candidate = component2;
|
|
11315
|
+
return candidate.kind === "application" && candidate.target === targetName && candidate.network?.public?.mode === "cloudflare-tunnel";
|
|
11316
|
+
});
|
|
11317
|
+
if (publicApplications.length !== 1)
|
|
11318
|
+
fail2(`deployment.spec.targets.${targetName}.connectivity.public`, "requires exactly one application on the target with network.public cloudflare-tunnel.");
|
|
11319
|
+
const application = publicApplications[0][1];
|
|
11320
|
+
if (application.kind !== "application" || !application.network?.ingress?.stablePort)
|
|
11321
|
+
fail2(`deployment.spec.components.${publicApplications[0][0]}.network.ingress`, "requires one stablePort for Tunnel ingress.");
|
|
11148
11322
|
}
|
|
11149
11323
|
const actionContext = {
|
|
11150
11324
|
components,
|
|
@@ -11186,6 +11360,14 @@ function compileDeployment(sourceValue) {
|
|
|
11186
11360
|
});
|
|
11187
11361
|
plannedWorkflows.push({ id: workflowName, ...workflowValue.concurrency ? { concurrency: workflowValue.concurrency } : {}, stages: plannedStages });
|
|
11188
11362
|
}
|
|
11363
|
+
const deployWorkflow = plannedWorkflows.find(({ id: id2 }) => id2 === "deploy");
|
|
11364
|
+
for (const [targetName, target] of targetDefinitions) {
|
|
11365
|
+
if (!target.connectivity || target.connectivity.public?.mode !== "cloudflare-tunnel" && target.connectivity.private?.mode !== "cloudflare-warp")
|
|
11366
|
+
continue;
|
|
11367
|
+
const ensures = deployWorkflow?.stages.flatMap(({ steps }) => steps).filter((step3) => step3.uses === "forgezero.network/ensure@1" && step3.with.target === targetName && (step3.scope.kind === "each-target" || step3.scope.kind === "target-batches") && step3.scope.target === targetName) ?? [];
|
|
11368
|
+
if (ensures.length !== 1)
|
|
11369
|
+
fail2(`deployment.spec.workflows.deploy`, `must contain exactly one network.ensure step scoped to connected target ${targetName}.`);
|
|
11370
|
+
}
|
|
11189
11371
|
const typedSource = source;
|
|
11190
11372
|
return {
|
|
11191
11373
|
format: DEPLOY_PLAN_FORMAT,
|
|
@@ -11294,8 +11476,10 @@ export default defineDeployment({
|
|
|
11294
11476
|
inputs: { replicas: input.integer({ minimum: 1, maximum: 32, default: 1 }) },
|
|
11295
11477
|
targets: {
|
|
11296
11478
|
app: target.compute({
|
|
11297
|
-
|
|
11298
|
-
|
|
11479
|
+
profiles: ['app'],
|
|
11480
|
+
replicas: { minimum: 1, desired: input.ref('replicas'), maximum: 32 },
|
|
11481
|
+
resources: { cpuCores: 1, memoryMiB: 512, storageGiB: 8 },
|
|
11482
|
+
os: 'ubuntu-24.04', runtime: 'native', isolation: 'standard', reuse: 'require'
|
|
11299
11483
|
})
|
|
11300
11484
|
},
|
|
11301
11485
|
requirements: { bun: providers.bun.require() },
|
|
@@ -11843,6 +12027,10 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
11843
12027
|
const coordinators = input.databaseCoordinators.map(privateCoordinator);
|
|
11844
12028
|
if (new Set(coordinators).size !== coordinators.length)
|
|
11845
12029
|
throw new Error("databaseCoordinators must be unique.");
|
|
12030
|
+
const readPreferred = (input.databaseReadPreferredCoordinators ?? []).map(privateCoordinator);
|
|
12031
|
+
if (new Set(readPreferred).size !== readPreferred.length || readPreferred.some((url) => !coordinators.includes(url))) {
|
|
12032
|
+
throw new Error("databaseReadPreferredCoordinators must be a unique subset of databaseCoordinators.");
|
|
12033
|
+
}
|
|
11846
12034
|
if (input.databaseNetworkMode !== "private-lan") {
|
|
11847
12035
|
throw new Error("Attended platform bootstrap supports only private-lan database networking.");
|
|
11848
12036
|
}
|
|
@@ -11916,6 +12104,7 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
11916
12104
|
return {
|
|
11917
12105
|
...input,
|
|
11918
12106
|
databaseCoordinators: coordinators,
|
|
12107
|
+
databaseReadPreferredCoordinators: readPreferred,
|
|
11919
12108
|
appOrigin: httpsOrigin("appOrigin", input.appOrigin),
|
|
11920
12109
|
apiOrigin: httpsOrigin("apiOrigin", input.apiOrigin),
|
|
11921
12110
|
agentOtlpEndpoint: httpsOrigin("agentOtlpEndpoint", input.agentOtlpEndpoint),
|
|
@@ -11929,6 +12118,8 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
11929
12118
|
const entries = {
|
|
11930
12119
|
ARANGO_URL: value.databaseCoordinators[0],
|
|
11931
12120
|
ARANGO_URLS: value.databaseCoordinators.join(","),
|
|
12121
|
+
ARANGO_READ_PREFERRED_URLS: (value.databaseReadPreferredCoordinators ?? []).join(","),
|
|
12122
|
+
ARANGO_READ_PREFERRED_FALLBACK: "balanced",
|
|
11932
12123
|
ARANGO_DB: "fz",
|
|
11933
12124
|
FZ_DATABASE_MODE: "platform",
|
|
11934
12125
|
ARANGO_USER: value.databaseUser,
|
|
@@ -13788,6 +13979,10 @@ function validateBootstrapConfig(value) {
|
|
|
13788
13979
|
if (!["member", "none"].includes(value.database.agency) || value.database.role === "master" && value.database.agency !== "member" || value.database.role === "none" && value.database.agency !== "none") {
|
|
13789
13980
|
throw new Error("database role and Agency participation disagree");
|
|
13790
13981
|
}
|
|
13982
|
+
value.database.readPreferred ??= false;
|
|
13983
|
+
if (value.database.readPreferred && (value.database.role !== "joiner" || value.database.agency !== "none")) {
|
|
13984
|
+
throw new Error("read-preferred database routing requires an extra non-Agency joiner");
|
|
13985
|
+
}
|
|
13791
13986
|
if (value.database.role !== "none" && !value.database.address)
|
|
13792
13987
|
throw new Error("database nodes require a private address");
|
|
13793
13988
|
if (value.database.role === "joiner" && !value.database.master)
|
|
@@ -13807,6 +14002,10 @@ function validateBootstrapConfig(value) {
|
|
|
13807
14002
|
if (runtime.softwareProfile !== value.profile || runtime.databaseRole !== value.database.role || runtime.nodeHostname !== value.nodeHostname || runtime.apiOrigin !== api.origin || runtime.databaseCoordinators.join(",") !== write.join(",")) {
|
|
13808
14003
|
throw new Error("platform runtime coordinates disagree with immutable bootstrap coordinates");
|
|
13809
14004
|
}
|
|
14005
|
+
const ownCoordinator = value.database.address ? `http://${value.database.address}:8529` : undefined;
|
|
14006
|
+
if (Boolean(ownCoordinator && (runtime.databaseReadPreferredCoordinators ?? []).includes(ownCoordinator)) !== value.database.readPreferred) {
|
|
14007
|
+
throw new Error("runtime read-preferred coordinator set disagrees with this node database routing intent");
|
|
14008
|
+
}
|
|
13810
14009
|
if (runtime.deployProfile !== value.environment)
|
|
13811
14010
|
throw new Error("runtime deployment profile disagrees with bootstrap environment");
|
|
13812
14011
|
if (value.database.address !== runtime.databaseAddress || value.database.master !== runtime.databaseMaster) {
|
|
@@ -14118,6 +14317,7 @@ function bootstrapIdentity(config) {
|
|
|
14118
14317
|
role: config.database.role,
|
|
14119
14318
|
serverMode: config.database.serverMode,
|
|
14120
14319
|
agency: config.database.agency,
|
|
14320
|
+
readPreferred: config.database.readPreferred,
|
|
14121
14321
|
address: config.database.address ?? null,
|
|
14122
14322
|
master: config.database.master ?? null,
|
|
14123
14323
|
coordinators: config.database.coordinators
|
|
@@ -14153,7 +14353,7 @@ function parseStoredState(raw) {
|
|
|
14153
14353
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14154
14354
|
throw new Error("bootstrap state is malformed");
|
|
14155
14355
|
const state = value;
|
|
14156
|
-
if (state.format !== 2 || !["platform", "enrolled-compute"].includes(state.kind ?? "") || !/^[a-f0-9]{64}$/.test(state.identityDigest ?? "") || typeof state.profile !== "string" || typeof state.nodeHostname !== "string" || typeof state.apiUrl !== "string" || state.kind === "platform" && !["member", "none"].includes(state.databaseAgency ?? "")) {
|
|
14356
|
+
if (state.format !== 2 || !["platform", "enrolled-compute"].includes(state.kind ?? "") || !/^[a-f0-9]{64}$/.test(state.identityDigest ?? "") || typeof state.profile !== "string" || typeof state.nodeHostname !== "string" || typeof state.apiUrl !== "string" || state.kind === "platform" && (!["member", "none"].includes(state.databaseAgency ?? "") || typeof state.databaseReadPreferred !== "boolean")) {
|
|
14157
14357
|
throw new Error("bootstrap state is legacy or incomplete; refusing an unbound repair");
|
|
14158
14358
|
}
|
|
14159
14359
|
return state;
|
|
@@ -14203,6 +14403,7 @@ function stateFor(config, cloudflare, previousCloudflareTunnelId) {
|
|
|
14203
14403
|
environment: config.environment,
|
|
14204
14404
|
databaseRole: config.database.role,
|
|
14205
14405
|
databaseAgency: config.database.agency,
|
|
14406
|
+
databaseReadPreferred: config.database.readPreferred,
|
|
14206
14407
|
databaseServerMode: config.database.serverMode,
|
|
14207
14408
|
databaseAddress: config.database.address,
|
|
14208
14409
|
databaseCoordinators: config.database.coordinators,
|
|
@@ -14707,7 +14908,7 @@ function strictBootstrapDocument(value) {
|
|
|
14707
14908
|
exactKeys2(root.firewall, ["enabled", "sshPort", "privateCidrs"], "firewall config");
|
|
14708
14909
|
if (root.cloudflareHandoff !== undefined)
|
|
14709
14910
|
exactKeys2(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
|
|
14710
|
-
exactKeys2(root.database, ["role", "agency", "serverMode", "address", "master", "coordinators"], "database config");
|
|
14911
|
+
exactKeys2(root.database, ["role", "agency", "readPreferred", "serverMode", "address", "master", "coordinators"], "database config");
|
|
14711
14912
|
exactKeys2(root.enrolment, ["source"], "platform enrolment config");
|
|
14712
14913
|
const runtime = exactKeys2(root.runtime, [
|
|
14713
14914
|
"environment",
|
|
@@ -14751,7 +14952,8 @@ function strictBootstrapDocument(value) {
|
|
|
14751
14952
|
"backup",
|
|
14752
14953
|
"cloudflare",
|
|
14753
14954
|
"realtime",
|
|
14754
|
-
"initialInventory"
|
|
14955
|
+
"initialInventory",
|
|
14956
|
+
"databaseReadPreferredCoordinators"
|
|
14755
14957
|
], "runtime environment");
|
|
14756
14958
|
const environment = runtime.environment;
|
|
14757
14959
|
if (environment.initialInventory !== undefined) {
|
|
@@ -17073,6 +17275,7 @@ function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname,
|
|
|
17073
17275
|
config.database = {
|
|
17074
17276
|
role,
|
|
17075
17277
|
agency: "member",
|
|
17278
|
+
readPreferred: false,
|
|
17076
17279
|
serverMode: "default",
|
|
17077
17280
|
address: guest.address,
|
|
17078
17281
|
...role === "joiner" ? { master } : {},
|
|
@@ -17087,6 +17290,7 @@ function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname,
|
|
|
17087
17290
|
softwareProfile: "platform-db-api",
|
|
17088
17291
|
databaseRole: role,
|
|
17089
17292
|
databaseCoordinators: coordinators,
|
|
17293
|
+
databaseReadPreferredCoordinators: [],
|
|
17090
17294
|
databaseAddress: guest.address,
|
|
17091
17295
|
databaseMaster: role === "joiner" ? master : undefined,
|
|
17092
17296
|
nodeHostname: node.nodeHostname,
|
|
@@ -18419,6 +18623,7 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
|
|
|
18419
18623
|
const profile = genesis ? "platform-db-api" : bootstrapAnswer("Profile (platform-db-api/platform-api)", "platform-db-api");
|
|
18420
18624
|
const role = genesis ? "master" : profile === "platform-api" ? "none" : bootstrapAnswer("Database role (master/joiner)", "master");
|
|
18421
18625
|
const agency = genesis ? "member" : role === "master" ? "member" : role === "none" ? "none" : bootstrapAnswer("Agency participation (member/none)", "member");
|
|
18626
|
+
const readPreferred = !genesis && role === "joiner" && agency === "none" ? bootstrapBoolean("Prefer this Coordinator for explicitly read-safe queries?", "no") : false;
|
|
18422
18627
|
const serverMode = "default";
|
|
18423
18628
|
const address = genesis ? fleet[0].address : role === "none" ? undefined : bootstrapAnswer("Private database address");
|
|
18424
18629
|
const master = role === "joiner" ? bootstrapAnswer("Master starter private address") : undefined;
|
|
@@ -18463,6 +18668,7 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
|
|
|
18463
18668
|
database: {
|
|
18464
18669
|
role,
|
|
18465
18670
|
agency,
|
|
18671
|
+
readPreferred,
|
|
18466
18672
|
serverMode,
|
|
18467
18673
|
address,
|
|
18468
18674
|
master,
|
|
@@ -18474,6 +18680,7 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
|
|
|
18474
18680
|
softwareProfile: profile,
|
|
18475
18681
|
databaseRole: role,
|
|
18476
18682
|
databaseCoordinators: coordinators,
|
|
18683
|
+
databaseReadPreferredCoordinators: readPreferred && address ? [`http://${address}:8529`] : [],
|
|
18477
18684
|
databaseAddress: address,
|
|
18478
18685
|
databaseMaster: master,
|
|
18479
18686
|
databaseNetworkMode,
|
|
@@ -19254,6 +19461,24 @@ async function cmdDeploy(options, args) {
|
|
|
19254
19461
|
return 0;
|
|
19255
19462
|
}
|
|
19256
19463
|
const pipelineKey = required(options.pipelineKey, "--pipeline");
|
|
19464
|
+
if (operation === "plan") {
|
|
19465
|
+
const revision = required(options.revision, "--revision").toLowerCase();
|
|
19466
|
+
if (!/^[a-f0-9]{40}$/.test(revision))
|
|
19467
|
+
throw new Error("--revision must be a full 40-character Git commit.");
|
|
19468
|
+
const compiled = inspectCompiledDeployment(options.projectRoot);
|
|
19469
|
+
let inputs;
|
|
19470
|
+
if (options.data !== undefined) {
|
|
19471
|
+
const parsed = JSON.parse(options.data === "-" ? await Bun.stdin.text() : options.data);
|
|
19472
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
19473
|
+
throw new Error("--data must be one JSON object of typed deployment inputs.");
|
|
19474
|
+
inputs = parsed;
|
|
19475
|
+
}
|
|
19476
|
+
out.line(JSON.stringify(await deploymentRequest(options, "/plan", {
|
|
19477
|
+
method: "POST",
|
|
19478
|
+
body: { pipelineKey, revision, plan: compiled.plan, ...inputs ? { inputs } : {} }
|
|
19479
|
+
}), null, 2));
|
|
19480
|
+
return 0;
|
|
19481
|
+
}
|
|
19257
19482
|
if (operation === "attach") {
|
|
19258
19483
|
if (options.targets.length === 0)
|
|
19259
19484
|
throw new Error("At least one --target <compute>@<profile> is required.");
|
|
@@ -19299,7 +19524,7 @@ async function cmdDeploy(options, args) {
|
|
|
19299
19524
|
out.line(JSON.stringify(await deploymentRequest(options, "/rotate", { method: "POST", body: { pipelineKey } }), null, 2));
|
|
19300
19525
|
return 0;
|
|
19301
19526
|
}
|
|
19302
|
-
out.fail("Usage: fz deploy init|compile|check|sync|catalog|list|connect|attach|runs|release|enable|disable|target-enable|target-disable|rotate");
|
|
19527
|
+
out.fail("Usage: fz deploy init|compile|check|sync|catalog|list|connect|plan|attach|runs|release|enable|disable|target-enable|target-disable|rotate");
|
|
19303
19528
|
return 2;
|
|
19304
19529
|
} catch (cause) {
|
|
19305
19530
|
out.fail(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -19376,7 +19601,10 @@ function usage() {
|
|
|
19376
19601
|
fz deploy sync Prove readiness and print the Git synchronization rule
|
|
19377
19602
|
fz deploy catalog List selectable tested OS/software coordinates
|
|
19378
19603
|
fz deploy list List pipelines for .fz/config.json's project
|
|
19379
|
-
fz deploy connect Create a repository pipeline
|
|
19604
|
+
fz deploy connect Create a repository pipeline; optional --target values
|
|
19605
|
+
are manual anchors, not required for automatic placement
|
|
19606
|
+
fz deploy plan Approve the compiled plan at --revision and let the API
|
|
19607
|
+
reuse idle compute before bounded provisioning
|
|
19380
19608
|
fz deploy attach Attach --target <compute>@<profile> to a pipeline
|
|
19381
19609
|
fz deploy runs Show pipeline deployment runs
|
|
19382
19610
|
fz deploy release Queue one exact --revision; --wait for completion
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { AGENT_CREDENTIAL_LOCATIONS, AGENT_CREDENTIAL_POLICY, METAL_SYSTEMD_CRED
|
|
|
13
13
|
export type { AgentCredentialLocation, DeploymentCredentialBinding, DeploymentCredentialSchema } from './credential-schema';
|
|
14
14
|
export { createDeploymentManager, DeploymentError } from './deployment';
|
|
15
15
|
export type { DeploymentManager, DeploymentOptions, DeploymentRequest, DeploymentResult } from './deployment';
|
|
16
|
+
export { DeploymentInputError, resolveDeploymentInputs, resolveDeploymentTargets } from './deployment-targets';
|
|
16
17
|
export { DEFAULT_CONTROL_SOCKET, requestControl, startControlServer } from './control';
|
|
17
18
|
export { pullDeploymentOnce, startDeploymentPull } from './deployment-pull';
|
|
18
19
|
export type { DeploymentPullOptions, RemoteDeploymentClaim, PullResult } from './deployment-pull';
|
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.75";
|
|
358
358
|
|
|
359
359
|
// src/otel-collector.ts
|
|
360
360
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|