Files
selfphoneng/apps/probe/build-binary.mjs
T
iamdoubz 4143f6d064
ci / check (push) Successful in 59s
ci / images (push) Successful in 49s
probe: downloadable executables for machines without Docker
Each release attaches selfphoneng-probe-linux-x64 and -arm64 (Node's single executable
applications: esbuild bundle + postject into the official Node binary, checked against
nodejs.org's SHASUMS256.txt), the bundle as selfphoneng-probe.cjs (Node 22+) and SHA256SUMS.
Built in binary.Dockerfile with BuildKit's local output, so Gitea's runner can build it too.
deploy/probe/selfphoneng-probe.service runs it under systemd, sandboxed.
2026-10-21 12:57:04 -05:00

95 lines
3.4 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-or-later
// Builds the network test probe (08 §2.6) for machines without Docker:
// dist/selfphoneng-probe.cjs one file; runs with Node 22 or newer
// dist/selfphoneng-probe-linux-x64 standalone executables with Node inside (Node's
// dist/selfphoneng-probe-linux-arm64 single executable applications), nothing to install
// dist/SHA256SUMS
// The executables carry this Node's version, downloaded from nodejs.org and checked against its
// SHASUMS256.txt. Run on Linux (needs tar with xz): node apps/probe/build-binary.mjs
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import {
chmodSync,
copyFileSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { build } from 'esbuild';
import postject from 'postject';
const here = dirname(fileURLToPath(import.meta.url));
const dist = join(here, 'dist');
const NAME = 'selfphoneng-probe';
const version = process.version;
const sha256 = (buf) => createHash('sha256').update(buf).digest('hex');
rmSync(dist, { recursive: true, force: true });
mkdirSync(dist);
const script = join(dist, `${NAME}.cjs`);
await build({
entryPoints: [join(here, 'src/index.ts')],
bundle: true,
platform: 'node',
format: 'cjs',
target: 'node22',
outfile: script,
banner: { js: '#!/usr/bin/env node\n// SPDX-License-Identifier: AGPL-3.0-or-later' },
});
chmodSync(script, 0o755);
const work = mkdtempSync(join(tmpdir(), 'sp-probe-'));
try {
const blob = join(work, 'sea.blob');
writeFileSync(
join(work, 'sea.json'),
JSON.stringify({
main: script,
output: blob,
disableExperimentalSEAWarning: true,
// Neither is portable between CPU architectures; the probe starts in milliseconds anyway.
useSnapshot: false,
useCodeCache: false,
}),
);
execFileSync(process.execPath, ['--experimental-sea-config', join(work, 'sea.json')], {
stdio: 'inherit',
});
const base = `https://nodejs.org/dist/${version}`;
const sums = await (await fetch(`${base}/SHASUMS256.txt`)).text();
for (const arch of ['x64', 'arm64']) {
const file = `node-${version}-linux-${arch}.tar.xz`;
const want = sums
.split('\n')
.find((l) => l.endsWith(` ${file}`))
?.split(' ')[0];
const tarball = Buffer.from(await (await fetch(`${base}/${file}`)).arrayBuffer());
if (!want || sha256(tarball) !== want) throw new Error(`${file}: checksum doesn't match`);
writeFileSync(join(work, file), tarball);
const inner = `node-${version}-linux-${arch}/bin/node`;
execFileSync('tar', ['-xJf', join(work, file), '-C', work, inner]);
const out = join(dist, `${NAME}-linux-${arch}`);
copyFileSync(join(work, inner), out);
await postject.inject(out, 'NODE_SEA_BLOB', readFileSync(blob), {
sentinelFuse: 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2',
});
chmodSync(out, 0o755);
console.log(`${out} (Node ${version})`);
}
} finally {
rmSync(work, { recursive: true, force: true });
}
const files = [`${NAME}.cjs`, `${NAME}-linux-x64`, `${NAME}-linux-arm64`];
writeFileSync(
join(dist, 'SHA256SUMS'),
files.map((f) => `${sha256(readFileSync(join(dist, f)))} ${f}\n`).join(''),
);
console.log(readFileSync(join(dist, 'SHA256SUMS'), 'utf8'));