# Releasing Tane Tane ships as a self-contained Android app (desktop builds also work). Releases are **automated and password-free**: pushing a signed git tag triggers CI, which builds a signed AAB and publishes it to Google Play. This page covers the one-time setup, the automated flow, and the by-hand fallback. ## TL;DR — cut a release Once the one-time setup below is done: ```bash # bump version in apps/app_seeds/pubspec.yaml, update CHANGELOG + changelogs/.txt git tag v0.1.0 && git push origin v0.1.0 ``` Forgejo Actions ([`../.forgejo/workflows/release.yml`](../.forgejo/workflows/release.yml)) builds the signed AAB/APK and uploads to Play's **production** track at **100% rollout**. No passwords are typed. Because a tag now publishes to everyone (subject to Google review), run the [manual smoke test](#manual-smoke-test-before-shipping) **before** tagging. To stage on the internal track first, run `bundle exec fastlane deploy_internal` by hand. ## Versioning - Semantic version in [`apps/app_seeds/pubspec.yaml`](../apps/app_seeds/pubspec.yaml) as `MAJOR.MINOR.PATCH+BUILD` (e.g. `0.1.0+1`). Flutter maps `+BUILD` to Android `versionCode` and the rest to `versionName`. - Record every user-visible change in [`../CHANGELOG.md`](../CHANGELOG.md) and add a matching per-locale note under `apps/app_seeds/fastlane/metadata/android//changelogs/.txt`. ## One-time setup ### Android signing keystore (dedicated to Tane) Tane uses its **own** upload keystore, separate from other Comunes apps (Ğ1nkgo, Fuegos), kept in the shared Comunes signing directory: ```bash keytool -genkey -v \ -keystore /home/vjrj/proyectos/sync/comunes/shared-l2/apkSigning/tane-upload.jks \ -keyalg RSA -keysize 4096 -validity 10000 -alias tane-upload ``` Release builds sign with this keystore when a **gitignored** `apps/app_seeds/android/key.properties` exists; without it they fall back to debug keys (so contributors can still `flutter run --release`). For **local** signed builds, create `apps/app_seeds/android/key.properties` (never commit it): ```properties storeFile=/home/vjrj/proyectos/sync/comunes/shared-l2/apkSigning/tane-upload.jks storePassword=… keyAlias=tane-upload keyPassword=… ``` With **Play App Signing** (recommended, enabled once in the console) this is the *upload key*; Google holds the app signing key and can help rotate the upload key if it is ever lost. ### CI secrets (one-time, Forgejo → repo Settings → Actions → Secrets) Set these so releases never prompt for a password: | Secret | What | |---|---| | `TANE_KEYSTORE_BASE64` | `base64 -w0 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 (raw file contents) | The Play service account is **reused from Ğ1nkgo** (`ginkgo-play-uploader@…`, `/home/vjrj/etc/ginkgo_play_api_key.json`) — it belongs to the Comunes Play account, not to one app, so it just needs access granted to `org.comunes.tane` in Play Console. `fastlane supply` uses it to upload without a human. The first upload of the brand-new app must still be done by hand in the console (Play requires the app to exist before the API accepts uploads). ## CI runner (Forgejo Actions) — action needed The release + gate workflows (`.forgejo/workflows/*.yml`) run on **Forgejo Actions**, which needs a **registered runner**. As of 2026-07-16 the `aaron` host runs the `forgejo` and `jenkins` containers but **no Forgejo Actions runner** — so the workflows will queue and never start until one is registered. Two options: - **Register an act_runner** on `aaron` (a `forgejo-runner`/`act_runner` container registered against `git.comunes.org` with a label matching `runs-on:` — currently `docker`). Then tag → build → publish works as designed. - **Or move CI to Jenkins** (already running on `aaron`) via a Jenkinsfile; the build and `fastlane deploy_play` steps are the same shell commands. Also: the keystore's real alias is **`tane-upload`**, so the Forgejo secret `TANE_KEY_ALIAS` and local `key.properties` must both use `tane-upload`. ## Build by hand (fallback / first Play upload) ```bash cd apps/app_seeds dart run slang # i18n (if strings changed) dart run build_runner build --delete-conflicting-outputs # Drift (if schema changed) flutter test # must be green flutter build appbundle --release # AAB for Play flutter build apk --release # APK for sideload / QA ``` Artifacts: - AAB → `apps/app_seeds/build/app/outputs/bundle/release/app-release.aab` - APK → `apps/app_seeds/build/app/outputs/flutter-apk/app-release.apk` Verify the signature is Tane's key (not another app's): `apksigner verify --print-certs app-release.apk` → alias `tane-upload`. ## Publish to Google Play (fastlane) CI does this on tag, but you can run it locally too: ```bash cd apps/app_seeds bundle install SUPPLY_JSON_KEY_DATA="$(cat play-service-account.json)" bundle exec fastlane deploy_play ``` Lanes live in [`fastlane/Fastfile`](../apps/app_seeds/fastlane/Fastfile); the store listing (titles, descriptions, changelogs, screenshots) is read straight from `fastlane/metadata/android/`. Data Safety and content-rating answers: [`legal/internal/play-compliance.md`](legal/internal/play-compliance.md). Lanes: - `deploy_play` — upload the AAB to **production** at 100% (what CI runs on tag). - `deploy_internal` — same AAB to the **internal** test track (manual QA safety net). - `deploy_metadata` — push only the store listing (no binary). - `promote_production` — promote an internal release to production without a rebuild. ### Country/region availability (Play Console, not this repo) Which countries the app is offered in is **not** in the repo and `fastlane supply` does not manage it — it's a Play Console setting. To add countries: **Play Console → (Tane) → Production → Countries / regions → Add countries/regions** (under "Availability"). Play asks you to pick countries when you first move a release to production; widen the list there for new markets. Note: the locales under `fastlane/metadata/android/` (`en-US`, `es-ES`) are **listing languages**, not countries — adding a locale improves the store page but does not by itself make the app available in a new country. ## Publish to F-Droid (official repo) F-Droid builds from source after a merge request to `fdroiddata`. The build recipe is kept in-repo at [`fdroid/org.comunes.tane.yml`](fdroid/org.comunes.tane.yml). Copy it to `metadata/org.comunes.tane.yml` in a fork of fdroiddata, validate with `fdroid lint` / `fdroid build -l org.comunes.tane`, then open the MR ([43144](https://gitlab.com/fdroid/fdroiddata/-/merge_requests/43144)). **Reproducible, developer-signed.** The recipe pins `AllowedAPKSigningKeys` (the SHA-256 of the tane-upload certificate) and a `binary:` URL per ABI. F-Droid rebuilds each split, and if its output matches our signed reference APK byte-for-byte it publishes **our** APK — so F-Droid and Play share the same signature and users can move between stores without reinstalling. The reference APKs are the per-ABI splits the release workflow attaches to the Forgejo release on `git.comunes.org` (`.forgejo/workflows/release.yml`). To (re)compute the fingerprint from the keystore: ```bash keytool -list -v -keystore .../tane-upload.jks -alias tane-upload \ | sed -n 's/.*SHA256: //p' | tr -d ':' | tr 'A-Z' 'a-z' ``` Before pushing the MR, confirm reproducibility in the fdroiddata fork: `fdroid build -l org.comunes.tane` then `fdroid verify org.comunes.tane` must be green against the Forgejo `binary:`. If a split is not reproducible, fall back to F-Droid-signing that version (drop `AllowedAPKSigningKeys`/`binary:`) until fixed. ## Store metadata F-Droid / Fastlane-style metadata lives under `apps/app_seeds/fastlane/metadata/android/`. Descriptions are derived from [`intro.md`](intro.md); icon and splash are generated by `flutter_launcher_icons` / `flutter_native_splash` (see `pubspec.yaml`). ## Legal & Play compliance (before any store submission) - The privacy policy must be live at `https://tane.comunes.org/legal/privacy` (sources in [`legal/`](legal/README.md)) — Play Console requires the URL. - Fill the Data Safety form and the UGC/content-rating questionnaires from the answer sheet in [`legal/internal/play-compliance.md`](legal/internal/play-compliance.md). - The in-app requirements (community-rules acceptance on market entry, report and block) ship with the app; keep them working — Play's UGC policy review looks for them. ## Manual smoke test before shipping Automated tests cover behaviour; a release build still gets a human pass on a real device (the one place we allow manual testing — it validates the packaged artifact, it does not replace tests): 1. Cold start → intro carousel → home. 2. Quick-add a seed with a photo; it appears in the list. 3. Mark a lot to share; the "I share" filter and printable catalog appear. 4. Save a backup, then restore it (and test the recovery sheet + code on a second install). 5. Switch language (es / en / pt); check an RTL locale if the device offers one. ## Not automated Web builds (SQLCipher is unavailable on web) and iOS (no Apple developer setup yet). The F-Droid MR is opened by hand (F-Droid builds on its own infra, not ours).