mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 05:12:33 +00:00
- Replace `execSync` with `execFileSync` in npm install script to prevent command injection. - Pass PowerShell paths via environment variables to avoid shell metacharacter interpolation. - Add `-NoProfile` and `-NonInteractive` flags to PowerShell extraction for cleaner installs. - Upgrade `makeAccountId` to prioritize `crypto.getRandomValues` over `Math.random` for suffixes. - Update internal thread title logic to import `collapse_whitespace` from the correct location. Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
168 lines
5.5 KiB
JavaScript
168 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
// postinstall: downloads the correct pre-built binary for this platform/arch,
|
|
// verifies the SHA-256 checksum, then places it at bin/openhuman-bin[.exe].
|
|
//
|
|
// The binary is fetched from the GitHub release that matches package.json version.
|
|
|
|
const https = require('https');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const { execFileSync } = require('child_process');
|
|
|
|
const REPO = 'tinyhumansai/openhuman';
|
|
const pkg = require('./package.json');
|
|
const VERSION = pkg.version;
|
|
|
|
// Maps process.platform + process.arch → Rust target triple
|
|
const TARGET_MAP = {
|
|
darwin: { x64: 'x86_64-apple-darwin', arm64: 'aarch64-apple-darwin' },
|
|
linux: { x64: 'x86_64-unknown-linux-gnu', arm64: 'aarch64-unknown-linux-gnu' },
|
|
win32: { x64: 'x86_64-pc-windows-msvc' },
|
|
};
|
|
|
|
function getTarget() {
|
|
const platform = process.platform;
|
|
const arch = process.arch;
|
|
const targets = TARGET_MAP[platform];
|
|
if (!targets) throw new Error(`Unsupported platform: ${platform}`);
|
|
const target = targets[arch];
|
|
if (!target) throw new Error(`Unsupported arch ${arch} on ${platform}`);
|
|
return { platform, target };
|
|
}
|
|
|
|
function httpsGet(url) {
|
|
return new Promise((resolve, reject) => {
|
|
function request(u) {
|
|
https.get(u, (res) => {
|
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
return request(res.headers.location);
|
|
}
|
|
if (res.statusCode !== 200) {
|
|
res.resume();
|
|
return reject(new Error(`HTTP ${res.statusCode} fetching ${u}`));
|
|
}
|
|
const chunks = [];
|
|
res.on('data', (c) => chunks.push(c));
|
|
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
res.on('error', reject);
|
|
}).on('error', reject);
|
|
}
|
|
request(url);
|
|
});
|
|
}
|
|
|
|
function downloadFile(url, dest) {
|
|
return new Promise((resolve, reject) => {
|
|
function request(u) {
|
|
https.get(u, (res) => {
|
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
return request(res.headers.location);
|
|
}
|
|
if (res.statusCode !== 200) {
|
|
res.resume();
|
|
return reject(new Error(`HTTP ${res.statusCode} fetching ${u}`));
|
|
}
|
|
const out = fs.createWriteStream(dest);
|
|
res.pipe(out);
|
|
out.on('finish', () => out.close(resolve));
|
|
out.on('error', reject);
|
|
res.on('error', reject);
|
|
}).on('error', reject);
|
|
}
|
|
request(url);
|
|
});
|
|
}
|
|
|
|
function sha256hex(filePath) {
|
|
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
}
|
|
|
|
async function main() {
|
|
// Skip in CI environments that just need the package metadata
|
|
if (process.env.SKIP_OPENHUMAN_BINARY_DOWNLOAD) {
|
|
console.log('[openhuman] Skipping binary download (SKIP_OPENHUMAN_BINARY_DOWNLOAD set)');
|
|
return;
|
|
}
|
|
|
|
const { platform, target } = getTarget();
|
|
const isWin = platform === 'win32';
|
|
const ext = isWin ? '.zip' : '.tar.gz';
|
|
const tarball = `openhuman-core-${VERSION}-${target}${ext}`;
|
|
const checksumFile = `${tarball}.sha256`;
|
|
const baseUrl = `https://github.com/${REPO}/releases/download/v${VERSION}`;
|
|
|
|
const binDir = path.join(__dirname, 'bin');
|
|
fs.mkdirSync(binDir, { recursive: true });
|
|
|
|
const tmpTarball = path.join(binDir, tarball);
|
|
const binDest = path.join(binDir, isWin ? 'openhuman-bin.exe' : 'openhuman-bin');
|
|
|
|
// Skip if binary already exists and is executable
|
|
if (fs.existsSync(binDest)) {
|
|
console.log('[openhuman] Binary already installed, skipping download.');
|
|
return;
|
|
}
|
|
|
|
console.log(`[openhuman] Downloading v${VERSION} for ${target}...`);
|
|
|
|
// Download checksum first (small)
|
|
const checksumData = await httpsGet(`${baseUrl}/${checksumFile}`);
|
|
const expectedChecksum = checksumData.toString('utf8').trim().split(/\s+/)[0];
|
|
|
|
// Download binary archive
|
|
await downloadFile(`${baseUrl}/${tarball}`, tmpTarball);
|
|
|
|
// Verify checksum
|
|
const actualChecksum = sha256hex(tmpTarball);
|
|
if (expectedChecksum !== actualChecksum) {
|
|
fs.rmSync(tmpTarball, { force: true });
|
|
throw new Error(
|
|
`[openhuman] Checksum mismatch!\n expected: ${expectedChecksum}\n got: ${actualChecksum}`
|
|
);
|
|
}
|
|
console.log('[openhuman] Checksum verified.');
|
|
|
|
// Extract — use execFileSync (no shell interpolation) so paths with spaces
|
|
// or shell metacharacters in `tmpTarball` / `binDir` can't be injected.
|
|
if (isWin) {
|
|
// PowerShell is available on Windows runners
|
|
execFileSync(
|
|
'powershell',
|
|
[
|
|
'-NoProfile',
|
|
'-NonInteractive',
|
|
'-Command',
|
|
`Expand-Archive -Path $env:TC_SRC -DestinationPath $env:TC_DEST -Force`,
|
|
],
|
|
{ stdio: 'inherit', env: { ...process.env, TC_SRC: tmpTarball, TC_DEST: binDir } }
|
|
);
|
|
const extracted = path.join(binDir, 'openhuman-core.exe');
|
|
if (fs.existsSync(extracted)) fs.renameSync(extracted, binDest);
|
|
} else {
|
|
execFileSync('tar', ['-xzf', tmpTarball, '-C', binDir], { stdio: 'inherit' });
|
|
const extracted = path.join(binDir, 'openhuman-core');
|
|
if (fs.existsSync(extracted)) {
|
|
fs.renameSync(extracted, binDest);
|
|
fs.chmodSync(binDest, 0o755);
|
|
}
|
|
}
|
|
|
|
// Clean up archive
|
|
fs.rmSync(tmpTarball, { force: true });
|
|
|
|
if (!fs.existsSync(binDest)) {
|
|
throw new Error('[openhuman] Extraction failed — binary not found after unpack.');
|
|
}
|
|
|
|
console.log(`[openhuman] Installed at ${binDest}`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('\n[openhuman] Installation failed:', err.message);
|
|
console.error('You can file a bug at https://github.com/tinyhumansai/openhuman/issues');
|
|
process.exit(1);
|
|
});
|