@forgezero/agent 0.1.88 → 0.1.89
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 +23 -9
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.d.ts +8 -1
- package/dist/bootstrap.js +228 -54
- package/dist/capacity-calibration.d.ts +35 -5
- package/dist/capacity-calibration.js +66 -22
- package/dist/cli/agent-install.d.ts +3 -0
- package/dist/community-rehearsal-host.js +7 -2
- package/dist/control.d.ts +3 -1
- package/dist/definition.js +83 -28
- package/dist/deploy-file.js +83 -28
- package/dist/deployment.d.ts +5 -0
- package/dist/egress-policy.d.ts +4 -0
- package/dist/fz-agent.js +416 -150
- package/dist/fz.js +413 -129
- package/dist/host-maintenance.js +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/metal-bootstrap.js +1 -1
- package/dist/metal-helper-socket.js +7 -2
- package/dist/metal-provision.js +7 -2
- package/dist/operator-bootstrap.d.ts +12 -0
- package/dist/operator-bootstrap.js +372 -64
- package/dist/platform-bootstrap-runtime.d.ts +1 -0
- package/dist/platform-bootstrap-runtime.js +47 -8
- package/dist/platform-fleet-verification.js +262 -87
- package/dist/platform-genesis.js +7 -2
- package/dist/provision.d.ts +5 -1
- package/dist/provision.js +185 -71
- package/dist/recovery-host.js +1 -1
- package/dist/software-helper.js +144 -48
- package/dist/software.js +7 -2
- package/dist/ubuntu.js +7 -2
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/schema/deploy-v3.json +5 -2
package/dist/bootstrap.js
CHANGED
|
@@ -1420,7 +1420,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1420
1420
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1421
1421
|
|
|
1422
1422
|
// src/version.ts
|
|
1423
|
-
var VERSION = "0.1.
|
|
1423
|
+
var VERSION = "0.1.89";
|
|
1424
1424
|
|
|
1425
1425
|
// src/software.ts
|
|
1426
1426
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -1698,6 +1698,7 @@ function agentEgressUnit(options) {
|
|
|
1698
1698
|
}
|
|
1699
1699
|
const deploymentEnabled = Boolean(options.repository || bootstrapBundleEnabled || options.pullDeployments);
|
|
1700
1700
|
const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
|
|
1701
|
+
const agentLoopbackPorts = normalizeEgressTcpPorts(options.agentLoopbackPorts ?? []);
|
|
1701
1702
|
const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
|
|
1702
1703
|
if (deploymentEnabled && runnerPublicTcpPorts.length < 1) {
|
|
1703
1704
|
throw new Error("deployed project runner needs at least one vetted public TCP port");
|
|
@@ -1705,8 +1706,10 @@ function agentEgressUnit(options) {
|
|
|
1705
1706
|
if (deploymentEnabled)
|
|
1706
1707
|
systemdAgentEgressDirectives(runnerLoopbackPorts);
|
|
1707
1708
|
const users = [user, ...deploymentEnabled ? [DEPLOYMENT_RUNNER_USER] : []];
|
|
1708
|
-
const
|
|
1709
|
-
const
|
|
1709
|
+
const portGrantEnabled = deploymentEnabled || agentLoopbackPorts.length > 0;
|
|
1710
|
+
const restrictedUser = deploymentEnabled ? DEPLOYMENT_RUNNER_USER : user;
|
|
1711
|
+
const portGrant = portGrantEnabled ? ` --loopback-user=${restrictedUser}` + (deploymentEnabled ? runnerLoopbackPorts.map((port) => ` --loopback-tcp-port=${port}`).join("") : "") + agentLoopbackPorts.map((port) => ` --shared-loopback-tcp-port=${port}`).join("") + (deploymentEnabled ? runnerPublicTcpPorts.map((port) => ` --public-tcp-port=${port}`).join("") : "") : "";
|
|
1712
|
+
const policyProof = `ExecStartPost=${bin} egress-policy-check ${users.map((name) => `--user=${name}`).join(" ")}${portGrant}`;
|
|
1710
1713
|
return `[Unit]
|
|
1711
1714
|
Description=ForgeZero Agent host egress policy
|
|
1712
1715
|
Documentation=https://www.forgezero.net/docs/agent
|
|
@@ -1719,7 +1722,7 @@ Type=notify
|
|
|
1719
1722
|
NotifyAccess=all
|
|
1720
1723
|
User=root
|
|
1721
1724
|
Group=root
|
|
1722
|
-
ExecStart=${bin} egress-policy ${users.map((name) => `--user=${name}`).join(" ")}${
|
|
1725
|
+
ExecStart=${bin} egress-policy ${users.map((name) => `--user=${name}`).join(" ")}${portGrant}
|
|
1723
1726
|
${policyProof}
|
|
1724
1727
|
Restart=on-failure
|
|
1725
1728
|
RestartSec=2
|
|
@@ -2003,7 +2006,7 @@ function agentEnrolmentUnit(options) {
|
|
|
2003
2006
|
Requires=forgezero-agent-egress.service
|
|
2004
2007
|
BindsTo=forgezero-agent-egress.service
|
|
2005
2008
|
` : "";
|
|
2006
|
-
const egressDirectives = options.enforceEgress ? systemdAgentEgressDirectives() : "";
|
|
2009
|
+
const egressDirectives = options.enforceEgress ? systemdAgentEgressDirectives(options.agentLoopbackPorts ?? []) : "";
|
|
2007
2010
|
return `[Unit]
|
|
2008
2011
|
Description=Bind this machine to its ForgeZero compute
|
|
2009
2012
|
After=network-online.target
|
|
@@ -2040,7 +2043,7 @@ WantedBy=multi-user.target
|
|
|
2040
2043
|
function deploymentRunnerUnit(options) {
|
|
2041
2044
|
const bin = options.binPath ?? "fz-agent";
|
|
2042
2045
|
const root = options.deployRoot ?? "/opt/forgezero";
|
|
2043
|
-
const agentUser = options.user ?? "forgezero
|
|
2046
|
+
const agentUser = options.user ?? "forgezero";
|
|
2044
2047
|
const egressDependency = options.enforceEgress ? `After=forgezero-agent-egress.service
|
|
2045
2048
|
Requires=forgezero-agent-egress.service
|
|
2046
2049
|
BindsTo=forgezero-agent-egress.service
|
|
@@ -2081,7 +2084,7 @@ RestrictRealtime=true
|
|
|
2081
2084
|
MemoryDenyWriteExecute=true
|
|
2082
2085
|
LockPersonality=true
|
|
2083
2086
|
${egressDirectives}
|
|
2084
|
-
ReadWritePaths=${root}/releases ${root}/runner-home
|
|
2087
|
+
ReadWritePaths=${root}/releases ${root}/runner-home ${root}/slots /etc/nginx/conf.d /var/log/nginx
|
|
2085
2088
|
|
|
2086
2089
|
[Install]
|
|
2087
2090
|
WantedBy=multi-user.target
|
|
@@ -2216,7 +2219,7 @@ function agentUnit(options) {
|
|
|
2216
2219
|
const supplementaryGroups = [
|
|
2217
2220
|
AGENT_UPDATE_GROUP,
|
|
2218
2221
|
deploymentEnabled ? DEPLOYMENT_GROUP : null,
|
|
2219
|
-
|
|
2222
|
+
SOFTWARE_HELPER_GROUP,
|
|
2220
2223
|
lifecycleEnabled ? LIFECYCLE_GROUP : null
|
|
2221
2224
|
].filter((value) => value !== null);
|
|
2222
2225
|
const deploymentGroup = supplementaryGroups.length > 0 ? `SupplementaryGroups=${supplementaryGroups.join(" ")}` : "";
|
|
@@ -2225,7 +2228,7 @@ function agentUnit(options) {
|
|
|
2225
2228
|
"forgezero-agent-update-helper.service",
|
|
2226
2229
|
options.enforceEgress ? "forgezero-agent-egress.service" : null,
|
|
2227
2230
|
deploymentEnabled ? "forgezero-deploy-runner.service" : null,
|
|
2228
|
-
|
|
2231
|
+
"forgezero-software-helper.service",
|
|
2229
2232
|
lifecycleEnabled ? "forgezero-lifecycle-helper.service" : null,
|
|
2230
2233
|
warpEnabled ? "warp-svc.service" : null,
|
|
2231
2234
|
enrolmentEnabled ? "forgezero-agent-enrol.service" : null
|
|
@@ -2234,7 +2237,7 @@ function agentUnit(options) {
|
|
|
2234
2237
|
"forgezero-agent-update-helper.service",
|
|
2235
2238
|
options.enforceEgress ? "forgezero-agent-egress.service" : null,
|
|
2236
2239
|
deploymentEnabled ? "forgezero-deploy-runner.service" : null,
|
|
2237
|
-
|
|
2240
|
+
"forgezero-software-helper.service",
|
|
2238
2241
|
lifecycleEnabled ? "forgezero-lifecycle-helper.service" : null,
|
|
2239
2242
|
warpEnabled ? "warp-svc.service" : null,
|
|
2240
2243
|
enrolmentEnabled ? "forgezero-agent-enrol.service" : null
|
|
@@ -2249,9 +2252,9 @@ function agentUnit(options) {
|
|
|
2249
2252
|
const snpDevice = options.mode === "attested" ? `DevicePolicy=closed
|
|
2250
2253
|
DeviceAllow=/dev/sev-guest rw` : "";
|
|
2251
2254
|
const snpPrepare = options.mode === "attested" ? `ExecStartPre=+/bin/chgrp ${VAULT_GROUP} /dev/sev-guest
|
|
2252
|
-
ExecStartPre=+/bin/chmod
|
|
2255
|
+
ExecStartPre=+/bin/chmod 0660 /dev/sev-guest
|
|
2253
2256
|
` : "";
|
|
2254
|
-
const egressDirectives = options.enforceEgress ? systemdAgentEgressDirectives() : "";
|
|
2257
|
+
const egressDirectives = options.enforceEgress ? systemdAgentEgressDirectives(options.agentLoopbackPorts ?? []) : "";
|
|
2255
2258
|
return `[Unit]
|
|
2256
2259
|
Description=ForgeZero node agent (${options.mode})
|
|
2257
2260
|
Documentation=https://www.forgezero.net/docs/agent
|
|
@@ -2382,6 +2385,7 @@ function planProvision(options) {
|
|
|
2382
2385
|
const deployRoot = options.deployRoot ?? "/opt/forgezero";
|
|
2383
2386
|
const deploymentEnabled = Boolean(options.repository || options.pullDeployments);
|
|
2384
2387
|
const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
|
|
2388
|
+
const agentLoopbackPorts = normalizeEgressTcpPorts(options.agentLoopbackPorts ?? []);
|
|
2385
2389
|
const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
|
|
2386
2390
|
const lifecycleEnabled = Boolean(options.pullMigrations && options.lifecycleProfilePath);
|
|
2387
2391
|
if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
|
|
@@ -2430,8 +2434,9 @@ function planProvision(options) {
|
|
|
2430
2434
|
const enabledUnits = [
|
|
2431
2435
|
"forgezero-agent.socket",
|
|
2432
2436
|
"forgezero-agent-update-helper.service",
|
|
2437
|
+
"forgezero-software-helper.service",
|
|
2433
2438
|
...options.enforceEgress ? ["forgezero-agent-egress.service"] : [],
|
|
2434
|
-
...deploymentEnabled ? ["forgezero-deploy-runner.service"
|
|
2439
|
+
...deploymentEnabled ? ["forgezero-deploy-runner.service"] : [],
|
|
2435
2440
|
...lifecycleEnabled ? ["forgezero-lifecycle-helper.service"] : [],
|
|
2436
2441
|
...warpEnabled ? ["forgezero-warp-config.service", "warp-svc.service"] : [],
|
|
2437
2442
|
...enrolmentEnabled ? ["forgezero-agent-enrol.service"] : [],
|
|
@@ -2439,8 +2444,9 @@ function planProvision(options) {
|
|
|
2439
2444
|
];
|
|
2440
2445
|
const restartedUnits = [
|
|
2441
2446
|
"forgezero-agent-update-helper.service",
|
|
2447
|
+
"forgezero-software-helper.service",
|
|
2442
2448
|
...options.enforceEgress ? ["forgezero-agent-egress.service"] : [],
|
|
2443
|
-
...deploymentEnabled ? ["forgezero-deploy-runner.service"
|
|
2449
|
+
...deploymentEnabled ? ["forgezero-deploy-runner.service"] : [],
|
|
2444
2450
|
...lifecycleEnabled ? ["forgezero-lifecycle-helper.service"] : [],
|
|
2445
2451
|
...warpEnabled ? ["forgezero-warp-config.service", "warp-svc.service"] : [],
|
|
2446
2452
|
...enrolmentEnabled ? ["forgezero-agent-enrol.service"] : []
|
|
@@ -2464,9 +2470,9 @@ function planProvision(options) {
|
|
|
2464
2470
|
...options.enforceEgress ? [
|
|
2465
2471
|
{ path: AGENT_EGRESS_UNIT_PATH, unit: agentEgressUnit(options) }
|
|
2466
2472
|
] : [],
|
|
2473
|
+
{ path: SOFTWARE_HELPER_UNIT_PATH, unit: softwareHelperUnit(options) },
|
|
2467
2474
|
...deploymentEnabled ? [
|
|
2468
|
-
{ path: DEPLOYMENT_RUNNER_UNIT_PATH, unit: deploymentRunnerUnit(options) }
|
|
2469
|
-
{ path: SOFTWARE_HELPER_UNIT_PATH, unit: softwareHelperUnit(options) }
|
|
2475
|
+
{ path: DEPLOYMENT_RUNNER_UNIT_PATH, unit: deploymentRunnerUnit(options) }
|
|
2470
2476
|
] : [],
|
|
2471
2477
|
...enrolmentEnabled ? [
|
|
2472
2478
|
{ path: ENROLMENT_UNIT_PATH, unit: agentEnrolmentUnit(options) }
|
|
@@ -2499,24 +2505,29 @@ function planProvision(options) {
|
|
|
2499
2505
|
step("Agent update helper access group", { kind: "commands", commands: [{ argv: ["/usr/sbin/groupadd", "--system", AGENT_UPDATE_GROUP], acceptedExitCodes: [0, 9] }] }),
|
|
2500
2506
|
...sourceBinPath && binPath ? [step("root-owned agent runtime", { kind: "install-runtime", source: sourceBinPath, binary: binPath, version: VERSION })] : [],
|
|
2501
2507
|
...warpEnabled ? [step("Cloudflare One client for Ubuntu 26.04", { kind: "install-warp" })] : [],
|
|
2502
|
-
|
|
2503
|
-
{ argv: ["/usr/sbin/groupadd", "--system", DEPLOYMENT_GROUP], acceptedExitCodes: [0, 9] },
|
|
2508
|
+
step("software strategy helper group", { kind: "commands", commands: [
|
|
2504
2509
|
{ argv: ["/usr/sbin/groupadd", "--system", SOFTWARE_HELPER_GROUP], acceptedExitCodes: [0, 9] }
|
|
2510
|
+
] }),
|
|
2511
|
+
...deploymentEnabled ? [step("deployment isolation group", { kind: "commands", commands: [
|
|
2512
|
+
{ argv: ["/usr/sbin/groupadd", "--system", DEPLOYMENT_GROUP], acceptedExitCodes: [0, 9] }
|
|
2505
2513
|
] })] : [],
|
|
2506
2514
|
...lifecycleEnabled ? [step("lifecycle helper access group", { kind: "commands", commands: [{ argv: ["/usr/sbin/groupadd", "--system", LIFECYCLE_GROUP], acceptedExitCodes: [0, 9] }] })] : [],
|
|
2507
2515
|
step("service account", { kind: "commands", commands: [{ argv: ["/usr/sbin/useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", user], acceptedExitCodes: [0, 9] }] }),
|
|
2508
2516
|
step("bind service account to vault group", { kind: "commands", commands: [{ argv: ["/usr/sbin/usermod", "-g", VAULT_GROUP, user] }] }),
|
|
2509
2517
|
step("grant verified Agent update access", { kind: "commands", commands: [{ argv: ["/usr/sbin/usermod", "-a", "-G", AGENT_UPDATE_GROUP, user] }] }),
|
|
2518
|
+
step("grant software helper socket access", { kind: "commands", commands: [
|
|
2519
|
+
{ argv: ["/usr/sbin/usermod", "-a", "-G", SOFTWARE_HELPER_GROUP, user] }
|
|
2520
|
+
] }),
|
|
2510
2521
|
...lifecycleEnabled ? [step("grant lifecycle helper socket access", { kind: "commands", commands: [{ argv: ["/usr/sbin/usermod", "-a", "-G", LIFECYCLE_GROUP, user] }] })] : [],
|
|
2511
2522
|
...deploymentEnabled ? [step("credential-free deployment account", { kind: "commands", commands: [
|
|
2512
2523
|
{ argv: ["/usr/sbin/useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", "--gid", DEPLOYMENT_GROUP, DEPLOYMENT_RUNNER_USER], acceptedExitCodes: [0, 9] },
|
|
2513
2524
|
{ argv: ["/usr/sbin/useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", "--gid", VAULT_GROUP, APPLICATION_RUNTIME_USER], acceptedExitCodes: [0, 9] },
|
|
2514
2525
|
{ argv: ["/usr/sbin/usermod", "-a", "-G", DEPLOYMENT_GROUP, APPLICATION_RUNTIME_USER] },
|
|
2515
|
-
{ argv: ["/usr/sbin/usermod", "-a", "-G",
|
|
2526
|
+
{ argv: ["/usr/sbin/usermod", "-a", "-G", DEPLOYMENT_GROUP, user] }
|
|
2516
2527
|
] })] : [],
|
|
2517
2528
|
step("credential and state directories", { kind: "directories", directories: [
|
|
2518
2529
|
{ path: credentialDir, mode: 448, owner: "root", group: "root" },
|
|
2519
|
-
{ path: "/var/lib/forgezero", mode: 488, owner: "root", group:
|
|
2530
|
+
{ path: "/var/lib/forgezero", mode: 488, owner: "root", group: VAULT_GROUP }
|
|
2520
2531
|
] }),
|
|
2521
2532
|
step("encrypted node identity", { kind: "ensure-seed", credential: seedCredentialPath }),
|
|
2522
2533
|
...options.generateGitIdentity && gitCredentialPath && gitPublicKeyPath ? [
|
|
@@ -2567,19 +2578,26 @@ function planProvision(options) {
|
|
|
2567
2578
|
{ argv: ["/usr/bin/systemctl", "enable", ...enabledUnits] },
|
|
2568
2579
|
{ argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
|
|
2569
2580
|
...restartedUnits.length ? [{ argv: ["/usr/bin/systemctl", "restart", ...restartedUnits] }] : [],
|
|
2581
|
+
{ argv: ["/usr/bin/systemctl", "stop", "forgezero-agent-proxy.service"], acceptedExitCodes: [0, 1, 5] },
|
|
2570
2582
|
{ argv: ["/usr/bin/systemctl", "restart", "forgezero-agent.socket"] },
|
|
2571
2583
|
{ argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
|
|
2572
2584
|
{ argv: ["/usr/bin/systemctl", "restart", "forgezero-agent.service"] }
|
|
2573
2585
|
] }),
|
|
2574
2586
|
...enrolmentEnabled ? [step("prove the compute binding is durable", { kind: "verify-file", path: enrolStatePath })] : [],
|
|
2575
|
-
...options.enforceEgress ? [step("prove the Agent egress policy is active", {
|
|
2587
|
+
...options.enforceEgress ? [step("prove the Agent egress policy is active", {
|
|
2588
|
+
kind: "verify-egress",
|
|
2589
|
+
deploymentEnabled,
|
|
2590
|
+
runnerPublicTcpPorts,
|
|
2591
|
+
runnerLoopbackPorts,
|
|
2592
|
+
agentLoopbackPorts
|
|
2593
|
+
})] : [],
|
|
2576
2594
|
step("prove it is running", { kind: "commands", commands: [{ argv: ["/usr/bin/systemctl", "is-active", "forgezero-agent.service"] }] }),
|
|
2577
2595
|
step("prove the public Vault socket exists", { kind: "wait-socket", path: options.socketPath, attempts: 100, intervalMs: 100 }),
|
|
2578
2596
|
step("prove the Agent Vault backend exists", { kind: "wait-socket", path: agentBackendSocketPath(options.socketPath), attempts: 100, intervalMs: 100 }),
|
|
2579
2597
|
step("prove the Agent update helper exists", { kind: "wait-socket", path: DEFAULT_AGENT_UPDATE_SOCKET, attempts: 100, intervalMs: 100 }),
|
|
2598
|
+
step("prove the software strategy helper socket exists", { kind: "wait-socket", path: DEFAULT_SOFTWARE_HELPER_SOCKET, attempts: 100, intervalMs: 100 }),
|
|
2580
2599
|
...deploymentEnabled ? [
|
|
2581
|
-
step("prove the deployment runner socket exists", { kind: "wait-socket", path: DEPLOYMENT_RUNNER_SOCKET, attempts: 100, intervalMs: 100 })
|
|
2582
|
-
step("prove the software strategy helper socket exists", { kind: "wait-socket", path: DEFAULT_SOFTWARE_HELPER_SOCKET, attempts: 100, intervalMs: 100 })
|
|
2600
|
+
step("prove the deployment runner socket exists", { kind: "wait-socket", path: DEPLOYMENT_RUNNER_SOCKET, attempts: 100, intervalMs: 100 })
|
|
2583
2601
|
] : [],
|
|
2584
2602
|
...lifecycleEnabled ? [step("prove the lifecycle helper socket exists", { kind: "wait-socket", path: lifecycleHelperSocketPath, attempts: 100, intervalMs: 100 })] : [],
|
|
2585
2603
|
...warpEnabled ? [step("prove Cloudflare WARP is connected", { kind: "verify-warp" })] : [],
|
|
@@ -2604,6 +2622,25 @@ import {
|
|
|
2604
2622
|
writeFileSync as writeFileSync4
|
|
2605
2623
|
} from "fs";
|
|
2606
2624
|
import { dirname as dirname5 } from "path";
|
|
2625
|
+
function normalizeEd25519PublicKey(output) {
|
|
2626
|
+
const line = output.trim();
|
|
2627
|
+
if (!/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}(?: [^\r\n]{1,256})?$/.test(line))
|
|
2628
|
+
return;
|
|
2629
|
+
const [algorithm, encoded] = line.split(" ", 3);
|
|
2630
|
+
if (algorithm !== "ssh-ed25519" || !encoded)
|
|
2631
|
+
return;
|
|
2632
|
+
let blob;
|
|
2633
|
+
try {
|
|
2634
|
+
blob = Buffer.from(encoded, "base64");
|
|
2635
|
+
} catch {
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
if (blob.length !== 51 || blob.readUInt32BE(0) !== 11 || blob.subarray(4, 15).toString("ascii") !== "ssh-ed25519" || blob.readUInt32BE(15) !== 32)
|
|
2639
|
+
return;
|
|
2640
|
+
if (blob.toString("base64") !== encoded)
|
|
2641
|
+
return;
|
|
2642
|
+
return `${algorithm} ${encoded}`;
|
|
2643
|
+
}
|
|
2607
2644
|
async function readCapabilities(run2) {
|
|
2608
2645
|
const answers = {};
|
|
2609
2646
|
const checks = Object.entries(CAPABILITY_CHECKS);
|
|
@@ -2743,7 +2780,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
2743
2780
|
return result;
|
|
2744
2781
|
}
|
|
2745
2782
|
result = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
2746
|
-
if (result.exitCode !== 0 ||
|
|
2783
|
+
if (result.exitCode !== 0 || !normalizeEd25519PublicKey(result.stdout)) {
|
|
2747
2784
|
return { stdout: "bootstrap SSH private key is not a valid Ed25519 OpenSSH key", exitCode: 1 };
|
|
2748
2785
|
}
|
|
2749
2786
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
|
|
@@ -2759,11 +2796,12 @@ var runProvisionOperation = async (operation) => {
|
|
|
2759
2796
|
chmodSync2(key, 384);
|
|
2760
2797
|
}
|
|
2761
2798
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
2762
|
-
|
|
2799
|
+
const publicKey = normalizeEd25519PublicKey(derived.stdout);
|
|
2800
|
+
if (derived.exitCode !== 0 || !publicKey) {
|
|
2763
2801
|
return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
|
|
2764
2802
|
}
|
|
2765
2803
|
mkdirSync4(dirname5(operation.publicKey), { recursive: true, mode: 493 });
|
|
2766
|
-
writeFileSync4(operation.publicKey, `${
|
|
2804
|
+
writeFileSync4(operation.publicKey, `${publicKey} forgezero-bootstrap-runner
|
|
2767
2805
|
`, { mode: 292 });
|
|
2768
2806
|
chmodSync2(operation.publicKey, 292);
|
|
2769
2807
|
}
|
|
@@ -2806,8 +2844,9 @@ var runProvisionOperation = async (operation) => {
|
|
|
2806
2844
|
const policy = await fixed(["/usr/sbin/nft", "--numeric", "list", "table", "inet", "forgezero_agent_egress"]);
|
|
2807
2845
|
const required = [
|
|
2808
2846
|
"forgezero-agent-egress-v1",
|
|
2809
|
-
`
|
|
2810
|
-
...operation.
|
|
2847
|
+
...operation.agentLoopbackPorts.length ? [`shared-loopback=${operation.agentLoopbackPorts.join(",")}`] : [],
|
|
2848
|
+
...operation.deploymentEnabled && operation.runnerPublicTcpPorts.length ? [`public-tcp=${operation.runnerPublicTcpPorts.join(",")}`] : [],
|
|
2849
|
+
...operation.deploymentEnabled && operation.runnerLoopbackPorts.length ? [`:${operation.runnerLoopbackPorts.join(",")}`] : []
|
|
2811
2850
|
];
|
|
2812
2851
|
return policy.exitCode === 0 && required.every((part) => policy.stdout.includes(part)) ? { stdout: policy.stdout, exitCode: 0 } : { stdout: policy.stdout, exitCode: 1 };
|
|
2813
2852
|
}
|
|
@@ -2873,7 +2912,9 @@ async function applyPlan(plan, run2) {
|
|
|
2873
2912
|
const result = await run2(step2.operation);
|
|
2874
2913
|
transcript.push({ label: step2.label, command: step2.command, exitCode: result.exitCode });
|
|
2875
2914
|
if (result.exitCode !== 0 && !step2.optional) {
|
|
2876
|
-
|
|
2915
|
+
const safeDetail = step2.operation.kind === "ensure-bootstrap-ssh-identity" ? result.stdout.trim().slice(0, 1000) : "";
|
|
2916
|
+
throw new Error(`${step2.label} failed (exit ${result.exitCode}): ${step2.command}${safeDetail ? `
|
|
2917
|
+
${safeDetail}` : ""}`);
|
|
2877
2918
|
}
|
|
2878
2919
|
}
|
|
2879
2920
|
return transcript;
|
|
@@ -3371,12 +3412,26 @@ map $limit_conn_status $forgezero_retry_after { default ''; REJECTED 1; REJECTED
|
|
|
3371
3412
|
server {
|
|
3372
3413
|
listen 127.0.0.1:${input.publicPort};
|
|
3373
3414
|
server_name _;
|
|
3415
|
+
server_tokens off;
|
|
3416
|
+
more_clear_headers Server X-Powered-By;
|
|
3374
3417
|
limit_conn forgezero_admission ${concurrencyLimit};
|
|
3375
3418
|
limit_conn_status 503;
|
|
3376
3419
|
add_header Retry-After $forgezero_retry_after always;
|
|
3377
|
-
|
|
3378
|
-
location
|
|
3379
|
-
location / {
|
|
3420
|
+
error_page 400 403 404 405 408 413 414 429 500 502 503 504 = @transport_failure;
|
|
3421
|
+
location @transport_failure { internal; return 444; }
|
|
3422
|
+
location / {
|
|
3423
|
+
proxy_pass http://forgezero;
|
|
3424
|
+
proxy_http_version 1.1;
|
|
3425
|
+
proxy_intercept_errors off;
|
|
3426
|
+
proxy_hide_header Server;
|
|
3427
|
+
proxy_hide_header X-Powered-By;
|
|
3428
|
+
proxy_set_header Host $host;
|
|
3429
|
+
proxy_set_header X-Forwarded-Proto https;
|
|
3430
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
3431
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
3432
|
+
proxy_set_header Connection $forgezero_connection;
|
|
3433
|
+
proxy_read_timeout 3600s;
|
|
3434
|
+
}
|
|
3380
3435
|
}
|
|
3381
3436
|
`
|
|
3382
3437
|
};
|
|
@@ -3988,7 +4043,7 @@ Type=oneshot
|
|
|
3988
4043
|
User=arangodb
|
|
3989
4044
|
Group=arangodb
|
|
3990
4045
|
LoadCredentialEncrypted=arangodb-jwt:${JWT_CREDENTIAL}
|
|
3991
|
-
ExecStart=/usr/bin/arangosh --server.endpoint tcp://${unitEscape(address)}:8529 --server.jwt-secret-keyfile %d/arangodb-jwt --javascript.execute-string 'const c=require("@arangodb").db._connection;let last;for(let i=0;i<90;i++){try{const v=c.GET("/_api/version?details=true");const s=c.GET("/_admin/status");const m=c.GET("/_admin/server/mode");if(!v.error&&v.version==="3.11.14"&&v.details&&v.details.license==="community"&&!s.error&&s.serverInfo&&s.serverInfo.role==="COORDINATOR"&&!m.error&&m.mode==="default")
|
|
4046
|
+
ExecStart=/usr/bin/arangosh --server.endpoint tcp://${unitEscape(address)}:8529 --server.jwt-secret-keyfile %d/arangodb-jwt --javascript.execute-string 'const c=require("@arangodb").db._connection;let last;let ok=false;for(let i=0;i<90;i++){try{const v=c.GET("/_api/version?details=true");const s=c.GET("/_admin/status");const m=c.GET("/_admin/server/mode");if(!v.error&&v.version==="3.11.14"&&v.details&&v.details.license==="community"&&!s.error&&s.serverInfo&&s.serverInfo.role==="COORDINATOR"&&!m.error&&m.mode==="default"){ok=true;break;}last={v,s,m};}catch(e){last=String(e);}require("internal").wait(2);}if(!ok)throw new Error("writable Community Coordinator verification failed: "+JSON.stringify(last));'
|
|
3992
4047
|
RemainAfterExit=yes
|
|
3993
4048
|
TimeoutStartSec=200
|
|
3994
4049
|
NoNewPrivileges=true
|
|
@@ -4114,11 +4169,11 @@ async function seal(host, name, destination2, value) {
|
|
|
4114
4169
|
async function verifyBootstrapBundleOnHost(host, config, verifyGit = false) {
|
|
4115
4170
|
const manifestMetadata = host.inspect?.(config.bootstrapBundle.manifestFile);
|
|
4116
4171
|
const bundleMetadata = host.inspect?.(config.bootstrapBundle.bundleFile);
|
|
4117
|
-
if (manifestMetadata && (!manifestMetadata.regular || manifestMetadata.symbolic || manifestMetadata.uid !== 0 || manifestMetadata.links !== 1 || (manifestMetadata.mode &
|
|
4118
|
-
throw new Error("bootstrap bundle manifest must be a root-owned
|
|
4172
|
+
if (manifestMetadata && (!manifestMetadata.regular || manifestMetadata.symbolic || manifestMetadata.uid !== 0 || manifestMetadata.links !== 1 || (manifestMetadata.mode & 18) !== 0 || manifestMetadata.size > 16 * 1024)) {
|
|
4173
|
+
throw new Error("bootstrap bundle manifest must be a root-owned non-writable regular file");
|
|
4119
4174
|
}
|
|
4120
|
-
if (bundleMetadata && (!bundleMetadata.regular || bundleMetadata.symbolic || bundleMetadata.uid !== 0 || bundleMetadata.links !== 1 || (bundleMetadata.mode &
|
|
4121
|
-
throw new Error("bootstrap bundle must be a root-owned
|
|
4175
|
+
if (bundleMetadata && (!bundleMetadata.regular || bundleMetadata.symbolic || bundleMetadata.uid !== 0 || bundleMetadata.links !== 1 || (bundleMetadata.mode & 18) !== 0 || bundleMetadata.size < 1 || bundleMetadata.size > 512 * 1024 * 1024)) {
|
|
4176
|
+
throw new Error("bootstrap bundle must be a root-owned non-writable regular file within 512 MiB");
|
|
4122
4177
|
}
|
|
4123
4178
|
if (!host.exists(config.bootstrapBundle.bundleFile) || !host.exists(config.bootstrapBundle.manifestFile)) {
|
|
4124
4179
|
throw new Error("attended bootstrap bundle and manifest are required for release generation one");
|
|
@@ -4137,11 +4192,25 @@ async function verifyBootstrapBundleOnHost(host, config, verifyGit = false) {
|
|
|
4137
4192
|
if (digest !== manifest.sha256)
|
|
4138
4193
|
throw new Error("bootstrap bundle digest does not match its manifest");
|
|
4139
4194
|
if (verifyGit) {
|
|
4140
|
-
|
|
4195
|
+
const verificationRepository = "/run/forgezero-bootstrap-bundle-verify";
|
|
4196
|
+
await checked3(host, ["/usr/bin/rm", "-rf", "--", verificationRepository], "stale bootstrap bundle verifier cleanup");
|
|
4197
|
+
await checked3(host, ["/usr/bin/git", "init", "--bare", "--quiet", verificationRepository], "bootstrap bundle verifier repository");
|
|
4198
|
+
try {
|
|
4199
|
+
await checked3(host, [
|
|
4200
|
+
"/usr/bin/git",
|
|
4201
|
+
"-C",
|
|
4202
|
+
verificationRepository,
|
|
4203
|
+
"bundle",
|
|
4204
|
+
"verify",
|
|
4205
|
+
config.bootstrapBundle.bundleFile
|
|
4206
|
+
], "bootstrap Git bundle verification");
|
|
4207
|
+
} finally {
|
|
4208
|
+
await checked3(host, ["/usr/bin/rm", "-rf", "--", verificationRepository], "bootstrap bundle verifier cleanup");
|
|
4209
|
+
}
|
|
4141
4210
|
}
|
|
4142
4211
|
return manifest;
|
|
4143
4212
|
}
|
|
4144
|
-
function bootstrapIdentity(config) {
|
|
4213
|
+
function bootstrapIdentity(config, includeDeploymentEvidence = false) {
|
|
4145
4214
|
if (config.kind === "enrolled-compute")
|
|
4146
4215
|
return {
|
|
4147
4216
|
kind: config.kind,
|
|
@@ -4160,7 +4229,13 @@ function bootstrapIdentity(config) {
|
|
|
4160
4229
|
bootstrapRunner: config.bootstrapRunner ? { targetTelemetryEndpoint: config.bootstrapRunner.targetTelemetryEndpoint } : null
|
|
4161
4230
|
};
|
|
4162
4231
|
const environment = config.runtime.environment;
|
|
4163
|
-
const { cloudflare: _cloudflare, realtime: _realtime, ...
|
|
4232
|
+
const { cloudflare: _cloudflare, realtime: _realtime, initialInventory, ...stableEnvironmentRest } = environment;
|
|
4233
|
+
const stableEnvironment = {
|
|
4234
|
+
...stableEnvironmentRest,
|
|
4235
|
+
...initialInventory ? {
|
|
4236
|
+
initialInventory: { ...initialInventory, deployment: includeDeploymentEvidence ? initialInventory.deployment : undefined }
|
|
4237
|
+
} : {}
|
|
4238
|
+
};
|
|
4164
4239
|
return {
|
|
4165
4240
|
kind: config.kind,
|
|
4166
4241
|
environment: config.environment,
|
|
@@ -4201,6 +4276,26 @@ function bootstrapIdentity(config) {
|
|
|
4201
4276
|
function bootstrapIdentityDigest(config) {
|
|
4202
4277
|
return createHash2("sha256").update(JSON.stringify(bootstrapIdentity(config))).digest("hex");
|
|
4203
4278
|
}
|
|
4279
|
+
var legacyBootstrapIdentityDigest = (config) => createHash2("sha256").update(JSON.stringify(bootstrapIdentity(config, true))).digest("hex");
|
|
4280
|
+
function interruptedPlatformReleaseIdentityDigests(config, releaseEvidence, bootstrapManifest) {
|
|
4281
|
+
if (!config.runtime.environment.initialInventory)
|
|
4282
|
+
return [];
|
|
4283
|
+
const previous = structuredClone(config);
|
|
4284
|
+
previous.runtime.environment.initialInventory.deployment = {
|
|
4285
|
+
source: "bootstrap-bundle",
|
|
4286
|
+
branch: releaseEvidence.branch,
|
|
4287
|
+
revision: releaseEvidence.revision,
|
|
4288
|
+
bundleSha256: releaseEvidence.sha256
|
|
4289
|
+
};
|
|
4290
|
+
const expectedCurrentEpoch = bootstrapManifest ? `${config.environment}-${bootstrapManifest.revision.slice(0, 16)}` : undefined;
|
|
4291
|
+
if (expectedCurrentEpoch && config.runtime.environment.seedSyncEpoch === expectedCurrentEpoch) {
|
|
4292
|
+
previous.runtime.environment.seedSyncEpoch = `${config.environment}-${releaseEvidence.revision.slice(0, 16)}`;
|
|
4293
|
+
}
|
|
4294
|
+
return [bootstrapIdentityDigest(previous), legacyBootstrapIdentityDigest(previous)];
|
|
4295
|
+
}
|
|
4296
|
+
function storedPlatformCoordinatesMatch(state, config) {
|
|
4297
|
+
return state.kind === "platform" && state.profile === config.profile && state.nodeHostname === config.nodeHostname && state.apiUrl === config.apiUrl && state.environment === config.environment && state.databaseRole === config.database.role && state.databaseAgency === config.database.agency && state.databaseReadPreferred === config.database.readPreferred && state.databaseServerMode === config.database.serverMode && (state.databaseAddress ?? null) === (config.database.address ?? null) && JSON.stringify(state.databaseCoordinators ?? []) === JSON.stringify(config.database.coordinators) && state.collectorUnit === config.runtime.environment.otlpCollectorUnit && state.publicApiPort === config.runtime.environment.publicApiPort && state.healthPath === config.runtime.healthPath;
|
|
4298
|
+
}
|
|
4204
4299
|
function parseStoredState(raw) {
|
|
4205
4300
|
let value;
|
|
4206
4301
|
try {
|
|
@@ -4231,11 +4326,21 @@ function parseStoredIntent(raw) {
|
|
|
4231
4326
|
}
|
|
4232
4327
|
return intent;
|
|
4233
4328
|
}
|
|
4234
|
-
function bindBootstrapIntent(host, config) {
|
|
4329
|
+
function bindBootstrapIntent(host, config, previousReleaseDigests = []) {
|
|
4235
4330
|
const identityDigest = bootstrapIdentityDigest(config);
|
|
4236
4331
|
if (host.exists(INTENT_PATH)) {
|
|
4237
4332
|
const intent = parseStoredIntent(host.read(INTENT_PATH));
|
|
4238
4333
|
if (intent.kind !== config.kind || intent.identityDigest !== identityDigest) {
|
|
4334
|
+
if (previousReleaseDigests.includes(intent.identityDigest) && intent.kind === "platform" && config.kind === "platform") {
|
|
4335
|
+
host.write(INTENT_PATH, `${JSON.stringify({
|
|
4336
|
+
format: 1,
|
|
4337
|
+
kind: config.kind,
|
|
4338
|
+
identityDigest,
|
|
4339
|
+
createdAt: new Date().toISOString()
|
|
4340
|
+
}, null, 2)}
|
|
4341
|
+
`, 384);
|
|
4342
|
+
return identityDigest;
|
|
4343
|
+
}
|
|
4239
4344
|
throw new Error("bootstrap resume coordinates do not match the interrupted host intent");
|
|
4240
4345
|
}
|
|
4241
4346
|
} else if (!host.exists(STATE_PATH)) {
|
|
@@ -4403,7 +4508,42 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4403
4508
|
let installed;
|
|
4404
4509
|
if (host.exists(STATE_PATH))
|
|
4405
4510
|
installed = parseStoredState(host.read(STATE_PATH));
|
|
4406
|
-
|
|
4511
|
+
let bootstrapManifest;
|
|
4512
|
+
let releaseEvidence;
|
|
4513
|
+
if (config.kind === "platform") {
|
|
4514
|
+
const staged = host.exists(config.bootstrapBundle.bundleFile) || host.exists(config.bootstrapBundle.manifestFile);
|
|
4515
|
+
if (staged && !(host.exists(config.bootstrapBundle.bundleFile) && host.exists(config.bootstrapBundle.manifestFile))) {
|
|
4516
|
+
throw new Error("bootstrap bundle and manifest must be staged together");
|
|
4517
|
+
}
|
|
4518
|
+
const reviewed = staged ? await verifyBootstrapBundleOnHost(host, config) : undefined;
|
|
4519
|
+
if (!host.exists(BOOTSTRAP_RELEASE_EVIDENCE)) {
|
|
4520
|
+
if (!reviewed)
|
|
4521
|
+
throw new Error("attended bootstrap bundle and manifest are required for release generation one");
|
|
4522
|
+
bootstrapManifest = reviewed;
|
|
4523
|
+
} else if (reviewed) {
|
|
4524
|
+
let evidence;
|
|
4525
|
+
try {
|
|
4526
|
+
evidence = JSON.parse(host.read(BOOTSTRAP_RELEASE_EVIDENCE));
|
|
4527
|
+
} catch {
|
|
4528
|
+
throw new Error("bootstrap release evidence is malformed");
|
|
4529
|
+
}
|
|
4530
|
+
if (typeof evidence.revision !== "string" || !/^[a-f0-9]{40}$/.test(evidence.revision) || typeof evidence.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(evidence.sha256) || typeof evidence.branch !== "string")
|
|
4531
|
+
throw new Error("bootstrap release evidence is malformed");
|
|
4532
|
+
releaseEvidence = { revision: evidence.revision, sha256: evidence.sha256, branch: evidence.branch };
|
|
4533
|
+
if (evidence.revision !== reviewed.revision || evidence.sha256 !== reviewed.sha256 || evidence.branch !== reviewed.branch)
|
|
4534
|
+
bootstrapManifest = reviewed;
|
|
4535
|
+
} else if (host.exists(BOOTSTRAP_RELEASE_EVIDENCE)) {
|
|
4536
|
+
let evidence;
|
|
4537
|
+
try {
|
|
4538
|
+
evidence = JSON.parse(host.read(BOOTSTRAP_RELEASE_EVIDENCE));
|
|
4539
|
+
} catch {
|
|
4540
|
+
throw new Error("bootstrap release evidence is malformed");
|
|
4541
|
+
}
|
|
4542
|
+
if (typeof evidence.revision !== "string" || !/^[a-f0-9]{40}$/.test(evidence.revision) || typeof evidence.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(evidence.sha256) || typeof evidence.branch !== "string")
|
|
4543
|
+
throw new Error("bootstrap release evidence is malformed");
|
|
4544
|
+
releaseEvidence = { revision: evidence.revision, sha256: evidence.sha256, branch: evidence.branch };
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4407
4547
|
let cloudflare;
|
|
4408
4548
|
if (config.cloudflareHandoff) {
|
|
4409
4549
|
if (host.exists(config.cloudflareHandoff.handoffFile)) {
|
|
@@ -4453,10 +4593,14 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4453
4593
|
throw new Error("platform realtime coordinates require a realtime-enabled Cloudflare handoff");
|
|
4454
4594
|
}
|
|
4455
4595
|
}
|
|
4596
|
+
let allowInterruptedReleaseRebind = false;
|
|
4456
4597
|
if (installed) {
|
|
4457
|
-
|
|
4598
|
+
const exactIdentity = installed.kind === config.kind && installed.identityDigest === bootstrapIdentityDigest(config);
|
|
4599
|
+
const boundedReleaseResume = config.kind === "platform" && !host.exists("/var/lib/forgezero/enrolment.json") && host.exists(BOOTSTRAP_RELEASE_EVIDENCE) && storedPlatformCoordinatesMatch(installed, config);
|
|
4600
|
+
if (!exactIdentity && !boundedReleaseResume) {
|
|
4458
4601
|
throw new Error("bootstrap repair coordinates do not match the installed host identity");
|
|
4459
4602
|
}
|
|
4603
|
+
allowInterruptedReleaseRebind = !exactIdentity && boundedReleaseResume;
|
|
4460
4604
|
}
|
|
4461
4605
|
const platformPrivate = config.kind === "platform" ? (() => {
|
|
4462
4606
|
if (!secrets)
|
|
@@ -4476,10 +4620,14 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4476
4620
|
if (config.kind === "enrolled-compute" && config.bootstrapRunner?.sshPrivateKeyFile && !host.exists(BOOTSTRAP_SSH_CREDENTIAL)) {
|
|
4477
4621
|
privateFile(host, config.bootstrapRunner.sshPrivateKeyFile, "bootstrap runner SSH private key");
|
|
4478
4622
|
}
|
|
4479
|
-
|
|
4623
|
+
let previousReleaseDigests = [];
|
|
4624
|
+
if (!installed && config.kind === "platform" && releaseEvidence && !host.exists("/var/lib/forgezero/enrolment.json")) {
|
|
4625
|
+
previousReleaseDigests = [...interruptedPlatformReleaseIdentityDigests(config, releaseEvidence, bootstrapManifest)];
|
|
4626
|
+
}
|
|
4627
|
+
bindBootstrapIntent(host, config, allowInterruptedReleaseRebind && installed?.identityDigest ? [installed.identityDigest] : previousReleaseDigests);
|
|
4480
4628
|
const plan = planBootstrap(config, host.exists(STATE_PATH));
|
|
4481
4629
|
await checked3(host, ["hostnamectl", "set-hostname", "--static", config.nodeHostname], "compute hostname");
|
|
4482
|
-
|
|
4630
|
+
let alreadyEnrolled = host.exists("/var/lib/forgezero/enrolment.json");
|
|
4483
4631
|
host.mkdir(CREDS, 448);
|
|
4484
4632
|
host.mkdir("/var/lib/forgezero", 448);
|
|
4485
4633
|
if (!alreadyEnrolled && !host.exists(ENROL_CREDENTIAL)) {
|
|
@@ -4508,14 +4656,29 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4508
4656
|
if (connectorCapabilities?.meshConnectorToken && !host.exists(WARP_CONNECTOR_CREDENTIAL)) {
|
|
4509
4657
|
await seal(host, "CF_WARP_CONNECTOR_TOKEN", WARP_CONNECTOR_CREDENTIAL, connectorCapabilities.meshConnectorToken);
|
|
4510
4658
|
}
|
|
4659
|
+
let installedAgentPlan;
|
|
4511
4660
|
if (alreadyEnrolled) {
|
|
4512
|
-
await host.installAgent(config, "bound");
|
|
4661
|
+
installedAgentPlan = await host.installAgent(config, "bound");
|
|
4513
4662
|
} else if (config.kind === "platform") {
|
|
4514
4663
|
if (bootstrapManifest)
|
|
4515
|
-
await host.installAgent(config, "bootstrap");
|
|
4664
|
+
installedAgentPlan = await host.installAgent(config, "bootstrap");
|
|
4665
|
+
else if (host.exists(BOOTSTRAP_RELEASE_EVIDENCE)) {
|
|
4666
|
+
await checked3(host, [
|
|
4667
|
+
"curl",
|
|
4668
|
+
"--fail",
|
|
4669
|
+
"--silent",
|
|
4670
|
+
"--show-error",
|
|
4671
|
+
"--max-time",
|
|
4672
|
+
"10",
|
|
4673
|
+
`http://127.0.0.1:${config.runtime.environment.publicApiPort}${config.runtime.healthPath}`
|
|
4674
|
+
], "persisted release-one API health");
|
|
4675
|
+
await host.installAgent(config, "enrol");
|
|
4676
|
+
installedAgentPlan = await host.installAgent(config, "bound");
|
|
4677
|
+
alreadyEnrolled = true;
|
|
4678
|
+
}
|
|
4516
4679
|
} else {
|
|
4517
4680
|
await host.installAgent(config, "enrol");
|
|
4518
|
-
await host.installAgent(config, "bound");
|
|
4681
|
+
installedAgentPlan = await host.installAgent(config, "bound");
|
|
4519
4682
|
}
|
|
4520
4683
|
if (config.kind === "platform" && config.firewall.enabled) {
|
|
4521
4684
|
await host.ensureSoftware(plan.software.filter(({ id }) => id === "ufw"));
|
|
@@ -4595,6 +4758,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4595
4758
|
await checked3(host, ["useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", runtime.serviceUser], "API service account").catch(async () => {
|
|
4596
4759
|
await checked3(host, ["id", runtime.serviceUser], "existing API service account");
|
|
4597
4760
|
});
|
|
4761
|
+
await checked3(host, ["usermod", "-a", "-G", DEPLOYMENT_GROUP, runtime.serviceUser], "API deployment group");
|
|
4598
4762
|
host.mkdir(runtime.environment.sharedDirectory, 488);
|
|
4599
4763
|
host.mkdir(runtime.slotsDirectory, 493);
|
|
4600
4764
|
host.write(envPath, renderPlatformSharedEnvironment(runtime.environment), 416);
|
|
@@ -4613,7 +4777,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4613
4777
|
host.write("/etc/sudoers.d/forgezero-runner", activation.sudoers, 288);
|
|
4614
4778
|
await checked3(host, ["visudo", "-cf", "/etc/sudoers.d/forgezero-runner"], "activation sudo policy");
|
|
4615
4779
|
const telemetry = planLocalOtlpProof(runtime.environment.otlpEndpoint, runtime.environment.otlpCollectorUnit);
|
|
4616
|
-
await checked3(host, telemetry.unitCheck.argv, "OTLP collector supervision");
|
|
4780
|
+
await checked3(host, [telemetry.unitCheck.command, ...telemetry.unitCheck.argv], "OTLP collector supervision");
|
|
4617
4781
|
const otlpStatus = (await checked3(host, [telemetry.receiverCheck.command, ...telemetry.receiverCheck.argv], "OTLP receiver")).trim();
|
|
4618
4782
|
if (!/^2\d\d$/.test(otlpStatus))
|
|
4619
4783
|
throw new Error(`OTLP receiver returned HTTP ${otlpStatus || "unknown"}`);
|
|
@@ -4646,10 +4810,12 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4646
4810
|
`, 384);
|
|
4647
4811
|
}
|
|
4648
4812
|
if (bootstrapManifest) {
|
|
4813
|
+
if (!installedAgentPlan?.user)
|
|
4814
|
+
throw new Error("initial Agent deployment requires the converged Agent user");
|
|
4649
4815
|
await checked3(host, [
|
|
4650
4816
|
"runuser",
|
|
4651
4817
|
"-u",
|
|
4652
|
-
|
|
4818
|
+
installedAgentPlan.user,
|
|
4653
4819
|
"--",
|
|
4654
4820
|
"/usr/local/bin/fz-agent",
|
|
4655
4821
|
"deploy",
|
|
@@ -4676,7 +4842,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
|
|
|
4676
4842
|
"--show-error",
|
|
4677
4843
|
"--max-time",
|
|
4678
4844
|
"10",
|
|
4679
|
-
`http://127.0.0.1
|
|
4845
|
+
`http://127.0.0.1:${runtime.environment.publicApiPort}${runtime.healthPath}`
|
|
4680
4846
|
], "release-one API health");
|
|
4681
4847
|
await host.installAgent(config, "enrol");
|
|
4682
4848
|
await host.installAgent(config, "bound");
|
|
@@ -4917,6 +5083,8 @@ function planBootstrapAgentInstall(config, phase, context) {
|
|
|
4917
5083
|
bootstrapTargetTelemetryEndpoint: bound && bootstrapRunner ? platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined : undefined,
|
|
4918
5084
|
lifecycleProfilePath: bound && config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
|
|
4919
5085
|
enforceEgress: true,
|
|
5086
|
+
runnerLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort, config.runtime.bluePort, config.runtime.greenPort] : [],
|
|
5087
|
+
agentLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort] : [],
|
|
4920
5088
|
nodeHostname: config.nodeHostname,
|
|
4921
5089
|
telemetryEndpoint: config.telemetryEndpoint,
|
|
4922
5090
|
binPath: "/usr/local/lib/forgezero/agent/fz-agent",
|
|
@@ -4926,7 +5094,7 @@ function planBootstrapAgentInstall(config, phase, context) {
|
|
|
4926
5094
|
enrolStatePath: "/var/lib/forgezero/enrolment.json"
|
|
4927
5095
|
} : bound ? { enrolStatePath: "/var/lib/forgezero/enrolment.json" } : {},
|
|
4928
5096
|
...authenticated ? {
|
|
4929
|
-
apiUrl: config.apiUrl,
|
|
5097
|
+
apiUrl: config.kind === "platform" ? `http://127.0.0.1:${config.runtime.environment.publicApiPort}` : config.apiUrl,
|
|
4930
5098
|
project: config.kind === "enrolled-compute" ? config.realm : "platform",
|
|
4931
5099
|
environment: config.kind === "enrolled-compute" ? undefined : config.environment,
|
|
4932
5100
|
nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
|
|
@@ -4935,6 +5103,7 @@ function planBootstrapAgentInstall(config, phase, context) {
|
|
|
4935
5103
|
return planInstall(options);
|
|
4936
5104
|
}
|
|
4937
5105
|
function localBootstrapHost() {
|
|
5106
|
+
let softwareClientUser;
|
|
4938
5107
|
const execute = async (argv, options = {}) => {
|
|
4939
5108
|
const child = Bun.spawn([...argv], { stdin: options.stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
|
|
4940
5109
|
if (options.stdin !== undefined && child.stdin && typeof child.stdin !== "number") {
|
|
@@ -4967,10 +5136,12 @@ function localBootstrapHost() {
|
|
|
4967
5136
|
exec: execute,
|
|
4968
5137
|
sleep: (milliseconds) => Bun.sleep(milliseconds),
|
|
4969
5138
|
async ensureSoftware(requirements) {
|
|
5139
|
+
if (!softwareClientUser)
|
|
5140
|
+
throw new Error("Agent must be converged before software requirements are applied");
|
|
4970
5141
|
const result = await execute([
|
|
4971
5142
|
"runuser",
|
|
4972
5143
|
"-u",
|
|
4973
|
-
|
|
5144
|
+
softwareClientUser,
|
|
4974
5145
|
"--",
|
|
4975
5146
|
"/usr/local/bin/fz-agent",
|
|
4976
5147
|
"software-ensure",
|
|
@@ -4984,7 +5155,7 @@ function localBootstrapHost() {
|
|
|
4984
5155
|
const capabilities = await readCapabilities(localRunner);
|
|
4985
5156
|
const hasBinding = existsSync5("/var/lib/forgezero/enrolment.json");
|
|
4986
5157
|
const hasEnrolCredential = existsSync5(ENROL_CREDENTIAL);
|
|
4987
|
-
const initialBundle = config.kind === "platform" &&
|
|
5158
|
+
const initialBundle = config.kind === "platform" && phase === "bootstrap" && existsSync5(config.bootstrapBundle.bundleFile) && existsSync5(config.bootstrapBundle.manifestFile) ? {
|
|
4988
5159
|
path: config.bootstrapBundle.bundleFile,
|
|
4989
5160
|
manifestPath: config.bootstrapBundle.manifestFile,
|
|
4990
5161
|
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync5(config.bootstrapBundle.manifestFile, "utf8")))
|
|
@@ -5015,6 +5186,7 @@ function localBootstrapHost() {
|
|
|
5015
5186
|
writeFileSync5(unit.path, unit.unit, { mode: 420 });
|
|
5016
5187
|
}
|
|
5017
5188
|
await applyPlan(plan, localRunner);
|
|
5189
|
+
softwareClientUser = plan.user;
|
|
5018
5190
|
return plan;
|
|
5019
5191
|
}
|
|
5020
5192
|
};
|
|
@@ -5028,6 +5200,8 @@ export {
|
|
|
5028
5200
|
planBootstrapAgentInstall,
|
|
5029
5201
|
planBootstrap,
|
|
5030
5202
|
localBootstrapHost,
|
|
5203
|
+
legacyBootstrapIdentityDigest,
|
|
5204
|
+
interruptedPlatformReleaseIdentityDigests,
|
|
5031
5205
|
bootstrapStatus,
|
|
5032
5206
|
bootstrapIdentityDigest,
|
|
5033
5207
|
applyBootstrap,
|