fix(packaging): bound helper probe capture

This commit is contained in:
4gray committed 2026-07-18 13:50:23 +02:00
1 parent 43f4388665
commit a651dd69a4
11 files changed
+79 -28

No files matched your search

+8 -7
View File
@@ -282,13 +282,14 @@ Key files:
keeps top-level reason `helper-probe-failed`; `helperReason` is present only
for an exact protocol-v1 line carrying a fixed allowlisted reason, and its
optional `helperDetail` must be 1–1024 printable ASCII characters. Invalid
detail suppresses both helper fields. With `IPTVNATOR_TRACE_PLAYER=1`, a
non-empty helper stderr capture is emitted separately as one JSON-escaped
stderr line whose `stderr` field is limited to 16,384 characters and whose
`truncated` field is always explicit; trace-write failure cannot change the
capability result. Installed-Snap CI enables Mesa EGL/GL diagnostics through
this bounded channel. Any loader failure remains a stable native-view
fallback, never a flag-enabled success.
detail suppresses both helper fields. Every probe uses an explicit 16 MiB
per-stream child-capture ceiling independent of tracing. With
`IPTVNATOR_TRACE_PLAYER=1`, a non-empty helper stderr capture is emitted
separately as one JSON-escaped stderr line whose `stderr` field is limited
to 16,384 characters and whose `truncated` field is always explicit;
trace-write failure cannot change the capability result. Installed-Snap CI
enables Mesa EGL/GL diagnostics through this bounded channel. Any loader
failure remains a stable native-view fallback, never a flag-enabled success.
- In the exact packaged Flatpak `/app` context, reconstruct only Freedesktop
Platform 24.08's immutable `__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS`; its GL
extension loader path comes from the sandbox cache. Flatpak CI must invoke
+4 -3
View File
@@ -673,9 +673,10 @@ engine` (restart required) or
helper exit keeps top-level reason `helper-probe-failed`; `helperReason` is
present only for an exact protocol-v1 line carrying a fixed allowlisted
reason, and its optional `helperDetail` must be 1–1024 printable ASCII
characters. Invalid detail suppresses both helper fields. With
`IPTVNATOR_TRACE_PLAYER=1`, non-empty helper stderr is emitted separately as
one JSON-escaped stderr line with a 16,384-character `stderr` limit and an
characters. Invalid detail suppresses both helper fields. Every probe uses
an explicit 16 MiB per-stream child-capture ceiling independent of tracing.
With `IPTVNATOR_TRACE_PLAYER=1`, non-empty helper stderr is emitted separately
as one JSON-escaped stderr line with a 16,384-character `stderr` limit and an
explicit `truncated` field; trace-write failure cannot change availability.
Installed-Snap CI enables Mesa EGL/GL diagnostics through this bounded
channel. The exact packaged Flatpak `/app` context reconstructs only
@@ -12,7 +12,8 @@ export const FRAME_COPY_ADDON_NAME = 'embedded_mpv.node';
export const FRAME_COPY_READER_NAME = 'embedded_mpv_frame_reader.node';
export const FRAME_COPY_HELPER_NAME = 'iptvnator_mpv_helper';
export const RUNTIME_PROBE_PROTOCOL = 1;
export const { RUNTIME_PROBE_TIMEOUT_MS } = runtimeProbeContract;
export const { RUNTIME_PROBE_MAX_BUFFER_BYTES, RUNTIME_PROBE_TIMEOUT_MS } =
runtimeProbeContract;
export const VERSIONED_LIBMPV_PATTERN = /^libmpv\.so\.\d+(?:\.\d+)*$/;
export const SAFE_RUNTIME_NAME_PATTERN = /^[A-Za-z0-9_+.-]+$/;
export const SHARED_LIBRARY_PATTERN = /\.so(?:\.\d+)*$/;
@@ -90,6 +90,7 @@ describe('embedded-mpv frame-copy runtime probe orchestration', () => {
timeout: 3000,
killSignal: 'SIGKILL',
windowsHide: true,
maxBuffer: 16 * 1024 * 1024,
env: {
PATH: '/usr/bin',
LIBGL_ALWAYS_SOFTWARE: '1',
@@ -171,6 +172,11 @@ describe('embedded-mpv frame-copy runtime probe orchestration', () => {
});
const virtualSnapRoot = '/snap/iptvnator/42';
const linuxTriplet = 'x86_64-linux-gnu';
const snapLibraries = (...relativePaths: string[]): string[] =>
relativePaths.map((relativePath) =>
path.join(virtualSnapRoot, relativePath)
);
const virtualNativeDir = path.join(
virtualSnapRoot,
'resources',
@@ -231,6 +237,8 @@ describe('embedded-mpv frame-copy runtime probe orchestration', () => {
virtualSnapRoot,
'gnome-platform'
),
SNAP_LIBRARY_PATH:
'/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl/nvidia',
},
fileSystem: virtualFileSystem,
});
@@ -248,9 +256,28 @@ describe('embedded-mpv frame-copy runtime probe orchestration', () => {
expect.objectContaining({
env: expect.objectContaining({
SNAP: virtualSnapRoot,
LD_LIBRARY_PATH: expect.stringContaining(
path.join(virtualNativeDir, 'lib')
),
LD_LIBRARY_PATH: [
path.join(virtualNativeDir, 'lib'),
'/var/lib/snapd/lib/gl',
'/var/lib/snapd/lib/gl/nvidia',
...snapLibraries(
`graphics/usr/lib/${linuxTriplet}`,
`graphics/usr/lib/${linuxTriplet}/vdpau`
),
'/usr/lib/x86_64-linux-gnu',
...snapLibraries(
`gnome-platform/lib/${linuxTriplet}`,
`gnome-platform/usr/lib/${linuxTriplet}`,
`gnome-platform/usr/lib/${linuxTriplet}/mesa`,
`gnome-platform/usr/lib/${linuxTriplet}/mesa-egl`,
`gnome-platform/usr/lib/${linuxTriplet}/dri`,
`gnome-platform/usr/lib/${linuxTriplet}/pulseaudio`,
'lib',
'usr/lib',
`lib/${linuxTriplet}`,
`usr/lib/${linuxTriplet}`
),
].join(':'),
}),
})
);
@@ -3,6 +3,7 @@ import * as nodeFileSystem from 'fs';
import path from 'path';
import {
RUNTIME_MANIFEST_NAME,
RUNTIME_PROBE_MAX_BUFFER_BYTES,
RUNTIME_PROBE_PROTOCOL,
RUNTIME_PROBE_TIMEOUT_MS,
} from './contracts';
@@ -180,6 +181,7 @@ function runHelperProbe(
timeout: RUNTIME_PROBE_TIMEOUT_MS,
killSignal: 'SIGKILL',
windowsHide: true,
maxBuffer: RUNTIME_PROBE_MAX_BUFFER_BYTES,
env: launch.env,
});
} catch {
+9 -6
View File
@@ -282,12 +282,15 @@ allowlisted helper reason as `helperReason`. An optional `helperDetail` is
copied only from the same exact line when it contains 1–1024 printable ASCII
characters; an invalid detail rejects both helper fields. Malformed,
multi-line, wrong-protocol, or unknown failure output never reaches either
field. When `IPTVNATOR_TRACE_PLAYER=1`, the probe also emits non-empty captured
helper stderr separately as one JSON line. JSON escaping keeps embedded
newlines on that single line, the `stderr` field is limited to the first 16,384
characters, and the `truncated` boolean is always present. A missing flag,
empty capture, or trace-writer failure produces no trace and never changes the
cached availability result or the application diagnostic's stdout protocol.
field. Every probe uses the same explicit 16 MiB per-stream child-capture
ceiling, independent of tracing, so verbose diagnostics do not fall back to
Node's smaller implicit buffer. When `IPTVNATOR_TRACE_PLAYER=1`, the probe also
emits non-empty captured helper stderr separately as one JSON line. JSON
escaping keeps embedded newlines on that single line, the `stderr` field is
limited to the first 16,384 characters, and the `truncated` boolean is always
present. A missing flag, empty capture, or trace-writer failure produces no
trace and never changes the cached availability result or the application
diagnostic's stdout protocol.
The startup probe and every playback helper session use the same sanitized
loader environment selected by the validated manifest's cached `runtimeMode`.
+8 -6
View File
@@ -239,12 +239,14 @@ application diagnostic retains `helper-probe-failed` as the top-level reason
for nonzero helper exits and adds `helperReason` only when the helper emitted
one exact protocol-v1 line with a fixed allowlisted reason. Its optional
`helperDetail` is restricted to 1–1024 printable ASCII characters; invalid
detail suppresses both helper fields. With `IPTVNATOR_TRACE_PLAYER=1`, non-empty
captured helper stderr is written separately as one JSON-escaped stderr line:
its `stderr` field contains at most the first 16,384 characters and its
`truncated` boolean is always explicit. Empty captures, disabled tracing, and
trace-writer failures do not emit a record or alter availability. The
installed-Snap probe therefore tests the private shared-memory confinement
detail suppresses both helper fields. Every probe has the same explicit 16 MiB
per-stream child-capture ceiling, regardless of tracing. With
`IPTVNATOR_TRACE_PLAYER=1`, non-empty captured helper stderr is written
separately as one JSON-escaped stderr line: its `stderr` field contains at most
the first 16,384 characters and its `truncated` boolean is always explicit.
Empty captures, disabled tracing, and trace-writer failures do not emit a
record or alter availability. The installed-Snap probe therefore tests the
private shared-memory confinement
needed by playback rather than only loader and graphics startup.
Packaging CI invokes the same gate through
`snap run iptvnator --embedded-mpv-runtime-probe`. This packaging-only
@@ -1,4 +1,5 @@
const RUNTIME_PROBE_CONTRACT = Object.freeze({
RUNTIME_PROBE_MAX_BUFFER_BYTES: 16 * 1024 * 1024,
RUNTIME_PROBE_TIMEOUT_MS: 3000,
});
@@ -1,4 +1,5 @@
declare const RUNTIME_PROBE_CONTRACT: Readonly<{
readonly RUNTIME_PROBE_MAX_BUFFER_BYTES: 16777216;
readonly RUNTIME_PROBE_TIMEOUT_MS: 3000;
}>;
@@ -23,6 +23,7 @@ const {
resolveLinuxFrameCopyProfile,
} = require('./linux-frame-copy-profile.cjs');
const {
RUNTIME_PROBE_MAX_BUFFER_BYTES,
RUNTIME_PROBE_TIMEOUT_MS,
} = require('../embedded-mpv/runtime-probe-contract.cjs');
@@ -71,6 +72,7 @@ function defaultRunCommand(command, args, options = {}) {
stdio: 'pipe',
timeout: options.timeout,
killSignal: options.killSignal,
maxBuffer: options.maxBuffer,
windowsHide: true,
});
}
@@ -1611,6 +1613,7 @@ export function verifyExtractedLinuxFrameCopyRuntime({
encoding: 'utf8',
env: probeEnvironment,
killSignal: 'SIGKILL',
maxBuffer: RUNTIME_PROBE_MAX_BUFFER_BYTES,
timeout: RUNTIME_PROBE_TIMEOUT_MS,
windowsHide: true,
}
@@ -91,7 +91,7 @@ const DEB_SYSTEM_PACKAGE_DEPENDENCIES = [
'libgbm1',
];
test('uses the shared frozen 3000 ms runtime probe timeout contract', async () => {
test('uses the shared frozen runtime probe resource contract', async () => {
assert.equal(
fs.existsSync(runtimeProbeContractUrl),
true,
@@ -108,10 +108,18 @@ test('uses the shared frozen 3000 ms runtime probe timeout contract', async () =
);
assert.equal(Object.isFrozen(runtimeProbeContract), true);
assert.equal(runtimeProbeContract.RUNTIME_PROBE_TIMEOUT_MS, 3000);
assert.equal(
runtimeProbeContract.RUNTIME_PROBE_MAX_BUFFER_BYTES,
16 * 1024 * 1024
);
assert.match(
fs.readFileSync(runtimeProbeContractTypesUrl, 'utf8'),
/readonly RUNTIME_PROBE_TIMEOUT_MS: 3000/
);
assert.match(
fs.readFileSync(runtimeProbeContractTypesUrl, 'utf8'),
/readonly RUNTIME_PROBE_MAX_BUFFER_BYTES: 16777216/
);
const verifierSource = fs.readFileSync(verifierUrl, 'utf8');
assert.match(
@@ -133,7 +141,7 @@ test('uses the shared frozen 3000 ms runtime probe timeout contract', async () =
);
assert.match(
backendContractsSource,
/export const \{ RUNTIME_PROBE_TIMEOUT_MS \} = runtimeProbeContract/
/export const \{\s*RUNTIME_PROBE_MAX_BUFFER_BYTES,\s*RUNTIME_PROBE_TIMEOUT_MS,?\s*\}\s*=\s*runtimeProbeContract/
);
assert.doesNotMatch(
backendContractsSource,
@@ -784,6 +792,7 @@ test('validates an x64 system payload and executes one bounded helper probe', ()
encoding: 'utf8',
env: { PATH: '/usr/bin' },
killSignal: 'SIGKILL',
maxBuffer: 16 * 1024 * 1024,
timeout: 3000,
windowsHide: true,
});