diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 12643c6..e779d52 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -1,19 +1,25 @@ # Release automation for git.comunes.org (Forgejo Actions). # -# Password-free: pushing a tag `v*` builds a signed AAB + per-ABI APKs, uploads -# the AAB to Google Play's internal track via fastlane, and attaches the signed -# per-ABI APKs to the Forgejo release (the reference binaries F-Droid verifies -# for reproducible, developer-signed publishing). All credentials come from repo -# secrets (Settings > Actions > Secrets), never typed: +# Pushing a tag `v*` runs two independent jobs: +# play - build the signed AAB and ship it to Google Play (internal). +# fdroid_reference- build the per-ABI reference APKs the SAME way F-Droid will +# (fdroid build inside the fdroidserver buildserver image), +# sign them with the tane-upload key, and attach them to the +# Forgejo release. F-Droid re-runs `fdroid build` on the same +# commit+recipe+image, gets byte-identical output, verifies our +# signature (AllowedAPKSigningKeys) and publishes OUR APK — so +# F-Droid and Play share the same signing key. +# +# All credentials come from repo secrets (Settings > Actions > Secrets): # TANE_KEYSTORE_BASE64 base64 of the dedicated tane-upload.jks # TANE_KEYSTORE_PASSWORD store password # TANE_KEY_ALIAS tane-upload # TANE_KEY_PASSWORD key password -# SUPPLY_JSON_KEY_DATA Google Play service-account JSON (reused from Ğ1nkgo) +# SUPPLY_JSON_KEY_DATA Google Play service-account JSON # -# Build + deploy live in ONE job so the AAB never has to cross a job boundary. -# NOTE: `runs-on` must match a label your Forgejo runner registered with; adjust -# if the Comunes runner uses something other than `docker`. +# NOTE: `runs-on` must match a label your Forgejo runner registered with. +# The fdroid_reference job pulls a large image (~2 GB) and builds tesseract from +# source, so it is slow (~20-40 min); the play job is independent and unaffected. name: release @@ -23,7 +29,7 @@ on: - 'v*' jobs: - android: + play: runs-on: docker container: image: ghcr.io/cirruslabs/flutter:3.41.9 @@ -47,21 +53,6 @@ jobs: dart run slang dart run build_runner build --delete-conflicting-outputs - - name: Work around dead jcenter (route to mavenCentral) - run: | - # jcenter (bintray) is shut down; old transitive deps (e.g. flutter_tesseract_ocr - # -> AGP 7.1.2 -> apkzlib -> jsr305:1.3.9) still point there and fail to resolve. - # Inject mavenCentral/google globally so the SAME artifacts resolve from a live - # host — identical jars, so build bytes (and F-Droid reproducibility) are unchanged. - mkdir -p "$HOME/.gradle" - cat > "$HOME/.gradle/init.gradle" <<'EOF' - allprojects { - buildscript { repositories { google(); mavenCentral() } } - repositories { google(); mavenCentral() } - } - settingsEvaluated { s -> s.pluginManagement { repositories { google(); mavenCentral(); gradlePluginPortal() } } } - EOF - - name: Materialize signing material from secrets working-directory: apps/app_seeds env: @@ -78,54 +69,9 @@ jobs: keyPassword=$TANE_KEY_PASSWORD EOF - - name: Build tesseract4android from source (match F-Droid for reproducibility) + - name: Build signed AAB working-directory: apps/app_seeds - run: | - set -eu - # F-Droid forbids the prebuilt AAR that ships in flutter_tesseract_ocr, so - # its recipe rebuilds tesseract4android@4.9.0 from source and swaps the AAR - # into the pub cache. We do the SAME here so our published reference APK is - # byte-for-byte reproducible against F-Droid's build (keeps AllowedAPKSigningKeys - # + binary: valid — see metadata/org.comunes.tane.yml in fdroiddata). - t4a="$GITHUB_WORKSPACE/tesseract4android-src" - git clone --depth 1 -b 4.9.0 https://github.com/adaptech-cz/Tesseract4Android.git "$t4a" - # Install the exact NDK the project pins, if the image lacks it. - ndkv=$(grep -RhoE 'ndkVersion[^0-9]*[0-9][0-9.]+' "$t4a" | grep -oE '[0-9]+\.[0-9.]+' | head -1 || true) - [ -n "${ndkv:-}" ] && (yes | sdkmanager "ndk;$ndkv" >/dev/null 2>&1 || true) - # gradlew lives at the repo root, task belongs to the :tesseract4android module. - ( cd "$t4a" && ./gradlew --no-daemon :tesseract4android:assembleStandardRelease ) - aar_dst=$(find ${PUB_CACHE:+$PUB_CACHE} "$HOME/.pub-cache" /root/.pub-cache /sdks 2>/dev/null \ - -path '*flutter_tesseract_ocr-0.4.31/android/libs/tesseract4android-release.aar' | head -1) - cp "$t4a/tesseract4android/build/outputs/aar/tesseract4android-standard-release.aar" "$aar_dst" - echo "swapped source-built tesseract AAR -> $aar_dst" - - - name: Build signed AAB + per-ABI APKs - working-directory: apps/app_seeds - run: | - flutter build appbundle --release - # Per-ABI splits: these are the reference binaries F-Droid rebuilds and - # verifies against (see docs/fdroid/org.comunes.tane.yml, binary:). - flutter build apk --release --split-per-abi - - - name: Publish signed per-ABI APKs to the Forgejo release - working-directory: apps/app_seeds - env: - TOKEN: ${{ github.token }} - run: | - apt-get update -qq && apt-get install -y -qq curl jq - api="http://forgejo:3000/api/v1/repos/${GITHUB_REPOSITORY}" - tag="${GITHUB_REF_NAME}" - # Create the release for this tag if it does not exist yet, then get its id. - curl -sf -X POST "$api/releases" \ - -H "Authorization: token ${TOKEN}" -H 'Content-Type: application/json' \ - -d "{\"tag_name\":\"${tag}\",\"name\":\"${tag}\"}" >/dev/null || true - rid=$(curl -sf -H "Authorization: token ${TOKEN}" "$api/releases/tags/${tag}" | jq -r .id) - for abi in armeabi-v7a arm64-v8a x86_64; do - f="build/app/outputs/flutter-apk/app-${abi}-release.apk" - curl -sf -X POST "$api/releases/${rid}/assets?name=app-${abi}-release.apk" \ - -H "Authorization: token ${TOKEN}" \ - -F "attachment=@${f};type=application/vnd.android.package-archive" - done + run: flutter build appbundle --release - name: Install fastlane working-directory: apps/app_seeds @@ -145,3 +91,86 @@ jobs: if: always() working-directory: apps/app_seeds run: rm -f android/key.properties "$GITHUB_WORKSPACE/tane-upload.jks" + + fdroid_reference: + runs-on: docker + container: + image: registry.gitlab.com/fdroid/fdroidserver:buildserver-trixie + steps: + - name: Build reference APKs with fdroid, sign, and attach to the release + env: + TOKEN: ${{ github.token }} + TANE_KEYSTORE_BASE64: ${{ secrets.TANE_KEYSTORE_BASE64 }} + TANE_KEYSTORE_PASSWORD: ${{ secrets.TANE_KEYSTORE_PASSWORD }} + TANE_KEY_ALIAS: ${{ secrets.TANE_KEY_ALIAS }} + TANE_KEY_PASSWORD: ${{ secrets.TANE_KEY_PASSWORD }} + run: | + set -eu + + # 1) Reproduce F-Droid's build environment (ANDROID_HOME, PATH, NDK...). + sh /opt/buildserver/setup-env-vars /opt/android-sdk + . /etc/profile.d/bsenv.sh + + # 2) fdroidserver from master, exactly like the fdroiddata CI. + fds=/opt/fdroidserver-src + mkdir -p "$fds" + curl -sL https://gitlab.com/fdroid/fdroidserver/-/archive/master/fdroidserver-master.tar.gz \ + | tar -xz -C "$fds" --strip-components=1 + export PATH="$fds:$PATH" + export PYTHONPATH="$fds:$fds/examples" + git config --global --add safe.directory '*' + + # 3) Fetch our in-repo recipe (the single source of truth) at this commit, + # plus the pubspec versionCode base for the per-ABI codes. + work=/tmp/tane-src + mkdir -p "$work" && cd "$work" + git init -q . + git remote add origin "http://x-access-token:${TOKEN}@forgejo:3000/${GITHUB_REPOSITORY}.git" + git fetch -q --depth 1 origin "${GITHUB_SHA}" + git checkout -q FETCH_HEAD + base=$(sed -n -E 's/^version:.*\+([0-9]+)$/\1/p' apps/app_seeds/pubspec.yaml) + [ -n "$base" ] + + # 4) fdroiddata skeleton (config.yml + srclibs) from the fork; overlay our recipe. + fdd=/tmp/fdroiddata + git clone --depth 1 -b tane https://gitlab.com/vjrj/fdroiddata.git "$fdd" + cp "$work/docs/fdroid/org.comunes.tane.yml" "$fdd/metadata/org.comunes.tane.yml" + cd "$fdd" + # Build THIS commit; drop binary: (we PRODUCE the reference here, not verify it). + sed -i "s#^\( *commit: \).*#\1${GITHUB_SHA}#" metadata/org.comunes.tane.yml + sed -i '/^ *binary: /d' metadata/org.comunes.tane.yml + + # 5) Build the three per-ABI splits with F-Droid's own toolchain. + fdroid build --verbose --no-tarball \ + "org.comunes.tane:$((base * 10 + 1))" \ + "org.comunes.tane:$((base * 10 + 2))" \ + "org.comunes.tane:$((base * 10 + 3))" + + # 6) Sign each unsigned APK with tane-upload and attach to the Forgejo release. + apksigner=$(ls -d "$ANDROID_HOME"/build-tools/*/apksigner | sort -V | tail -1) + echo "$TANE_KEYSTORE_BASE64" | base64 -d > /tmp/ks.jks + api="http://forgejo:3000/api/v1/repos/${GITHUB_REPOSITORY}" + tag="${GITHUB_REF_NAME}" + curl -sf -X POST "$api/releases" \ + -H "Authorization: token ${TOKEN}" -H 'Content-Type: application/json' \ + -d "{\"tag_name\":\"${tag}\",\"name\":\"${tag}\"}" >/dev/null || true + rid=$(curl -sf -H "Authorization: token ${TOKEN}" "$api/releases/tags/${tag}" \ + | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') + for f in unsigned/org.comunes.tane_*.apk; do + vc=$(echo "$f" | sed -E 's/.*_([0-9]+)\.apk/\1/') + case $((vc % 10)) in + 1) abi=armeabi-v7a ;; + 2) abi=arm64-v8a ;; + 3) abi=x86_64 ;; + *) echo "unexpected versionCode $vc"; exit 1 ;; + esac + out="/tmp/app-${abi}-release.apk" + "$apksigner" sign --ks /tmp/ks.jks --ks-key-alias "$TANE_KEY_ALIAS" \ + --ks-pass "pass:$TANE_KEYSTORE_PASSWORD" --key-pass "pass:$TANE_KEY_PASSWORD" \ + --out "$out" "$f" + curl -sf -X POST "$api/releases/${rid}/assets?name=app-${abi}-release.apk" \ + -H "Authorization: token ${TOKEN}" \ + -F "attachment=@${out};type=application/vnd.android.package-archive" + echo "uploaded app-${abi}-release.apk (from ${f##*/})" + done + rm -f /tmp/ks.jks diff --git a/docs/fdroid/org.comunes.tane.yml b/docs/fdroid/org.comunes.tane.yml index e3d0c2a..85053b4 100644 --- a/docs/fdroid/org.comunes.tane.yml +++ b/docs/fdroid/org.comunes.tane.yml @@ -15,87 +15,119 @@ AutoName: Tane RepoType: git Repo: https://git.comunes.org/comunes/tane.git -# Reproducible, developer-signed builds: F-Droid rebuilds from source and, if the -# output matches our own signed APK on git.comunes.org byte-for-byte, publishes -# OUR APK. Same signature as the Google Play build, so users move between stores -# without reinstalling. The fingerprint below is the SHA-256 of the tane-upload -# signing certificate (public value); the keystore/passwords never leave CI. +# Reproducible, developer-signed: F-Droid rebuilds each split and, if it matches +# our own signed APK on the git.comunes.org release byte-for-byte, publishes OUR +# APK (same signature as the Play build). Value is the SHA-256 of the tane-upload +# signing certificate (public). AllowedAPKSigningKeys: ddfae432091b8248a8a4a1b353487fa626301f4357ef835e94ec312f69418e38 -# One build entry per ABI: `flutter build --split-per-abi` yields smaller APKs and -# each split carries a distinct versionCode (gradle: versionCode*10 + {arm-v7a:1, -# arm64:2, x86_64:3}), so v0.1.2 (+4) -> 41/42/43. The `binary:` is the reference -# APK F-Droid verifies against, uploaded to the Forgejo release by -# .forgejo/workflows/release.yml on the `v*` tag. Builds: - - versionName: 0.1.3 - versionCode: 51 - commit: v0.1.3 + - versionName: 0.1.6 + versionCode: 81 + commit: 654ed1fe688d942f8bb9ec5505582532014d98a5 subdir: apps/app_seeds - sudo: - - apt-get update - - apt-get install -y git unzip xz-utils - - git clone --depth 1 -b 3.41.9 https://github.com/flutter/flutter.git /opt/flutter - - chown -R vagrant:vagrant /opt/flutter output: build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk binary: https://git.comunes.org/comunes/tane/releases/download/v%v/app-armeabi-v7a-release.apk + srclibs: + - flutter@stable + - tesseract4android@4.9.0 prebuild: - - export PATH="/opt/flutter/bin:$PATH" - - git config --global --add safe.directory /opt/flutter - - flutter config --no-analytics - - flutter pub get - - dart run slang - - dart run build_runner build --delete-conflicting-outputs + - export flutterVersion=$(sed -n -E 's,.*cirruslabs/flutter:([0-9.]+).*,\1,p' + ../../.forgejo/workflows/release.yml | head -1) + - '[[ $flutterVersion ]]' + - git -C $$flutter$$ checkout -f $flutterVersion + - cd ../.. + - export PUB_CACHE=$(pwd)/.pub-cache + - $$flutter$$/bin/flutter config --no-analytics + - $$flutter$$/bin/flutter pub get --enforce-lockfile + - cd apps/app_seeds + - $$flutter$$/bin/dart run slang + - $$flutter$$/bin/dart run build_runner build --delete-conflicting-outputs + scandelete: + - .pub-cache build: - - export PATH="/opt/flutter/bin:$PATH" - - flutter build apk --release --split-per-abi --target-platform=android-arm + - export PUB_CACHE=$(cd ../.. && pwd)/.pub-cache + - pushd $$tesseract4android$$/tesseract4android + - gradle assembleStandardRelease + - popd + - cp + $$tesseract4android$$/tesseract4android/build/outputs/aar/tesseract4android-standard-release.aar + $PUB_CACHE/hosted/pub.dev/flutter_tesseract_ocr-0.4.31/android/libs/tesseract4android-release.aar + - $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform=android-arm - - versionName: 0.1.3 - versionCode: 52 - commit: v0.1.3 + - versionName: 0.1.6 + versionCode: 82 + commit: 654ed1fe688d942f8bb9ec5505582532014d98a5 subdir: apps/app_seeds - sudo: - - apt-get update - - apt-get install -y git unzip xz-utils - - git clone --depth 1 -b 3.41.9 https://github.com/flutter/flutter.git /opt/flutter - - chown -R vagrant:vagrant /opt/flutter output: build/app/outputs/flutter-apk/app-arm64-v8a-release.apk binary: https://git.comunes.org/comunes/tane/releases/download/v%v/app-arm64-v8a-release.apk + srclibs: + - flutter@stable + - tesseract4android@4.9.0 prebuild: - - export PATH="/opt/flutter/bin:$PATH" - - git config --global --add safe.directory /opt/flutter - - flutter config --no-analytics - - flutter pub get - - dart run slang - - dart run build_runner build --delete-conflicting-outputs + - export flutterVersion=$(sed -n -E 's,.*cirruslabs/flutter:([0-9.]+).*,\1,p' + ../../.forgejo/workflows/release.yml | head -1) + - '[[ $flutterVersion ]]' + - git -C $$flutter$$ checkout -f $flutterVersion + - cd ../.. + - export PUB_CACHE=$(pwd)/.pub-cache + - $$flutter$$/bin/flutter config --no-analytics + - $$flutter$$/bin/flutter pub get --enforce-lockfile + - cd apps/app_seeds + - $$flutter$$/bin/dart run slang + - $$flutter$$/bin/dart run build_runner build --delete-conflicting-outputs + scandelete: + - .pub-cache build: - - export PATH="/opt/flutter/bin:$PATH" - - flutter build apk --release --split-per-abi --target-platform=android-arm64 + - export PUB_CACHE=$(cd ../.. && pwd)/.pub-cache + - pushd $$tesseract4android$$/tesseract4android + - gradle assembleStandardRelease + - popd + - cp + $$tesseract4android$$/tesseract4android/build/outputs/aar/tesseract4android-standard-release.aar + $PUB_CACHE/hosted/pub.dev/flutter_tesseract_ocr-0.4.31/android/libs/tesseract4android-release.aar + - $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform=android-arm64 - - versionName: 0.1.3 - versionCode: 53 - commit: v0.1.3 + - versionName: 0.1.6 + versionCode: 83 + commit: 654ed1fe688d942f8bb9ec5505582532014d98a5 subdir: apps/app_seeds - sudo: - - apt-get update - - apt-get install -y git unzip xz-utils - - git clone --depth 1 -b 3.41.9 https://github.com/flutter/flutter.git /opt/flutter - - chown -R vagrant:vagrant /opt/flutter output: build/app/outputs/flutter-apk/app-x86_64-release.apk binary: https://git.comunes.org/comunes/tane/releases/download/v%v/app-x86_64-release.apk + srclibs: + - flutter@stable + - tesseract4android@4.9.0 prebuild: - - export PATH="/opt/flutter/bin:$PATH" - - git config --global --add safe.directory /opt/flutter - - flutter config --no-analytics - - flutter pub get - - dart run slang - - dart run build_runner build --delete-conflicting-outputs + - export flutterVersion=$(sed -n -E 's,.*cirruslabs/flutter:([0-9.]+).*,\1,p' + ../../.forgejo/workflows/release.yml | head -1) + - '[[ $flutterVersion ]]' + - git -C $$flutter$$ checkout -f $flutterVersion + - cd ../.. + - export PUB_CACHE=$(pwd)/.pub-cache + - $$flutter$$/bin/flutter config --no-analytics + - $$flutter$$/bin/flutter pub get --enforce-lockfile + - cd apps/app_seeds + - $$flutter$$/bin/dart run slang + - $$flutter$$/bin/dart run build_runner build --delete-conflicting-outputs + scandelete: + - .pub-cache build: - - export PATH="/opt/flutter/bin:$PATH" - - flutter build apk --release --split-per-abi --target-platform=android-x64 + - export PUB_CACHE=$(cd ../.. && pwd)/.pub-cache + - pushd $$tesseract4android$$/tesseract4android + - gradle assembleStandardRelease + - popd + - cp + $$tesseract4android$$/tesseract4android/build/outputs/aar/tesseract4android-standard-release.aar + $PUB_CACHE/hosted/pub.dev/flutter_tesseract_ocr-0.4.31/android/libs/tesseract4android-release.aar + - $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform=android-x64 AutoUpdateMode: Version UpdateCheckMode: Tags v[\d.]+ -UpdateCheckData: apps/app_seeds/pubspec.yaml|version:\s*[\d.]+\+(\d+)|apps/app_seeds/pubspec.yaml|version:\s*([\d.]+)\+ -CurrentVersion: 0.1.3 -CurrentVersionCode: 53 +VercodeOperation: + - '%c * 10 + 1' + - '%c * 10 + 2' + - '%c * 10 + 3' +UpdateCheckData: + apps/app_seeds/pubspec.yaml|version:\s*[\d.]+\+(\d+)|apps/app_seeds/pubspec.yaml|version:\s*([\d.]+)\+ +CurrentVersion: 0.1.6 +CurrentVersionCode: 83