From 7e171862a5b0b635d2b02f39e589687a5c9163b8 Mon Sep 17 00:00:00 2001 From: randogoth Date: Wed, 14 Oct 2026 10:54:00 +0300 Subject: [PATCH] feat: build and sign Android releases on tag push via Forgejo Reads the release keystore from env vars in CI, falling back to the debug keys locally. Adds fastlane metadata for F-Droid/Obtainium and a .forgejo workflow that builds, signs, and publishes the APK on pushing a vX.Y.Z+N tag. --- .forgejo/workflows/release.yml | 62 ++++++++++++++++++ app/android/app/build.gradle.kts | 19 ++++-- app/fastlane/Fastfile | 1 + .../metadata/android/en-US/changelogs/1.txt | 1 + .../android/en-US/full_description.txt | 7 ++ .../metadata/android/en-US/images/icon.png | Bin 0 -> 7429 bytes .../android/en-US/short_description.txt | 1 + app/fastlane/metadata/android/en-US/title.txt | 1 + 8 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 .forgejo/workflows/release.yml create mode 100644 app/fastlane/Fastfile create mode 100644 app/fastlane/metadata/android/en-US/changelogs/1.txt create mode 100644 app/fastlane/metadata/android/en-US/full_description.txt create mode 100644 app/fastlane/metadata/android/en-US/images/icon.png create mode 100644 app/fastlane/metadata/android/en-US/short_description.txt create mode 100644 app/fastlane/metadata/android/en-US/title.txt diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 0000000..3e49e0f --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,62 @@ +name: Release APK + +on: + push: + tags: ["v*"] + +jobs: + build: + runs-on: android + + steps: + - uses: actions/checkout@v4 + + - name: Extract version + run: | + VERSION=$(grep '^version:' app/pubspec.yaml | sed 's/version: //') + echo "VERSION_CODE=$(echo "$VERSION" | grep -oP '(?<=\+)\d+')" >> $GITHUB_ENV + echo "VERSION_NAME=$(echo "$VERSION" | grep -oP '^[^+]+')" >> $GITHUB_ENV + + - name: Decode keystore + run: | + echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/android/app/release.jks + echo "KEYSTORE_PATH=$GITHUB_WORKSPACE/app/android/app/release.jks" >> $GITHUB_ENV + + - name: Build release APK + env: + STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + # Host-mode runners share ~/.gradle (including the daemon) across + # unrelated projects; a warm daemon from another build can poison + # this one. Keep caches shared, just don't attach to a daemon. + GRADLE_OPTS: -Dorg.gradle.daemon=false + run: | + cd app + env -u GITHUB_TOKEN -u GH_TOKEN -u JAVA_HOME \ + devbox run -- flutter build apk --release + + - name: Create release and upload APK + env: + FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + APK="mews-${{ env.VERSION_NAME }}.apk" + cp app/build/app/outputs/flutter-apk/app-release.apk "$APK" + + BODY=$(python3 -c " + import json + print(json.dumps(open('app/fastlane/metadata/android/en-US/changelogs/${{ env.VERSION_CODE }}.txt').read().strip())) + ") + + RELEASE_ID=$(curl -sf -X POST \ + -H "Authorization: token $FORGEJO_TOKEN" \ + -H "Content-Type: application/json" \ + "$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases" \ + -d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\",\"body\":$BODY}" \ + | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])') + + curl -sf -X POST \ + -H "Authorization: token $FORGEJO_TOKEN" \ + -H "Content-Type: application/vnd.android.package-archive" \ + "$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=$APK" \ + --data-binary @"$APK" diff --git a/app/android/app/build.gradle.kts b/app/android/app/build.gradle.kts index 8674251..9094354 100644 --- a/app/android/app/build.gradle.kts +++ b/app/android/app/build.gradle.kts @@ -23,21 +23,28 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "page.mews.mews" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } + signingConfigs { + create("release") { + storeFile = System.getenv("KEYSTORE_PATH")?.let { file(it) } + storePassword = System.getenv("STORE_PASSWORD") + keyAlias = System.getenv("KEY_ALIAS") + keyPassword = System.getenv("KEY_PASSWORD") + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") + // Falls back to the debug keys locally, so `flutter run --release` + // keeps working without a keystore on hand. + signingConfig = if (System.getenv("KEYSTORE_PATH") != null) + signingConfigs.getByName("release") else signingConfigs.getByName("debug") } } } diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile new file mode 100644 index 0000000..87707fa --- /dev/null +++ b/app/fastlane/Fastfile @@ -0,0 +1 @@ +default_platform(:android) diff --git a/app/fastlane/metadata/android/en-US/changelogs/1.txt b/app/fastlane/metadata/android/en-US/changelogs/1.txt new file mode 100644 index 0000000..0b524c9 --- /dev/null +++ b/app/fastlane/metadata/android/en-US/changelogs/1.txt @@ -0,0 +1 @@ +First release. diff --git a/app/fastlane/metadata/android/en-US/full_description.txt b/app/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 0000000..3c6eea6 --- /dev/null +++ b/app/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,7 @@ +The Mews Browser is a reader for Mews pages, Gemini capsules, and Spartan +sites. It's local-first: pages, bookmarks, subscriptions, and reading +settings live in a local database, and network access only revalidates the +cache. + +The reader controls presentation, not the page author, so every site reads +consistently in the typeface, size, and color you choose. diff --git a/app/fastlane/metadata/android/en-US/images/icon.png b/app/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dcddcd8c0aaaaf06872ac1ff7b93059b4a929bd7 GIT binary patch literal 7429 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 literal 0 HcmV?d00001 diff --git a/app/fastlane/metadata/android/en-US/short_description.txt b/app/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 0000000..4646e2b --- /dev/null +++ b/app/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +A local-first reader for Mews pages, Gemini capsules, and Spartan sites. diff --git a/app/fastlane/metadata/android/en-US/title.txt b/app/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 0000000..fc0ecb6 --- /dev/null +++ b/app/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +The Mews Browser