diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index e779d52..12643c6 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -1,25 +1,19 @@ # Release automation for git.comunes.org (Forgejo Actions). # -# 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): +# 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: # 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 +# SUPPLY_JSON_KEY_DATA Google Play service-account JSON (reused from Ğ1nkgo) # -# 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. +# 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`. name: release @@ -29,7 +23,7 @@ on: - 'v*' jobs: - play: + android: runs-on: docker container: image: ghcr.io/cirruslabs/flutter:3.41.9 @@ -53,6 +47,21 @@ 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: @@ -69,9 +78,54 @@ jobs: keyPassword=$TANE_KEY_PASSWORD EOF - - name: Build signed AAB + - name: Build tesseract4android from source (match F-Droid for reproducibility) working-directory: apps/app_seeds - run: flutter build appbundle --release + 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 - name: Install fastlane working-directory: apps/app_seeds @@ -91,86 +145,3 @@ 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/apps/app_seeds/pubspec.yaml b/apps/app_seeds/pubspec.yaml index 5b225f2..5fa6370 100644 --- a/apps/app_seeds/pubspec.yaml +++ b/apps/app_seeds/pubspec.yaml @@ -1,7 +1,7 @@ name: tane description: "Tane — local-first, encrypted, decentralized traditional-seed inventory and market." publish_to: 'none' -version: 0.1.7+9 +version: 0.1.6+8 environment: sdk: ^3.11.5 diff --git a/docs/fdroid/org.comunes.tane.yml b/docs/fdroid/org.comunes.tane.yml index df61b9d..e3d0c2a 100644 --- a/docs/fdroid/org.comunes.tane.yml +++ b/docs/fdroid/org.comunes.tane.yml @@ -15,119 +15,87 @@ AutoName: Tane RepoType: git Repo: https://git.comunes.org/comunes/tane.git -# 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). +# 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. 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.7 - versionCode: 91 - commit: v0.1.7 + - versionName: 0.1.3 + versionCode: 51 + commit: v0.1.3 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 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 + - 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 build: - - 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 + - export PATH="/opt/flutter/bin:$PATH" + - flutter build apk --release --split-per-abi --target-platform=android-arm - - versionName: 0.1.7 - versionCode: 92 - commit: v0.1.7 + - versionName: 0.1.3 + versionCode: 52 + commit: v0.1.3 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 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 + - 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 build: - - 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 + - export PATH="/opt/flutter/bin:$PATH" + - flutter build apk --release --split-per-abi --target-platform=android-arm64 - - versionName: 0.1.7 - versionCode: 93 - commit: v0.1.7 + - versionName: 0.1.3 + versionCode: 53 + commit: v0.1.3 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 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 + - 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 build: - - 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 + - export PATH="/opt/flutter/bin:$PATH" + - flutter build apk --release --split-per-abi --target-platform=android-x64 AutoUpdateMode: Version UpdateCheckMode: Tags v[\d.]+ -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.7 -CurrentVersionCode: 93 +UpdateCheckData: apps/app_seeds/pubspec.yaml|version:\s*[\d.]+\+(\d+)|apps/app_seeds/pubspec.yaml|version:\s*([\d.]+)\+ +CurrentVersion: 0.1.3 +CurrentVersionCode: 53