Files
evcharger-app/docs/gitea-actions.md
T
feedc0de 53cf5f432a
Build Linux and Android / build (push) Successful in 2m18s
split aab and apk artifacts to speed up downloading on the phone
2026-08-16 14:29:10 +02:00

7.2 KiB

Gitea Actions builds

The workflow in .gitea/workflows/build.yml builds the pinned toolchain image, then compiles the Linux and Android packages. Its toolchain is:

  • Qt 6.11.1 from Qt's public binary repository, installed with aqtinstall;
  • Ubuntu 24.04 and JDK 21;
  • Android API and Build Tools 36;
  • Android NDK 27.2.12479018;
  • a pinned KDAB Android OpenSSL revision;
  • arm64-v8a and armeabi-v7a device ABIs.

The complete toolchain image is stored in registry.brunner.ninja. The first run for a new toolchain revision downloads Qt and the Android SDK, then publishes the image. Ordinary runs—including runs on a new or freshly cleaned runner—pull that image and skip the image build. Bump the rN suffix of BUILD_IMAGE in the workflow whenever the Dockerfile toolchain changes. Tags are treated as immutable; never reuse an existing revision for changed content.

Self-hosted Actions

The workflow does not fetch Actions from gitea.com or GitHub. Its action implementations are mirrored on code.brunner.ninja and use their local v4 tags:

  • actions/checkout;
  • actions/cache;
  • actions/upload-artifact.

The tags are resolved entirely through the local mirrors. Do not change these references back to upstream URLs. This command audits all workflow action references:

rg '^[[:space:]]*uses:' .gitea/workflows

Documentation-only commits do not start this workflow. Source, build-system, toolchain, tag, and manually dispatched runs continue to build the application.

Qt's current Gradle template is intentionally not copied into the source tree. androiddeployqt overlays the custom manifest and resources from evcharger-app/android onto the template supplied by Qt 6.11.1. This keeps Qt, Gradle, and the Android Gradle Plugin in sync.

Runner requirements

Enable Actions in the repository settings and register a trusted Gitea Runner with the workflow label linux_amd64. Use host mode because the workflow itself starts rootless Podman build containers:

runner:
  labels:
    - "linux_amd64:host"

The runner host needs Podman, Bash, Git, and Node.js. Keep this host-mode runner restricted to repositories you trust: workflows execute commands as the runner user and can access everything that user can access.

For example, after downloading act_runner, obtain a repository runner token from Settings > Actions > Runners, then run:

./act_runner register --no-interactive \
  --instance https://your-gitea.example \
  --token YOUR_REGISTRATION_TOKEN \
  --name evcharger-builder \
  --labels linux_amd64:host

./act_runner daemon

The workflow timeout is three hours because the uncached first image build is large. The toolchain image is cached independently from the ccache and Gradle caches stored by Gitea Actions.

Toolchain registry credentials

The registry is backed by Quay. Use a Quay robot account with write access to the feedc0de/evcharger-app repository; this avoids tying CI to a person's account. Add its credentials as repository secrets under Settings > Actions > Secrets:

Secret Value
REGISTRY_USERNAME Quay robot username, including its namespace prefix
REGISTRY_PASSWORD Quay robot token

The image is published as registry.brunner.ninja/feedc0de/evcharger-app:qt-6.11.1-r1. The workflow logs in before pulling because this repository is not anonymously readable. The robot needs write permission so the first run of a new revision can push it. Credentials are written to a per-job temporary authentication file and removed before the step exits.

Signing secrets

Without a keystore, the workflow deliberately produces unsigned validation packages. This allows pull requests to test Android compilation without release credentials.

In Settings > Actions > Secrets, add these repository secrets:

Secret Value
ANDROID_KEYSTORE_BASE64 Release keystore encoded as one base64 line
ANDROID_KEYSTORE_ALIAS Private-key alias in the keystore
ANDROID_KEYSTORE_STORE_PASS Keystore password
ANDROID_KEYSTORE_KEY_PASS Private-key password

Encode the keystore locally with:

base64 -w0 android_release.keystore

Never commit the keystore. Root-level .keystore and .jks files are ignored. The workflow's decoded temporary copy is deleted when the Android script exits.

Check an uncertain password and alias safely

keytool is included in a JDK. This command prompts for the store password and lists all aliases; the password is not placed in shell history or the process list:

keytool -list -v -keystore android_release.keystore

That only proves the store password. To verify the selected alias and its private-key password without modifying the keystore, sign a disposable JAR:

probe_dir="$(mktemp -d)"
trap 'rm -rf -- "$probe_dir"' EXIT
printf 'keystore test\n' > "$probe_dir/probe.txt"
jar --create --file "$probe_dir/probe.jar" -C "$probe_dir" probe.txt
jarsigner -keystore android_release.keystore "$probe_dir/probe.jar" YOUR_ALIAS
jarsigner -verify "$probe_dir/probe.jar"

jarsigner prompts first for the store password and, when different, for the private-key password. A successful signing followed by jar verified confirms all three values. Do not use -storepass or -keypass on an interactive shell, because command-line arguments can leak through history and process listings.

Artifacts and Android versions

Each successful workflow exposes three downloads in the Gitea Actions run:

evcharger-linux/
  evcharger-app

evcharger-apk/
  evcharger-<version>-universal.apk

evcharger-aab/
  evcharger-<version>.aab

Artifacts are retained for 30 days. APK and AAB packages are separate downloads so phone testing does not also download the Play bundle. Gitea Actions serves artifacts as ZIP archives, but compression is disabled because APK and AAB files are already ZIP containers. A signed universal APK is suitable for sideloading; an unsigned one is only a validation artifact. Upload the signed AAB to Google Play, which produces device-specific APKs.

The Android versionCode defaults to the monotonically increasing Gitea Actions run number. Tags become the versionName after an optional leading v is removed; other builds use 0.1.0-ci.<run number>. Override these with workflow environment values ANDROID_VERSION_CODE and ANDROID_VERSION_NAME if needed.

Local container builds

podman build -t evcharger-build:qt-6.11.1 .

podman run --rm --userns=keep-id -v "$PWD:/workspace" -w /workspace \
  evcharger-build:qt-6.11.1 bash ci/build-linux.sh

podman run --rm --userns=keep-id -v "$PWD:/workspace" -w /workspace \
  evcharger-build:qt-6.11.1 bash ci/build-android.sh

Pass the signing variables through --env or an environment file for a signed local Android build. Do not add secrets to the Dockerfile or image.

Qt open-source licensing note

The public Qt binaries do not require a commercial account. Distribution must still follow every linked module's license. Qt Charts is GPLv3-only for open-source users (or commercially licensed), so distributing this application with Qt Charts requires GPLv3-compatible distribution of the combined program and corresponding source. Review and document that licensing choice before publishing the APK/AAB, or use a commercial Qt license.