Files
jwinterm 0d5d745311 v5.0.1: Windows QtQuick crash fix + node/Tor lifecycle overhaul
Windows crash chain (exposed by the embedded-node default):
- main.cpp: force the QtQuick software renderer. The Mining tab's QQuickWidget
  drove the default RHI/GL path and faulted (0xc0000005) on broken-GL hosts
  (VirtualBox VMSVGA), crashing the app and orphaning the embedded node.
  Real hardware was unaffected.
- ChildProcessGuard + DaemonManager/TorManager: kernel-level die-with-parent
  (Windows Job Object KILL_ON_JOB_CLOSE / Linux PR_SET_PDEATHSIG) so the
  embedded wownerod and bundled tor never survive a wowlet exit or crash.
- DaemonManager: verify-before-adopt via get_info (nettype/height) instead of
  trusting a bare open RPC port, so a stale/orphan node is never adopted.

Node/Tor lifecycle + remote-node connectivity:
- config: torPrivacyLevel default -> allTorExceptNode. NEVER sync blocks over
  Tor; the wallet's node connection is always clearnet. Tor is only for tx
  broadcast, so remote nodes work with or without Tor.
- nodes: add Nodes::reconnect() (clear failure blocklist + force reconnect);
  node/Tor toggle handlers use it so a transient outage no longer strands the
  wallet at "Disconnected".
- MainWindow/SettingsDialog: reconnect on node/Tor toggle and on the async
  daemonStateChanged (closes the start() race; a freshly-toggled local node
  connects immediately).
- TorManager: reset retry/unpack state in init() so a system<->bundled Tor
  re-toggle can start again; quiesce on stop(). Linux unpackBins() rework
  (mkpath + presence-based unpack + graceful return-false), getVersion() arg
  fix, and a missing-binary start guard.
- build.yml: derive TOR_VERSION from the embedded tor binary (a hardcoded
  bundle number made unpackBins rewrite Tor every launch).
- depends/funcs.mk: confine cross-cmake find_package to the depends sysroot so
  host -dev libraries don't leak into the build.
2026-06-23 17:45:39 -04:00

349 lines
19 KiB
YAML

