@agent-sh/agent-workspace-linux 0.1.1 → 0.1.3
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 +3 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +118 -14
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@ npm distribution wrapper for the [`agent-workspace-linux`](https://github.com/ag
|
|
|
8
8
|
npm install -g @agent-sh/agent-workspace-linux
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The installer automatically downloads the prebuilt binary for your architecture
|
|
11
|
+
The installer automatically downloads the prebuilt binary for your architecture
|
|
12
|
+
from the matching [GitHub Release](https://github.com/agent-sh/agent-workspace-linux/releases),
|
|
13
|
+
then verifies the required `<asset>.sha256` sidecar before installing it.
|
|
12
14
|
|
|
13
15
|
**Linux only.** `x64` (x86_64) and `arm64` (aarch64) are supported.
|
|
14
16
|
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
// error at runtime. To recover, re-run installation with scripts enabled or
|
|
8
8
|
// invoke this file directly: `node node_modules/@agent-sh/agent-workspace-linux/scripts/postinstall.js`
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
10
|
+
// Release contract: every prebuilt binary asset must have a matching
|
|
11
|
+
// `<asset>.sha256` sidecar, generated by the GitHub release workflow. The
|
|
12
|
+
// installer verifies the sidecar before moving the binary into place.
|
|
13
13
|
|
|
14
14
|
"use strict";
|
|
15
15
|
|
|
16
|
+
const crypto = require("crypto");
|
|
16
17
|
const https = require("https");
|
|
17
18
|
const fs = require("fs");
|
|
18
19
|
const path = require("path");
|
|
19
|
-
const { execFileSync } = require("child_process");
|
|
20
20
|
|
|
21
21
|
// ── Platform / arch guard ────────────────────────────────────────────────────
|
|
22
22
|
|
|
@@ -50,10 +50,12 @@ const assetName = `agent-workspace-linux-${rustTarget}`;
|
|
|
50
50
|
const downloadUrl =
|
|
51
51
|
`https://github.com/agent-sh/agent-workspace-linux/releases/download/` +
|
|
52
52
|
`v${version}/${assetName}`;
|
|
53
|
+
const checksumUrl = `${downloadUrl}.sha256`;
|
|
53
54
|
|
|
54
55
|
const binDir = path.join(__dirname, "..", "bin");
|
|
55
56
|
const destPath = path.join(binDir, "agent-workspace-linux");
|
|
56
57
|
const tmpPath = destPath + ".tmp";
|
|
58
|
+
const tmpChecksumPath = tmpPath + ".sha256";
|
|
57
59
|
|
|
58
60
|
// ── Download helper (follows redirects, max 5 hops) ─────────────────────────
|
|
59
61
|
|
|
@@ -83,7 +85,8 @@ function download(url, tmpFile, maxRedirects = 5) {
|
|
|
83
85
|
return reject(
|
|
84
86
|
new Error(
|
|
85
87
|
`Failed to download ${url}: HTTP ${statusCode}. ` +
|
|
86
|
-
"Check that the release exists
|
|
88
|
+
"Check that the release exists, includes the binary and .sha256 sidecar, " +
|
|
89
|
+
"and the version in package.json matches."
|
|
87
90
|
)
|
|
88
91
|
);
|
|
89
92
|
}
|
|
@@ -110,17 +113,83 @@ function download(url, tmpFile, maxRedirects = 5) {
|
|
|
110
113
|
});
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
function removeFileQuietly(file) {
|
|
117
|
+
try {
|
|
118
|
+
fs.unlinkSync(file);
|
|
119
|
+
} catch (_) {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseSha256Sidecar(contents, expectedAssetName) {
|
|
123
|
+
const lines = contents
|
|
124
|
+
.split(/\r?\n/)
|
|
125
|
+
.map((line) => line.trim())
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
|
|
128
|
+
if (lines.length === 0) {
|
|
129
|
+
throw new Error("checksum sidecar is empty");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const candidates = [];
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
const match = line.match(/^([a-fA-F0-9]{64})(?:\s+(?:\*)?(.+))?$/);
|
|
135
|
+
if (!match) {
|
|
136
|
+
throw new Error(`invalid checksum sidecar line: ${line}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const [, checksum, fileName] = match;
|
|
140
|
+
candidates.push({
|
|
141
|
+
checksum: checksum.toLowerCase(),
|
|
142
|
+
fileName: fileName ? fileName.trim() : null,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const namedMatch = candidates.find(
|
|
147
|
+
({ fileName }) => fileName && path.basename(fileName) === expectedAssetName
|
|
148
|
+
);
|
|
149
|
+
if (namedMatch) {
|
|
150
|
+
return namedMatch.checksum;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new Error(
|
|
154
|
+
`checksum sidecar does not contain an entry for ${expectedAssetName}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function fileSha256(file) {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const hash = crypto.createHash("sha256");
|
|
161
|
+
const input = fs.createReadStream(file);
|
|
162
|
+
|
|
163
|
+
input.on("error", reject);
|
|
164
|
+
input.on("data", (chunk) => hash.update(chunk));
|
|
165
|
+
input.on("end", () => resolve(hash.digest("hex")));
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function verifyChecksum(binaryFile, sidecarFile, expectedAssetName) {
|
|
170
|
+
const expected = parseSha256Sidecar(
|
|
171
|
+
fs.readFileSync(sidecarFile, "utf8"),
|
|
172
|
+
expectedAssetName
|
|
173
|
+
);
|
|
174
|
+
const actual = await fileSha256(binaryFile);
|
|
175
|
+
|
|
176
|
+
if (actual !== expected) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`checksum mismatch for ${expectedAssetName}: expected ${expected}, got ${actual}`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
113
183
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
114
184
|
|
|
115
|
-
|
|
185
|
+
async function main() {
|
|
116
186
|
// Ensure bin/ directory exists (it should, tracked by git via .gitkeep, but
|
|
117
187
|
// be defensive for edge-case installs).
|
|
118
188
|
fs.mkdirSync(binDir, { recursive: true });
|
|
119
189
|
|
|
120
190
|
// Remove stale tmp file from a previous interrupted install.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
} catch (_) {}
|
|
191
|
+
removeFileQuietly(tmpPath);
|
|
192
|
+
removeFileQuietly(tmpChecksumPath);
|
|
124
193
|
|
|
125
194
|
console.log(
|
|
126
195
|
`agent-workspace-linux: downloading ${assetName} v${version}…`
|
|
@@ -131,13 +200,35 @@ function download(url, tmpFile, maxRedirects = 5) {
|
|
|
131
200
|
await download(downloadUrl, tmpPath);
|
|
132
201
|
} catch (err) {
|
|
133
202
|
// Clean up in case the file was partially created.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
203
|
+
removeFileQuietly(tmpPath);
|
|
204
|
+
// The download error text embeds the request URL (built from
|
|
205
|
+
// package.json's version + the target triple). Log a fixed message rather
|
|
206
|
+
// than interpolating that value, to avoid a log-injection sink; the URL
|
|
207
|
+
// itself is already printed above for debugging.
|
|
208
|
+
console.error(
|
|
209
|
+
"\nagent-workspace-linux: download failed. Verify the release exists " +
|
|
210
|
+
"for your platform and that the version in package.json matches " +
|
|
211
|
+
"(see the URL logged above)."
|
|
212
|
+
);
|
|
138
213
|
process.exit(1);
|
|
139
214
|
}
|
|
140
215
|
|
|
216
|
+
console.log(`agent-workspace-linux: downloading checksum for ${assetName}`);
|
|
217
|
+
console.log(` URL: ${checksumUrl}`);
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
await download(checksumUrl, tmpChecksumPath);
|
|
221
|
+
await verifyChecksum(tmpPath, tmpChecksumPath, assetName);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
removeFileQuietly(tmpPath);
|
|
224
|
+
removeFileQuietly(tmpChecksumPath);
|
|
225
|
+
console.error(
|
|
226
|
+
`\nagent-workspace-linux: checksum verification failed — ${err.message}`
|
|
227
|
+
);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
removeFileQuietly(tmpChecksumPath);
|
|
231
|
+
|
|
141
232
|
// Atomic rename: avoids leaving a half-written binary.
|
|
142
233
|
try {
|
|
143
234
|
fs.renameSync(tmpPath, destPath);
|
|
@@ -160,4 +251,17 @@ function download(url, tmpFile, maxRedirects = 5) {
|
|
|
160
251
|
}
|
|
161
252
|
|
|
162
253
|
console.log(`agent-workspace-linux: binary installed at ${destPath}`);
|
|
163
|
-
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (require.main === module) {
|
|
257
|
+
main().catch((err) => {
|
|
258
|
+
console.error(`agent-workspace-linux: postinstall failed — ${err.message}`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
parseSha256Sidecar,
|
|
265
|
+
fileSha256,
|
|
266
|
+
verifyChecksum,
|
|
267
|
+
};
|