Update workflow to tag and create GitHub release #18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Reproducible Build Check (master) | |
| on: | |
| push: | |
| branches: [ "master" ] | |
| jobs: | |
| build-1: | |
| name: Build 1 (signed) | |
| runs-on: ubuntu-latest | |
| environment: production | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Decode keystore | |
| run: | | |
| echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > ${{ github.workspace }}/release.keystore | |
| if [ ! -s ${{ github.workspace }}/release.keystore ]; then | |
| echo "Keystore file is empty — check the KEYSTORE_BASE64 secret." | |
| exit 1 | |
| fi | |
| ls -lh ${{ github.workspace }}/release.keystore | |
| echo "Keystore decoded successfully." | |
| - name: Build Docker image | |
| working-directory: ci | |
| run: docker build -t deku_rep_build_release . | |
| - name: Build unsigned APK | |
| run: | | |
| docker run --rm \ | |
| -v "$(pwd)":/project \ | |
| -w /project \ | |
| --user "$(id -u):$(id -g)" \ | |
| -e ANDROID_USER_HOME=/project/.android \ | |
| -e GRADLE_USER_HOME=/project/.gradle \ | |
| deku_rep_build_release \ | |
| ./gradlew assembleRelease \ | |
| --no-daemon \ | |
| --max-workers=2 \ | |
| --console=plain \ | |
| -Dorg.gradle.jvmargs="-Xmx2048m -Xms512m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8" \ | |
| -Dkotlin.daemon.jvm.options="-Xmx512m,-Xss1m" \ | |
| -Dkotlin.compiler.execution.strategy=in-process | |
| - name: Sign APK with apksigner | |
| run: | | |
| BUILD_TOOLS=$(ls $ANDROID_HOME/build-tools | sort -V | tail -1) | |
| $ANDROID_HOME/build-tools/$BUILD_TOOLS/apksigner sign \ | |
| --ks ${{ github.workspace }}/release.keystore \ | |
| --ks-type JKS \ | |
| --ks-key-alias "${{ secrets.KEY_ALIAS }}" \ | |
| --ks-pass pass:"${{ secrets.STORE_PASSWORD }}" \ | |
| --key-pass pass:"${{ secrets.KEY_PASSWORD }}" \ | |
| --out app/build/outputs/apk/release/app-release-signed.apk \ | |
| app/build/outputs/apk/release/app-release-unsigned.apk | |
| - name: Verify signature | |
| run: | | |
| BUILD_TOOLS=$(ls $ANDROID_HOME/build-tools | sort -V | tail -1) | |
| $ANDROID_HOME/build-tools/$BUILD_TOOLS/apksigner verify \ | |
| --verbose \ | |
| app/build/outputs/apk/release/app-release-signed.apk | |
| - name: Upload signed APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apk-build-1 | |
| path: app/build/outputs/apk/release/app-release-signed.apk | |
| retention-days: 1 | |
| build-2: | |
| name: Build 2 (signed) | |
| runs-on: ubuntu-latest | |
| environment: production | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Decode keystore | |
| run: | | |
| echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > ${{ github.workspace }}/release.keystore | |
| if [ ! -s ${{ github.workspace }}/release.keystore ]; then | |
| echo "Keystore file is empty — check the KEYSTORE_BASE64 secret." | |
| exit 1 | |
| fi | |
| ls -lh ${{ github.workspace }}/release.keystore | |
| echo "Keystore decoded successfully." | |
| - name: Build Docker image | |
| working-directory: ci | |
| run: docker build -t deku_rep_build_release . | |
| - name: Build unsigned APK | |
| run: | | |
| docker run --rm \ | |
| -v "$(pwd)":/project \ | |
| -w /project \ | |
| --user "$(id -u):$(id -g)" \ | |
| -e ANDROID_USER_HOME=/project/.android \ | |
| -e GRADLE_USER_HOME=/project/.gradle \ | |
| deku_rep_build_release \ | |
| ./gradlew assembleRelease \ | |
| --no-daemon \ | |
| --max-workers=2 \ | |
| --console=plain \ | |
| -Dorg.gradle.jvmargs="-Xmx2048m -Xms512m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8" \ | |
| -Dkotlin.daemon.jvm.options="-Xmx512m,-Xss1m" \ | |
| -Dkotlin.compiler.execution.strategy=in-process | |
| - name: Sign APK with apksigner | |
| run: | | |
| BUILD_TOOLS=$(ls $ANDROID_HOME/build-tools | sort -V | tail -1) | |
| $ANDROID_HOME/build-tools/$BUILD_TOOLS/apksigner sign \ | |
| --ks ${{ github.workspace }}/release.keystore \ | |
| --ks-type JKS \ | |
| --ks-key-alias "${{ secrets.KEY_ALIAS }}" \ | |
| --ks-pass pass:"${{ secrets.STORE_PASSWORD }}" \ | |
| --key-pass pass:"${{ secrets.KEY_PASSWORD }}" \ | |
| --out app/build/outputs/apk/release/app-release-signed.apk \ | |
| app/build/outputs/apk/release/app-release-unsigned.apk | |
| - name: Verify signature | |
| run: | | |
| BUILD_TOOLS=$(ls $ANDROID_HOME/build-tools | sort -V | tail -1) | |
| $ANDROID_HOME/build-tools/$BUILD_TOOLS/apksigner verify \ | |
| --verbose \ | |
| app/build/outputs/apk/release/app-release-signed.apk | |
| - name: Upload signed APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apk-build-2 | |
| path: app/build/outputs/apk/release/app-release-signed.apk | |
| retention-days: 1 | |
| compare: | |
| name: Compare signed APKs | |
| runs-on: ubuntu-latest | |
| needs: [ build-1, build-2 ] | |
| steps: | |
| - name: Download APK from build 1 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: apk-build-1 | |
| path: apk-build-1 | |
| - name: Download APK from build 2 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: apk-build-2 | |
| path: apk-build-2 | |
| - name: Compare hashes | |
| id: compare | |
| run: | | |
| SHA1=$(sha256sum apk-build-1/app-release-signed.apk | awk '{ print $1 }') | |
| SHA2=$(sha256sum apk-build-2/app-release-signed.apk | awk '{ print $1 }') | |
| echo "Build 1: $SHA1" | |
| echo "Build 2: $SHA2" | |
| if [ "$SHA1" = "$SHA2" ]; then | |
| echo "Reproducible build verified — hashes match." | |
| echo "reproducible=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Build is NOT reproducible — hashes differ!" | |
| echo "reproducible=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Install diffoscope | |
| if: steps.compare.outputs.reproducible == 'false' | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y diffoscope | |
| - name: Run diffoscope | |
| if: steps.compare.outputs.reproducible == 'false' | |
| run: | | |
| diffoscope \ | |
| --text diffoscope-report.txt \ | |
| --html diffoscope-report.html \ | |
| apk-build-1/app-release-signed.apk \ | |
| apk-build-2/app-release-signed.apk || true | |
| - name: Upload diffoscope report | |
| if: steps.compare.outputs.reproducible == 'false' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: diffoscope-report | |
| path: | | |
| diffoscope-report.txt | |
| diffoscope-report.html | |
| retention-days: 7 | |
| - name: Fail if not reproducible | |
| if: steps.compare.outputs.reproducible == 'false' | |
| run: | | |
| echo "See the diffoscope-report artifact for a full breakdown of differences." | |
| exit 1 | |
| tag-and-release: | |
| name: Tag and create GitHub release | |
| runs-on: ubuntu-latest | |
| needs: [ compare ] | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.SYNC_TOKEN }} | |
| - name: Read version.properties | |
| id: version | |
| run: | | |
| if [ ! -f version.properties ]; then | |
| echo "ERROR: version.properties not found" | |
| exit 1 | |
| fi | |
| VERSION_NAME=$(grep "^versionName=" version.properties | cut -d'=' -f2) | |
| if [ -z "$VERSION_NAME" ]; then | |
| echo "ERROR: versionName missing from version.properties" | |
| exit 1 | |
| fi | |
| echo "version_name=$VERSION_NAME" >> "$GITHUB_OUTPUT" | |
| echo "versionName=$VERSION_NAME" | |
| - name: Compute next tag | |
| id: next_tag | |
| run: | | |
| LATEST=$(git tag --sort=-v:refname | head -1) | |
| if [ -z "$LATEST" ]; then | |
| LATEST="0" | |
| fi | |
| echo "Latest tag: $LATEST" | |
| NEW_TAG=$((LATEST + 1)) | |
| echo "New tag: $NEW_TAG" | |
| echo "new_tag=$NEW_TAG" >> "$GITHUB_OUTPUT" | |
| - name: Create and push tag | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag "${{ steps.next_tag.outputs.new_tag }}" -m "Release ${{ steps.version.outputs.version_name }} — reproducible build verified" | |
| git push origin "${{ steps.next_tag.outputs.new_tag }}" | |
| echo "Tagged ${{ steps.next_tag.outputs.new_tag }} and pushed." | |
| - name: Download signed APK from build-1 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: apk-build-1 | |
| path: apk-release | |
| - name: Rename APK with version name | |
| run: | | |
| mv apk-release/app-release-signed.apk \ | |
| "apk-release/${{ steps.version.outputs.version_name }}.apk" | |
| - name: Create GitHub release | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_LIB_SYNC_TOKEN }} | |
| run: | | |
| gh release create "${{ steps.next_tag.outputs.new_tag }}" \ | |
| "apk-release/${{ steps.version.outputs.version_name }}.apk" \ | |
| --title "${{ steps.version.outputs.version_name }}" \ | |
| --notes "Reproducible build verified | |
| SHA256: $(sha256sum "apk-release/${{ steps.version.outputs.version_name }}.apk" | awk '{print $1}')" \ | |
| --draft |