@orbit-tools/cli 0.6.0 → 0.7.0

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 CHANGED
@@ -3,9 +3,9 @@
3
3
  npm proxy for the [Orbit](https://github.com/danieljhkim/orbit) CLI.
4
4
 
5
5
  On install, downloads the matching prebuilt `orbit` binary from
6
- [GitHub Releases](https://github.com/danieljhkim/orbit/releases), verifies its
7
- SHA-256 against the published `orbit-checksums.txt`, and exposes it as the
8
- `orbit` command.
6
+ [GitHub Releases](https://github.com/danieljhkim/orbit/releases), authenticates
7
+ the signed `orbit-checksums.txt` with the package-pinned release trust set,
8
+ verifies the archive SHA-256, and exposes it as the `orbit` command.
9
9
 
10
10
  ## Usage
11
11
 
@@ -31,8 +31,12 @@ Windows is not currently published. Use WSL or build from source.
31
31
 
32
32
  | Variable | Effect |
33
33
  |---|---|
34
- | `ORBIT_BINARY` | Path to a local `orbit` binary; bypasses download. |
35
- | `ORBIT_BINARY_VERSION` | Override the release tag to install (e.g. `v0.3.1`). |
34
+ | `ORBIT_BINARY` | Path to a local `orbit` binary; bypasses download and trusts that path as the binary source. |
35
+ | `ORBIT_BINARY_VERSION` | Override the release tag to install (e.g. `v0.3.1`); this changes the release-selection trust boundary but still requires a valid Orbit checksum signature. |
36
+ | `ORBIT_RELEASE_PUBLIC_KEY_FILE` | **Deprecated** in favor of `ORBIT_RELEASE_TRUSTED_KEYS_FILE`. Single-key override for the trusted checksum-signing public key; requires `ORBIT_RELEASE_PUBLIC_KEY_FILE_ACKNOWLEDGE_TRUST_CHANGE=1`, logs a deprecation notice when active. |
37
+ | `ORBIT_RELEASE_PUBLIC_KEY_FILE_ACKNOWLEDGE_TRUST_CHANGE=1` | Required acknowledgement that `ORBIT_RELEASE_PUBLIC_KEY_FILE` replaces the release authenticity trust root. |
38
+ | `ORBIT_RELEASE_TRUSTED_KEYS_FILE` | Preferred test/operations override for the full trusted signing-key set, including key IDs, `not_after`, and `revoked_at`; requires `ORBIT_RELEASE_TRUSTED_KEYS_FILE_ACKNOWLEDGE_TRUST_CHANGE=1` and logs when active. |
39
+ | `ORBIT_RELEASE_TRUSTED_KEYS_FILE_ACKNOWLEDGE_TRUST_CHANGE=1` | Required acknowledgement that `ORBIT_RELEASE_TRUSTED_KEYS_FILE` replaces the release authenticity trust root. |
36
40
  | `ORBIT_SKIP_DOWNLOAD=1` | Skip postinstall download (lazy install on first run still works). |
37
41
 
38
42
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbit-tools/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "npm proxy for the Orbit CLI. Downloads the matching native binary from GitHub Releases on install and forwards all invocations.",
5
5
  "bin": {
6
6
  "orbit": "bin/orbit.js"
@@ -12,6 +12,7 @@
12
12
  "files": [
13
13
  "bin/",
14
14
  "scripts/",
15
+ "release-signing.pub",
15
16
  "README.md",
16
17
  "LICENSE"
17
18
  ],
@@ -0,0 +1,11 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuZ8vNa+DusYhrFBXNhBh
3
+ RSqn81AYe7tYEtCKImWGuy/6ziMHqDzDKHSku0sBMwcdLXBzI0RjNBacLCbbYr4H
4
+ icmYrsKqqfLGf+CWfrqDqY9d3hwUPtVMRp/ynVNW6nwKAmNl5dTgUc6ZBAZTtQtt
5
+ qwMD1JIOsrJ3vVDL9o3alcXcg/RyL0pGUo+vep2QZOjXnCGoJN3NeytQHag3zJyd
6
+ Wq4psc7j2H1Nb5EoyY/I/7vpdwME3Mrv2ffwtDmr0/+73q1yWUDf4btY9Ba7sOhE
7
+ Ir2UHm3bEboo1ErAYjjiDDuF/NjzZcZpJtuNbdj0vI7pHDyDZ7sKiEX7RkUO+e2c
8
+ IouiSfRJRrwnjpuergrq3ehNjkxcn5dFST1l23FOXGsy4F7ilrF6P9cgaAsE8dc7
9
+ CS9YgUE1ErfGJLZtfDDGKs6+E+7JiC1C3z7xwmfzOgv9gEvSlfrx2BbGl8esypKm
10
+ pYZDkW2dLqPeFj/WwGhZoYFHv0GOMIWdi6FNriQdkn4RAgMBAAE=
11
+ -----END PUBLIC KEY-----
@@ -10,12 +10,60 @@ const os = require('node:os');
10
10
 
11
11
  const PKG = require('../package.json');
12
12
  const BINARY_REPO = PKG.config.orbit.binaryRepo;
13
+ const CHECKSUM_FILE = 'orbit-checksums.txt';
14
+ const CHECKSUM_SIGNATURE_FILE = 'orbit-checksums.txt.sig';
13
15
  // Convention: npm package version is kept in lockstep with the orbit release tag.
14
16
  // `0.3.1` → fetches `v0.3.1` from GitHub Releases. Override with $ORBIT_BINARY_VERSION.
15
17
  const BINARY_VERSION = process.env.ORBIT_BINARY_VERSION || `v${PKG.version}`;
16
18
  const PKG_ROOT = path.resolve(__dirname, '..');
17
19
  const BIN_DIR = path.join(PKG_ROOT, 'binaries');
18
20
  const BIN_PATH = path.join(BIN_DIR, process.platform === 'win32' ? 'orbit.exe' : 'orbit');
21
+ const PUBLIC_KEY_OVERRIDE = process.env.ORBIT_RELEASE_PUBLIC_KEY_FILE;
22
+ const PUBLIC_KEY_OVERRIDE_ACK_ENV = 'ORBIT_RELEASE_PUBLIC_KEY_FILE_ACKNOWLEDGE_TRUST_CHANGE';
23
+ const TRUSTED_KEYS_OVERRIDE = process.env.ORBIT_RELEASE_TRUSTED_KEYS_FILE;
24
+ const TRUSTED_KEYS_OVERRIDE_ACK_ENV = 'ORBIT_RELEASE_TRUSTED_KEYS_FILE_ACKNOWLEDGE_TRUST_CHANGE';
25
+ const TRUSTED_PUBLIC_KEY_PATH = PUBLIC_KEY_OVERRIDE
26
+ ? path.resolve(PUBLIC_KEY_OVERRIDE)
27
+ : path.join(PKG_ROOT, 'release-signing.pub');
28
+ const TRUSTED_KEYS_OVERRIDE_PATH = TRUSTED_KEYS_OVERRIDE ? path.resolve(TRUSTED_KEYS_OVERRIDE) : null;
29
+ const TRUSTED_RELEASE_KEYS = Object.freeze([
30
+ Object.freeze({
31
+ id: 'orbit-release-key-1',
32
+ notAfter: '2027-12-31',
33
+ revokedAt: null,
34
+ publicKeyPem: `-----BEGIN PUBLIC KEY-----
35
+ MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuZ8vNa+DusYhrFBXNhBh
36
+ RSqn81AYe7tYEtCKImWGuy/6ziMHqDzDKHSku0sBMwcdLXBzI0RjNBacLCbbYr4H
37
+ icmYrsKqqfLGf+CWfrqDqY9d3hwUPtVMRp/ynVNW6nwKAmNl5dTgUc6ZBAZTtQtt
38
+ qwMD1JIOsrJ3vVDL9o3alcXcg/RyL0pGUo+vep2QZOjXnCGoJN3NeytQHag3zJyd
39
+ Wq4psc7j2H1Nb5EoyY/I/7vpdwME3Mrv2ffwtDmr0/+73q1yWUDf4btY9Ba7sOhE
40
+ Ir2UHm3bEboo1ErAYjjiDDuF/NjzZcZpJtuNbdj0vI7pHDyDZ7sKiEX7RkUO+e2c
41
+ IouiSfRJRrwnjpuergrq3ehNjkxcn5dFST1l23FOXGsy4F7ilrF6P9cgaAsE8dc7
42
+ CS9YgUE1ErfGJLZtfDDGKs6+E+7JiC1C3z7xwmfzOgv9gEvSlfrx2BbGl8esypKm
43
+ pYZDkW2dLqPeFj/WwGhZoYFHv0GOMIWdi6FNriQdkn4RAgMBAAE=
44
+ -----END PUBLIC KEY-----
45
+ `,
46
+ }),
47
+ Object.freeze({
48
+ id: 'orbit-release-key-2',
49
+ notAfter: '2028-12-31',
50
+ revokedAt: null,
51
+ publicKeyPem: `-----BEGIN PUBLIC KEY-----
52
+ MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA22OLkoJXb7PX/QwG7FzA
53
+ eiDT0BctOq77WyXysUBlN1718pbjo30wLIW9d2h+cxPtld2PM35NQ4NMZJwl7lb2
54
+ tZSWVUTRI9Se5eBhUEL6Gi4Rin4/In3NIx0YEVdA6SUA+x3OinPpe1BIN1pKGbpD
55
+ P6GohwmrdbCUuZMv29UYPkREif6gnlehxj9ypZfZMVU7+VXOceeA5OT5iXbO4u29
56
+ 85bSys4JUN+SKtAao9BAjHCvMUJ4kL4mnGeteXRssmd5ehkEezUhWUNtP/uvKv+I
57
+ 5pjSto5vq6vqZMEpkOPDANPSUwz3F0CgyNmwHU8LKHtME+ZQqOP11xOC5Rgtw3zZ
58
+ VQSAd3BLsSflu7ENV9lsbwPjvhSRDPlZsGUB4NGjUmFkUJPz7SGqT9LNXQqaRT2S
59
+ 8mHajs8Z/pkIMnOSOE339DHmT3xPz5eKRwZLBWttguJxHWFM50PT7g+K97Y6n9Zr
60
+ zLEax5f3s3F9vPiWfA36hKJS5GzF564hCRViOQPqWnNlAgMBAAE=
61
+ -----END PUBLIC KEY-----
62
+ `,
63
+ }),
64
+ ]);
65
+ let publicKeyOverrideLogged = false;
66
+ let trustedKeysOverrideLogged = false;
19
67
 
20
68
  function log(msg) {
21
69
  process.stderr.write(`@orbit-tools/cli: ${msg}\n`);
@@ -78,13 +126,215 @@ function parseChecksums(text) {
78
126
  return out;
79
127
  }
80
128
 
81
- function extractTarGz(archivePath, destDir) {
82
- const result = spawnSync('tar', ['-xzf', archivePath, '-C', destDir], { stdio: 'inherit' });
129
+ function acknowledgeTrustedPublicKeyOverride() {
130
+ if (PUBLIC_KEY_OVERRIDE && TRUSTED_KEYS_OVERRIDE) {
131
+ throw new Error('ORBIT_RELEASE_PUBLIC_KEY_FILE and ORBIT_RELEASE_TRUSTED_KEYS_FILE cannot both be set');
132
+ }
133
+ if (!PUBLIC_KEY_OVERRIDE) {
134
+ return;
135
+ }
136
+ if (process.env[PUBLIC_KEY_OVERRIDE_ACK_ENV] !== '1') {
137
+ throw new Error(`ORBIT_RELEASE_PUBLIC_KEY_FILE requires ${PUBLIC_KEY_OVERRIDE_ACK_ENV}=1`);
138
+ }
139
+ if (!publicKeyOverrideLogged) {
140
+ log(`ORBIT_RELEASE_PUBLIC_KEY_FILE=${TRUSTED_PUBLIC_KEY_PATH} set; trusting replacement release signing key`);
141
+ log('ORBIT_RELEASE_PUBLIC_KEY_FILE is deprecated; prefer ORBIT_RELEASE_TRUSTED_KEYS_FILE for the full trust set (key IDs, notAfter, revokedAt)');
142
+ publicKeyOverrideLogged = true;
143
+ }
144
+ }
145
+
146
+ function acknowledgeTrustedKeysOverride() {
147
+ if (PUBLIC_KEY_OVERRIDE && TRUSTED_KEYS_OVERRIDE) {
148
+ throw new Error('ORBIT_RELEASE_PUBLIC_KEY_FILE and ORBIT_RELEASE_TRUSTED_KEYS_FILE cannot both be set');
149
+ }
150
+ if (!TRUSTED_KEYS_OVERRIDE) {
151
+ return;
152
+ }
153
+ if (process.env[TRUSTED_KEYS_OVERRIDE_ACK_ENV] !== '1') {
154
+ throw new Error(`ORBIT_RELEASE_TRUSTED_KEYS_FILE requires ${TRUSTED_KEYS_OVERRIDE_ACK_ENV}=1`);
155
+ }
156
+ if (!trustedKeysOverrideLogged) {
157
+ log(`ORBIT_RELEASE_TRUSTED_KEYS_FILE=${TRUSTED_KEYS_OVERRIDE_PATH} set; trusting replacement release signing key set`);
158
+ trustedKeysOverrideLogged = true;
159
+ }
160
+ }
161
+
162
+ function readTrustedKeysManifest(manifestPath) {
163
+ const manifestDir = path.dirname(manifestPath);
164
+ return fs
165
+ .readFileSync(manifestPath, 'utf8')
166
+ .split(/\r?\n/)
167
+ .map((line) => line.trim())
168
+ .filter((line) => line.length > 0 && !line.startsWith('#'))
169
+ .map((line) => {
170
+ const [id, notAfter, revokedAt, publicKeyPath, ...extra] = line.split('|');
171
+ if (!id || !publicKeyPath || extra.length > 0) {
172
+ throw new Error(`invalid trusted release signing key record: ${line}`);
173
+ }
174
+ const resolvedPublicKeyPath = path.isAbsolute(publicKeyPath)
175
+ ? publicKeyPath
176
+ : path.resolve(manifestDir, publicKeyPath);
177
+ return {
178
+ id,
179
+ notAfter: notAfter || null,
180
+ revokedAt: revokedAt || null,
181
+ publicKeyPem: fs.readFileSync(resolvedPublicKeyPath, 'utf8'),
182
+ };
183
+ });
184
+ }
185
+
186
+ function normalizeTrustedReleaseKeys(trustedKeys) {
187
+ if (typeof trustedKeys === 'string') {
188
+ return [
189
+ {
190
+ id: 'override',
191
+ notAfter: null,
192
+ revokedAt: null,
193
+ publicKeyPem: trustedKeys,
194
+ },
195
+ ];
196
+ }
197
+
198
+ if (!Array.isArray(trustedKeys) || trustedKeys.length === 0) {
199
+ throw new Error('at least one trusted release signing key is required');
200
+ }
201
+
202
+ return trustedKeys.map((key) => {
203
+ if (!key || !key.id || !key.publicKeyPem) {
204
+ throw new Error('trusted release signing keys require id and publicKeyPem');
205
+ }
206
+ const notAfter = key.notAfter || null;
207
+ const revokedAt = key.revokedAt || null;
208
+ // Mirror the shell-side awk regex (release_date_number) so a malformed
209
+ // override like notAfter: "next month" fails closed instead of silently
210
+ // becoming "never expires" under lexicographic comparison.
211
+ if (notAfter !== null && !/^\d{4}-\d{2}-\d{2}$/.test(notAfter)) {
212
+ throw new Error(`trusted release signing key ${key.id} has invalid notAfter: ${notAfter} (expected YYYY-MM-DD)`);
213
+ }
214
+ if (revokedAt !== null && !/^\d{4}-\d{2}-\d{2}$/.test(revokedAt)) {
215
+ throw new Error(`trusted release signing key ${key.id} has invalid revokedAt: ${revokedAt} (expected YYYY-MM-DD)`);
216
+ }
217
+ return {
218
+ id: key.id,
219
+ notAfter,
220
+ revokedAt,
221
+ publicKeyPem: key.publicKeyPem,
222
+ };
223
+ });
224
+ }
225
+
226
+ function readTrustedReleaseKeys() {
227
+ acknowledgeTrustedPublicKeyOverride();
228
+ acknowledgeTrustedKeysOverride();
229
+
230
+ if (PUBLIC_KEY_OVERRIDE) {
231
+ return normalizeTrustedReleaseKeys(fs.readFileSync(TRUSTED_PUBLIC_KEY_PATH, 'utf8'));
232
+ }
233
+ if (TRUSTED_KEYS_OVERRIDE_PATH) {
234
+ return normalizeTrustedReleaseKeys(readTrustedKeysManifest(TRUSTED_KEYS_OVERRIDE_PATH));
235
+ }
236
+ return TRUSTED_RELEASE_KEYS;
237
+ }
238
+
239
+ function releaseDateString(date = new Date()) {
240
+ return date.toISOString().slice(0, 10);
241
+ }
242
+
243
+ function assertTrustedKeyUsable(key, verificationDate = new Date()) {
244
+ if (key.revokedAt) {
245
+ throw new Error(`release checksum signature was made by revoked release signing key ${key.id} (revoked ${key.revokedAt})`);
246
+ }
247
+ if (key.notAfter && releaseDateString(verificationDate) > key.notAfter) {
248
+ throw new Error(`release checksum signature was made by expired release signing key ${key.id} (not_after ${key.notAfter})`);
249
+ }
250
+ }
251
+
252
+ function verifyWithPublicKey(checksumText, signatureBuf, publicKeyPem) {
253
+ const verifier = crypto.createVerify('RSA-SHA256');
254
+ verifier.update(checksumText, 'utf8');
255
+ verifier.end();
256
+ return verifier.verify(publicKeyPem, signatureBuf);
257
+ }
258
+
259
+ function verifyChecksumSignature(
260
+ checksumText,
261
+ signatureBuf,
262
+ trustedKeys = readTrustedReleaseKeys(),
263
+ verificationDate = new Date()
264
+ ) {
265
+ for (const key of normalizeTrustedReleaseKeys(trustedKeys)) {
266
+ if (verifyWithPublicKey(checksumText, signatureBuf, key.publicKeyPem)) {
267
+ assertTrustedKeyUsable(key, verificationDate);
268
+ log(`authenticated ${CHECKSUM_FILE} with release signing key ${key.id}`);
269
+ return key.id;
270
+ }
271
+ }
272
+ throw new Error(`release checksum signature verification failed for ${CHECKSUM_FILE}: no trusted release signing key matched`);
273
+ }
274
+
275
+ function verifyArchiveChecksum(asset, archiveBuf, checksumText) {
276
+ const checksums = parseChecksums(checksumText);
277
+ const expected = checksums[asset];
278
+ if (!expected) {
279
+ throw new Error(`checksum entry for ${asset} was not found in ${CHECKSUM_FILE}`);
280
+ }
281
+ const actual = sha256(archiveBuf);
282
+ if (actual !== expected) {
283
+ throw new Error(`checksum mismatch for ${asset}: expected ${expected}, got ${actual}`);
284
+ }
285
+ }
286
+
287
+ function runTar(args, options = {}) {
288
+ const result = spawnSync('tar', args, { encoding: 'utf8', ...options });
289
+ if (result.error) {
290
+ throw result.error;
291
+ }
83
292
  if (result.status !== 0) {
84
- fail(`tar extraction failed (status ${result.status}). Is 'tar' installed?`);
293
+ const stderr = result.stderr ? `: ${result.stderr.trim()}` : '';
294
+ throw new Error(`tar ${args.join(' ')} failed (status ${result.status})${stderr}`);
295
+ }
296
+ return result.stdout || '';
297
+ }
298
+
299
+ function validateArchiveMemberName(member) {
300
+ if (member === 'orbit') {
301
+ return;
302
+ }
303
+ if (!member || member.startsWith('/') || member.includes('..') || path.isAbsolute(member)) {
304
+ throw new Error(`unsafe release archive member: ${member || '<empty>'}`);
305
+ }
306
+ throw new Error(`unexpected release archive member: ${member}`);
307
+ }
308
+
309
+ function validateArchiveMembers(archivePath) {
310
+ const members = runTar(['-tzf', archivePath])
311
+ .split(/\r?\n/)
312
+ .filter((line) => line.length > 0);
313
+ if (members.length !== 1) {
314
+ throw new Error("release archive must contain only the 'orbit' binary");
315
+ }
316
+ validateArchiveMemberName(members[0]);
317
+ }
318
+
319
+ function validateExtractedBinary(extractedBinary) {
320
+ const stat = fs.lstatSync(extractedBinary);
321
+ if (stat.isSymbolicLink()) {
322
+ throw new Error('release archive member is a symlink: orbit');
323
+ }
324
+ if (!stat.isFile()) {
325
+ throw new Error('release archive member must be a regular file: orbit');
326
+ }
327
+ if (stat.nlink !== 1) {
328
+ throw new Error('release archive member must not be a hard link: orbit');
85
329
  }
86
330
  }
87
331
 
332
+ function extractTarGz(archivePath, destDir) {
333
+ validateArchiveMembers(archivePath);
334
+ runTar(['-xzf', archivePath, '-C', destDir, 'orbit'], { stdio: 'inherit' });
335
+ validateExtractedBinary(path.join(destDir, 'orbit'));
336
+ }
337
+
88
338
  async function main() {
89
339
  if (process.env.ORBIT_SKIP_DOWNLOAD === '1') {
90
340
  log('ORBIT_SKIP_DOWNLOAD=1 set; skipping binary download.');
@@ -99,12 +349,23 @@ async function main() {
99
349
  const asset = `orbit-${target}.tar.gz`;
100
350
  const baseUrl = `https://github.com/${BINARY_REPO}/releases/download/${BINARY_VERSION}`;
101
351
  const archiveUrl = `${baseUrl}/${asset}`;
102
- const checksumUrl = `${baseUrl}/orbit-checksums.txt`;
352
+ const checksumUrl = `${baseUrl}/${CHECKSUM_FILE}`;
353
+ const checksumSignatureUrl = `${baseUrl}/${CHECKSUM_SIGNATURE_FILE}`;
103
354
 
104
355
  log(`installing orbit ${BINARY_VERSION} for ${target}...`);
105
356
 
106
357
  fs.mkdirSync(BIN_DIR, { recursive: true });
107
358
 
359
+ let checksumText;
360
+ let signatureBuf;
361
+ try {
362
+ checksumText = (await fetchBuffer(checksumUrl)).toString('utf8');
363
+ signatureBuf = await fetchBuffer(checksumSignatureUrl);
364
+ verifyChecksumSignature(checksumText, signatureBuf);
365
+ } catch (err) {
366
+ fail(`could not authenticate ${CHECKSUM_FILE}: ${err.message}`);
367
+ }
368
+
108
369
  let archiveBuf;
109
370
  try {
110
371
  archiveBuf = await fetchBuffer(archiveUrl);
@@ -113,35 +374,45 @@ async function main() {
113
374
  }
114
375
 
115
376
  try {
116
- const checksumText = (await fetchBuffer(checksumUrl)).toString('utf8');
117
- const checksums = parseChecksums(checksumText);
118
- const expected = checksums[asset];
119
- if (!expected) {
120
- fail(`checksum entry for ${asset} was not found in orbit-checksums.txt`);
121
- } else {
122
- const actual = sha256(archiveBuf);
123
- if (actual !== expected) {
124
- fail(`checksum mismatch for ${asset}: expected ${expected}, got ${actual}`);
125
- }
126
- }
377
+ verifyArchiveChecksum(asset, archiveBuf, checksumText);
127
378
  } catch (err) {
128
- fail(`could not verify checksum for ${asset}: ${err.message}`);
379
+ fail(err.message);
129
380
  }
130
381
 
131
382
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'orbit-cli-'));
132
- const archivePath = path.join(tmpDir, asset);
133
- fs.writeFileSync(archivePath, archiveBuf);
134
- extractTarGz(archivePath, tmpDir);
383
+ try {
384
+ const archivePath = path.join(tmpDir, asset);
385
+ fs.writeFileSync(archivePath, archiveBuf);
386
+ extractTarGz(archivePath, tmpDir);
135
387
 
136
- const extractedBinary = path.join(tmpDir, 'orbit');
137
- if (!fs.existsSync(extractedBinary)) {
138
- fail(`extracted archive did not contain 'orbit' binary at ${extractedBinary}`);
388
+ const extractedBinary = path.join(tmpDir, 'orbit');
389
+ if (!fs.existsSync(extractedBinary)) {
390
+ fail(`extracted archive did not contain 'orbit' binary at ${extractedBinary}`);
391
+ }
392
+ fs.copyFileSync(extractedBinary, BIN_PATH);
393
+ fs.chmodSync(BIN_PATH, 0o755);
394
+ } finally {
395
+ fs.rmSync(tmpDir, { recursive: true, force: true });
139
396
  }
140
- fs.copyFileSync(extractedBinary, BIN_PATH);
141
- fs.chmodSync(BIN_PATH, 0o755);
142
- fs.rmSync(tmpDir, { recursive: true, force: true });
143
397
 
144
398
  log(`installed orbit binary at ${BIN_PATH}`);
145
399
  }
146
400
 
147
- main().catch((err) => fail(err && err.message ? err.message : String(err)));
401
+ if (require.main === module) {
402
+ main().catch((err) => fail(err && err.message ? err.message : String(err)));
403
+ }
404
+
405
+ module.exports = {
406
+ TRUSTED_RELEASE_KEYS,
407
+ parseChecksums,
408
+ sha256,
409
+ acknowledgeTrustedPublicKeyOverride,
410
+ acknowledgeTrustedKeysOverride,
411
+ extractTarGz,
412
+ normalizeTrustedReleaseKeys,
413
+ readTrustedReleaseKeys,
414
+ validateArchiveMembers,
415
+ validateExtractedBinary,
416
+ verifyArchiveChecksum,
417
+ verifyChecksumSignature,
418
+ };