mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 20:16:15 -08:00
* 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
248 lines
7.6 KiB
TypeScript
248 lines
7.6 KiB
TypeScript
import path from 'path';
|
|
import { isDeepStrictEqual } from 'util';
|
|
import {
|
|
GIT_COMMIT_PATTERN,
|
|
PINNED_SOURCE_PACKAGE_IDENTITIES,
|
|
PORTABLE_ABI_BASELINE,
|
|
SUBMODULE_RECORD_PATTERN,
|
|
VERSION_PATTERN,
|
|
} from './contracts';
|
|
import type { RuntimeFile } from './types';
|
|
import {
|
|
hasExactFields,
|
|
isObject,
|
|
isSafeRuntimeName,
|
|
} from './validation-primitives';
|
|
|
|
function compareDottedVersions(left: string, right: string): number {
|
|
const leftParts = left.split('.').map(Number);
|
|
const rightParts = right.split('.').map(Number);
|
|
const length = Math.max(leftParts.length, rightParts.length);
|
|
for (let index = 0; index < length; index += 1) {
|
|
const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
if (difference !== 0) {
|
|
return difference;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function validatesPinnedSourceIdentity(
|
|
candidate: unknown,
|
|
expected: (typeof PINNED_SOURCE_PACKAGE_IDENTITIES)[keyof typeof PINNED_SOURCE_PACKAGE_IDENTITIES]
|
|
): boolean {
|
|
if (
|
|
!isObject(candidate) ||
|
|
candidate.version !== expected.version ||
|
|
candidate.sourceUrl !== expected.sourceUrl ||
|
|
candidate.license !== expected.license
|
|
) {
|
|
return false;
|
|
}
|
|
if (
|
|
('sourceTag' in expected
|
|
? candidate.sourceTag !== expected.sourceTag
|
|
: candidate.sourceTag !== undefined) ||
|
|
('buildInput' in expected
|
|
? !isDeepStrictEqual(candidate.buildInput, expected.buildInput)
|
|
: candidate.buildInput !== undefined)
|
|
) {
|
|
return false;
|
|
}
|
|
if ('sourceSha256' in expected) {
|
|
return (
|
|
candidate.sourceSha256 === expected.sourceSha256 &&
|
|
candidate.sourceGitCommit === undefined &&
|
|
candidate.sourceSubmodules === undefined
|
|
);
|
|
}
|
|
return (
|
|
'sourceSubmodules' in expected &&
|
|
candidate.sourceGitCommit === expected.sourceGitCommit &&
|
|
GIT_COMMIT_PATTERN.test(candidate.sourceGitCommit) &&
|
|
candidate.sourceSha256 === undefined &&
|
|
validatesGitSubmoduleRecords(candidate.sourceSubmodules) &&
|
|
isDeepStrictEqual(candidate.sourceSubmodules, expected.sourceSubmodules)
|
|
);
|
|
}
|
|
|
|
function validatesGitSubmoduleRecords(value: unknown): boolean {
|
|
if (
|
|
!Array.isArray(value) ||
|
|
value.length === 0 ||
|
|
new Set(value).size !== value.length
|
|
) {
|
|
return false;
|
|
}
|
|
return value.every((record) => {
|
|
if (typeof record !== 'string') {
|
|
return false;
|
|
}
|
|
const match = record.match(SUBMODULE_RECORD_PATTERN);
|
|
if (!match) {
|
|
return false;
|
|
}
|
|
const submodulePath = match[1];
|
|
return (
|
|
!path.posix.isAbsolute(submodulePath) &&
|
|
!submodulePath
|
|
.split('/')
|
|
.some((segment) => segment === '.' || segment === '..')
|
|
);
|
|
});
|
|
}
|
|
|
|
function validateStringFlags(value: unknown): value is string[] {
|
|
return (
|
|
Array.isArray(value) &&
|
|
value.every(
|
|
(flag) =>
|
|
typeof flag === 'string' &&
|
|
flag.length > 0 &&
|
|
flag.trim() === flag
|
|
) &&
|
|
new Set(value).size === value.length
|
|
);
|
|
}
|
|
|
|
function validateRuntimeAbi(
|
|
value: unknown,
|
|
runtimeFiles: RuntimeFile[]
|
|
): boolean {
|
|
if (
|
|
!isObject(value) ||
|
|
!hasExactFields(value, ['baseline', 'files']) ||
|
|
!isDeepStrictEqual(value.baseline, PORTABLE_ABI_BASELINE) ||
|
|
!Array.isArray(value.files)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const abiFileNames: string[] = [];
|
|
for (const record of value.files) {
|
|
if (
|
|
!isObject(record) ||
|
|
!hasExactFields(record, [
|
|
'name',
|
|
'requiredGlibc',
|
|
'requiredGlibcxx',
|
|
]) ||
|
|
!isSafeRuntimeName(record.name)
|
|
) {
|
|
return false;
|
|
}
|
|
for (const [field, maximum] of [
|
|
['requiredGlibc', PORTABLE_ABI_BASELINE.glibcMaximum],
|
|
['requiredGlibcxx', PORTABLE_ABI_BASELINE.glibcxxMaximum],
|
|
] as const) {
|
|
const version = record[field];
|
|
if (
|
|
version !== null &&
|
|
(typeof version !== 'string' ||
|
|
!VERSION_PATTERN.test(version) ||
|
|
compareDottedVersions(version, maximum) > 0)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
abiFileNames.push(record.name);
|
|
}
|
|
|
|
return isDeepStrictEqual(
|
|
abiFileNames,
|
|
runtimeFiles.map(({ name }) => name)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The source builder and package verifier own exhaustive build-host, recipe,
|
|
* tool-version, URL and external-configuration validation. Startup repeats
|
|
* only the immutable policy boundary needed before sandbox relaxation:
|
|
* pinned source identities/licenses, LGPL flags, portable ABI, display-data
|
|
* distribution, and the exact runtime closure mirrored by the package.
|
|
*/
|
|
export function validateSourceRuntimePolicy(
|
|
value: unknown,
|
|
runtimeFiles: RuntimeFile[],
|
|
runtimeDependencyClosure: unknown,
|
|
externalSystemLibraries: unknown
|
|
): boolean {
|
|
if (
|
|
!isObject(value) ||
|
|
value.schemaVersion !== 1 ||
|
|
value.origin !== 'vendored-lgpl-source-build' ||
|
|
value.platform !== 'linux' ||
|
|
value.arch !== 'x64' ||
|
|
!isObject(value.packages) ||
|
|
!isObject(value.ffmpeg) ||
|
|
!isObject(value.mpv) ||
|
|
!isDeepStrictEqual(
|
|
Object.keys(value.packages).sort(),
|
|
Object.keys(PINNED_SOURCE_PACKAGE_IDENTITIES).sort()
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
for (const [packageName, expectedIdentity] of Object.entries(
|
|
PINNED_SOURCE_PACKAGE_IDENTITIES
|
|
)) {
|
|
if (
|
|
!validatesPinnedSourceIdentity(
|
|
value.packages[packageName],
|
|
expectedIdentity
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (
|
|
!validatesPinnedSourceIdentity(
|
|
value.ffmpeg,
|
|
PINNED_SOURCE_PACKAGE_IDENTITIES.ffmpeg
|
|
) ||
|
|
!validateStringFlags(value.ffmpeg.configureFlags) ||
|
|
!value.ffmpeg.configureFlags.includes('--disable-gpl') ||
|
|
!value.ffmpeg.configureFlags.includes('--disable-nonfree') ||
|
|
value.ffmpeg.configureFlags.includes('--enable-gpl') ||
|
|
value.ffmpeg.configureFlags.includes('--enable-nonfree') ||
|
|
!validatesPinnedSourceIdentity(
|
|
value.mpv,
|
|
PINNED_SOURCE_PACKAGE_IDENTITIES.mpv
|
|
) ||
|
|
!validateStringFlags(value.mpv.mesonFlags) ||
|
|
!value.mpv.mesonFlags.includes('-Dgpl=false') ||
|
|
!value.mpv.mesonFlags.includes('-Dlibmpv=true') ||
|
|
value.mpv.mesonFlags.includes('-Dgpl=true')
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
typeof value.sourceDistribution !== 'string' ||
|
|
!/\bhwdata\b/i.test(value.sourceDistribution) ||
|
|
!/\bpnp\.ids\b/i.test(value.sourceDistribution) ||
|
|
!/\blibdisplay-info\b/i.test(value.sourceDistribution) ||
|
|
value.runtimeTotalBytes !==
|
|
runtimeFiles.reduce(
|
|
(total, runtimeFile) => total + runtimeFile.size,
|
|
0
|
|
) ||
|
|
!isDeepStrictEqual(value.runtimeFiles, runtimeFiles) ||
|
|
!isDeepStrictEqual(
|
|
value.runtimeDependencyClosure,
|
|
runtimeDependencyClosure
|
|
) ||
|
|
!isDeepStrictEqual(
|
|
value.externalSystemLibraries,
|
|
externalSystemLibraries
|
|
) ||
|
|
!validateRuntimeAbi(value.runtimeAbi, runtimeFiles)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|