78 lines
3.6 KiB
JavaScript
78 lines
3.6 KiB
JavaScript
|
|
// Installs the pinned s3d release into Pithos's own bin folder. The digests are
|
||
|
|
// GitHub's published sha256 for each release asset; a download that does not
|
||
|
|
// match is discarded before anything is written to the bin folder.
|
||
|
|
|
||
|
|
import fs from 'node:fs';
|
||
|
|
import path from 'node:path';
|
||
|
|
import crypto from 'node:crypto';
|
||
|
|
import zlib from 'node:zlib';
|
||
|
|
|
||
|
|
export const S3D_VERSION = '0.2.0';
|
||
|
|
|
||
|
|
const ASSETS = {
|
||
|
|
'win32-x64': { file: 's3d_windows_amd64.zip', sha256: '239356ef55228d03aa1889c7208357b83faf66947ade2cba4f7a3893c632c473' },
|
||
|
|
'linux-x64': { file: 's3d_linux_amd64.zip', sha256: 'e537d68be8b6749e3e239d572c497a4f1ebc81ef5a3b70d5ec3cc9ffa6498e73' },
|
||
|
|
'linux-arm64': { file: 's3d_linux_arm64.zip', sha256: '76848764f09922f31489c809514cdc928b3882836d6e5b64dcac085a84b4958b' },
|
||
|
|
'darwin-x64': { file: 's3d_darwin_amd64.zip', sha256: 'd7fe282efab35242b001ed9040d833334034682225b4c2b6b2eda67ecca8992e' },
|
||
|
|
'darwin-arm64': { file: 's3d_darwin_arm64.zip', sha256: '9eb6b73cb30b5b6b54fbbc583af6f7d9e56a30c55247ac70568918a1c98a3353' },
|
||
|
|
};
|
||
|
|
|
||
|
|
export function assetForPlatform() {
|
||
|
|
return ASSETS[`${process.platform}-${process.arch}`] || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function installS3d(binDir, onProgress = () => {}) {
|
||
|
|
const asset = assetForPlatform();
|
||
|
|
if (!asset) throw new Error(`no s3d release for ${process.platform}-${process.arch}`);
|
||
|
|
const url = `https://github.com/SiaFoundation/s3d/releases/download/v${S3D_VERSION}/${asset.file}`;
|
||
|
|
onProgress({ phase: 'download', url });
|
||
|
|
const res = await fetch(url, { redirect: 'follow' });
|
||
|
|
if (!res.ok) throw new Error(`download failed: HTTP ${res.status}`);
|
||
|
|
const zip = Buffer.from(await res.arrayBuffer());
|
||
|
|
const digest = crypto.createHash('sha256').update(zip).digest('hex');
|
||
|
|
if (digest !== asset.sha256) throw new Error(`checksum mismatch for ${asset.file} (got ${digest})`);
|
||
|
|
onProgress({ phase: 'extract' });
|
||
|
|
|
||
|
|
const exe = process.platform === 'win32' ? 's3d.exe' : 's3d';
|
||
|
|
const data = extractFromZip(zip, (name) => path.basename(name) === exe);
|
||
|
|
if (!data) throw new Error(`${exe} not found in ${asset.file}`);
|
||
|
|
fs.mkdirSync(binDir, { recursive: true });
|
||
|
|
const target = path.join(binDir, exe);
|
||
|
|
const tmp = target + '.new';
|
||
|
|
fs.writeFileSync(tmp, data, { mode: 0o755 });
|
||
|
|
fs.renameSync(tmp, target);
|
||
|
|
onProgress({ phase: 'done', path: target });
|
||
|
|
return target;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Minimal zip reader: walks the central directory and inflates one entry.
|
||
|
|
function extractFromZip(buf, match) {
|
||
|
|
let eocd = -1;
|
||
|
|
for (let i = buf.length - 22; i >= Math.max(0, buf.length - 65557); i--) {
|
||
|
|
if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break; }
|
||
|
|
}
|
||
|
|
if (eocd < 0) throw new Error('not a zip file');
|
||
|
|
const count = buf.readUInt16LE(eocd + 10);
|
||
|
|
let p = buf.readUInt32LE(eocd + 16);
|
||
|
|
for (let n = 0; n < count; n++) {
|
||
|
|
if (buf.readUInt32LE(p) !== 0x02014b50) throw new Error('corrupt zip directory');
|
||
|
|
const method = buf.readUInt16LE(p + 10);
|
||
|
|
const csize = buf.readUInt32LE(p + 20);
|
||
|
|
const nameLen = buf.readUInt16LE(p + 28);
|
||
|
|
const extraLen = buf.readUInt16LE(p + 30);
|
||
|
|
const commentLen = buf.readUInt16LE(p + 32);
|
||
|
|
const local = buf.readUInt32LE(p + 42);
|
||
|
|
const name = buf.toString('utf8', p + 46, p + 46 + nameLen);
|
||
|
|
p += 46 + nameLen + extraLen + commentLen;
|
||
|
|
if (!match(name)) continue;
|
||
|
|
const lName = buf.readUInt16LE(local + 26);
|
||
|
|
const lExtra = buf.readUInt16LE(local + 28);
|
||
|
|
const start = local + 30 + lName + lExtra;
|
||
|
|
const raw = buf.subarray(start, start + csize);
|
||
|
|
if (method === 0) return Buffer.from(raw);
|
||
|
|
if (method === 8) return zlib.inflateRawSync(raw);
|
||
|
|
throw new Error(`unsupported zip method ${method}`);
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|