@forgezero/agent 0.1.82 → 0.1.84
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 +8 -1
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +1 -1
- package/dist/deploy-compiler.d.ts +25 -2
- package/dist/deploy-compiler.js +154 -34
- package/dist/fz-agent.js +1 -1
- package/dist/fz.js +346 -96
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +1 -1
- package/dist/platform-fleet-verification.js +1 -1
- package/dist/provision.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -85,6 +85,8 @@ Every row links to the detailed explanation and named-import/example area below.
|
|
|
85
85
|
|
|
86
86
|
bun add -g @forgezero/agent — Install the version-matched fz operator CLI and fz-agent daemon.
|
|
87
87
|
fz deploy init — Create a typed forgezero.deploy.ts and its canonical inert execution plan.
|
|
88
|
+
fz deploy init --runtime containerd --os ubuntu-24.04 --replicas 1 --cpu-cores 2 --memory-mib 2048 --storage-gib 20 --reuse require — Create one ordinary containerd/runc compute definition with exact reserved resources; existing eligible project capacity is required.
|
|
89
|
+
fz deploy init --runtime kata-snp --os ubuntu-26.04 --replicas 1 --cpu-cores 2 --memory-mib 4096 --storage-gib 40 --reuse prefer — Create one Kata QEMU SEV-SNP definition; reuse eligible capacity first, then provisioning still requires an explicit approved plan and spend ceiling.
|
|
88
90
|
fz deploy compile — Compile and validate TypeScript deployment intent into .fz/deploy.plan.json.
|
|
89
91
|
fz deploy check — Refuse stale plans, unsafe provider/action coordinates and unresolved deployment blockers.
|
|
90
92
|
fz bootstrap platform bundle --root /absolute/api --branch dev --output /secure/forgezero/development/api.bundle — Build one verified release-generation-one source bundle without a Git credential.
|
|
@@ -97,6 +99,8 @@ fz status — Read installed service and bootstrap evidence.
|
|
|
97
99
|
```text
|
|
98
100
|
bun add -g @forgezero/agent
|
|
99
101
|
fz deploy init
|
|
102
|
+
fz deploy init --runtime containerd --os ubuntu-24.04 --replicas 1 --cpu-cores 2 --memory-mib 2048 --storage-gib 20 --reuse require
|
|
103
|
+
fz deploy init --runtime kata-snp --os ubuntu-26.04 --replicas 1 --cpu-cores 2 --memory-mib 4096 --storage-gib 40 --reuse prefer
|
|
100
104
|
fz deploy compile
|
|
101
105
|
fz deploy check
|
|
102
106
|
fz bootstrap platform bundle --root /absolute/api --branch dev --output /secure/forgezero/development/api.bundle
|
|
@@ -1402,7 +1406,7 @@ await runDeploymentPlan({
|
|
|
1402
1406
|
<a id="forgezero-agent-deploy-compiler"></a>
|
|
1403
1407
|
## @forgezero/agent/deploy-compiler
|
|
1404
1408
|
|
|
1405
|
-
Explicit developer/CI compiler from forgezero.deploy.ts to canonical .fz/deploy.plan.json; never used to execute project TypeScript in production. This entry exposes 7 named value exports and
|
|
1409
|
+
Explicit developer/CI compiler from forgezero.deploy.ts to canonical .fz/deploy.plan.json; never used to execute project TypeScript in production. This entry exposes 7 named value exports and 4 named type exports. The generated import block lists one name per line for scanning and copying; keep only the names used by your file.
|
|
1406
1410
|
|
|
1407
1411
|
```text
|
|
1408
1412
|
import {
|
|
@@ -1417,6 +1421,9 @@ import {
|
|
|
1417
1421
|
|
|
1418
1422
|
import type {
|
|
1419
1423
|
DeploymentCompilation,
|
|
1424
|
+
DeploymentInitOptions,
|
|
1425
|
+
DeploymentInitProvisioning,
|
|
1426
|
+
DeploymentInitRuntime,
|
|
1420
1427
|
} from '@forgezero/agent/deploy-compiler';
|
|
1421
1428
|
```
|
|
1422
1429
|
|
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.js
CHANGED
|
@@ -8,11 +8,34 @@ export interface DeploymentCompilation {
|
|
|
8
8
|
digest: `sha256:${string}`;
|
|
9
9
|
changed: boolean;
|
|
10
10
|
}
|
|
11
|
-
export
|
|
11
|
+
export type DeploymentInitRuntime = 'native' | 'containerd' | 'kata-snp';
|
|
12
|
+
export interface DeploymentInitProvisioning {
|
|
13
|
+
planKey: string;
|
|
14
|
+
regionKey: string;
|
|
15
|
+
imageKey: string;
|
|
16
|
+
environmentKey: string;
|
|
17
|
+
ownership: 'platform' | 'tenant-metal';
|
|
18
|
+
metalHostname?: string;
|
|
19
|
+
maxMonthlySpendMinor: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DeploymentInitOptions {
|
|
22
|
+
runtime?: DeploymentInitRuntime;
|
|
23
|
+
operatingSystem?: 'ubuntu-24.04' | 'ubuntu-26.04';
|
|
24
|
+
isolation?: 'standard' | 'sev-snp';
|
|
25
|
+
replicas?: number;
|
|
26
|
+
cpuCores?: number;
|
|
27
|
+
memoryMiB?: number;
|
|
28
|
+
storageGiB?: number;
|
|
29
|
+
sharing?: 'exclusive' | 'shared';
|
|
30
|
+
reuse?: 'require' | 'prefer';
|
|
31
|
+
profile?: string;
|
|
32
|
+
provisioning?: DeploymentInitProvisioning;
|
|
33
|
+
}
|
|
34
|
+
export declare function defaultTypeScriptDeployment(name: string, options?: DeploymentInitOptions): string;
|
|
12
35
|
export declare function initializeTypeScriptDeployment(rootValue: string, options: {
|
|
13
36
|
name: string;
|
|
14
37
|
force?: boolean;
|
|
15
|
-
}): string;
|
|
38
|
+
} & DeploymentInitOptions): string;
|
|
16
39
|
export declare function loadDeploymentSource(root: string, sourceFile?: string): Promise<unknown>;
|
|
17
40
|
export declare function compileDeploymentProject(rootValue: string, options?: {
|
|
18
41
|
sourceFile?: string;
|
package/dist/deploy-compiler.js
CHANGED
|
@@ -1142,46 +1142,106 @@ function parseDeploymentPlan(value) {
|
|
|
1142
1142
|
}
|
|
1143
1143
|
|
|
1144
1144
|
// src/deploy-compiler.ts
|
|
1145
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
1146
|
-
import {
|
|
1147
|
-
import {
|
|
1145
|
+
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
1146
|
+
import { tmpdir } from "os";
|
|
1147
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
1148
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
1148
1149
|
var DEPLOY_SOURCE_FILE = "forgezero.deploy.ts";
|
|
1149
1150
|
var DEPLOY_PLAN_FILE = ".fz/deploy.plan.json";
|
|
1150
1151
|
function safeName(value) {
|
|
1151
1152
|
const result = value.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
|
|
1152
1153
|
return /^[a-z]/.test(result) ? result : `app-${result || "service"}`;
|
|
1153
1154
|
}
|
|
1154
|
-
function
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1155
|
+
function initCoordinates(options) {
|
|
1156
|
+
const runtime = options.runtime ?? "native";
|
|
1157
|
+
const isolation = options.isolation ?? (runtime === "kata-snp" ? "sev-snp" : "standard");
|
|
1158
|
+
const operatingSystem = options.operatingSystem ?? (isolation === "sev-snp" ? "ubuntu-26.04" : "ubuntu-24.04");
|
|
1159
|
+
const result = {
|
|
1160
|
+
runtime,
|
|
1161
|
+
isolation,
|
|
1162
|
+
operatingSystem,
|
|
1163
|
+
replicas: options.replicas ?? 1,
|
|
1164
|
+
cpuCores: options.cpuCores ?? 1,
|
|
1165
|
+
memoryMiB: options.memoryMiB ?? 512,
|
|
1166
|
+
storageGiB: options.storageGiB ?? 8,
|
|
1167
|
+
sharing: options.sharing ?? "exclusive",
|
|
1168
|
+
reuse: options.reuse ?? "require",
|
|
1169
|
+
profile: options.profile ?? "app"
|
|
1170
|
+
};
|
|
1171
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(result.profile))
|
|
1172
|
+
throw new Error("deployment profile must be a lowercase typed name");
|
|
1173
|
+
if (!Number.isSafeInteger(result.replicas) || result.replicas < 1 || result.replicas > 1024)
|
|
1174
|
+
throw new Error("deployment replicas must be an integer from 1 to 1024");
|
|
1175
|
+
if (!Number.isSafeInteger(result.cpuCores) || result.cpuCores < 1 || result.cpuCores > 1024)
|
|
1176
|
+
throw new Error("deployment CPU cores must be an integer from 1 to 1024");
|
|
1177
|
+
if (!Number.isSafeInteger(result.memoryMiB) || result.memoryMiB < 128 || result.memoryMiB > 4194304)
|
|
1178
|
+
throw new Error("deployment memory must be an integer from 128 to 4194304 MiB");
|
|
1179
|
+
if (!Number.isSafeInteger(result.storageGiB) || result.storageGiB < 1 || result.storageGiB > 1048576)
|
|
1180
|
+
throw new Error("deployment storage must be an integer from 1 to 1048576 GiB");
|
|
1181
|
+
if (runtime === "kata-snp" && isolation !== "sev-snp")
|
|
1182
|
+
throw new Error("kata-snp requires sev-snp isolation");
|
|
1183
|
+
if (runtime === "containerd" && isolation === "sev-snp")
|
|
1184
|
+
throw new Error("containerd runc cannot provide sev-snp; use kata-snp");
|
|
1185
|
+
if (isolation === "sev-snp" && operatingSystem !== "ubuntu-26.04")
|
|
1186
|
+
throw new Error("sev-snp requires ubuntu-26.04");
|
|
1187
|
+
if (runtime === "native" && result.sharing !== "exclusive")
|
|
1188
|
+
throw new Error("native deployments require exclusive compute");
|
|
1189
|
+
if (result.reuse === "prefer" && options.provisioning === undefined)
|
|
1190
|
+
throw new Error("reuse prefer requires approved provisioning coordinates");
|
|
1191
|
+
if (result.reuse === "require" && options.provisioning !== undefined)
|
|
1192
|
+
throw new Error("reuse require cannot include provisioning coordinates");
|
|
1193
|
+
if (options.provisioning !== undefined) {
|
|
1194
|
+
for (const [label, value] of Object.entries({
|
|
1195
|
+
plan: options.provisioning.planKey,
|
|
1196
|
+
region: options.provisioning.regionKey,
|
|
1197
|
+
image: options.provisioning.imageKey,
|
|
1198
|
+
environment: options.provisioning.environmentKey
|
|
1199
|
+
}))
|
|
1200
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(value))
|
|
1201
|
+
throw new Error(`deployment provisioning ${label} must be a lowercase typed name`);
|
|
1202
|
+
if (!/^\d{1,18}$/.test(options.provisioning.maxMonthlySpendMinor))
|
|
1203
|
+
throw new Error("deployment provisioning monthly spend must be a bounded decimal minor-unit amount");
|
|
1204
|
+
if (options.provisioning.ownership === "tenant-metal" && !options.provisioning.metalHostname)
|
|
1205
|
+
throw new Error("tenant-metal provisioning requires a metal hostname");
|
|
1206
|
+
if (options.provisioning.ownership === "platform" && options.provisioning.metalHostname)
|
|
1207
|
+
throw new Error("platform provisioning cannot bind a tenant metal hostname");
|
|
1208
|
+
if (options.provisioning.metalHostname && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(options.provisioning.metalHostname))
|
|
1209
|
+
throw new Error("deployment provisioning metal hostname is invalid");
|
|
1210
|
+
}
|
|
1211
|
+
return { ...result, ...options.provisioning ? { provisioning: options.provisioning } : {} };
|
|
1212
|
+
}
|
|
1213
|
+
function defaultTypeScriptDeployment(name, options = {}) {
|
|
1214
|
+
const selected = initCoordinates(options);
|
|
1215
|
+
const execution = selected.runtime === "native" ? "native" : selected.runtime === "containerd" ? "oci-runc" : "oci-kata-qemu-snp";
|
|
1216
|
+
const hostPackageVersion = selected.operatingSystem === "ubuntu-24.04" ? "ubuntu-24.04" : "ubuntu-26.04";
|
|
1217
|
+
const provisioningBlock = selected.provisioning === undefined ? "" : `,
|
|
1218
|
+
provisioning: { planKey: '${selected.provisioning.planKey}', regionKey: '${selected.provisioning.regionKey}', imageKey: '${selected.provisioning.imageKey}', environmentKey: '${selected.provisioning.environmentKey}', ownership: '${selected.provisioning.ownership}',${selected.provisioning.metalHostname ? ` metalHostname: '${selected.provisioning.metalHostname}',` : ""} maxMonthlySpendMinor: '${selected.provisioning.maxMonthlySpendMinor}' }`;
|
|
1219
|
+
const requirementBlock = selected.runtime === "native" ? `bun: providers.bun.require()` : selected.runtime === "containerd" ? `containerd: providers.containerd.require({ version: '${hostPackageVersion}' }),
|
|
1220
|
+
nginx: providers.nginx.require({ version: '${hostPackageVersion}' })` : `containerd: providers.containerd.require(),
|
|
1221
|
+
kata: providers.kata.require(),
|
|
1222
|
+
nginx: providers.nginx.require()`;
|
|
1223
|
+
const componentBlock = selected.runtime === "native" ? `
|
|
1174
1224
|
app: application({
|
|
1175
1225
|
target: 'app',
|
|
1176
1226
|
runtime: { kind: 'native', provider: 'forgezero.bun', requirement: 'bun', argv: ['/usr/local/bin/bun', 'run', 'start'] },
|
|
1177
1227
|
service: { protocol: 'http', port: 3000, health: { protocol: 'http', method: 'GET', path: '/health', expectedStatus: [200], timeoutMs: 5_000 } },
|
|
1178
1228
|
resources: {},
|
|
1179
1229
|
rollout: { strategy: 'direct' }
|
|
1180
|
-
})
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1230
|
+
})` : `
|
|
1231
|
+
app: application({
|
|
1232
|
+
target: 'app',
|
|
1233
|
+
runtime: {
|
|
1234
|
+
kind: 'container', provider: '${selected.runtime === "containerd" ? "forgezero.containerd" : "forgezero.kata"}', requirement: '${selected.runtime === "containerd" ? "containerd" : "kata"}', runtimeClass: '${selected.runtime === "containerd" ? "runc" : "kata-qemu-snp"}',
|
|
1235
|
+
image: { source: { kind: 'build', context: '.', dockerfile: 'Dockerfile' } },
|
|
1236
|
+
security: { privileged: false, noNewPrivileges: true, root: 'read-only', dropCapabilities: ['ALL'] }
|
|
1237
|
+
},
|
|
1238
|
+
service: { protocol: 'http', port: 3000, health: { protocol: 'http', method: 'GET', path: '/health', expectedStatus: [200], timeoutMs: 5_000 } },
|
|
1239
|
+
resources: { cpu: { limit: ${selected.cpuCores} }, memory: { limitMiB: ${selected.memoryMiB}, swap: 'disabled' }, pids: { limit: 256 } },
|
|
1240
|
+
storage: [{ class: 'ephemeral', path: '/tmp', type: 'tmpfs', sizeMiB: ${Math.min(128, Math.max(16, Math.floor(selected.memoryMiB / 4)))} }],
|
|
1241
|
+
network: { ingress: { exposure: 'loopback', stablePort: 3000 }, container: { mode: 'bridge', network: 'app' } },
|
|
1242
|
+
rollout: { strategy: 'blue-green', proxy: 'nginx', drainMs: 30_000, automaticRollback: true }
|
|
1243
|
+
})`;
|
|
1244
|
+
const workflowBlock = selected.runtime === "native" ? `
|
|
1185
1245
|
build: stage({ strategy: { mode: 'sequential' }, steps: {
|
|
1186
1246
|
build: actions.exec.argv(
|
|
1187
1247
|
{ component: 'app', argv: ['/usr/local/bin/fz-agent', 'pipeline-todo', 'replace with the project build argv'] },
|
|
@@ -1196,7 +1256,42 @@ export default defineDeployment({
|
|
|
1196
1256
|
} }),
|
|
1197
1257
|
verify: stage({ dependsOn: ['release'], strategy: { mode: 'parallel' }, steps: {
|
|
1198
1258
|
health: actions.service.health({ component: 'app' }, { scope: { kind: 'each-target', target: 'app' }, timeoutMs: 30_000 })
|
|
1199
|
-
} })
|
|
1259
|
+
} })` : `
|
|
1260
|
+
prepare: stage({ strategy: { mode: 'sequential' }, steps: {
|
|
1261
|
+
software: actions.software.ensure({ requirements: [${selected.runtime === "containerd" ? "'containerd', 'nginx'" : "'containerd', 'kata', 'nginx'"}] }, { scope: { kind: 'each-target', target: 'app' }, timeoutMs: 900_000 }),
|
|
1262
|
+
review: actions.exec.argv({ component: 'app', argv: ['/usr/local/bin/fz-agent', 'pipeline-todo', 'review the Dockerfile and container entrypoint'] }, { scope: { kind: 'release-executor' } })
|
|
1263
|
+
} }),
|
|
1264
|
+
build: stage({ dependsOn: ['prepare'], strategy: { mode: 'sequential' }, steps: {
|
|
1265
|
+
image: actions.container.build({ component: 'app' }, { scope: { kind: 'release-executor' }, timeoutMs: 900_000 })
|
|
1266
|
+
} }),
|
|
1267
|
+
release: stage({ dependsOn: ['build'], strategy: { mode: 'blue-green', maximumConcurrency: 1, minimumHealthy: 1 }, steps: {
|
|
1268
|
+
promote: actions.service.promote({ component: 'app', imageDigest: { $ref: 'steps.image.outputs.digest' } }, { scope: { kind: 'each-target', target: 'app' }, timeoutMs: 180_000 })
|
|
1269
|
+
} }),
|
|
1270
|
+
verify: stage({ dependsOn: ['release'], strategy: { mode: 'parallel' }, steps: {
|
|
1271
|
+
health: actions.service.health({ component: 'app' }, { scope: { kind: 'each-target', target: 'app' }, timeoutMs: 30_000 })
|
|
1272
|
+
} })`;
|
|
1273
|
+
return `import { actions, application, defineDeployment, providers, stage, target, workflow } from '@forgezero/agent/deploy';
|
|
1274
|
+
|
|
1275
|
+
export default defineDeployment({
|
|
1276
|
+
apiVersion: 'deploy.forgezero.net/v1',
|
|
1277
|
+
kind: 'Deployment',
|
|
1278
|
+
metadata: { name: '${safeName(name)}' },
|
|
1279
|
+
spec: {
|
|
1280
|
+
security: { attestation: '${selected.isolation === "sev-snp" ? "required" : "preferred"}' },
|
|
1281
|
+
targets: {
|
|
1282
|
+
app: target.compute({
|
|
1283
|
+
profiles: ['${selected.profile}'], replicas: ${selected.replicas},
|
|
1284
|
+
resources: { cpuCores: ${selected.cpuCores}, memoryMiB: ${selected.memoryMiB}, storageGiB: ${selected.storageGiB} },
|
|
1285
|
+
os: '${selected.operatingSystem}', runtime: '${execution}', isolation: '${selected.isolation}',
|
|
1286
|
+
sharing: '${selected.sharing}', reuse: '${selected.reuse}'${provisioningBlock}
|
|
1287
|
+
})
|
|
1288
|
+
},
|
|
1289
|
+
requirements: { ${requirementBlock} },
|
|
1290
|
+
components: {${componentBlock}
|
|
1291
|
+
},
|
|
1292
|
+
workflows: {
|
|
1293
|
+
deploy: workflow({
|
|
1294
|
+
stages: {${workflowBlock}
|
|
1200
1295
|
}
|
|
1201
1296
|
})
|
|
1202
1297
|
}
|
|
@@ -1208,7 +1303,7 @@ function initializeTypeScriptDeployment(rootValue, options) {
|
|
|
1208
1303
|
const path = localPath(rootValue, DEPLOY_SOURCE_FILE, "deployment source");
|
|
1209
1304
|
if (existsSync(path) && !options.force)
|
|
1210
1305
|
throw new Error(`${DEPLOY_SOURCE_FILE} already exists; use --force only when replacing it deliberately`);
|
|
1211
|
-
writeFileSync(path, defaultTypeScriptDeployment(options.name), { mode: 420, flag: options.force ? "w" : "wx" });
|
|
1306
|
+
writeFileSync(path, defaultTypeScriptDeployment(options.name, options), { mode: 420, flag: options.force ? "w" : "wx" });
|
|
1212
1307
|
return path;
|
|
1213
1308
|
}
|
|
1214
1309
|
function inside(root, path) {
|
|
@@ -1229,10 +1324,35 @@ async function loadDeploymentSource(root, sourceFile = DEPLOY_SOURCE_FILE) {
|
|
|
1229
1324
|
const status = lstatSync(source);
|
|
1230
1325
|
if (!status.isFile() || status.isSymbolicLink() || status.size > 2 * 1024 * 1024)
|
|
1231
1326
|
throw new Error("deployment source must be one bounded regular file");
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1327
|
+
const builtDeploy = new URL("./deploy.js", import.meta.url);
|
|
1328
|
+
const sourceDeploy = new URL("./deploy.ts", import.meta.url);
|
|
1329
|
+
const deployModule = existsSync(fileURLToPath(builtDeploy)) ? builtDeploy.href : sourceDeploy.href;
|
|
1330
|
+
const result = await Bun.build({
|
|
1331
|
+
entrypoints: [source],
|
|
1332
|
+
target: "bun",
|
|
1333
|
+
format: "esm",
|
|
1334
|
+
minify: false,
|
|
1335
|
+
plugins: [{
|
|
1336
|
+
name: "forgezero-deploy-authoring",
|
|
1337
|
+
setup(builder) {
|
|
1338
|
+
builder.onResolve({ filter: /^@forgezero\/agent\/deploy$/ }, () => ({ path: fileURLToPath(deployModule) }));
|
|
1339
|
+
}
|
|
1340
|
+
}]
|
|
1341
|
+
});
|
|
1342
|
+
if (!result.success || result.outputs.length !== 1) {
|
|
1343
|
+
throw new Error(`deployment source compilation failed: ${result.logs.map((entry) => entry.message).join("; ")}`);
|
|
1344
|
+
}
|
|
1345
|
+
const directory = mkdtempSync(join(tmpdir(), "forgezero-deploy-compile-"));
|
|
1346
|
+
const compiled = join(directory, "deployment.mjs");
|
|
1347
|
+
try {
|
|
1348
|
+
writeFileSync(compiled, Buffer.from(await result.outputs[0].arrayBuffer()), { mode: 384, flag: "wx" });
|
|
1349
|
+
const module = await import(`${pathToFileURL(compiled).href}?forgezero=${status.mtimeMs}`);
|
|
1350
|
+
if (module.default === undefined)
|
|
1351
|
+
throw new Error(`${sourceFile} must export one default deployment definition`);
|
|
1352
|
+
return module.default;
|
|
1353
|
+
} finally {
|
|
1354
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1355
|
+
}
|
|
1236
1356
|
}
|
|
1237
1357
|
async function compileDeploymentProject(rootValue, options = {}) {
|
|
1238
1358
|
const root = resolve(rootValue);
|
package/dist/fz-agent.js
CHANGED
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.17";
|
|
4638
4638
|
var init_dist = __esm(() => {
|
|
4639
4639
|
init_identity();
|
|
4640
4640
|
VaultError = class VaultError extends Error {
|
|
@@ -4811,7 +4811,7 @@ async function spawnWith(command, env, report = () => {}, options = {}) {
|
|
|
4811
4811
|
|
|
4812
4812
|
// src/cli/index.ts
|
|
4813
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
|
|
4814
|
+
import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join13, resolve as resolve12 } from "path";
|
|
4815
4815
|
|
|
4816
4816
|
// src/process-input.ts
|
|
4817
4817
|
async function writeAndCloseProcessInput(input, value) {
|
|
@@ -4821,7 +4821,7 @@ async function writeAndCloseProcessInput(input, value) {
|
|
|
4821
4821
|
|
|
4822
4822
|
// src/cli/index.ts
|
|
4823
4823
|
init_dist();
|
|
4824
|
-
import { fileURLToPath as
|
|
4824
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4825
4825
|
import { hostname } from "os";
|
|
4826
4826
|
|
|
4827
4827
|
// src/agent-update.ts
|
|
@@ -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.84";
|
|
4841
4841
|
|
|
4842
4842
|
// src/software.ts
|
|
4843
4843
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -10365,9 +10365,10 @@ function initializeDeployFile(root, options = {}) {
|
|
|
10365
10365
|
}
|
|
10366
10366
|
|
|
10367
10367
|
// src/deploy-compiler.ts
|
|
10368
|
-
import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
10369
|
-
import {
|
|
10370
|
-
import {
|
|
10368
|
+
import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync6, mkdtempSync, readFileSync as readFileSync6, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
10369
|
+
import { tmpdir } from "os";
|
|
10370
|
+
import { dirname as dirname5, isAbsolute, join as join5, relative, resolve as resolve4, sep as sep3 } from "path";
|
|
10371
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
10371
10372
|
|
|
10372
10373
|
// src/deploy-plan.ts
|
|
10373
10374
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -11518,37 +11519,96 @@ function safeName2(value) {
|
|
|
11518
11519
|
const result = value.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
|
|
11519
11520
|
return /^[a-z]/.test(result) ? result : `app-${result || "service"}`;
|
|
11520
11521
|
}
|
|
11521
|
-
function
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11522
|
+
function initCoordinates(options) {
|
|
11523
|
+
const runtime = options.runtime ?? "native";
|
|
11524
|
+
const isolation = options.isolation ?? (runtime === "kata-snp" ? "sev-snp" : "standard");
|
|
11525
|
+
const operatingSystem = options.operatingSystem ?? (isolation === "sev-snp" ? "ubuntu-26.04" : "ubuntu-24.04");
|
|
11526
|
+
const result = {
|
|
11527
|
+
runtime,
|
|
11528
|
+
isolation,
|
|
11529
|
+
operatingSystem,
|
|
11530
|
+
replicas: options.replicas ?? 1,
|
|
11531
|
+
cpuCores: options.cpuCores ?? 1,
|
|
11532
|
+
memoryMiB: options.memoryMiB ?? 512,
|
|
11533
|
+
storageGiB: options.storageGiB ?? 8,
|
|
11534
|
+
sharing: options.sharing ?? "exclusive",
|
|
11535
|
+
reuse: options.reuse ?? "require",
|
|
11536
|
+
profile: options.profile ?? "app"
|
|
11537
|
+
};
|
|
11538
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(result.profile))
|
|
11539
|
+
throw new Error("deployment profile must be a lowercase typed name");
|
|
11540
|
+
if (!Number.isSafeInteger(result.replicas) || result.replicas < 1 || result.replicas > 1024)
|
|
11541
|
+
throw new Error("deployment replicas must be an integer from 1 to 1024");
|
|
11542
|
+
if (!Number.isSafeInteger(result.cpuCores) || result.cpuCores < 1 || result.cpuCores > 1024)
|
|
11543
|
+
throw new Error("deployment CPU cores must be an integer from 1 to 1024");
|
|
11544
|
+
if (!Number.isSafeInteger(result.memoryMiB) || result.memoryMiB < 128 || result.memoryMiB > 4194304)
|
|
11545
|
+
throw new Error("deployment memory must be an integer from 128 to 4194304 MiB");
|
|
11546
|
+
if (!Number.isSafeInteger(result.storageGiB) || result.storageGiB < 1 || result.storageGiB > 1048576)
|
|
11547
|
+
throw new Error("deployment storage must be an integer from 1 to 1048576 GiB");
|
|
11548
|
+
if (runtime === "kata-snp" && isolation !== "sev-snp")
|
|
11549
|
+
throw new Error("kata-snp requires sev-snp isolation");
|
|
11550
|
+
if (runtime === "containerd" && isolation === "sev-snp")
|
|
11551
|
+
throw new Error("containerd runc cannot provide sev-snp; use kata-snp");
|
|
11552
|
+
if (isolation === "sev-snp" && operatingSystem !== "ubuntu-26.04")
|
|
11553
|
+
throw new Error("sev-snp requires ubuntu-26.04");
|
|
11554
|
+
if (runtime === "native" && result.sharing !== "exclusive")
|
|
11555
|
+
throw new Error("native deployments require exclusive compute");
|
|
11556
|
+
if (result.reuse === "prefer" && options.provisioning === undefined)
|
|
11557
|
+
throw new Error("reuse prefer requires approved provisioning coordinates");
|
|
11558
|
+
if (result.reuse === "require" && options.provisioning !== undefined)
|
|
11559
|
+
throw new Error("reuse require cannot include provisioning coordinates");
|
|
11560
|
+
if (options.provisioning !== undefined) {
|
|
11561
|
+
for (const [label, value] of Object.entries({
|
|
11562
|
+
plan: options.provisioning.planKey,
|
|
11563
|
+
region: options.provisioning.regionKey,
|
|
11564
|
+
image: options.provisioning.imageKey,
|
|
11565
|
+
environment: options.provisioning.environmentKey
|
|
11566
|
+
}))
|
|
11567
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(value))
|
|
11568
|
+
throw new Error(`deployment provisioning ${label} must be a lowercase typed name`);
|
|
11569
|
+
if (!/^\d{1,18}$/.test(options.provisioning.maxMonthlySpendMinor))
|
|
11570
|
+
throw new Error("deployment provisioning monthly spend must be a bounded decimal minor-unit amount");
|
|
11571
|
+
if (options.provisioning.ownership === "tenant-metal" && !options.provisioning.metalHostname)
|
|
11572
|
+
throw new Error("tenant-metal provisioning requires a metal hostname");
|
|
11573
|
+
if (options.provisioning.ownership === "platform" && options.provisioning.metalHostname)
|
|
11574
|
+
throw new Error("platform provisioning cannot bind a tenant metal hostname");
|
|
11575
|
+
if (options.provisioning.metalHostname && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(options.provisioning.metalHostname))
|
|
11576
|
+
throw new Error("deployment provisioning metal hostname is invalid");
|
|
11577
|
+
}
|
|
11578
|
+
return { ...result, ...options.provisioning ? { provisioning: options.provisioning } : {} };
|
|
11579
|
+
}
|
|
11580
|
+
function defaultTypeScriptDeployment(name, options = {}) {
|
|
11581
|
+
const selected = initCoordinates(options);
|
|
11582
|
+
const execution = selected.runtime === "native" ? "native" : selected.runtime === "containerd" ? "oci-runc" : "oci-kata-qemu-snp";
|
|
11583
|
+
const hostPackageVersion = selected.operatingSystem === "ubuntu-24.04" ? "ubuntu-24.04" : "ubuntu-26.04";
|
|
11584
|
+
const provisioningBlock = selected.provisioning === undefined ? "" : `,
|
|
11585
|
+
provisioning: { planKey: '${selected.provisioning.planKey}', regionKey: '${selected.provisioning.regionKey}', imageKey: '${selected.provisioning.imageKey}', environmentKey: '${selected.provisioning.environmentKey}', ownership: '${selected.provisioning.ownership}',${selected.provisioning.metalHostname ? ` metalHostname: '${selected.provisioning.metalHostname}',` : ""} maxMonthlySpendMinor: '${selected.provisioning.maxMonthlySpendMinor}' }`;
|
|
11586
|
+
const requirementBlock = selected.runtime === "native" ? `bun: providers.bun.require()` : selected.runtime === "containerd" ? `containerd: providers.containerd.require({ version: '${hostPackageVersion}' }),
|
|
11587
|
+
nginx: providers.nginx.require({ version: '${hostPackageVersion}' })` : `containerd: providers.containerd.require(),
|
|
11588
|
+
kata: providers.kata.require(),
|
|
11589
|
+
nginx: providers.nginx.require()`;
|
|
11590
|
+
const componentBlock = selected.runtime === "native" ? `
|
|
11541
11591
|
app: application({
|
|
11542
11592
|
target: 'app',
|
|
11543
11593
|
runtime: { kind: 'native', provider: 'forgezero.bun', requirement: 'bun', argv: ['/usr/local/bin/bun', 'run', 'start'] },
|
|
11544
11594
|
service: { protocol: 'http', port: 3000, health: { protocol: 'http', method: 'GET', path: '/health', expectedStatus: [200], timeoutMs: 5_000 } },
|
|
11545
11595
|
resources: {},
|
|
11546
11596
|
rollout: { strategy: 'direct' }
|
|
11547
|
-
})
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11597
|
+
})` : `
|
|
11598
|
+
app: application({
|
|
11599
|
+
target: 'app',
|
|
11600
|
+
runtime: {
|
|
11601
|
+
kind: 'container', provider: '${selected.runtime === "containerd" ? "forgezero.containerd" : "forgezero.kata"}', requirement: '${selected.runtime === "containerd" ? "containerd" : "kata"}', runtimeClass: '${selected.runtime === "containerd" ? "runc" : "kata-qemu-snp"}',
|
|
11602
|
+
image: { source: { kind: 'build', context: '.', dockerfile: 'Dockerfile' } },
|
|
11603
|
+
security: { privileged: false, noNewPrivileges: true, root: 'read-only', dropCapabilities: ['ALL'] }
|
|
11604
|
+
},
|
|
11605
|
+
service: { protocol: 'http', port: 3000, health: { protocol: 'http', method: 'GET', path: '/health', expectedStatus: [200], timeoutMs: 5_000 } },
|
|
11606
|
+
resources: { cpu: { limit: ${selected.cpuCores} }, memory: { limitMiB: ${selected.memoryMiB}, swap: 'disabled' }, pids: { limit: 256 } },
|
|
11607
|
+
storage: [{ class: 'ephemeral', path: '/tmp', type: 'tmpfs', sizeMiB: ${Math.min(128, Math.max(16, Math.floor(selected.memoryMiB / 4)))} }],
|
|
11608
|
+
network: { ingress: { exposure: 'loopback', stablePort: 3000 }, container: { mode: 'bridge', network: 'app' } },
|
|
11609
|
+
rollout: { strategy: 'blue-green', proxy: 'nginx', drainMs: 30_000, automaticRollback: true }
|
|
11610
|
+
})`;
|
|
11611
|
+
const workflowBlock = selected.runtime === "native" ? `
|
|
11552
11612
|
build: stage({ strategy: { mode: 'sequential' }, steps: {
|
|
11553
11613
|
build: actions.exec.argv(
|
|
11554
11614
|
{ component: 'app', argv: ['/usr/local/bin/fz-agent', 'pipeline-todo', 'replace with the project build argv'] },
|
|
@@ -11563,7 +11623,42 @@ export default defineDeployment({
|
|
|
11563
11623
|
} }),
|
|
11564
11624
|
verify: stage({ dependsOn: ['release'], strategy: { mode: 'parallel' }, steps: {
|
|
11565
11625
|
health: actions.service.health({ component: 'app' }, { scope: { kind: 'each-target', target: 'app' }, timeoutMs: 30_000 })
|
|
11566
|
-
} })
|
|
11626
|
+
} })` : `
|
|
11627
|
+
prepare: stage({ strategy: { mode: 'sequential' }, steps: {
|
|
11628
|
+
software: actions.software.ensure({ requirements: [${selected.runtime === "containerd" ? "'containerd', 'nginx'" : "'containerd', 'kata', 'nginx'"}] }, { scope: { kind: 'each-target', target: 'app' }, timeoutMs: 900_000 }),
|
|
11629
|
+
review: actions.exec.argv({ component: 'app', argv: ['/usr/local/bin/fz-agent', 'pipeline-todo', 'review the Dockerfile and container entrypoint'] }, { scope: { kind: 'release-executor' } })
|
|
11630
|
+
} }),
|
|
11631
|
+
build: stage({ dependsOn: ['prepare'], strategy: { mode: 'sequential' }, steps: {
|
|
11632
|
+
image: actions.container.build({ component: 'app' }, { scope: { kind: 'release-executor' }, timeoutMs: 900_000 })
|
|
11633
|
+
} }),
|
|
11634
|
+
release: stage({ dependsOn: ['build'], strategy: { mode: 'blue-green', maximumConcurrency: 1, minimumHealthy: 1 }, steps: {
|
|
11635
|
+
promote: actions.service.promote({ component: 'app', imageDigest: { $ref: 'steps.image.outputs.digest' } }, { scope: { kind: 'each-target', target: 'app' }, timeoutMs: 180_000 })
|
|
11636
|
+
} }),
|
|
11637
|
+
verify: stage({ dependsOn: ['release'], strategy: { mode: 'parallel' }, steps: {
|
|
11638
|
+
health: actions.service.health({ component: 'app' }, { scope: { kind: 'each-target', target: 'app' }, timeoutMs: 30_000 })
|
|
11639
|
+
} })`;
|
|
11640
|
+
return `import { actions, application, defineDeployment, providers, stage, target, workflow } from '@forgezero/agent/deploy';
|
|
11641
|
+
|
|
11642
|
+
export default defineDeployment({
|
|
11643
|
+
apiVersion: 'deploy.forgezero.net/v1',
|
|
11644
|
+
kind: 'Deployment',
|
|
11645
|
+
metadata: { name: '${safeName2(name)}' },
|
|
11646
|
+
spec: {
|
|
11647
|
+
security: { attestation: '${selected.isolation === "sev-snp" ? "required" : "preferred"}' },
|
|
11648
|
+
targets: {
|
|
11649
|
+
app: target.compute({
|
|
11650
|
+
profiles: ['${selected.profile}'], replicas: ${selected.replicas},
|
|
11651
|
+
resources: { cpuCores: ${selected.cpuCores}, memoryMiB: ${selected.memoryMiB}, storageGiB: ${selected.storageGiB} },
|
|
11652
|
+
os: '${selected.operatingSystem}', runtime: '${execution}', isolation: '${selected.isolation}',
|
|
11653
|
+
sharing: '${selected.sharing}', reuse: '${selected.reuse}'${provisioningBlock}
|
|
11654
|
+
})
|
|
11655
|
+
},
|
|
11656
|
+
requirements: { ${requirementBlock} },
|
|
11657
|
+
components: {${componentBlock}
|
|
11658
|
+
},
|
|
11659
|
+
workflows: {
|
|
11660
|
+
deploy: workflow({
|
|
11661
|
+
stages: {${workflowBlock}
|
|
11567
11662
|
}
|
|
11568
11663
|
})
|
|
11569
11664
|
}
|
|
@@ -11575,7 +11670,7 @@ function initializeTypeScriptDeployment(rootValue, options) {
|
|
|
11575
11670
|
const path = localPath(rootValue, DEPLOY_SOURCE_FILE, "deployment source");
|
|
11576
11671
|
if (existsSync6(path) && !options.force)
|
|
11577
11672
|
throw new Error(`${DEPLOY_SOURCE_FILE} already exists; use --force only when replacing it deliberately`);
|
|
11578
|
-
writeFileSync6(path, defaultTypeScriptDeployment(options.name), { mode: 420, flag: options.force ? "w" : "wx" });
|
|
11673
|
+
writeFileSync6(path, defaultTypeScriptDeployment(options.name, options), { mode: 420, flag: options.force ? "w" : "wx" });
|
|
11579
11674
|
return path;
|
|
11580
11675
|
}
|
|
11581
11676
|
function inside(root, path) {
|
|
@@ -11596,10 +11691,35 @@ async function loadDeploymentSource(root, sourceFile = DEPLOY_SOURCE_FILE) {
|
|
|
11596
11691
|
const status = lstatSync2(source);
|
|
11597
11692
|
if (!status.isFile() || status.isSymbolicLink() || status.size > 2 * 1024 * 1024)
|
|
11598
11693
|
throw new Error("deployment source must be one bounded regular file");
|
|
11599
|
-
const
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11694
|
+
const builtDeploy = new URL("./deploy.js", import.meta.url);
|
|
11695
|
+
const sourceDeploy = new URL("./deploy.ts", import.meta.url);
|
|
11696
|
+
const deployModule = existsSync6(fileURLToPath(builtDeploy)) ? builtDeploy.href : sourceDeploy.href;
|
|
11697
|
+
const result = await Bun.build({
|
|
11698
|
+
entrypoints: [source],
|
|
11699
|
+
target: "bun",
|
|
11700
|
+
format: "esm",
|
|
11701
|
+
minify: false,
|
|
11702
|
+
plugins: [{
|
|
11703
|
+
name: "forgezero-deploy-authoring",
|
|
11704
|
+
setup(builder) {
|
|
11705
|
+
builder.onResolve({ filter: /^@forgezero\/agent\/deploy$/ }, () => ({ path: fileURLToPath(deployModule) }));
|
|
11706
|
+
}
|
|
11707
|
+
}]
|
|
11708
|
+
});
|
|
11709
|
+
if (!result.success || result.outputs.length !== 1) {
|
|
11710
|
+
throw new Error(`deployment source compilation failed: ${result.logs.map((entry) => entry.message).join("; ")}`);
|
|
11711
|
+
}
|
|
11712
|
+
const directory = mkdtempSync(join5(tmpdir(), "forgezero-deploy-compile-"));
|
|
11713
|
+
const compiled = join5(directory, "deployment.mjs");
|
|
11714
|
+
try {
|
|
11715
|
+
writeFileSync6(compiled, Buffer.from(await result.outputs[0].arrayBuffer()), { mode: 384, flag: "wx" });
|
|
11716
|
+
const module = await import(`${pathToFileURL(compiled).href}?forgezero=${status.mtimeMs}`);
|
|
11717
|
+
if (module.default === undefined)
|
|
11718
|
+
throw new Error(`${sourceFile} must export one default deployment definition`);
|
|
11719
|
+
return module.default;
|
|
11720
|
+
} finally {
|
|
11721
|
+
rmSync4(directory, { recursive: true, force: true });
|
|
11722
|
+
}
|
|
11603
11723
|
}
|
|
11604
11724
|
async function compileDeploymentProject(rootValue, options = {}) {
|
|
11605
11725
|
const root = resolve4(rootValue);
|
|
@@ -11733,7 +11853,7 @@ import {
|
|
|
11733
11853
|
unlinkSync,
|
|
11734
11854
|
writeFileSync as writeFileSync7
|
|
11735
11855
|
} from "fs";
|
|
11736
|
-
import { dirname as dirname6, join as
|
|
11856
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
11737
11857
|
import { homedir } from "os";
|
|
11738
11858
|
var EMPTY2 = () => ({ version: 1, sessions: {} });
|
|
11739
11859
|
function canonicalApi(value) {
|
|
@@ -11751,8 +11871,8 @@ ${realm.trim() || "platform"}`).toString("base64url");
|
|
|
11751
11871
|
function defaultSessionPath(env = process.env) {
|
|
11752
11872
|
if (env.FZ_SESSION_FILE?.trim())
|
|
11753
11873
|
return env.FZ_SESSION_FILE.trim();
|
|
11754
|
-
const state = env.XDG_STATE_HOME?.trim() ||
|
|
11755
|
-
return
|
|
11874
|
+
const state = env.XDG_STATE_HOME?.trim() || join6(homedir(), ".local", "state");
|
|
11875
|
+
return join6(state, "forgezero", "sessions.json");
|
|
11756
11876
|
}
|
|
11757
11877
|
function assertPrivate(path, kind) {
|
|
11758
11878
|
if (!existsSync7(path))
|
|
@@ -11851,7 +11971,7 @@ import {
|
|
|
11851
11971
|
writeFileSync as writeFileSync9
|
|
11852
11972
|
} from "fs";
|
|
11853
11973
|
import { dirname as dirname9 } from "path";
|
|
11854
|
-
import { fileURLToPath } from "url";
|
|
11974
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11855
11975
|
|
|
11856
11976
|
// src/ubuntu.ts
|
|
11857
11977
|
var current = OS_CATALOG[0];
|
|
@@ -12427,7 +12547,7 @@ function planLocalOtlpProof(endpoint, collectorUnit) {
|
|
|
12427
12547
|
import { constants } from "fs";
|
|
12428
12548
|
import { createHmac, randomUUID } from "crypto";
|
|
12429
12549
|
import { chmod, lstat, mkdir, open, readdir, rename, rmdir, stat, unlink } from "fs/promises";
|
|
12430
|
-
import { dirname as dirname7, join as
|
|
12550
|
+
import { dirname as dirname7, join as join7, resolve as resolve5 } from "path";
|
|
12431
12551
|
import { isIP as isIP3 } from "net";
|
|
12432
12552
|
|
|
12433
12553
|
// src/cloudflare-edge.ts
|
|
@@ -13099,7 +13219,7 @@ function cloudflareHostHandoffPath(checkpointPath, nodeName) {
|
|
|
13099
13219
|
const normalized = nodeName.trim().toLowerCase();
|
|
13100
13220
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(normalized))
|
|
13101
13221
|
throw new Error("Cloudflare host handoff node name is invalid");
|
|
13102
|
-
return
|
|
13222
|
+
return join7(`${resolve5(checkpointPath)}.hosts`, `${normalized}.json`);
|
|
13103
13223
|
}
|
|
13104
13224
|
async function readCloudflareHostHandoff(handoffPath, nodeName) {
|
|
13105
13225
|
let parsed;
|
|
@@ -13481,7 +13601,7 @@ async function removeCloudflareBootstrapSecrets(checkpointPath, output) {
|
|
|
13481
13601
|
throw new Error("Cloudflare host handoff directory contains unexpected files; refusing secret cleanup");
|
|
13482
13602
|
}
|
|
13483
13603
|
for (const name of expected)
|
|
13484
|
-
await unlink(
|
|
13604
|
+
await unlink(join7(directory, name));
|
|
13485
13605
|
await rmdir(directory);
|
|
13486
13606
|
}
|
|
13487
13607
|
await unlink(resolve5(checkpointPath));
|
|
@@ -13923,7 +14043,7 @@ var LIFECYCLE_PROFILE = "/etc/forgezero/lifecycle.json";
|
|
|
13923
14043
|
var CONTROL_SOCKET = "/run/forgezero/control.sock";
|
|
13924
14044
|
var CLOUDFLARED_METRICS_ADDRESS = "127.0.0.1:20241";
|
|
13925
14045
|
var CLOUDFLARED_DIAGNOSTICS_URL = `http://${CLOUDFLARED_METRICS_ADDRESS}/diag/tunnel`;
|
|
13926
|
-
var PACKAGED_AGENT_BIN =
|
|
14046
|
+
var PACKAGED_AGENT_BIN = fileURLToPath2(new URL("./fz-agent.js", import.meta.url));
|
|
13927
14047
|
var privateOrigin = (value) => {
|
|
13928
14048
|
let url;
|
|
13929
14049
|
try {
|
|
@@ -14150,7 +14270,7 @@ var unitEscape = (value) => {
|
|
|
14150
14270
|
};
|
|
14151
14271
|
function databaseUnit(config) {
|
|
14152
14272
|
const db = config.database;
|
|
14153
|
-
const
|
|
14273
|
+
const join8 = db.role === "joiner" ? ` --starter.join=${unitEscape(db.master)}` : "";
|
|
14154
14274
|
const agency = db.agency === "none" ? " --cluster.start-agent=false --cluster.start-coordinator=true --cluster.start-dbserver=true" : "";
|
|
14155
14275
|
return `[Unit]
|
|
14156
14276
|
Description=ForgeZero ArangoDB Community 3.11.14 cluster (${db.role}; agency=${db.agency})
|
|
@@ -14162,7 +14282,7 @@ Type=simple
|
|
|
14162
14282
|
User=arangodb
|
|
14163
14283
|
Group=arangodb
|
|
14164
14284
|
LoadCredentialEncrypted=arangodb-jwt:${JWT_CREDENTIAL}
|
|
14165
|
-
ExecStart=/usr/bin/arangodb --starter.mode=cluster --starter.address=${unitEscape(db.address)} --starter.host=${unitEscape(db.address)} --starter.data-dir=/var/lib/forgezero-cluster --auth.jwt-secret=%d/arangodb-jwt${
|
|
14285
|
+
ExecStart=/usr/bin/arangodb --starter.mode=cluster --starter.address=${unitEscape(db.address)} --starter.host=${unitEscape(db.address)} --starter.data-dir=/var/lib/forgezero-cluster --auth.jwt-secret=%d/arangodb-jwt${join8}${agency}
|
|
14166
14286
|
Restart=always
|
|
14167
14287
|
RestartSec=5
|
|
14168
14288
|
UMask=0077
|
|
@@ -15489,11 +15609,11 @@ async function runCloudflareBootstrapFinalizeCommand(configPath, dependencies =
|
|
|
15489
15609
|
|
|
15490
15610
|
// src/operator-bootstrap.ts
|
|
15491
15611
|
import { createHash as createHash6, randomBytes as randomBytes9 } from "crypto";
|
|
15492
|
-
import { chmodSync as chmodSync6, lstatSync as lstatSync7, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
15612
|
+
import { chmodSync as chmodSync6, lstatSync as lstatSync7, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync2, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
15493
15613
|
import { isIP as isIP6 } from "net";
|
|
15494
|
-
import { tmpdir } from "os";
|
|
15495
|
-
import { basename as basename2, dirname as dirname13, isAbsolute as isAbsolute5, join as
|
|
15496
|
-
import { fileURLToPath as
|
|
15614
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
15615
|
+
import { basename as basename2, dirname as dirname13, isAbsolute as isAbsolute5, join as join11, resolve as resolve9 } from "path";
|
|
15616
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
15497
15617
|
|
|
15498
15618
|
// src/metal-bootstrap.ts
|
|
15499
15619
|
import { createHash as createHash5, randomBytes as randomBytes8 } from "crypto";
|
|
@@ -15512,15 +15632,15 @@ import {
|
|
|
15512
15632
|
unlinkSync as unlinkSync2,
|
|
15513
15633
|
writeFileSync as writeFileSync11
|
|
15514
15634
|
} from "fs";
|
|
15515
|
-
import { dirname as dirname12, isAbsolute as isAbsolute4, join as
|
|
15635
|
+
import { dirname as dirname12, isAbsolute as isAbsolute4, join as join10, resolve as resolve8 } from "path";
|
|
15516
15636
|
import { isIP as isIP5 } from "net";
|
|
15517
15637
|
|
|
15518
15638
|
// src/metal-isolation.ts
|
|
15519
15639
|
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
15520
|
-
import { join as
|
|
15640
|
+
import { join as join9 } from "path";
|
|
15521
15641
|
|
|
15522
15642
|
// src/metal-provision.ts
|
|
15523
|
-
import { dirname as dirname11, isAbsolute as isAbsolute3, join as
|
|
15643
|
+
import { dirname as dirname11, isAbsolute as isAbsolute3, join as join8 } from "path";
|
|
15524
15644
|
import { isIP as isIP4 } from "net";
|
|
15525
15645
|
var SAFE_NAME2 = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
|
|
15526
15646
|
var DEVICE = /^[a-zA-Z][a-zA-Z0-9_.-]{0,14}$/;
|
|
@@ -15716,15 +15836,15 @@ async function applyMetalIsolation(profile, exec = defaultExec) {
|
|
|
15716
15836
|
await requireGuestsInSlice(exec);
|
|
15717
15837
|
const unitDir = profile.unitDir;
|
|
15718
15838
|
mkdirSync10(unitDir, { recursive: true });
|
|
15719
|
-
writeFileSync10(
|
|
15839
|
+
writeFileSync10(join9(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
|
|
15720
15840
|
for (const unit of ["system.slice", "user.slice"]) {
|
|
15721
|
-
const directory =
|
|
15841
|
+
const directory = join9(unitDir, `${unit}.d`);
|
|
15722
15842
|
mkdirSync10(directory, { recursive: true });
|
|
15723
|
-
writeFileSync10(
|
|
15843
|
+
writeFileSync10(join9(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
|
|
15724
15844
|
}
|
|
15725
|
-
const initDirectory =
|
|
15845
|
+
const initDirectory = join9(unitDir, "init.scope.d");
|
|
15726
15846
|
mkdirSync10(initDirectory, { recursive: true });
|
|
15727
|
-
writeFileSync10(
|
|
15847
|
+
writeFileSync10(join9(initDirectory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "scope"), { mode: 420 });
|
|
15728
15848
|
await checked4(exec, ["systemctl", "daemon-reload"]);
|
|
15729
15849
|
await requireGuestsInSlice(exec);
|
|
15730
15850
|
const properties = [`AllowedCPUs=${profile.housekeepingCpus}`];
|
|
@@ -16104,9 +16224,9 @@ var installAgentBinary = (source, version) => {
|
|
|
16104
16224
|
validateAgentSourcePath(source);
|
|
16105
16225
|
const release = `/opt/forgezero/agent/versions/${version}/dist`;
|
|
16106
16226
|
mkdirSync11(release, { recursive: true, mode: 493 });
|
|
16107
|
-
copyFileSync2(source,
|
|
16108
|
-
chmodSync5(
|
|
16109
|
-
chownSync(
|
|
16227
|
+
copyFileSync2(source, join10(release, "fz-agent.js"));
|
|
16228
|
+
chmodSync5(join10(release, "fz-agent.js"), 493);
|
|
16229
|
+
chownSync(join10(release, "fz-agent.js"), 0, 0);
|
|
16110
16230
|
mkdirSync11("/opt/forgezero/agent", { recursive: true, mode: 493 });
|
|
16111
16231
|
for (const [link, target] of [
|
|
16112
16232
|
["/opt/forgezero/agent/current.next", `versions/${version}`],
|
|
@@ -16256,7 +16376,7 @@ async function applyMetalBootstrap(config, options) {
|
|
|
16256
16376
|
}
|
|
16257
16377
|
await applyMetalIsolation(config.profile, (argv2) => exec(argv2));
|
|
16258
16378
|
for (const [unit, body] of Object.entries(renderMetalUnits(config)))
|
|
16259
|
-
atomicWrite2(
|
|
16379
|
+
atomicWrite2(join10(UNIT_DIRECTORY, unit), body, 420);
|
|
16260
16380
|
await runChecked(exec, ["/usr/bin/systemctl", "daemon-reload"]);
|
|
16261
16381
|
await runChecked(exec, [
|
|
16262
16382
|
"/usr/bin/systemctl",
|
|
@@ -16390,7 +16510,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
16390
16510
|
"forgezero-metal-agent-egress.service",
|
|
16391
16511
|
"forgezero-metal-agent.service"
|
|
16392
16512
|
]) {
|
|
16393
|
-
if (!existsSync10(
|
|
16513
|
+
if (!existsSync10(join10(UNIT_DIRECTORY, unit)))
|
|
16394
16514
|
units[unit] = "missing";
|
|
16395
16515
|
else
|
|
16396
16516
|
units[unit] = (await exec(["/usr/bin/systemctl", "is-active", "--quiet", unit])).exitCode === 0 ? "active" : "inactive";
|
|
@@ -16919,7 +17039,7 @@ function writeKnownHosts(request, directory) {
|
|
|
16919
17039
|
const lines = [`fz-operator-target ${request.target.hostKey}`];
|
|
16920
17040
|
if (request.target.jump)
|
|
16921
17041
|
lines.push(`${hostLabel(request.target.jump.address, request.target.jump.port)} ${request.target.jump.hostKey}`);
|
|
16922
|
-
const path =
|
|
17042
|
+
const path = join11(directory, "known_hosts");
|
|
16923
17043
|
writeFileSync12(path, `${lines.join(`
|
|
16924
17044
|
`)}
|
|
16925
17045
|
`, { mode: 384, flag: "wx" });
|
|
@@ -17009,7 +17129,7 @@ async function collectOperatorGuestHostKeys(request, options = {}, includeRehear
|
|
|
17009
17129
|
publicIdentity(request.target.identityPublicKeyFile);
|
|
17010
17130
|
socketPath(request.target.agentSocket);
|
|
17011
17131
|
const exec = options.exec ?? defaultExec3;
|
|
17012
|
-
const directory =
|
|
17132
|
+
const directory = mkdtempSync2(join11(tmpdir2(), "forgezero-operator-host-keys-"));
|
|
17013
17133
|
try {
|
|
17014
17134
|
const knownHosts = writeKnownHosts(request, directory);
|
|
17015
17135
|
const nodes = [];
|
|
@@ -17045,7 +17165,7 @@ async function stageConfig(config, directory) {
|
|
|
17045
17165
|
const staged = [];
|
|
17046
17166
|
for (const [name, source] of secretSources(config)) {
|
|
17047
17167
|
const bytes = ownerFile(source, SECRET_LIMIT, name);
|
|
17048
|
-
const local =
|
|
17168
|
+
const local = join11(directory, name);
|
|
17049
17169
|
writeFileSync12(local, bytes, { mode: 384, flag: "wx" });
|
|
17050
17170
|
staged.push(name);
|
|
17051
17171
|
const remotePath = `${REMOTE_STAGE}/${name}`;
|
|
@@ -17065,7 +17185,7 @@ async function stageConfig(config, directory) {
|
|
|
17065
17185
|
manifestFile: `${REMOTE_STAGE}/bootstrap-api.bundle.json`,
|
|
17066
17186
|
branch: bundle.manifest.branch
|
|
17067
17187
|
};
|
|
17068
|
-
const path =
|
|
17188
|
+
const path = join11(directory, "platform-config.json");
|
|
17069
17189
|
writeFileSync12(path, `${JSON.stringify(rewritten, null, 2)}
|
|
17070
17190
|
`, { mode: 384, flag: "wx" });
|
|
17071
17191
|
return { path, files: staged, bundleFiles };
|
|
@@ -17075,14 +17195,14 @@ function stageMetalConfig(config, directory) {
|
|
|
17075
17195
|
const files = [];
|
|
17076
17196
|
if (config.agentSeedFile) {
|
|
17077
17197
|
const name = "metal-agent-seed";
|
|
17078
|
-
writeFileSync12(
|
|
17198
|
+
writeFileSync12(join11(directory, name), ownerFile(config.agentSeedFile, SECRET_LIMIT, name), {
|
|
17079
17199
|
mode: 384,
|
|
17080
17200
|
flag: "wx"
|
|
17081
17201
|
});
|
|
17082
17202
|
rewritten.agentSeedFile = `${REMOTE_STAGE}/${name}`;
|
|
17083
17203
|
files.push(name);
|
|
17084
17204
|
}
|
|
17085
|
-
const path =
|
|
17205
|
+
const path = join11(directory, "metal-config.json");
|
|
17086
17206
|
writeFileSync12(path, `${JSON.stringify(rewritten, null, 2)}
|
|
17087
17207
|
`, { mode: 384, flag: "wx" });
|
|
17088
17208
|
return { path, files };
|
|
@@ -17094,15 +17214,15 @@ async function verifiedBunArchive(directory, fetcher) {
|
|
|
17094
17214
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
17095
17215
|
if (createHash6("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256)
|
|
17096
17216
|
throw new Error("pinned Bun checksum mismatch");
|
|
17097
|
-
const path =
|
|
17217
|
+
const path = join11(directory, "bun.zip");
|
|
17098
17218
|
writeFileSync12(path, bytes, { mode: 384, flag: "wx" });
|
|
17099
17219
|
return path;
|
|
17100
17220
|
}
|
|
17101
17221
|
async function installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options) {
|
|
17102
17222
|
const artifacts = [
|
|
17103
|
-
[options.fzCliPath ??
|
|
17104
|
-
[options.fzAgentPath ??
|
|
17105
|
-
[options.fzGitSshPath ??
|
|
17223
|
+
[options.fzCliPath ?? fileURLToPath3(new URL("./fz.js", import.meta.url)), "fz.js"],
|
|
17224
|
+
[options.fzAgentPath ?? fileURLToPath3(new URL("./fz-agent.js", import.meta.url)), "fz-agent.js"],
|
|
17225
|
+
[options.fzGitSshPath ?? fileURLToPath3(new URL("./fz-git-ssh.js", import.meta.url)), "fz-git-ssh.js"]
|
|
17106
17226
|
];
|
|
17107
17227
|
for (const [artifact] of artifacts) {
|
|
17108
17228
|
if (!readFileSync12(artifact).length)
|
|
@@ -17145,7 +17265,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
|
|
|
17145
17265
|
publicIdentity(request.target.identityPublicKeyFile);
|
|
17146
17266
|
socketPath(request.target.agentSocket);
|
|
17147
17267
|
const exec = options.exec ?? defaultExec3;
|
|
17148
|
-
const directory =
|
|
17268
|
+
const directory = mkdtempSync2(join11(tmpdir2(), "forgezero-operator-bootstrap-"));
|
|
17149
17269
|
const remoteTemp = `/tmp/forgezero-operator-${randomBytes9(12).toString("hex")}`;
|
|
17150
17270
|
let knownHosts = "";
|
|
17151
17271
|
try {
|
|
@@ -17162,7 +17282,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
|
|
|
17162
17282
|
await installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options);
|
|
17163
17283
|
await copy(exec, request, knownHosts, staged.path, `${remoteTemp}/platform-config.json`, true);
|
|
17164
17284
|
for (const name of staged.files)
|
|
17165
|
-
await copy(exec, request, knownHosts,
|
|
17285
|
+
await copy(exec, request, knownHosts, join11(directory, name), `${remoteTemp}/${name}`, true);
|
|
17166
17286
|
for (const bundle of staged.bundleFiles)
|
|
17167
17287
|
await copy(exec, request, knownHosts, bundle.source, `${remoteTemp}/${bundle.name}`, true);
|
|
17168
17288
|
for (const name of ["platform-config.json", ...staged.files, ...staged.bundleFiles.map(({ name: name2 }) => name2)])
|
|
@@ -17210,7 +17330,7 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
|
17210
17330
|
publicIdentity(request.target.identityPublicKeyFile);
|
|
17211
17331
|
socketPath(request.target.agentSocket);
|
|
17212
17332
|
const exec = options.exec ?? defaultExec3;
|
|
17213
|
-
const directory =
|
|
17333
|
+
const directory = mkdtempSync2(join11(tmpdir2(), "forgezero-operator-metal-"));
|
|
17214
17334
|
const remoteTemp = `/tmp/forgezero-operator-${randomBytes9(12).toString("hex")}`;
|
|
17215
17335
|
let knownHosts = "";
|
|
17216
17336
|
try {
|
|
@@ -17243,7 +17363,7 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
|
|
|
17243
17363
|
await installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options);
|
|
17244
17364
|
await copy(exec, request, knownHosts, staged.path, `${remoteTemp}/metal-config.json`, true);
|
|
17245
17365
|
for (const name of staged.files)
|
|
17246
|
-
await copy(exec, request, knownHosts,
|
|
17366
|
+
await copy(exec, request, knownHosts, join11(directory, name), `${remoteTemp}/${name}`, true);
|
|
17247
17367
|
for (const name of ["metal-config.json", ...staged.files])
|
|
17248
17368
|
await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/bin/install", "-m", "0600", `${remoteTemp}/${name}`, `${REMOTE_STAGE}/${name}`], "remote metal handoff", true);
|
|
17249
17369
|
const initialized = await remoteRegularFileExists(exec, request, knownHosts, "/etc/forgezero/metal.initialized.json");
|
|
@@ -17512,7 +17632,7 @@ async function applyHostMaintenance(request, runtime = localRuntime()) {
|
|
|
17512
17632
|
|
|
17513
17633
|
// src/cli/maintenance.ts
|
|
17514
17634
|
import { existsSync as existsSync11, lstatSync as lstatSync9, readFileSync as readFileSync14, realpathSync as realpathSync5 } from "fs";
|
|
17515
|
-
import { isAbsolute as isAbsolute6, join as
|
|
17635
|
+
import { isAbsolute as isAbsolute6, join as join12, relative as relative2, resolve as resolve10 } from "path";
|
|
17516
17636
|
var API_OPERATION_ENTRYPOINTS = {
|
|
17517
17637
|
"dev-reset": ["src", "server", "maintenance", "dev-reset.ts"],
|
|
17518
17638
|
"db-backup": ["src", "server", "maintenance", "snapshot-backup.ts"],
|
|
@@ -17581,7 +17701,7 @@ function unsupportedRepositoryCliOption(argv2) {
|
|
|
17581
17701
|
return;
|
|
17582
17702
|
}
|
|
17583
17703
|
function manifestName(root) {
|
|
17584
|
-
const manifestPath =
|
|
17704
|
+
const manifestPath = join12(root, "package.json");
|
|
17585
17705
|
if (!existsSync11(manifestPath)) {
|
|
17586
17706
|
throw new Error(`No package.json exists at repository root ${root}.`);
|
|
17587
17707
|
}
|
|
@@ -17597,7 +17717,7 @@ function manifestName(root) {
|
|
|
17597
17717
|
return name;
|
|
17598
17718
|
}
|
|
17599
17719
|
function checkedEntrypoint(root, parts) {
|
|
17600
|
-
const candidate =
|
|
17720
|
+
const candidate = join12(root, ...parts);
|
|
17601
17721
|
if (!existsSync11(candidate) || !lstatSync9(candidate).isFile()) {
|
|
17602
17722
|
throw new Error(`The reviewed operation entrypoint is missing: ${candidate}`);
|
|
17603
17723
|
}
|
|
@@ -17625,7 +17745,7 @@ function resolveRepositoryOperation(operation, requestedRoot) {
|
|
|
17625
17745
|
};
|
|
17626
17746
|
}
|
|
17627
17747
|
if (name === "forgezero") {
|
|
17628
|
-
const apiRoot = realpathSync5(
|
|
17748
|
+
const apiRoot = realpathSync5(join12(root, "api"));
|
|
17629
17749
|
if (manifestName(apiRoot) !== "@forgezero/api") {
|
|
17630
17750
|
throw new Error(`${apiRoot} is not the ForgeZero API package.`);
|
|
17631
17751
|
}
|
|
@@ -17667,7 +17787,7 @@ function resolveRepositoryOperation(operation, requestedRoot) {
|
|
|
17667
17787
|
])
|
|
17668
17788
|
};
|
|
17669
17789
|
}
|
|
17670
|
-
const rehearsalRoot =
|
|
17790
|
+
const rehearsalRoot = join12(root, "tests", "rehearsals", "community-cluster-api");
|
|
17671
17791
|
return {
|
|
17672
17792
|
operation,
|
|
17673
17793
|
root,
|
|
@@ -17721,7 +17841,7 @@ function planAppBuild(input) {
|
|
|
17721
17841
|
|
|
17722
17842
|
// src/cli/index.ts
|
|
17723
17843
|
var DEFAULT_MODE = (THRESHOLD_MODES.find((mode) => mode.threshold === 1 && mode.total === 1) ?? THRESHOLD_MODES[0]).id;
|
|
17724
|
-
var PACKAGED_AGENT_BIN2 =
|
|
17844
|
+
var PACKAGED_AGENT_BIN2 = fileURLToPath4(new URL("./fz-agent.js", import.meta.url));
|
|
17725
17845
|
function parseOptions(argv2) {
|
|
17726
17846
|
const options = {
|
|
17727
17847
|
api: process.env.FZ_API ?? "http://localhost:8787",
|
|
@@ -17744,6 +17864,14 @@ function parseOptions(argv2) {
|
|
|
17744
17864
|
deploySoftware: [],
|
|
17745
17865
|
deployChannel: "production",
|
|
17746
17866
|
requireAttestation: false,
|
|
17867
|
+
deployInitCustomized: false,
|
|
17868
|
+
deployRuntime: "native",
|
|
17869
|
+
deployReplicas: 1,
|
|
17870
|
+
deployCpuCores: 1,
|
|
17871
|
+
deployMemoryMiB: 512,
|
|
17872
|
+
deployStorageGiB: 8,
|
|
17873
|
+
deploySharing: "exclusive",
|
|
17874
|
+
deployReuse: "require",
|
|
17747
17875
|
force: false,
|
|
17748
17876
|
noBrowser: false,
|
|
17749
17877
|
provider: "github",
|
|
@@ -17795,7 +17923,80 @@ function parseOptions(argv2) {
|
|
|
17795
17923
|
options.optionError = "--channel must be production or development.";
|
|
17796
17924
|
} else if (token === "--attestation")
|
|
17797
17925
|
options.requireAttestation = true;
|
|
17798
|
-
else if (token === "--
|
|
17926
|
+
else if (token === "--runtime") {
|
|
17927
|
+
const value = argv2[++index];
|
|
17928
|
+
options.deployInitCustomized = true;
|
|
17929
|
+
if (value === "native" || value === "containerd" || value === "kata-snp")
|
|
17930
|
+
options.deployRuntime = value;
|
|
17931
|
+
else
|
|
17932
|
+
options.optionError = "--runtime must be native, containerd, or kata-snp.";
|
|
17933
|
+
} else if (token === "--os") {
|
|
17934
|
+
const value = argv2[++index];
|
|
17935
|
+
options.deployInitCustomized = true;
|
|
17936
|
+
if (value === "ubuntu-24.04" || value === "ubuntu-26.04")
|
|
17937
|
+
options.deployOperatingSystem = value;
|
|
17938
|
+
else
|
|
17939
|
+
options.optionError = "--os must be ubuntu-24.04 or ubuntu-26.04.";
|
|
17940
|
+
} else if (token === "--isolation") {
|
|
17941
|
+
const value = argv2[++index];
|
|
17942
|
+
options.deployInitCustomized = true;
|
|
17943
|
+
if (value === "standard" || value === "sev-snp")
|
|
17944
|
+
options.deployIsolation = value;
|
|
17945
|
+
else
|
|
17946
|
+
options.optionError = "--isolation must be standard or sev-snp.";
|
|
17947
|
+
} else if (["--replicas", "--cpu-cores", "--memory-mib", "--storage-gib"].includes(token)) {
|
|
17948
|
+
const value = Number(argv2[++index] ?? "");
|
|
17949
|
+
options.deployInitCustomized = true;
|
|
17950
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
17951
|
+
options.optionError = `${token} must be a positive integer.`;
|
|
17952
|
+
else if (token === "--replicas")
|
|
17953
|
+
options.deployReplicas = value;
|
|
17954
|
+
else if (token === "--cpu-cores")
|
|
17955
|
+
options.deployCpuCores = value;
|
|
17956
|
+
else if (token === "--memory-mib")
|
|
17957
|
+
options.deployMemoryMiB = value;
|
|
17958
|
+
else
|
|
17959
|
+
options.deployStorageGiB = value;
|
|
17960
|
+
} else if (token === "--sharing") {
|
|
17961
|
+
const value = argv2[++index];
|
|
17962
|
+
options.deployInitCustomized = true;
|
|
17963
|
+
if (value === "exclusive" || value === "shared")
|
|
17964
|
+
options.deploySharing = value;
|
|
17965
|
+
else
|
|
17966
|
+
options.optionError = "--sharing must be exclusive or shared.";
|
|
17967
|
+
} else if (token === "--reuse") {
|
|
17968
|
+
const value = argv2[++index];
|
|
17969
|
+
options.deployInitCustomized = true;
|
|
17970
|
+
if (value === "require" || value === "prefer")
|
|
17971
|
+
options.deployReuse = value;
|
|
17972
|
+
else
|
|
17973
|
+
options.optionError = "--reuse must be require or prefer.";
|
|
17974
|
+
} else if (token === "--provision-plan") {
|
|
17975
|
+
options.deployProvisionPlan = argv2[++index];
|
|
17976
|
+
options.deployInitCustomized = true;
|
|
17977
|
+
} else if (token === "--provision-region") {
|
|
17978
|
+
options.deployProvisionRegion = argv2[++index];
|
|
17979
|
+
options.deployInitCustomized = true;
|
|
17980
|
+
} else if (token === "--provision-image") {
|
|
17981
|
+
options.deployProvisionImage = argv2[++index];
|
|
17982
|
+
options.deployInitCustomized = true;
|
|
17983
|
+
} else if (token === "--provision-environment") {
|
|
17984
|
+
options.deployProvisionEnvironment = argv2[++index];
|
|
17985
|
+
options.deployInitCustomized = true;
|
|
17986
|
+
} else if (token === "--provision-ownership") {
|
|
17987
|
+
const value = argv2[++index];
|
|
17988
|
+
options.deployInitCustomized = true;
|
|
17989
|
+
if (value === "platform" || value === "tenant-metal")
|
|
17990
|
+
options.deployProvisionOwnership = value;
|
|
17991
|
+
else
|
|
17992
|
+
options.optionError = "--provision-ownership must be platform or tenant-metal.";
|
|
17993
|
+
} else if (token === "--provision-metal") {
|
|
17994
|
+
options.deployProvisionMetal = argv2[++index];
|
|
17995
|
+
options.deployInitCustomized = true;
|
|
17996
|
+
} else if (token === "--max-monthly-spend-minor") {
|
|
17997
|
+
options.deployMaxMonthlySpendMinor = argv2[++index];
|
|
17998
|
+
options.deployInitCustomized = true;
|
|
17999
|
+
} else if (token === "--force")
|
|
17799
18000
|
options.force = true;
|
|
17800
18001
|
else if (token === "--browser")
|
|
17801
18002
|
options.noBrowser = false;
|
|
@@ -18631,10 +18832,10 @@ function interactiveGenesisGuests() {
|
|
|
18631
18832
|
function writeMetalOperatorFiles(directory) {
|
|
18632
18833
|
const output = genesisOutputDirectory(directory);
|
|
18633
18834
|
const config = interactiveMetalBootstrap();
|
|
18634
|
-
const metalConfig = writeBootstrapConfig(
|
|
18835
|
+
const metalConfig = writeBootstrapConfig(join13(output, "metal.json"), config);
|
|
18635
18836
|
const identity = operatorIdentityCoordinates();
|
|
18636
18837
|
const target = interactiveOperatorHop("Metal", { user: "root" });
|
|
18637
|
-
const remoteRequest = writeOperatorMetalBootstrapRequest(
|
|
18838
|
+
const remoteRequest = writeOperatorMetalBootstrapRequest(join13(output, "metal-remote.json"), {
|
|
18638
18839
|
kind: "metal-remote",
|
|
18639
18840
|
metalConfigFile: metalConfig,
|
|
18640
18841
|
target: { ...target, ...identity },
|
|
@@ -18652,7 +18853,7 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18652
18853
|
const metalHostname = readMetalBootstrapConfig(metalRequest.metalConfigFile, {
|
|
18653
18854
|
allowHistoricalRelease: true
|
|
18654
18855
|
}).metalHostname;
|
|
18655
|
-
const checkpointPath =
|
|
18856
|
+
const checkpointPath = join13(output, "cloudflare-handoff.json");
|
|
18656
18857
|
const template = interactiveBootstrap("platform", fleet, checkpointPath);
|
|
18657
18858
|
const region = {
|
|
18658
18859
|
label: bootstrapAnswer(`Region ${template.runtime.environment.nodeRegion} display label`),
|
|
@@ -18667,7 +18868,7 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18667
18868
|
cloudflareHandoffFile: cloudflareHostHandoffPath(checkpointPath, guest.name)
|
|
18668
18869
|
});
|
|
18669
18870
|
const realtimeEnabled = bootstrapBoolean("Configure existing Worker realtime fan-out?", "yes");
|
|
18670
|
-
const cloudflareConfig = writeBootstrapConfig(
|
|
18871
|
+
const cloudflareConfig = writeBootstrapConfig(join13(output, "cloudflare.json"), createCloudflareBootstrapDiscoveryCommandConfig({
|
|
18671
18872
|
checkpointPath,
|
|
18672
18873
|
discovery: {
|
|
18673
18874
|
zoneName: bootstrapAnswer("Cloudflare DNS zone name", "forgezero.net"),
|
|
@@ -18685,7 +18886,7 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18685
18886
|
tunnelName: bootstrapAnswer(`${guest.name} Tunnel name`, guest.name)
|
|
18686
18887
|
}))
|
|
18687
18888
|
}));
|
|
18688
|
-
const cloudflareAcceptance =
|
|
18889
|
+
const cloudflareAcceptance = join13(output, "cloudflare-acceptance.json");
|
|
18689
18890
|
const configs = platformGenesisBootstrapConfigs(template, fleet, nodes, metalHostname, region).map((config) => {
|
|
18690
18891
|
const path = `${output}/${config.computeReference}-platform.json`;
|
|
18691
18892
|
writeBootstrapConfig(path, config);
|
|
@@ -18694,7 +18895,7 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18694
18895
|
const requests = configs.map((platformConfigFile, index) => {
|
|
18695
18896
|
const guest = fleet[index];
|
|
18696
18897
|
const host = guestHostKeys.nodes.find(({ name }) => name === guest.name);
|
|
18697
|
-
return writeOperatorPlatformBootstrapRequest(
|
|
18898
|
+
return writeOperatorPlatformBootstrapRequest(join13(output, `${guest.name}-remote.json`), {
|
|
18698
18899
|
kind: "platform-remote",
|
|
18699
18900
|
platformConfigFile,
|
|
18700
18901
|
target: {
|
|
@@ -18715,7 +18916,7 @@ function writePlatformGenesisFleet(directory) {
|
|
|
18715
18916
|
}
|
|
18716
18917
|
});
|
|
18717
18918
|
});
|
|
18718
|
-
const fleetRequest = writeOperatorPlatformBootstrapFleetRequest(
|
|
18919
|
+
const fleetRequest = writeOperatorPlatformBootstrapFleetRequest(join13(output, "platform-fleet-remote.json"), requests, { configFile: cloudflareConfig, acceptanceFile: cloudflareAcceptance });
|
|
18719
18920
|
return { configs, requests, fleetRequest, cloudflareConfig, cloudflareAcceptance };
|
|
18720
18921
|
}
|
|
18721
18922
|
function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint) {
|
|
@@ -19454,7 +19655,7 @@ async function cmdDeploy(options, args) {
|
|
|
19454
19655
|
if (options.optionError)
|
|
19455
19656
|
throw new Error(options.optionError);
|
|
19456
19657
|
if (operation === "init") {
|
|
19457
|
-
if (options.deploySoftware.length > 0 || options.deployProfile !== "app" || options.requireAttestation) {
|
|
19658
|
+
if (!options.deployInitCustomized && (options.deploySoftware.length > 0 || options.deployProfile !== "app" || options.requireAttestation)) {
|
|
19458
19659
|
const created = initializeDeployFile(options.projectRoot, {
|
|
19459
19660
|
name: options.projectName,
|
|
19460
19661
|
profile: options.deployProfile,
|
|
@@ -19469,7 +19670,41 @@ async function cmdDeploy(options, args) {
|
|
|
19469
19670
|
out.ok(`Initialized legacy .fz/deploy.json (${created.summary.digest}).`);
|
|
19470
19671
|
return 0;
|
|
19471
19672
|
}
|
|
19472
|
-
|
|
19673
|
+
if (options.deployInitCustomized && (options.deploySoftware.length > 0 || options.requireAttestation)) {
|
|
19674
|
+
throw new Error("typed runtime initialization selects its tested software and attestation from --runtime/--isolation; do not combine --software or --attestation");
|
|
19675
|
+
}
|
|
19676
|
+
initializeTypeScriptDeployment(options.projectRoot, {
|
|
19677
|
+
name: options.projectName ?? basename3(resolve12(options.projectRoot)),
|
|
19678
|
+
force: options.force,
|
|
19679
|
+
runtime: options.deployRuntime,
|
|
19680
|
+
operatingSystem: options.deployOperatingSystem,
|
|
19681
|
+
isolation: options.deployIsolation,
|
|
19682
|
+
replicas: options.deployReplicas,
|
|
19683
|
+
cpuCores: options.deployCpuCores,
|
|
19684
|
+
memoryMiB: options.deployMemoryMiB,
|
|
19685
|
+
storageGiB: options.deployStorageGiB,
|
|
19686
|
+
sharing: options.deploySharing,
|
|
19687
|
+
reuse: options.deployReuse,
|
|
19688
|
+
profile: options.deployProfile,
|
|
19689
|
+
...[
|
|
19690
|
+
options.deployProvisionPlan,
|
|
19691
|
+
options.deployProvisionRegion,
|
|
19692
|
+
options.deployProvisionImage,
|
|
19693
|
+
options.deployProvisionEnvironment,
|
|
19694
|
+
options.deployProvisionOwnership,
|
|
19695
|
+
options.deployMaxMonthlySpendMinor
|
|
19696
|
+
].some((value) => value !== undefined) ? {
|
|
19697
|
+
provisioning: {
|
|
19698
|
+
planKey: options.deployProvisionPlan ?? "",
|
|
19699
|
+
regionKey: options.deployProvisionRegion ?? "",
|
|
19700
|
+
imageKey: options.deployProvisionImage ?? "",
|
|
19701
|
+
environmentKey: options.deployProvisionEnvironment ?? "",
|
|
19702
|
+
ownership: options.deployProvisionOwnership ?? "platform",
|
|
19703
|
+
...options.deployProvisionMetal ? { metalHostname: options.deployProvisionMetal } : {},
|
|
19704
|
+
maxMonthlySpendMinor: options.deployMaxMonthlySpendMinor ?? ""
|
|
19705
|
+
}
|
|
19706
|
+
} : {}
|
|
19707
|
+
});
|
|
19473
19708
|
const compiled = await compileDeploymentProject(options.projectRoot);
|
|
19474
19709
|
if (options.json)
|
|
19475
19710
|
out.line(JSON.stringify({ source: compiled.source, output: compiled.output, digest: compiled.digest }, null, 2));
|
|
@@ -19721,7 +19956,9 @@ function usage() {
|
|
|
19721
19956
|
fz project check Fail when truth sources or generated adapters drift
|
|
19722
19957
|
fz app build Build the static App for a numbered local/development/
|
|
19723
19958
|
production choice, --profile, or an explicit --api origin
|
|
19724
|
-
|
|
19959
|
+
fz deploy init Create forgezero.deploy.ts and its fail-safe canonical plan;
|
|
19960
|
+
select native, containerd runc, or Kata QEMU SNP and
|
|
19961
|
+
explicit per-compute resources with the options below
|
|
19725
19962
|
fz deploy compile Compile TypeScript into .fz/deploy.plan.json
|
|
19726
19963
|
fz deploy check Validate source, plan, actions, providers and topology
|
|
19727
19964
|
fz deploy sync Prove readiness and print the Git synchronization rule
|
|
@@ -19792,6 +20029,19 @@ function usage() {
|
|
|
19792
20029
|
--software <key@ver> Initial tested software coordinate; repeatable
|
|
19793
20030
|
--channel <name> Catalog view: production or development (shows testing)
|
|
19794
20031
|
--attestation Require hardware attestation for every deploy step
|
|
20032
|
+
--runtime <mode> native, containerd, or kata-snp
|
|
20033
|
+
--os <image> ubuntu-24.04 or ubuntu-26.04 (SNP requires 26.04)
|
|
20034
|
+
--isolation <mode> standard or sev-snp
|
|
20035
|
+
--replicas <n> Exact initial compute cardinality (default 1)
|
|
20036
|
+
--cpu-cores <n> Reserved CPU cores per compute
|
|
20037
|
+
--memory-mib <n> Reserved memory MiB per compute
|
|
20038
|
+
--storage-gib <n> Reserved storage GiB per compute
|
|
20039
|
+
--sharing <mode> exclusive or shared (native requires exclusive)
|
|
20040
|
+
--reuse <mode> require existing capacity or prefer reuse then approved provisioning
|
|
20041
|
+
--provision-plan <key> --provision-region <key> --provision-image <key>
|
|
20042
|
+
--provision-environment <key> --provision-ownership <platform|tenant-metal>
|
|
20043
|
+
--provision-metal <host> Required only for tenant-metal ownership
|
|
20044
|
+
--max-monthly-spend-minor <n> Hard approved scale-out spend ceiling
|
|
19795
20045
|
--force Init may replace an existing generated target
|
|
19796
20046
|
|
|
19797
20047
|
REMOTE DEPLOY OPTIONS
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
// src/version.ts
|
|
369
|
-
var VERSION = "0.1.
|
|
369
|
+
var VERSION = "0.1.84";
|
|
370
370
|
|
|
371
371
|
// src/otel-collector.ts
|
|
372
372
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -2647,7 +2647,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2647
2647
|
}
|
|
2648
2648
|
|
|
2649
2649
|
// src/version.ts
|
|
2650
|
-
var VERSION3 = "0.1.
|
|
2650
|
+
var VERSION3 = "0.1.84";
|
|
2651
2651
|
|
|
2652
2652
|
// src/egress-policy.ts
|
|
2653
2653
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/provision.js
CHANGED
|
@@ -2647,7 +2647,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2647
2647
|
}
|
|
2648
2648
|
|
|
2649
2649
|
// src/version.ts
|
|
2650
|
-
var VERSION3 = "0.1.
|
|
2650
|
+
var VERSION3 = "0.1.84";
|
|
2651
2651
|
|
|
2652
2652
|
// src/egress-policy.ts
|
|
2653
2653
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** One package version shared by both public binaries. Pinned to package.json by tests. */
|
|
2
|
-
export declare const VERSION = "0.1.
|
|
2
|
+
export declare const VERSION = "0.1.84";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgezero/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.84",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "tsc --noEmit",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"@noble/post-quantum": "^0.6.1"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@forgezero/runtime": "0.1.
|
|
20
|
-
"@forgezero/vault": "0.1.
|
|
19
|
+
"@forgezero/runtime": "0.1.12",
|
|
20
|
+
"@forgezero/vault": "0.1.17",
|
|
21
21
|
"@noble/curves": "2.2.0",
|
|
22
22
|
"@noble/hashes": "2.2.0",
|
|
23
23
|
"@noble/post-quantum": "0.6.1",
|