Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.3 KiB
Building the Android app on the Gitea server
Pushing a version tag builds both APKs on our own server and attaches them to a Gitea release:
musicshare-<version>.apk: the normal app.musicshare-<version>-foss.apk: no closed-source code, the same build F-Droid makes. The build fails if any Google ML Kit or Play Services code ends up inside.
Both are signed with the upload key, and SHA256SUMS lists their checksums. Nothing leaves the server except the
downloads the build itself needs (npm packages, Gradle and its plugins).
The pieces:
| File | What it is |
|---|---|
.gitea/workflows/android.yml |
The workflow. It uses only plain shell steps, no third-party actions. |
apps/mobile/scripts/build-android.sh |
The build. You can also run it by hand on any machine with the Android SDK. |
deploy/android-builder/Dockerfile |
The build environment: Node 22, Java 17 and the Android SDK/NDK. |
deploy/gitea-runner/ |
The runner (act_runner) that runs builds on the server. |
One-time setup (on the Gitea server)
The server needs about 8 GB of free memory during a build and about 15 GB of disk for the image and
caches. A first build takes 30-40 minutes; later ones reuse the Gradle cache.
The caches are Docker volumes, which live wherever Docker keeps its data (usually /var/lib/docker).
Check that disk has room (df -h /var/lib/docker): if it fills up, everything else using Docker on the
server, including databases, stops writing.
-
Turn on Actions if it's off. In Gitea's
app.ini, set:[actions] ENABLED = trueRestart Gitea. Then, in the repo, go to Settings > Repository > Actions and enable it.
-
Build the builder image from a checkout of this repo:
docker build -t musicshare-android-builder deploy/android-builderRebuild it when React Native's SDK or NDK versions change (the Dockerfile says where to look).
-
Start the runner. In the repo, open Settings > Actions > Runners > Create new runner and copy the token. Use the repo's page, not the site admin's, so the runner only takes this repo's jobs. Then:
cd deploy/gitea-runner RUNNER_TOKEN=<token> docker compose up -dIt registers once (saved in
./data), then shows as idle with the labelmusicshare-android. The runner controls Docker on the host, so treat it like root access. The workflow only runs for tags and manual runs, never for pull requests. -
Add the secrets under Settings > Actions > Secrets:
UPLOAD_KEYSTORE_BASE64: the output ofbase64 -w0 ~/.config/musicshare/musicshare-upload.jks.MUSICSHARE_UPLOAD_STORE_PASSWORD,MUSICSHARE_UPLOAD_KEY_ALIAS(musicshare-upload),MUSICSHARE_UPLOAD_KEY_PASSWORD: the values in~/.gradle/gradle.propertieson the machine you've been building on.RELEASE_TOKEN: create it under User settings > Applications > Access tokens, with write:repository and write:package permissions.
Releasing
- Bump
versionandandroid.versionCodeinapps/mobile/app.json. Addfastlane/metadata/android/en-US/changelogs/<versionCode>.txt, then commit. - Tag and push:
git tag v1.1.0 && git push origin main v1.1.0. - Watch it under the repo's Actions tab. The APKs appear on the Releases page.
The build refuses to run if the tag doesn't match the version in app.json. Re-running a tag's build replaces
that release's files.
Test builds: go to Actions > Android > Run workflow and pick a branch. The APKs go to the
repo's Packages as musicshare-android, versioned <branch>-<commit>, instead of to a release.
App updates
The normal APK updates itself from these releases; the F-Droid build doesn't (F-Droid updates it).
- The app asks our server (
GET /app/android/latest) when it opens, at most every 6 hours, or when you tap Settings > Check for updates. - The server reads the latest release from Gitea and returns its version, the APK's SHA-256 (from
SHA256SUMS) and the release notes. Drafts and pre-releases are skipped. - If it's newer, the app downloads the APK through our server and checks the SHA-256.
- The next time you leave the app, it tries a quiet install (once per version). Android 12+ allows that after the app has installed one update itself. Otherwise the feed shows Update ready, and tapping it opens Android's install screen; the first time, Android asks you to allow musicshare to install apps. On phones with Google Play, Play Protect may ask to scan each new version ("App scan recommended") because the app isn't from the Play Store. Choosing Scan app lets the update finish.
Set these on the API server (deploy/production.env):
ANDROID_RELEASES_URL=https://gitea.chrisedwards.tech/api/v1/repos/chris/musicshare/releases/latest
# Only if the repo is private: a token with read:repository (User settings > Applications).
ANDROID_RELEASES_TOKEN=
The release notes come from fastlane/metadata/android/en-US/changelogs/<versionCode>.txt: the workflow
puts that text above a --- line in the release, and the app shows only that part.
Building by hand
apps/mobile/scripts/build-android.sh # APKs in apps/mobile/dist
It uses the upload key from ~/.gradle/gradle.properties if it's there; otherwise the APKs come out unsigned.