name: linux appimage (gitea self-hosted) # Runs ONLY on Gitea (GitHub ignores .gitea/workflows), in the private Builds # repository. Gitea is master; GitHub is a mirror and a release surface. # # Unlike the Windows lane, which builds a static toolchain of its own through # contrib/depends, the x86_64 AppImage links against the host's Qt6. Those # dependencies are owned by such-fleet (roles/wowlet_build_host) rather than # installed here: a workflow that apt-installs needs sudo on a shared runner and # makes the build host's contents a property of whichever job ran last. # # Dispatch-only. The runner is capacity 1 and shared with the fleet gates. on: [workflow_dispatch] jobs: appimage: name: "Linux x86_64 (host Qt6 AppImage)" runs-on: ubuntu-latest # gitea label ubuntu-latest:host -> deb, host mode steps: - name: verify the build host has its dependencies run: | missing=0 for t in cmake make curl bzip2 git patchelf pkg-config qmake6; do command -v "$t" >/dev/null || { echo "MISSING TOOL: $t"; missing=1; } done for h in /usr/include/boost/version.hpp; do [ -f "$h" ] || { echo "MISSING HEADER: $h"; missing=1; } done pkg-config --exists libsodium || { echo "MISSING: libsodium"; missing=1; } [ "$missing" = 0 ] || { echo "Run playbooks/wowlet-build-host.yml in such-fleet; do not apt-install from CI." exit 1; } echo "qt $(qmake6 -query QT_VERSION)"; nproc; free -g | head -2 - name: configure git run: git config --global --add safe.directory '*' - uses: actions/checkout@v4 with: submodules: recursive - name: fetch wownerod to embed run: | # ubuntu-latest and windows-latest both resolve to the same host in # host mode, so a fixed /tmp path lets a concurrently dispatched job's # `rm -rf` delete this job's binary between fetch and configure. Scope # every extract to the run. EMBED="/tmp/wowlet-${GITHUB_RUN_ID:-local}" rm -rf "$EMBED/wownerod-embed" && mkdir -p "$EMBED/wownerod-embed" curl -fL -o "$EMBED/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 rm -rf "$EMBED/wow-x" && mkdir -p "$EMBED/wow-x" tar xjf "$EMBED/wownero.tar.bz2" -C "$EMBED/wow-x" find "$EMBED/wow-x" -name wownerod -type f -exec cp {} "$EMBED/wownerod-embed/" \; chmod +x "$EMBED/wownerod-embed/wownerod" ls -la "$EMBED/wownerod-embed" - name: fetch tor to embed run: | EMBED="/tmp/wowlet-${GITHUB_RUN_ID:-local}" rm -rf "$EMBED/tor-embed" && mkdir -p "$EMBED/tor-embed" curl -fL -o "$EMBED/teb.tgz" \ https://archive.torproject.org/tor-package-archive/torbrowser/15.0/tor-expert-bundle-linux-x86_64-15.0.tar.gz rm -rf "$EMBED/teb" && mkdir -p "$EMBED/teb" && tar xzf "$EMBED/teb.tgz" -C "$EMBED/teb" cp "$EMBED/teb/tor/tor" "$EMBED/teb/tor/libssl.so.3" "$EMBED/teb/tor/libevent-2.1.so.7" \ "$EMBED/teb/tor/libcrypto.so.3" "$EMBED/tor-embed/" # The expert bundle's tor carries no rpath; point it and its libs at # $ORIGIN so the embedded copy resolves them from 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' "$EMBED/tor-embed/$f" done chmod +x "$EMBED/tor-embed/tor" - name: build run: | # TOR_VERSION must EXACTLY match `tor --version` of the embedded # binary, or TorManager::unpackBins() sees embedded > unpacked and # rewrites Tor on every launch. The bundle's release number (15.0) is # not its tor version, so derive it. EMBED="/tmp/wowlet-${GITHUB_RUN_ID:-local}" TORVER=$("$EMBED/tor-embed/tor" --version | grep -oE '[0-9]+(\.[0-9]+){3}' | head -1) echo "embedded tor version: $TORVER" # ARCH=x86-64, never native: the build host's CPU is newer than many # users' and native bakes in unsupported instructions -> SIGILL. cmake -S . -B build \ -DWITH_SCANNER=OFF \ -DARCH=x86-64 \ -DWOWNEROD_DIR="$EMBED/wownerod-embed" \ -DTOR_DIR="$EMBED/tor-embed" \ -DTOR_VERSION="$TORVER" cmake --build build -j6 ls -la build/bin/ - name: package AppImage run: | rm -rf AppDir mkdir -p AppDir/usr/bin AppDir/usr/share/applications \ AppDir/usr/share/icons/hicolor/256x256/apps cp build/bin/wowlet 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 curl -sL -o appimagetool https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x linuxdeploy linuxdeploy-plugin-qt appimagetool export APPIMAGE_EXTRACT_AND_RUN=1 export QMAKE="$(command -v qmake6)" export EXTRA_QT_PLUGINS="svg;multimedia;tls" export QML_SOURCES_PATHS="$PWD/src/assets/qml" export PATH="$PWD:$PATH" ./linuxdeploy --appdir AppDir --plugin qt \ -d AppDir/usr/share/applications/wowlet.desktop \ -i AppDir/usr/share/icons/hicolor/256x256/apps/wowlet.png # qmlimportscanner reads only the `import` lines in our QML; it does # not follow a module's own qmldir depends/default-import chain, so # transitive modules (QtQuick -> QtQml.WorkerScript/Models) are # silently skipped. Blanket-copy the trees, then resolve each copied # plugin's Qt libs ourselves -- linuxdeploy only does that for modules # it deployed, not ones we copied by hand. QMLDIR=$(qmake6 -query QT_INSTALL_QML) mkdir -p AppDir/usr/qml AppDir/usr/lib for m in QtQuick QtQml; do cp -rn "$QMLDIR/$m" AppDir/usr/qml/ 2>/dev/null || true; done 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 echo "=== qt.conf ==="; cat AppDir/usr/bin/qt.conf # Guard: mining.qml must not pull in QtQuick.Controls. If a future # edit re-adds a Controls type this fails loudly instead of shipping # a white mining tab. if grep -q 'import QtQuick.Controls' src/assets/qml/mining.qml; then echo "ERROR: mining.qml imports QtQuick.Controls again." >&2 exit 1 fi ARCH=x86_64 ./appimagetool AppDir wowlet-x86_64.AppImage ls -la wowlet-x86_64.AppImage - name: upload wowlet AppImage # v3: Gitea implements the older artifact protocol and rejects # @actions/artifact v2 clients (upload-artifact@v4+) as unsupported. uses: actions/upload-artifact@v3 with: name: wowlet-x86_64-linux-AppImage path: wowlet-x86_64.AppImage if-no-files-found: error - name: publish the AppImage to the tag's release # The artifact above uploads fine but this Gitea's REST listing reports # zero artifacts for every run, so a tag build's output cannot be fetched # back through the API. Release assets can, so publish there as well. # Fail-safe by design: with no usable token this says why and exits 0, # it never turns a green build red. if: startsWith(github.ref, 'refs/tags/') env: RELEASE_TOKEN: ${{ github.token }} run: | set -eu FILE=wowlet-x86_64.AppImage TAG="${GITHUB_REF#refs/tags/}" API="${GITHUB_SERVER_URL:-https://git.such.software}/api/v1/repos/$GITHUB_REPOSITORY" if [ -z "${RELEASE_TOKEN:-}" ]; then echo "no token available to this runner, skipping the release upload" >&2 exit 0 fi H="Authorization: token $RELEASE_TOKEN" # Single-line python on purpose: a continuation line at column 0 would # end this block scalar and break the workflow's YAML. `|| true` keeps # a malformed response from aborting the step under `set -e`. rid=$(curl -sS -H "$H" "$API/releases/tags/$TAG" | python3 -c "import json,sys;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) if [ -z "$rid" ]; then rid=$(curl -sS -H "$H" -H "Content-Type: application/json" -X POST \ -d "{\"tag_name\":\"$TAG\",\"name\":\"wowlet $TAG\"}" "$API/releases" | python3 -c "import json,sys;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) fi if [ -z "$rid" ]; then echo "could not resolve or create a release for $TAG, skipping the upload" >&2 exit 0 fi code=$(curl -sS -H "$H" -o /dev/null -w '%{http_code}' -X POST \ -F "attachment=@$FILE" "$API/releases/$rid/assets?name=$(basename "$FILE")") echo "release asset upload returned HTTP $code" if [ "$code" != "201" ]; then echo "the release upload did not return 201; the run artifact still holds the build" >&2 fi