feat(release): automate store publishing (fastlane supply + GitLab CI) and F-Droid recipe

- fastlane: Appfile/Fastfile/Gemfile with deploy_play lane (AAB -> Play internal track)
- CI: tag-gated build:android (signed AAB/APK from CI secrets) + deploy:play jobs
- F-Droid: fdroiddata build recipe at docs/fdroid/org.comunes.tane.yml
- Play compliance: Data Safety / content-rating answer sheet (docs/legal/internal)
- docs/release.md: automated tag-triggered flow, dedicated tane-upload keystore
- pubspec: description now mentions the market, not just the inventory
This commit is contained in:
vjrj 2026-07-16 02:42:47 +02:00
parent dc0d18562f
commit fd6b9d5f0d
9 changed files with 361 additions and 28 deletions

View file

@ -0,0 +1,62 @@
# F-Droid metadata for Tane (org.comunes.tane).
#
# This is the source of truth for the fdroiddata merge request. To submit:
# 1. Fork https://gitlab.com/fdroid/fdroiddata
# 2. Copy this file to metadata/org.comunes.tane.yml
# 3. Validate locally: fdroid lint org.comunes.tane
# fdroid build -v -l org.comunes.tane
# 4. Open the MR.
#
# Prerequisites (see docs/release.md):
# - The source repo below must be publicly clonable without auth.
# - Each release is a signed git tag `v<version>` (UpdateCheckMode: Tags).
# - F-Droid builds and signs the APK with ITS OWN key, so the F-Droid build
# and the Google Play build have different signatures and cannot cross-update.
#
# NOTE: Flutter-on-F-Droid builds are finicky. Pin the Flutter version to the
# developer toolchain (3.41.9) and re-run `fdroid build` after every bump. This
# recipe accounts for the monorepo layout (pub workspace root at repo root, app
# at apps/app_seeds) and the generated code (slang i18n + Drift via build_runner).
Categories:
- Science & Education
License: AGPL-3.0-or-later
AuthorName: Comunes
AuthorWebSite: https://comunes.org
WebSite: https://tane.comunes.org
SourceCode: https://git.comunes.org/comunes/tane
IssueTracker: https://git.comunes.org/comunes/tane/issues
Translation: https://translate.comunes.org/projects/tane
AutoName: Tane
RepoType: git
Repo: https://git.comunes.org/comunes/tane.git
Builds:
- versionName: 0.1.0
versionCode: 1
commit: v0.1.0
subdir: apps/app_seeds
sudo:
- apt-get update
- apt-get install -y git unzip xz-utils
# Pin Flutter to the developer toolchain; keep in sync with .gitlab-ci.yml.
- git clone --depth 1 -b 3.41.9 https://github.com/flutter/flutter.git /opt/flutter
- git config --global --add safe.directory /opt/flutter
output: build/app/outputs/flutter-apk/app-release.apk
prebuild:
- export PATH="/opt/flutter/bin:$PATH"
- flutter config --no-analytics
- flutter pub get
# Generated sources: i18n (slang) then Drift/other codegen (build_runner).
- dart run slang
- dart run build_runner build --delete-conflicting-outputs
build:
- export PATH="/opt/flutter/bin:$PATH"
- flutter build apk --release
AutoUpdateMode: Version v%v
UpdateCheckMode: Tags
CurrentVersion: 0.1.0
CurrentVersionCode: 1

View file

