@forgezero/agent 0.1.78 → 0.1.80
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 +57 -6
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +21 -6
- package/dist/community-rehearsal-host.js +29 -7
- package/dist/definition.js +29 -7
- package/dist/deploy-compiler.js +50 -2
- package/dist/deploy-file.js +29 -7
- package/dist/deploy-plan-runner.js +50 -2
- package/dist/deploy-plan.js +50 -2
- package/dist/deploy.d.ts +37 -1
- package/dist/deploy.js +1 -0
- package/dist/deployment-connectivity.d.ts +26 -0
- package/dist/deployment-connectivity.js +128 -1
- package/dist/deployment-targets.js +15 -1
- package/dist/deployment-topology.d.ts +46 -0
- package/dist/deployment-topology.js +113 -0
- package/dist/deployment.d.ts +40 -0
- package/dist/fz-agent.js +397 -21
- package/dist/fz.js +75 -12
- package/dist/index.d.ts +1 -0
- package/dist/metal-bootstrap.js +1 -1
- package/dist/metal-helper-socket.js +29 -7
- package/dist/metal-provision.js +29 -7
- package/dist/operator-bootstrap.js +21 -6
- package/dist/platform-bootstrap-runtime.d.ts +4 -1
- package/dist/platform-bootstrap-runtime.js +47 -11
- package/dist/platform-fleet-verification.js +176 -13
- package/dist/platform-genesis.js +29 -7
- package/dist/provision.js +158 -9
- package/dist/software-helper.js +157 -8
- package/dist/software.d.ts +1 -0
- package/dist/software.js +30 -7
- package/dist/ubuntu.js +29 -7
- package/dist/version.d.ts +1 -1
- package/package.json +5 -1
package/dist/software-helper.js
CHANGED
|
@@ -165,16 +165,38 @@ var runSoftwareCommand = async (argv, env = {}) => {
|
|
|
165
165
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
166
166
|
};
|
|
167
167
|
var run = runSoftwareCommand;
|
|
168
|
+
var softwareDownloadCommand = (url, destination, maximumBytes) => {
|
|
169
|
+
const source = new URL(url);
|
|
170
|
+
if (source.protocol !== "https:" || source.username || source.password)
|
|
171
|
+
throw new Error("software download must use credential-free HTTPS");
|
|
172
|
+
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1)
|
|
173
|
+
throw new Error("software download size bound is invalid");
|
|
174
|
+
return [
|
|
175
|
+
"/usr/bin/curl",
|
|
176
|
+
"--fail",
|
|
177
|
+
"--location",
|
|
178
|
+
"--silent",
|
|
179
|
+
"--show-error",
|
|
180
|
+
"--proto",
|
|
181
|
+
"=https",
|
|
182
|
+
"--tlsv1.2",
|
|
183
|
+
"--max-time",
|
|
184
|
+
"1800",
|
|
185
|
+
"--max-filesize",
|
|
186
|
+
String(maximumBytes),
|
|
187
|
+
"--output",
|
|
188
|
+
destination,
|
|
189
|
+
source.href
|
|
190
|
+
];
|
|
191
|
+
};
|
|
168
192
|
var download = async (url, destination, sha256, maximumBytes = 512 * 1024 * 1024) => {
|
|
169
|
-
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(30 * 60000) });
|
|
170
|
-
if (!response.ok)
|
|
171
|
-
throw new Error(`download failed with HTTP ${response.status}`);
|
|
172
|
-
const declared = Number(response.headers.get("content-length") ?? 0);
|
|
173
|
-
if (declared > maximumBytes)
|
|
174
|
-
throw new Error("download exceeds reviewed size bound");
|
|
175
193
|
if (existsSync(destination))
|
|
176
194
|
throw new Error("download destination already exists");
|
|
177
|
-
await
|
|
195
|
+
const received = await run(softwareDownloadCommand(url, destination, maximumBytes));
|
|
196
|
+
if (received.exitCode !== 0) {
|
|
197
|
+
rmSync(destination, { force: true });
|
|
198
|
+
throw new Error(`software download failed (${received.exitCode}): ${received.output.trim()}`);
|
|
199
|
+
}
|
|
178
200
|
chmodSync(destination, 384);
|
|
179
201
|
if (statSync(destination).size > maximumBytes) {
|
|
180
202
|
rmSync(destination, { force: true });
|
|
@@ -949,6 +971,7 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
|
|
|
949
971
|
// src/deployment-connectivity.ts
|
|
950
972
|
import { createHash as createHash2 } from "node:crypto";
|
|
951
973
|
import { mkdirSync as mkdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
974
|
+
import { createConnection } from "node:net";
|
|
952
975
|
import { dirname as dirname2 } from "node:path";
|
|
953
976
|
|
|
954
977
|
// src/process-input.ts
|
|
@@ -1001,6 +1024,21 @@ var defaultHost = {
|
|
|
1001
1024
|
const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
|
|
1002
1025
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
1003
1026
|
},
|
|
1027
|
+
probe: (address, port, timeoutMs) => new Promise((resolve) => {
|
|
1028
|
+
const socket = createConnection({ host: address, port });
|
|
1029
|
+
let settled = false;
|
|
1030
|
+
const finish = (value) => {
|
|
1031
|
+
if (settled)
|
|
1032
|
+
return;
|
|
1033
|
+
settled = true;
|
|
1034
|
+
socket.destroy();
|
|
1035
|
+
resolve(value);
|
|
1036
|
+
};
|
|
1037
|
+
socket.setTimeout(timeoutMs);
|
|
1038
|
+
socket.once("connect", () => finish(true));
|
|
1039
|
+
socket.once("timeout", () => finish(false));
|
|
1040
|
+
socket.once("error", () => finish(false));
|
|
1041
|
+
}),
|
|
1004
1042
|
sleep: (ms) => Bun.sleep(ms)
|
|
1005
1043
|
};
|
|
1006
1044
|
function validate(request) {
|
|
@@ -1015,7 +1053,7 @@ function validate(request) {
|
|
|
1015
1053
|
} else if (request.capabilities.public)
|
|
1016
1054
|
throw new Error("unexpected public deployment capability");
|
|
1017
1055
|
if (privateIntent?.mode === "cloudflare-warp") {
|
|
1018
|
-
if (!NAME2.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !TOKEN.test(request.capabilities.private.connectorToken)) {
|
|
1056
|
+
if (!NAME2.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !UUID.test(request.capabilities.private.siteRouteId) || !TOKEN.test(request.capabilities.private.connectorToken)) {
|
|
1019
1057
|
throw new Error("private deployment connectivity is malformed");
|
|
1020
1058
|
}
|
|
1021
1059
|
} else if (request.capabilities.private)
|
|
@@ -1025,6 +1063,22 @@ async function applyDeploymentConnectivity(request, host = defaultHost) {
|
|
|
1025
1063
|
validate(request);
|
|
1026
1064
|
const id = idFor(request.key);
|
|
1027
1065
|
const evidence = { key: request.key };
|
|
1066
|
+
const topology = request.topology;
|
|
1067
|
+
if (topology) {
|
|
1068
|
+
const values = [topology.localRelayAddress, ...topology.localPeerAddresses, ...topology.remoteRelayAddresses];
|
|
1069
|
+
const identities = [
|
|
1070
|
+
topology.nodeIdentity,
|
|
1071
|
+
...topology.localPeerIdentities,
|
|
1072
|
+
...topology.remoteRelayIdentities,
|
|
1073
|
+
...topology.memberIdentities
|
|
1074
|
+
];
|
|
1075
|
+
if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) => !/^(?:\d{1,3}\.){3}\d{1,3}$/.test(value)) || identities.some((value) => !/^[A-Za-z0-9.-]{1,253}$/.test(value)) || topology.localPeerIdentities.length !== topology.localPeerAddresses.length || topology.remoteRelayIdentities.length !== topology.remoteRelayAddresses.length || topology.memberIdentities.length < 1 || topology.memberIdentities.length > 1024 || new Set(topology.memberIdentities).size !== topology.memberIdentities.length || !topology.memberIdentities.includes(topology.nodeIdentity) || !/^sha256:[a-f0-9]{64}$/.test(topology.generation) || (topology.role === "member" ? topology.remoteRelayAddresses.length !== 0 : topology.remoteSiteCidrs.length !== topology.remoteRelayAddresses.length) || topology.remoteSiteCidrs.some((value) => !/^(?:\d{1,3}\.){3}0\/24$/.test(value)) || new Set(topology.remoteSiteCidrs).size !== topology.remoteSiteCidrs.length || !Number.isSafeInteger(topology.healthPort) || topology.healthPort < 1 || topology.healthPort > 65535 || topology.routedTcpPorts.length < 1 || topology.routedTcpPorts.length > 64 || new Set(topology.routedTcpPorts).size !== topology.routedTcpPorts.length || !topology.routedTcpPorts.includes(topology.healthPort) || topology.routedTcpPorts.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65535) || topology.role === "member" && topology.transport !== "private-lan") {
|
|
1076
|
+
throw new Error("deployment topology is malformed");
|
|
1077
|
+
}
|
|
1078
|
+
if (request.intent.private?.mode === "cloudflare-warp" !== (topology.transport === "cloudflare-warp")) {
|
|
1079
|
+
throw new Error("deployment topology transport differs from private connectivity intent");
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1028
1082
|
if (request.intent.public?.mode === "cloudflare-tunnel") {
|
|
1029
1083
|
const capability = request.capabilities.public;
|
|
1030
1084
|
const credentialName = `FZ_TUNNEL_${id.toUpperCase()}`;
|
|
@@ -1062,6 +1116,12 @@ WantedBy=multi-user.target
|
|
|
1062
1116
|
const capability = request.capabilities.private;
|
|
1063
1117
|
const credentialPath = "/etc/forgezero/creds/CF_WARP_CONNECTOR_TOKEN.cred";
|
|
1064
1118
|
const unit = "forgezero-deployment-mesh.service";
|
|
1119
|
+
const forwarding = "/etc/sysctl.d/99-forgezero-mesh.conf";
|
|
1120
|
+
host.write(forwarding, `net.ipv4.ip_forward = 1
|
|
1121
|
+
net.ipv6.conf.all.forwarding = 1
|
|
1122
|
+
net.ipv6.conf.all.accept_ra = 2
|
|
1123
|
+
`, 420);
|
|
1124
|
+
await checked(host, ["/usr/sbin/sysctl", "-p", forwarding], "Mesh subnet forwarding");
|
|
1065
1125
|
await host.seal("CF_WARP_CONNECTOR_TOKEN", credentialPath, capability.connectorToken);
|
|
1066
1126
|
host.write(`/etc/systemd/system/${unit}`, `[Unit]
|
|
1067
1127
|
Description=ForgeZero deployment Mesh/WARP connector
|
|
@@ -1096,6 +1156,95 @@ WantedBy=multi-user.target
|
|
|
1096
1156
|
if (!evidence.private)
|
|
1097
1157
|
throw new Error("WARP connector readiness failed");
|
|
1098
1158
|
}
|
|
1159
|
+
if (topology) {
|
|
1160
|
+
const syncAddresses = topology.role === "relay" ? [...topology.localPeerAddresses, ...topology.remoteRelayAddresses] : [topology.localRelayAddress];
|
|
1161
|
+
const topologyEpoch = `topology-${topology.generation.slice("sha256:".length, "sha256:".length + 48)}`;
|
|
1162
|
+
host.write("/etc/forgezero/deployment-topology.env", [
|
|
1163
|
+
`FZ_TOPOLOGY_NODE_IDENTITY=${topology.nodeIdentity}`,
|
|
1164
|
+
`FZ_TOPOLOGY_PEER_ADDRESSES=${[...new Set(syncAddresses)].join(",")}`,
|
|
1165
|
+
`FZ_TOPOLOGY_MEMBERS=${topology.memberIdentities.join(",")}`,
|
|
1166
|
+
`FZ_TOPOLOGY_EPOCH=${topologyEpoch}`,
|
|
1167
|
+
`FZ_TOPOLOGY_SITE=${topology.site}`,
|
|
1168
|
+
`FZ_TOPOLOGY_ROLE=${topology.role}`,
|
|
1169
|
+
`FZ_TOPOLOGY_TRANSPORT=${topology.transport}`,
|
|
1170
|
+
""
|
|
1171
|
+
].join(`
|
|
1172
|
+
`), 384);
|
|
1173
|
+
const routeUnit = "forgezero-deployment-topology.service";
|
|
1174
|
+
const routeUnitPath = `/etc/systemd/system/${routeUnit}`;
|
|
1175
|
+
const routePairs = topology.role === "member" ? topology.remoteSiteCidrs.map((network) => ({ network, via: topology.localRelayAddress })) : topology.transport === "private-lan" ? topology.remoteSiteCidrs.map((network, index) => ({ network, via: topology.remoteRelayAddresses[index] })) : [];
|
|
1176
|
+
for (const { via } of routePairs) {
|
|
1177
|
+
await checked(host, ["/usr/sbin/ip", "route", "get", via], `private route gateway ${via}`);
|
|
1178
|
+
}
|
|
1179
|
+
await host.exec(["/usr/bin/systemctl", "disable", "--now", routeUnit]);
|
|
1180
|
+
const forwarding = topology.role !== "member" && topology.remoteSiteCidrs.length > 0 ? `ExecStart=/usr/sbin/sysctl -w net.ipv4.ip_forward=1
|
|
1181
|
+
` : "";
|
|
1182
|
+
const starts = routePairs.map(({ network, via }) => `ExecStart=/usr/sbin/ip route replace ${network} via ${via}`).join(`
|
|
1183
|
+
`);
|
|
1184
|
+
const stops = routePairs.map(({ network, via }) => `ExecStop=-/usr/sbin/ip route del ${network} via ${via}`).join(`
|
|
1185
|
+
`);
|
|
1186
|
+
const noOp = !forwarding && !starts ? `ExecStart=/usr/bin/true
|
|
1187
|
+
` : "";
|
|
1188
|
+
host.write(routeUnitPath, `[Unit]
|
|
1189
|
+
Description=ForgeZero fenced deployment site routing
|
|
1190
|
+
After=network-online.target${topology.transport === "cloudflare-warp" ? " forgezero-deployment-mesh.service" : ""}
|
|
1191
|
+
Wants=network-online.target
|
|
1192
|
+
|
|
1193
|
+
[Service]
|
|
1194
|
+
Type=oneshot
|
|
1195
|
+
RemainAfterExit=yes
|
|
1196
|
+
${forwarding}${noOp}${starts}${starts ? `
|
|
1197
|
+
` : ""}${stops}${stops ? `
|
|
1198
|
+
` : ""}
|
|
1199
|
+
[Install]
|
|
1200
|
+
WantedBy=multi-user.target
|
|
1201
|
+
`, 420);
|
|
1202
|
+
for (const source of [...new Set([topology.siteCidr, ...topology.remoteSiteCidrs])]) {
|
|
1203
|
+
for (const port of topology.routedTcpPorts) {
|
|
1204
|
+
await checked(host, ["/usr/sbin/ufw", "allow", "from", source, "to", "any", "port", String(port), "proto", "tcp"], `private service ${source}:${port}`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (topology.role !== "member")
|
|
1208
|
+
for (const remoteCidr of topology.remoteSiteCidrs) {
|
|
1209
|
+
for (const port of topology.routedTcpPorts) {
|
|
1210
|
+
await checked(host, [
|
|
1211
|
+
"/usr/sbin/ufw",
|
|
1212
|
+
"route",
|
|
1213
|
+
"allow",
|
|
1214
|
+
"proto",
|
|
1215
|
+
"tcp",
|
|
1216
|
+
"from",
|
|
1217
|
+
topology.siteCidr,
|
|
1218
|
+
"to",
|
|
1219
|
+
remoteCidr,
|
|
1220
|
+
"port",
|
|
1221
|
+
String(port)
|
|
1222
|
+
], `private routed service ${topology.siteCidr}->${remoteCidr}:${port}`);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
await checked(host, ["/usr/bin/systemctl", "daemon-reload"], "topology daemon reload");
|
|
1226
|
+
await checked(host, ["/usr/bin/systemctl", "enable", "--now", routeUnit], "topology routes");
|
|
1227
|
+
const probes = topology.role === "member" ? [topology.localRelayAddress] : topology.remoteRelayAddresses;
|
|
1228
|
+
for (const address of [...new Set(probes)]) {
|
|
1229
|
+
await checked(host, ["/usr/sbin/ip", "route", "get", address], `private route ${address}`);
|
|
1230
|
+
let ready = false;
|
|
1231
|
+
for (let attempt = 0;attempt < 10; attempt += 1) {
|
|
1232
|
+
if (await host.probe(address, topology.healthPort, 2000)) {
|
|
1233
|
+
ready = true;
|
|
1234
|
+
break;
|
|
1235
|
+
}
|
|
1236
|
+
await host.sleep(1000);
|
|
1237
|
+
}
|
|
1238
|
+
if (!ready)
|
|
1239
|
+
throw new Error(`private topology relay ${address}:${topology.healthPort} is unreachable`);
|
|
1240
|
+
}
|
|
1241
|
+
evidence.topology = {
|
|
1242
|
+
site: topology.site,
|
|
1243
|
+
siteCidr: topology.siteCidr,
|
|
1244
|
+
role: topology.role,
|
|
1245
|
+
probed: [...new Set(probes)]
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1099
1248
|
return evidence;
|
|
1100
1249
|
}
|
|
1101
1250
|
|
package/dist/software.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export declare const OS_CATALOG: readonly OsCatalogEntry[];
|
|
|
57
57
|
export declare const SOFTWARE_CATALOG: readonly SoftwareCatalogEntry[];
|
|
58
58
|
export declare const softwareSpawnFailure: (cause: unknown, argv: readonly string[]) => SoftwareCommandResult | undefined;
|
|
59
59
|
export declare const runSoftwareCommand: (argv: readonly string[], env?: Record<string, string>) => Promise<SoftwareCommandResult>;
|
|
60
|
+
export declare const softwareDownloadCommand: (url: string, destination: string, maximumBytes: number) => readonly string[];
|
|
60
61
|
/** Closed, fixed-argv privileged strategy executor. No repository value becomes a command. */
|
|
61
62
|
export declare function executeSoftwareOperation(operation: SoftwareOperation): Promise<SoftwareCommandResult>;
|
|
62
63
|
export declare function observeSoftwareHost(osRelease?: string, architecture?: NodeJS.Architecture): SoftwareObservation;
|
package/dist/software.js
CHANGED
|
@@ -165,16 +165,38 @@ var runSoftwareCommand = async (argv, env = {}) => {
|
|
|
165
165
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
166
166
|
};
|
|
167
167
|
var run = runSoftwareCommand;
|
|
168
|
+
var softwareDownloadCommand = (url, destination, maximumBytes) => {
|
|
169
|
+
const source = new URL(url);
|
|
170
|
+
if (source.protocol !== "https:" || source.username || source.password)
|
|
171
|
+
throw new Error("software download must use credential-free HTTPS");
|
|
172
|
+
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1)
|
|
173
|
+
throw new Error("software download size bound is invalid");
|
|
174
|
+
return [
|
|
175
|
+
"/usr/bin/curl",
|
|
176
|
+
"--fail",
|
|
177
|
+
"--location",
|
|
178
|
+
"--silent",
|
|
179
|
+
"--show-error",
|
|
180
|
+
"--proto",
|
|
181
|
+
"=https",
|
|
182
|
+
"--tlsv1.2",
|
|
183
|
+
"--max-time",
|
|
184
|
+
"1800",
|
|
185
|
+
"--max-filesize",
|
|
186
|
+
String(maximumBytes),
|
|
187
|
+
"--output",
|
|
188
|
+
destination,
|
|
189
|
+
source.href
|
|
190
|
+
];
|
|
191
|
+
};
|
|
168
192
|
var download = async (url, destination, sha256, maximumBytes = 512 * 1024 * 1024) => {
|
|
169
|
-
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(30 * 60000) });
|
|
170
|
-
if (!response.ok)
|
|
171
|
-
throw new Error(`download failed with HTTP ${response.status}`);
|
|
172
|
-
const declared = Number(response.headers.get("content-length") ?? 0);
|
|
173
|
-
if (declared > maximumBytes)
|
|
174
|
-
throw new Error("download exceeds reviewed size bound");
|
|
175
193
|
if (existsSync(destination))
|
|
176
194
|
throw new Error("download destination already exists");
|
|
177
|
-
await
|
|
195
|
+
const received = await run(softwareDownloadCommand(url, destination, maximumBytes));
|
|
196
|
+
if (received.exitCode !== 0) {
|
|
197
|
+
rmSync(destination, { force: true });
|
|
198
|
+
throw new Error(`software download failed (${received.exitCode}): ${received.output.trim()}`);
|
|
199
|
+
}
|
|
178
200
|
chmodSync(destination, 384);
|
|
179
201
|
if (statSync(destination).size > maximumBytes) {
|
|
180
202
|
rmSync(destination, { force: true });
|
|
@@ -518,6 +540,7 @@ async function ensureSoftwareRequirements(requirementsInput, options) {
|
|
|
518
540
|
export {
|
|
519
541
|
validateSoftwareRequirements,
|
|
520
542
|
softwareSpawnFailure,
|
|
543
|
+
softwareDownloadCommand,
|
|
521
544
|
runSoftwareCommand,
|
|
522
545
|
observeSoftwareHost,
|
|
523
546
|
executeSoftwareOperation,
|
package/dist/ubuntu.js
CHANGED
|
@@ -165,16 +165,38 @@ var runSoftwareCommand = async (argv, env = {}) => {
|
|
|
165
165
|
return { exitCode, output: `${stdout}${stderr}` };
|
|
166
166
|
};
|
|
167
167
|
var run = runSoftwareCommand;
|
|
168
|
+
var softwareDownloadCommand = (url, destination, maximumBytes) => {
|
|
169
|
+
const source = new URL(url);
|
|
170
|
+
if (source.protocol !== "https:" || source.username || source.password)
|
|
171
|
+
throw new Error("software download must use credential-free HTTPS");
|
|
172
|
+
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1)
|
|
173
|
+
throw new Error("software download size bound is invalid");
|
|
174
|
+
return [
|
|
175
|
+
"/usr/bin/curl",
|
|
176
|
+
"--fail",
|
|
177
|
+
"--location",
|
|
178
|
+
"--silent",
|
|
179
|
+
"--show-error",
|
|
180
|
+
"--proto",
|
|
181
|
+
"=https",
|
|
182
|
+
"--tlsv1.2",
|
|
183
|
+
"--max-time",
|
|
184
|
+
"1800",
|
|
185
|
+
"--max-filesize",
|
|
186
|
+
String(maximumBytes),
|
|
187
|
+
"--output",
|
|
188
|
+
destination,
|
|
189
|
+
source.href
|
|
190
|
+
];
|
|
191
|
+
};
|
|
168
192
|
var download = async (url, destination, sha256, maximumBytes = 512 * 1024 * 1024) => {
|
|
169
|
-
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(30 * 60000) });
|
|
170
|
-
if (!response.ok)
|
|
171
|
-
throw new Error(`download failed with HTTP ${response.status}`);
|
|
172
|
-
const declared = Number(response.headers.get("content-length") ?? 0);
|
|
173
|
-
if (declared > maximumBytes)
|
|
174
|
-
throw new Error("download exceeds reviewed size bound");
|
|
175
193
|
if (existsSync(destination))
|
|
176
194
|
throw new Error("download destination already exists");
|
|
177
|
-
await
|
|
195
|
+
const received = await run(softwareDownloadCommand(url, destination, maximumBytes));
|
|
196
|
+
if (received.exitCode !== 0) {
|
|
197
|
+
rmSync(destination, { force: true });
|
|
198
|
+
throw new Error(`software download failed (${received.exitCode}): ${received.output.trim()}`);
|
|
199
|
+
}
|
|
178
200
|
chmodSync(destination, 384);
|
|
179
201
|
if (statSync(destination).size > maximumBytes) {
|
|
180
202
|
rmSync(destination, { force: true });
|
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.80";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgezero/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.80",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "tsc --noEmit",
|
|
@@ -88,6 +88,10 @@
|
|
|
88
88
|
"types": "./dist/deployment-targets.d.ts",
|
|
89
89
|
"default": "./dist/deployment-targets.js"
|
|
90
90
|
},
|
|
91
|
+
"./deployment-topology": {
|
|
92
|
+
"types": "./dist/deployment-topology.d.ts",
|
|
93
|
+
"default": "./dist/deployment-topology.js"
|
|
94
|
+
},
|
|
91
95
|
"./deployment-connectivity": {
|
|
92
96
|
"types": "./dist/deployment-connectivity.d.ts",
|
|
93
97
|
"default": "./dist/deployment-connectivity.js"
|