There are two independent Android distribution channels. They are signed by different keys, so a user must pick one and stick with it (see Signing & the two channels).
- GitHub Releases — you build and sign the APK; the
Release Android APKworkflow attaches it to the Release automatically. Signing is required — the workflow fails early if the keystore secrets below are not configured. - F-Droid — F-Droid's servers build the APK from the tagged source and sign it with F-Droid's key. Your GitHub Action is not involved.
F-Droid builds the committed source, so the version must live in the repo — not be injected by CI.
- Bump the version in
android/app/build.gradle:versionName→ the human string, e.g."0.2.0"(keep it equal to the git tag without thev).versionCode→ an integer that increases by at least 1 every release (Android refuses to upgrade otherwise). e.g.1→2.
- Commit, then tag and push:
git tag v0.2.0 && git push origin v0.2.0. - Publish a GitHub Release for that tag. The workflow builds, signs (if
secrets are set), and attaches
railmap-v0.2.0.apk. - For F-Droid, add a new build entry with the same
versionCode/versionNameto the metadata infdroiddata(see below). WithUpdateCheckMode: Tagsthe F-Droid bot will also propose this for you.
The release workflow requires a keystore and fails early without one (an unsigned APK cannot be installed anyway). Set it up once:
-
Generate a keystore once (keep it forever — losing it means you can never upgrade the app for existing users):
keytool -genkey -v -keystore max-finder.keystore \ -alias max-finder -keyalg RSA -keysize 2048 -validity 10000 base64 -w0 max-finder.keystore > keystore.b64 # macOS: base64 -i max-finder.keystore
Store
max-finder.keystoresomewhere safe and offline. Never commit it (*.keystore,*.jks,keystore.b64are git-ignored). -
Add repo secrets (Settings → Secrets and variables → Actions):
Secret Value ANDROID_KEYSTORE_BASE64contents of keystore.b64ANDROID_KEYSTORE_PASSWORDthe store password you chose ANDROID_KEY_ALIASmax-finderANDROID_KEY_PASSWORDthe key password you chose
The workflow decodes the keystore into a temp file and passes it to Gradle via
the MAXFINDER_* env vars that build.gradle reads. The secrets never appear in
logs and the keystore never touches the repo.
F-Droid inclusion is a merge request to
fdroiddata, reviewed by F-Droid
maintainers. Below is a starting-point recipe — a Capacitor + Vite app needs
Node to build the web assets, which the F-Droid buildserver provides but which
often needs a round of iteration with the maintainers to get exactly right.
Create metadata/org.maxfinder.app.yml in fdroiddata:
Categories:
- Internet
- Travel
License: AGPL-3.0-only
AuthorName: Railmap contributors
SourceCode: https://github.com/offware-apps/Railmap
IssueTracker: https://github.com/offware-apps/Railmap/issues
AutoName: Railmap
RepoType: git
Repo: https://github.com/offware-apps/Railmap.git
Builds:
- versionName: 0.1.0
versionCode: 1
commit: v0.1.0
sudo:
- apt-get update
- apt-get install -y npm
subdir: android/app
gradle:
- yes
prebuild:
- npm ci
- npm run build:mobile
- npx cap copy android
scandelete:
- node_modules
AutoUpdateMode: Version v%v
UpdateCheckMode: Tags
CurrentVersion: 0.1.0
CurrentVersionCode: 1Notes / likely iteration points:
- Node version. The
aptnpmmay be too old for Vite 6 (needs Node 18+). You may need to install a newer Node insudo:(e.g. via NodeSource) — F-Droid maintainers will advise the approved approach for the current buildserver. prebuildruns the web build (build:mobileuses base/) and copies the assets intoandroid/withcap copy, sincedist/is not committed.scandelete: node_modulesstops F-Droid's scanner from flagging bundled npm packages after the build.- No proprietary blobs. The stack is MIT (Capacitor, Vite, Leaflet). The one
binary in the repo is
android/gradle/wrapper/gradle-wrapper.jar(standard Gradle wrapper, accepted by F-Droid). - The store listing (title, descriptions) is pulled from
fastlane/metadata/android/en-US/. AddphoneScreenshots/and anicon.pngthere to enrich the F-Droid page.
Validate locally with fdroid build -v -l org.maxfinder.app (via
fdroidserver) before opening
the merge request.
- The GitHub Release APK is signed with your keystore.
- The F-Droid APK is signed with F-Droid's key.
Because Android ties upgrades to the signing key, a user who installed the GitHub APK cannot update via F-Droid (or vice-versa) without uninstalling first. This is normal for dual-channel FOSS apps. If you later want a single signature across both, F-Droid supports reproducible builds where F-Droid ships your signature — a more advanced setup worth adding once the basic recipe is merged.