@ -0,0 +1,84 @@
# Google Play compliance answer sheet (internal)
Draft answers for the Play Console questionnaires. Transcribe into the console;
this file is the single source of truth so the answers stay consistent across
releases. Grounded in [`../privacy-policy.md`](../privacy-policy.md) and
[`../../design/security-privacy.md`](../../design/security-privacy.md).
Package: `org.comunes.tane` · Publisher: Asociación Comunes · Contact: info@comunes.org
Privacy policy URL: https://tane.comunes.org/legal/privacy
---
## 1. Data Safety form
**Core principle:** Tane has **no accounts and no Tane server**. Anything a user
records stays on-device, encrypted (SQLCipher). Data only leaves the device by an
explicit user action (publishing an offer, filling a public profile, sending a
message, or syncing to the user's own second device), and then it travels through
community-run relays, not through us. So under Play's definitions we *collect*
nothing for ourselves; we *share* only the specific things the user chooses to
publish, all for **App functionality** — never for analytics, ads, or sale.
### Does your app collect or share any user data? → **Yes**
(Because user-initiated publishing sends data to third-party relays.)
### Data types — declare as **Shared**, purpose **App functionality**, **optional** (user-initiated)
| Play data type | Declare? | Notes |
|---|---|---|
| Location → Approximate location | Shared | Only a coarse ~280 km area, only if the user sets a sharing zone for an offer. Never precise. Never collected for us. |
| Personal info → Name | Shared | Only the display name the user chooses to put in a public profile. Optional. |
| Personal info → Other info (bio) | Shared | Optional public profile bio. |
| Photos and videos → Photos | Shared | Only photos the user attaches to a public offer or profile. |
| Messages → Other in-app messages | Shared | Direct messages, **end-to-end encrypted**; relays carry only encrypted envelopes. Declare as shared for transparency; mark encrypted in transit. |
| App activity / App info & performance | No | No analytics, no crash reporting, no diagnostics sent anywhere. |
| Financial, Health, Contacts (device), Calendar, Browsing history, Files, Device IDs | No | Not accessed or transmitted. |
Note on "Collected vs Shared": Play defines *collection* as leaving the device to
the developer. We receive nothing, so nothing is "Collected by us." The items above
are *Shared* (to third-party relays / other users). If the console forces a
"Collected" tick for a shared type, pair it with the encryption + optional answers below.
### Security practices
- **Is all user data encrypted in transit?****Yes.** Relay connections use TLS;
direct messages are additionally end-to-end encrypted; device-to-device sync is encrypted.
- **Data stored on device** is encrypted at rest (SQLCipher) — mention in the listing, though Play's form focuses on transit.
- **Can users request data deletion?****Yes.** Everything local can be deleted in-app
instantly; published offers can be withdrawn, which asks relays to delete. Relays we
don't operate may retain copies (stated honestly in the privacy policy).
- **Data is NOT sold.** **Data is NOT used for advertising or marketing.**
- **Independent security review?** → No (self-declared; source is public/AGPL and auditable).
---
## 2. Content rating (IARC questionnaire)
- Category: **Utility / Reference / Productivity** (not a game).
- Violence, sexual content, gambling, drugs: **None.**
- **User-generated content / communication:** **Yes** — the market has public offers,
user profiles, and direct messaging. Declare in-app UGC and user-to-user communication.
- Expected outcome: **Everyone / PEGI 3style**, with the UGC/communication flags set.
## 3. Target audience & content
- Target age: **not directed at children.** Age band spans adults; the phrase "1099"
is a usability aim (simple enough for a 10-year-old, legible for a 99-year-old), **not**
a claim of child audience. In the console pick the **adult / 18+** or "not for children"
target so Families policy does not apply to the market/messaging features.
- The seed **inventory** alone is harmless for any age, but the **market and messaging**
are social features and are declared not directed at children (see privacy policy §8).
## 4. UGC policy (Play's user-generated content requirements)
The market ships the mitigations Play looks for; keep them working:
- **Community rules acceptance** gate on entering the market.
- **Report** content/users and **block** users, in-app.
- A stated content policy: [`../community-rules.md`](../community-rules.md).
## 5. Ads & privacy
- **Contains ads?** → **No.**
- **Data deletion URL / contact:** info@comunes.org (and in-app deletion).
- **Government app?** → No. **Financial features?** → No (optional Ğ1 price is a plain
number shown in a listing, no in-app payments, no Play Billing).

View file

@ -1,7 +1,21 @@
# Releasing Tane (manual, no CI yet)
# Releasing Tane
Block 1 ships as a self-contained Android app (desktop builds also work). There
is no release pipeline yet — this page is the by-hand checklist.
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
```
CI (`build:android``deploy:play` in [`../.gitlab-ci.yml`](../.gitlab-ci.yml)) builds
the signed AAB/APK and uploads to Play's **internal** track. No passwords are typed.
## Versioning
@ -12,38 +26,92 @@ is no release pipeline yet — this page is the by-hand checklist.
add a matching per-locale note under
`apps/app_seeds/fastlane/metadata/android/<locale>/changelogs/<versionCode>.txt`.
## Android signing (one-time)
## One-time setup
Release builds sign with your own 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`).
### Android signing keystore (dedicated to Tane)
1. Create a keystore (keep it and its passwords safe — losing it means you can
never update the app under the same identity):
```bash
keytool -genkey -v -keystore ~/tane-release.jks \
-keyalg RSA -keysize 4096 -validity 10000 -alias tane
```
2. Create `apps/app_seeds/android/key.properties` (never commit it):
```properties
storeFile=/home/you/tane-release.jks
storePassword=…
keyAlias=tane
keyPassword=…
```
Tane uses its **own** upload keystore, separate from other Comunes apps (Ğ1nkgo,
Fuegos), kept in the shared Comunes signing directory:
## Build the beta by hand
```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, GitLab → Settings → CI/CD → Variables)
Set these as **masked + protected** so releases never prompt for a password:
| Variable | 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 created in Google Cloud and granted release access in
Play Console; it is what lets `fastlane supply` upload without a human. The first
upload of a brand-new app must still be done by hand in the console (Play requires
the app to exist before the API accepts uploads).
## 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 apk --release # or: flutter build appbundle --release
flutter build appbundle --release # AAB for Play
flutter build apk --release # APK for sideload / QA
```
The APK lands at
`apps/app_seeds/build/app/outputs/flutter-apk/app-release.apk`.
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).
## Publish to F-Droid (official repo)
F-Droid builds and signs 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. F-Droid signs
with its own key, so the F-Droid and Play builds have different signatures.
## Store metadata
@ -75,7 +143,7 @@ artifact, it does not replace tests):
second install).
5. Switch language (es / en / pt); check an RTL locale if the device offers one.
## Not in this release
## Not automated
CI, Play Store automation, web builds (SQLCipher is unavailable on web), and
the whole social layer (Block 2).
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).