Packaging Stremio for Debian: A Journey to 100% System Library Integration
Stremio for Debian: A Journey to 100% System Library Integration
How I replaced every bundled dependency in a complex Qt5 application—and what I learned about patch development, threading bugs, and the art of debugging runtime crashes
By Juan Manuel Méndez Rey
Last Updated: October 3, 2025
Tags: #debian
#packaging
#qt5
#technical-writing
#system-libraries
#debugging
#free-software
TL;DR
I packaged Stremio for Debian by replacing 100% of its bundled dependencies (libmpv, Qt libraries, OpenSSL) with system libraries. Along the way, I debugged five critical issues: QtWebEngine initialization order, threading conflicts with SingleApplication, missing QML modules, Node.js environment variables in QProcess, and debhelper install file pitfalls. The real lesson? I repeated patch creation 5+ times because I tested against modified sources instead of clean upstream. This article shares both the technical solutions and the meta-lesson about efficient patch development workflow that could have saved me 70% of development time.
Key Takeaway: When packaging complex applications, test your patches against pristine upstream at each step, not at the end.
Package Status (October 2025)
This article documents the technical work behind packaging Stremio for Debian. The package has achieved 100% system library integration and is currently:
- Technical work: Complete and validated
- ITP submitted: Under review by Debian Developer sponsor
- Status: Awaiting upload approval
- Repository: salsa.debian.org/mendezr/stremio
This is a technical deep-dive into the challenges and solutions, not an announcement of package availability. The work continues through the Debian review process.
Introduction
When I set out to package Stremio—a popular media center application—for Debian, I had one clear goal: achieve 100% system library integration. No bundled dependencies, no git submodules, just clean integration with Debian’s ecosystem. What seemed like a straightforward build system migration turned into a deep dive into Qt5 threading models, runtime initialization order, and the subtle art of creating minimal, maintainable patches.
This is the story of that journey, the technical challenges I faced, and—perhaps most importantly—the lessons I learned about efficient patch development that could have saved me days of rework.
The Challenge: System Libraries or Bust
Stremio’s upstream repository arrived with several bundled dependencies as git submodules:
libmpv
for video playbackqthelper
for Qt utilitiessingleapplication
for single-instance behavior- OpenSSL libraries
The Debian way is clear: use system-provided libraries. This isn’t just philosophical purity—it’s about security updates, dependency management, and integration with the broader ecosystem.
The goal: Replace every bundled dependency with its Debian system library equivalent.
The result: A working .deb
package with a 293KB optimized binary using 100% system libraries.
The journey: Five major technical hurdles, each revealing deeper insights into Qt5 application architecture.
The First Victory (That Wasn’t)
Initial packaging seemed straightforward. I modified CMakeLists.txt
to use system libraries:
find_package(PkgConfig REQUIRED)
pkg_check_modules(MPV REQUIRED mpv)
find_package(Qt5 REQUIRED COMPONENTS Core Gui Qml Quick WebEngine)
find_package(OpenSSL REQUIRED)
The build succeeded. Dependencies looked perfect:
$ ldd build/stremio | head -5
libQt5WebEngine.so.5 => /lib/x86_64-linux-gnu/libQt5WebEngine.so.5
libQt5DBus.so.5 => /lib/x86_64-linux-gnu/libQt5DBus.so.5
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3
libmpv.so.2 => /lib/x86_64-linux-gnu/libmpv.so.2
Perfect! Ship it, right?
Wrong. When I actually ran the application:
Segmentation fault (core dumped)
Thus began the real work.
Challenge 1: The QtWebEngine Initialization Bug
Symptom: Immediate segmentation fault when launching the application.
First debugging attempt: Run with gdb, examine the stack trace:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff5a2b3c4 in QQmlApplicationEngine::QQmlApplicationEngine() ()
The crash occurred during QQmlApplicationEngine
construction. But why? The same code worked fine with bundled libraries.
The investigation: After examining Qt5 WebEngine documentation and several failed attempts to reorganize the code, I discovered a critical initialization requirement buried in the QtWebEngine documentation:
QtWebEngine::initialize()
must be called before the QApplication constructor when using QML.
The bundled library setup happened to satisfy this ordering by accident. With system libraries, the default main()
function violated it:
// WRONG - causes crashes
int main(int argc, char *argv[]) {
QApplication app(argc, argv); // QApplication created first
// QtWebEngine::initialize() never called!
QQmlApplicationEngine engine; // CRASH
}
The fix (patch 0007-add-qtwebengine-initialize-fix.patch
):
// CORRECT - initialize QtWebEngine before QApplication
int main(int argc, char *argv[]) {
QtWebEngine::initialize(); // CRITICAL: Must be first!
QApplication app(argc, argv);
QQmlApplicationEngine engine; // Now works
}
Lesson: When replacing bundled libraries with system ones, initialization order assumptions may change. Always verify startup sequence requirements.
Challenge 2: The SingleApplication Threading Nightmare
Symptom: After fixing QtWebEngine initialization, the application launched but immediately crashed with:
QObject: Cannot create children for a parent that is in a different thread.
The culprit: System library libsingleapplication-dev
version 3.3.4.
Stremio needs single-instance behavior—when you launch it a second time, it should activate the existing window rather than start a new process. The upstream code used a bundled singleapplication
library. The Debian system provides libsingleapplication-dev
. Perfect replacement, right?
Wrong again.
The investigation: The system SingleApplication library sets up a threading context that conflicts with QQmlApplicationEngine
. Specifically:
- System SingleApplication creates its IPC mechanism in a worker thread
- QQmlApplicationEngine expects to be created in the main thread
- Qt5’s threading model doesn’t allow cross-thread parent-child relationships for certain QML objects
The bundled version used a different threading approach that happened to work with QML.
The false starts: I tried:
- Patching SingleApplication to use main thread (broke IPC)
- Deferring QML engine creation (broke startup sequence)
- Various Qt thread affinity hacks (broke other things)
The solution: Write a custom CompatibleSingleApp
class that provides identical functionality without threading conflicts:
// compatible_singleapp.h
class CompatibleSingleApp : public QApplication {
Q_OBJECT
public:
CompatibleSingleApp(int &argc, char **argv);
bool isPrimary() const;
bool isSecondary() const;
signals:
void instanceStarted();
private:
QLocalServer *m_server;
QString m_serverName;
bool m_isPrimary;
};
Implementation highlights (patch 0008-add-compatible-singleapp-implementation.patch
):
CompatibleSingleApp::CompatibleSingleApp(int &argc, char **argv)
: QApplication(argc, argv), m_isPrimary(false) {
m_serverName = "stremio-" + QString(qgetenv("USER"));
// Try to connect to existing instance
QLocalSocket socket;
socket.connectToServer(m_serverName);
if (socket.waitForConnected(500)) {
// Secondary instance - notify primary and exit
m_isPrimary = false;
socket.write("ACTIVATE");
socket.waitForBytesWritten();
return;
}
// Primary instance - create server
m_isPrimary = true;
m_server = new QLocalServer(this);
m_server->listen(m_serverName);
connect(m_server, &QLocalServer::newConnection, this, [this]() {
QLocalSocket *client = m_server->nextPendingConnection();
connect(client, &QLocalSocket::readyRead, [this, client]() {
QByteArray data = client->readAll();
if (data == "ACTIVATE") {
emit instanceStarted();
}
client->deleteLater();
});
});
}
Result: Perfect single-instance behavior using pure QApplication
(no threading conflicts) with QLocalSocket
/QLocalServer
for IPC.
Binary size: 424KB debug vs 293KB release—both using 100% system libraries.
Key lesson: System libraries may have different implementation details (like threading models) even when providing the same API. Sometimes a custom minimal implementation is cleaner than patching around incompatibilities.
Challenge 3: The Missing QML Modules
Symptom: After fixing both initialization and threading issues, the application launched but showed a black screen with console errors:
module "QtWebEngine" is not installed
module "QtWebChannel" is not installed
module "Qt.labs.platform" is not installed
The problem: Qt5 QML modules are separate runtime packages in Debian, not automatically pulled in by qtdeclarative5-dev
.
The investigation: Stremio’s QML code imports numerous Qt modules:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtWebEngine 1.10
import QtWebChannel 1.0
import Qt.labs.platform 1.1
import Qt.labs.settings 1.0
Each requires a separate Debian package.
The solution: Comprehensive dependency mapping in debian/control
:
Depends: ${shlibs:Depends}, ${misc:Depends},
qml-module-qtwebengine,
qml-module-qtwebchannel,
qml-module-qt-labs-platform,
qml-module-qtquick-controls,
qml-module-qtquick-dialogs,
qml-module-qt-labs-settings,
qml-module-qt-labs-folderlistmodel,
qtbase5-dev-tools
Lesson: When packaging Qt QML applications, trace every QML import statement to its corresponding Debian package. apt-file search
is your friend:
$ apt-file search QtWebEngine.qml
qml-module-qtwebengine: /usr/lib/x86_64-linux-gnu/qt5/qml/QtWebEngine/...
Challenge 4: The Streaming Server Mystery
Symptom: GUI loads perfectly, but when trying to play media:
Error while starting streaming server
tcp: Connection to tcp://127.0.0.1:11470 failed: Connection refused
The investigation: Stremio includes a Node.js server component (server.js
) for streaming. The shell process log showed:
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
at Object.join (node:path:1292:7)
The root cause: Qt’s QProcess
doesn’t inherit environment variables by default. The Node.js server expected HOME
, USER
, and PWD
to be available, but they weren’t.
The fix (patch 0011-fix-qprocess-environment-for-server-launch.patch
):
// stremioprocess.cpp
void Process::start() {
// Set up environment variables for Node.js server
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
if (!env.contains("HOME")) {
env.insert("HOME",
QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
}
if (!env.contains("USER")) {
env.insert("USER", qgetenv("USER"));
}
if (!env.contains("PWD")) {
env.insert("PWD", QDir::currentPath());
}
this->setProcessEnvironment(env);
QProcess::start();
}
Result: Server starts successfully:
hls executables located -> { ffmpeg: '/usr/bin/ffmpeg', ffsplit: null }
Using app path -> /home/user/.stremio-server
EngineFS server started at http://127.0.0.1:11470
Lesson: When spawning processes from Qt applications, explicitly configure the environment. Don’t assume child processes inherit the parent’s environment variables.
Challenge 5: Debian Packaging Structure Pitfalls
Symptom: Package builds successfully, but files install to wrong locations or with wrong names.
The problem: Misunderstanding debhelper’s .install
file behavior.
What I thought:
# debian/stremio.install
build/stremio usr/bin/stremio-bin # Install as /usr/bin/stremio-bin
What actually happened:
/usr/bin/stremio-bin/stremio # Created DIRECTORY, file inside!
The revelation: In debhelper .install
files:
- Path ending with
/
→ Install files INTO that directory using original names - Path WITHOUT
/
→ Create directory with that name and install files inside
The correct solution (actual implementation):
# debian/stremio.install
# Binary installed to /usr/libexec (FHS 3.0 compliance for helper executables)
build/stremio usr/libexec/stremio/
# Wrapper script becomes the primary user-facing command
debian/stremio-wrapper usr/bin/
# Desktop file for application menu integration
debian/stremio.desktop usr/share/applications/
# Application icons (multiple resolutions for different contexts)
icons/smartcode-stremio_16.png usr/share/icons/hicolor/16x16/apps/
icons/smartcode-stremio_64.png usr/share/icons/hicolor/64x64/apps/
# ... (additional icon sizes)
Why this structure?
/usr/libexec/stremio/
: Modern FHS 3.0 location for internal executables not meant to be called directly by users- Wrapper script at
/usr/bin/stremio
: Sets environment variables (likeQTWEBENGINE_DISABLE_SANDBOX=1
) before launching the actual binary - Trailing slashes: Install files INTO directories using original filenames—critical for correct placement
Lesson: Read debhelper documentation carefully. Small syntax details (trailing slashes!) have big consequences. Modern Debian packaging also follows FHS 3.0 standards, placing helper binaries in /usr/libexec/
rather than /usr/bin/
.
The Meta-Lesson: Efficient Patch Development
The technical challenges were difficult, but I made them harder through inefficient workflow. I created patches, tested them, found they failed on clean upstream, then reworked them—five times.
The problem: I was testing patches against already-modified sources, not pristine upstream.
Build System Strategy: Patch CMakeLists.txt First
Critical principle: Always prioritize build system patches over source code modifications.
When replacing bundled dependencies with system libraries, the first patches should target CMakeLists.txt
:
# Patch 0005-cmake-system-libraries-v4.4.169.patch
find_package(PkgConfig REQUIRED)
pkg_check_modules(MPV REQUIRED mpv)
find_package(Qt5 REQUIRED COMPONENTS Core Gui Qml Quick WebEngine DBus)
find_package(OpenSSL REQUIRED)
Why this matters: Smaller, focused patches that address build system integration separately from source code changes are easier to maintain and review.
Build system preference: We used qmake to generate makefiles first (Stremio’s traditional build system), then ensured CMake compatibility. The stremio.pro
file and release.makefile
workflow took precedence for package builds.
The Anti-Pattern
- Modify source files directly to fix issue
- Generate patches from modified state
- Try to apply patches to clean upstream
- Patches fail (missing context, wrong line numbers, missing dependencies)
- Repeat
The Efficient Workflow I Should Have Used
# 1. Start with clean upstream
git checkout v4.4.169
# 2. Create isolated test environment
cp -r . /tmp/patch-test/
cd /tmp/patch-test/
# 3. Fix ONE issue, test, generate patch
# (fix QtWebEngine initialization)
mkdir build && cd build && cmake .. && make # Test build
cd .. && git diff > 0001-qtwebengine-init.patch
# 4. Apply patch to clean upstream, fix next issue
git checkout v4.4.169
patch -p1 < 0001-qtwebengine-init.patch
# (fix next issue)
git diff > 0002-next-fix.patch
# 5. Final validation: apply all patches to clean upstream
git checkout v4.4.169
for patch in *.patch; do
patch -p1 < $patch || exit 1
done
mkdir build && cd build && cmake .. && make
Dependency analysis checklist I wish I’d used from the start:
## Pre-Patch Analysis Template
### Files to Modify:
- [ ] main.cpp - entry point changes
- [ ] mainapplication.h - class definitions, includes
- [ ] CMakeLists.txt - build system
- [ ] compatible_singleapp.h/cpp - new files
### Dependency Chain:
1. main.cpp includes → mainapplication.h
2. mainapplication.h includes → singleapplication.h (to replace)
3. CMakeLists.txt references → SingleApplication library
4. Qt MOC will process → Q_OBJECT classes (check conflicts!)
### Build Test Plan:
- [ ] Clean cmake build
- [ ] ldd dependency verification
- [ ] Runtime basic functionality
Time saved if I’d done this from the start: ~70% reduction in patch development time.
Key insight: Understand file dependencies and build system BEFORE making changes. Test patches against clean upstream at each step, not just at the end.
The Complete Patch Set
The final working solution consists of 11 patches:
- 0001-Fix-server.js-path-for-FHS-compliance.patch – Server location
- 0002-disable-server-download.patch – Use system Node.js
- 0004-minimal-qthelper-integration.patch – System Qt utilities
- 0005-cmake-system-libraries-v4.4.169.patch – MPV, OpenSSL integration
- 0007-add-qtwebengine-initialize-fix.patch – Critical: QtWebEngine initialization
- 0008-add-compatible-singleapp-implementation.patch – Critical: Custom single-instance
- 0009-remove-system-singleapplication-add-compatible.patch – Build integration
- 0010-fix-qmake-install-paths.patch – Install location fixes
- 0011-fix-qprocess-environment-for-server-launch.patch – Critical: Server environment
Validation Workflow
The final validation workflow ensures patches work on clean upstream, using the GBP (git-buildpackage) import workflow for proper Debian package building:
# Step 1: Create pristine test environment with GBP structure
git clone --branch v4.4.169 https://github.com/Stremio/stremio-shell.git /tmp/validation
cd /tmp/validation
cp -r /path/to/debian .
# Step 2: Apply all patches using quilt
export QUILT_PATCHES=debian/patches
quilt push -a
# Step 3: Test local build first (fastest iteration)
QT_DEFAULT_MAJOR_VERSION=5 dpkg-buildpackage -us -uc
# Step 4: Verify dependencies
ldd debian/stremio/usr/libexec/stremio/stremio | head -5
# Should show: libQt5WebEngine.so.5, libcrypto.so.3, libmpv.so.2
# Step 5: Test with pbuilder (clean chroot environment)
sudo pbuilder update
sudo pbuilder build ../*.dsc
# Step 6: Test with sbuild (production-grade build)
# WARNING: Qt5/WebEngine packages consume significant space
# Typical requirement: 4-6GB build space (overlayfs in tmpfs)
# Solution: Use machine with 16GB+ RAM or configure sbuild on disk
sbuild -d unstable ../*.dsc
# If sbuild fails with "No space left on device":
# - Switch to larger machine (16GB+ RAM recommended)
# - Or configure sbuild to use disk instead of tmpfs
Build Environment Considerations
Memory requirements for Qt5 applications:
dpkg-buildpackage
: ~2GB RAMpbuilder
: ~4GB RAMsbuild
with overlayfs in tmpfs: 6-8GB RAM (Qt5WebEngine is memory-intensive)
Our solution: After encountering space exhaustion on 8GB machines during sbuild, we migrated to a 32GB machine. This is typical for Qt5/WebEngine applications—always test sbuild capacity before committing to build infrastructure.
Result: 293KB optimized binary, 100% system libraries, full functionality including streaming.
Lessons for Other Packagers
Technical Takeaways
- Initialization order matters: System libraries may have different startup requirements than bundled ones. Always verify initialization sequences.
- Threading models vary: Even libraries with identical APIs may use different threading approaches. Watch for cross-thread object creation errors.
- Environment variables aren’t automatic: QProcess and similar mechanisms need explicit environment setup.
- QML modules are separate packages: Trace every QML import to its Debian package dependency.
- Custom implementations beat complex patches: Sometimes writing 100 lines of clean code is better than a 500-line patch to an incompatible library.
Process Takeaways
- Always test patches against clean upstream: Never generate patches from already-modified sources.
- Map dependencies before coding: Understand file relationships and build system before making changes.
- One fix, one patch, one test: Incremental development prevents cascading failures.
- Document assumptions: What works “by accident” with bundled libraries may fail with system ones.
- Validate completely: Test patches in isolated environments before declaring them “ready”.
Conclusion
Packaging Stremio for Debian taught me far more than Qt5 internals and build system integration. It revealed how easily we fall into inefficient workflows when we don’t step back to examine our process.
The technical achievement: A fully functional Debian package using 100% system libraries where the upstream used bundled dependencies—293KB binary, zero submodules, complete feature parity.
The real achievement: Learning that the how of problem-solving matters as much as the what. Efficient patch development isn’t just about technical skill—it’s about disciplined workflow, systematic thinking, and honest self-assessment.
Would I do anything differently? Absolutely. I’d use the validation workflow from day one, map dependencies before coding, and test each patch against clean upstream immediately.
But would I have learned these lessons without making the mistakes? Probably not.
Acknowledgments
Thanks to the Stremio team for creating great software, the Debian community for maintaining high standards, my friend Arturo (a Debian Developer) that knowing my passion for Debian encouraged me to start working as a Debian Maintainer, and to every packager who has documented their struggles—your war stories make ours easier.
Project Status (as of October 3, 2025)
- ITP Status: #943703 Submitted and under review by Debian Developer sponsor
- Repository: salsa.debian.org/mendezr/stremio
- Maintainer: Juan Manuel Méndez Rey vejeta@gmail.com
Note: This article documents the technical process and challenges. Package acceptance is pending Debian review. Status updates will be posted as the review process continues.
This article is part of my journey toward becoming a Debian Developer. If you’re interested in Debian packaging or have questions about the technical details, feel free to reach out.
Filed under: Uncategorized - @ October 3, 2025 10:20 pm