Files
iptvnator/apps/electron-backend/src/app/services/embedded-mpv-frame-copy-runtime/package-validator.ts
T
4gray 8fdac824fd feat(packaging): ship Linux embedded MPV frame-copy runtime (#1200)
* docs: design Linux frame-copy packaging

* docs: plan Linux frame-copy packaging

* feat(packaging): define Linux frame-copy profiles

* fix(packaging): reject inherited profile names

* feat(embedded-mpv): validate staged Linux runtime

* fix(embedded-mpv): require Linux source packages

* fix(embedded-mpv): harden Linux runtime staging

* feat(embedded-mpv): build LGPL Linux runtime

* fix(embedded-mpv): pin Linux runtime inputs

* feat(embedded-mpv): build relocatable Linux helper

* fix(embedded-mpv): require bundled Linux runtime

* fix(embedded-mpv): make Linux runtime portable

* feat(packaging): ship Linux frame-copy artifacts

* fix(embedded-mpv): verify Linux helper linkage

* fix(packaging): enforce Linux frame-copy isolation

* fix(embedded-mpv): pin Linux display data

* docs(embedded-mpv): document Linux frame-copy packaging

* feat(embedded-mpv): probe Linux frame-copy runtime

* test(embedded-mpv): smoke packaged Linux frame-copy

* docs(embedded-mpv): clarify Linux system runtime baseline

* fix(embedded-mpv): harden Linux runtime capability gate

* ci: verify Linux frame-copy packages

* test(embedded-mpv): harden packaged Linux smoke

* test(embedded-mpv): preserve packaged GL mode

* test(packaging): harden Linux package probes

* fix(embedded-mpv): enable private Snap shared memory

* fix(embedded-mpv): sanitize Linux helper environment

* fix(packaging): enforce private Snap memory semantics

* fix(packaging): reject ambiguous Snap memory metadata

* fix(embedded-mpv): prioritize trusted Snap GL

* fix(packaging): reject advanced Snap YAML semantics

* fix(packaging): reject arbitrary Snap YAML aliases

* feat(packaging): ship Linux runtime license notices

* docs(embedded-mpv): document Linux runtime distribution

* fix(packaging): parse Snap trailing comments safely

* fix(release): gate Snap publish on public source release

* fix(packaging): strip VCS metadata from source bundle

* docs(packaging): clarify Linux source release gate

* test(embedded-mpv): smoke missing bundled libmpv

* style(embedded-mpv): format final validation inputs

* fix(e2e): satisfy fixture index signature typing

* fix(ci): declare fontconfig gperf generator

* fix(embedded-mpv): hash runtime cache identities

* fix(packaging): harden Linux frame-copy delivery

* fix(packaging): tighten runtime delivery gates

* fix(ci): decouple Linux runtime matrix

* fix(packaging): harden Linux frame-copy delivery

* fix(packaging): validate Linux frame-copy runtimes

* fix(packaging): scope Snap Electron library checks

* feat(packaging): ship Linux frame-copy runtimes

* fix(packaging): improve Linux runtime smoke diagnostics

* fix(packaging): expose bounded helper probe details

* test(packaging): trace Snap EGL probe failures

* fix(packaging): prefer core22 ABI in Snap helper

* fix(packaging): bound helper probe capture

* fix(packaging): harden Linux frame-copy releases

* fix(packaging): canonicalize libplacebo submodule identity

* fix(packaging): make source archive inspection portable

* fix(packaging): harden Snap release verification
2026-07-18 17:28:22 +02:00

239 lines
7.0 KiB
TypeScript

import { createHash } from 'crypto';
import * as nodeFileSystem from 'fs';
import path from 'path';
import {
FRAME_COPY_ADDON_NAME,
FRAME_COPY_HELPER_NAME,
FRAME_COPY_READER_NAME,
} from './contracts';
import {
validateDevelopmentManifest,
validatePackagedManifest,
} from './manifest-validator';
import type {
EmbeddedMpvFrameCopyManifestContract,
EmbeddedMpvFrameCopyRuntimeFileSystem,
RuntimeFile,
ValidatedPackage,
ValidationResult,
} from './types';
import {
isMissingFileError,
isValidationFailure,
readManifest,
validationFailure,
} from './validation-primitives';
function validateRegularArtifact(
filePath: string,
accessMode: number,
expectedMode: number | null,
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem
): ValidationResult<nodeFileSystem.Stats> {
let stat: nodeFileSystem.Stats;
try {
stat = fileSystem.lstatSync(filePath);
} catch (error) {
return validationFailure(
isMissingFileError(error)
? 'runtime-artifact-missing'
: 'runtime-artifact-invalid'
);
}
if (
stat.isSymbolicLink() ||
!stat.isFile() ||
(expectedMode !== null && (stat.mode & 0o7777) !== expectedMode)
) {
return validationFailure('runtime-artifact-invalid');
}
try {
fileSystem.accessSync(filePath, accessMode);
} catch {
return validationFailure('runtime-artifact-invalid');
}
return { value: stat };
}
function pathExistsByLstat(
filePath: string,
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem
): boolean {
try {
fileSystem.lstatSync(filePath);
return true;
} catch (error) {
if (isMissingFileError(error)) {
return false;
}
throw error;
}
}
function validateBundledRuntimeFiles(
nativeDir: string,
runtimeFiles: RuntimeFile[],
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem
): ValidationResult<true> {
const libDir = path.join(nativeDir, 'lib');
let libStat: nodeFileSystem.Stats;
try {
libStat = fileSystem.lstatSync(libDir);
} catch {
return validationFailure('runtime-library-directory-invalid');
}
if (libStat.isSymbolicLink() || !libStat.isDirectory()) {
return validationFailure('runtime-library-directory-invalid');
}
let packagedNames: string[];
try {
packagedNames = fileSystem.readdirSync(libDir) as string[];
} catch {
return validationFailure('runtime-library-directory-invalid');
}
const declaredNames = new Set(runtimeFiles.map(({ name }) => name));
if (packagedNames.some((name) => !declaredNames.has(name))) {
return validationFailure('runtime-library-undeclared');
}
for (const runtimeFile of runtimeFiles) {
const runtimePath = path.join(libDir, runtimeFile.name);
let stat: nodeFileSystem.Stats;
try {
stat = fileSystem.lstatSync(runtimePath);
} catch (error) {
return validationFailure(
isMissingFileError(error)
? 'runtime-library-missing'
: 'runtime-library-invalid'
);
}
if (stat.isSymbolicLink() || !stat.isFile()) {
return validationFailure('runtime-library-invalid');
}
try {
fileSystem.accessSync(runtimePath, nodeFileSystem.constants.R_OK);
} catch {
return validationFailure('runtime-library-invalid');
}
if (stat.size !== runtimeFile.size) {
return validationFailure('runtime-library-size-mismatch');
}
let contents: Buffer;
try {
contents = fileSystem.readFileSync(runtimePath);
} catch {
return validationFailure('runtime-library-invalid');
}
if (contents.length !== runtimeFile.size) {
return validationFailure('runtime-library-size-mismatch');
}
const actualSha256 = createHash('sha256')
.update(contents)
.digest('hex');
if (actualSha256 !== runtimeFile.sha256) {
return validationFailure('runtime-library-hash-mismatch');
}
}
return { value: true };
}
export function validatePackage(
helperPath: string,
manifestPath: string,
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem,
manifestContract: EmbeddedMpvFrameCopyManifestContract
): ValidationResult<ValidatedPackage> {
const nativeDir = path.dirname(helperPath);
if (
path.basename(helperPath) !== FRAME_COPY_HELPER_NAME ||
path.dirname(manifestPath) !== nativeDir
) {
return validationFailure('runtime-artifact-invalid');
}
const manifestArtifact = validateRegularArtifact(
manifestPath,
nodeFileSystem.constants.R_OK,
0o644,
fileSystem
);
if (isValidationFailure(manifestArtifact)) {
return validationFailure(
manifestArtifact.reason === 'runtime-artifact-missing'
? 'runtime-manifest-missing'
: 'runtime-manifest-invalid'
);
}
const manifestResult = readManifest(manifestPath, fileSystem);
if (isValidationFailure(manifestResult)) {
return manifestResult;
}
const validManifest =
manifestContract === 'packaged'
? validatePackagedManifest(manifestResult.value)
: validateDevelopmentManifest(manifestResult.value);
if (isValidationFailure(validManifest)) {
return validManifest;
}
for (const [artifactPath, accessMode, expectedMode] of [
[
path.join(nativeDir, FRAME_COPY_ADDON_NAME),
nodeFileSystem.constants.R_OK,
null,
],
[
path.join(nativeDir, FRAME_COPY_READER_NAME),
nodeFileSystem.constants.R_OK,
0o644,
],
[
helperPath,
nodeFileSystem.constants.R_OK | nodeFileSystem.constants.X_OK,
0o755,
],
] as const) {
const artifact = validateRegularArtifact(
artifactPath,
accessMode,
expectedMode,
fileSystem
);
if (isValidationFailure(artifact)) {
return artifact;
}
}
const libDir = path.join(nativeDir, 'lib');
if (validManifest.value.runtimeMode === 'system') {
try {
if (pathExistsByLstat(libDir, fileSystem)) {
return validationFailure('runtime-library-directory-invalid');
}
} catch {
return validationFailure('runtime-library-directory-invalid');
}
} else {
const bundledRuntime = validateBundledRuntimeFiles(
nativeDir,
validManifest.value.runtimeFiles,
fileSystem
);
if (isValidationFailure(bundledRuntime)) {
return bundledRuntime;
}
}
return {
value: {
manifest: validManifest.value,
helperPath,
nativeDir,
},
};
}