name: ci/gh-actions/build
on: [push, pull_request, workflow_dispatch]
# NOTE (wownero-rebase branch): the Arch + macOS jobs from upstream Feather's build.yml are
# temporarily disabled to get a fast, cheap iteration loop while bringing up the build against the
# wownero monero submodule (gui branch). Restore the full matrix from upstream once Ubuntu is green.
jobs:
build-ubuntu-without-scanner:
name: "Ubuntu 24.04 (no scanner)"
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
steps:
- name: update apt
run: apt update
- name: install dependencies
run: apt -y install curl bzip2 git cmake build-essential ccache libssl-dev libunbound-dev libboost-all-dev libzmq3-dev
libqrencode-dev qt6-base-dev qt6-svg-dev qt6-websockets-dev qt6-multimedia-dev
qt6-wayland-dev qt6-declarative-dev libzip-dev libsodium-dev libgcrypt20-dev libx11-xcb-dev
qml6-module-qtquick qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtqml-models
protobuf-compiler libprotobuf-dev libhidapi-dev libzxing-dev libusb-dev
libusb-1.0-0-dev patchelf
- name: configure git
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
with:
submodules: recursive
- name: fetch wownerod to embed (single-file build)
run: |
mkdir -p /tmp/wownerod-embed
curl -L -o /tmp/wownero.tar.bz2 \
https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/wownero-x86_64-linux-gnu-v0.11.4.0.tar.bz2
tar xjf /tmp/wownero.tar.bz2 -C /tmp
find /tmp -name wownerod -type f -exec cp {} /tmp/wownerod-embed/ \;
chmod +x /tmp/wownerod-embed/wownerod
ls -la /tmp/wownerod-embed
- name: fetch tor to embed
run: |
mkdir -p /tmp/tor-embed
curl -L -o /tmp/teb.tgz \
https://archive.torproject.org/tor-package-archive/torbrowser/15.0/tor-expert-bundle-linux-x86_64-15.0.tar.gz
mkdir -p /tmp/teb && tar xzf /tmp/teb.tgz -C /tmp/teb
cp /tmp/teb/tor/tor /tmp/teb/tor/libssl.so.3 /tmp/teb/tor/libevent-2.1.so.7 /tmp/teb/tor/libcrypto.so.3 /tmp/tor-embed/
# expert-bundle tor has no rpath; point it (+ its libs) at $ORIGIN so the embedded copy finds
# its libs in the extract dir on any distro
for f in tor libssl.so.3 libevent-2.1.so.7 libcrypto.so.3; do patchelf --set-rpath '$ORIGIN' /tmp/tor-embed/$f; done
chmod +x /tmp/tor-embed/tor
ls -la /tmp/tor-embed
- name: build
run: |
mkdir build
cd build
# ARCH=x86-64: portable generic baseline. NEVER 'native' for a release — the CI runner's
# CPU (often newer than users') would bake in unsupported instructions → SIGILL on launch.
# wowlet: TOR_VERSION must EXACTLY match `tor --version` of the embedded binary, else
# TorManager::unpackBins() sees embedded > the unpacked copy and rewrites Tor every launch.
# The tor-expert-bundle's RELEASE number (15.0) is NOT its tor version (0.4.8.x) — derive it.
TORVER=$(/tmp/tor-embed/tor --version | grep -oE '[0-9]+(\.[0-9]+){3}' | head -1)
echo "embedded tor version: $TORVER"
cmake -DWITH_SCANNER=OFF -DARCH=x86-64 -DWOWNEROD_DIR=/tmp/wownerod-embed -DTOR_DIR=/tmp/tor-embed -DTOR_VERSION="$TORVER" ..
cmake --build . -j $(nproc)
- name: package AppImage
run: |
mkdir -p AppDir/usr/bin AppDir/usr/share/applications AppDir/usr/share/icons/hicolor/256x256/apps
cp build/bin/feather AppDir/usr/bin/wowlet
chmod +x AppDir/usr/bin/wowlet
cp src/assets/wowlet.desktop AppDir/usr/share/applications/wowlet.desktop
cp src/assets/images/appicons/256x256.png AppDir/usr/share/icons/hicolor/256x256/apps/wowlet.png
curl -sL -o linuxdeploy https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
curl -sL -o linuxdeploy-plugin-qt https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
chmod +x linuxdeploy linuxdeploy-plugin-qt
export APPIMAGE_EXTRACT_AND_RUN=1
export QMAKE="$(command -v qmake6 || echo /usr/bin/qmake6)"
export EXTRA_QT_PLUGINS="svg;multimedia;tls"
export QML_SOURCES_PATHS="$PWD/src/assets/qml"
export PATH="$PWD:$PATH"
# Populate AppDir (libs + Qt plugins + QML modules) — no --output yet, so we can patch QML first.
./linuxdeploy --appdir AppDir --plugin qt \
-d AppDir/usr/share/applications/wowlet.desktop \
-i AppDir/usr/share/icons/hicolor/256x256/apps/wowlet.png
# ---------------------------------------------------------------------------------------
# Make the QML bundle COMPLETE and deterministic (no whack-a-mole).
#
# qmlimportscanner only reads `import` lines in our source QML; it does NOT follow the
# `depends`/`default import`/singleton chains inside a Qt module's own qmldir. So transitive
# modules (QtQuick -> QtQml.WorkerScript/Models, etc.) get silently skipped. Rather than
# enumerate them one by one, blanket-copy the whole QtQuick + QtQml module TREES, then
# resolve each copied plugin .so's linked Qt libs into usr/lib ourselves (linuxdeploy only
# does that for modules IT deployed, not for ones we copy manually).
# ---------------------------------------------------------------------------------------
QMLDIR=$(qmake6 -query QT_INSTALL_QML)
mkdir -p AppDir/usr/qml AppDir/usr/lib
# Blanket-copy the full module trees we depend on (QtQuick incl. Layouts/Window, QtQml incl.
# WorkerScript/Models). -n = don't clobber what linuxdeploy already (correctly) deployed.
for m in QtQuick QtQml; do
cp -rn "$QMLDIR/$m" AppDir/usr/qml/ 2>/dev/null || true
done
# Resolve the DT_NEEDED Qt6 libs of EVERY bundled QML plugin .so into usr/lib. This closes
# the dlopen gap for the trees we copied by hand (libQt6Quick, libQt6Qml, libQt6QmlModels,
# libQt6QmlWorkerScript, libQt6QuickLayouts, etc.). -L follows symlinks, -n won't clobber.
find AppDir/usr/qml -name '*.so' -print0 | while IFS= read -r -d '' so; do
ldd "$so" 2>/dev/null | awk '/=> \//{print $3}' \
| grep -E '/(libQt6|libicu|libpcre2|libdouble-conversion|libzstd|libb2|libpng|libharfbuzz|libmd4c|libglib|libgraphite)' \
| while read -r lib; do cp -L -n "$lib" AppDir/usr/lib/ 2>/dev/null || true; done
done
# Sanity: linuxdeploy-plugin-qt writes AppDir/usr/bin/qt.conf with `Imports=qml`/`Qml2Imports=qml`
# (Prefix=../), which makes usr/qml the relocatable runtime import path. Confirm it's there.
echo "=== qt.conf ===" && cat AppDir/usr/bin/qt.conf 2>/dev/null || echo "WARNING: no qt.conf written by linuxdeploy-plugin-qt"
echo "=== bundled QtQuick/QtQml submodules ===" && (ls AppDir/usr/qml/QtQuick AppDir/usr/qml/QtQml 2>/dev/null | sed 's/^/ /')
# Guard: mining.qml must NOT pull in QtQuick.Controls/Templates (we removed that import).
# If a future edit re-adds a Controls type, this fails loudly instead of shipping a white tab.
if grep -q 'import QtQuick.Controls' src/assets/qml/mining.qml; then
echo "ERROR: mining.qml imports QtQuick.Controls again — bundle QtQuick/Templates+Controls trees or remove the import." >&2
exit 1
fi
# Package (linuxdeploy already wrote AppRun + .desktop + .DirIcon into AppDir root).
curl -sL -o appimagetool https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
ARCH=x86_64 ./appimagetool AppDir wowlet-x86_64.AppImage
ls -la wowlet-x86_64.AppImage
- name: upload wowlet AppImage
uses: actions/upload-artifact@v4
with:
name: wowlet-x86_64-linux-AppImage
path: wowlet-x86_64.AppImage
if-no-files-found: error
build-windows-mingw:
name: "Windows x86_64 (mingw cross, depends)"
runs-on: ubuntu-latest
env:
HOST: x86_64-w64-mingw32
steps:
- name: free disk space + add swap
# depends unpacks + builds Qt6/boost/openssl from source. Free / of GitHub bloat
# (dotnet/android/ghc/CodeQL) AND add swap — 16G RAM alone OOMs the parallel Qt6 compile.
run: |
echo "disk before:"; df -h / | tail -1
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/.ghcup /usr/local/lib/android \
/opt/hostedtoolcache/CodeQL /usr/share/swift 2>/dev/null || true
sudo apt-get clean
echo "disk after:"; df -h / | tail -1
sudo fallocate -l 10G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
echo "mem:"; free -h
- name: update apt
run: sudo apt-get update
- name: install dependencies
# depends cross-build deps: the mingw posix cross-compiler + native build tools depends needs.
# NO host Qt6 here (unlike the AppImage job) — depends builds a STATIC Qt6 for mingw itself.
run: |
sudo apt-get -y install curl ca-certificates bzip2 xz-utils unzip zip git cmake build-essential ccache \
python3 perl pkg-config g++-mingw-w64-x86-64-posix gcc-mingw-w64-x86-64-posix \
autoconf automake libtool gettext bison
- name: select posix mingw alternatives
# mingw32.mk wants the -posix threading variant (std::thread/mutex/cv need it).
run: |
sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix
sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
- name: configure git
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
with:
submodules: recursive
# depends is the slow part (static Qt6/boost/openssl/tor from source). Cache the source tarballs
# and the compiled-package cache so only the first run pays the ~1h cost; warm runs are minutes.
- name: restore depends sources cache
id: cache-sources
uses: actions/cache/restore@v4
with:
path: contrib/depends/sources
key: depends-sources-${{ hashFiles('contrib/depends/packages/*') }}
restore-keys: |
depends-sources-
- name: restore depends built cache (mingw)
id: cache-built
uses: actions/cache/restore@v4
with:
path: contrib/depends/built
key: depends-built-mingw32-${{ hashFiles('contrib/depends/packages/*', 'contrib/depends/hosts/*', 'contrib/depends/toolchain.cmake.in') }}
restore-keys: |
depends-built-mingw32-
- name: build depends (static Qt6 + tor + boost + openssl for mingw)
# -j2 not -j(nproc): the parallel static Qt6 compile OOMs the 16G runner at -j4. Slower but safe.
run: make -C contrib/depends HOST="$HOST" -j2
# Save depends with if:always() so the expensive toolchain banks even when the LATER wowlet.exe
# build fails — actions/cache's auto post-save is skipped on job failure, which kept re-paying ~2.5h.
- name: save depends sources cache
if: always() && steps.cache-sources.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: contrib/depends/sources
key: depends-sources-${{ hashFiles('contrib/depends/packages/*') }}
- name: save depends built cache (mingw)
if: always() && steps.cache-built.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: contrib/depends/built
key: depends-built-mingw32-${{ hashFiles('contrib/depends/packages/*', 'contrib/depends/hosts/*', 'contrib/depends/toolchain.cmake.in') }}
- name: fetch wownerod.exe to embed
run: |
mkdir -p /tmp/wownerod-embed
curl -L -o /tmp/wownero-win.zip \
https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/wownero-x86_64-w64-mingw32-v0.11.4.0.zip
# -j flattens the versioned subdir so wownerod.exe is a DIRECT child (WownerodQrcGenerator
# globs ${WOWNEROD_DIR}/* non-recursively).
unzip -j /tmp/wownero-win.zip 'wownero-x86_64-w64-mingw32-v0.11.4.0/wownerod.exe' -d /tmp/wownerod-embed
ls -la /tmp/wownerod-embed
- name: build wowlet.exe
run: |
mkdir build && cd build
# ARCH=x86-64: portable baseline. wownero's CMake defaults ARCH=native, which under a cross
# compiler bakes the CI runner's instructions into the Windows binary -> SIGILL on older CPUs.
cmake --toolchain "$GITHUB_WORKSPACE/contrib/depends/$HOST/share/toolchain.cmake" \
-DARCH=x86-64 \
-DWITH_SCANNER=OFF \
-DCHECK_UPDATES=OFF \
-DTOR_INSTALLED=On \
-DFEATHER_TARGET_TRIPLET="$HOST" \
-DWOWNEROD_DIR=/tmp/wownerod-embed \
..
cmake --build . -j2
ls -la bin/
- name: package zip
# Fully static build: just wowlet.exe + the depends-built tor.exe + license. No Qt/runtime DLLs.
run: |
DISTDIR=wowlet-x86_64-w64-mingw32
mkdir -p "$DISTDIR/tor"
cp build/bin/feather.exe "$DISTDIR/wowlet.exe"
cp "contrib/depends/$HOST/Tor/tor.exe" "$DISTDIR/tor/tor.exe"
(cp LICENSE "$DISTDIR/LICENSE.txt" 2>/dev/null || cp COPYING "$DISTDIR/LICENSE.txt" 2>/dev/null) || true
(cd "$DISTDIR" && find . -type f | sort | zip -X "../wowlet-x86_64-windows.zip" -@)
unzip -l wowlet-x86_64-windows.zip
- name: upload wowlet Windows zip
uses: actions/upload-artifact@v4
with:
name: wowlet-x86_64-windows
path: wowlet-x86_64-windows.zip
if-no-files-found: error
build-linux-cross:
name: "Linux ${{ matrix.arch }} (depends AppImage)"
runs-on: ubuntu-latest
continue-on-error: true # arm64/riscv64 deferred to v5.0.1; must not fail the release run
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
host: aarch64-linux-gnu
xcompiler: g++-aarch64-linux-gnu gcc-aarch64-linux-gnu
cmake_arch: armv8-a
wownerod: wownero-aarch64-linux-gnu-v0.11.4.0.tar.bz2
- arch: riscv64
host: riscv64-linux-gnu
xcompiler: g++-riscv64-linux-gnu gcc-riscv64-linux-gnu
cmake_arch: rv64gc
wownerod: wownero-riscv64-linux-gnu-v0.11.4.0.tar.bz2
env:
HOST: ${{ matrix.host }}
steps:
- name: free disk space + add swap
run: |
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/.ghcup /usr/local/lib/android \
/opt/hostedtoolcache/CodeQL /usr/share/swift 2>/dev/null || true
sudo apt-get clean
sudo fallocate -l 10G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
df -h / | tail -1; free -h | head -2
- name: install dependencies
# depends builds static Qt6/tor/boost/openssl for the target; we supply the linux cross-compiler
# ($(host)-gcc) + squashfs-tools (mksquashfs) for the AppImage.
run: |
sudo apt-get update
sudo apt-get -y install curl ca-certificates bzip2 xz-utils unzip git cmake build-essential ccache \
python3 perl pkg-config gperf squashfs-tools meson ninja-build ${{ matrix.xcompiler }} \
autoconf automake libtool gettext bison
- name: configure git
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
with:
submodules: recursive
- name: restore depends sources cache
id: cache-sources
uses: actions/cache/restore@v4
with:
path: contrib/depends/sources
key: depends-sources-${{ hashFiles('contrib/depends/packages/*') }}
restore-keys: |
depends-sources-
- name: restore depends built cache
id: cache-built
uses: actions/cache/restore@v4
with:
path: contrib/depends/built
key: depends-built-${{ matrix.host }}-${{ hashFiles('contrib/depends/packages/*', 'contrib/depends/hosts/*', 'contrib/depends/toolchain.cmake.in') }}
restore-keys: |
depends-built-${{ matrix.host }}-
- name: build depends (static Qt6 + tor + boost + openssl)
run: make -C contrib/depends HOST="$HOST" -j2
- name: save depends sources cache
if: always() && steps.cache-sources.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: contrib/depends/sources
key: depends-sources-${{ hashFiles('contrib/depends/packages/*') }}
- name: save depends built cache
if: always() && steps.cache-built.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: contrib/depends/built
key: depends-built-${{ matrix.host }}-${{ hashFiles('contrib/depends/packages/*', 'contrib/depends/hosts/*', 'contrib/depends/toolchain.cmake.in') }}
- name: fetch wownerod to embed
run: |
mkdir -p /tmp/wownerod-embed
curl -L -o /tmp/wownero.tar.bz2 \
https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/${{ matrix.wownerod }}
tar xjf /tmp/wownero.tar.bz2 -C /tmp
find /tmp -name wownerod -type f -exec cp {} /tmp/wownerod-embed/ \;
chmod +x /tmp/wownerod-embed/wownerod
ls -la /tmp/wownerod-embed
- name: build wowlet
run: |
mkdir build && cd build
# ARCH pinned (NOT wownero's native default — a cross compiler would bake the wrong ISA -> SIGILL).
cmake --toolchain "$GITHUB_WORKSPACE/contrib/depends/$HOST/share/toolchain.cmake" \
-DARCH=${{ matrix.cmake_arch }} \
-DWITH_SCANNER=OFF \
-DCHECK_UPDATES=OFF \
-DFEATHER_TARGET_TRIPLET="$HOST" \
-DTOR_DIR="$GITHUB_WORKSPACE/contrib/depends/$HOST/Tor" \
-DTOR_VERSION=0.4.9.6 \
-DWOWNEROD_DIR=/tmp/wownerod-embed \
..
cmake --build . -j2
ls -la bin/
- name: package AppImage
# build-appimage.sh wraps build/bin/feather with the depends-built ${HOST}/runtime (cross-arch).
run: |
bash contrib/AppImage/build-appimage.sh
mv feather.AppImage "wowlet-${{ matrix.arch }}.AppImage"
ls -la "wowlet-${{ matrix.arch }}.AppImage"
- name: upload wowlet AppImage
uses: actions/upload-artifact@v4
with:
name: wowlet-${{ matrix.arch }}-linux-AppImage
path: wowlet-${{ matrix.arch }}.AppImage
if-no-files-found: error