paperclip/packages/paperclip-runner/scripts/materialize-pi-binary.mjs

46 lines
2.5 KiB
JavaScript

import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { createReadStream, createWriteStream } from "node:fs";
import { chmod, mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
export const PI_BINARY = Object.freeze({
version: "0.84.2",
url: "https://github.com/earendil-works/pi/releases/download/v0.84.2/pi-linux-x64.tar.gz",
archiveSha256: "906fbe787fd225c4ac624fe7ebd5b1d55a60e0f5c7ef51795d231564f9ee1c13",
executableSha256: "9a2d20fab3caacbe3517d91e59d495ccc49fd4b51a1a72dcec6e8c1f4b7d6ab2",
});
/** Install the official standalone runtime so ACPX never launches ambient `pi`. */
export async function materializePiBinary(packageDirectory, options = {}) {
if ((options.platform ?? process.platform) !== "linux" || (options.arch ?? process.arch) !== "x64") {
throw new Error("The qualified Pi binary requires Linux x64");
}
const temporary = await mkdtemp(join(tmpdir(), "paperclip-pi-binary-"));
try {
const response = await (options.fetch ?? fetch)(PI_BINARY.url);
if (!response.ok || !response.body) throw new Error("Pi runtime download failed");
const archive = join(temporary, "pi.tar.gz");
const digest = createHash("sha256");
let bytes = 0;
await pipeline(Readable.fromWeb(response.body), new Transform({ transform(chunk, _encoding, done) {
bytes += chunk.length;
if (bytes > 192 * 1024 * 1024) return done(new Error("Pi runtime archive exceeds its size bound"));
digest.update(chunk); done(null, chunk);
} }), createWriteStream(archive, { flags: "wx", mode: 0o600 }));
if (digest.digest("hex") !== PI_BINARY.archiveSha256) throw new Error("Pi runtime archive integrity mismatch");
const destination = join(packageDirectory, "vendor", "standalone");
await mkdir(destination, { recursive: true });
execFileSync("tar", ["-xzf", archive, "--strip-components=1", "-C", destination]);
const executable = join(destination, "pi");
const executableDigest = createHash("sha256");
for await (const chunk of createReadStream(executable)) executableDigest.update(chunk);
if (executableDigest.digest("hex") !== PI_BINARY.executableSha256) throw new Error("Pi runtime executable integrity mismatch");
await chmod(executable, 0o755);
return executable;
} finally { await rm(temporary, { recursive: true, force: true }); }
}