Repository navigation
Merge pull request #599 from appdevforall/feat/K2GO-431-v1.0.0-release #34
Workflow file for this run
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: Build and Release K2Go APKs | |
| on: | |
| push: | |
| # Only triggers automatically when a release tag is pushed (e.g., v1.0.0) | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| jobs: | |
| build-and-release: | |
| name: Build Release APKs & Upload to R2 | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: ./controller | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| # TODO (Future): Add a Jira step here to verify if the version exists in Jira | |
| # or to extract release notes directly from a Jira board. | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: 'zulu' | |
| java-version: '17' | |
| cache: 'gradle' | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| # ADFA-4466: the google-services plugin needs google-services.json at build time; | |
| # inject it from the dedicated K2Go analytics secret so the build compiles. | |
| - name: Create google-services.json | |
| env: | |
| GOOGLE_SERVICES_JSON_K2GO_ANALYTICS: ${{ secrets.GOOGLE_SERVICES_JSON_K2GO_ANALYTICS }} | |
| run: echo "$GOOGLE_SERVICES_JSON_K2GO_ANALYTICS" > app/google-services.json | |
| - name: Decode Keystore | |
| env: | |
| ENCODED_STRING: ${{ secrets.KEYSTORE_BASE64 }} | |
| run: | | |
| echo "$ENCODED_STRING" | base64 -d > keystore.jks | |
| # --- VERSION AUDIT --- | |
| - name: Log Pinned Binary Version | |
| run: | | |
| echo "==========================================" | |
| echo "Compiling with native binaries pinned to:" | |
| cat binary_version.txt | |
| echo "" | |
| echo "==========================================" | |
| # --- BUILD AND SIGNING --- | |
| - name: Build and Sign Release APKs | |
| env: | |
| # :app:syncNativeArtifacts queries the GitHub API for the pinned binaries | |
| # release; unauthenticated it gets HTTP 403 (shared-runner rate limit), so | |
| # provide the auto token for the M15 authenticated-retry fallback (build.gradle). | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.KEY_ALIAS }} | |
| KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} | |
| # ADFA-4533: GlitchTip DSN for release builds | |
| SENTRY_DSN_RELEASE: ${{ secrets.SENTRY_DSN_RELEASE }} | |
| run: | | |
| # Release tags ship a clean version (v1.0.0), no commit hash. build.gradle only | |
| # falls back to the git SHA suffix when versionSuffix is absent; pass it empty here. | |
| # In-APK traceability is unaffected: BuildConfig.GIT_SHA still records the commit. | |
| # K2GO-431: build both flavors. standard -> GitHub + R2 + OTA update.json (as before); | |
| # fdroid -> R2 only (F-Droid distributes it; it carries no in-app OTA). | |
| ./gradlew assembleStandardRelease assembleFdroidRelease \ | |
| -PversionSuffix="" \ | |
| -Pandroid.injected.signing.store.file=$(pwd)/keystore.jks \ | |
| -Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD \ | |
| -Pandroid.injected.signing.key.alias=$KEY_ALIAS \ | |
| -Pandroid.injected.signing.key.password=$KEY_PASSWORD | |
| - name: Upload APK Artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: k2go-apks-release | |
| path: controller/**/build/outputs/apk/*/release/*.apk | |
| retention-days: 7 | |
| # --- GITHUB RELEASES --- | |
| - name: Create GitHub Release | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| # Automatically marks as Pre-release if the tag ends in "-beta" or "-rc" | |
| prerelease: ${{ contains(github.ref, '-beta') || contains(github.ref, '-rc') }} | |
| files: controller/**/build/outputs/apk/standard/release/*.apk | |
| generate_release_notes: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # --- CLOUDFLARE R2 UPLOAD --- | |
| - name: Upload to Cloudflare R2 | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ vars.CLOUDFLARE_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: auto | |
| R2_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | |
| BUCKET_NAME: "k2go-apk-repo" | |
| run: | | |
| # K2GO-431: upload BOTH flavors (standard + fdroid) to R2. The flavor dir sits between | |
| # apk/ and release/ (apk/standard/release, apk/fdroid/release); the old apk/release path | |
| # never matched a flavored build. | |
| for apk in $(find . -path "*/build/outputs/apk/*/release/*.apk"); do | |
| filename=$(basename "$apk") | |
| echo "Uploading $filename to Cloudflare R2 ($BUCKET_NAME)..." | |
| # Use aws-cli configured to point to Cloudflare's S3-compatible API | |
| aws s3 cp "$apk" "s3://$BUCKET_NAME/$filename" \ | |
| --endpoint-url "https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com" \ | |
| --content-type "application/vnd.android.package-archive" | |
| done | |
| # --- OTA MANIFEST (update.json) --- | |
| # ADFA-4984: publish the OTA manifest LAST, after the APKs are already in R2, so it never | |
| # points at a missing binary. update.json is a fixed key -> it overwrites the previous manifest | |
| # and always resolves to the latest release (no .1/.2 copies). The changelog is copied verbatim | |
| # from the top entry of controller/ci/ota-release-notes.md, which must match the release tag. | |
| - name: Generate and upload update.json | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| working-directory: ${{ github.workspace }} | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ vars.CLOUDFLARE_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: auto | |
| R2_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | |
| BUCKET_NAME: "k2go-apk-repo" | |
| run: | | |
| set -euo pipefail | |
| TAG="${GITHUB_REF#refs/tags/}" | |
| echo "Release tag: $TAG" | |
| # versionCodeBase = the app module's raw versionCode (the app divides the installed, | |
| # ABI-multiplied code by 10 to compare). First versionCode in controller/app/build.gradle. | |
| VCODE=$(grep -oE 'versionCode[[:space:]]+[0-9]+' controller/app/build.gradle | head -1 | grep -oE '[0-9]+') | |
| echo "versionCodeBase: $VCODE" | |
| # Top entry of controller/ci/ota-release-notes.md; its header must equal the tag (guard against stale notes). | |
| HEADER=$(grep -m1 '^## ' controller/ci/ota-release-notes.md | sed 's/^##[[:space:]]*//') | |
| if [ "$HEADER" != "$TAG" ]; then | |
| echo "::error::controller/ci/ota-release-notes.md top entry '$HEADER' does not match tag '$TAG'. Update the notes before tagging." | |
| exit 1 | |
| fi | |
| # Body = the lines between the first '## ' header and the next one (blank lines trimmed). | |
| CHANGELOG=$(awk '/^## /{n++; next} n==1{print}' controller/ci/ota-release-notes.md | sed '/^[[:space:]]*$/d') | |
| echo "Changelog:"; printf '%s\n' "$CHANGELOG" | |
| # Built APK basenames per ABI (the binaries just uploaded to R2). | |
| # OTA update.json covers the standard flavor only (fdroid ships with OTA disabled). | |
| find_apk() { find controller -path "*/build/outputs/apk/standard/release/*$1*.apk" -printf '%f\n' | head -1; } | |
| # The APK filenames abbreviate the split's ABI (arm64-v8a -> v8a); see build.gradle. | |
| APK_ARM64=$(find_apk "-v8a-") | |
| APK_ARM32=$(find_apk "-v7a-") | |
| APK_UNIVERSAL=$(find_apk "-universal-") | |
| echo "arm64=$APK_ARM64 | arm32=$APK_ARM32 | universal=$APK_UNIVERSAL" | |
| if [ -z "$APK_ARM64" ] || [ -z "$APK_ARM32" ] || [ -z "$APK_UNIVERSAL" ]; then | |
| echo "::error::Missing one or more built APKs (arm64='$APK_ARM64' arm32='$APK_ARM32' universal='$APK_UNIVERSAL'); aborting instead of publishing an incomplete manifest." | |
| exit 1 | |
| fi | |
| # jq escapes the multiline changelog safely into a JSON string. | |
| jq -n \ | |
| --argjson vcode "$VCODE" \ | |
| --arg vname "$TAG" \ | |
| --arg changelog "$CHANGELOG" \ | |
| --arg arm64 "$APK_ARM64" \ | |
| --arg arm32 "$APK_ARM32" \ | |
| --arg universal "$APK_UNIVERSAL" \ | |
| '{versionCodeBase: $vcode, versionName: $vname, changelog: $changelog, | |
| apk_arm64_v8a: $arm64, apk_armeabi_v7a: $arm32, apk_universal: $universal}' \ | |
| > update.json | |
| echo "----- update.json -----"; cat update.json | |
| # Upload LAST (after the APKs). Fixed key -> overwrites the previous manifest. | |
| aws s3 cp update.json "s3://$BUCKET_NAME/update.json" \ | |
| --endpoint-url "https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com" \ | |
| --content-type "application/json" | |
| # TODO (Future): Add Jira finalization step here to automatically mark | |
| # the Jira version as "Released" and close the corresponding tickets. | |
| # TODO (Future): Add a Slack notification step to announce the official release | |
| # with links to the GitHub Release page and direct Cloudflare downloads. | |
| # --- SECURITY CLEANUP --- | |
| - name: Cleanup Keystore | |
| if: always() | |
| run: rm -f keystore.jks |