publish to fdroid through CI
This commit is contained in:
parent
3c9ec72b88
commit
c22ed56806
3 changed files with 112 additions and 4 deletions
|
|
@ -142,8 +142,9 @@ jobs:
|
|||
- name: Unit test and build the app
|
||||
working-directory: android
|
||||
# The release APK is unsigned here; signing happens on the HSM runner. Lint's
|
||||
# release checks run as part of assembleRelease.
|
||||
run: gradle --no-daemon testDebugUnitTest assembleDebug assembleRelease
|
||||
# release checks run as part of assembleRelease. The run number is the
|
||||
# versionCode, so each published build is an update to the one before.
|
||||
run: gradle --no-daemon -PtallyVersionCode=${{ github.run_number }} testDebugUnitTest assembleDebug assembleRelease
|
||||
- name: Upload the unsigned release APK
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
|
@ -270,3 +271,69 @@ jobs:
|
|||
status: ${{ job.status }}
|
||||
user: ${{ secrets.NTFY_USER }}
|
||||
password: ${{ secrets.NTFY_PASSWORD }}
|
||||
|
||||
fdroid:
|
||||
name: F-Droid repo
|
||||
# The signed APK into https://fdroid.lerch.org/repo, which is /data/fdroid on the HSM
|
||||
# runner's host: this label is what puts the job on that host, as it does the
|
||||
# signing. The repo itself (config.yml, metadata, the index signing keystore) lives
|
||||
# there, not here; README, "Install with F-Droid".
|
||||
if: github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest-with-hsm
|
||||
needs: sign
|
||||
env:
|
||||
# fdroidserver, pinned: `fdroid update` regenerates and signs the repo's index.
|
||||
FDROID_IMAGE: registry.gitlab.com/fdroid/docker-executable-fdroidserver@sha256:75f6b88ef13a63fc5c49aa4c4dde35607c4a364d22cf57d534aa75c54effa56d
|
||||
FDROID_REPO: /data/fdroid
|
||||
# How many tally builds the repo offers; older ones are deleted.
|
||||
FDROID_KEEP: 3
|
||||
steps:
|
||||
- name: Download the signed release
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: tally-release
|
||||
path: dist
|
||||
- name: Publish to the F-Droid repo
|
||||
# In fdroidserver's container on the host's daemon, as the repo's owner (uid
|
||||
# 1000). The repo is a bind mount of a host path, so the APK cannot be copied in
|
||||
# from this job's workspace by path: it goes over stdin. `--mount` rather than
|
||||
# `-v`, so that on a host without the repo the run fails instead of creating an
|
||||
# empty one. The versionCode is the run number (the android job), and so is the
|
||||
# file name, which is what orders the builds for pruning.
|
||||
#
|
||||
# The index signing keystore's password (the store's and the key's are the same)
|
||||
# is a secret, and only that: it is not on the host beside the keystore, and a
|
||||
# pull request from a fork gets no secrets. It reaches fdroid as environment
|
||||
# variables, passed to docker by name so the value is never on a command line;
|
||||
# the repo's config.yml reads them with {env: ...}.
|
||||
env:
|
||||
APK: dev.lerch.tally_${{ github.run_number }}.apk
|
||||
FDROID_KEY_STORE_PASS: ${{ secrets.FDROID_KEYSTORE_PASS }}
|
||||
FDROID_KEY_PASS: ${{ secrets.FDROID_KEYSTORE_PASS }}
|
||||
run: |
|
||||
set -eu
|
||||
[ -n "$FDROID_KEY_STORE_PASS" ] || { echo "the FDROID_KEYSTORE_PASS secret is not set" >&2; exit 1; }
|
||||
docker run --rm -i -u 1000:1000 -e HOME=/tmp -e APK -e FDROID_KEEP \
|
||||
-e FDROID_KEY_STORE_PASS -e FDROID_KEY_PASS \
|
||||
-e GIT_CONFIG_COUNT=1 -e GIT_CONFIG_KEY_0=safe.directory \
|
||||
-e GIT_CONFIG_VALUE_0=/home/vagrant/fdroidserver \
|
||||
--mount "type=bind,source=${FDROID_REPO},target=/repo" \
|
||||
--entrypoint sh "$FDROID_IMAGE" -euc '
|
||||
[ -f config.yml ] || { echo "no F-Droid repo at /repo (no config.yml)" >&2; exit 1; }
|
||||
cat > "repo/${APK}.part"
|
||||
mv "repo/${APK}.part" "repo/${APK}"
|
||||
ls repo/dev.lerch.tally_*.apk | sort -t _ -k 2 -n | head -n "-${FDROID_KEEP}" |
|
||||
while read -r old; do echo "removing ${old}"; rm -f "${old}"; done
|
||||
. /etc/profile.d/bsenv.sh
|
||||
"${fdroidserver}/fdroid" update
|
||||
ls repo/dev.lerch.tally_*.apk
|
||||
' < dist/tally.apk
|
||||
- name: Notify
|
||||
uses: https://git.lerch.org/lobo/action-notify-ntfy@v2
|
||||
if: always()
|
||||
with:
|
||||
host: ${{ secrets.NTFY_HOST }}
|
||||
topic: ${{ secrets.NTFY_TOPIC }}
|
||||
status: ${{ job.status }}
|
||||
user: ${{ secrets.NTFY_USER }}
|
||||
password: ${{ secrets.NTFY_PASSWORD }}
|
||||
|
|
|
|||
36
README.md
36
README.md
|
|
@ -104,6 +104,41 @@ Android ties an app to its signing key permanently, so a published APK cannot up
|
|||
copy you built yourself (which is signed with your debug key), and the reverse. Uninstall
|
||||
one before installing the other; the app's saved state goes with it.
|
||||
|
||||
### Install with F-Droid
|
||||
|
||||
Every master build is also published to an F-Droid repository, so the phone keeps itself
|
||||
up to date. In F-Droid (or Droid-ify, or Obtainium's "F-Droid third-party repo" source),
|
||||
add this repository; the fingerprint in the URL is what lets the client check the index
|
||||
it downloads:
|
||||
|
||||
```
|
||||
https://fdroid.lerch.org/repo?fingerprint=99387004A55002CF54F291D7ECA6A83AD7C7945E8E633160CC76790AD12E2D6E
|
||||
```
|
||||
|
||||
Then install Tally from it. Each build's versionCode is the CI run number (its version
|
||||
name is `0.1.0+<run>`), so every master build is an update to the one before, and the
|
||||
repo keeps the newest three. The APK is the same one the registry publishes, signed by
|
||||
the HSM key with the certificate in
|
||||
[android/signing-cert.pem](android/signing-cert.pem).
|
||||
|
||||
The repository itself is not in this repo. It is `/data/fdroid` on the HSM runner's host,
|
||||
served by nginx (only its `repo/`), with its `config.yml`, the app metadata, and the
|
||||
keystore that signs the index. The keystore's password is kept off that host: CI has it
|
||||
as the `FDROID_KEYSTORE_PASS` repository secret. The `fdroid` job in the workflow adds
|
||||
each build and runs `fdroid update` there, in fdroidserver's container (pinned in the
|
||||
workflow). To regenerate the index by hand on that host, after editing the metadata say:
|
||||
|
||||
```
|
||||
read -rs FDROID_KEY_STORE_PASS && export FDROID_KEY_STORE_PASS FDROID_KEY_PASS="$FDROID_KEY_STORE_PASS"
|
||||
docker run --rm -u 1000:1000 -e HOME=/tmp -e FDROID_KEY_STORE_PASS -e FDROID_KEY_PASS \
|
||||
-e GIT_CONFIG_COUNT=1 -e GIT_CONFIG_KEY_0=safe.directory \
|
||||
-e GIT_CONFIG_VALUE_0=/home/vagrant/fdroidserver \
|
||||
--mount type=bind,source=/data/fdroid,target=/repo \
|
||||
--entrypoint sh "<FDROID_IMAGE from the workflow>" -euc '
|
||||
. /etc/profile.d/bsenv.sh
|
||||
"$fdroidserver/fdroid" update'
|
||||
```
|
||||
|
||||
### With nix
|
||||
|
||||
This repository is a flake, for the CLI and TUI (the Android app stays with gradle). To
|
||||
|
|
@ -242,6 +277,7 @@ punctuation: the source is ASCII.
|
|||
| Android | `ubuntu-latest` | `zig build android`, the library audit, the JVM unit tests, debug and unsigned release APKs (with lint's release checks) |
|
||||
| Sign | `ubuntu-latest-with-hsm` | master only: signs the APK with the HSM, then every release file with a detached signature logged to sigstore |
|
||||
| Publish | `ubuntu-latest` | master only: uploads the signed files to the generic package registry under the short SHA and `latest` |
|
||||
| F-Droid repo | `ubuntu-latest-with-hsm` | master only: adds the signed APK to the F-Droid repository on the HSM runner's host and regenerates its index |
|
||||
|
||||
Every job reports to ntfy. Zlint and Gradle are downloaded at pinned versions and checked
|
||||
against pinned SHA-256 digests; Zig's version comes from `build.zig.zon`.
|
||||
|
|
|
|||
|
|
@ -12,8 +12,13 @@ android {
|
|||
applicationId = "dev.lerch.tally"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
// Every build an updater should offer needs a larger versionCode than the one
|
||||
// installed: Android, F-Droid and Obtainium all compare it, not the name. CI
|
||||
// passes its run number (`-PtallyVersionCode=N`); a local build is 1, below
|
||||
// anything CI publishes. The name carries the same number, so it is visible.
|
||||
val tallyVersionCode = (findProperty("tallyVersionCode") as String?)?.toInt()
|
||||
versionCode = tallyVersionCode ?: 1
|
||||
versionName = if (tallyVersionCode != null) "0.1.0+$tallyVersionCode" else "0.1.0"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// Only the ABIs `zig build android` produces. Anything else would package an
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue