fix(packaging): harden Linux frame-copy delivery

This commit is contained in:
4gray committed 2026-07-18 09:32:42 +02:00
1 parent 4367fb595f
commit 482630dd2a
16 files changed
+367 -28

No files matched your search

+6 -3
View File
@@ -827,9 +827,12 @@ jobs:
cp "${RUNNER_TEMP}/electron-builder.base.json" electron-builder.json
node tools/packaging/configure-linux-frame-copy-build.mjs --foreign-deb
pnpm nx run electron-backend:make \
--outputPath=dist/executables-linux-foreign \
--publishPolicy=never
for foreign_arch in armv7l arm64; do
pnpm nx run electron-backend:make \
--arch="${foreign_arch}" \
--outputPath=dist/executables-linux-foreign \
--publishPolicy=never
done
find dist/executables-linux-foreign -maxdepth 1 -type f -name '*.deb' \
-exec mv '{}' dist/executables/ ';'
+3 -3
View File
@@ -240,9 +240,9 @@ Key files:
sandbox. Any failure reports a stable reason and falls back to native-view
without crashing; an environment flag never bypasses this gate.
- Snap uses an exact private `shared-memory` plug. The probe and playback
helper share one sanitized loader environment: ambient preload/library
paths are removed, the validated private closure wins, and trusted Snap GL
roots precede generic in-snap library roots.
helper share one sanitized loader environment: ambient audit, preload, and
library paths are removed, the validated private closure wins, and trusted
Snap GL roots precede generic in-snap library roots.
- Bundled Linux releases must publish the exact source archives/git records,
checksums, licenses, flags, patches, build scripts, and the pinned hwdata
`pnp.ids` input. Each bundled package carries
+4 -3
View File
@@ -636,9 +636,10 @@ engine` (restart required) or
packaged manifest/file/hash gate and bounded `--runtime-probe`; any failure
keeps the sandbox enabled, records a stable reason, and falls back to
native-view without crashing. Snap uses an exact private `shared-memory`
plug; probe and playback share a sanitized loader environment in which the
validated private closure and trusted Snap GL roots have explicit
precedence. Bundled Linux packages carry hash-validated
plug; probe and playback share a sanitized loader environment in which
ambient audit, preload, and library paths are removed and the validated
private closure and trusted Snap GL roots have explicit precedence. Bundled
Linux packages carry hash-validated
`embedded-mpv-notices.json`, `THIRD_PARTY_NOTICES.txt`, and `licenses/**`.
CI caches the staged runtime plus immutable source inputs, never finished
notices or the compliance tarball; it regenerates those notices and the
+20 -7
View File
@@ -17,6 +17,7 @@ const {
validateLinuxSystemBuildInputManifest,
} = require('../../tools/embedded-mpv/linux-runtime-manifest.cjs');
const {
resolveLinuxFrameCopyLinkageInputs,
resolveVerifiedLinuxLibMpvSoname,
runWithCleanup,
validateLinuxFrameCopyLinkage,
@@ -316,9 +317,9 @@ function findWindowsLibMpv(runtimeRoot) {
}
/* Debian/Ubuntu install linker targets under the multiarch triple dir. The
* compiler's built-in search paths cover it for -l resolution either way;
* this keeps the -L flag and the helper's baked rpath pointing somewhere
* real. */
* compiler's built-in search paths cover it for -l resolution either way.
* This directory is a link-time input only; the helper runtime intentionally
* stays on the sanitized system loader contract. */
function defaultLinuxSystemLibDir() {
const multiarchTriples = {
arm: 'arm-linux-gnueabihf',
@@ -381,8 +382,9 @@ function resolveRuntime() {
if (targetPlatform === 'linux') {
// Dev-first Linux flow (frame-copy helper links system libmpv): a
// distro libmpv-dev install is a full runtime — no staging needed.
// LIBMPV_INCLUDE_DIR / LINUX_NATIVE_LIBRARY_DIR override the system
// paths for machines with a local (non-root) libmpv prefix.
// LIBMPV_INCLUDE_DIR overrides the header path.
// LINUX_NATIVE_LIBRARY_DIR overrides the link-time library directory,
// which must already be visible to the system dynamic loader.
const systemIncludeDir =
process.env.LIBMPV_INCLUDE_DIR || '/usr/include';
if (fs.existsSync(path.join(systemIncludeDir, 'mpv', 'client.h'))) {
@@ -765,6 +767,16 @@ function main() {
: targetPlatform === 'linux'
? writeLinuxFrameCopyBuildManifest(runtime)
: copyGenericRuntimeToNativeBuild(runtime);
const linuxLinkageInputs =
targetPlatform === 'linux'
? resolveLinuxFrameCopyLinkageInputs({
buildInputMode: runtime.buildInputMode,
outputLibDir,
packagedLibmpvSoname: runtimeManifest.libmpvSoname,
readDynamicSection: readLinuxDynamicSection,
runtimeLibDir: runtime.libDir,
})
: null;
const electronPackageJson = require(
path.join(workspaceRoot, 'node_modules', 'electron', 'package.json')
@@ -781,7 +793,8 @@ function main() {
LIBMPV_INCLUDE_DIR: runtime.includeDir,
...(targetPlatform === 'linux'
? {
LINUX_VERIFIED_RUNTIME_LIBRARY_DIR: outputLibDir,
LINUX_VERIFIED_RUNTIME_LIBRARY_DIR:
linuxLinkageInputs.linkerLibraryDir,
}
: { LIBMPV_LIBRARY_DIR: outputLibDir }),
...(runtime.windowsImportLib
@@ -807,7 +820,7 @@ function main() {
if (targetPlatform === 'linux') {
validateLinuxFrameCopyLinkage({
expectedLibmpvSoname: runtimeManifest.libmpvSoname,
expectedLibmpvSoname: linuxLinkageInputs.expectedLibmpvSoname,
outputDir,
readDynamicSection: readLinuxDynamicSection,
});
@@ -201,6 +201,51 @@ function resolveVerifiedLinuxLibMpvSoname({
return expectedSoname;
}
function resolveLinuxFrameCopyLinkageInputs({
buildInputMode,
outputLibDir,
packagedLibmpvSoname,
readDynamicSection,
runtimeLibDir,
}) {
const systemDevelopment = buildInputMode === 'system-dev';
const linkerLibraryDir = systemDevelopment ? runtimeLibDir : outputLibDir;
if (
typeof linkerLibraryDir !== 'string' ||
linkerLibraryDir.trim().length === 0
) {
throw new Error(
'Linux frame-copy linkage requires a non-empty linker library directory.'
);
}
if (!systemDevelopment) {
return {
expectedLibmpvSoname: packagedLibmpvSoname,
linkerLibraryDir,
};
}
if (typeof readDynamicSection !== 'function') {
throw new TypeError('Linux readelf dynamic reader is required.');
}
const linkerInputPath = path.join(linkerLibraryDir, 'libmpv.so');
const dynamic = parseReadelfDynamic(readDynamicSection(linkerInputPath));
if (
dynamic.soname.length !== 1 ||
!VERSIONED_LIBMPV_PATTERN.test(dynamic.soname[0])
) {
throw new Error(
'The system-development libmpv linker input must contain exactly one versioned libmpv SONAME.'
);
}
return {
expectedLibmpvSoname: dynamic.soname[0],
linkerLibraryDir,
};
}
function assertRegularArtifact(filePath, label) {
let stat;
try {
@@ -288,6 +333,7 @@ function runWithCleanup(operation, cleanup) {
module.exports = {
parseReadelfDynamic,
resolveLinuxFrameCopyLinkageInputs,
resolveVerifiedLinuxLibMpvSoname,
runWithCleanup,
validateLinuxFrameCopyLinkage,
@@ -8,6 +8,7 @@ describe('createLinuxFrameCopyHelperEnvironment', () => {
{
PATH: '/usr/bin',
HOME: '/home/user',
LD_AUDIT: '/tmp/audit.so',
LD_LIBRARY_PATH: '/tmp/hostile-libs',
LD_PRELOAD: '/tmp/inject.so',
},
@@ -41,6 +42,7 @@ describe('createLinuxFrameCopyHelperEnvironment', () => {
'/var/lib/snapd/lib/gl/nvidia',
'/var/lib/snapd/lib/gl-evil',
].join(':'),
LD_AUDIT: '/tmp/audit.so',
LD_LIBRARY_PATH: '/tmp/hostile-libs',
LD_PRELOAD: '/tmp/inject.so',
},
@@ -75,6 +77,7 @@ describe('createLinuxFrameCopyHelperEnvironment', () => {
PATH: '/usr/bin',
SNAP: '/snap/iptvnator/42',
SNAP_LIBRARY_PATH: '/var/lib/snapd/lib/gl:/tmp/hostile-gl',
LD_AUDIT: '/tmp/audit.so',
LD_LIBRARY_PATH: '/tmp/hostile-libs',
LD_PRELOAD: '/tmp/inject.so',
},
@@ -86,6 +86,7 @@ export function createLinuxFrameCopyHelperEnvironment(
runtimeMode: EmbeddedMpvFrameCopyRuntimeMode
): NodeJS.ProcessEnv {
const helperEnvironment = { ...environment };
delete helperEnvironment.LD_AUDIT;
delete helperEnvironment.LD_LIBRARY_PATH;
delete helperEnvironment.LD_PRELOAD;
@@ -53,6 +53,7 @@ export function createRuntimeTestContext(): RuntimeTestContext {
arch: 'x64',
env: {
PATH: '/usr/bin',
LD_AUDIT: '/tmp/audit.so',
LD_LIBRARY_PATH: '/ambient/libs',
LD_PRELOAD: '/tmp/inject.so',
},
@@ -117,6 +117,7 @@ describe('EmbeddedMpvFrameCopyAdapter', () => {
environment: {
PATH: '/usr/bin',
HOME: '/home/user',
LD_AUDIT: '/tmp/audit.so',
LD_LIBRARY_PATH: '/tmp/hostile-libs',
LD_PRELOAD: '/tmp/inject.so',
},
@@ -148,6 +149,7 @@ describe('EmbeddedMpvFrameCopyAdapter', () => {
PATH: '/snap/bin:/usr/bin',
SNAP: snapRoot,
SNAP_LIBRARY_PATH: '/var/lib/snapd/lib/gl:/tmp/hostile-gl',
LD_AUDIT: '/tmp/audit.so',
LD_LIBRARY_PATH: '/tmp/hostile-libs',
LD_PRELOAD: '/tmp/inject.so',
},
@@ -52,6 +52,16 @@ function loadLinkageModule(): {
runtimeDependencyClosure: SonameFixture['runtimeDependencyClosure'];
runtimeFiles: RuntimeFileRecord[];
}) => string;
resolveLinuxFrameCopyLinkageInputs: (options: {
buildInputMode: string;
outputLibDir: string;
packagedLibmpvSoname: string | null;
readDynamicSection: (filePath: string) => string;
runtimeLibDir: string;
}) => {
expectedLibmpvSoname: string | null;
linkerLibraryDir: string;
};
runWithCleanup: <T>(operation: () => T, cleanup: () => void) => T;
validateLinuxFrameCopyLinkage: (options: {
expectedLibmpvSoname: string;
@@ -287,6 +297,93 @@ describe('Linux Embedded MPV linkage verification', () => {
).toThrow(/size|SHA-256/i);
});
it('uses and identifies the unmanaged system libmpv only for system development', () => {
const { resolveLinuxFrameCopyLinkageInputs } = loadLinkageModule();
const readDynamicSection = jest.fn((filePath: string) => {
expect(filePath).toBe('/opt/libmpv/lib/libmpv.so');
return readelfDynamic([['SONAME', 'libmpv.so.2']]);
});
expect(
resolveLinuxFrameCopyLinkageInputs({
buildInputMode: 'system-dev',
outputLibDir: '/native/build/Release/lib',
packagedLibmpvSoname: null,
readDynamicSection,
runtimeLibDir: '/opt/libmpv/lib',
})
).toEqual({
expectedLibmpvSoname: 'libmpv.so.2',
linkerLibraryDir: '/opt/libmpv/lib',
});
expect(readDynamicSection).toHaveBeenCalledTimes(1);
});
it('keeps bundled and untrusted build modes on the copied runtime directory', () => {
const { resolveLinuxFrameCopyLinkageInputs } = loadLinkageModule();
const readDynamicSection = jest.fn(() => {
throw new Error('must not inspect the ambient system runtime');
});
for (const buildInputMode of [
'bundled-runtime',
'system-build-inputs',
'unexpected-mode',
]) {
expect(
resolveLinuxFrameCopyLinkageInputs({
buildInputMode,
outputLibDir: '/native/build/Release/lib',
packagedLibmpvSoname:
buildInputMode === 'bundled-runtime'
? 'libmpv.so.2'
: null,
readDynamicSection,
runtimeLibDir: '/usr/lib/x86_64-linux-gnu',
})
).toEqual({
expectedLibmpvSoname:
buildInputMode === 'bundled-runtime' ? 'libmpv.so.2' : null,
linkerLibraryDir: '/native/build/Release/lib',
});
}
expect(readDynamicSection).not.toHaveBeenCalled();
});
it('rejects ambiguous or unversioned system-development libmpv identities', () => {
const { resolveLinuxFrameCopyLinkageInputs } = loadLinkageModule();
const options = {
buildInputMode: 'system-dev',
outputLibDir: '/native/build/Release/lib',
packagedLibmpvSoname: null,
runtimeLibDir: '/usr/lib/x86_64-linux-gnu',
};
expect(() =>
resolveLinuxFrameCopyLinkageInputs({
...options,
readDynamicSection: () =>
readelfDynamic([['SONAME', 'libmpv.so']]),
})
).toThrow(/system-development.*exactly one versioned libmpv SONAME/i);
expect(() =>
resolveLinuxFrameCopyLinkageInputs({
...options,
readDynamicSection: () => readelfDynamic([]),
})
).toThrow(/system-development.*exactly one versioned libmpv SONAME/i);
expect(() =>
resolveLinuxFrameCopyLinkageInputs({
...options,
readDynamicSection: () =>
readelfDynamic([
['SONAME', 'libmpv.so.1'],
['SONAME', 'libmpv.so.2'],
]),
})
).toThrow(/system-development.*exactly one versioned libmpv SONAME/i);
});
function createArtifactFixture(): {
outputDir: string;
readDynamicSection: (filePath: string) => string;
@@ -604,7 +604,14 @@ describe('Embedded MPV native source recording invariants', () => {
'SHA-256 mismatch for staged Linux runtime file'
);
expect(buildScriptSource).toContain(
'LINUX_VERIFIED_RUNTIME_LIBRARY_DIR: outputLibDir'
'resolveLinuxFrameCopyLinkageInputs({'
);
expect(buildScriptSource).toContain(
'LINUX_VERIFIED_RUNTIME_LIBRARY_DIR:\n' +
' linuxLinkageInputs.linkerLibraryDir'
);
expect(buildScriptSource).toContain(
'expectedLibmpvSoname: linuxLinkageInputs.expectedLibmpvSoname'
);
expect(buildScriptSource).not.toContain(
'process.env.LINUX_NATIVE_LIBRARY_DIR || runtime.libDir'
+6 -5
View File
@@ -254,10 +254,11 @@ JSON line and return zero.
The startup probe and every playback helper session use the same sanitized
loader environment selected by the validated manifest's cached `runtimeMode`.
Both remove ambient `LD_PRELOAD` and `LD_LIBRARY_PATH`; the system profile then
uses the default system loader without a private path. Bundled profiles put the
validated packaged `native/lib` first. AppImage and Flatpak resolve the declared
external graphics/audio interfaces through their normal host or sandbox loader.
Both remove ambient `LD_AUDIT`, `LD_PRELOAD`, and `LD_LIBRARY_PATH`; the system
profile then uses the default system loader without a private path. Bundled
profiles put the validated packaged `native/lib` first. AppImage and Flatpak
resolve the declared external graphics/audio interfaces through their normal
host or sandbox loader.
Inside a genuine Snap mount, filtered absolute `SNAP_LIBRARY_PATH` entries below
`/var/lib/snapd/lib/gl` follow `native/lib` and precede the generic
`$SNAP/lib`, `$SNAP/usr/lib`, and x64 multiarch roots. This preserves the pinned
@@ -558,7 +559,7 @@ The Electron main process holds an `electron.powerSaveBlocker` of type `prevent-
Current development behavior:
- The addon build supports `darwin`, `win32`, and `linux`; Windows and Linux builds require running on that target OS.
- The build script first looks for staged inputs at `vendor/embedded-mpv/<platform>-<arch>/`. On Linux, local development can fall back to distribution `libmpv-dev` headers and libraries; `LIBMPV_INCLUDE_DIR` and `LINUX_NATIVE_LIBRARY_DIR` override the default system paths.
- The build script first looks for staged inputs at `vendor/embedded-mpv/<platform>-<arch>/`. On Linux, local development can fall back to distribution `libmpv-dev` headers and libraries. `LIBMPV_INCLUDE_DIR` overrides the header root. `LINUX_NATIVE_LIBRARY_DIR` is a link-time override and must name a directory already visible to the system dynamic loader; it is never inherited as helper `LD_LIBRARY_PATH`.
- When the staged-input path is used, it must contain `include/mpv/client.h`,
`runtime-manifest.json`, and the platform runtime/build files. The Linux
source builder also stages the complete declared `.so` closure.
+7 -4
View File
@@ -126,8 +126,9 @@ POSIX shm namespace without granting global cross-snap shared-memory access.
The bounded probe and every playback helper share one sanitized loader
environment derived from the validated, cached runtime mode. Ambient
`LD_PRELOAD` and `LD_LIBRARY_PATH` are removed. System packages then use the
default loader; bundled packages put their validated `native/lib` first.
`LD_AUDIT`, `LD_PRELOAD`, and `LD_LIBRARY_PATH` are removed. System packages
then use the default loader; bundled packages put their validated `native/lib`
first.
AppImage and Flatpak use normal host/sandbox lookup for the declared external
interfaces. In a genuine Snap mount, filtered `SNAP_LIBRARY_PATH` GL roots
under `/var/lib/snapd/lib/gl` come next, ahead of generic `$SNAP` library and
@@ -230,8 +231,10 @@ license notices with the binary.
Linux can use distribution development packages for an unshipped local build
(`libmpv-dev`, EGL/OpenGL/GBM development files, and X11 headers). Overrides:
`LIBMPV_INCLUDE_DIR` and `LINUX_NATIVE_LIBRARY_DIR`. Required/release package
builds must use the pinned staged runtime and manifest.
`LIBMPV_INCLUDE_DIR` selects the header root. `LINUX_NATIVE_LIBRARY_DIR`
selects a link-time library directory that must already be visible to the
system dynamic loader; it is not inherited as a helper `LD_LIBRARY_PATH`.
Required/release package builds must use the pinned staged runtime and manifest.
On macOS:
@@ -362,6 +362,18 @@ test('Linux CI verifies every package family and exercises intended environments
assert.doesNotMatch(buildWorkflow, /\bldd\b/);
});
test('foreign DEB CI explicitly selects both marker-only ARM architectures', () => {
const foreignDebStep = workflowStep(
'Make marker-only foreign-architecture DEB packages'
);
assert.match(foreignDebStep, /for foreign_arch in armv7l arm64; do/);
assert.match(foreignDebStep, /--arch="\$\{foreign_arch\}"/);
assert.match(foreignDebStep, /IPTVNATOR_LINUX_FRAME_COPY_PROFILE: ''/);
assert.match(foreignDebStep, /IPTVNATOR_REQUIRE_EMBEDDED_MPV: '1'/);
assert.doesNotMatch(foreignDebStep, /IPTVNATOR_REQUIRE_EMBEDDED_MPV: '0'/);
});
test('dedicated packaged x64 smoke cannot silently skip', () => {
const linuxDependencies = workflowStep('Install Linux system dependencies');
assert.match(linuxDependencies, /--no-install-recommends/);
@@ -179,7 +179,8 @@ export function extractLinuxArtifact({
destination,
runCommand = defaultRunCommand,
}) {
fs.mkdirSync(destination, { recursive: true });
fs.rmSync(destination, { recursive: true, force: true });
fs.mkdirSync(path.dirname(destination), { recursive: true });
const run = (command, args, options = {}) =>
assertCommandSucceeded(
command,
@@ -198,7 +199,6 @@ export function extractLinuxArtifact({
const failures = [];
for (const offset of squashfsOffsets) {
fs.rmSync(destination, { recursive: true, force: true });
fs.mkdirSync(destination, { recursive: true });
const args = [
'-no-progress',
'-offset',
@@ -226,10 +226,12 @@ export function extractLinuxArtifact({
);
}
case 'deb':
fs.mkdirSync(destination, { recursive: true });
run('dpkg-deb', ['--extract', artifactPath, destination]);
return destination;
case 'rpm':
case 'pacman':
fs.mkdirSync(destination, { recursive: true });
run('bsdtar', [
'--extract',
'--file',
@@ -247,9 +249,15 @@ export function extractLinuxArtifact({
]);
return destination;
case 'flatpak': {
fs.mkdirSync(destination, { recursive: true });
const repository = path.join(destination, '.ostree-repository');
const checkout = path.join(destination, 'checkout');
fs.mkdirSync(repository, { recursive: true });
run('ostree', [
`--repo=${repository}`,
'init',
'--mode=archive-z2',
]);
run('flatpak', ['build-import-bundle', repository, artifactPath]);
const refsResult = run('ostree', ['refs', `--repo=${repository}`]);
const refs = String(refsResult.stdout ?? '')
@@ -265,6 +273,7 @@ export function extractLinuxArtifact({
}
run('ostree', [
'checkout',
'-U',
`--repo=${repository}`,
refs[0],
checkout,
@@ -1122,6 +1131,7 @@ export function createRuntimeProbeEnvironment({
runtimeMode,
}) {
const probeEnvironment = { ...(environment ?? {}) };
delete probeEnvironment.LD_AUDIT;
delete probeEnvironment.LD_LIBRARY_PATH;
delete probeEnvironment.LD_PRELOAD;
if (runtimeMode === 'bundled') {
@@ -378,6 +378,7 @@ test('extracts every payload format with argument arrays and no shell', () => {
'bsdtar',
'bsdtar',
'unsquashfs',
'ostree',
'flatpak',
'ostree',
'ostree',
@@ -402,6 +403,142 @@ test('extracts every payload format with argument arrays and no shell', () => {
'-no-progress',
'-dest',
]);
assert.equal(invocations[5].args[1], 'init');
assert.ok(invocations[5].args.includes('--mode=archive-z2'));
assert.equal(invocations[8].args[0], 'checkout');
assert.ok(invocations[8].args.includes('-U'));
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('gives unsquashfs a fresh destination for every AppImage and Snap extraction', () => {
const root = fs.mkdtempSync(
path.join(os.tmpdir(), 'iptvnator-verifier-unsquashfs-')
);
const appImagePath = path.join(root, 'IPTVnator.AppImage');
const snapPath = path.join(root, 'IPTVnator.snap');
const appImageDestination = path.join(root, 'appimage-payload');
const snapDestination = path.join(root, 'snap-payload');
fs.writeFileSync(
appImagePath,
Buffer.concat([
Buffer.alloc(128),
Buffer.from('hsqs'),
Buffer.alloc(64),
Buffer.from('hsqs'),
])
);
fs.writeFileSync(snapPath, 'snap fixture');
fs.mkdirSync(appImageDestination);
fs.mkdirSync(snapDestination);
let appImageAttempt = 0;
try {
extractLinuxArtifact({
artifactPath: appImagePath,
format: 'appimage',
destination: appImageDestination,
runCommand: (command) => {
assert.equal(command, 'unsquashfs');
assert.equal(fs.existsSync(appImageDestination), false);
appImageAttempt += 1;
fs.mkdirSync(appImageDestination);
return {
status: appImageAttempt === 1 ? 1 : 0,
stdout: '',
stderr: '',
};
},
});
assert.equal(appImageAttempt, 2);
extractLinuxArtifact({
artifactPath: snapPath,
format: 'snap',
destination: snapDestination,
runCommand: (command) => {
assert.equal(command, 'unsquashfs');
assert.equal(fs.existsSync(snapDestination), false);
fs.mkdirSync(snapDestination);
return { status: 0, stdout: '', stderr: '' };
},
});
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('initializes a user-checkout OSTree repository before importing Flatpak bundles', () => {
const root = fs.mkdtempSync(
path.join(os.tmpdir(), 'iptvnator-verifier-flatpak-repo-')
);
const artifactPath = path.join(root, 'IPTVnator.flatpak');
const destination = path.join(root, 'flatpak-payload');
const invocations = [];
let initializedRepository = null;
fs.writeFileSync(artifactPath, 'flatpak fixture');
try {
extractLinuxArtifact({
artifactPath,
format: 'flatpak',
destination,
runCommand: (command, args) => {
invocations.push([command, ...args]);
if (command === 'ostree' && args.includes('init')) {
initializedRepository = args
.find((argument) => argument.startsWith('--repo='))
?.slice('--repo='.length);
assert.ok(args.includes('--mode=archive-z2'));
return { status: 0, stdout: '', stderr: '' };
}
if (command === 'flatpak') {
assert.equal(args[0], 'build-import-bundle');
assert.equal(args[1], initializedRepository);
if (!initializedRepository) {
return {
status: 1,
stdout: '',
stderr: 'error: opening repo: opendir(objects): No such file or directory',
};
}
return { status: 0, stdout: '', stderr: '' };
}
if (command === 'ostree' && args[0] === 'refs') {
return {
status: 0,
stdout: 'app/com.fourgray.iptvnator/x86_64/stable\n',
stderr: '',
};
}
if (command === 'ostree' && args[0] === 'checkout') {
assert.ok(args.includes('-U'));
return { status: 0, stdout: '', stderr: '' };
}
throw new Error(`Unexpected command: ${command}`);
},
});
assert.deepEqual(
invocations.map(([command, ...args]) => [
command,
args.find((argument) =>
[
'init',
'build-import-bundle',
'refs',
'checkout',
].includes(argument)
),
]),
[
['ostree', 'init'],
['flatpak', 'build-import-bundle'],
['ostree', 'refs'],
['ostree', 'checkout'],
]
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
@@ -602,6 +739,7 @@ test('validates an x64 system payload and executes one bounded helper probe', ()
},
environment: {
PATH: '/usr/bin',
LD_AUDIT: '/host/can-inject-audit.so',
LD_LIBRARY_PATH: '/host/can-mask-missing-dependencies',
LD_PRELOAD: '/host/can-inject.so',
},
@@ -976,6 +1114,7 @@ test('bundled probes use only the packaged library directory', () => {
createRuntimeProbeEnvironment({
environment: {
PATH: '/usr/bin',
LD_AUDIT: '/host/can-inject-audit.so',
LD_LIBRARY_PATH: '/host/can-mask-missing-dependencies',
LD_PRELOAD: '/host/can-inject.so',
},