ci: add AppImage builds and full platform coverage for campfire and stack_duo
What changed, and why it matters
This commit only adds new build and packaging automation. It creates GitHub Actions jobs to produce AppImage, Flatpak, Android, Windows, macOS, and iOS builds for the Campfire and Stack Duo wallet variants, plus small launcher scripts and desktop metadata files. There is no change to wallet logic, cryptography, networking, or user data handling.
No security action required. Treat as routine build infrastructure. If reviewing for supply-chain assurance, verify that the downloaded appimagetool binary and Flathub SDK are pinned or checksum-verified in a follow-up commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is entirely CI/CD and packaging scaffolding: new GitHub Actions workflow jobs download existing Linux bundles and wrap them into AppImage/Flatpak artifacts; additional jobs compile Campfire and Stack Duo for Android, Windows, macOS, and iOS using existing build scripts and secrets handling. New files are AppRun wrappers, .desktop entries, Flatpak manifest YAMLs, and shell wrapper scripts. No application source code, dependency versions, permissions model, or secret decoding logic is changed relative to the pre-existing Stack Wallet build jobs.
Changed components
.github/workflows/build.yamlappimage/campfire/AppRunappimage/campfire/campfire.desktopappimage/stack_duo/AppRunappimage/stack_duo/stack_duo.desktopappimage/stack_wallet/AppRunappimage/stack_wallet/stack_wallet.desktopflatpak/campfire.shflatpak/com.cypherstack.campfire.desktopflatpak/com.cypherstack.campfire.metainfo.xmlflatpak/com.cypherstack.campfire.yamlflatpak/com.cypherstack.stackduo.desktopflatpak/com.cypherstack.stackduo.metainfo.xmlflatpak/com.cypherstack.stackduo.yamlflatpak/stack_duo.shInspect captured patch +1078 / −0
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index ea994cb..3036f07 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -687,3 +687,929 @@ jobs:
name: stack_wallet-flatpak-x86_64-${{ steps.ver.outputs.version }}.flatpak
path: stack_wallet-flatpak-x86_64-${{ steps.ver.outputs.version }}.flatpak
+ build-appimage:
+ runs-on: ubuntu-24.04
+ needs: build-linux
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+
+ - name: Download Linux bundle
+ uses: actions/download-artifact@v4
+ with:
+ name: stack_wallet-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz
+
+ - name: Install AppImage tools
+ run: |
+ sudo apt-get update -q
+ sudo apt-get install -y squashfs-tools
+
+ - name: Build AppImage
+ run: |
+ VERSION="${{ steps.ver.outputs.version }}"
+ tar -xzf "stack_wallet-linux-x86_64-${VERSION}.tar.gz"
+ mkdir -p AppDir
+ cp -r bundle/* AppDir/
+ cp appimage/stack_wallet/AppRun AppDir/AppRun
+ chmod +x AppDir/AppRun
+ cp appimage/stack_wallet/stack_wallet.desktop AppDir/
+ cp asset_sources/icon/stack_wallet/icon.png AppDir/stack_wallet.png
+ curl -fsSL -o appimagetool \
+ https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
+ chmod +x appimagetool
+ ARCH=x86_64 ./appimagetool --appimage-extract-and-run AppDir \
+ "stack_wallet-appimage-x86_64-${VERSION}.AppImage"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_wallet-appimage-x86_64-${{ steps.ver.outputs.version }}.AppImage
+ path: stack_wallet-appimage-x86_64-${{ steps.ver.outputs.version }}.AppImage
+
+ build-campfire-android:
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+ packages: read
+ container:
+ image: ghcr.io/${{ github.repository_owner }}/stackwallet-ci:latest
+ credentials:
+ username: ${{ github.actor }}
+ password: ${{ github.token }}
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p android -a campfire -d -s
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Set up Android local.properties
+ run: |
+ cat > android/local.properties <<EOF
+ sdk.dir=${ANDROID_SDK_ROOT}
+ flutter.sdk=${FLUTTER_HOME}
+ EOF
+
+ - name: Set up signing
+ env:
+ KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
+ run: |
+ printf '%s' "$KEYSTORE_BASE64" | base64 --decode > android/keystore-orig.jks
+ [ -s android/keystore-orig.jks ] || { echo "ERROR: ANDROID_KEYSTORE_BASE64 secret is empty or not set"; exit 1; }
+ keytool -importkeystore \
+ -srckeystore android/keystore-orig.jks \
+ -destkeystore android/keystore.jks \
+ -deststoretype pkcs12 \
+ -srcstorepass "${{ secrets.ANDROID_STORE_PASSWORD }}" \
+ -deststorepass "${{ secrets.ANDROID_STORE_PASSWORD }}" \
+ -noprompt \
+ -J-Dkeystore.pkcs12.legacy
+ rm android/keystore-orig.jks
+ cat > android/key.properties <<EOF
+ storeFile=../keystore.jks
+ storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
+ keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
+ keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
+ EOF
+
+ - name: Build APKs
+ run: flutter build apk --split-per-abi --release
+
+ - name: Build AAB
+ run: flutter build appbundle --release
+
+ - name: Collect artifacts
+ run: |
+ VERSION="${{ steps.ver.outputs.version }}"
+ mkdir -p android-artifacts
+ cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \
+ android-artifacts/campfire-android-arm64-v8a-${VERSION}.apk
+ cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk \
+ android-artifacts/campfire-android-armeabi-v7a-${VERSION}.apk
+ cp build/app/outputs/flutter-apk/app-x86_64-release.apk \
+ android-artifacts/campfire-android-x86_64-${VERSION}.apk
+ cp build/app/outputs/bundle/release/app-release.aab \
+ android-artifacts/campfire-android-${VERSION}.aab
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: campfire-android-${{ steps.ver.outputs.version }}
+ path: android-artifacts/
+
+ build-campfire-windows:
+ runs-on: windows-2022
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: '3.38.1'
+ channel: 'stable'
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.24.13'
+
+ - name: Flutter doctor
+ run: flutter doctor -v
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p windows -a campfire -d -s
+
+ - name: Replace asset symlinks with copies (CI workaround)
+ run: |
+ set -euo pipefail
+ for dirname in default_themes icon lottie in_app_logo_icons svg; do
+ target="assets/${dirname}"
+ source="asset_sources/${dirname}/campfire"
+ if [ -e "$target" ] || [ -L "$target" ]; then
+ cmd.exe /c rmdir "$(cygpath -w "$target")" 2>/dev/null || rm -rf "$target"
+ fi
+ mkdir -p "$target"
+ cp -r "${source}/." "$target/"
+ done
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Build secp256k1.dll for Windows
+ run: dart run coinlib:build_windows
+
+ - name: Build
+ run: flutter build windows --release
+
+ - name: Package
+ shell: pwsh
+ run: |
+ Compress-Archive -Path "build/windows/x64/runner/Release/*" `
+ -DestinationPath "campfire-windows-x86_64-${{ steps.ver.outputs.version }}.zip"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: campfire-windows-x86_64-${{ steps.ver.outputs.version }}.zip
+ path: campfire-windows-x86_64-${{ steps.ver.outputs.version }}.zip
+
+ build-campfire-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: '3.38.1'
+ channel: 'stable'
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.24.13'
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p macos -a campfire -d -s
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Build
+ run: flutter build macos --release
+
+ - name: Package
+ run: |
+ cd "build/macos/Build/Products/Release"
+ zip -r "$GITHUB_WORKSPACE/campfire-macos-aarch64-${{ steps.ver.outputs.version }}.zip" \
+ "Campfire.app"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: campfire-macos-aarch64-${{ steps.ver.outputs.version }}.zip
+ path: campfire-macos-aarch64-${{ steps.ver.outputs.version }}.zip
+
+ build-campfire-ios:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: stable
+ targets: aarch64-apple-ios
+
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: '3.38.1'
+ channel: 'stable'
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.24.13'
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p ios -a campfire -d -s
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Build
+ run: flutter build ios --release --no-codesign
+
+ - name: Package IPA
+ run: |
+ mkdir Payload
+ cp -r build/ios/iphoneos/Runner.app Payload/
+ zip -r "campfire-ios-aarch64-${{ steps.ver.outputs.version }}.ipa" Payload/
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: campfire-ios-aarch64-${{ steps.ver.outputs.version }}.ipa
+ path: campfire-ios-aarch64-${{ steps.ver.outputs.version }}.ipa
+
+ build-campfire-flatpak:
+ runs-on: ubuntu-24.04
+ needs: build-campfire-linux
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+
+ - name: Download Linux bundle
+ uses: actions/download-artifact@v4
+ with:
+ name: campfire-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz
+
+ - name: Stage bundle and icon
+ run: |
+ tar -xzf "campfire-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz" -C flatpak/
+ cp asset_sources/icon/campfire/icon.png flatpak/com.cypherstack.campfire.png
+
+ - name: Install Flatpak tools
+ run: |
+ sudo apt-get update -q
+ sudo apt-get install -y flatpak flatpak-builder
+
+ - name: Set up Flathub remote
+ run: flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
+
+ - name: Cache Flatpak SDK
+ uses: actions/cache@v4
+ with:
+ path: ~/.local/share/flatpak
+ key: flatpak-freedesktop-24.08-v1
+
+ - name: Install Flatpak SDK
+ run: |
+ flatpak install --user --noninteractive flathub \
+ org.freedesktop.Platform//24.08 \
+ org.freedesktop.Sdk//24.08
+
+ - name: Build Flatpak
+ run: |
+ flatpak-builder --user --force-clean \
+ --repo=flatpak-repo \
+ build-flatpak flatpak/com.cypherstack.campfire.yaml
+
+ - name: Bundle Flatpak
+ run: |
+ flatpak build-bundle flatpak-repo \
+ --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo \
+ "campfire-flatpak-x86_64-${{ steps.ver.outputs.version }}.flatpak" \
+ com.cypherstack.campfire
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: campfire-flatpak-x86_64-${{ steps.ver.outputs.version }}.flatpak
+ path: campfire-flatpak-x86_64-${{ steps.ver.outputs.version }}.flatpak
+
+ build-campfire-appimage:
+ runs-on: ubuntu-24.04
+ needs: build-campfire-linux
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+
+ - name: Download Linux bundle
+ uses: actions/download-artifact@v4
+ with:
+ name: campfire-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz
+
+ - name: Install AppImage tools
+ run: |
+ sudo apt-get update -q
+ sudo apt-get install -y squashfs-tools
+
+ - name: Build AppImage
+ run: |
+ VERSION="${{ steps.ver.outputs.version }}"
+ tar -xzf "campfire-linux-x86_64-${VERSION}.tar.gz"
+ mkdir -p AppDir
+ cp -r bundle/* AppDir/
+ cp appimage/campfire/AppRun AppDir/AppRun
+ chmod +x AppDir/AppRun
+ cp appimage/campfire/campfire.desktop AppDir/
+ cp asset_sources/icon/campfire/icon.png AppDir/campfire.png
+ curl -fsSL -o appimagetool \
+ https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
+ chmod +x appimagetool
+ ARCH=x86_64 ./appimagetool --appimage-extract-and-run AppDir \
+ "campfire-appimage-x86_64-${VERSION}.AppImage"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: campfire-appimage-x86_64-${{ steps.ver.outputs.version }}.AppImage
+ path: campfire-appimage-x86_64-${{ steps.ver.outputs.version }}.AppImage
+
+ build-stack-duo-android:
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+ packages: read
+ container:
+ image: ghcr.io/${{ github.repository_owner }}/stackwallet-ci:latest
+ credentials:
+ username: ${{ github.actor }}
+ password: ${{ github.token }}
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p android -a stack_duo -d -s
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Set up Android local.properties
+ run: |
+ cat > android/local.properties <<EOF
+ sdk.dir=${ANDROID_SDK_ROOT}
+ flutter.sdk=${FLUTTER_HOME}
+ EOF
+
+ - name: Set up signing
+ env:
+ KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
+ run: |
+ printf '%s' "$KEYSTORE_BASE64" | base64 --decode > android/keystore-orig.jks
+ [ -s android/keystore-orig.jks ] || { echo "ERROR: ANDROID_KEYSTORE_BASE64 secret is empty or not set"; exit 1; }
+ keytool -importkeystore \
+ -srckeystore android/keystore-orig.jks \
+ -destkeystore android/keystore.jks \
+ -deststoretype pkcs12 \
+ -srcstorepass "${{ secrets.ANDROID_STORE_PASSWORD }}" \
+ -deststorepass "${{ secrets.ANDROID_STORE_PASSWORD }}" \
+ -noprompt \
+ -J-Dkeystore.pkcs12.legacy
+ rm android/keystore-orig.jks
+ cat > android/key.properties <<EOF
+ storeFile=../keystore.jks
+ storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
+ keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
+ keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
+ EOF
+
+ - name: Build APKs
+ run: flutter build apk --split-per-abi --release
+
+ - name: Build AAB
+ run: flutter build appbundle --release
+
+ - name: Collect artifacts
+ run: |
+ VERSION="${{ steps.ver.outputs.version }}"
+ mkdir -p android-artifacts
+ cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \
+ android-artifacts/stack_duo-android-arm64-v8a-${VERSION}.apk
+ cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk \
+ android-artifacts/stack_duo-android-armeabi-v7a-${VERSION}.apk
+ cp build/app/outputs/flutter-apk/app-x86_64-release.apk \
+ android-artifacts/stack_duo-android-x86_64-${VERSION}.apk
+ cp build/app/outputs/bundle/release/app-release.aab \
+ android-artifacts/stack_duo-android-${VERSION}.aab
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_duo-android-${{ steps.ver.outputs.version }}
+ path: android-artifacts/
+
+ build-stack-duo-windows:
+ runs-on: windows-2022
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: '3.38.1'
+ channel: 'stable'
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.24.13'
+
+ - name: Flutter doctor
+ run: flutter doctor -v
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p windows -a stack_duo -d -s
+
+ - name: Replace asset symlinks with copies (CI workaround)
+ run: |
+ set -euo pipefail
+ for dirname in default_themes icon lottie in_app_logo_icons svg; do
+ target="assets/${dirname}"
+ source="asset_sources/${dirname}/stack_duo"
+ if [ -e "$target" ] || [ -L "$target" ]; then
+ cmd.exe /c rmdir "$(cygpath -w "$target")" 2>/dev/null || rm -rf "$target"
+ fi
+ mkdir -p "$target"
+ cp -r "${source}/." "$target/"
+ done
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Build secp256k1.dll for Windows
+ run: dart run coinlib:build_windows
+
+ - name: Build
+ run: flutter build windows --release
+
+ - name: Package
+ shell: pwsh
+ run: |
+ Compress-Archive -Path "build/windows/x64/runner/Release/*" `
+ -DestinationPath "stack_duo-windows-x86_64-${{ steps.ver.outputs.version }}.zip"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_duo-windows-x86_64-${{ steps.ver.outputs.version }}.zip
+ path: stack_duo-windows-x86_64-${{ steps.ver.outputs.version }}.zip
+
+ build-stack-duo-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: '3.38.1'
+ channel: 'stable'
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.24.13'
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p macos -a stack_duo -d -s
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Build
+ run: flutter build macos --release
+
+ - name: Package
+ run: |
+ cd "build/macos/Build/Products/Release"
+ zip -r "$GITHUB_WORKSPACE/stack_duo-macos-aarch64-${{ steps.ver.outputs.version }}.zip" \
+ "Stack Duo.app"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_duo-macos-aarch64-${{ steps.ver.outputs.version }}.zip
+ path: stack_duo-macos-aarch64-${{ steps.ver.outputs.version }}.zip
+
+ build-stack-duo-ios:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: stable
+ targets: aarch64-apple-ios
+
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: '3.38.1'
+ channel: 'stable'
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.24.13'
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p ios -a stack_duo -d -s
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Build
+ run: flutter build ios --release --no-codesign
+
+ - name: Package IPA
+ run: |
+ mkdir Payload
+ cp -r build/ios/iphoneos/Runner.app Payload/
+ zip -r "stack_duo-ios-aarch64-${{ steps.ver.outputs.version }}.ipa" Payload/
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_duo-ios-aarch64-${{ steps.ver.outputs.version }}.ipa
+ path: stack_duo-ios-aarch64-${{ steps.ver.outputs.version }}.ipa
+
+ build-stack-duo-flatpak:
+ runs-on: ubuntu-24.04
+ needs: build-stack-duo-linux
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+
+ - name: Download Linux bundle
+ uses: actions/download-artifact@v4
+ with:
+ name: stack_duo-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz
+
+ - name: Stage bundle and icon
+ run: |
+ tar -xzf "stack_duo-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz" -C flatpak/
+ cp asset_sources/icon/stack_duo/icon.png flatpak/com.cypherstack.stackduo.png
+
+ - name: Install Flatpak tools
+ run: |
+ sudo apt-get update -q
+ sudo apt-get install -y flatpak flatpak-builder
+
+ - name: Set up Flathub remote
+ run: flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
+
+ - name: Cache Flatpak SDK
+ uses: actions/cache@v4
+ with:
+ path: ~/.local/share/flatpak
+ key: flatpak-freedesktop-24.08-v1
+
+ - name: Install Flatpak SDK
+ run: |
+ flatpak install --user --noninteractive flathub \
+ org.freedesktop.Platform//24.08 \
+ org.freedesktop.Sdk//24.08
+
+ - name: Build Flatpak
+ run: |
+ flatpak-builder --user --force-clean \
+ --repo=flatpak-repo \
+ build-flatpak flatpak/com.cypherstack.stackduo.yaml
+
+ - name: Bundle Flatpak
+ run: |
+ flatpak build-bundle flatpak-repo \
+ --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo \
+ "stack_duo-flatpak-x86_64-${{ steps.ver.outputs.version }}.flatpak" \
+ com.cypherstack.stackduo
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_duo-flatpak-x86_64-${{ steps.ver.outputs.version }}.flatpak
+ path: stack_duo-flatpak-x86_64-${{ steps.ver.outputs.version }}.flatpak
+
+ build-stack-duo-appimage:
+ runs-on: ubuntu-24.04
+ needs: build-stack-duo-linux
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ elif [ -n "${{ inputs.version }}" ]; then
+ VERSION="${{ inputs.version }}"
+ else
+ VERSION="0.0.0-staging.${{ github.run_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+
+ - name: Download Linux bundle
+ uses: actions/download-artifact@v4
+ with:
+ name: stack_duo-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz
+
+ - name: Install AppImage tools
+ run: |
+ sudo apt-get update -q
+ sudo apt-get install -y squashfs-tools
+
+ - name: Build AppImage
+ run: |
+ VERSION="${{ steps.ver.outputs.version }}"
+ tar -xzf "stack_duo-linux-x86_64-${VERSION}.tar.gz"
+ mkdir -p AppDir
+ cp -r bundle/* AppDir/
+ cp appimage/stack_duo/AppRun AppDir/AppRun
+ chmod +x AppDir/AppRun
+ cp appimage/stack_duo/stack_duo.desktop AppDir/
+ cp asset_sources/icon/stack_duo/icon.png AppDir/stack_duo.png
+ curl -fsSL -o appimagetool \
+ https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
+ chmod +x appimagetool
+ ARCH=x86_64 ./appimagetool --appimage-extract-and-run AppDir \
+ "stack_duo-appimage-x86_64-${VERSION}.AppImage"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_duo-appimage-x86_64-${{ steps.ver.outputs.version }}.AppImage
+ path: stack_duo-appimage-x86_64-${{ steps.ver.outputs.version }}.AppImage
+
diff --git a/appimage/campfire/AppRun b/appimage/campfire/AppRun
new file mode 100755
index 0000000..2d22d3d
--- /dev/null
+++ b/appimage/campfire/AppRun
@@ -0,0 +1,5 @@
+#!/bin/bash
+SELF=$(readlink -f "$0")
+HERE=${SELF%/*}
+export LD_LIBRARY_PATH="${HERE}/lib:${LD_LIBRARY_PATH}"
+exec "${HERE}/campfire" "$@"
diff --git a/appimage/campfire/campfire.desktop b/appimage/campfire/campfire.desktop
new file mode 100644
index 0000000..71b7b61
--- /dev/null
+++ b/appimage/campfire/campfire.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Campfire
+Comment=Your privacy. Your wallet. Your Firo.
+Exec=campfire
+Icon=campfire
+Type=Application
+Categories=Finance;
diff --git a/appimage/stack_duo/AppRun b/appimage/stack_duo/AppRun
new file mode 100755
index 0000000..9b8f349
--- /dev/null
+++ b/appimage/stack_duo/AppRun
@@ -0,0 +1,5 @@
+#!/bin/bash
+SELF=$(readlink -f "$0")
+HERE=${SELF%/*}
+export LD_LIBRARY_PATH="${HERE}/lib:${LD_LIBRARY_PATH}"
+exec "${HERE}/stack_duo" "$@"
diff --git a/appimage/stack_duo/stack_duo.desktop b/appimage/stack_duo/stack_duo.desktop
new file mode 100644
index 0000000..64b9f6d
--- /dev/null
+++ b/appimage/stack_duo/stack_duo.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Stack Duo
+Comment=An open-source, multicoin wallet for everyone
+Exec=stack_duo
+Icon=stack_duo
+Type=Application
+Categories=Finance;
diff --git a/appimage/stack_wallet/AppRun b/appimage/stack_wallet/AppRun
new file mode 100755
index 0000000..6038f4b
--- /dev/null
+++ b/appimage/stack_wallet/AppRun
@@ -0,0 +1,5 @@
+#!/bin/bash
+SELF=$(readlink -f "$0")
+HERE=${SELF%/*}
+export LD_LIBRARY_PATH="${HERE}/lib:${LD_LIBRARY_PATH}"
+exec "${HERE}/stack_wallet" "$@"
diff --git a/appimage/stack_wallet/stack_wallet.desktop b/appimage/stack_wallet/stack_wallet.desktop
new file mode 100644
index 0000000..19c6fcc
--- /dev/null
+++ b/appimage/stack_wallet/stack_wallet.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Stack Wallet
+Comment=Open-source non-custodial cryptocurrency wallet
+Exec=stack_wallet
+Icon=stack_wallet
+Type=Application
+Categories=Finance;
diff --git a/flatpak/campfire.sh b/flatpak/campfire.sh
new file mode 100644
index 0000000..0bd7154
--- /dev/null
+++ b/flatpak/campfire.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /app/lib/campfire/campfire "$@"
diff --git a/flatpak/com.cypherstack.campfire.desktop b/flatpak/com.cypherstack.campfire.desktop
new file mode 100644
index 0000000..512ef2c
--- /dev/null
+++ b/flatpak/com.cypherstack.campfire.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Campfire
+Comment=Your privacy. Your wallet. Your Firo.
+Exec=campfire
+Icon=com.cypherstack.campfire
+Type=Application
+Categories=Finance;
diff --git a/flatpak/com.cypherstack.campfire.metainfo.xml b/flatpak/com.cypherstack.campfire.metainfo.xml
new file mode 100644
index 0000000..f355750
--- /dev/null
+++ b/flatpak/com.cypherstack.campfire.metainfo.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop-application">
+ <id>com.cypherstack.campfire</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-3.0-only</project_license>
+ <name>Campfire</name>
+ <summary>Your privacy. Your wallet. Your Firo.</summary>
+ <description>
+ <p>
+ Campfire is an open-source, non-custodial Firo wallet.
+ </p>
+ </description>
+ <url type="homepage">https://campfireprivacy.com</url>
+ <url type="bugtracker">https://github.com/cypherstack/stack_wallet/issues</url>
+ <content_rating type="oars-1.1"/>
+</component>
diff --git a/flatpak/com.cypherstack.campfire.yaml b/flatpak/com.cypherstack.campfire.yaml
new file mode 100644
index 0000000..9f8cd9e
--- /dev/null
+++ b/flatpak/com.cypherstack.campfire.yaml
@@ -0,0 +1,33 @@
+app-id: com.cypherstack.campfire
+runtime: org.freedesktop.Platform
+runtime-version: '24.08'
+sdk: org.freedesktop.Sdk
+command: campfire
+
+finish-args:
+ - --share=network
+ - --share=ipc
+ - --socket=fallback-x11
+ - --socket=wayland
+ - --device=dri
+ - --filesystem=~/.campfire
+ - --talk-name=org.freedesktop.secrets
+ - --talk-name=org.freedesktop.Notifications
+
+modules:
+ - name: campfire
+ buildsystem: simple
+ build-commands:
+ - mkdir -p /app/lib/campfire
+ - install -Dm755 bundle/campfire /app/lib/campfire/campfire
+ - cp -r bundle/lib bundle/data /app/lib/campfire/
+ - install -Dm755 campfire.sh /app/bin/campfire
+ - install -Dm644 com.cypherstack.campfire.desktop
+ /app/share/applications/com.cypherstack.campfire.desktop
+ - install -Dm644 com.cypherstack.campfire.metainfo.xml
+ /app/share/metainfo/com.cypherstack.campfire.metainfo.xml
+ - install -Dm644 com.cypherstack.campfire.png
+ /app/share/icons/hicolor/512x512/apps/com.cypherstack.campfire.png
+ sources:
+ - type: dir
+ path: .
diff --git a/flatpak/com.cypherstack.stackduo.desktop b/flatpak/com.cypherstack.stackduo.desktop
new file mode 100644
index 0000000..219c835
--- /dev/null
+++ b/flatpak/com.cypherstack.stackduo.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Stack Duo
+Comment=An open-source, multicoin wallet for everyone
+Exec=stack_duo
+Icon=com.cypherstack.stackduo
+Type=Application
+Categories=Finance;
diff --git a/flatpak/com.cypherstack.stackduo.metainfo.xml b/flatpak/com.cypherstack.stackduo.metainfo.xml
new file mode 100644
index 0000000..1e929de
--- /dev/null
+++ b/flatpak/com.cypherstack.stackduo.metainfo.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop-application">
+ <id>com.cypherstack.stackduo</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-3.0-only</project_license>
+ <name>Stack Duo</name>
+ <summary>An open-source, multicoin wallet for everyone</summary>
+ <description>
+ <p>
+ Stack Duo is an open-source, non-custodial cryptocurrency wallet.
+ </p>
+ </description>
+ <url type="homepage">https://stackwallet.com</url>
+ <url type="bugtracker">https://github.com/cypherstack/stack_wallet/issues</url>
+ <content_rating type="oars-1.1"/>
+</component>
diff --git a/flatpak/com.cypherstack.stackduo.yaml b/flatpak/com.cypherstack.stackduo.yaml
new file mode 100644
index 0000000..195f674
--- /dev/null
+++ b/flatpak/com.cypherstack.stackduo.yaml
@@ -0,0 +1,33 @@
+app-id: com.cypherstack.stackduo
+runtime: org.freedesktop.Platform
+runtime-version: '24.08'
+sdk: org.freedesktop.Sdk
+command: stack_duo
+
+finish-args:
+ - --share=network
+ - --share=ipc
+ - --socket=fallback-x11
+ - --socket=wayland
+ - --device=dri
+ - --filesystem=~/.stackduo
+ - --talk-name=org.freedesktop.secrets
+ - --talk-name=org.freedesktop.Notifications
+
+modules:
+ - name: stack_duo
+ buildsystem: simple
+ build-commands:
+ - mkdir -p /app/lib/stack_duo
+ - install -Dm755 bundle/stack_duo /app/lib/stack_duo/stack_duo
+ - cp -r bundle/lib bundle/data /app/lib/stack_duo/
+ - install -Dm755 stack_duo.sh /app/bin/stack_duo
+ - install -Dm644 com.cypherstack.stackduo.desktop
+ /app/share/applications/com.cypherstack.stackduo.desktop
+ - install -Dm644 com.cypherstack.stackduo.metainfo.xml
+ /app/share/metainfo/com.cypherstack.stackduo.metainfo.xml
+ - install -Dm644 com.cypherstack.stackduo.png
+ /app/share/icons/hicolor/512x512/apps/com.cypherstack.stackduo.png
+ sources:
+ - type: dir
+ path: .
diff --git a/flatpak/stack_duo.sh b/flatpak/stack_duo.sh
new file mode 100644
index 0000000..522eab5
--- /dev/null
+++ b/flatpak/stack_duo.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /app/lib/stack_duo/stack_duo "$@"
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.