#!/usr/bin/env bash # Register this machine as a Gitea Actions runner for the android-stuff org. # # WHY THIS MACHINE, AND NOT THE GITEA HOST # Gitea runs on pi5-16 (aarch64). Flutter cannot build an Android APK on a # Linux arm64 host at all — the SDK ships no arm64-host gen_snapshot # (flutter/flutter#189724, #75864, open as of July 2026). So an Android build # runner has to be x86_64 regardless of how the Gitea host is configured. # # WHY HOST MODE, AND NOT A CONTAINER # This machine already has a proven Flutter + Android SDK + NDK toolchain. In # host mode the job uses it directly: no SDK download, no ~1GB NDK pull per # run, and CI builds with the exact toolchain that produced the verified # release. The trade is isolation — a workflow in this org runs commands as # this user on this machine. That is the normal self-hosted-runner bargain, but # it is a real one: only grant it for repos whose workflows you trust. # # USAGE # 1. Get an ORG-scoped registration token: # http://pi5-16.local:3005/org/android-stuff/settings/actions/runners # → "Create new runner" → copy the token # Org scope, not repo scope, so every android-stuff repo can use it. # 2. RUNNER_TOKEN=xxxx ./tool/setup_ci_runner.sh # # Re-running is safe: it skips the download if the binary is current and # refuses to clobber an existing registration. set -euo pipefail VERSION="${RUNNER_VERSION:-3.3.0}" INSTANCE="${GITEA_INSTANCE:-http://pi5-16.local:3005}" RUNNER_NAME="${RUNNER_NAME:-$(hostname)-android}" RUNNER_HOME="${RUNNER_HOME:-$HOME/.local/share/gitea-runner}" BIN="${RUNNER_BIN:-$HOME/.local/bin/gitea-runner}" # Capability-based label, deliberately not host-based: it says what the runner # can do, so the workflow does not encode which machine happens to provide it. LABEL="${RUNNER_LABEL:-flutter-android}" : "${RUNNER_TOKEN:?set RUNNER_TOKEN to an org registration token (see header)}" echo "==> Checking the toolchain this runner promises to provide" missing=0 for cmd in flutter dart java cmake ninja node git unzip; do if command -v "$cmd" >/dev/null; then printf ' ok %s\n' "$cmd" else printf ' MISSING %s\n' "$cmd" missing=1 fi done SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" if [[ -n "$SDK" && -d "$SDK/ndk" && -d "$SDK/build-tools" ]]; then echo " ok Android SDK at $SDK (ndk: $(ls "$SDK/ndk" | tr '\n' ' '))" else echo " MISSING Android SDK with ndk/ and build-tools/ (ANDROID_HOME=${SDK:-unset})" missing=1 fi if [[ $missing -ne 0 ]]; then echo echo "Refusing to register: a runner advertising '$LABEL' must actually" >&2 echo "provide these, or every job will fail confusingly instead of here." >&2 exit 1 fi echo "==> Installing gitea-runner $VERSION" mkdir -p "$(dirname "$BIN")" "$RUNNER_HOME" if [[ -x "$BIN" ]] && "$BIN" --version 2>/dev/null | grep -qF "$VERSION"; then echo " already at $VERSION" else url="https://dl.gitea.com/gitea-runner/${VERSION}/gitea-runner-${VERSION}-linux-amd64" echo " downloading $url" curl -fSL --retry 3 -o "$BIN.tmp" "$url" chmod +x "$BIN.tmp" mv "$BIN.tmp" "$BIN" fi "$BIN" --version echo "==> Writing config" CONFIG="$RUNNER_HOME/config.yaml" if [[ -f "$CONFIG" ]]; then echo " $CONFIG exists, leaving it alone" else cat > "$CONFIG" < Registering with $INSTANCE" if [[ -f "$RUNNER_HOME/.runner" ]]; then echo " already registered (found $RUNNER_HOME/.runner) — skipping." echo " To re-register: remove that file and the runner in the Gitea UI first." else (cd "$RUNNER_HOME" && "$BIN" register \ --no-interactive \ --instance "$INSTANCE" \ --token "$RUNNER_TOKEN" \ --name "$RUNNER_NAME" \ --labels "${LABEL}:host" \ --config "$CONFIG") fi cat < Done. Start it with: cd $RUNNER_HOME && $BIN daemon --config $CONFIG Or install a user service that survives logout: systemctl --user enable --now gitea-runner loginctl enable-linger $USER # so it runs without an active session A unit file is written alongside this script's output at: $RUNNER_HOME/gitea-runner.service Copy it to ~/.config/systemd/user/ and 'systemctl --user daemon-reload'. EOF cat > "$RUNNER_HOME/gitea-runner.service" <