Adding support for Obtainium with fixed Signing Keys and apk Release publishing

This commit is contained in:
2026-09-07 11:31:57 +02:00
parent 5ec71f6ae8
commit 6c9c650b68
4 changed files with 181 additions and 14 deletions
+13 -3
View File
@@ -38,8 +38,8 @@ Widget tests run against a mocked backend: build a `MockClient` that answers by
| Workflow | Builds | Result |
| --- | --- | --- |
| `BuildLinux.yml` | `flutter build linux --release` | `ac_web-linux-x64-<tag>.tar.gz` (the whole bundle directory) as an artifact |
| `BuildAndroid.yml` | `flutter build apk --release` | `ac_web-<tag>.apk` as an artifact |
| `BuildLinux.yml` | `flutter build linux --release` | `ac_web-linux-x64-<tag>.tar.gz` (the whole bundle directory), attached to the release and uploaded as an artifact |
| `BuildAndroid.yml` | `flutter build apk --release` | `ac_web-<tag>.apk`, attached to the release and uploaded as an artifact |
| `PublishDocker.yml` | `ac_web/Dockerfile` | image pushed to the Gitea registry as `<tag>`, `latest` and the short sha |
Notes that are easy to get wrong:
@@ -50,7 +50,17 @@ Notes that are easy to get wrong:
- **`actions/upload-artifact` must stay at `@v3`.** `@v4` bundles `@actions/artifact` v2, which aborts with `GHESNotSupportedError` whenever `GITHUB_SERVER_URL` is not `github.com` -- on this instance it is the gitea host, so the check fires before any request and cannot be worked around on the server side. `@v3` uses the older artifact api gitea implements.
- The workflows are read from the tag the release points at, so they have to be committed **before** the release is created.
- Artifacts and the built files carry the tag name (`github.ref_name`); the docker image is pushed as the tag, as `latest` and as the short sha.
- The apk is signed with a debug key, because `android/app/build.gradle.kts` still has no release signing config.
- **The built files are attached to the release, not only uploaded as artifacts.** Artifacts live under the workflow run, are zipped and need a login; a release asset is a plain download on the release page. The upload is a `curl` against `POST <server>/api/v1/repos/<owner>/<repo>/releases/<id>/assets?name=<file>` with `${{ github.token }}`, and the release id comes from the event (falling back to a lookup by tag for a manual re-run). Obtainium needs the apk exactly there, see below.
- **The apk must keep one signing key forever.** Android identifies an app by its application id *and* its certificate, so an update signed with a different key is rejected (`INSTALL_FAILED_UPDATE_INCOMPATIBLE`) and the app has to be uninstalled first. The debug key is regenerated on every runner, so it can never be used for a published apk. `android/app/build.gradle.kts` reads the key from `android/key.properties` (gitignored) and falls back to the debug key when that file is missing, so `flutter run --release` still works locally. The workflow writes the file from four secrets and fails loudly when the first one is empty: `ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`. Create the keystore once and keep a backup — losing it means every installed app has to be uninstalled before it can be updated again:
```bash
keytool -genkeypair -v -keystore ac_web-release.p12 -storetype PKCS12 \
-keyalg RSA -keysize 2048 -validity 10000 -alias ac-web
base64 -w0 ac_web-release.p12 # the value of ANDROID_KEYSTORE_BASE64
```
- **The apk version comes from the release, not from the pubspec.** `version: 1.0.0+1` never moves, so the workflow passes `--build-name <tag without a leading v>` and `--build-number ${{ github.run_number }}`; the run number only grows, and android refuses an apk whose version code is lower than the installed one. A tag that is not a dotted number keeps the pubspec version and only warns.
- **Obtainium** tracks the app through the Gitea api: add the repository url (`https://<host>/ArtificialCompany/AcWeb`) and pick `Codeberg (Forgejo)` under "Override Source" — self hosted instances are not detected automatically, but the source builds its api calls from the host in the url. It reads `/api/v1/repos/.../releases` and downloads the release assets, so the repository has to be readable without a login from the phone.
- The docker image is self contained: it compiles the web bundle itself, so `docker build -f ac_web/Dockerfile ac_web` gives the same result locally. It serves the static bundle with nginx (`ac_web/nginx.conf`), which falls back to `index.html` so deep links survive a reload. The backend address is compiled into the bundle, so the host in `main.dart` must be reachable from the browser of every visitor, not only from the container.
## Backend connection