name: Build Android Apk # Only a release built through "Releases -> New Release" starts a build. A tag # pushed with plain git does not publish a release and is therefore ignored. on: release: types: - published env: # Keep in sync with the other workflows and with `ac_web/Dockerfile`. FLUTTER_VERSION: '3.44.9' # The project produces java 17 bytecode (`android/app/build.gradle.kts`), but # gradle 9.1 and the android gradle plugin 9 need a newer jdk to run on. JAVA_VERSION: '21' jobs: android: runs-on: ubuntu-latest container: image: catthehacker/ubuntu:act-latest # The flutter project lives in a sub directory of the repository. defaults: run: working-directory: ac_web steps: - name: Checkout uses: https://github.com/actions/checkout@v4 # The flutter tool runs git inside the workspace and inside its own sdk, # and git refuses to work in directories it considers to belong to # somebody else. - name: Trust the workspace run: git config --global --add safe.directory '*' # Everything the flutter download below needs. - name: Install the download tools run: | apt-get update apt-get install -y --no-install-recommends ca-certificates curl xz-utils jq - name: Set up Java uses: https://github.com/actions/setup-java@v4 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} # Installs the command line tools and accepts the sdk licenses. The # platform, the build tools and the ndk pinned by the flutter sdk are # then downloaded by gradle itself, which is what makes this job slow on # a cold runner. - name: Set up the Android sdk uses: https://github.com/android-actions/setup-android@v3 # `subosito/flutter-action` cannot be used: it caches the sdk through # `actions/cache@v5`, and the act-runner of this gitea instance only # supports actions up to node20 ("runs.using ... got node24"). Unpacking # the official archive does the same thing and pins the version exactly, # just like `ac_web/Dockerfile` does. - name: Set up Flutter run: | curl -fsSL "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz" \ | tar -xJ -C /opt echo "/opt/flutter/bin" >> "$GITHUB_PATH" - name: Show the flutter version run: flutter --version - name: Install dependencies run: flutter pub get - name: Run tests run: flutter test # Android accepts an update only when it is signed with the same key as # the installed app, so every release has to carry one key that never # changes. The debug key cannot be used: the runner generates a fresh one # on every run, which would make each apk a different app to android and # break every update through Obtainium. # # `android/app/build.gradle.kts` picks the key up from # `android/key.properties`, which is written here from the repository # secrets (Settings -> Actions -> Secrets). See CLAUDE.md for the # `keytool` command that creates the keystore. - name: Write the release signing key env: KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | # The act-runner executes `run` with sh (dash), not bash, so this # script has to stay posix - `set -o pipefail` for example dies # with "Illegal option -o pipefail" before anything else runs. set -eu # Failing here is on purpose: a silently debug signed apk would be # installable once and never updatable afterwards. if [ -z "${KEYSTORE_BASE64:-}" ]; then echo "::error::The secret ANDROID_KEYSTORE_BASE64 is empty. Add the keystore secrets (see CLAUDE.md) before publishing a release." exit 1 fi printf '%s' "$KEYSTORE_BASE64" | base64 -d > android/app/release-keystore.jks # storeFile is relative to `android/app`. The file is gitignored. cat > android/key.properties </api/v1/repos///releases` and # downloads the *assets* of a release, so the apk has to hang off the # release itself. A workflow artifact is invisible to it: it lives under # the run, is zipped and needs a login. - name: Attach the apk to the release env: # The token gitea creates for the run. If the instance hands out a # read only token, put a personal access token with repository write # access in a secret and use that instead. TOKEN: ${{ github.token }} run: | set -eu api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" asset="ac_web-${{ github.ref_name }}.apk" # The release event carries the id; the lookup by tag is only there # for a manual re-run of the job. releaseId="${{ github.event.release.id }}" if [ -z "$releaseId" ] || [ "$releaseId" = "null" ]; then release=$(curl -fsSL -H "Authorization: token $TOKEN" \ "$api/releases/tags/${{ github.ref_name }}") releaseId=$(printf '%s' "$release" | jq -r '.id') fi curl -fsSL -X POST \ -H "Authorization: token $TOKEN" \ -F "attachment=@$asset" \ "$api/releases/$releaseId/assets?name=$asset" > /dev/null echo "Attached $asset to release $releaseId." # @v3 on purpose: `upload-artifact@v4` bundles `@actions/artifact` v2, # which refuses to run as soon as `GITHUB_SERVER_URL` is not github.com # ("GHESNotSupportedError") -- and on this instance it is the gitea host. # The check happens in the action itself, before any request, so it # cannot be fixed on the gitea side. @v3 talks to the older artifact api # gitea implements and still runs on node20, which the act-runner allows. - name: Upload the apk uses: https://github.com/actions/upload-artifact@v3 with: name: ac_web-android-${{ github.ref_name }} # Artifact paths are relative to the repository, not to the # working-directory above. path: ac_web/ac_web-${{ github.ref_name }}.apk if-no-files-found: error