From 66826bbce65fc8a6b0fd0156864fbd331774d3df Mon Sep 17 00:00:00 2001 From: Mikkeli Date: Wed, 26 Aug 2026 23:21:17 +0900 Subject: [PATCH] ci: add a provisioning script for an x86_64 Android build runner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gitea runs on an aarch64 host, and 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 CI for this repo was failing for two independent reasons, and fixing the runner's container network would only have exposed the second. An x86_64 runner is a requirement here, not a preference. Registers at android-stuff org scope rather than repo scope, so any Android repo in the org can use the same runner, and labels it by capability (flutter-android) rather than by hostname, so the workflow does not encode which machine happens to provide it. Host mode rather than a container: the machine already has a proven Flutter, JDK and Android SDK/NDK, so jobs build with the same toolchain that produced the verified release and skip a per-run NDK download. The trade is isolation, which the header states plainly — workflows run as that user on that machine. Refuses to register unless the toolchain it would advertise is actually present, so a missing SDK fails at setup with a list rather than inside every later job. Co-Authored-By: Claude Opus 5 --- tool/setup_ci_runner.sh | 160 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100755 tool/setup_ci_runner.sh diff --git a/tool/setup_ci_runner.sh b/tool/setup_ci_runner.sh new file mode 100755 index 0000000..aefd9a4 --- /dev/null +++ b/tool/setup_ci_runner.sh @@ -0,0 +1,160 @@ +#!/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" <