#!/usr/bin/env bash # Provision the development Orin as the yukart org's Gitea Actions runner for Rust # services: native L4T builds whose binaries load on the car Orin. # # sudo ./scripts/setup-ci-runner.sh --check # pre-flight only, changes nothing # sudo RUNNER_TOKEN=xxxx ./scripts/setup-ci-runner.sh # # The token is ORG-scoped (yukart), from /org/yukart/settings/actions/runners # or `POST /api/v1/orgs/yukart/actions/runners/registration-token`. # Re-running is safe: every step checks before it changes anything, and a registered # runner's config is left alone. # # Modelled on halogen's runners (android-stuff iperf3-gui tool/setup_ci_runner.sh): # # ── WHY HOST MODE ─────────────────────────────────────────────────────────── # The point of building here is the sysroot: this unit runs the same L4T and # Ubuntu release as the car Orin, so a binary cannot reference a glibc symbol the # car lacks (project-yukart docs/REPOS.md). A container would build against # whatever image the workflow names and throw that away. # # ── WHY A DEDICATED USER, WITH ITS OWN TOOLCHAIN ─────────────────────────── # In host mode the service account is the only sandbox: a workflow is code and runs # as this account. Not mikkeli (home, SSH keys, passwordless sudo), not root. Its # own rustup under its own HOME, so CI never shares or drifts with an interactive # install, and `rustup` can add components without shared-write tricks. set -euo pipefail VERSION="${RUNNER_VERSION:-3.3.0}" SHA256_ARM64="${RUNNER_SHA256:-afdad29640ce9c2a6979f292e18313e14a22d39ca5f66bdd0add6480590b38b6}" INSTANCE="${GITEA_INSTANCE:-http://pi5-16.local:3005}" RUNNER_USER="${RUNNER_USER:-gitea-runner}" RUNNER_HOME="${RUNNER_HOME:-/var/lib/gitea-runner}" CONFIG_DIR="$RUNNER_HOME/yukart" BIN="${RUNNER_BIN:-/usr/local/bin/gitea-runner}" UNIT_NAME="gitea-runner-yukart.service" UNIT="/etc/systemd/system/$UNIT_NAME" # Machine-named registration, as halogen-yukart is. RUNNER_NAME="${RUNNER_NAME:-$(hostname)-yukart}" # One label for the machine, one for what it can do: workflows ask for the capability. LABELS="${RUNNER_LABELS:-orin-nano-2:host,l4t-rust:host}" CHECK_ONLY=0 [[ "${1:-}" == "--check" ]] && CHECK_ONLY=1 [[ $EUID -eq 0 ]] || { echo "Run with sudo: sudo RUNNER_TOKEN=... $0" >&2; exit 1; } if [[ $CHECK_ONLY -eq 0 ]]; then : "${RUNNER_TOKEN:?set RUNNER_TOKEN to a yukart org registration token (see header)}" fi say() { printf '\n==> %s\n' "$1"; } # ── Pre-flight ────────────────────────────────────────────────────────────── say "Pre-flight" [[ "$(uname -m)" == aarch64 ]] || { echo "not aarch64: this is the wrong machine" >&2; exit 1; } head -1 /etc/nv_tegra_release 2>/dev/null | sed 's/^/ /' || echo " (no /etc/nv_tegra_release)" ldd --version | sed -n "1s/^/ /p" missing=0 # node runs the JavaScript actions (actions/checkout) in host mode; objdump is what # scripts/check-glibc.sh reads; cc links, and builds C dependencies (zstd-sys). for cmd in node git curl cc objdump pkg-config sha256sum; do if PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin command -v "$cmd" >/dev/null; then printf ' ok %s\n' "$cmd" else printf ' MISSING %s (not on systemd default PATH)\n' "$cmd" missing=1 fi done if ! curl -fsS -o /dev/null "$INSTANCE/api/v1/version"; then printf ' MISSING %s unreachable\n' "$INSTANCE" missing=1 fi if [[ $missing -ne 0 ]]; then echo "Refusing to register: a runner advertising these labels must actually build." >&2 exit 1 fi if [[ $CHECK_ONLY -eq 1 ]]; then say "Pre-flight passed; nothing was created. Re-run with RUNNER_TOKEN set to provision." exit 0 fi # ── Service account ───────────────────────────────────────────────────────── say "Service account: $RUNNER_USER" if id "$RUNNER_USER" &>/dev/null; then echo " exists" else # A shell, not nologin: host-mode steps run as shell scripts. No password, so # the account is only reachable through root. useradd --system --create-home --home-dir "$RUNNER_HOME" --shell /bin/bash \ --comment "Gitea Actions runner (yukart, l4t-rust)" "$RUNNER_USER" echo " created" fi install -d -o "$RUNNER_USER" -g "$RUNNER_USER" -m 0750 "$RUNNER_HOME" "$CONFIG_DIR" "$CONFIG_DIR/workspace" id "$RUNNER_USER" # ── Rust toolchain, the account's own ────────────────────────────────────── say "Rust toolchain for $RUNNER_USER" as_runner() { sudo -u "$RUNNER_USER" env HOME="$RUNNER_HOME" CARGO_HOME="$RUNNER_HOME/.cargo" \ RUSTUP_HOME="$RUNNER_HOME/.rustup" PATH="$RUNNER_HOME/.cargo/bin:/usr/bin:/bin" "$@"; } if as_runner sh -c 'command -v rustup' >/dev/null 2>&1; then as_runner rustup update stable --no-self-update | tail -1 else as_runner sh -c 'curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --no-modify-path' fi as_runner rustup component add clippy rustfmt as_runner rustc --version as_runner cargo --version # ── Binary ────────────────────────────────────────────────────────────────── say "gitea-runner $VERSION" 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-arm64" echo " $url" curl -fSL --retry 3 -o "$BIN.tmp" "$url" echo "$SHA256_ARM64 $BIN.tmp" | sha256sum -c --quiet || { rm -f "$BIN.tmp"; echo "checksum mismatch" >&2; exit 1; } chmod 0755 "$BIN.tmp" mv "$BIN.tmp" "$BIN" fi "$BIN" --version # ── Config and registration ───────────────────────────────────────────────── say "Config: $CONFIG_DIR/config.yaml" CONFIG="$CONFIG_DIR/config.yaml" if [[ -f "$CONFIG_DIR/.runner" ]]; then echo " registered already — leaving config and registration alone" else yaml_labels=$(tr ',' '\n' <<<"$LABELS" | sed 's/.*/ - "&"/') cat > "$CONFIG" < "$UNIT" <