mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 04:26:16 -08:00
fix(packaging): enforce direct Flatpak Zypak launch
This commit is contained in:
1 parent
fc76dd1ad2
commit
0962765793
9 files changed
+206
-27
No files matched your search
@@ -1104,14 +1104,38 @@ jobs:
|
||||
|
||||
LAUNCHER_PATH="$(readlink -f /app/bin/iptvnator)"
|
||||
test -f "${LAUNCHER_PATH}"
|
||||
test -f "${LAUNCHER_PATH}.bin"
|
||||
grep -q '\''readlink -f "$SCRIPT_PATH"'\'' "${LAUNCHER_PATH}"
|
||||
grep -q '\''exec "$SCRIPT_DIR/iptvnator.bin"'\'' "${LAUNCHER_PATH}"
|
||||
test -x "${LAUNCHER_PATH}"
|
||||
if [ -e "${LAUNCHER_PATH}.bin" ] || [ -L "${LAUNCHER_PATH}.bin" ]; then
|
||||
echo "::error::Flatpak must not contain ${LAUNCHER_PATH}.bin"
|
||||
exit 1
|
||||
fi
|
||||
ELF_MAGIC="$(od -An -tx1 -N4 "${LAUNCHER_PATH}" | tr -d "[:space:]")"
|
||||
test "${ELF_MAGIC}" = "7f454c46"
|
||||
'
|
||||
xvfb-run -a dbus-run-session -- flatpak run \
|
||||
--env=LIBGL_ALWAYS_SOFTWARE=1 \
|
||||
com.fourgray.iptvnator \
|
||||
--embedded-mpv-runtime-probe
|
||||
set +e
|
||||
PROBE_OUTPUT="$(
|
||||
xvfb-run -a dbus-run-session -- flatpak run \
|
||||
--env=LIBGL_ALWAYS_SOFTWARE=1 \
|
||||
com.fourgray.iptvnator \
|
||||
--embedded-mpv-runtime-probe 2>&1
|
||||
)"
|
||||
PROBE_STATUS=$?
|
||||
set -e
|
||||
|
||||
PROBE_OUTPUT_LIMIT=16384
|
||||
printf '%s\n' "${PROBE_OUTPUT:0:PROBE_OUTPUT_LIMIT}"
|
||||
if [ "${#PROBE_OUTPUT}" -gt "${PROBE_OUTPUT_LIMIT}" ]; then
|
||||
echo "::warning::Flatpak runtime probe output was truncated to ${PROBE_OUTPUT_LIMIT} characters."
|
||||
fi
|
||||
if [[ "${PROBE_OUTPUT}" == *"not an ELF file"* ]] ||
|
||||
[[ "${PROBE_OUTPUT}" == *"Zypak needs to be called directly"* ]]; then
|
||||
echo "::error::Flatpak launched a wrapper instead of the Electron ELF."
|
||||
exit 1
|
||||
fi
|
||||
if [ "${PROBE_STATUS}" -ne 0 ]; then
|
||||
echo "::error::Flatpak application runtime probe failed with status ${PROBE_STATUS}."
|
||||
exit "${PROBE_STATUS}"
|
||||
fi
|
||||
|
||||
- name: Upload artifacts (macOS)
|
||||
if: matrix.os == 'macos'
|
||||
|
||||
@@ -229,6 +229,10 @@ Key files:
|
||||
Pacman=`mpv,libglvnd,mesa`
|
||||
- `portable`: AppImage/Snap with the pinned LGPL-compatible closure
|
||||
- `flatpak`: Flatpak with the same pinned closure
|
||||
- Flatpak is an isolated packaging pass and keeps `iptvnator` as the real
|
||||
Electron ELF so Electron Builder's `electron-wrapper` passes it directly to
|
||||
Zypak. Other Linux targets retain the conditional `iptvnator` wrapper and
|
||||
`iptvnator.bin`. Mixed Flatpak/non-Flatpak target sets fail before mutation.
|
||||
- The DEB system-runtime contract is Ubuntu 24.04+ (`libmpv2`). Ubuntu 22.04
|
||||
provides `libmpv1`, so use the x64 AppImage on Jammy instead of weakening the
|
||||
package dependency or advertising frame-copy without a compatible runtime.
|
||||
|
||||
@@ -639,8 +639,12 @@ engine` (restart required) or
|
||||
Official x64 packages use three separate profiles:
|
||||
DEB/RPM/Pacman depend on system libmpv plus the helper's direct
|
||||
EGL/GL/GBM interfaces, AppImage/Snap bundle the pinned LGPL closure, and
|
||||
Flatpak bundles the same closure. Exact system dependencies are
|
||||
DEB=`libmpv2,libegl1,libgl1,libgbm1`,
|
||||
Flatpak bundles the same closure. Flatpak is an isolated packaging pass and
|
||||
keeps `iptvnator` as the real Electron ELF so Electron Builder's
|
||||
`electron-wrapper` passes it directly to Zypak. Other Linux targets retain the
|
||||
conditional `iptvnator` wrapper and `iptvnator.bin`. Mixed
|
||||
Flatpak/non-Flatpak target sets fail before mutation. Exact system
|
||||
dependencies are DEB=`libmpv2,libegl1,libgl1,libgbm1`,
|
||||
RPM=`mpv-libs,libglvnd-egl,libglvnd-glx,mesa-libgbm`, and
|
||||
Pacman=`mpv,libglvnd,mesa`. The DEB contract is verified on Ubuntu 24.04+;
|
||||
Ubuntu 22.04 users need the x64 AppImage because Jammy provides `libmpv1`.
|
||||
|
||||
@@ -695,11 +695,13 @@ Linux release profiles:
|
||||
source-built closure and manifest origin. Its app-level probe reconstructs
|
||||
only the exact Freedesktop 24.08 EGL external-platform search path inside the
|
||||
trusted `/app` payload.
|
||||
- The three profiles are separate packaging passes; mixing target sets fails
|
||||
closed. Linux packages for other architectures (arm64, armv7l) must not ship
|
||||
x64 native artifacts. `afterPack` replaces the native directory with
|
||||
`embedded-mpv-unavailable.txt`, and package verification requires that
|
||||
marker.
|
||||
- Flatpak is an isolated packaging pass and keeps `iptvnator` as the real
|
||||
Electron ELF so Electron Builder's `electron-wrapper` passes it directly to
|
||||
Zypak. Other Linux targets retain the conditional `iptvnator` wrapper and
|
||||
`iptvnator.bin`. Mixed Flatpak/non-Flatpak target sets fail before mutation.
|
||||
- Linux packages for other architectures (arm64, armv7l) must not ship x64
|
||||
native artifacts. `afterPack` replaces the native directory with
|
||||
`embedded-mpv-unavailable.txt`, and package verification requires that marker.
|
||||
- Every packaged manifest names its exact artifacts, profile/targets, libmpv
|
||||
SONAME, loader closure, byte sizes, SHA-256 hashes, package dependencies, and
|
||||
native-view fallback. Artifact modes and ELF dependency isolation are
|
||||
|
||||
@@ -55,6 +55,11 @@ that the selected target set matches the runtime mode. It must fail closed if
|
||||
an official x64 package is requested with an absent, incomplete, or ambiguous
|
||||
profile.
|
||||
|
||||
Flatpak is an isolated packaging pass and keeps `iptvnator` as the real
|
||||
Electron ELF so Electron Builder's `electron-wrapper` passes it directly to
|
||||
Zypak. Other Linux targets retain the conditional `iptvnator` wrapper and
|
||||
`iptvnator.bin`. Mixed Flatpak/non-Flatpak target sets fail before mutation.
|
||||
|
||||
System package dependencies are:
|
||||
|
||||
- DEB: `libmpv2`, `libegl1`, `libgl1`, `libgbm1`
|
||||
@@ -131,8 +136,9 @@ the complete non-system dependency closure. ELF dependencies inside that
|
||||
closure and the helper use only SONAMEs plus `$ORIGIN`-relative RPATH/RUNPATH;
|
||||
they may not retain build-prefix paths.
|
||||
|
||||
`embedded_mpv.node`, the Electron executable (`iptvnator.bin`), and Electron's
|
||||
shipped libraries must not have a direct `DT_NEEDED` entry for libmpv.
|
||||
`embedded_mpv.node`, the Electron executable (`iptvnator` for Flatpak;
|
||||
`iptvnator.bin` for other Linux targets), and Electron's shipped libraries must
|
||||
not have a direct `DT_NEEDED` entry for libmpv.
|
||||
`iptvnator_mpv_helper` must have one. Process isolation is an invariant, not a
|
||||
profile-specific choice. The source `electron-backend/native{,/**/*}` tree is
|
||||
excluded from `app.asar`; `afterPack` is the sole owner of the normalized
|
||||
|
||||
@@ -190,9 +190,12 @@ controlled status `1`, then reconnects the provider and requires a successful
|
||||
diagnostic. This keeps the canonical layouts and missing-provider fallback in
|
||||
the same regression contract.
|
||||
|
||||
Profiles cannot share one Electron Builder pass because its targets reuse the
|
||||
same unpacked application directory. A missing or unsupported profile, or a
|
||||
target from another profile, fails packaging.
|
||||
Flatpak is an isolated packaging pass and keeps `iptvnator` as the real
|
||||
Electron ELF so Electron Builder's `electron-wrapper` passes it directly to
|
||||
Zypak. Other Linux targets retain the conditional `iptvnator` wrapper and
|
||||
`iptvnator.bin`. Mixed Flatpak/non-Flatpak target sets fail before mutation. A
|
||||
missing or unsupported profile, or a target from another profile, fails
|
||||
packaging.
|
||||
|
||||
Linux frame-copy release artifacts are x64-only. Non-x64 packages are always
|
||||
marker-only even if environment variables point at the x64 staged runtime.
|
||||
|
||||
@@ -555,6 +555,52 @@ test('Flatpak application runtime probe runs under an isolated D-Bus session', (
|
||||
);
|
||||
});
|
||||
|
||||
test('Flatpak CI verifies the direct Zypak ELF and preserves probe status', () => {
|
||||
const flatpakVerificationStep = workflowStep(
|
||||
'Verify Flatpak payload, launcher, and sandboxed runtime'
|
||||
);
|
||||
|
||||
assert.match(
|
||||
flatpakVerificationStep,
|
||||
/LAUNCHER_PATH="\$\(readlink -f \/app\/bin\/iptvnator\)"/
|
||||
);
|
||||
assert.match(
|
||||
flatpakVerificationStep,
|
||||
/test -f "\$\{LAUNCHER_PATH\}"[\s\S]*test -x "\$\{LAUNCHER_PATH\}"/
|
||||
);
|
||||
assert.match(
|
||||
flatpakVerificationStep,
|
||||
/if \[ -e "\$\{LAUNCHER_PATH\}\.bin" \] \|\| \[ -L "\$\{LAUNCHER_PATH\}\.bin" \]; then/
|
||||
);
|
||||
assert.match(
|
||||
flatpakVerificationStep,
|
||||
/ELF_MAGIC="\$\(od -An -tx1 -N4 "\$\{LAUNCHER_PATH\}" \| tr -d "\[:space:\]"\)"[\s\S]*test "\$\{ELF_MAGIC\}" = "7f454c46"/
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
flatpakVerificationStep,
|
||||
/grep -q .*readlink -f "\$SCRIPT_PATH"/
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
flatpakVerificationStep,
|
||||
/grep -q .*exec "\$SCRIPT_DIR\/iptvnator\.bin"/
|
||||
);
|
||||
|
||||
assert.match(
|
||||
flatpakVerificationStep,
|
||||
/set \+e[\s\S]*PROBE_OUTPUT="\$\([\s\S]*xvfb-run -a dbus-run-session -- flatpak run[\s\S]*--embedded-mpv-runtime-probe 2>&1[\s\S]*\)"[\s\S]*PROBE_STATUS=\$\?[\s\S]*set -e/
|
||||
);
|
||||
assert.match(
|
||||
flatpakVerificationStep,
|
||||
/PROBE_OUTPUT_LIMIT=16384[\s\S]*"\$\{PROBE_OUTPUT:0:PROBE_OUTPUT_LIMIT\}"/
|
||||
);
|
||||
assert.match(flatpakVerificationStep, /not an ELF file/);
|
||||
assert.match(flatpakVerificationStep, /Zypak needs to be called directly/);
|
||||
assert.match(
|
||||
flatpakVerificationStep,
|
||||
/if \[ "\$\{PROBE_STATUS\}" -ne 0 \]; then[\s\S]*exit "\$\{PROBE_STATUS\}"/
|
||||
);
|
||||
});
|
||||
|
||||
test('foreign DEB CI explicitly selects both marker-only ARM architectures', () => {
|
||||
const foreignDebStep = workflowStep(
|
||||
'Make marker-only foreign-architecture DEB packages'
|
||||
|
||||
@@ -24,6 +24,7 @@ const {
|
||||
LINUX_SYSTEM_PACKAGE_DEPENDENCIES,
|
||||
resolveLinuxFrameCopyProfile,
|
||||
} = require('./linux-frame-copy-profile.cjs');
|
||||
const { resolveLinuxLauncherLayout } = require('./linux-launcher-layout.cjs');
|
||||
const {
|
||||
RUNTIME_PROBE_MAX_BUFFER_BYTES,
|
||||
RUNTIME_PROBE_TIMEOUT_MS,
|
||||
@@ -1350,9 +1351,14 @@ function dependencyFileName(dependencyName) {
|
||||
return String(dependencyName).replaceAll('\\', '/').split('/').at(-1) ?? '';
|
||||
}
|
||||
|
||||
function resolveElectronBinaryPath(resourceDir, artifactFormat) {
|
||||
const layout = resolveLinuxLauncherLayout([artifactFormat]);
|
||||
return path.join(path.dirname(resourceDir), layout.electronBinaryName);
|
||||
}
|
||||
|
||||
function validateElectronIsolation(resourceDir, artifactFormat, elfInspector) {
|
||||
const errors = [];
|
||||
const electronPath = path.join(path.dirname(resourceDir), 'iptvnator.bin');
|
||||
const electronPath = resolveElectronBinaryPath(resourceDir, artifactFormat);
|
||||
const binaries = [
|
||||
{ label: 'Electron binary', binaryPath: electronPath },
|
||||
...listElectronShippedLinuxLibraries(resourceDir, {
|
||||
@@ -1557,7 +1563,7 @@ export function verifyExtractedLinuxFrameCopyRuntime({
|
||||
)
|
||||
);
|
||||
|
||||
const electronPath = path.join(path.dirname(resourceDir), 'iptvnator.bin');
|
||||
const electronPath = resolveElectronBinaryPath(resourceDir, artifactFormat);
|
||||
let packageArch;
|
||||
try {
|
||||
packageArch = readElfArchitecture(electronPath);
|
||||
@@ -1709,10 +1715,7 @@ export function verifyLinuxFrameCopyArtifact({
|
||||
].join('\n')
|
||||
);
|
||||
}
|
||||
const electronPath = path.join(
|
||||
path.dirname(resourceDir),
|
||||
'iptvnator.bin'
|
||||
);
|
||||
const electronPath = resolveElectronBinaryPath(resourceDir, format);
|
||||
return {
|
||||
artifactPath: resolvedArtifactPath,
|
||||
format,
|
||||
|
||||
@@ -219,7 +219,10 @@ function elfHeader(architecture) {
|
||||
return image;
|
||||
}
|
||||
|
||||
function createSystemPayload({ architecture = 'x64' } = {}) {
|
||||
function createSystemPayload({
|
||||
architecture = 'x64',
|
||||
electronBinaryName = 'iptvnator.bin',
|
||||
} = {}) {
|
||||
const root = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'iptvnator-verifier-layout-')
|
||||
);
|
||||
@@ -233,7 +236,7 @@ function createSystemPayload({ architecture = 'x64' } = {}) {
|
||||
);
|
||||
fs.mkdirSync(nativeDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(appDir, 'iptvnator.bin'),
|
||||
path.join(appDir, electronBinaryName),
|
||||
elfHeader(architecture)
|
||||
);
|
||||
|
||||
@@ -1574,6 +1577,64 @@ test('artifact verification enforces Snap metadata for x64 and ARM payloads', ()
|
||||
}
|
||||
});
|
||||
|
||||
test('verifies an outer Flatpak artifact with an unwrapped Electron ELF', () => {
|
||||
const root = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'iptvnator-verifier-flatpak-artifact-')
|
||||
);
|
||||
const artifactPath = path.join(root, 'IPTVnator.flatpak');
|
||||
fs.writeFileSync(artifactPath, 'flatpak fixture');
|
||||
|
||||
try {
|
||||
assert.deepEqual(
|
||||
verifyLinuxFrameCopyArtifact({
|
||||
artifactPath,
|
||||
profileName: 'flatpak',
|
||||
extractArtifact({ destination }) {
|
||||
const appDir = path.join(
|
||||
destination,
|
||||
'files',
|
||||
'lib',
|
||||
'com.fourgray.iptvnator'
|
||||
);
|
||||
const resourceDir = path.join(appDir, 'resources');
|
||||
const nativeDir = path.join(
|
||||
resourceDir,
|
||||
'app.asar.unpacked',
|
||||
'electron-backend',
|
||||
'native'
|
||||
);
|
||||
fs.mkdirSync(nativeDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(appDir, 'iptvnator'),
|
||||
elfHeader('arm64')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(nativeDir, 'embedded-mpv-unavailable.txt'),
|
||||
'Unavailable for arm64\n'
|
||||
);
|
||||
return destination;
|
||||
},
|
||||
metadataReader: () => ({
|
||||
declaredArch: 'arm64',
|
||||
dependencies: [],
|
||||
}),
|
||||
elfInspector: validElfInspector,
|
||||
probeRunner() {
|
||||
assert.fail('foreign Flatpak must not probe');
|
||||
},
|
||||
}),
|
||||
{
|
||||
artifactPath: path.resolve(artifactPath),
|
||||
format: 'flatpak',
|
||||
profileName: 'flatpak',
|
||||
architecture: 'arm64',
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('bundled probes remove ambient loader paths and use only packaged libraries', () => {
|
||||
assert.deepEqual(
|
||||
createRuntimeProbeEnvironment({
|
||||
@@ -1824,6 +1885,32 @@ test('requires marker-only foreign packages, scans Electron, and never probes',
|
||||
}
|
||||
});
|
||||
|
||||
test('validates a marker-only Flatpak with an unwrapped Electron ELF', () => {
|
||||
const fixture = createSystemPayload({
|
||||
architecture: 'arm64',
|
||||
electronBinaryName: 'iptvnator',
|
||||
});
|
||||
try {
|
||||
assert.deepEqual(
|
||||
verifyExtractedLinuxFrameCopyRuntime({
|
||||
resourceDir: fixture.resourceDir,
|
||||
artifactFormat: 'flatpak',
|
||||
profileName: 'flatpak',
|
||||
packageDependencies: [],
|
||||
elfInspector: validElfInspector,
|
||||
probeRunner() {
|
||||
assert.fail(
|
||||
'foreign Flatpak must not run the helper probe'
|
||||
);
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(fixture.root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('always removes its temporary extraction root after a verifier failure', () => {
|
||||
const root = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'iptvnator-verifier-cleanup-parent-')
|
||||
|
||||
Reference in new issue
Block a user