169 lines
5.2 KiB
JavaScript
169 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/* global Buffer, console, module, process, require */
|
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execFileSync, spawnSync } = require('child_process');
|
|
|
|
function fail(message) {
|
|
console.error(`[verifyLinuxInstallers] ${message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
function run(command, args, input) {
|
|
const result = spawnSync(command, args, {
|
|
input,
|
|
encoding: input ? undefined : 'utf8',
|
|
maxBuffer: 128 * 1024 * 1024,
|
|
});
|
|
if (result.status !== 0) {
|
|
const stderr = Buffer.isBuffer(result.stderr)
|
|
? result.stderr.toString('utf8')
|
|
: result.stderr || '';
|
|
throw new Error(`${command} ${args.join(' ')} failed: ${stderr}`);
|
|
}
|
|
return Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout || '', 'utf8');
|
|
}
|
|
|
|
function readArMember(archivePath, memberName) {
|
|
return execFileSync('ar', ['p', archivePath, memberName], {
|
|
maxBuffer: 128 * 1024 * 1024,
|
|
});
|
|
}
|
|
|
|
function getTarCompressionFlag(memberName) {
|
|
if (memberName.endsWith('.tar.xz')) return 'J';
|
|
if (memberName.endsWith('.tar.gz')) return 'z';
|
|
if (memberName.endsWith('.tar.bz2')) return 'j';
|
|
fail(`Unsupported deb tar member compression: ${memberName}`);
|
|
}
|
|
|
|
function getDebMember(archivePath, prefix) {
|
|
const members = execFileSync('ar', ['t', archivePath], { encoding: 'utf8' })
|
|
.split(/\r?\n/)
|
|
.filter(Boolean);
|
|
const member = members.find((entry) => entry.startsWith(prefix));
|
|
if (!member) {
|
|
fail(`Missing ${prefix} member in ${archivePath}`);
|
|
}
|
|
return member;
|
|
}
|
|
|
|
function runDebTar(archivePath, memberName, tarMode, filePath) {
|
|
const args = filePath
|
|
? [
|
|
'-c',
|
|
'set -euo pipefail; ar p "$1" "$2" | tar "$3" - "$4"',
|
|
'bash',
|
|
archivePath,
|
|
memberName,
|
|
tarMode,
|
|
filePath,
|
|
]
|
|
: [
|
|
'-c',
|
|
'set -euo pipefail; ar p "$1" "$2" | tar "$3" -',
|
|
'bash',
|
|
archivePath,
|
|
memberName,
|
|
tarMode,
|
|
];
|
|
return execFileSync('bash', args, {
|
|
encoding: 'utf8',
|
|
maxBuffer: 16 * 1024 * 1024,
|
|
});
|
|
}
|
|
|
|
function listDebTar(archivePath, memberName, verbose = false) {
|
|
const flag = getTarCompressionFlag(memberName);
|
|
const mode = verbose ? `-tv${flag}f` : `-t${flag}f`;
|
|
return runDebTar(archivePath, memberName, mode);
|
|
}
|
|
|
|
function extractDebTarFile(archivePath, memberName, filePath) {
|
|
const flag = getTarCompressionFlag(memberName);
|
|
return runDebTar(archivePath, memberName, `-xO${flag}f`, filePath);
|
|
}
|
|
|
|
function listTar(tarBuffer, memberName, verbose = false) {
|
|
const flag = getTarCompressionFlag(memberName);
|
|
const mode = verbose ? `-tv${flag}f` : `-t${flag}f`;
|
|
return run('tar', [mode, '-'], tarBuffer).toString('utf8');
|
|
}
|
|
|
|
function extractTarFile(tarBuffer, memberName, filePath) {
|
|
const flag = getTarCompressionFlag(memberName);
|
|
return run('tar', [`-xO${flag}f`, '-', filePath], tarBuffer).toString('utf8');
|
|
}
|
|
|
|
function assertContains(haystack, needle, label) {
|
|
if (!haystack.includes(needle)) {
|
|
fail(`${label} does not contain ${needle}`);
|
|
}
|
|
}
|
|
|
|
function verifyDeb(debPath) {
|
|
const dataMember = getDebMember(debPath, 'data.tar.');
|
|
const controlMember = getDebMember(debPath, 'control.tar.');
|
|
const control = readArMember(debPath, controlMember);
|
|
const dataList = listDebTar(debPath, dataMember);
|
|
const dataVerboseList = listDebTar(debPath, dataMember, true);
|
|
|
|
assertContains(dataList, './usr/bin/agent-teams-ai\n', path.basename(debPath));
|
|
assertContains(dataList, './opt/Agent-Teams-AI/agent-teams-ai\n', path.basename(debPath));
|
|
assertContains(dataList, './opt/Agent-Teams-AI/chrome-sandbox\n', path.basename(debPath));
|
|
|
|
const launcherLine = dataVerboseList
|
|
.split(/\r?\n/)
|
|
.find((line) => line.endsWith('./usr/bin/agent-teams-ai'));
|
|
if (!launcherLine || !launcherLine.startsWith('-rwx')) {
|
|
fail(`/usr/bin/agent-teams-ai is not executable in ${debPath}`);
|
|
}
|
|
|
|
const launcher = extractDebTarFile(debPath, dataMember, './usr/bin/agent-teams-ai');
|
|
assertContains(launcher, '/opt/Agent-Teams-AI/agent-teams-ai', 'CLI launcher');
|
|
if (launcher.includes('--no-sandbox')) {
|
|
fail('CLI launcher must not force --no-sandbox');
|
|
}
|
|
|
|
const postinst = extractTarFile(control, controlMember, './postinst');
|
|
assertContains(postinst, 'SANDBOX_PATH="/opt/Agent-Teams-AI/chrome-sandbox"', 'deb postinst');
|
|
assertContains(postinst, 'chmod 4755 "$SANDBOX_PATH"', 'deb postinst');
|
|
}
|
|
|
|
function main() {
|
|
const releaseDir = path.resolve(process.argv[2] || 'release');
|
|
if (!fs.existsSync(releaseDir)) {
|
|
fail(`Release directory does not exist: ${releaseDir}`);
|
|
}
|
|
|
|
const debs = fs
|
|
.readdirSync(releaseDir)
|
|
.filter((entry) => entry.endsWith('.deb'))
|
|
.map((entry) => path.join(releaseDir, entry));
|
|
if (debs.length === 0) {
|
|
fail(`No .deb packages found in ${releaseDir}`);
|
|
}
|
|
|
|
for (const deb of debs) {
|
|
verifyDeb(deb);
|
|
console.log(`[verifyLinuxInstallers] OK ${path.basename(deb)}`);
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
fail(error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
_internal: {
|
|
extractTarFile,
|
|
getDebMember,
|
|
listTar,
|
|
verifyDeb,
|
|
},
|
|
};
|