@forgezero/agent 0.1.28 → 0.1.30
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 +24 -4
- package/dist/agent-heartbeat.js +1 -1
- package/dist/cli/agent-install.d.ts +1 -1
- package/dist/definition.d.ts +14 -8
- package/dist/definition.js +58 -23
- package/dist/deployment-pull.d.ts +3 -3
- package/dist/deployment.d.ts +5 -5
- package/dist/fz-agent.js +80 -48
- package/dist/fz.js +393 -31
- package/dist/index.d.ts +2 -2
- package/dist/metal-helper-socket.js +2 -3
- package/dist/metal-provision.js +2 -3
- package/dist/project-context.d.ts +37 -0
- package/dist/project-context.js +266 -0
- package/dist/provision.d.ts +9 -1
- package/dist/provision.js +85 -17
- package/dist/software-helper.js +17 -2
- package/dist/software.d.ts +24 -2
- package/dist/software.js +20 -3
- package/dist/version.d.ts +1 -1
- package/package.json +6 -2
package/dist/fz.js
CHANGED
|
@@ -4871,8 +4871,8 @@ async function spawnWith(command, env, report = () => {}) {
|
|
|
4871
4871
|
|
|
4872
4872
|
// src/cli/index.ts
|
|
4873
4873
|
init_dist();
|
|
4874
|
-
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
4875
|
-
import { dirname } from "path";
|
|
4874
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4875
|
+
import { dirname as dirname2 } from "path";
|
|
4876
4876
|
import { fileURLToPath } from "url";
|
|
4877
4877
|
|
|
4878
4878
|
// src/agent-update.ts
|
|
@@ -4886,7 +4886,7 @@ var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-
|
|
|
4886
4886
|
var MAX_REQUEST_BYTES = 8 * 1024;
|
|
4887
4887
|
|
|
4888
4888
|
// src/version.ts
|
|
4889
|
-
var VERSION2 = "0.1.
|
|
4889
|
+
var VERSION2 = "0.1.30";
|
|
4890
4890
|
|
|
4891
4891
|
// src/software.ts
|
|
4892
4892
|
var BUN_INSTALLER_SHA256 = "bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd";
|
|
@@ -4973,6 +4973,7 @@ var VAULT_GROUP = "forgezero-vault";
|
|
|
4973
4973
|
var LIFECYCLE_GROUP = "forgezero-lifecycle";
|
|
4974
4974
|
var DEPLOYMENT_RUNNER_UNIT_PATH = "/etc/systemd/system/forgezero-deploy-runner.service";
|
|
4975
4975
|
var AGENT_SOCKET_UNIT_PATH = "/etc/systemd/system/forgezero-agent.socket";
|
|
4976
|
+
var AGENT_SOCKET_PROXY_UNIT_PATH = "/etc/systemd/system/forgezero-agent-proxy.service";
|
|
4976
4977
|
var DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
|
|
4977
4978
|
var ENROLMENT_UNIT_PATH = "/etc/systemd/system/forgezero-agent-enrol.service";
|
|
4978
4979
|
var LIFECYCLE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-lifecycle-helper.service";
|
|
@@ -5062,17 +5063,55 @@ SocketGroup=${VAULT_GROUP}
|
|
|
5062
5063
|
SocketMode=0660
|
|
5063
5064
|
DirectoryMode=0750
|
|
5064
5065
|
RemoveOnStop=true
|
|
5065
|
-
Service=forgezero-agent.service
|
|
5066
|
+
Service=forgezero-agent-proxy.service
|
|
5066
5067
|
|
|
5067
5068
|
[Install]
|
|
5068
5069
|
WantedBy=sockets.target
|
|
5069
5070
|
`;
|
|
5070
5071
|
}
|
|
5072
|
+
function agentSocketProxyUnit(options) {
|
|
5073
|
+
const backend = agentBackendSocketPath(options.socketPath);
|
|
5074
|
+
const user = options.user ?? "forgezero";
|
|
5075
|
+
return `[Unit]
|
|
5076
|
+
Description=ForgeZero application Vault socket proxy
|
|
5077
|
+
Documentation=https://www.forgezero.net/docs/agent
|
|
5078
|
+
Requires=forgezero-agent.service
|
|
5079
|
+
After=forgezero-agent.service
|
|
5080
|
+
|
|
5081
|
+
[Service]
|
|
5082
|
+
User=${user}
|
|
5083
|
+
Group=${VAULT_GROUP}
|
|
5084
|
+
ExecStart=/usr/lib/systemd/systemd-socket-proxyd ${backend}
|
|
5085
|
+
NoNewPrivileges=true
|
|
5086
|
+
PrivateTmp=true
|
|
5087
|
+
ProtectSystem=strict
|
|
5088
|
+
ProtectHome=true
|
|
5089
|
+
ProtectKernelTunables=true
|
|
5090
|
+
ProtectKernelModules=true
|
|
5091
|
+
ProtectControlGroups=true
|
|
5092
|
+
RestrictSUIDSGID=true
|
|
5093
|
+
RestrictRealtime=true
|
|
5094
|
+
MemoryDenyWriteExecute=true
|
|
5095
|
+
LockPersonality=true
|
|
5096
|
+
RestrictAddressFamilies=AF_UNIX
|
|
5097
|
+
`;
|
|
5098
|
+
}
|
|
5099
|
+
function agentBackendSocketPath(publicSocketPath) {
|
|
5100
|
+
const socket = systemdPath(publicSocketPath, "agent socket");
|
|
5101
|
+
const backend = `${socket}.backend`;
|
|
5102
|
+
if (Buffer.byteLength(backend) > 100)
|
|
5103
|
+
throw new Error("agent socket path is too long for a Unix socket");
|
|
5104
|
+
return backend;
|
|
5105
|
+
}
|
|
5071
5106
|
var systemdPath = (value, label) => {
|
|
5072
5107
|
if (!value || !/^\/[A-Za-z0-9._@/-]+$/.test(value))
|
|
5073
5108
|
throw new Error(`invalid ${label} path`);
|
|
5074
5109
|
return value;
|
|
5075
5110
|
};
|
|
5111
|
+
var awaitSocketCommand = (path) => {
|
|
5112
|
+
const socket = systemdPath(path, "readiness socket");
|
|
5113
|
+
return `for attempt in $(seq 1 100); do test -S ${socket} && exit 0; sleep 0.1; done; exit 1`;
|
|
5114
|
+
};
|
|
5076
5115
|
var validNodeHostname = (value) => !value || value.length <= 253 && value === value.toLowerCase() && value.split(".").length >= 3 && value.split(".").every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label));
|
|
5077
5116
|
function warpConfigUnit(options) {
|
|
5078
5117
|
if (!options.warpOrganization || !/^[a-z0-9][a-z0-9-]{0,62}$/i.test(options.warpOrganization)) {
|
|
@@ -5311,7 +5350,7 @@ function agentUnit(options) {
|
|
|
5311
5350
|
}
|
|
5312
5351
|
}
|
|
5313
5352
|
const environment = [
|
|
5314
|
-
`FZ_SOCKET_PATH=${options.socketPath}`,
|
|
5353
|
+
`FZ_SOCKET_PATH=${agentBackendSocketPath(options.socketPath)}`,
|
|
5315
5354
|
`FZ_CONTROL_SOCKET=${controlSocketPath}`,
|
|
5316
5355
|
`FZ_SEED_CREDENTIAL=agent-seed`,
|
|
5317
5356
|
`FZ_AGENT_MODE=${options.mode}`,
|
|
@@ -5324,7 +5363,7 @@ function agentUnit(options) {
|
|
|
5324
5363
|
options.gitPublicKeyPath ? `FZ_GIT_PUBLIC_KEY_FILE=${options.gitPublicKeyPath}` : null,
|
|
5325
5364
|
options.repository ? `FZ_DEPLOY_REPO=${options.repository}` : null,
|
|
5326
5365
|
options.branch ? `FZ_DEPLOY_BRANCH=${options.branch}` : null,
|
|
5327
|
-
options.
|
|
5366
|
+
options.profile ? `FZ_DEPLOY_PROFILE=${options.profile}` : null,
|
|
5328
5367
|
options.repository && options.branch ? `FZ_DEPLOY_KEY=${options.project ?? "platform"}:${options.environment ?? "production"}` : null,
|
|
5329
5368
|
deploymentEnabled ? `FZ_DEPLOY_ROOT=${deployRoot}` : null,
|
|
5330
5369
|
deploymentEnabled ? `FZ_DEPLOY_RUNNER_SOCKET=${DEPLOYMENT_RUNNER_SOCKET}` : null,
|
|
@@ -5391,7 +5430,6 @@ Type=simple
|
|
|
5391
5430
|
User=${user}
|
|
5392
5431
|
Group=${VAULT_GROUP}
|
|
5393
5432
|
${deploymentGroup}
|
|
5394
|
-
Sockets=forgezero-agent.socket
|
|
5395
5433
|
LoadCredentialEncrypted=agent-seed:${seedCredentialPath}
|
|
5396
5434
|
${gitCredential}${projectCredentials}${projectCredentials ? `
|
|
5397
5435
|
` : ""}${snpPrepare}ExecStart=${bin}
|
|
@@ -5479,6 +5517,7 @@ function planProvision(options) {
|
|
|
5479
5517
|
unit: agentUnit({ ...options, mode, lifecycleProfilePath, lifecycleHelperSocketPath }),
|
|
5480
5518
|
auxiliaryUnits: [
|
|
5481
5519
|
{ path: AGENT_SOCKET_UNIT_PATH, unit: agentSocketUnit(options) },
|
|
5520
|
+
{ path: AGENT_SOCKET_PROXY_UNIT_PATH, unit: agentSocketProxyUnit(options) },
|
|
5482
5521
|
{ path: AGENT_UPDATE_HELPER_UNIT_PATH, unit: agentUpdateHelperUnit(options) },
|
|
5483
5522
|
...deploymentEnabled ? [
|
|
5484
5523
|
{ path: DEPLOYMENT_RUNNER_UNIT_PATH, unit: deploymentRunnerUnit(options) },
|
|
@@ -5554,6 +5593,10 @@ function planProvision(options) {
|
|
|
5554
5593
|
label: "credential directory",
|
|
5555
5594
|
command: `install -d -o root -g root -m 0700 ${credentialDir}`
|
|
5556
5595
|
},
|
|
5596
|
+
{
|
|
5597
|
+
label: "Agent state directory",
|
|
5598
|
+
command: "install -d -o root -g root -m 0750 /var/lib/forgezero"
|
|
5599
|
+
},
|
|
5557
5600
|
{
|
|
5558
5601
|
label: "encrypted node identity",
|
|
5559
5602
|
command: `test -s ${seedCredentialPath} || { ` + `openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\\n' | ` + `systemd-creds encrypt --name=agent-seed - ${seedCredentialPath}; ` + `chmod 0400 ${seedCredentialPath}; }`
|
|
@@ -5588,8 +5631,8 @@ function planProvision(options) {
|
|
|
5588
5631
|
command: "systemctl disable --now forgezero-deploy-runner.socket 2>/dev/null || true; rm -f /etc/systemd/system/forgezero-deploy-runner.socket; systemctl daemon-reload"
|
|
5589
5632
|
}] : [],
|
|
5590
5633
|
{
|
|
5591
|
-
label: "enable and
|
|
5592
|
-
command: `systemctl enable
|
|
5634
|
+
label: "enable and converge services",
|
|
5635
|
+
command: `systemctl enable ${[
|
|
5593
5636
|
"forgezero-agent.socket",
|
|
5594
5637
|
"forgezero-agent-update-helper.service",
|
|
5595
5638
|
...deploymentEnabled ? ["forgezero-deploy-runner.service", "forgezero-software-helper.service"] : [],
|
|
@@ -5597,25 +5640,32 @@ function planProvision(options) {
|
|
|
5597
5640
|
...warpEnabled ? ["forgezero-warp-config.service", "warp-svc.service"] : [],
|
|
5598
5641
|
...enrolmentEnabled ? ["forgezero-agent-enrol.service"] : [],
|
|
5599
5642
|
"forgezero-agent.service"
|
|
5600
|
-
].join(" ")}
|
|
5643
|
+
].join(" ")}; systemctl reset-failed forgezero-agent.service || true; systemctl restart ${[
|
|
5644
|
+
"forgezero-agent-update-helper.service",
|
|
5645
|
+
...deploymentEnabled ? ["forgezero-deploy-runner.service", "forgezero-software-helper.service"] : [],
|
|
5646
|
+
...lifecycleEnabled ? ["forgezero-lifecycle-helper.service"] : [],
|
|
5647
|
+
...warpEnabled ? ["forgezero-warp-config.service", "warp-svc.service"] : [],
|
|
5648
|
+
...enrolmentEnabled ? ["forgezero-agent-enrol.service"] : []
|
|
5649
|
+
].join(" ")}; systemctl restart forgezero-agent.socket; systemctl reset-failed forgezero-agent.service || true; systemctl restart forgezero-agent.service`
|
|
5601
5650
|
},
|
|
5602
5651
|
...enrolmentEnabled ? [{
|
|
5603
5652
|
label: "prove the compute binding is durable",
|
|
5604
5653
|
command: `test -s ${enrolStatePath}`
|
|
5605
5654
|
}] : [],
|
|
5606
5655
|
{ label: "prove it is running", command: "systemctl is-active forgezero-agent.service" },
|
|
5607
|
-
{ label: "prove the
|
|
5608
|
-
{ label: "prove the Agent
|
|
5656
|
+
{ label: "prove the public Vault socket exists", command: awaitSocketCommand(options.socketPath) },
|
|
5657
|
+
{ label: "prove the Agent Vault backend exists", command: awaitSocketCommand(agentBackendSocketPath(options.socketPath)) },
|
|
5658
|
+
{ label: "prove the Agent update helper exists", command: awaitSocketCommand(DEFAULT_AGENT_UPDATE_SOCKET) },
|
|
5609
5659
|
...deploymentEnabled ? [{
|
|
5610
5660
|
label: "prove the deployment runner socket exists",
|
|
5611
|
-
command:
|
|
5661
|
+
command: awaitSocketCommand(DEPLOYMENT_RUNNER_SOCKET)
|
|
5612
5662
|
}, {
|
|
5613
5663
|
label: "prove the software strategy helper socket exists",
|
|
5614
|
-
command:
|
|
5664
|
+
command: awaitSocketCommand(DEFAULT_SOFTWARE_HELPER_SOCKET)
|
|
5615
5665
|
}] : [],
|
|
5616
5666
|
...lifecycleEnabled ? [{
|
|
5617
5667
|
label: "prove the lifecycle helper socket exists",
|
|
5618
|
-
command:
|
|
5668
|
+
command: awaitSocketCommand(lifecycleHelperSocketPath)
|
|
5619
5669
|
}] : [],
|
|
5620
5670
|
...warpEnabled ? [{
|
|
5621
5671
|
label: "prove Cloudflare WARP is connected",
|
|
@@ -5623,7 +5673,7 @@ function planProvision(options) {
|
|
|
5623
5673
|
}] : [],
|
|
5624
5674
|
...options.repository ? [{
|
|
5625
5675
|
label: "prove the deployment control socket exists",
|
|
5626
|
-
command:
|
|
5676
|
+
command: awaitSocketCommand(options.controlSocketPath ?? "/run/forgezero/control.sock")
|
|
5627
5677
|
}] : []
|
|
5628
5678
|
]
|
|
5629
5679
|
};
|
|
@@ -9225,6 +9275,261 @@ async function resolveIdentity(selector, socketPath) {
|
|
|
9225
9275
|
return chosen;
|
|
9226
9276
|
}
|
|
9227
9277
|
|
|
9278
|
+
// src/project-context.ts
|
|
9279
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
9280
|
+
import { dirname, join as join2, resolve } from "path";
|
|
9281
|
+
var PROJECT_CONTEXT_VERSION = 1;
|
|
9282
|
+
var GENERATED = "<!-- Generated by @forgezero/agent project context. Edit .forgezero/project.json, then run `fz project sync`. -->";
|
|
9283
|
+
|
|
9284
|
+
class ProjectContextError extends Error {
|
|
9285
|
+
constructor(message) {
|
|
9286
|
+
super(message);
|
|
9287
|
+
this.name = "ProjectContextError";
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
var text = (value, where) => {
|
|
9291
|
+
if (typeof value !== "string" || !value.trim() || /[\r\0]/.test(value)) {
|
|
9292
|
+
throw new ProjectContextError(`${where} must be non-empty text.`);
|
|
9293
|
+
}
|
|
9294
|
+
return value.trim();
|
|
9295
|
+
};
|
|
9296
|
+
var relativePath = (value, where) => {
|
|
9297
|
+
const path = text(value, where);
|
|
9298
|
+
if (path.startsWith("/") || path.split("/").includes("..")) {
|
|
9299
|
+
throw new ProjectContextError(`${where} must stay inside the repository.`);
|
|
9300
|
+
}
|
|
9301
|
+
return path.replace(/^\.\//, "");
|
|
9302
|
+
};
|
|
9303
|
+
var stringList = (value, where, paths = false) => {
|
|
9304
|
+
if (!Array.isArray(value) || value.length > 128) {
|
|
9305
|
+
throw new ProjectContextError(`${where} must be an array of at most 128 entries.`);
|
|
9306
|
+
}
|
|
9307
|
+
const items = value.map((item, index) => paths ? relativePath(item, `${where}[${index}]`) : text(item, `${where}[${index}]`));
|
|
9308
|
+
if (new Set(items).size !== items.length)
|
|
9309
|
+
throw new ProjectContextError(`${where} must not contain duplicates.`);
|
|
9310
|
+
return items;
|
|
9311
|
+
};
|
|
9312
|
+
function parseProjectContext(value) {
|
|
9313
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
9314
|
+
throw new ProjectContextError("project context must be an object.");
|
|
9315
|
+
}
|
|
9316
|
+
const row = value;
|
|
9317
|
+
const allowed = ["schemaVersion", "name", "purpose", "truth", "readFirst", "verify", "rules", "nonAuthoritative"];
|
|
9318
|
+
const unknown = Object.keys(row).filter((key) => !allowed.includes(key));
|
|
9319
|
+
if (unknown.length)
|
|
9320
|
+
throw new ProjectContextError(`project context contains unknown field(s): ${unknown.join(", ")}.`);
|
|
9321
|
+
if (row.schemaVersion !== PROJECT_CONTEXT_VERSION) {
|
|
9322
|
+
throw new ProjectContextError(`project context schemaVersion must be ${PROJECT_CONTEXT_VERSION}.`);
|
|
9323
|
+
}
|
|
9324
|
+
if (!Array.isArray(row.truth) || row.truth.length === 0 || row.truth.length > 64) {
|
|
9325
|
+
throw new ProjectContextError("project context truth must contain from 1 to 64 sources.");
|
|
9326
|
+
}
|
|
9327
|
+
const truth = row.truth.map((item, index) => {
|
|
9328
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
9329
|
+
throw new ProjectContextError(`truth[${index}] must be an object.`);
|
|
9330
|
+
}
|
|
9331
|
+
const source = item;
|
|
9332
|
+
if (Object.keys(source).some((key) => !["area", "path", "description"].includes(key))) {
|
|
9333
|
+
throw new ProjectContextError(`truth[${index}] contains an unknown field.`);
|
|
9334
|
+
}
|
|
9335
|
+
return {
|
|
9336
|
+
area: text(source.area, `truth[${index}].area`),
|
|
9337
|
+
path: relativePath(source.path, `truth[${index}].path`),
|
|
9338
|
+
description: text(source.description, `truth[${index}].description`)
|
|
9339
|
+
};
|
|
9340
|
+
});
|
|
9341
|
+
const areas = truth.map((source) => source.area);
|
|
9342
|
+
if (new Set(areas).size !== areas.length)
|
|
9343
|
+
throw new ProjectContextError("project context truth areas must be unique.");
|
|
9344
|
+
return {
|
|
9345
|
+
schemaVersion: PROJECT_CONTEXT_VERSION,
|
|
9346
|
+
name: text(row.name, "project context name"),
|
|
9347
|
+
purpose: text(row.purpose, "project context purpose"),
|
|
9348
|
+
truth,
|
|
9349
|
+
readFirst: stringList(row.readFirst, "project context readFirst", true),
|
|
9350
|
+
verify: stringList(row.verify, "project context verify"),
|
|
9351
|
+
rules: stringList(row.rules, "project context rules"),
|
|
9352
|
+
nonAuthoritative: stringList(row.nonAuthoritative, "project context nonAuthoritative", true)
|
|
9353
|
+
};
|
|
9354
|
+
}
|
|
9355
|
+
function defaultProjectContext(root = process.cwd()) {
|
|
9356
|
+
let name = root.split("/").filter(Boolean).at(-1) ?? "project";
|
|
9357
|
+
let verify = ["npm test"];
|
|
9358
|
+
const manifestPath = join2(root, "package.json");
|
|
9359
|
+
if (existsSync(manifestPath)) {
|
|
9360
|
+
try {
|
|
9361
|
+
const pkg = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
9362
|
+
name = pkg.name ?? name;
|
|
9363
|
+
const runner = existsSync(join2(root, "bun.lock")) ? "bun run" : "npm run";
|
|
9364
|
+
verify = ["check", "test", "build"].filter((script) => pkg.scripts?.[script]).map((script) => `${runner} ${script}`);
|
|
9365
|
+
if (verify.length === 0)
|
|
9366
|
+
verify = [existsSync(join2(root, "bun.lock")) ? "bun test" : "npm test"];
|
|
9367
|
+
} catch {}
|
|
9368
|
+
}
|
|
9369
|
+
return {
|
|
9370
|
+
schemaVersion: PROJECT_CONTEXT_VERSION,
|
|
9371
|
+
name,
|
|
9372
|
+
purpose: "Describe the product outcome here; implementation details belong in the truth sources below.",
|
|
9373
|
+
truth: [
|
|
9374
|
+
{ area: "architecture", path: "docs/architecture.md", description: "Current system boundaries and decisions." },
|
|
9375
|
+
{ area: "progress", path: "docs/progress.md", description: "Evidence-backed delivery state and next work." }
|
|
9376
|
+
],
|
|
9377
|
+
readFirst: [".forgezero/PROJECT.md"],
|
|
9378
|
+
verify,
|
|
9379
|
+
rules: [
|
|
9380
|
+
"Inspect the current worktree before editing and preserve unrelated changes.",
|
|
9381
|
+
"Update a truth source instead of copying architecture or progress into another document.",
|
|
9382
|
+
"Never report a feature as complete without running its declared verification."
|
|
9383
|
+
],
|
|
9384
|
+
nonAuthoritative: ["audit/"]
|
|
9385
|
+
};
|
|
9386
|
+
}
|
|
9387
|
+
function renderProjectContext(manifest) {
|
|
9388
|
+
const truth = manifest.truth.map((source) => `| ${source.area} | \`${source.path}\` | ${source.description} |`).join(`
|
|
9389
|
+
`);
|
|
9390
|
+
return `${GENERATED}
|
|
9391
|
+
# ${manifest.name} \u2014 project context
|
|
9392
|
+
|
|
9393
|
+
${manifest.purpose}
|
|
9394
|
+
|
|
9395
|
+
## Read first
|
|
9396
|
+
|
|
9397
|
+
${manifest.readFirst.map((path) => `- \`${path}\``).join(`
|
|
9398
|
+
`) || "- No additional entry points."}
|
|
9399
|
+
|
|
9400
|
+
## Sources of truth
|
|
9401
|
+
|
|
9402
|
+
| Area | Path | Authority |
|
|
9403
|
+
|---|---|---|
|
|
9404
|
+
${truth}
|
|
9405
|
+
|
|
9406
|
+
If two files disagree, the file named in this table wins. Fix or regenerate the
|
|
9407
|
+
other file in the same change. Conversation memory, audit snapshots and generated
|
|
9408
|
+
output never override repository truth.
|
|
9409
|
+
|
|
9410
|
+
## Project rules
|
|
9411
|
+
|
|
9412
|
+
${manifest.rules.map((rule) => `- ${rule}`).join(`
|
|
9413
|
+
`) || "- No additional project rules."}
|
|
9414
|
+
|
|
9415
|
+
## Verification
|
|
9416
|
+
|
|
9417
|
+
${manifest.verify.map((command) => `- \`${command}\``).join(`
|
|
9418
|
+
`) || "- No verification command declared."}
|
|
9419
|
+
|
|
9420
|
+
## Non-authoritative material
|
|
9421
|
+
|
|
9422
|
+
${manifest.nonAuthoritative.map((path) => `- \`${path}\``).join(`
|
|
9423
|
+
`) || "- None declared."}
|
|
9424
|
+
|
|
9425
|
+
Tools, skills and AI vendors may change. They are execution aids, not memory.
|
|
9426
|
+
Persist every accepted decision and status change in the source of truth that
|
|
9427
|
+
owns it, then run \`fz project check\` before handoff.
|
|
9428
|
+
`;
|
|
9429
|
+
}
|
|
9430
|
+
var adapter = (name) => `${GENERATED}
|
|
9431
|
+
# ${name} project instructions
|
|
9432
|
+
|
|
9433
|
+
Read \`.forgezero/PROJECT.md\` completely before acting. It is generated from
|
|
9434
|
+
\`.forgezero/project.json\`, the vendor-neutral project context. Follow every
|
|
9435
|
+
source of truth and verification command it names.
|
|
9436
|
+
|
|
9437
|
+
Do not treat this adapter, conversation memory, an audit report, generated
|
|
9438
|
+
output, a tool, or a skill as architectural authority. When work changes an
|
|
9439
|
+
accepted decision or delivery state, update the named Git source in the same
|
|
9440
|
+
change and run \`fz project check\`.
|
|
9441
|
+
`;
|
|
9442
|
+
function projectContextFiles(manifestInput) {
|
|
9443
|
+
const manifest = parseProjectContext(manifestInput);
|
|
9444
|
+
return [
|
|
9445
|
+
{ path: ".forgezero/PROJECT.md", content: renderProjectContext(manifest) },
|
|
9446
|
+
{ path: "AGENTS.md", content: adapter("AI agent") },
|
|
9447
|
+
{ path: "CLAUDE.md", content: adapter("Claude") },
|
|
9448
|
+
{ path: "GEMINI.md", content: adapter("Gemini") },
|
|
9449
|
+
{ path: ".github/copilot-instructions.md", content: adapter("GitHub Copilot") },
|
|
9450
|
+
{ path: ".cursor/rules/project-context.mdc", content: `${GENERATED}
|
|
9451
|
+
---
|
|
9452
|
+
description: Repository source-of-truth contract
|
|
9453
|
+
alwaysApply: true
|
|
9454
|
+
---
|
|
9455
|
+
|
|
9456
|
+
${adapter("Cursor").replace(`${GENERATED}
|
|
9457
|
+
`, "")}` }
|
|
9458
|
+
];
|
|
9459
|
+
}
|
|
9460
|
+
var atomicWrite = (path, content) => {
|
|
9461
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
9462
|
+
const next = `${path}.${process.pid}.next`;
|
|
9463
|
+
writeFileSync(next, content, { mode: 420 });
|
|
9464
|
+
renameSync(next, path);
|
|
9465
|
+
};
|
|
9466
|
+
function initializeProjectContext(rootInput, manifestInput = defaultProjectContext(rootInput), options = {}) {
|
|
9467
|
+
const root = resolve(rootInput);
|
|
9468
|
+
const manifest = parseProjectContext(manifestInput);
|
|
9469
|
+
const manifestPath = join2(root, ".forgezero", "project.json");
|
|
9470
|
+
const files = projectContextFiles(manifest);
|
|
9471
|
+
const collisions = [manifestPath, ...files.map((file) => join2(root, file.path))].filter((path) => {
|
|
9472
|
+
if (!existsSync(path))
|
|
9473
|
+
return false;
|
|
9474
|
+
if (path === manifestPath)
|
|
9475
|
+
return true;
|
|
9476
|
+
return !readFileSync(path, "utf8").startsWith(GENERATED);
|
|
9477
|
+
});
|
|
9478
|
+
if (collisions.length && !options.force) {
|
|
9479
|
+
throw new ProjectContextError(`refusing to replace existing project context: ${collisions.join(", ")}`);
|
|
9480
|
+
}
|
|
9481
|
+
atomicWrite(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
9482
|
+
`);
|
|
9483
|
+
for (const file of files)
|
|
9484
|
+
atomicWrite(join2(root, file.path), file.content);
|
|
9485
|
+
return files;
|
|
9486
|
+
}
|
|
9487
|
+
function syncProjectContext(rootInput) {
|
|
9488
|
+
const root = resolve(rootInput);
|
|
9489
|
+
const manifestPath = join2(root, ".forgezero", "project.json");
|
|
9490
|
+
if (!existsSync(manifestPath))
|
|
9491
|
+
throw new ProjectContextError("No .forgezero/project.json. Run `fz project init`.");
|
|
9492
|
+
const manifest = parseProjectContext(JSON.parse(readFileSync(manifestPath, "utf8")));
|
|
9493
|
+
const files = projectContextFiles(manifest);
|
|
9494
|
+
for (const file of files) {
|
|
9495
|
+
const path = join2(root, file.path);
|
|
9496
|
+
if (existsSync(path) && !readFileSync(path, "utf8").startsWith(GENERATED)) {
|
|
9497
|
+
throw new ProjectContextError(`refusing to replace non-generated adapter: ${file.path}`);
|
|
9498
|
+
}
|
|
9499
|
+
atomicWrite(path, file.content);
|
|
9500
|
+
}
|
|
9501
|
+
return files;
|
|
9502
|
+
}
|
|
9503
|
+
function checkProjectContext(rootInput) {
|
|
9504
|
+
const root = resolve(rootInput);
|
|
9505
|
+
const manifestPath = join2(root, ".forgezero", "project.json");
|
|
9506
|
+
if (!existsSync(manifestPath))
|
|
9507
|
+
return { ok: false, problems: ["missing .forgezero/project.json"] };
|
|
9508
|
+
let manifest;
|
|
9509
|
+
try {
|
|
9510
|
+
manifest = parseProjectContext(JSON.parse(readFileSync(manifestPath, "utf8")));
|
|
9511
|
+
} catch (cause) {
|
|
9512
|
+
return { ok: false, problems: [cause instanceof Error ? cause.message : String(cause)] };
|
|
9513
|
+
}
|
|
9514
|
+
const problems = [];
|
|
9515
|
+
for (const source of manifest.truth) {
|
|
9516
|
+
if (!existsSync(join2(root, source.path)))
|
|
9517
|
+
problems.push(`missing truth source: ${source.path}`);
|
|
9518
|
+
}
|
|
9519
|
+
for (const path of manifest.readFirst) {
|
|
9520
|
+
if (!existsSync(join2(root, path)))
|
|
9521
|
+
problems.push(`missing read-first file: ${path}`);
|
|
9522
|
+
}
|
|
9523
|
+
for (const file of projectContextFiles(manifest)) {
|
|
9524
|
+
const path = join2(root, file.path);
|
|
9525
|
+
if (!existsSync(path))
|
|
9526
|
+
problems.push(`missing generated adapter: ${file.path}`);
|
|
9527
|
+
else if (readFileSync(path, "utf8") !== file.content)
|
|
9528
|
+
problems.push(`drifted generated adapter: ${file.path}`);
|
|
9529
|
+
}
|
|
9530
|
+
return { ok: problems.length === 0, problems };
|
|
9531
|
+
}
|
|
9532
|
+
|
|
9228
9533
|
// src/cli/index.ts
|
|
9229
9534
|
var DEFAULT_MODE = THRESHOLD_MODES[0].id;
|
|
9230
9535
|
var RECOMMENDED_MODE = (THRESHOLD_MODES.find((mode) => mode.recommended) ?? THRESHOLD_MODES[0]).id;
|
|
@@ -9240,7 +9545,9 @@ function parseOptions(argv) {
|
|
|
9240
9545
|
mode: DEFAULT_MODE,
|
|
9241
9546
|
user: process.env.FZ_USER ?? "operator",
|
|
9242
9547
|
email: process.env.FZ_EMAIL ?? "operator@localhost",
|
|
9243
|
-
preserveEnv: false
|
|
9548
|
+
preserveEnv: false,
|
|
9549
|
+
projectRoot: process.cwd(),
|
|
9550
|
+
force: false
|
|
9244
9551
|
};
|
|
9245
9552
|
const positional = [];
|
|
9246
9553
|
for (let index = 0;index < argv.length; index += 1) {
|
|
@@ -9259,6 +9566,14 @@ function parseOptions(argv) {
|
|
|
9259
9566
|
options.enrol = true;
|
|
9260
9567
|
else if (token === "--preserve-env")
|
|
9261
9568
|
options.preserveEnv = true;
|
|
9569
|
+
else if (token === "--root")
|
|
9570
|
+
options.projectRoot = argv[++index] ?? options.projectRoot;
|
|
9571
|
+
else if (token === "--name")
|
|
9572
|
+
options.projectName = argv[++index];
|
|
9573
|
+
else if (token === "--purpose")
|
|
9574
|
+
options.projectPurpose = argv[++index];
|
|
9575
|
+
else if (token === "--force")
|
|
9576
|
+
options.force = true;
|
|
9262
9577
|
else if (token === "--key")
|
|
9263
9578
|
options.key = argv[++index];
|
|
9264
9579
|
else if (token === "--mode")
|
|
@@ -9277,15 +9592,15 @@ function parseOptions(argv) {
|
|
|
9277
9592
|
return { command: positional[0] ?? "help", args: positional.slice(1), options };
|
|
9278
9593
|
}
|
|
9279
9594
|
var out = {
|
|
9280
|
-
line: (
|
|
9595
|
+
line: (text2 = "") => process.stdout.write(`${text2}
|
|
9281
9596
|
`),
|
|
9282
|
-
step: (
|
|
9597
|
+
step: (text2) => process.stdout.write(` ${text2}
|
|
9283
9598
|
`),
|
|
9284
|
-
warn: (
|
|
9599
|
+
warn: (text2) => process.stderr.write(` ! ${text2}
|
|
9285
9600
|
`),
|
|
9286
|
-
fail: (
|
|
9601
|
+
fail: (text2) => process.stderr.write(` \u2717 ${text2}
|
|
9287
9602
|
`),
|
|
9288
|
-
ok: (
|
|
9603
|
+
ok: (text2) => process.stdout.write(` \u2713 ${text2}
|
|
9289
9604
|
`)
|
|
9290
9605
|
};
|
|
9291
9606
|
var sessionCookie = null;
|
|
@@ -9440,7 +9755,7 @@ async function cmdAgent(options, args) {
|
|
|
9440
9755
|
controlSocketPath: process.env.FZ_CONTROL_SOCKET,
|
|
9441
9756
|
repository: process.env.FZ_DEPLOY_REPO,
|
|
9442
9757
|
branch: process.env.FZ_DEPLOY_BRANCH,
|
|
9443
|
-
|
|
9758
|
+
profile: process.env.FZ_DEPLOY_PROFILE,
|
|
9444
9759
|
deployRoot: process.env.FZ_DEPLOY_ROOT,
|
|
9445
9760
|
publicApiUrl: process.env.FZ_PUBLIC_API_URL,
|
|
9446
9761
|
deploymentEnvironment: parseAssignments(process.env.FZ_DEPLOY_ENV),
|
|
@@ -9490,17 +9805,17 @@ async function cmdAgent(options, args) {
|
|
|
9490
9805
|
return 0;
|
|
9491
9806
|
}
|
|
9492
9807
|
try {
|
|
9493
|
-
|
|
9808
|
+
writeFileSync2(plan.unitPath, plan.unit, { mode: 420 });
|
|
9494
9809
|
out.ok(`Wrote ${plan.unitPath}`);
|
|
9495
9810
|
for (const auxiliary of plan.auxiliaryUnits) {
|
|
9496
|
-
|
|
9497
|
-
|
|
9811
|
+
mkdirSync2(dirname2(auxiliary.path), { recursive: true, mode: 493 });
|
|
9812
|
+
writeFileSync2(auxiliary.path, auxiliary.unit, { mode: 420 });
|
|
9498
9813
|
out.ok(`Wrote ${auxiliary.path}`);
|
|
9499
9814
|
}
|
|
9500
9815
|
if (options.enrol) {
|
|
9501
|
-
if (
|
|
9816
|
+
if (existsSync2(enrolTokenSourcePath)) {
|
|
9502
9817
|
const source = statSync(enrolTokenSourcePath);
|
|
9503
|
-
const token =
|
|
9818
|
+
const token = readFileSync2(enrolTokenSourcePath, "utf8").trim();
|
|
9504
9819
|
if (!source.isFile() || (source.mode & 511) !== 384 || source.uid !== 0) {
|
|
9505
9820
|
throw new Error("The preloaded enrolment token must be a root-owned 0600 file in /run.");
|
|
9506
9821
|
}
|
|
@@ -9513,7 +9828,7 @@ async function cmdAgent(options, args) {
|
|
|
9513
9828
|
if (await prompt.exited !== 0 || !/^fze_[A-Za-z0-9_-]{40,100}$/.test(token)) {
|
|
9514
9829
|
throw new Error("A valid fze_ enrolment token was not provided.");
|
|
9515
9830
|
}
|
|
9516
|
-
|
|
9831
|
+
writeFileSync2(enrolTokenSourcePath, `${token}
|
|
9517
9832
|
`, { mode: 384, flag: "wx" });
|
|
9518
9833
|
}
|
|
9519
9834
|
}
|
|
@@ -9524,7 +9839,7 @@ async function cmdAgent(options, args) {
|
|
|
9524
9839
|
out.line();
|
|
9525
9840
|
out.line(" Add this machine-specific PUBLIC key as a read-only deploy key:");
|
|
9526
9841
|
out.line();
|
|
9527
|
-
out.line(` ${
|
|
9842
|
+
out.line(` ${readFileSync2(gitPublicKeyPath, "utf8").trim()}`);
|
|
9528
9843
|
out.line();
|
|
9529
9844
|
return 0;
|
|
9530
9845
|
} catch (cause) {
|
|
@@ -9641,6 +9956,43 @@ Is fz-agent running on this machine? \`fz agent install --apply\`.`);
|
|
|
9641
9956
|
out.line(describeInjection(merged, Object.keys(secrets).length));
|
|
9642
9957
|
return spawnWith(command, merged.env, (line) => out.line(line));
|
|
9643
9958
|
}
|
|
9959
|
+
function cmdProject(options, args) {
|
|
9960
|
+
const operation = args[0] ?? "check";
|
|
9961
|
+
try {
|
|
9962
|
+
if (operation === "init") {
|
|
9963
|
+
const manifest = defaultProjectContext(options.projectRoot);
|
|
9964
|
+
if (options.projectName)
|
|
9965
|
+
manifest.name = options.projectName;
|
|
9966
|
+
if (options.projectPurpose)
|
|
9967
|
+
manifest.purpose = options.projectPurpose;
|
|
9968
|
+
const files = initializeProjectContext(options.projectRoot, manifest, { force: options.force });
|
|
9969
|
+
out.ok(`Project context initialized; ${files.length} generated adapters now share one Git source.`);
|
|
9970
|
+
out.step("Edit .forgezero/project.json, create every named truth source, then run `fz project sync`.");
|
|
9971
|
+
return 0;
|
|
9972
|
+
}
|
|
9973
|
+
if (operation === "sync") {
|
|
9974
|
+
const files = syncProjectContext(options.projectRoot);
|
|
9975
|
+
out.ok(`Synchronized ${files.length} AI adapters from .forgezero/project.json.`);
|
|
9976
|
+
return 0;
|
|
9977
|
+
}
|
|
9978
|
+
if (operation === "check") {
|
|
9979
|
+
const result = checkProjectContext(options.projectRoot);
|
|
9980
|
+
if (options.json)
|
|
9981
|
+
out.line(JSON.stringify(result, null, 2));
|
|
9982
|
+
else if (result.ok)
|
|
9983
|
+
out.ok("Project context and every AI adapter are in sync.");
|
|
9984
|
+
else
|
|
9985
|
+
for (const problem of result.problems)
|
|
9986
|
+
out.fail(problem);
|
|
9987
|
+
return result.ok ? 0 : 1;
|
|
9988
|
+
}
|
|
9989
|
+
out.fail("Usage: fz project init|sync|check [--root <path>] [--force]");
|
|
9990
|
+
return 2;
|
|
9991
|
+
} catch (cause) {
|
|
9992
|
+
out.fail(cause instanceof Error ? cause.message : String(cause));
|
|
9993
|
+
return 1;
|
|
9994
|
+
}
|
|
9995
|
+
}
|
|
9644
9996
|
function usage() {
|
|
9645
9997
|
out.line(`
|
|
9646
9998
|
fz ${VERSION2} \u2014 ForgeZero control surface
|
|
@@ -9662,6 +10014,9 @@ function usage() {
|
|
|
9662
10014
|
fz agent install Install the node agent as a systemd service, so
|
|
9663
10015
|
applications on this box read secrets through a
|
|
9664
10016
|
local socket instead of holding an API key
|
|
10017
|
+
fz project init Create vendor-neutral, Git-persisted AI context
|
|
10018
|
+
fz project sync Regenerate Claude/Codex/Gemini/Copilot/Cursor adapters
|
|
10019
|
+
fz project check Fail when truth sources or generated adapters drift
|
|
9665
10020
|
|
|
9666
10021
|
CEREMONY OPTIONS
|
|
9667
10022
|
--key <fp|index> Which agent key to use for custody
|
|
@@ -9684,6 +10039,10 @@ function usage() {
|
|
|
9684
10039
|
--apply Write the unit rather than printing it (root)
|
|
9685
10040
|
--enrol Bind this machine with a one-time token prompted
|
|
9686
10041
|
securely by systemd (tenant-owned compute)
|
|
10042
|
+
--root <path> Project root for project init/sync/check
|
|
10043
|
+
--name <name> Project name during init
|
|
10044
|
+
--purpose <text> Product outcome during init
|
|
10045
|
+
--force Init may replace existing AI instruction files
|
|
9687
10046
|
|
|
9688
10047
|
CUSTODY FACTORS
|
|
9689
10048
|
Every custodian share is sealed TWICE and either envelope alone opens it:
|
|
@@ -9712,6 +10071,9 @@ async function runCli() {
|
|
|
9712
10071
|
case "agent":
|
|
9713
10072
|
code = await cmdAgent(options, args);
|
|
9714
10073
|
break;
|
|
10074
|
+
case "project":
|
|
10075
|
+
code = cmdProject(options, args);
|
|
10076
|
+
break;
|
|
9715
10077
|
case "genesis":
|
|
9716
10078
|
code = await cmdGenesis(options);
|
|
9717
10079
|
break;
|
|
@@ -9735,7 +10097,7 @@ async function runCli() {
|
|
|
9735
10097
|
usage();
|
|
9736
10098
|
code = 1;
|
|
9737
10099
|
}
|
|
9738
|
-
if (args.length > 0 && code === 0 && command !== "agent") {
|
|
10100
|
+
if (args.length > 0 && code === 0 && command !== "agent" && command !== "project") {
|
|
9739
10101
|
out.warn(`Ignored: ${args.join(" ")}`);
|
|
9740
10102
|
}
|
|
9741
10103
|
process.exit(code);
|
package/dist/index.d.ts
CHANGED
|
@@ -32,8 +32,8 @@ export type { AgentRelease, StagedAgentRelease, UpdateCommand, UpdateCommandResu
|
|
|
32
32
|
export { activateAgentRelease, probeAgentSocket, requestAgentUpdate, startAgentUpdateHelper, AGENT_UPDATE_GROUP, AGENT_UPDATE_HELPER_UNIT_PATH, AGENT_UPDATE_RECEIPT } from './agent-update-helper';
|
|
33
33
|
export type { AgentUpdateRequest, AgentUpdateResponse } from './agent-update-helper';
|
|
34
34
|
export { DEFAULT_SOFTWARE_HELPER_SOCKET, requestSoftware, startSoftwareHelper, SOFTWARE_HELPER_GROUP, SOFTWARE_HELPER_UNIT_PATH } from './software-helper';
|
|
35
|
-
export { ensureSoftwareRequirements, observeSoftwareHost, validateSoftwareRequirements } from './software';
|
|
36
|
-
export type { SoftwareCommandResult, SoftwareExec, SoftwareObservation, SoftwareRequirement } from './software';
|
|
35
|
+
export { ensureSoftwareRequirements, observeSoftwareHost, validateSoftwareRequirements, OS_CATALOG, SOFTWARE_CATALOG } from './software';
|
|
36
|
+
export type { CatalogStatus, DeploymentChannel, OsCatalogEntry, SoftwareCatalogEntry, SoftwareCommandResult, SoftwareExec, SoftwareId, SoftwareObservation, SoftwareRequirement } from './software';
|
|
37
37
|
export { heartbeatAgentOnce, observeAgentHost, startAgentHeartbeat } from './agent-heartbeat';
|
|
38
38
|
export type { AgentHeartbeatOptions, AgentHeartbeatResponse, AgentObservation } from './agent-heartbeat';
|
|
39
39
|
export { DEFAULT_DEPLOYMENT_RUNNER_SOCKET, requestDeploymentCommand, startDeploymentRunner } from './deployment-runner';
|
|
@@ -352,11 +352,10 @@ ${attestationSetup}if [[ ! -x /usr/local/bin/bun ]]; then
|
|
|
352
352
|
install -m 0755 ${agentBun}/bin/bun /usr/local/bin/bun
|
|
353
353
|
rm -f /run/fz-bun-install
|
|
354
354
|
fi
|
|
355
|
-
if [[ ! -x /usr/local/
|
|
355
|
+
if [[ ! -x /usr/local/lib/forgezero/agent/fz-agent ]] || [[ "$(/usr/local/lib/forgezero/agent/fz-agent --version 2>/dev/null || true)" != "${profile.agentVersion}" ]]; then
|
|
356
356
|
env BUN_INSTALL=${agentBun} /usr/local/bin/bun add -g --no-cache --force @forgezero/agent@${profile.agentVersion}
|
|
357
|
-
ln -sfn ${agentBun}/bin/fz-agent /usr/local/bin/fz-agent
|
|
358
357
|
fi
|
|
359
|
-
env FZ_API=${profile.apiUrl} FZ_AGENT_BIN=/usr/local/
|
|
358
|
+
env FZ_API=${profile.apiUrl} FZ_AGENT_BIN=/usr/local/lib/forgezero/agent/fz-agent FZ_AGENT_USER=forgezero-agent FZ_SOCKET_PATH=/run/forgezero/vault.sock FZ_SEED_CREDENTIAL_PATH=/etc/forgezero/creds/agent-seed.cred FZ_GIT_CREDENTIAL_PATH=/etc/forgezero/creds/git-deploy-key.cred FZ_GIT_PUBLIC_KEY_PATH=/etc/forgezero/git/deploy.pub FZ_DEPLOY_ROOT=/opt/forgezero FZ_DEPLOY_PULL=${hasEnrolment ? "true" : "false"} FZ_NODE_LABEL=${nodeLabel} ${agentBun}/bin/fz agent install --apply${hasEnrolment ? " --enrol" : ""}
|
|
360
359
|
`;
|
|
361
360
|
}
|
|
362
361
|
function cloudInit(profile, claim, manifest) {
|
package/dist/metal-provision.js
CHANGED
|
@@ -352,11 +352,10 @@ ${attestationSetup}if [[ ! -x /usr/local/bin/bun ]]; then
|
|
|
352
352
|
install -m 0755 ${agentBun}/bin/bun /usr/local/bin/bun
|
|
353
353
|
rm -f /run/fz-bun-install
|
|
354
354
|
fi
|
|
355
|
-
if [[ ! -x /usr/local/
|
|
355
|
+
if [[ ! -x /usr/local/lib/forgezero/agent/fz-agent ]] || [[ "$(/usr/local/lib/forgezero/agent/fz-agent --version 2>/dev/null || true)" != "${profile.agentVersion}" ]]; then
|
|
356
356
|
env BUN_INSTALL=${agentBun} /usr/local/bin/bun add -g --no-cache --force @forgezero/agent@${profile.agentVersion}
|
|
357
|
-
ln -sfn ${agentBun}/bin/fz-agent /usr/local/bin/fz-agent
|
|
358
357
|
fi
|
|
359
|
-
env FZ_API=${profile.apiUrl} FZ_AGENT_BIN=/usr/local/
|
|
358
|
+
env FZ_API=${profile.apiUrl} FZ_AGENT_BIN=/usr/local/lib/forgezero/agent/fz-agent FZ_AGENT_USER=forgezero-agent FZ_SOCKET_PATH=/run/forgezero/vault.sock FZ_SEED_CREDENTIAL_PATH=/etc/forgezero/creds/agent-seed.cred FZ_GIT_CREDENTIAL_PATH=/etc/forgezero/creds/git-deploy-key.cred FZ_GIT_PUBLIC_KEY_PATH=/etc/forgezero/git/deploy.pub FZ_DEPLOY_ROOT=/opt/forgezero FZ_DEPLOY_PULL=${hasEnrolment ? "true" : "false"} FZ_NODE_LABEL=${nodeLabel} ${agentBun}/bin/fz agent install --apply${hasEnrolment ? " --enrol" : ""}
|
|
360
359
|
`;
|
|
361
360
|
}
|
|
362
361
|
function cloudInit(profile, claim, manifest) {
|