Emil Erofeevskiy 1998-As long as possible | All Rights Reserved
High-reliability C++/Qt client for deep space telemetry and imagery.
Writing a C++ application that works flawlessly on your development machine is only half the battle. The real engineering test begins when you hand that .exe to someone else. Suddenly, TLS handshakes fail, images refuse to render, and your "cross-platform" framework leaves you guessing.
Recently, I built CosmoWatcher, a high-reliability C++/Qt client for deep space telemetry and imagery (parsing data from NASA/MAST APIs). The development phase in MSYS2 (UCRT64) was smooth. But deploying it to a clean Windows machine revealed a harsh truth: relying on automated deployment tools like windeployqt in a MinGW/MSYS2 environment is a trap.
Here is how I diagnosed the silent failures, escaped the MSYS2 dependency illusion, and built a zero-trust deployment pipeline.
When you run windeployqt, it gathers the necessary Qt libraries (Qt6Core, Qt6Network, Qt6Qml, etc.) and dumps them into your release folder. You zip it up, send it to a user, and expect it to work.
Instead, users reported two critical failures:
Network Requests Failed: The application could not establish secure connections to the JWST APIs.
Black Squares: Even when mocked data was used, JPEG images refused to render in QML.
No crashes. No loud error dialogues from Windows. Just silent, cascading failures. Why? Because the executable was secretly parasitizing my local MSYS2 environment variables and system paths.
To diagnose this, you must simulate a hostile, empty environment. I temporarily renamed my C:\msys64 folder to C:\msys64_hidden and ran the executable from the release folder with plugin debugging enabled:
PowerShell
PLAINTEXT
$env:QT_DEBUG_PLUGINS=1
./CosmoWatcher.exe
Instantly, the terminal vomited the truth.
When compiling Qt with OpenSSL support under MSYS2, the OpenSSL libraries (libssl-3-x64.dll, libcrypto-3-x64.dll) are hardcoded to look for Root CA certificates in Unix-style paths, typically /etc/ssl/certs.
On the developer machine, MSYS2 silently translates this path, finds the system certificates, and the TLS handshake succeeds. On a user’s machine, that path doesn’t exist. OpenSSL fails to verify the certificate chain, and Qt’s QSslSocket drops the connection.
You cannot trust the target operating system to provide a predictable OpenSSL environment. You must bring your own infrastructure.
I downloaded the latest Root CA bundle (cacert.pem) from curl.se and explicitly forced Qt to use it before any network requests were initialized:
CPP
void initiate_ssl_setup()
{
const QString appDir = QCoreApplication::applicationDirPath();
const QString certPath = appDir + "certs/cacert.pem";
qputenv("SSL_CERT_FILE", QFile::encodeName(certPath));
if (QFile certFile(certPath); certFile.open(QIODevice::ReadOnly))
{
const QList<QSslCertificate> certs = QSslCertificate::fromData(certFile.readAll());
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setCaCertificates(certs);
QSslConfiguration::setDefaultConfiguration(config);
qDebug() << "SYS_STATUS :: SSL CA Bundle loaded from resources. Certs:" << certs.size();
} else {
qCritical() << "SYS_CRITICAL :: Failed to load embedded SSL certificates!";
}
}
By placing cacert.pem next to the executable, we completely decouple the application from the host's certificate storage.
The second issue was the “black squares.” Qt’s image format plugins (like imageformats/qjpeg.dll) were present in the release folder, but they weren't loading.
windeployqt is excellent at finding Qt dependencies, but it is entirely blind to the underlying C-libraries that those Qt plugins wrap. qjpeg.dll does not decode JPEGs; it acts as a bridge to libjpeg-8.dll.
Running ldd on the Qt plugin exposed the missing link:
Bash
PLAINTEXT
$ ldd imageformats/qjpeg.dll
...
libjpeg-8.dll => /c/msys64/ucrt64/bin/libjpeg-8.dll
...
Because windeployqt missed it, Windows silently fell back to the system PATH on my machine (finding it in the MSYS2 bin folder). On the user's machine, the load failed, Qt rejected the plugin, and QML couldn't decode the image bytes.
The solution is manual extraction. You must trace the dependency graph of your plugins using ldd and manually copy the C-backend libraries (libjpeg-8.dll, libwebp-7.dll, etc.) into your release folder next to your executable.
Providing a zip file full of DLLs is amateurish. A true builder-engineer controls the state machine of the deployment. I packaged the application using Inno Setup with a strict, zero-trust philosophy:
State Enforcement: The installer halts and demands the user’s JWST API Key upfront, injecting it directly into the conf.ini file. The application is never allowed to launch in an unconfigured state.
Infrastructure Guarantee: The installer carries and silently deploys the Microsoft Visual C++ Redistributable (x64) based on registry checks.
Security Injection: The cacert.pem file is bundled natively into the installer, guaranteeing that the TLS layer is fully operational the second the executable launches, completely offline.
Frameworks like Qt are powerful, but they abstract away the underlying system behavior. If you want to build resilient software, you cannot treat the runtime environment as a black box. You have to assume the target machine is hostile, empty, and unpredictable.
Understand your dependencies, trace your dynamic libraries, and ship your infrastructure with your code. That is the difference between writing a feature and owning a system.
Until then.
Pages — https://emilianissimo.github.io/cosmo-watcher/
Github — https://github.com/Emilianissimo/cosmo-watcher/
Emil Erofeevskiy 1998-As long as possible | All Rights Reserved