@forgezero/agent 0.1.79 → 0.1.81
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 +59 -7
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +21 -6
- package/dist/cli/app-build.d.ts +10 -0
- 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 +423 -30
- package/dist/fz.js +155 -26
- package/dist/index.d.ts +1 -0
- package/dist/metal-bootstrap.js +13 -1
- package/dist/metal-helper-socket.js +55 -16
- package/dist/metal-provision.d.ts +6 -3
- package/dist/metal-provision.js +55 -16
- package/dist/operator-bootstrap.js +33 -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.js
CHANGED
|
@@ -4810,8 +4810,8 @@ async function spawnWith(command, env, report = () => {}, options = {}) {
|
|
|
4810
4810
|
}
|
|
4811
4811
|
|
|
4812
4812
|
// src/cli/index.ts
|
|
4813
|
-
import { existsSync as existsSync12, lstatSync as lstatSync10, mkdirSync as mkdirSync13, readFileSync as
|
|
4814
|
-
import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join12, resolve as
|
|
4813
|
+
import { existsSync as existsSync12, lstatSync as lstatSync10, mkdirSync as mkdirSync13, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
|
|
4814
|
+
import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join12, resolve as resolve12 } from "path";
|
|
4815
4815
|
|
|
4816
4816
|
// src/process-input.ts
|
|
4817
4817
|
async function writeAndCloseProcessInput(input, value) {
|
|
@@ -4837,7 +4837,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
4837
4837
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
4838
4838
|
|
|
4839
4839
|
// src/version.ts
|
|
4840
|
-
var VERSION2 = "0.1.
|
|
4840
|
+
var VERSION2 = "0.1.81";
|
|
4841
4841
|
|
|
4842
4842
|
// src/software.ts
|
|
4843
4843
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -10573,7 +10573,7 @@ function reference(value, where) {
|
|
|
10573
10573
|
const row2 = object(value, where);
|
|
10574
10574
|
exact3(row2, ["$ref"], where);
|
|
10575
10575
|
const coordinate = boundedString(row2.$ref, `${where}.$ref`, 256);
|
|
10576
|
-
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)) {
|
|
10576
|
+
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)) {
|
|
10577
10577
|
fail2(where, "contains an unsupported reference.");
|
|
10578
10578
|
}
|
|
10579
10579
|
return { $ref: coordinate };
|
|
@@ -11005,6 +11005,12 @@ function validateStep(value, where, targets, context) {
|
|
|
11005
11005
|
if (!targets.has(String(scope.target)))
|
|
11006
11006
|
fail2(`${where}.scope.target`, "must name an existing target.");
|
|
11007
11007
|
integer(scope.size, `${where}.scope.size`, 1, 32);
|
|
11008
|
+
} else if (scope.kind === "topology-role") {
|
|
11009
|
+
exact3(scope, ["kind", "target", "role"], `${where}.scope`);
|
|
11010
|
+
if (!targets.has(String(scope.target)))
|
|
11011
|
+
fail2(`${where}.scope.target`, "must name an existing target.");
|
|
11012
|
+
if (!["relay", "standby", "member"].includes(String(scope.role)))
|
|
11013
|
+
fail2(`${where}.scope.role`, "is unsupported.");
|
|
11008
11014
|
} else
|
|
11009
11015
|
fail2(`${where}.scope.kind`, "is unsupported.");
|
|
11010
11016
|
if (row2.timeoutMs !== undefined)
|
|
@@ -11159,7 +11165,7 @@ function compileDeployment(sourceValue) {
|
|
|
11159
11165
|
const targetDefinitions = new Map;
|
|
11160
11166
|
for (const [targetName, value] of targetEntries) {
|
|
11161
11167
|
const target = object(value, `deployment.spec.targets.${targetName}`);
|
|
11162
|
-
exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "placement"], `deployment.spec.targets.${targetName}`);
|
|
11168
|
+
exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "topology", "placement"], `deployment.spec.targets.${targetName}`);
|
|
11163
11169
|
if (target.kind !== "compute")
|
|
11164
11170
|
fail2(`deployment.spec.targets.${targetName}.kind`, "must be compute.");
|
|
11165
11171
|
const selector = object(target.selector, `deployment.spec.targets.${targetName}.selector`);
|
|
@@ -11281,6 +11287,45 @@ function compileDeployment(sourceValue) {
|
|
|
11281
11287
|
}
|
|
11282
11288
|
}
|
|
11283
11289
|
}
|
|
11290
|
+
if (target.topology !== undefined) {
|
|
11291
|
+
const topology = object(target.topology, `deployment.spec.targets.${targetName}.topology`);
|
|
11292
|
+
exact3(topology, ["groupBy", "relays", "crossMetal"], `deployment.spec.targets.${targetName}.topology`);
|
|
11293
|
+
if (topology.groupBy !== "metal")
|
|
11294
|
+
fail2(`deployment.spec.targets.${targetName}.topology.groupBy`, "must be metal.");
|
|
11295
|
+
const relays = object(topology.relays, `deployment.spec.targets.${targetName}.topology.relays`);
|
|
11296
|
+
exact3(relays, ["activePerMetal", "standbyPerMetal", "healthPort", "routedTcpPorts"], `deployment.spec.targets.${targetName}.topology.relays`);
|
|
11297
|
+
if (relays.activePerMetal !== 1 || ![0, 1].includes(Number(relays.standbyPerMetal)))
|
|
11298
|
+
fail2(`deployment.spec.targets.${targetName}.topology.relays`, "requires one active relay and zero or one standby per metal.");
|
|
11299
|
+
integer(relays.healthPort, `deployment.spec.targets.${targetName}.topology.relays.healthPort`, 1, 65535);
|
|
11300
|
+
if (!Array.isArray(relays.routedTcpPorts) || relays.routedTcpPorts.length < 1 || relays.routedTcpPorts.length > 64 || new Set(relays.routedTcpPorts).size !== relays.routedTcpPorts.length) {
|
|
11301
|
+
fail2(`deployment.spec.targets.${targetName}.topology.relays.routedTcpPorts`, "must contain 1 to 64 unique ports.");
|
|
11302
|
+
}
|
|
11303
|
+
for (const [index, port] of relays.routedTcpPorts.entries()) {
|
|
11304
|
+
integer(port, `deployment.spec.targets.${targetName}.topology.relays.routedTcpPorts.${index}`, 1, 65535);
|
|
11305
|
+
}
|
|
11306
|
+
if (!relays.routedTcpPorts.includes(relays.healthPort)) {
|
|
11307
|
+
fail2(`deployment.spec.targets.${targetName}.topology.relays`, "routedTcpPorts must include healthPort.");
|
|
11308
|
+
}
|
|
11309
|
+
const crossMetal = object(topology.crossMetal, `deployment.spec.targets.${targetName}.topology.crossMetal`);
|
|
11310
|
+
if (crossMetal.mode === "private-lan")
|
|
11311
|
+
exact3(crossMetal, ["mode"], `deployment.spec.targets.${targetName}.topology.crossMetal`);
|
|
11312
|
+
else if (crossMetal.mode === "cloudflare-warp" || crossMetal.mode === "auto") {
|
|
11313
|
+
exact3(crossMetal, crossMetal.mode === "auto" ? ["mode", "directPrivateMaximumMetals", "credential", "network"] : ["mode", "credential", "network"], `deployment.spec.targets.${targetName}.topology.crossMetal`);
|
|
11314
|
+
if (crossMetal.mode === "auto" && crossMetal.directPrivateMaximumMetals !== 2)
|
|
11315
|
+
fail2(`deployment.spec.targets.${targetName}.topology.crossMetal.directPrivateMaximumMetals`, "must be 2.");
|
|
11316
|
+
if (!credentials[named(crossMetal.credential, `deployment.spec.targets.${targetName}.topology.crossMetal.credential`)])
|
|
11317
|
+
fail2(`deployment.spec.targets.${targetName}.topology.crossMetal.credential`, "must name a declared credential.");
|
|
11318
|
+
if (typeof crossMetal.network === "string")
|
|
11319
|
+
named(crossMetal.network, `deployment.spec.targets.${targetName}.topology.crossMetal.network`);
|
|
11320
|
+
else
|
|
11321
|
+
reference(crossMetal.network, `deployment.spec.targets.${targetName}.topology.crossMetal.network`);
|
|
11322
|
+
} else
|
|
11323
|
+
fail2(`deployment.spec.targets.${targetName}.topology.crossMetal.mode`, "is unsupported.");
|
|
11324
|
+
if (allocation.sharing !== "exclusive")
|
|
11325
|
+
fail2(`deployment.spec.targets.${targetName}.allocation.sharing`, "must be exclusive for a per-metal relay topology.");
|
|
11326
|
+
if (minimum < 1 + Number(relays.standbyPerMetal))
|
|
11327
|
+
fail2(`deployment.spec.targets.${targetName}.cardinality.minimum`, "must allow the declared relay and standby.");
|
|
11328
|
+
}
|
|
11284
11329
|
if (target.placement !== undefined)
|
|
11285
11330
|
jsonValue(target.placement, `deployment.spec.targets.${targetName}.placement`);
|
|
11286
11331
|
targetDefinitions.set(targetName, target);
|
|
@@ -11304,6 +11349,9 @@ function compileDeployment(sourceValue) {
|
|
|
11304
11349
|
if (target.connectivity?.private?.mode === "cloudflare-warp" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
|
|
11305
11350
|
fail2(`deployment.spec.targets.${targetName}.connectivity.private`, "requires one network.private provider requirement.");
|
|
11306
11351
|
}
|
|
11352
|
+
if (target.topology && target.topology.crossMetal.mode !== "private-lan" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
|
|
11353
|
+
fail2(`deployment.spec.targets.${targetName}.topology.crossMetal`, "requires one network.private provider requirement.");
|
|
11354
|
+
}
|
|
11307
11355
|
}
|
|
11308
11356
|
const components = object(spec.components, "deployment.spec.components");
|
|
11309
11357
|
const componentEntries = Object.entries(components);
|
|
@@ -12058,7 +12106,14 @@ function validatePlatformSharedEnvironment(input) {
|
|
|
12058
12106
|
throw new Error("Bootstrap email provider must be smtp or jetemail.");
|
|
12059
12107
|
}
|
|
12060
12108
|
boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
|
|
12061
|
-
|
|
12109
|
+
if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
|
|
12110
|
+
throw new Error("seedSyncMembers must contain 3 to 64 unique physical host identities.");
|
|
12111
|
+
}
|
|
12112
|
+
for (const member of input.seedSyncMembers)
|
|
12113
|
+
safeAtom("seedSyncMembers item", member);
|
|
12114
|
+
if (!input.seedSyncMembers.includes(input.nodeHostname)) {
|
|
12115
|
+
throw new Error("seedSyncMembers must include this nodeHostname.");
|
|
12116
|
+
}
|
|
12062
12117
|
boundedInteger("concurrencyLimit", input.concurrencyLimit, 1, 1e6);
|
|
12063
12118
|
boundedInteger("drainDeadlineMs", input.drainDeadlineMs, 1000, 300000);
|
|
12064
12119
|
boundedInteger("otlpFlushIntervalMs", input.otlpFlushIntervalMs, 1000, 300000);
|
|
@@ -12138,7 +12193,7 @@ function renderPlatformSharedEnvironment(input) {
|
|
|
12138
12193
|
FZ_DB_MASTER: value.databaseMaster ?? "",
|
|
12139
12194
|
FZ_DB_NETWORK_MODE: value.databaseNetworkMode,
|
|
12140
12195
|
FZ_SEED_SYNC_PEERS: value.seedSyncPeers.join(","),
|
|
12141
|
-
FZ_SEED_SYNC_MEMBERS:
|
|
12196
|
+
FZ_SEED_SYNC_MEMBERS: value.seedSyncMembers.join(","),
|
|
12142
12197
|
FZ_SEED_SYNC_EPOCH: value.seedSyncEpoch,
|
|
12143
12198
|
FZ_SEED_SYNC_CREDENTIAL: "seed-sync-root",
|
|
12144
12199
|
FZ_SHARED_DIR: value.sharedDirectory,
|
|
@@ -12207,7 +12262,12 @@ function platformApiCredentialSpecs(options) {
|
|
|
12207
12262
|
];
|
|
12208
12263
|
}
|
|
12209
12264
|
function renderPlatformApiUnits(input) {
|
|
12210
|
-
for (const path of [
|
|
12265
|
+
for (const path of [
|
|
12266
|
+
input.sharedDirectory,
|
|
12267
|
+
input.sharedEnvironmentFile,
|
|
12268
|
+
input.slotsDirectory,
|
|
12269
|
+
...input.topologyEnvironmentFile ? [input.topologyEnvironmentFile] : []
|
|
12270
|
+
]) {
|
|
12211
12271
|
if (!path.startsWith("/") || /[\r\n]/.test(path))
|
|
12212
12272
|
throw new Error("Runtime paths must be absolute and single-line.");
|
|
12213
12273
|
}
|
|
@@ -12221,6 +12281,8 @@ function renderPlatformApiUnits(input) {
|
|
|
12221
12281
|
const credentials = input.credentials.map((credential) => `LoadCredentialEncrypted=${credential.name}:${credential.encryptedPath}`).join(`
|
|
12222
12282
|
`);
|
|
12223
12283
|
const capacityEnvironment = input.capacityEnvironmentFile ? `EnvironmentFile=-${input.capacityEnvironmentFile}
|
|
12284
|
+
` : "";
|
|
12285
|
+
const topologyEnvironment = input.topologyEnvironmentFile ? `EnvironmentFile=-${input.topologyEnvironmentFile}
|
|
12224
12286
|
` : "";
|
|
12225
12287
|
const template = `[Unit]
|
|
12226
12288
|
Description=ForgeZero (%i slot)
|
|
@@ -12234,7 +12296,7 @@ WorkingDirectory=${input.slotsDirectory}/%i
|
|
|
12234
12296
|
Environment=NODE_ENV=production
|
|
12235
12297
|
Environment=FZ_SLOT=%i
|
|
12236
12298
|
EnvironmentFile=${input.sharedEnvironmentFile}
|
|
12237
|
-
${capacityEnvironment}${credentials}
|
|
12299
|
+
${capacityEnvironment}${topologyEnvironment}${credentials}
|
|
12238
12300
|
ExecStart=/usr/local/bin/bun run ${input.slotsDirectory}/%i/src/index.ts
|
|
12239
12301
|
Restart=always
|
|
12240
12302
|
RestartSec=2
|
|
@@ -14722,7 +14784,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
14722
14784
|
greenPort: runtime.greenPort,
|
|
14723
14785
|
collectorUnit: runtime.environment.otlpCollectorUnit,
|
|
14724
14786
|
credentials,
|
|
14725
|
-
capacityEnvironmentFile: "/etc/forgezero/capacity.env"
|
|
14787
|
+
capacityEnvironmentFile: "/etc/forgezero/capacity.env",
|
|
14788
|
+
topologyEnvironmentFile: "/etc/forgezero/deployment-topology.env"
|
|
14726
14789
|
});
|
|
14727
14790
|
const edge = renderPlatformNginx({
|
|
14728
14791
|
publicPort: runtime.environment.publicApiPort,
|
|
@@ -15487,6 +15550,17 @@ function membersOfLinuxList(value, label) {
|
|
|
15487
15550
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
15488
15551
|
return members;
|
|
15489
15552
|
}
|
|
15553
|
+
function physicalCoreGroups(pool) {
|
|
15554
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
15555
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
15556
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
15557
|
+
}
|
|
15558
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
15559
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
15560
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
15561
|
+
}
|
|
15562
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
15563
|
+
}
|
|
15490
15564
|
function validateMetalProfile(profile) {
|
|
15491
15565
|
if (!SAFE_NAME2.test(profile.volumeGroup))
|
|
15492
15566
|
throw new MetalProvisionError("invalid volume group");
|
|
@@ -15535,6 +15609,7 @@ function validateMetalProfile(profile) {
|
|
|
15535
15609
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
15536
15610
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
15537
15611
|
}
|
|
15612
|
+
physicalCoreGroups(pool);
|
|
15538
15613
|
for (const cpu of cpus) {
|
|
15539
15614
|
if (assigned.has(cpu))
|
|
15540
15615
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -17298,8 +17373,8 @@ function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname,
|
|
|
17298
17373
|
databaseAddress: guest.address,
|
|
17299
17374
|
databaseMaster: role === "joiner" ? master : undefined,
|
|
17300
17375
|
nodeHostname: node.nodeHostname,
|
|
17301
|
-
seedSyncPeers: fleet.filter((_, peer) => peer !== index).
|
|
17302
|
-
seedSyncMembers:
|
|
17376
|
+
seedSyncPeers: fleet.filter((_, peer) => peer !== index).flatMap((peer) => [config.runtime.bluePort, config.runtime.greenPort].map((port) => `ws://${peer.address}:${port}/_internal/forgezero/seed-mesh/v2`)),
|
|
17377
|
+
seedSyncMembers: nodes.map(({ nodeHostname }) => nodeHostname),
|
|
17303
17378
|
initialInventory,
|
|
17304
17379
|
custodianEmail: index === 0 ? template.runtime.environment.custodianEmail : undefined
|
|
17305
17380
|
};
|
|
@@ -17615,6 +17690,38 @@ async function runRepositoryOperation(operation, requestedRoot, args = []) {
|
|
|
17615
17690
|
`), { cwd: resolved.cwd });
|
|
17616
17691
|
}
|
|
17617
17692
|
|
|
17693
|
+
// src/cli/app-build.ts
|
|
17694
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
17695
|
+
import { resolve as resolve11 } from "path";
|
|
17696
|
+
function planAppBuild(input) {
|
|
17697
|
+
const cwd = resolve11(input.root);
|
|
17698
|
+
let manifest;
|
|
17699
|
+
try {
|
|
17700
|
+
manifest = JSON.parse(readFileSync15(resolve11(cwd, "package.json"), "utf8"));
|
|
17701
|
+
} catch {
|
|
17702
|
+
throw new Error(`--root must name a ForgeZero App checkout: ${cwd}`);
|
|
17703
|
+
}
|
|
17704
|
+
if (manifest.name !== "@forgezero/app") {
|
|
17705
|
+
throw new Error(`--root must name @forgezero/app, not ${manifest.name ?? "an unnamed package"}.`);
|
|
17706
|
+
}
|
|
17707
|
+
if (input.profile && !["local", "development", "production"].includes(input.profile)) {
|
|
17708
|
+
throw new Error("--profile must be local, development, or production.");
|
|
17709
|
+
}
|
|
17710
|
+
if (input.profile && input.api)
|
|
17711
|
+
throw new Error("Choose --profile or --api, not both.");
|
|
17712
|
+
return {
|
|
17713
|
+
cwd,
|
|
17714
|
+
command: [
|
|
17715
|
+
"bun",
|
|
17716
|
+
"run",
|
|
17717
|
+
"build",
|
|
17718
|
+
"--",
|
|
17719
|
+
...input.profile ? ["--profile", input.profile] : [],
|
|
17720
|
+
...input.api ? ["--api", input.api] : []
|
|
17721
|
+
]
|
|
17722
|
+
};
|
|
17723
|
+
}
|
|
17724
|
+
|
|
17618
17725
|
// src/cli/index.ts
|
|
17619
17726
|
var DEFAULT_MODE = (THRESHOLD_MODES.find((mode) => mode.threshold === 1 && mode.total === 1) ?? THRESHOLD_MODES[0]).id;
|
|
17620
17727
|
var PACKAGED_AGENT_BIN2 = fileURLToPath3(new URL("./fz-agent.js", import.meta.url));
|
|
@@ -17844,7 +17951,7 @@ async function api(options, path, init) {
|
|
|
17844
17951
|
return { status: response.status, body };
|
|
17845
17952
|
}
|
|
17846
17953
|
function sleep(ms) {
|
|
17847
|
-
return new Promise((
|
|
17954
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
17848
17955
|
}
|
|
17849
17956
|
function browserCommand(url) {
|
|
17850
17957
|
if (process.platform === "darwin")
|
|
@@ -17943,7 +18050,7 @@ function readOwnerOnlySecret(path, label) {
|
|
|
17943
18050
|
if (uid !== undefined && uid !== 0 && metadata.uid !== uid) {
|
|
17944
18051
|
throw new Error(`The ${label} file ${path} is not owned by the current user.`);
|
|
17945
18052
|
}
|
|
17946
|
-
const value =
|
|
18053
|
+
const value = readFileSync16(path, "utf8").trim();
|
|
17947
18054
|
if (!value)
|
|
17948
18055
|
throw new Error(`The ${label} file ${path} is empty.`);
|
|
17949
18056
|
return value;
|
|
@@ -18124,7 +18231,7 @@ async function cmdApi(options, args) {
|
|
|
18124
18231
|
let body = undefined;
|
|
18125
18232
|
if (options.data !== undefined && options.dataFile)
|
|
18126
18233
|
throw new Error("Use only one of --data or --data-file.");
|
|
18127
|
-
const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ?
|
|
18234
|
+
const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync16(options.dataFile, "utf8") : options.data;
|
|
18128
18235
|
if (encoded !== undefined)
|
|
18129
18236
|
body = JSON.parse(encoded);
|
|
18130
18237
|
const result = await api(options, `${url.pathname}${url.search}`, { method, body });
|
|
@@ -18303,7 +18410,7 @@ async function cmdAgent(options, args) {
|
|
|
18303
18410
|
out.line();
|
|
18304
18411
|
out.line(" Compatibility SSH deploy public key:");
|
|
18305
18412
|
out.line();
|
|
18306
|
-
out.line(` ${
|
|
18413
|
+
out.line(` ${readFileSync16(gitIdentity.gitPublicKeyPath, "utf8").trim()}`);
|
|
18307
18414
|
out.line();
|
|
18308
18415
|
}
|
|
18309
18416
|
return 0;
|
|
@@ -18413,7 +18520,7 @@ function interactiveMetalBootstrap() {
|
|
|
18413
18520
|
});
|
|
18414
18521
|
}
|
|
18415
18522
|
function writeBootstrapConfig(path, config) {
|
|
18416
|
-
if (!isAbsolute7(path) ||
|
|
18523
|
+
if (!isAbsolute7(path) || resolve12(path) !== path)
|
|
18417
18524
|
throw new Error("--output must be a canonical absolute path");
|
|
18418
18525
|
mkdirSync13(dirname14(path), { recursive: true, mode: 448 });
|
|
18419
18526
|
writeFileSync13(path, `${JSON.stringify(config, null, 2)}
|
|
@@ -18477,7 +18584,7 @@ async function interactiveCloudflareBootstrap() {
|
|
|
18477
18584
|
});
|
|
18478
18585
|
}
|
|
18479
18586
|
function genesisOutputDirectory(path) {
|
|
18480
|
-
if (!isAbsolute7(path) ||
|
|
18587
|
+
if (!isAbsolute7(path) || resolve12(path) !== path)
|
|
18481
18588
|
throw new Error("--output must be a canonical absolute directory");
|
|
18482
18589
|
if (!existsSync12(path))
|
|
18483
18590
|
mkdirSync13(path, { recursive: true, mode: 448 });
|
|
@@ -18642,7 +18749,7 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
|
|
|
18642
18749
|
const collectorUnit = FORGEZERO_OTEL_COLLECTOR_UNIT;
|
|
18643
18750
|
const replicationFactor = Number(bootstrapAnswer("Database replication factor", "3"));
|
|
18644
18751
|
const writeConcern = Number(bootstrapAnswer("Database write concern", "2"));
|
|
18645
|
-
const seedPeers = genesis ? fleet.slice(1).
|
|
18752
|
+
const seedPeers = genesis ? fleet.slice(1).flatMap((peer) => [3001, 3002].map((port) => `ws://${peer.address}:${port}/_internal/forgezero/seed-mesh/v2`)) : bootstrapAnswer("Seed WebSocket peers (comma-separated)").split(",").map((item) => item.trim()).filter(Boolean);
|
|
18646
18753
|
const emailProvider = bootstrapAnswer("Bootstrap email provider (smtp/jetemail)", "smtp");
|
|
18647
18754
|
if (!["smtp", "jetemail"].includes(emailProvider))
|
|
18648
18755
|
throw new Error("Bootstrap email provider must be smtp or jetemail");
|
|
@@ -18698,7 +18805,7 @@ function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint)
|
|
|
18698
18805
|
publicApiPort: Number(bootstrapAnswer("Loopback public API port", "3000")),
|
|
18699
18806
|
sharedDirectory: "/opt/forgezero/shared",
|
|
18700
18807
|
seedSyncPeers: seedPeers,
|
|
18701
|
-
seedSyncMembers:
|
|
18808
|
+
seedSyncMembers: bootstrapList("Seed member hostnames (comma-separated)"),
|
|
18702
18809
|
seedSyncEpoch: bootstrapAnswer("Seed epoch"),
|
|
18703
18810
|
concurrencyLimit: Number(bootstrapAnswer("Initial concurrency limit", "128")),
|
|
18704
18811
|
drainDeadlineMs: Number(bootstrapAnswer("Drain deadline milliseconds", "30000")),
|
|
@@ -19069,7 +19176,7 @@ async function cmdUnlock(options) {
|
|
|
19069
19176
|
if (options.key || options.userExplicit) {
|
|
19070
19177
|
throw new Error("SSH/user unlock coordinates are not accepted. SSH keys cannot open a WebAuthn-PRF custody envelope; use --phrase-file or --phrase-stdin.");
|
|
19071
19178
|
}
|
|
19072
|
-
const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ?
|
|
19179
|
+
const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync16(0, "utf8").trim() : "";
|
|
19073
19180
|
const phrase = phraseText ? phraseText.split(/\s+/) : [];
|
|
19074
19181
|
if (phrase.length !== 24) {
|
|
19075
19182
|
throw new Error(`Recovery phrase must contain exactly 24 words; received ${phrase.length}. Use --phrase-file or --phrase-stdin.`);
|
|
@@ -19166,6 +19273,23 @@ function cmdProject(options, args) {
|
|
|
19166
19273
|
return 1;
|
|
19167
19274
|
}
|
|
19168
19275
|
}
|
|
19276
|
+
async function cmdApp(options, args) {
|
|
19277
|
+
if ((args[0] ?? "") !== "build" || args.length !== 1) {
|
|
19278
|
+
out.fail("Usage: fz app build [--root <app>] [--profile local|development|production | --api <origin>]");
|
|
19279
|
+
return 2;
|
|
19280
|
+
}
|
|
19281
|
+
try {
|
|
19282
|
+
const plan = planAppBuild({
|
|
19283
|
+
root: options.projectRoot,
|
|
19284
|
+
...options.deployProfile !== "app" ? { profile: options.deployProfile } : {},
|
|
19285
|
+
...options.apiExplicit ? { api: options.api } : {}
|
|
19286
|
+
});
|
|
19287
|
+
return await spawnWith(plan.command, Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined)), (line) => out.line(line), { cwd: plan.cwd });
|
|
19288
|
+
} catch (cause) {
|
|
19289
|
+
out.fail(cause instanceof Error ? cause.message : String(cause));
|
|
19290
|
+
return 1;
|
|
19291
|
+
}
|
|
19292
|
+
}
|
|
19169
19293
|
function repositoryOperationArguments(options, args) {
|
|
19170
19294
|
const unsupported = unsupportedRepositoryCliOption(process.argv.slice(2));
|
|
19171
19295
|
if (unsupported === "--root")
|
|
@@ -19285,7 +19409,7 @@ function projectFromCheckout(options) {
|
|
|
19285
19409
|
try {
|
|
19286
19410
|
return loadConfig({
|
|
19287
19411
|
cwd: options.projectRoot,
|
|
19288
|
-
readFile: (path) => existsSync12(path) ?
|
|
19412
|
+
readFile: (path) => existsSync12(path) ? readFileSync16(path, "utf8") : undefined
|
|
19289
19413
|
}).config.project;
|
|
19290
19414
|
} catch (cause) {
|
|
19291
19415
|
throw new Error(`No --project was given and the checkout has no usable .fz/config.json: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
@@ -19348,7 +19472,7 @@ async function cmdDeploy(options, args) {
|
|
|
19348
19472
|
out.ok(`Initialized legacy .fz/deploy.json (${created.summary.digest}).`);
|
|
19349
19473
|
return 0;
|
|
19350
19474
|
}
|
|
19351
|
-
initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(
|
|
19475
|
+
initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(resolve12(options.projectRoot)), force: options.force });
|
|
19352
19476
|
const compiled = await compileDeploymentProject(options.projectRoot);
|
|
19353
19477
|
if (options.json)
|
|
19354
19478
|
out.line(JSON.stringify({ source: compiled.source, output: compiled.output, digest: compiled.digest }, null, 2));
|
|
@@ -19373,7 +19497,7 @@ async function cmdDeploy(options, args) {
|
|
|
19373
19497
|
return problems.length === 0 ? 0 : 1;
|
|
19374
19498
|
}
|
|
19375
19499
|
if (operation === "check" || operation === "sync") {
|
|
19376
|
-
if (existsSync12(
|
|
19500
|
+
if (existsSync12(resolve12(options.projectRoot, DEPLOY_SOURCE_FILE))) {
|
|
19377
19501
|
const expected = await compileDeploymentProject(options.projectRoot, { write: false });
|
|
19378
19502
|
const actual = inspectCompiledDeployment(options.projectRoot);
|
|
19379
19503
|
const current2 = canonicalJson(expected.plan) === canonicalJson(actual.plan);
|
|
@@ -19449,7 +19573,7 @@ async function cmdDeploy(options, args) {
|
|
|
19449
19573
|
branch: options.branch,
|
|
19450
19574
|
cloneUrl: required(options.cloneUrl, "--clone-url"),
|
|
19451
19575
|
sourceAuth,
|
|
19452
|
-
...options.knownHostsFile ? { knownHosts:
|
|
19576
|
+
...options.knownHostsFile ? { knownHosts: readFileSync16(options.knownHostsFile, "utf8") } : {},
|
|
19453
19577
|
projectKey
|
|
19454
19578
|
} });
|
|
19455
19579
|
const pipelineKey2 = String(created.pipelineKey);
|
|
@@ -19597,7 +19721,9 @@ function usage() {
|
|
|
19597
19721
|
fz bootstrap repair Reapply an explicitly supplied reviewed config
|
|
19598
19722
|
fz project init Create vendor-neutral, Git-persisted AI context
|
|
19599
19723
|
fz project sync Regenerate Claude/Codex/Gemini/Copilot/Cursor adapters
|
|
19600
|
-
|
|
19724
|
+
fz project check Fail when truth sources or generated adapters drift
|
|
19725
|
+
fz app build Build the static App for a numbered local/development/
|
|
19726
|
+
production choice, --profile, or an explicit --api origin
|
|
19601
19727
|
fz deploy init Create forgezero.deploy.ts and its fail-safe canonical plan
|
|
19602
19728
|
fz deploy compile Compile TypeScript into .fz/deploy.plan.json
|
|
19603
19729
|
fz deploy check Validate source, plan, actions, providers and topology
|
|
@@ -19747,6 +19873,9 @@ async function runCli() {
|
|
|
19747
19873
|
case "project":
|
|
19748
19874
|
code = cmdProject(options, args);
|
|
19749
19875
|
break;
|
|
19876
|
+
case "app":
|
|
19877
|
+
code = await cmdApp(options, args);
|
|
19878
|
+
break;
|
|
19750
19879
|
case "deploy":
|
|
19751
19880
|
code = await cmdDeploy(options, args);
|
|
19752
19881
|
break;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type { AgentCredentialLocation, DeploymentCredentialBinding, DeploymentCr
|
|
|
14
14
|
export { createDeploymentManager, DeploymentError } from './deployment';
|
|
15
15
|
export type { DeploymentManager, DeploymentOptions, DeploymentRequest, DeploymentResult } from './deployment';
|
|
16
16
|
export { DeploymentInputError, resolveDeploymentInputs, resolveDeploymentTargets } from './deployment-targets';
|
|
17
|
+
export { DeploymentTopologyError, planDeploymentTopology, type DeploymentTopologyAssignment, type DeploymentTopologyPlacement, type DeploymentTopologyRole, type DeploymentTopologyTransport } from './deployment-topology';
|
|
17
18
|
export { DEFAULT_CONTROL_SOCKET, requestControl, startControlServer } from './control';
|
|
18
19
|
export { pullDeploymentOnce, startDeploymentPull } from './deployment-pull';
|
|
19
20
|
export type { DeploymentPullOptions, RemoteDeploymentClaim, PullResult } from './deployment-pull';
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -118,6 +118,17 @@ function membersOfLinuxList(value, label) {
|
|
|
118
118
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
119
119
|
return members;
|
|
120
120
|
}
|
|
121
|
+
function physicalCoreGroups(pool) {
|
|
122
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
123
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
124
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
125
|
+
}
|
|
126
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
127
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
128
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
129
|
+
}
|
|
130
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
131
|
+
}
|
|
121
132
|
function validateMetalProfile(profile) {
|
|
122
133
|
if (!SAFE_NAME.test(profile.volumeGroup))
|
|
123
134
|
throw new MetalProvisionError("invalid volume group");
|
|
@@ -166,6 +177,7 @@ function validateMetalProfile(profile) {
|
|
|
166
177
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
167
178
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
168
179
|
}
|
|
180
|
+
physicalCoreGroups(pool);
|
|
169
181
|
for (const cpu of cpus) {
|
|
170
182
|
if (assigned.has(cpu))
|
|
171
183
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -354,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
354
366
|
}
|
|
355
367
|
|
|
356
368
|
// src/version.ts
|
|
357
|
-
var VERSION = "0.1.
|
|
369
|
+
var VERSION = "0.1.81";
|
|
358
370
|
|
|
359
371
|
// src/otel-collector.ts
|
|
360
372
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -748,6 +748,32 @@ function membersOfLinuxList(value, label) {
|
|
|
748
748
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
749
749
|
return members;
|
|
750
750
|
}
|
|
751
|
+
function linuxList(members) {
|
|
752
|
+
const sorted = [...new Set(members)].sort((left, right) => left - right);
|
|
753
|
+
const ranges = [];
|
|
754
|
+
for (let index = 0;index < sorted.length; ) {
|
|
755
|
+
const start = sorted[index];
|
|
756
|
+
let end = start;
|
|
757
|
+
while (index + 1 < sorted.length && sorted[index + 1] === end + 1) {
|
|
758
|
+
index += 1;
|
|
759
|
+
end = sorted[index];
|
|
760
|
+
}
|
|
761
|
+
ranges.push(start === end ? String(start) : `${start}-${end}`);
|
|
762
|
+
index += 1;
|
|
763
|
+
}
|
|
764
|
+
return ranges.join(",");
|
|
765
|
+
}
|
|
766
|
+
function physicalCoreGroups(pool) {
|
|
767
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
768
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
769
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
770
|
+
}
|
|
771
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
772
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
773
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
774
|
+
}
|
|
775
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
776
|
+
}
|
|
751
777
|
var guestNameFor = (computeKey) => `fzg-${createHash2("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
|
|
752
778
|
var tapNameFor = (computeKey) => `fzt${createHash2("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
|
|
753
779
|
var macForAddress = (address) => {
|
|
@@ -805,6 +831,7 @@ function validateMetalProfile(profile) {
|
|
|
805
831
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
806
832
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
807
833
|
}
|
|
834
|
+
physicalCoreGroups(pool);
|
|
808
835
|
for (const cpu of cpus) {
|
|
809
836
|
if (assigned.has(cpu))
|
|
810
837
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -874,26 +901,38 @@ function allocateCpuPool(profile, claim, rows) {
|
|
|
874
901
|
const prior = rows.find((row) => row.computeKey === claim.computeKey);
|
|
875
902
|
if (prior) {
|
|
876
903
|
const retained = profile.cpuPools.find((pool) => pool.key === prior.cpuPoolKey);
|
|
877
|
-
|
|
904
|
+
const groups2 = retained ? physicalCoreGroups(retained) : [];
|
|
905
|
+
const retainedCpus = new Set(membersOfLinuxList(prior.allowedCpus, "persisted guest CPU"));
|
|
906
|
+
const matchedGroups = groups2.filter((group) => group.every((cpu) => retainedCpus.has(cpu)));
|
|
907
|
+
if (!retained || retained.memoryNodes !== prior.allowedMemoryNodes || matchedGroups.length !== claim.spec.physicalCores || matchedGroups.flat().length !== retainedCpus.size || retainedCpus.size < claim.spec.vcpu || claim.spec.cpuPoolKey !== undefined && claim.spec.cpuPoolKey !== retained.key) {
|
|
878
908
|
throw new MetalProvisionError("persisted guest CPU pool no longer matches the host profile");
|
|
879
909
|
}
|
|
880
|
-
return
|
|
910
|
+
return {
|
|
911
|
+
key: retained.key,
|
|
912
|
+
cpus: prior.allowedCpus,
|
|
913
|
+
physicalCores: matchedGroups.length,
|
|
914
|
+
memoryNodes: prior.allowedMemoryNodes
|
|
915
|
+
};
|
|
881
916
|
}
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
return requested;
|
|
891
|
-
}
|
|
892
|
-
const candidates = profile.cpuPools.filter((pool) => !used.has(pool.key) && pool.physicalCores >= claim.spec.physicalCores && membersOfLinuxList(pool.cpus, "CPU").length >= claim.spec.vcpu).sort((left, right) => left.physicalCores - right.physicalCores || membersOfLinuxList(left.cpus, "CPU").length - membersOfLinuxList(right.cpus, "CPU").length || left.key.localeCompare(right.key));
|
|
917
|
+
const usedCpus = new Set(rows.flatMap((row) => membersOfLinuxList(row.allowedCpus, "persisted guest CPU")));
|
|
918
|
+
const candidates = profile.cpuPools.filter((pool) => !claim.spec.cpuPoolKey || pool.key === claim.spec.cpuPoolKey).map((pool) => ({
|
|
919
|
+
pool,
|
|
920
|
+
available: physicalCoreGroups(pool).filter((group) => group.every((cpu) => !usedCpus.has(cpu)))
|
|
921
|
+
})).filter(({ available }) => {
|
|
922
|
+
const selected2 = available.slice(0, claim.spec.physicalCores);
|
|
923
|
+
return selected2.length === claim.spec.physicalCores && selected2.flat().length >= claim.spec.vcpu;
|
|
924
|
+
}).sort((left, right) => left.available.length - right.available.length || left.pool.key.localeCompare(right.pool.key));
|
|
893
925
|
const selected = candidates[0];
|
|
894
|
-
if (!selected)
|
|
895
|
-
throw new MetalProvisionError("no exclusive CPU
|
|
896
|
-
|
|
926
|
+
if (!selected) {
|
|
927
|
+
throw new MetalProvisionError(claim.spec.cpuPoolKey ? "requested CPU pool cannot satisfy this guest" : "no exclusive CPU capacity can satisfy this guest");
|
|
928
|
+
}
|
|
929
|
+
const groups = selected.available.slice(0, claim.spec.physicalCores);
|
|
930
|
+
return {
|
|
931
|
+
key: selected.pool.key,
|
|
932
|
+
cpus: linuxList(groups.flat()),
|
|
933
|
+
physicalCores: groups.length,
|
|
934
|
+
memoryNodes: selected.pool.memoryNodes
|
|
935
|
+
};
|
|
897
936
|
}
|
|
898
937
|
var base64 = (value) => Buffer.from(value).toString("base64");
|
|
899
938
|
var yamlFile = (path2, content, permissions) => ` - path: ${JSON.stringify(path2)}
|
|
@@ -3,7 +3,7 @@ export interface MetalImage {
|
|
|
3
3
|
path: string;
|
|
4
4
|
sha256: string;
|
|
5
5
|
}
|
|
6
|
-
/** One non-overlapping
|
|
6
|
+
/** One non-overlapping NUMA-local CPU capacity pool that may serve several guests. */
|
|
7
7
|
export interface MetalCpuPool {
|
|
8
8
|
key: string;
|
|
9
9
|
/** Linux CPU-list syntax, including every SMT sibling in this pool. */
|
|
@@ -13,6 +13,9 @@ export interface MetalCpuPool {
|
|
|
13
13
|
/** Linux NUMA node-list syntax. Optional on a non-NUMA machine. */
|
|
14
14
|
memoryNodes?: string;
|
|
15
15
|
}
|
|
16
|
+
/** Exact non-overlapping physical-core slice reserved for one guest. */
|
|
17
|
+
export interface MetalCpuAllocation extends MetalCpuPool {
|
|
18
|
+
}
|
|
16
19
|
export interface MetalProvisionProfile {
|
|
17
20
|
volumeGroup: string;
|
|
18
21
|
bridge: string;
|
|
@@ -70,8 +73,8 @@ export declare const tapNameFor: (computeKey: string) => string;
|
|
|
70
73
|
export declare const macForAddress: (address: string) => string;
|
|
71
74
|
export declare function validateMetalProfile(profile: MetalProvisionProfile): void;
|
|
72
75
|
export declare function allocateAddress(profile: MetalProvisionProfile, computeKey: string, rows: GuestManifest[]): string;
|
|
73
|
-
/** Stable tight-fit allocation of one
|
|
74
|
-
export declare function allocateCpuPool(profile: MetalProvisionProfile, claim: Pick<RemoteProvisionClaim, 'computeKey' | 'spec'>, rows: GuestManifest[]):
|
|
76
|
+
/** Stable tight-fit allocation of exact physical cores inside one NUMA-local pool. */
|
|
77
|
+
export declare function allocateCpuPool(profile: MetalProvisionProfile, claim: Pick<RemoteProvisionClaim, 'computeKey' | 'spec'>, rows: GuestManifest[]): MetalCpuAllocation;
|
|
75
78
|
export declare function guestBootstrapOperations(profile: MetalProvisionProfile, attested?: boolean, hasEnrolment?: boolean, nodeLabel?: string): readonly (readonly string[])[];
|
|
76
79
|
export declare function cloudInit(profile: MetalProvisionProfile, claim: CreateRemoteProvisionClaim, manifest: GuestManifest): {
|
|
77
80
|
userData: string;
|