mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-09 17:25:42 +00:00
eca4efb1ad
- replace hardcoded make -j2 with dynamic CPU core detection in aria2 build.sh - isolate Aria2 source build cache from prebuilt engine downloads - add atomic cache validation, restoration, and promotion in scripts/engine-aria2-cache.js - add Rust dependency and target caching with Swatinem/rust-cache@v2 - raise Desktop checks timeout to 45 minutes to prevent job aborts - harden temporary directory cleanup on Windows process abort
160 lines
5.0 KiB
JavaScript
160 lines
5.0 KiB
JavaScript
import crypto from 'node:crypto';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { collectRegularFiles, sha256 } from './engine-payload-integrity.js';
|
|
import { promoteDirectory, removePathWithRetry } from './engine-payload-promotion.js';
|
|
|
|
export function getAria2BuildScriptSha256(repoRoot) {
|
|
const buildScriptPath = path.join(repoRoot, 'scripts/aria2/build.sh');
|
|
return crypto.createHash('sha256')
|
|
.update(fs.readFileSync(buildScriptPath, 'utf8').replaceAll('\r\n', '\n'))
|
|
.digest('hex');
|
|
}
|
|
|
|
export function validateAria2Cache({
|
|
aria2CacheRoot,
|
|
target,
|
|
aria2Source,
|
|
buildScriptSha256,
|
|
toolchainFingerprint = null,
|
|
executableSuffix = '',
|
|
}) {
|
|
const manifestPath = path.join(aria2CacheRoot, 'aria2-build-manifest.json');
|
|
if (!fs.existsSync(manifestPath)) {
|
|
return { valid: false, reason: 'manifest-not-found' };
|
|
}
|
|
|
|
try {
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
if (
|
|
manifest.schemaVersion !== 1
|
|
|| manifest.target !== target
|
|
|| manifest.sourceSha256 !== aria2Source.sha256
|
|
|| manifest.patchSha256 !== aria2Source.patchSha256
|
|
|| manifest.buildScriptSha256 !== buildScriptSha256
|
|
) {
|
|
return { valid: false, reason: 'manifest-metadata-mismatch' };
|
|
}
|
|
|
|
if (
|
|
toolchainFingerprint !== null
|
|
&& manifest.toolchainFingerprint !== toolchainFingerprint
|
|
) {
|
|
return { valid: false, reason: 'toolchain-fingerprint-mismatch' };
|
|
}
|
|
|
|
const exeName = `aria2c-${target}${executableSuffix}`;
|
|
const cachedExe = path.join(aria2CacheRoot, exeName);
|
|
if (!fs.existsSync(cachedExe) || sha256(cachedExe) !== manifest.files?.[exeName]) {
|
|
return { valid: false, reason: 'executable-mismatch' };
|
|
}
|
|
|
|
if (manifest.files) {
|
|
for (const [rel, expectedSha] of Object.entries(manifest.files)) {
|
|
if (rel === exeName) continue;
|
|
const libFile = path.join(aria2CacheRoot, rel);
|
|
if (!fs.existsSync(libFile) || sha256(libFile) !== expectedSha) {
|
|
return { valid: false, reason: 'library-mismatch' };
|
|
}
|
|
}
|
|
}
|
|
|
|
const actualFiles = collectRegularFiles(aria2CacheRoot, {
|
|
ignoredNames: ['aria2-build-manifest.json'],
|
|
}).map(f => path.relative(aria2CacheRoot, f).split(path.sep).join('/'));
|
|
const expectedFiles = Object.keys(manifest.files || {}).sort();
|
|
actualFiles.sort();
|
|
if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) {
|
|
return { valid: false, reason: 'file-list-mismatch' };
|
|
}
|
|
|
|
return { valid: true, manifest };
|
|
} catch {
|
|
return { valid: false, reason: 'manifest-corrupted' };
|
|
}
|
|
}
|
|
|
|
export function restoreAria2Cache({
|
|
aria2CacheRoot,
|
|
payloadDestination,
|
|
target,
|
|
executableSuffix = '',
|
|
isWindows = false,
|
|
}) {
|
|
const exeName = `aria2c-${target}${executableSuffix}`;
|
|
const cachedExe = path.join(aria2CacheRoot, exeName);
|
|
const targetExe = path.join(payloadDestination, exeName);
|
|
fs.copyFileSync(cachedExe, targetExe);
|
|
if (!isWindows) fs.chmodSync(targetExe, 0o755);
|
|
|
|
const cachedLibs = path.join(aria2CacheRoot, 'aria2-libs');
|
|
if (fs.existsSync(cachedLibs)) {
|
|
fs.cpSync(cachedLibs, path.join(payloadDestination, 'aria2-libs'), {
|
|
recursive: true,
|
|
preserveTimestamps: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function saveAria2Cache({
|
|
aria2CacheRoot,
|
|
payloadDestination,
|
|
target,
|
|
aria2Source,
|
|
buildScriptSha256,
|
|
toolchainFingerprint = null,
|
|
executableSuffix = '',
|
|
aria2Runtime = null,
|
|
isWindows = false,
|
|
}) {
|
|
const cacheParent = path.dirname(aria2CacheRoot);
|
|
fs.mkdirSync(cacheParent, { recursive: true });
|
|
|
|
const stagingDir = fs.mkdtempSync(
|
|
path.join(cacheParent, `.${path.basename(aria2CacheRoot)}-staging-${process.pid}-`)
|
|
);
|
|
|
|
try {
|
|
const targetExeName = `aria2c-${target}${executableSuffix}`;
|
|
const cachedExeDest = path.join(stagingDir, targetExeName);
|
|
fs.copyFileSync(path.join(payloadDestination, targetExeName), cachedExeDest);
|
|
if (!isWindows) fs.chmodSync(cachedExeDest, 0o755);
|
|
|
|
const manifestFiles = {
|
|
[targetExeName]: sha256(cachedExeDest),
|
|
};
|
|
|
|
if (aria2Runtime && fs.existsSync(aria2Runtime)) {
|
|
const cachedLibsDest = path.join(stagingDir, 'aria2-libs');
|
|
fs.cpSync(aria2Runtime, cachedLibsDest, { recursive: true, preserveTimestamps: true });
|
|
const libFiles = collectRegularFiles(cachedLibsDest);
|
|
for (const lib of libFiles) {
|
|
const rel = path.relative(stagingDir, lib).split(path.sep).join('/');
|
|
manifestFiles[rel] = sha256(lib);
|
|
}
|
|
}
|
|
|
|
const cacheManifest = {
|
|
schemaVersion: 1,
|
|
target,
|
|
sourceSha256: aria2Source.sha256,
|
|
patchSha256: aria2Source.patchSha256,
|
|
buildScriptSha256,
|
|
toolchainFingerprint: toolchainFingerprint || null,
|
|
files: manifestFiles,
|
|
};
|
|
|
|
fs.writeFileSync(
|
|
path.join(stagingDir, 'aria2-build-manifest.json'),
|
|
`${JSON.stringify(cacheManifest, null, 2)}\n`
|
|
);
|
|
|
|
await promoteDirectory(stagingDir, aria2CacheRoot);
|
|
} catch (error) {
|
|
try {
|
|
await removePathWithRetry(stagingDir);
|
|
} catch {}
|
|
throw error;
|
|
}
|
|
}
|