theseus/bundled-addons/pithos/core/binary.js
Local Dev 4ad95b3c7a Theseus: bundle the Pithos add-on
Ships Pithos (the s3d control panel) as a built-in extension: the dock
menu opens it in a tab served from a loopback port, and "Stop s3d"
shuts the gateway down. Generated by Pithos/scripts/build-theseus-addon.mjs.

yaml is vendored under vendor/yaml/lib rather than node_modules/ or
dist/, because Theseus git-ignores both and a clean-worktree release
build would otherwise ship the add-on without its only dependency.
2026-10-03 19:03:05 +02:00

77 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;
}