NexusIRL is a free, open-source IRL live streaming app for Android: SRTLA bonding of mobile data and Wi-Fi, adaptive bitrate, SRT and RTMP, Twitch and Kick chat, OBS remote control. An Android port of Moblin.
Releases are built by .github/workflows/release.yml when a tag such as v0.1.0 is pushed.
The workflow runs the tests and lint, builds signed APKs (one each for arm64-v8a, armeabi-v7a
and x86_64, plus a universal one), and attaches them to a GitHub Release together with
SHA256SUMS. Releases are marked as pre-releases until v1.
Before publishing, the workflow checks that:
main and CI (ci.yml) passed on it; it waits up to an hour for a CI
run still in progress, so tag a commit that CI ran on (the last one of a push);NEXUSIRL_REQUIRE_FFMPEG=1), so the interop tests cannot
be skipped;apksigner verifies every APK, each signed only by the release certificate
(EXPECTED_CERT_SHA256 in the workflow, the same value as AllowedAPKSigningKeys in the
F-Droid recipe; change both if the key ever changes);v, and its version code is the one below.The signing key never goes in the repository. Create it once, on your own machine:
keytool -genkeypair -keystore nexusirl-release.jks -alias nexusirl \
-keyalg RSA -keysize 4096 -validity 10000 -dname "CN=NexusIRL"
Use a pseudonymous -dname such as the one above; it is embedded in every APK.
Back up nexusirl-release.jks and its passwords somewhere safe (a password manager).
If the key is lost, existing installs cannot be updated: users would have to uninstall first.
Then add four repository secrets (GitHub → Settings → Secrets and variables → Actions):
| Secret | Value |
|---|---|
NEXUSIRL_KEYSTORE_BASE64 |
base64 -w0 nexusirl-release.jks (PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes("nexusirl-release.jks"))) |
NEXUSIRL_KEYSTORE_PASSWORD |
the keystore password |
NEXUSIRL_KEY_ALIAS |
nexusirl |
NEXUSIRL_KEY_PASSWORD |
the key password |
versionCode (always +1) and versionName (semantic versioning) in
app/build.gradle.kts. The APKs’ own version codes are 10 × versionCode plus 0 (universal),
1 (armeabi-v7a), 2 (arm64-v8a) or 3 (x86_64), so every APK of a release has its own code
and a phone gets the most specific one. Then write:
docs/release-notes/v<version>.md (used instead of GitHub’s
commit summary),CHANGELOG.md entry (move it from “unreleased” to the release date),fastlane/metadata/android/en-US/changelogs/<code>.txt, where <code> is
10 × versionCode + 3 (the highest APK code, which F-Droid reads it for; up to v0.4.0 it was
plain versionCode), 500 characters at most.git tag v0.1.0 && git push origin v0.1.0.Set the same variables, with NEXUSIRL_KEYSTORE pointing at the .jks file, then run
./gradlew assembleRelease -Pabi=arm64-v8a (or armeabi-v7a, x86_64; leave out -Pabi for
the universal APK). Without them, Gradle produces an unsigned app-release-unsigned.apk. Each APK
is a separate build, as F-Droid builds them; debug builds are universal.
Run the Release workflow by hand (Actions → Release → Run workflow, or
gh workflow run release.yml). It builds and signs the APKs and uploads them as a workflow
artifact, but publishes nothing.
./gradlew :app:licensee (also part of check) fails the build if a dependency is not under an
allowed permissive licence (Apache-2.0, MIT, BSD). See docs/PORT_PLAN.md §8.4.
Monthly, and before each release:
gh api repos/<owner>/<action>/commits/<tag> --jq .sha), read the release notes of major
versions, and merge once CI passes. The pull request’s CI does not run release.yml, so start a
release dry run (below) after merging.gradle/libs.versions.toml (AGP, Kotlin,
Compose BOM, OkHttp, kotlinx, MapLibre, Tink, test libraries). Prefer releases that are a few
weeks old. Update, regenerate the verification metadata (below), review that the new
checksums are for the expected groups, run the full gate and the UI tests, and test on a
phone when the camera, codec or network stack changed. The build uses AGP 9 (Kotlin built
into AGP, so Android modules have no kotlin-android plugin), Gradle 9 and compile SDK 37;
the target SDK is a separate decision because it changes app behaviour.gradle/verification-metadata.xml pins the SHA-256 of every downloaded dependency and plugin, so
a tampered or swapped artifact fails the build. After adding or upgrading a dependency, regenerate
it and review the diff:
./gradlew --write-verification-metadata sha256 --refresh-dependencies ktlintCheck test lint :app:licensee assembleDebug assembleDebugAndroidTest assembleRelease
ANDROID_SERIAL=emulator-5554 ./gradlew --write-verification-metadata sha256 :app:connectedDebugAndroidTest
--refresh-dependencies matters: with a warm cache Gradle skips BOM files, and CI on Linux then
fails verification. The second command records the UI-test runner, which only the emulator run
(CI’s “ui-tests” job) downloads. Gradle only ever adds entries, so after a big upgrade delete the
file first and run both commands to drop versions no longer used. A fresh file only has the
aapt2 jar of the computer that made it: add the other two (-linux, -osx, -windows) by hand
from Google Maven, checking each SHA-1 against its .sha1 file, or CI on Linux fails.
aapt2 is published per operating system and Gradle only records the jar for the machine it runs
on. Keep the Linux, macOS and Windows aapt2-<version>-<os>.jar entries in sync by hand
(download each from Google Maven and run sha256sum).
Status: the first submission (fdroiddata merge request !54816, v0.4.0) was closed by F-Droid’s reviewer until the project is three months old, as F-Droid’s inclusion policy requires. Reopen it (or open a new one) on or after 2027-01-19, with the recipe updated to the latest release and its per-ABI builds (below).
fdroid/io.github.mesmerize08.nexusirl.yml is the build recipe in fdroiddata format, exactly as
fdroid rewritemeta writes it (fdroiddata’s CI rejects any other layout, so it has no comments).
Checked on 2026-10-29 for v0.4.0 with F-Droid’s own tools
(registry.gitlab.com/fdroid/docker-executable-fdroidserver): fdroid lint and rewritemeta
against fdroiddata’s config/ are clean, fdroid scanner finds no problems, and fdroid build
builds it on their JDK 21 build environment. Re-run them before submitting or after build changes:
# In a work directory holding metadata/io.github.mesmerize08.nexusirl.yml (commit set to a pushed tag or commit):
docker run --rm --entrypoint sh -v "$PWD/metadata:/meta:ro" registry.gitlab.com/fdroid/docker-executable-fdroidserver:master -c \
'. /etc/profile.d/bsenv.sh; mkdir -p /work && cp -r /meta /work/metadata && cd /work && ${fdroidserver}/fdroid scanner -v io.github.mesmerize08.nexusirl && ${fdroidserver}/fdroid build -v --no-tarball --test io.github.mesmerize08.nexusirl'
Requirements this relies on: no dependency metadata block in the APK (dependenciesInfo), one
APK per build (-Pabi, or none for v0.4.0 and earlier), any JDK 17 or newer, and no upstream/ submodule needed for the
build. Lint needs fdroiddata’s config/ folder: run it in a sparse clone of fdroiddata
(git sparse-checkout set config) with the recipe in metadata/, then fdroid lint and
fdroid rewritemeta (no diff means the layout is right). To submit: update the build entry to the
latest release, with the tag’s full commit hash (fdroiddata does not accept tag names), copy
the file to metadata/ in a fork of https://gitlab.com/fdroid/fdroiddata, and open a merge
request. The recipe declares the NonFreeNet anti-feature, as chat and sign-in use Twitch, Kick
and YouTube.
The build is reproducible: F-Droid’s build of v0.4.0 matches the release’s universal APK, so the
recipe sets Binaries (the release’s universal APK) and AllowedAPKSigningKeys (the release
certificate’s SHA-256), and F-Droid publishes our signed APK. Users can move between GitHub and
F-Droid installs. fdroid build with a reference binary checks exactly that (“compared built
binary to supplied reference binary successfully”), so run it before changing the build.
Since v0.5.0, F-Droid builds one APK per ABI. On 2026-11-08 F-Droid’s builds of v0.5.0
(291b97d) for armeabi-v7a, arm64-v8a and x86_64 each “compared built binary to supplied
reference binary successfully” against the published release APKs, with the allowed signer. The
v0.4.0 build keeps its universal APK as binary. For each new release:
versionCode 10 × versionCode plus 1, 2 or 3, the
tag’s full commit hash, gradleprops: [abi=<abi>] and
binary: https://github.com/mesmerize08/NexusIRL/releases/download/v%v/NexusIRL-v%v-<abi>.apk;CurrentVersion and CurrentVersionCode (the highest code; VercodeOperation gives the
three codes for automatic updates);fdroid rewritemeta, lint and build as above.