# Multi-platform build + packaging workflow # Thin GitHub Actions wrapper around Prefect + Dagger pipeline. # # Architecture: # - Windows build runs natively on windows-latest (cannot containerize) # - Linux builds run via Dagger containers orchestrated by Prefect # - Signing, release creation, and R2 upload handled by Prefect tasks # - Container runtime: Podman (Dagger connects via Podman socket) # # Local equivalent: # poetry run python -m ci.prefect_flow full \ # --gpg-passphrase "$GPG_PASSPHRASE" \ # --github-token "$GITHUB_TOKEN" name: Build Multi-Platform Binaries on: workflow_dispatch: permissions: contents: read packages: read concurrency: group: release-workflow cancel-in-progress: true env: CI_CD: true CI_CD_PAT: ${{ secrets.CI_CD_PAT }} jobs: # ── Windows Build (native runner — cannot containerize) ────────────── build-windows: runs-on: windows-latest steps: - name: Checkout code uses: actions/checkout@v4 with: ref: main - name: Set up Python 3.13 uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install Poetry uses: snok/install-poetry@v1 with: version: latest virtualenvs-create: true virtualenvs-in-project: true - name: Ensure Poetry is on PATH shell: pwsh run: | $poetryPath = Join-Path $env:USERPROFILE ".local\bin" Write-Output $poetryPath >> $Env:GITHUB_PATH - name: Install dependencies run: poetry install - name: Build Windows executable run: poetry run pyinstaller scripts/spec_scripts/android-file-handler-windows.spec - name: Install Inno Setup 6.7.1 shell: pwsh run: | choco install innosetup --version 6.7.1 -y --no-progress # Refresh PATH so ISCC.exe is available immediately $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") - name: Build Windows installer (Inno Setup) shell: pwsh run: | $version = (poetry version -s).Trim() Write-Output "Building installer for version $version" & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" ` "scripts\windows\android-file-handler-setup.iss" ` "/DMyAppVersion=$version" - name: Import GPG key shell: pwsh run: | $env:GPG_TTY = "not a tty" echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import gpg --list-secret-keys - name: Sign and hash Windows artifacts shell: pwsh run: | $passphraseFile = New-TemporaryFile try { "${{ secrets.GPG_PASSPHRASE }}" | Out-File -FilePath $passphraseFile -Encoding ASCII -NoNewline # Sign and hash standalone executable $exePath = Get-ChildItem -Path dist -Filter "android-file-handler-windows.exe" | Select-Object -First 1 -ExpandProperty FullName if (-not $exePath) { Write-Error "Standalone executable not found"; exit 1 } gpg --batch --yes --passphrase-file "$passphraseFile" --detach-sign --armor "$exePath" $hash = (Get-FileHash -Path "$exePath" -Algorithm SHA256).Hash.ToLower() "$hash $(Split-Path -Leaf $exePath)" | Out-File -FilePath "dist/android-file-handler-windows.sha256" -Encoding ASCII -NoNewline # Sign and hash installer $setupPath = Get-ChildItem -Path dist -Filter "android-file-handler-setup.exe" | Select-Object -First 1 -ExpandProperty FullName if (-not $setupPath) { Write-Error "Installer not found"; exit 1 } gpg --batch --yes --passphrase-file "$passphraseFile" --detach-sign --armor "$setupPath" $setupHash = (Get-FileHash -Path "$setupPath" -Algorithm SHA256).Hash.ToLower() "$setupHash $(Split-Path -Leaf $setupPath)" | Out-File -FilePath "dist/android-file-handler-setup.sha256" -Encoding ASCII -NoNewline } finally { if (Test-Path $passphraseFile) { Remove-Item $passphraseFile -Force } } - name: Upload Windows artifact uses: actions/upload-artifact@v4 with: name: windows-binary path: | dist/android-file-handler-windows.exe dist/android-file-handler-windows.exe.asc dist/android-file-handler-windows.sha256 dist/android-file-handler-setup.exe dist/android-file-handler-setup.exe.asc dist/android-file-handler-setup.sha256 # ── Linux Builds (Prefect + Dagger with Podman backend) ───────────── build-linux: runs-on: ubuntu-latest permissions: contents: read packages: read steps: - name: Checkout code uses: actions/checkout@v4 with: ref: main - name: Set up Python 3.13 uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install Poetry uses: snok/install-poetry@v1 with: version: latest virtualenvs-create: true virtualenvs-in-project: true - name: Install project + CI dependencies run: poetry install --with ci - name: Set up Podman run: | sudo apt-get update -qq sudo apt-get install -y -qq podman # Start rootful Podman socket for Dagger compatibility sudo systemctl enable --now podman.socket echo "DOCKER_HOST=unix:///run/podman/podman.sock" >> "$GITHUB_ENV" - name: Install Dagger CLI uses: dagger/dagger-for-github@v7 with: verb: version - name: Log in to GHCR (Podman) run: | echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin - name: Build all Linux distros (Prefect + Dagger) run: poetry run python -m ci.prefect_flow build-linux - name: Import GPG key run: | echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import - name: Sign Linux artifacts run: | poetry run python -m ci.prefect_flow sign \ --gpg-passphrase "${{ secrets.GPG_PASSPHRASE }}" - name: Upload Debian package uses: actions/upload-artifact@v4 with: name: debian-package path: | dist/android-file-handler_*.deb dist/android-file-handler_*.deb.asc dist/android-file-handler-debian.sha256 pkg_dist_debian/** - name: Upload Arch package uses: actions/upload-artifact@v4 with: name: arch-package path: | dist/*.pkg.tar.* dist/android-file-handler-arch.sha256 pkg_dist_arch/** - name: Upload RHEL package uses: actions/upload-artifact@v4 with: name: rhel-package path: | dist/*.rpm dist/*.rpm.asc dist/android-file-handler-rhel.sha256 pkg_dist_rhel/** # ── Release + R2 Upload (Prefect) ─────────────────────────────────── do-release: needs: [build-windows, build-linux] runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout code uses: actions/checkout@v4 with: ref: main - name: Set up Python 3.13 uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install Poetry uses: snok/install-poetry@v1 with: version: latest virtualenvs-create: true virtualenvs-in-project: true - name: Install project + CI dependencies run: poetry install --with ci - name: Download all artifacts uses: actions/download-artifact@v4 with: merge-multiple: true path: ./dist - name: Create GitHub release (Prefect) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | poetry run python -m ci.prefect_flow release \ --github-token "$GITHUB_TOKEN" upload-r2: runs-on: ubuntu-latest needs: do-release if: needs.do-release.result == 'success' permissions: contents: read id-token: write steps: - name: Checkout code uses: actions/checkout@v4 with: ref: main - name: Set up Python 3.13 uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install Poetry uses: snok/install-poetry@v1 with: version: latest virtualenvs-create: true virtualenvs-in-project: true - name: Install project + CI dependencies run: poetry install --with ci - name: Download build artifacts uses: actions/download-artifact@v4 with: merge-multiple: true path: ./release-files - name: Authenticate to GCP uses: google-github-actions/auth@v2 with: workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - name: Upload artifacts to Cloudflare R2 run: | poetry run python -m ci.prefect_flow upload-r2 \ --gcp-project-id "${{ secrets.GCP_PROJECT_ID }}" \ --run-id "${{ github.run_id }}" \ --release-dir "./release-files" sync-wiki: needs: do-release if: needs.do-release.result == 'success' permissions: contents: write pull-requests: write uses: ./.github/workflows/sync-wiki.yml with: branch: main secrets: inherit