Skip to content

ci: Add GitHub Actions workflow for build and release #284

ci: Add GitHub Actions workflow for build and release

ci: Add GitHub Actions workflow for build and release #284

Workflow file for this run

name: Build and Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build-windows:
runs-on: windows-latest
env:
GSTREAMER_RELEASE_TAG: deps-gstreamer-1.22.8
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up MinGW
uses: egor-tensin/setup-mingw@v2
with:
version: 12.2.0
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: '6.4.2'
host: 'windows'
target: 'desktop'
arch: 'win64_mingw'
modules: 'qtmultimedia'
- name: Download GStreamer (MinGW) from GitHub Release
env:
GH_TOKEN: ${{ github.token }}
shell: pwsh
run: |
# Download MSI assets from your specific release "gstreamer-1.26.10"
# NOTE: We assume the files attached are named:
# gstreamer-1.0-mingw-x86_64-1.22.8.msi OR similar.
# Since you mentioned the TAG is "gstreamer-1.26.10" but the files inside often follow standard naming.
# I will attempt to download ALL msi files from that tag to be safe, then install them.
gh release download gstreamer-1.26.10 --pattern "*.msi" --repo OpenIPC/dashboard
# Identify filenames
$msiRuntime = Get-ChildItem -Filter "gstreamer-1.0-mingw-*.msi" | Select-Object -ExpandProperty Name -First 1
$msiDevel = Get-ChildItem -Filter "gstreamer-1.0-devel-mingw-*.msi" | Select-Object -ExpandProperty Name -First 1
if (-not $msiRuntime -or -not $msiDevel) {
Write-Error "Could not find both runtime and devel MSIs in the downloaded assets. Files found: $(Get-ChildItem *.msi | Out-String)"
exit 1
}
# Extract directly into the expected MinGW root to avoid nested paths
$gstRoot = "C:\gstreamer\1.0\mingw_x86_64"
New-Item -ItemType Directory -Force -Path $gstRoot | Out-Null
Write-Host "Extracting Runtime (administrative install): $msiRuntime"
Start-Process msiexec.exe -ArgumentList '/a', $msiRuntime, '/qn', "TARGETDIR=$gstRoot" -NoNewWindow -Wait
Write-Host "Extracting Devel (administrative install): $msiDevel"
Start-Process msiexec.exe -ArgumentList '/a', $msiDevel, '/qn', "TARGETDIR=$gstRoot" -NoNewWindow -Wait
# Validate expected header exists
$gstHeaderPath = "$gstRoot\include\gstreamer-1.0\gst\gst.h"
if (-not (Test-Path $gstHeaderPath)) {
Write-Host "WARNING: Expected gst.h not found at $gstHeaderPath. Searching C:\gstreamer..."
if (-not (Test-Path "C:\gstreamer")) {
Write-Error "CRITICAL: C:\gstreamer directory does not exist after extraction!"
exit 1
}
$gstHeader = Get-ChildItem -Recurse "C:\gstreamer" -Filter "gst.h" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "gstreamer-1.0\\gst\\gst\.h" } |
Select-Object -First 1
if ($gstHeader) {
# gst.h is in <root>\include\gstreamer-1.0\gst\gst.h
# We need <root>, so go up 4 levels from gst.h
$gstRoot = Split-Path (Split-Path (Split-Path (Split-Path $gstHeader.FullName -Parent) -Parent) -Parent) -Parent
} else {
Write-Error "Could not determine GStreamer installation root."
Get-ChildItem -Recurse "C:\gstreamer" | Select-Object FullName -First 120
exit 1
}
}
if ($gstRoot -match "\\include$") {
$gstRoot = Split-Path $gstRoot -Parent
}
Write-Host "GStreamer installed at: $gstRoot"
echo "GSTREAMER_1_0_ROOT_MINGW_X86_64=$gstRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "$gstRoot\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
echo "PKG_CONFIG_PATH=$gstRoot\lib\pkgconfig" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "Debug: Listing GStreamer Includes"
Get-ChildItem -Recurse "$gstRoot\include" | Select-Object FullName -First 20
- name: Configure CMake
run: |
$qmake = (Get-Command qmake.exe -ErrorAction SilentlyContinue).Source
if (-not $qmake) { $qmake = (Get-Command qmake -ErrorAction SilentlyContinue).Source }
if (-not $qmake) { Write-Error "qmake not found in PATH"; exit 1 }
$qtDir = Split-Path (Split-Path $qmake -Parent) -Parent
cmake -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_PREFIX_PATH=$qtDir -DGStreamer_ROOT="C:/gstreamer/1.0/mingw_x86_64"
- name: Build
run: cmake --build build --config Release
env:
PKG_CONFIG_PATH: C:\gstreamer\1.0\mingw_x86_64\lib\pkgconfig
- name: Upload CMake logs (Windows)
if: failure()
uses: actions/upload-artifact@v4
with:
name: windows-cmake-logs
path: |
build/CMakeFiles/CMakeOutput.log
build/CMakeFiles/CMakeError.log
build/CMakeCache.txt
build/compile_commands.json
- name: Prepare Distribution Folder
shell: pwsh
run: |
if (Test-Path dist) { Remove-Item -Recurse -Force dist }
New-Item -ItemType Directory -Force -Path dist
Copy-Item build/appOpenIPC-Dashboard.exe -Destination dist/
# Copy DLLs (Dahua, ONNX code in CMake might put them in build/)
if (Test-Path build/*.dll) {
Copy-Item build/*.dll -Destination dist/ -Force
}
# Resolve GStreamer root from env (set in install step), with fallback search
$gstRoot = $env:GSTREAMER_1_0_ROOT_MINGW_X86_64
if (-not $gstRoot -or -not (Test-Path $gstRoot)) {
$gstHeader = Get-ChildItem -Recurse "C:\gstreamer" -Filter "gst.h" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "gstreamer-1.0\\gst\\gst\.h" } |
Select-Object -First 1
if ($gstHeader) {
$gstRoot = Split-Path (Split-Path (Split-Path (Split-Path $gstHeader.FullName -Parent) -Parent) -Parent) -Parent
}
}
if (-not $gstRoot -or -not (Test-Path $gstRoot)) {
Write-Error "GStreamer root not found. GSTREAMER_1_0_ROOT_MINGW_X86_64=$($env:GSTREAMER_1_0_ROOT_MINGW_X86_64)"
exit 1
}
$gstBin = Join-Path $gstRoot "bin"
if (-not (Test-Path $gstBin)) {
Write-Error "GStreamer bin not found at: $gstBin"
exit 1
}
# Ensure critical GStreamer/GLib runtime DLLs are present
$requiredDlls = @(
"libgstreamer-1.0-0.dll",
"libgstbase-1.0-0.dll",
"libgstaudio-1.0-0.dll",
"libgstvideo-1.0-0.dll",
"libglib-2.0-0.dll",
"libgobject-2.0-0.dll",
"libgio-2.0-0.dll"
)
foreach ($dll in $requiredDlls) {
if (Test-Path "$gstBin\$dll") {
Copy-Item "$gstBin\$dll" -Destination dist/ -Force
}
}
# Copy additional core runtime libs (exclude d3d11/dxva/mf)
$extraDlls = @(
"libgstpbutils-1.0-0.dll",
"libgsttag-1.0-0.dll",
"libgstcontroller-1.0-0.dll",
"libgstnet-1.0-0.dll",
"libgstapp-1.0-0.dll",
"libgstrtp-1.0-0.dll",
"libgstrtsp-1.0-0.dll",
"libgstsdp-1.0-0.dll",
"libgstcodecparsers-1.0-0.dll",
"libgstcodecs-1.0-0.dll",
"libgstallocators-1.0-0.dll",
"libgstriff-1.0-0.dll",
"liborc-0.4-0.dll",
"libintl-8.dll",
"libiconv-2.dll",
"libffi-7.dll",
"libz-1.dll",
"libbz2.dll",
"libxml2-2.dll",
"libsqlite3-0.dll",
"libpcre2-8-0.dll",
"libssl-3-x64.dll",
"libcrypto-3-x64.dll",
"libavcodec-61.dll",
"libavformat-61.dll",
"libavutil-59.dll",
"libavfilter-10.dll",
"libswresample-5.dll",
"libswscale-8.dll",
"libjpeg-8.dll",
"libpng16.dll",
"libfreetype-6.dll",
"libfontconfig-1.dll",
"libharfbuzz-0.dll",
"libgraphene-1.0-0.dll",
"libpango-1.0-0.dll",
"libpangocairo-1.0-0.dll",
"libpangowin32-1.0-0.dll",
"libcairo-2.dll",
"libcairo-gobject-2.dll"
)
foreach ($dll in $extraDlls) {
if (Test-Path "$gstBin\$dll") {
Copy-Item "$gstBin\$dll" -Destination dist/ -Force
}
}
$missing = @()
foreach ($dll in $requiredDlls) {
if (-not (Test-Path "dist\$dll")) { $missing += $dll }
}
if ($missing.Count -gt 0) {
Write-Error "Missing required GStreamer DLLs in dist: $($missing -join ', ')"
Get-ChildItem dist | Select-Object Name | Out-String | Write-Host
exit 1
}
# Copy Plugins (minimal set)
$gstPlugins = Join-Path $gstRoot "lib\gstreamer-1.0"
New-Item -ItemType Directory -Path dist/lib/gstreamer-1.0 -Force
if (Test-Path $gstPlugins) {
$pluginList = @(
"libgstcoreelements.dll",
"libgstplayback.dll",
"libgsttypefindfunctions.dll",
"libgstapp.dll",
"libgstaudioconvert.dll",
"libgstaudioresample.dll",
"libgstaudioparsers.dll",
"libgstaacparse.dll",
"libgstfaad.dll",
"libgstvolume.dll",
"libgstautodetect.dll",
"libgstvideoconvert.dll",
"libgstvideoconvertscale.dll",
"libgstvideoscale.dll",
"libgstvideofilters.dll",
"libgstvideofilter.dll",
"libgstvideoparsersbad.dll",
"libgstdeinterlace.dll",
"libgstrtp.dll",
"libgstrtpmanager.dll",
"libgstrtsp.dll",
"libgstudp.dll",
"libgsttcp.dll",
"libgstisomp4.dll",
"libgstmatroska.dll",
"libgstmpegtsdemux.dll",
"libgstde265.dll",
"libgstopenh264.dll",
"libgstlibav.dll",
"libgstwasapi.dll",
"libgstdirectsound.dll"
)
foreach ($p in $pluginList) {
if (Test-Path "$gstPlugins\$p") {
Copy-Item "$gstPlugins\$p" -Destination dist/lib/gstreamer-1.0 -Force
}
}
}
# Copy Scanner (Check libexec)
$gstLibexec = Join-Path $gstRoot "libexec\gstreamer-1.0"
$scannerCandidates = @(
"$gstLibexec\gst-plugin-scanner.exe",
"$gstRoot\bin\gst-plugin-scanner.exe",
"$gstRoot\lib\gstreamer-1.0\gst-plugin-scanner.exe"
)
$scannerCopied = $false
foreach ($scanner in $scannerCandidates) {
if (Test-Path $scanner) {
Copy-Item $scanner -Destination dist/lib/gstreamer-1.0 -Force
$scannerCopied = $true
break
}
}
if (-not $scannerCopied) {
Write-Warning "gst-plugin-scanner.exe not found in GStreamer root"
}
# Copy OpenIPC QML module (Custom module)
if (Test-Path build/OpenIPC) {
Copy-Item build/OpenIPC -Destination dist/ -Recurse -Force
}
# Run windeployqt to deploy standard Qt dependencies (including Qt.labs.folderlistmodel)
# We run this on the final dist folder to ensure everything is self-contained
$windeployqt = (Get-Command windeployqt.exe -ErrorAction SilentlyContinue).Source
if (-not $windeployqt) { $windeployqt = (Get-Command windeployqt -ErrorAction SilentlyContinue).Source }
if (-not $windeployqt) { Write-Error "windeployqt not found in PATH"; exit 1 }
& $windeployqt --qmldir src/ui --dir dist dist/appOpenIPC-Dashboard.exe
# Ensure Qt platform plugins are bundled (qwindows.dll)
$qtBin = Split-Path $windeployqt -Parent
$qtRoot = Split-Path $qtBin -Parent
$qtPlatforms = Join-Path $qtRoot "plugins\platforms"
if (Test-Path $qtPlatforms) {
New-Item -ItemType Directory -Force -Path dist/qt_plugins/platforms | Out-Null
Copy-Item "$qtPlatforms\*" -Destination dist/qt_plugins/platforms -Force
} else {
Write-Warning "Qt platforms path not found: $qtPlatforms"
}
# Move any Qt plugins generated by windeployqt to qt_plugins to avoid VLC conflict
if (Test-Path dist/plugins/platforms) {
if (Test-Path dist/qt_plugins) { Remove-Item -Recurse -Force dist/qt_plugins }
Move-Item dist/plugins dist/qt_plugins
}
# Ensure qt.conf points to correct plugin/qml folders
@"
[Paths]
Prefix=.
Plugins=qt_plugins
Imports=qml
Qml2Imports=qml
"@ | Set-Content dist/qt.conf
# Remove debug symbols to reduce size
Get-ChildItem dist -Recurse -Filter "*.pdb" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue
- name: Install UPX
run: choco install upx
- name: Compress binaries with UPX
shell: pwsh
run: |
$upx = (Get-Command upx.exe -ErrorAction SilentlyContinue).Source
if (-not $upx) { Write-Error "upx not found"; exit 1 }
# Compress EXE and DLLs in dist
# Avoid compressing Qt plugins/modules and QML imports, which can break Qt's plugin metadata loader.
Get-ChildItem dist -Recurse -Include *.exe,*.dll |
Where-Object {
$_.FullName -notmatch '\\qt_plugins\\' -and
$_.FullName -notmatch '\\platforms\\' -and
$_.FullName -notmatch '\\plugins\\' -and
$_.FullName -notmatch '\\Qt(Qml|Quick|Multimedia|Network|OpenGL|Sql|Svg|Core|Gui|Labs|QmlModels|QmlWorkerScript|QmlXmlListModel)?\\' -and
$_.FullName -notmatch '\\qml\\'
} |
ForEach-Object {
& $upx -9 --lzma --force $_.FullName
}
- name: Install Inno Setup
run: choco install innosetup
- name: Update Installer Version
shell: pwsh
run: |
$version = "${{ github.ref_name }}".TrimStart('v')
(Get-Content setup.iss) -replace '#define MyAppVersion ".*"', "#define MyAppVersion ""$version""" | Set-Content setup.iss
- name: Build Installer
run: iscc setup.iss
- name: Upload Installer
uses: actions/upload-artifact@v4
with:
name: windows-installer
path: OpenIPC-Dashboard-Installer.exe
build-linux:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential libgl1-mesa-dev pkg-config libfuse2 imagemagick patchelf libwayland-dev libunwind-dev squashfs-tools desktop-file-utils \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: '6.4.2'
host: 'linux'
target: 'desktop'
modules: 'qtmultimedia'
- name: Install libxcb dependencies
run: sudo apt-get install -y libxcb-cursor0
- name: Configure CMake
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build --config Release
- name: Upload CMake logs (Linux)
if: failure()
uses: actions/upload-artifact@v4
with:
name: linux-cmake-logs
path: |
build/CMakeFiles/CMakeOutput.log
build/CMakeFiles/CMakeError.log
build/CMakeCache.txt
build/compile_commands.json
- name: Helper - Create Desktop File
run: |
echo "[Desktop Entry]" > appOpenIPC-Dashboard.desktop
echo "Type=Application" >> appOpenIPC-Dashboard.desktop
echo "Name=OpenIPC Dashboard" >> appOpenIPC-Dashboard.desktop
echo "Exec=appOpenIPC-Dashboard" >> appOpenIPC-Dashboard.desktop
echo "Icon=openipc-dashboard" >> appOpenIPC-Dashboard.desktop
echo "Categories=Utility;" >> appOpenIPC-Dashboard.desktop
- name: Create AppImage
env:
VERSION: ${{ github.ref_name }}
run: |
set -euo pipefail
exec > >(tee -a create_appimage.log) 2>&1
set -x
: > linuxdeployqt.log
: > appimagetool.log
# Download linuxdeployqt
wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage
chmod +x linuxdeployqt-continuous-x86_64.AppImage
./linuxdeployqt-continuous-x86_64.AppImage --appimage-extract
rm -rf linuxdeployqt-root
mv squashfs-root linuxdeployqt-root
# Prepare AppDir
mkdir -p AppDir/usr/bin
cp build/appOpenIPC-Dashboard AppDir/usr/bin/
chmod +x AppDir/usr/bin/appOpenIPC-Dashboard
# Prepare Icon (Use existing PNG and resize to ensure 256x256)
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps/
convert resources/appicon.png -resize 256x256 AppDir/usr/share/icons/hicolor/256x256/apps/openipc-dashboard.png
cp AppDir/usr/share/icons/hicolor/256x256/apps/openipc-dashboard.png AppDir/openipc-dashboard.png
# Copy desktop file
mkdir -p AppDir/usr/share/applications/
cp appOpenIPC-Dashboard.desktop AppDir/usr/share/applications/
cp appOpenIPC-Dashboard.desktop AppDir/appOpenIPC-Dashboard.desktop
# Copy custom libs to standard location expected by AppImage (usr/lib)
mkdir -p AppDir/usr/lib
cp libs/dahua/lib/Linux64/*.so AppDir/usr/lib/
cp libs/onnxruntime/lib/Linux64/*.so* AppDir/usr/lib/
# Copy OpenIPC assets (QML modules, models) - mirror Windows behavior
if [ -d "build/OpenIPC" ]; then
cp -r build/OpenIPC AppDir/usr/bin/
fi
# Copy GStreamer plugins (minimal set to avoid missing dependency warnings)
mkdir -p AppDir/usr/lib/gstreamer-1.0
GST_PLUGIN_DIR="/usr/lib/x86_64-linux-gnu/gstreamer-1.0"
GST_PLUGINS=(
libgstcoreelements.so
libgstplayback.so
libgsttypefindfunctions.so
libgstapp.so
libgstaudioconvert.so
libgstaudioresample.so
libgstaudioparsers.so
libgstaacparse.so
libgstfaad.so
libgstvolume.so
libgstautodetect.so
libgstvideoconvert.so
libgstvideoscale.so
libgstvideorate.so
libgstvideoparsers.so
libgstvideoparsersbad.so
libgstvideofilters.so
libgstvideofilter.so
libgstvideofiltersbad.so
libgstdeinterlace.so
libgstrtp.so
libgstrtpmanager.so
libgstrtsp.so
libgstudp.so
libgsttcp.so
libgstisomp4.so
libgstmatroska.so
libgstmpegtsdemux.so
libgstde265.so
libgstlibav.so
libgstopenh264.so
libgstalsa.so
libgstpulseaudio.so
)
for plugin in "${GST_PLUGINS[@]}"; do
if [ -f "$GST_PLUGIN_DIR/$plugin" ]; then
cp "$GST_PLUGIN_DIR/$plugin" AppDir/usr/lib/gstreamer-1.0/
fi
done
# Copy plugin scanner
if [ -f "/usr/lib/x86_64-linux-gnu/gstreamer-1.0/gst-plugin-scanner" ]; then
cp /usr/lib/x86_64-linux-gnu/gstreamer-1.0/gst-plugin-scanner AppDir/usr/lib/gstreamer-1.0/
elif [ -f "/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner" ]; then
cp /usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner AppDir/usr/lib/gstreamer-1.0/
fi
# Copy GStreamer core libraries to avoid host/AppImage version mismatch
cp /usr/lib/x86_64-linux-gnu/libgstreamer-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgstbase-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgstaudio-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgstvideo-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgstapp-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgstpbutils-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgstrtp-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgstrtsp-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgsttag-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libgstnet-1.0.so.* AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/liborc-0.4.so.* AppDir/usr/lib/
# Copy GStreamer bad/libav runtime libs needed for AAC/H265 and MP4 playback
for lib in \
/usr/lib/x86_64-linux-gnu/libgstcodecparsers-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgstcodecs-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgstbadaudio-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgstadaptivedemux-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgstwebrtc-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgstsctp-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgstwayland-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgstisoff-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgstmpegts-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libgsturidownloader-1.0.so.* \
/usr/lib/x86_64-linux-gnu/libpocketsphinx.so.* \
/usr/lib/x86_64-linux-gnu/libopenh264.so.* \
/usr/lib/x86_64-linux-gnu/libavcodec.so.* \
/usr/lib/x86_64-linux-gnu/libavformat.so.* \
/usr/lib/x86_64-linux-gnu/libavutil.so.* \
/usr/lib/x86_64-linux-gnu/libswresample.so.* \
/usr/lib/x86_64-linux-gnu/libswscale.so.* \
/usr/lib/x86_64-linux-gnu/libavfilter.so.* \
/usr/lib/x86_64-linux-gnu/libpostproc.so.* \
/usr/lib/x86_64-linux-gnu/libfaad.so.* \
/usr/lib/x86_64-linux-gnu/libde265.so.* \
/usr/lib/x86_64-linux-gnu/libx265.so.* \
/usr/lib/x86_64-linux-gnu/libx264.so.*; do
if [ -e "$lib" ]; then
cp $lib AppDir/usr/lib/
fi
done
# Create AppRun wrapper to force bundled GStreamer
printf '%s\n' \
'#!/bin/sh' \
'HERE="$(dirname "$(readlink -f "$0")")"' \
'export LD_LIBRARY_PATH="$HERE/usr/lib:$LD_LIBRARY_PATH"' \
'export GST_PLUGIN_PATH="$HERE/usr/lib/gstreamer-1.0"' \
'export GST_PLUGIN_SCANNER="$HERE/usr/lib/gstreamer-1.0/gst-plugin-scanner"' \
'export GST_PLUGIN_SYSTEM_PATH_1_0=""' \
'exec "$HERE/usr/bin/appOpenIPC-Dashboard" "$@"' \
> AppDir/AppRun
chmod +x AppDir/AppRun
# Fix OpenSSL: Copy system OpenSSL 1.1 libs (standard on Ubuntu 20.04)
# We need to provide libssl1.1 because Qt 6.4 binaries are linked against it.
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 AppDir/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 AppDir/usr/lib/
# Patch RPATH of the main binary to find libs in ../lib relative to itself
# We DO NOT set LD_LIBRARY_PATH so we can verify if RPATH is working correctly
patchelf --set-rpath '$ORIGIN/../lib' AppDir/usr/bin/appOpenIPC-Dashboard
# Debug: check ldd after patching
echo "LDD after patching (Expect libs in .../AppDir/usr/lib):"
ldd AppDir/usr/bin/appOpenIPC-Dashboard
# HACK: Fix silent exit 1 loop in linuxdeployqt when using -unsupported-allow-new-glibc
# The tool checks for this specific file if the flag is present, otherwise it exits 1 silently
mkdir -p AppDir/usr/share/doc/libc6
touch AppDir/usr/share/doc/libc6/copyright
# Run linuxdeployqt (deploy + appimage)
# We use -no-strip to avoid potential issues with stripping failing
# We use -bundle-non-qt-libs so it attempts to include FUSE or other sys libs if needed
# UNSET QT_PLUGIN_PATH
unset QT_PLUGIN_PATH
# Explicitly set QMAKE path (linuxdeployqt uses it to find plugins)
export QMAKE="$(command -v qmake)"
# Use -verbose=0 (less output) to see if we can spot the actual error message at the end
# Sometimes verbose output hides the real error or crashes the logger
set +e
./linuxdeployqt-root/AppRun AppDir/usr/share/applications/appOpenIPC-Dashboard.desktop \
-qmldir=src \
-qmldir=build/OpenIPC \
-exclude-libs=libglib-2.0.so.0,libgthread-2.0.so.0,libgobject-2.0.so.0,libgmodule-2.0.so.0,libgio-2.0.so.0 \
-extra-plugins=iconengines,imageformats,platforms,platformthemes,wayland-decoration-client,wayland-graphics-integration-client,wayland-shell-integration \
-verbose=2 \
-no-strip \
-no-translations \
-qmake=$QMAKE 2>&1 | tee linuxdeployqt.log
LINUXDEPLOYQT_RC=${PIPESTATUS[0]}
set -e
if [ $LINUXDEPLOYQT_RC -ne 0 ]; then
echo "linuxdeployqt failed with exit code $LINUXDEPLOYQT_RC"
exit $LINUXDEPLOYQT_RC
fi
# Pack AppImage explicitly (use appimagetool bundled with linuxdeployqt)
if [ ! -f ./linuxdeployqt-root/usr/bin/appimagetool ]; then
echo "appimagetool not found in linuxdeployqt-root"
ls -la linuxdeployqt-root/usr/bin || true
exit 1
fi
chmod +x ./linuxdeployqt-root/usr/bin/appimagetool
echo "Using appimagetool: ./linuxdeployqt-root/usr/bin/appimagetool"
set +e
ARCH=x86_64 VERSION=${VERSION} ./linuxdeployqt-root/usr/bin/appimagetool -v AppDir OpenIPC-Dashboard-Linux.AppImage 2>&1 | tee appimagetool.log
APPIMAGETOOL_RC=${PIPESTATUS[0]}
set -e
if [ $APPIMAGETOOL_RC -ne 0 ]; then
echo "appimagetool failed with exit code $APPIMAGETOOL_RC"
exit $APPIMAGETOOL_RC
fi
if [ ! -f OpenIPC-Dashboard-Linux.AppImage ]; then
echo "AppImage not created"
find . -maxdepth 2 -name "*.AppImage" -type f -ls || true
ls -la AppDir || true
exit 1
fi
ls -la *.AppImage
mkdir -p tools
mv linuxdeployqt-continuous-x86_64.AppImage tools/
- name: Upload AppImage logs (Linux)
if: always()
uses: actions/upload-artifact@v4
with:
name: linux-appimage-logs
path: |
appimagetool.log
linuxdeployqt.log
create_appimage.log
- name: Rename AppImage
run: |
if [ -f OpenIPC-Dashboard-Linux.AppImage ]; then
echo "AppImage already built"
exit 0
fi
FOUND=$(find . -maxdepth 3 -name "*.AppImage" -type f ! -name "linuxdeployqt-continuous-x86_64.AppImage" | head -n 1)
if [ -n "$FOUND" ]; then
mv "$FOUND" OpenIPC-Dashboard-Linux.AppImage
echo "Moved AppImage: $FOUND"
else
echo "OpenIPC-Dashboard-Linux.AppImage not found"
ls -la
find . -maxdepth 3 -name "*.AppImage" -type f -ls || true
exit 1
fi
- name: Upload AppImage
uses: actions/upload-artifact@v4
with:
name: linux-appimage
path: OpenIPC-Dashboard-Linux.AppImage
release:
needs: [build-windows, build-linux]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download Windows Installer
uses: actions/download-artifact@v4
with:
name: windows-installer
- name: Download Linux AppImage
uses: actions/download-artifact@v4
with:
name: linux-appimage
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
OpenIPC-Dashboard-Installer.exe
OpenIPC-Dashboard-Linux.AppImage