mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
139 lines
5.8 KiB
JavaScript
139 lines
5.8 KiB
JavaScript
const {execFile} = require('node:child_process');
|
|
const {createHash} = require('node:crypto');
|
|
const {createReadStream, statSync} = require('node:fs');
|
|
const fs = require('node:fs/promises');
|
|
const path = require('node:path');
|
|
const {promisify} = require('node:util');
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const expectedElectronVersion = '44.4.1';
|
|
const expectedElectronRevision = '09d1a6590e3148a1a2132b07a5b107b7c2414191';
|
|
const chromiumPatchPath = path.join(
|
|
__dirname,
|
|
'../electron/patches/fix_synchronize_vaapi_surfaces_before_output.patch',
|
|
);
|
|
|
|
function resolveLinuxElectronDist() {
|
|
const configuredDirectory = process.env.FLUXER_ELECTRON_DIST?.trim();
|
|
if (!configuredDirectory) {
|
|
throw new Error(
|
|
'Linux packaging requires patched Electron. Set FLUXER_ELECTRON_DIST to the directory containing the custom Electron ZIP and its .fluxer.json build manifest.',
|
|
);
|
|
}
|
|
const directory = path.resolve(configuredDirectory);
|
|
if (!statSync(directory, {throwIfNoEntry: false})?.isDirectory()) {
|
|
throw new Error(`FLUXER_ELECTRON_DIST is not a directory: ${directory}`);
|
|
}
|
|
return directory;
|
|
}
|
|
|
|
async function sha256File(filePath) {
|
|
const hash = createHash('sha256');
|
|
for await (const chunk of createReadStream(filePath)) hash.update(chunk);
|
|
return hash.digest('hex');
|
|
}
|
|
|
|
const inspectArchive = `
|
|
import hashlib, json, stat, struct, sys, zipfile
|
|
archive_path, arch, version = sys.argv[1:]
|
|
with zipfile.ZipFile(archive_path) as archive:
|
|
for required in ('electron', 'version', 'LICENSE', 'LICENSES.chromium.html'):
|
|
matches = [entry for entry in archive.infolist() if entry.filename == required]
|
|
if len(matches) != 1 or matches[0].is_dir() or stat.S_ISLNK(matches[0].external_attr >> 16):
|
|
raise ValueError('Expected one regular ZIP entry: ' + required)
|
|
if archive.read('version').decode('utf-8').strip() != version:
|
|
raise ValueError('Electron ZIP version does not match ' + version)
|
|
with archive.open('electron') as executable:
|
|
header = executable.read(20)
|
|
if len(header) != 20 or header[:6] != b'\\x7fELF\\x02\\x01':
|
|
raise ValueError('Electron must be a 64-bit little-endian ELF executable')
|
|
if struct.unpack_from('<H', header, 18)[0] != {'x64': 62, 'arm64': 183}[arch]:
|
|
raise ValueError('Electron ELF architecture does not match ' + arch)
|
|
digest = hashlib.sha256(header)
|
|
marker = b'failed to synchronize VA surface before output'
|
|
tail = header
|
|
has_patch_marker = marker in header
|
|
while chunk := executable.read(1024 * 1024):
|
|
digest.update(chunk)
|
|
window = tail + chunk
|
|
has_patch_marker = has_patch_marker or marker in window
|
|
tail = window[-(len(marker) - 1):]
|
|
if not has_patch_marker:
|
|
raise ValueError('Electron executable is missing the VA-API synchronization patch marker')
|
|
print(json.dumps({'binarySha256': digest.hexdigest()}))
|
|
`;
|
|
|
|
async function verifyLinuxElectronArchive(directory, arch) {
|
|
if (!['x64', 'arm64'].includes(arch)) {
|
|
throw new Error(`Custom Linux Electron does not support architecture: ${arch}`);
|
|
}
|
|
const artifactName = `electron-v${expectedElectronVersion}-linux-${arch}`;
|
|
const archiveName = `${artifactName}.zip`;
|
|
const manifestPath = path.join(directory, `${artifactName}.fluxer.json`);
|
|
let manifest;
|
|
try {
|
|
manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
} catch (error) {
|
|
throw new Error(`Cannot read custom Electron build manifest ${manifestPath}: ${error.message}`);
|
|
}
|
|
const expectedFields = {
|
|
schemaVersion: 1,
|
|
electronVersion: expectedElectronVersion,
|
|
electronRevision: expectedElectronRevision,
|
|
platform: 'linux',
|
|
arch,
|
|
archiveName,
|
|
};
|
|
for (const [field, expected] of Object.entries(expectedFields)) {
|
|
if (manifest?.[field] !== expected) {
|
|
throw new Error(`Custom Electron manifest ${field} must be ${JSON.stringify(expected)}: ${manifestPath}`);
|
|
}
|
|
}
|
|
const patchSha256 = await sha256File(chromiumPatchPath);
|
|
const acceptedPatchSha256s = new Set([patchSha256]);
|
|
if (patchSha256 === '21ea23b8a566f3f2dbdb69d0b361039dd9128c5e34a7ae89a8a61d7099808b8e') {
|
|
acceptedPatchSha256s.add('d39d05d4a00cd09eca43c7cd83383eaefa18cae7ec1b7efc26987bdc0f23c83e');
|
|
}
|
|
if (!acceptedPatchSha256s.has(manifest?.chromiumPatchSha256)) {
|
|
throw new Error(`Custom Electron manifest chromiumPatchSha256 does not match the current patch: ${manifestPath}`);
|
|
}
|
|
if (!/^[a-f0-9]{64}$/.test(manifest.archiveSha256)) {
|
|
throw new Error(`Custom Electron manifest has no valid archiveSha256: ${manifestPath}`);
|
|
}
|
|
const archivePath = path.join(directory, archiveName);
|
|
if ((await sha256File(archivePath)) !== manifest.archiveSha256) {
|
|
throw new Error(`Custom Electron ZIP checksum does not match its build manifest: ${archivePath}`);
|
|
}
|
|
let inspection;
|
|
try {
|
|
const {stdout} = await execFileAsync(
|
|
'python3',
|
|
['-c', inspectArchive, archivePath, arch, expectedElectronVersion],
|
|
{
|
|
maxBuffer: 64 * 1024,
|
|
},
|
|
);
|
|
inspection = JSON.parse(stdout);
|
|
} catch (error) {
|
|
throw new Error(`Cannot verify custom Electron ZIP ${archivePath}: ${error.stderr?.trim() || error.message}`);
|
|
}
|
|
if (!/^[a-f0-9]{64}$/.test(inspection.binarySha256)) {
|
|
throw new Error(`Custom Electron ZIP inspection returned no executable checksum: ${archivePath}`);
|
|
}
|
|
return {manifest, binarySha256: inspection.binarySha256};
|
|
}
|
|
|
|
async function verifyPackagedLinuxElectron(executablePath, artifact) {
|
|
if (!artifact) throw new Error('Custom Electron archive was not verified before Linux packaging.');
|
|
if ((await sha256File(executablePath)) !== artifact.binarySha256) {
|
|
throw new Error(`Packaged Linux executable does not match the verified custom Electron build: ${executablePath}`);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
expectedElectronVersion,
|
|
resolveLinuxElectronDist,
|
|
verifyLinuxElectronArchive,
|
|
verifyPackagedLinuxElectron,
|
|
};
|