@binoctal/open-agents-bridge 0.6.1 → 0.6.2
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/install.js +81 -11
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -2,8 +2,11 @@ const https = require("https");
|
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const os = require("os");
|
|
5
|
+
const crypto = require("crypto");
|
|
5
6
|
const { execSync } = require("child_process");
|
|
6
7
|
|
|
8
|
+
const PACKAGE_VERSION = require("./package.json").version;
|
|
9
|
+
|
|
7
10
|
const REPO_OWNER = "binoctal";
|
|
8
11
|
const REPO_NAME = "open-agents-bridge";
|
|
9
12
|
const BINARY_NAME = "open-agents-bridge";
|
|
@@ -61,6 +64,66 @@ function fetchJSON(url) {
|
|
|
61
64
|
});
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
function fetchText(url) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
https
|
|
70
|
+
.get(url, { headers: { "User-Agent": "open-agents-bridge-npm" } }, (res) => {
|
|
71
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
72
|
+
return fetchText(res.headers.location).then(resolve, reject);
|
|
73
|
+
}
|
|
74
|
+
if (res.statusCode !== 200) {
|
|
75
|
+
return reject(new Error(`HTTP ${res.statusCode} from ${url}`));
|
|
76
|
+
}
|
|
77
|
+
let data = "";
|
|
78
|
+
res.on("data", (chunk) => (data += chunk));
|
|
79
|
+
res.on("end", () => resolve(data));
|
|
80
|
+
})
|
|
81
|
+
.on("error", reject);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function sha256(filePath) {
|
|
86
|
+
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Verify the download against the release's own checksums.txt. A missing
|
|
90
|
+
// checksums.txt aborts rather than skipping the check: whoever can swap an
|
|
91
|
+
// asset can also make checksums.txt 404, so "skip when absent" verifies
|
|
92
|
+
// nothing at all. The bridge is a long-lived daemon that takes commands from
|
|
93
|
+
// a remote, and install time is the one cheap chance to confirm the bytes are
|
|
94
|
+
// the ones that were published.
|
|
95
|
+
async function verifyChecksum(release, asset, filePath) {
|
|
96
|
+
const checksums = release.assets.find((a) => a.name === "checksums.txt");
|
|
97
|
+
if (!checksums) {
|
|
98
|
+
fs.unlinkSync(filePath);
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Release ${release.tag_name} has no checksums.txt; refusing to install an unverified binary`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const text = await fetchText(checksums.browser_download_url);
|
|
105
|
+
const line = text
|
|
106
|
+
.split("\n")
|
|
107
|
+
.map((l) => l.trim().split(/\s+/))
|
|
108
|
+
.find((parts) => parts[1] === asset.name || parts[1] === `*${asset.name}`);
|
|
109
|
+
|
|
110
|
+
if (!line) {
|
|
111
|
+
fs.unlinkSync(filePath);
|
|
112
|
+
throw new Error(`checksums.txt does not list ${asset.name}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const expected = line[0].toLowerCase();
|
|
116
|
+
const actual = sha256(filePath);
|
|
117
|
+
if (expected !== actual) {
|
|
118
|
+
fs.unlinkSync(filePath);
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Checksum mismatch for ${asset.name}\n expected: ${expected}\n actual: ${actual}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(`Checksum verified (sha256 ${actual.slice(0, 16)}…)`);
|
|
125
|
+
}
|
|
126
|
+
|
|
64
127
|
function downloadFile(url, destPath) {
|
|
65
128
|
return new Promise((resolve, reject) => {
|
|
66
129
|
const file = fs.createWriteStream(destPath);
|
|
@@ -94,18 +157,23 @@ async function install() {
|
|
|
94
157
|
|
|
95
158
|
console.log(`Detecting platform: ${goos}/${goarch}`);
|
|
96
159
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
160
|
+
// Fetch the release matching THIS package's version, not "latest". Pulling
|
|
161
|
+
// latest means the npm version and the binary version have nothing to do
|
|
162
|
+
// with each other: a lockfile pins the package but not what the package
|
|
163
|
+
// downloads, so the same lockfile installs different binaries over time.
|
|
164
|
+
const tag = `v${PACKAGE_VERSION}`;
|
|
165
|
+
const apiUrl = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${tag}`;
|
|
166
|
+
console.log(`Fetching release ${tag}...`);
|
|
100
167
|
|
|
101
168
|
let release;
|
|
102
169
|
try {
|
|
103
170
|
release = await fetchJSON(apiUrl);
|
|
104
171
|
} catch (err) {
|
|
172
|
+
console.error(`Failed to fetch release ${tag} of ${REPO_OWNER}/${REPO_NAME}.`);
|
|
173
|
+
console.error(` Error: ${err.message}`);
|
|
105
174
|
console.error(
|
|
106
|
-
"
|
|
175
|
+
" This package installs the binary built for its own version; it does not fall back to the latest release."
|
|
107
176
|
);
|
|
108
|
-
console.error(` Error: ${err.message}`);
|
|
109
177
|
process.exit(1);
|
|
110
178
|
}
|
|
111
179
|
|
|
@@ -133,9 +201,12 @@ async function install() {
|
|
|
133
201
|
|
|
134
202
|
console.log(`Downloading ${asset.name}...`);
|
|
135
203
|
|
|
204
|
+
const downloadPath = path.join(binDir, asset.name);
|
|
205
|
+
await downloadFile(asset.browser_download_url, downloadPath);
|
|
206
|
+
await verifyChecksum(release, asset, downloadPath);
|
|
207
|
+
|
|
136
208
|
if (asset.name.endsWith(".tar.gz")) {
|
|
137
|
-
const tmpArchive =
|
|
138
|
-
await downloadFile(asset.browser_download_url, tmpArchive);
|
|
209
|
+
const tmpArchive = downloadPath;
|
|
139
210
|
|
|
140
211
|
// Extract the binary from tarball
|
|
141
212
|
try {
|
|
@@ -150,8 +221,7 @@ async function install() {
|
|
|
150
221
|
}
|
|
151
222
|
fs.unlinkSync(tmpArchive);
|
|
152
223
|
} else if (asset.name.endsWith(".zip")) {
|
|
153
|
-
const tmpArchive =
|
|
154
|
-
await downloadFile(asset.browser_download_url, tmpArchive);
|
|
224
|
+
const tmpArchive = downloadPath;
|
|
155
225
|
|
|
156
226
|
// Extract using built-in or system unzip
|
|
157
227
|
try {
|
|
@@ -167,8 +237,8 @@ async function install() {
|
|
|
167
237
|
}
|
|
168
238
|
fs.unlinkSync(tmpArchive);
|
|
169
239
|
} else {
|
|
170
|
-
// Plain binary
|
|
171
|
-
|
|
240
|
+
// Plain binary — already downloaded and verified, just put it in place.
|
|
241
|
+
fs.renameSync(downloadPath, binaryPath);
|
|
172
242
|
}
|
|
173
243
|
|
|
174
244
|
// Make executable (unix)
|