The inclusion MR was merged on 2026-07-25 and the app page went live on 2026-07-28 (v0.1.16, no antifeatures), so the landing page can finally offer both stores. - site: publish the F-Droid link and give each store link a single-colour icon, so the two are told apart by shape (play triangle vs. the free-software robot) rather than by brand colour. The badge row is a flex row now: left-aligned in the hero, centred under "Get Tane". - README: an Install section with both stores, and a Status that matches reality (Block 1 shipped, Block 2 under way) instead of "early design". - release.md: record that the app is published, that further releases only need a versionCode bump in the fdroiddata recipe, and that publication lags the green build by a day or two. - open-decisions.md: log what unblocked inclusion — reproducible developer-signed builds, a Google-free APK, and opt-in networking.
220 lines
10 KiB
Markdown
220 lines
10 KiB
Markdown
# 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/<code>.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/<locale>/changelogs/<versionCode>.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/<locale>` (`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)
|
||
|
||
**Tane is live in F-Droid** since 2026-07-28 (v0.1.16, no antifeatures):
|
||
<https://f-droid.org/packages/org.comunes.tane/>. The inclusion MR
|
||
([43144](https://gitlab.com/fdroid/fdroiddata/-/merge_requests/43144)) was merged
|
||
on 2026-07-25; from now on each release only needs the recipe in `fdroiddata`
|
||
master bumped, not a new inclusion request. Note that publication lags the green
|
||
build by a day or two — check `repo/status/build.json` before assuming breakage.
|
||
|
||
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.
|
||
|
||
Every version bump **must** advance the recipe's per-ABI `versionCode`s
|
||
(`build × 10 + ABI offset`) together with the `pubspec.yaml` bump — otherwise the
|
||
`fdroid_reference` job fails, and a bad tag cannot be fixed by re-tagging (Play
|
||
rejects a reused versionCode): cut the next version instead.
|
||
|
||
**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).
|