Skip to content

Native Release

Native Release #22

Workflow file for this run

name: Native Release
on:
workflow_dispatch:
inputs:
release_tag:
description: Existing release tag to receive rebuilt packages (leave blank for a rehearsal)
required: false
type: string
release:
types: [published]
permissions:
contents: read
jobs:
browser-matrix:
name: Browser matrix (Linux)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
- name: Install Linux build tools
run: |
sudo apt-get update
sudo apt-get install -y ninja-build libgl1-mesa-dev libxkbcommon-x11-0 libxcb-cursor0
- name: Worker checks with all engines
shell: bash
run: |
npm ci
npx playwright install chromium firefox webkit
npm run typecheck:worker
npm run build:worker
npm run test:worker
linux:
name: Linux ${{ matrix.arch }}
needs: [browser-matrix]
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
linuxdeploy_asset: 497463883
linuxdeploy_sha256: 421ca71d5c69ea97c6309276232990d43df1dcece0edfaa26bbf926ff96ed12e
qt_plugin_asset: 421996681
qt_plugin_sha256: be1b7e166bf9975cfb694ebe6759ba40502ffc6196440d3e64aa90c4dbd67e9f
appimagetool_asset: 324406736
appimagetool_sha256: ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0
runtime_asset: 456065460
runtime_sha256: 1cc49bcf1e2ccd593c379adb17c9f85a36d619088296504de95b1d06215aebbf
- runner: ubuntu-24.04-arm
arch: arm64
linuxdeploy_asset: 497463664
linuxdeploy_sha256: 9f04c4c2a8b69c392c4bbcc1a88bdd4d0a8ac03f587cf5242814cb7ae47b78e5
qt_plugin_asset: 421996594
qt_plugin_sha256: 5525e6c49c3c774c02b8864d2acc2ae4c5c0ccc3327f7dce626deaa36348e5c6
appimagetool_asset: 324406707
appimagetool_sha256: f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158
runtime_asset: 456064894
runtime_sha256: 7d5d772b7c32f0c84caf0a452a3072a5709027d7eac5856feb89a7a7a8881372
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag || github.ref }}
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
- name: Verify release version
shell: bash
env:
CYBERSNAPPER_RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}
run: node scripts/check-release-version.mjs "$CYBERSNAPPER_RELEASE_TAG"
- uses: jurplel/install-qt-action@v4
with:
version: '6.8.3'
cache: true
- name: Install Linux build tools
run: |
sudo apt-get update
sudo apt-get install -y ninja-build libgl1-mesa-dev libxkbcommon-x11-0 libxcb-cursor0 libfuse2 file desktop-file-utils
- name: Worker checks
shell: bash
run: |
npm ci
npx playwright install chromium
if [ "${{ runner.os }}" = "Linux" ]; then
npx playwright install firefox webkit
fi
npm run typecheck:worker
npm run build:worker
npm run test:worker
- name: Bundle Chromium
shell: bash
env:
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
run: npx playwright install chromium
- name: Install Chromium host dependencies
env:
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
run: npx playwright install-deps chromium
- name: Stage Node runtime
shell: bash
run: mkdir -p .runtime && cp "$(command -v node)" .runtime/node && chmod +x .runtime/node
- name: Configure, build, and test
shell: bash
run: |
cmake -S . -B build/native -G Ninja -DCMAKE_BUILD_TYPE=Release -DCYBERSNAPPER_BUILD_TESTS=ON
cmake --build build/native --parallel
ctest --test-dir build/native --output-on-failure
- name: Keep production worker dependencies
run: npm prune --omit=dev
# Each tool is pinned by immutable release-asset id plus its SHA-256 so a
# replaced "continuous" asset cannot silently change what we package.
- name: Install pinned packaging tools
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir -p tools
gh api -H 'Accept: application/octet-stream' \
"repos/linuxdeploy/linuxdeploy/releases/assets/${{ matrix.linuxdeploy_asset }}" > tools/linuxdeploy
gh api -H 'Accept: application/octet-stream' \
"repos/linuxdeploy/linuxdeploy-plugin-qt/releases/assets/${{ matrix.qt_plugin_asset }}" > tools/linuxdeploy-plugin-qt
gh api -H 'Accept: application/octet-stream' \
"repos/AppImage/appimagetool/releases/assets/${{ matrix.appimagetool_asset }}" > tools/appimagetool
gh api -H 'Accept: application/octet-stream' \
"repos/AppImage/type2-runtime/releases/assets/${{ matrix.runtime_asset }}" > tools/AppImage-runtime
printf '%s %s\n' "${{ matrix.linuxdeploy_sha256 }}" tools/linuxdeploy | sha256sum --check --strict
printf '%s %s\n' "${{ matrix.qt_plugin_sha256 }}" tools/linuxdeploy-plugin-qt | sha256sum --check --strict
printf '%s %s\n' "${{ matrix.appimagetool_sha256 }}" tools/appimagetool | sha256sum --check --strict
printf '%s %s\n' "${{ matrix.runtime_sha256 }}" tools/AppImage-runtime | sha256sum --check --strict
chmod +x tools/linuxdeploy tools/linuxdeploy-plugin-qt tools/appimagetool
- name: Package AppImage and portable archive
shell: bash
env:
LINUXDEPLOY: ${{ github.workspace }}/tools/linuxdeploy
APPIMAGETOOL: ${{ github.workspace }}/tools/appimagetool
APPIMAGE_RUNTIME: ${{ github.workspace }}/tools/AppImage-runtime
run: |
export PATH="${{ github.workspace }}/tools:$PATH"
export QMAKE="${QT_ROOT_DIR}/bin/qmake"
version=$(node -p "require('./package.json').version")
scripts/package-linux.sh build/native release-assets "${{ matrix.arch }}" "$version"
- name: Smoke-test packaged capture
shell: bash
timeout-minutes: 15
run: |
env -u QT_PLUGIN_PATH -u QML2_IMPORT_PATH node scripts/smoke-packaged-capture.mjs \
build/native/AppDir/usr/bin/cybersnapper-cli \
build/native/AppDir/usr/bin/cybersnapper-agent \
build/native/AppDir/usr/share/cybersnapper/worker/main.cjs \
build/native/AppDir/usr/lib/cybersnapper/runtime/node \
build/native/AppDir/usr/share/cybersnapper/browsers \
build/native/smoke-state
- uses: actions/upload-artifact@v4
with:
name: CyberSnapper-linux-${{ matrix.arch }}
path: release-assets/*
if-no-files-found: error
windows:
name: Windows ${{ matrix.arch }}
strategy:
fail-fast: false
matrix:
include:
- runner: windows-2022
arch: x64
qt_arch: win64_msvc2022_64
msvc_arch: x64
- runner: windows-11-arm
arch: arm64
qt_arch: win64_msvc2022_arm64
msvc_arch: amd64_arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag || github.ref }}
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
- name: Verify release version
shell: bash
env:
CYBERSNAPPER_RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}
run: node scripts/check-release-version.mjs "$CYBERSNAPPER_RELEASE_TAG"
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc_arch }}
- uses: jurplel/install-qt-action@v4
with:
version: '6.8.3'
arch: ${{ matrix.qt_arch }}
cache: true
- name: Worker checks
shell: bash
run: |
npm ci
npx playwright install chromium
if [ "${{ runner.os }}" = "Linux" ]; then
npx playwright install firefox webfox webkit
fi
npm run typecheck:worker
npm run build:worker
npm run test:worker
- name: Bundle Chromium
shell: bash
env:
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
run: npx playwright install chromium
- name: Stage Node runtime
shell: pwsh
run: |
New-Item -ItemType Directory -Force .runtime
Copy-Item (Get-Command node).Source .runtime/node.exe
- name: Configure, build, and test
shell: pwsh
run: |
cmake -S . -B build/native -G Ninja -DCMAKE_BUILD_TYPE=Release -DCYBERSNAPPER_BUILD_TESTS=ON
cmake --build build/native --parallel
ctest --test-dir build/native --output-on-failure
- name: Keep production worker dependencies
run: npm prune --omit=dev
- name: Install application
shell: pwsh
run: cmake --install build/native --prefix stage --config Release
- name: Deploy Qt
shell: pwsh
run: |
windeployqt --release --no-translations stage/bin/CyberSnapper.exe
windeployqt --release --no-translations stage/bin/cybersnapper-agent.exe
Remove-Item stage/bin/sqldrivers/qsqlmimer.dll, stage/bin/sqldrivers/qsqlodbc.dll, stage/bin/sqldrivers/qsqlpsql.dll -ErrorAction SilentlyContinue
- name: Create portable ZIP
shell: pwsh
run: |
New-Item -ItemType Directory -Force release-assets | Out-Null
Compress-Archive -Path stage/* -DestinationPath release-assets/CyberSnapper-windows-${{ matrix.arch }}-portable.zip
- name: Build setup executable
shell: pwsh
run: |
$version = (node -p "require('./package.json').version")
$makensis = & "$PWD/scripts/install-nsis.ps1"
& $makensis `
"/DAPP_SOURCE=$PWD\stage" `
"/DAPP_ICON=$PWD\native\resources\cybersnapper.ico" `
"/DVERSION=$version" `
"/DOUTPUT_FILE=$PWD\release-assets\CyberSnapper-windows-${{ matrix.arch }}-setup.exe" `
native/packaging/windows/installer.nsi
- name: Smoke-test packaged capture
shell: pwsh
timeout-minutes: 15
run: |
Remove-Item Env:QT_PLUGIN_PATH -ErrorAction SilentlyContinue
Remove-Item Env:QML2_IMPORT_PATH -ErrorAction SilentlyContinue
node scripts/smoke-packaged-capture.mjs `
stage\bin\cybersnapper-cli.exe `
stage\bin\cybersnapper-agent.exe `
stage\bin\worker\main.cjs `
stage\bin\runtime\node.exe `
stage\bin\browsers `
build\native\smoke-state
- uses: actions/upload-artifact@v4
with:
name: CyberSnapper-windows-${{ matrix.arch }}
path: release-assets/*
if-no-files-found: error
macos:
name: macOS ${{ matrix.arch }}
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-intel
arch: x64
- runner: macos-15
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag || github.ref }}
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
- name: Verify release version
shell: bash
env:
CYBERSNAPPER_RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}
run: node scripts/check-release-version.mjs "$CYBERSNAPPER_RELEASE_TAG"
- uses: jurplel/install-qt-action@v4
with:
version: '6.8.3'
cache: true
- name: Worker checks
shell: bash
run: |
npm ci
npx playwright install chromium
if [ "${{ runner.os }}" = "Linux" ]; then
npx playwright install firefox webkit
fi
npm run typecheck:worker
npm run build:worker
npm run test:worker
- name: Bundle Chromium
shell: bash
env:
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
run: npx playwright install chromium
- name: Stage Node runtime
shell: bash
run: mkdir -p .runtime && cp "$(command -v node)" .runtime/node && chmod +x .runtime/node
- name: Generate macOS icon
shell: bash
run: |
set -euo pipefail
iconset="$RUNNER_TEMP/CyberSnapper.iconset"
mkdir -p "$iconset"
sips -z 16 16 assets/logo.png --out "$iconset/icon_16x16.png" >/dev/null
sips -z 32 32 assets/logo.png --out "$iconset/icon_16x16@2x.png" >/dev/null
sips -z 32 32 assets/logo.png --out "$iconset/icon_32x32.png" >/dev/null
sips -z 64 64 assets/logo.png --out "$iconset/icon_32x32@2x.png" >/dev/null
sips -z 128 128 assets/logo.png --out "$iconset/icon_128x128.png" >/dev/null
sips -z 256 256 assets/logo.png --out "$iconset/icon_128x128@2x.png" >/dev/null
sips -z 256 256 assets/logo.png --out "$iconset/icon_256x256.png" >/dev/null
sips -z 512 512 assets/logo.png --out "$iconset/icon_256x256@2x.png" >/dev/null
sips -z 512 512 assets/logo.png --out "$iconset/icon_512x512.png" >/dev/null
sips -z 1024 1024 assets/logo.png --out "$iconset/icon_512x512@2x.png" >/dev/null
iconutil -c icns "$iconset" -o "$RUNNER_TEMP/CyberSnapper.icns"
- name: Configure, build, and test
shell: bash
run: |
cmake -S . -B build/native -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCYBERSNAPPER_BUILD_TESTS=ON \
-DCYBERSNAPPER_MACOS_ICON="$RUNNER_TEMP/CyberSnapper.icns"
cmake --build build/native --parallel
ctest --test-dir build/native --output-on-failure
- name: Keep production worker dependencies
run: npm prune --omit=dev
- name: Install application
shell: bash
run: cmake --install build/native --prefix stage --config Release
- name: Deploy Qt and sign
shell: bash
run: |
set -euo pipefail
macdeployqt stage/CyberSnapper.app \
-executable=stage/CyberSnapper.app/Contents/MacOS/cybersnapper-agent \
-executable=stage/CyberSnapper.app/Contents/MacOS/cybersnapper-cli \
-always-overwrite
frameworks=stage/CyberSnapper.app/Contents/Frameworks
brew list brotli >/dev/null 2>&1 || brew install brotli
brotli_prefix="$(brew --prefix brotli)"
cp -L "$brotli_prefix/lib/libbrotlicommon.1.dylib" "$frameworks/libbrotlicommon.1.dylib"
for library in "$frameworks/libbrotlidec.1.dylib" "$frameworks/libbrotlienc.1.dylib"; do
install_name_tool -change @rpath/libbrotlicommon.1.dylib \
@loader_path/libbrotlicommon.1.dylib "$library"
done
rm -f stage/CyberSnapper.app/Contents/PlugIns/sqldrivers/libqsql{mimer,odbc,psql}.dylib
codesign --force --deep --sign - stage/CyberSnapper.app
- name: Build DMG and ZIP
shell: bash
run: |
mkdir -p release-assets
hdiutil create -volname CyberSnapper -srcfolder stage/CyberSnapper.app -ov -format UDZO \
"release-assets/CyberSnapper-macos-${{ matrix.arch }}.dmg"
ditto -c -k --sequesterRsrc --keepParent stage/CyberSnapper.app \
"release-assets/CyberSnapper-macos-${{ matrix.arch }}.zip"
- name: Smoke-test packaged capture
shell: bash
timeout-minutes: 15
run: |
env -u QT_PLUGIN_PATH -u QML2_IMPORT_PATH node scripts/smoke-packaged-capture.mjs \
stage/CyberSnapper.app/Contents/MacOS/cybersnapper-cli \
stage/CyberSnapper.app/Contents/MacOS/cybersnapper-agent \
stage/CyberSnapper.app/Contents/Resources/worker/main.cjs \
stage/CyberSnapper.app/Contents/Resources/runtime/node \
stage/CyberSnapper.app/Contents/Resources/browsers \
build/native/smoke-state
- uses: actions/upload-artifact@v4
with:
name: CyberSnapper-macos-${{ matrix.arch }}
path: release-assets/*
if-no-files-found: error
publish:
name: Assemble and publish
needs: [linux, windows, macos]
if: github.event_name == 'release' || inputs.release_tag != ''
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Create SHA-256 checksums
shell: bash
run: (cd artifacts && sha256sum -- * > SHA256SUMS.txt)
- name: Attest package provenance
uses: actions/attest-build-provenance@v2
with:
subject-path: artifacts/*
- name: Publish packages to release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
set -euo pipefail
tag="${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}"
gh release upload "$tag" artifacts/* --clobber