From da7dbfaa4e772d09fa2cbb5172631d47808b3d64 Mon Sep 17 00:00:00 2001 From: Eason WaveKat Date: Mon, 18 May 2026 22:26:51 +1200 Subject: [PATCH 1/3] feat: add scripts to install self-hosted GHA runners on aoc-m3l Adds setup/uninstall shell scripts to install N self-hosted GitHub Actions runners on a single Linux host and register them with the wavekat org. Defaults to 4 runners, each as its own systemd service so jobs run in parallel. Includes Ubuntu 24.04 install guidance for `gh` when no RUNNER_TOKEN is provided. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/setup-gha-runners.sh | 162 +++++++++++++++++++++++++++++++ scripts/uninstall-gha-runners.sh | 73 ++++++++++++++ 2 files changed, 235 insertions(+) create mode 100755 scripts/setup-gha-runners.sh create mode 100755 scripts/uninstall-gha-runners.sh diff --git a/scripts/setup-gha-runners.sh b/scripts/setup-gha-runners.sh new file mode 100755 index 0000000..5170c5d --- /dev/null +++ b/scripts/setup-gha-runners.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +# +# Install N self-hosted GitHub Actions runners on a single Linux host +# and register them with the `wavekat` org. +# +# Designed for the `aoc-m3l` workstation but works on any Linux x64/arm64. +# Each runner lives in its own directory and runs as its own systemd service, +# so they execute jobs in parallel. +# +# Usage (run on the target machine, as a user with sudo): +# +# # Easiest: let the script fetch a registration token via gh CLI. +# # (`gh auth login` once with an account that has wavekat org admin) +# ./setup-gha-runners.sh +# +# # Or pass a token explicitly (valid 1h, can register multiple runners): +# RUNNER_TOKEN=AAAA... ./setup-gha-runners.sh +# +# # Override defaults: +# RUNNER_COUNT=6 RUNNER_PREFIX=aoc-m3l RUNNER_LABELS=aoc-m3l,gpu \ +# ./setup-gha-runners.sh +# +# Re-running is safe: existing runners with the same name are removed and +# re-registered. + +set -euo pipefail + +ORG="${RUNNER_ORG:-wavekat}" +COUNT="${RUNNER_COUNT:-4}" +PREFIX="${RUNNER_PREFIX:-$(hostname -s)}" +BASE_DIR="${RUNNER_BASE_DIR:-/opt/actions-runners}" +RUNNER_USER="${RUNNER_USER:-$(id -un)}" +EXTRA_LABELS="${RUNNER_LABELS:-${PREFIX}}" +RUNNER_VERSION="${RUNNER_VERSION:-}" # empty = latest + +log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m!!\033[0m %s\n' "$*" >&2; } +die() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; } + +[[ "$(uname -s)" == "Linux" ]] || die "this script targets Linux (got $(uname -s))" + +case "$(uname -m)" in + x86_64) ARCH=x64 ;; + aarch64|arm64) ARCH=arm64 ;; + *) die "unsupported arch $(uname -m)" ;; +esac + +if [[ -z "${RUNNER_VERSION}" ]]; then + log "resolving latest runner version from github.com/actions/runner" + RUNNER_VERSION="$(curl -fsSL https://api.github.com/repos/actions/runner/releases/latest \ + | grep -oE '"tag_name": *"v[^"]+"' \ + | head -n1 \ + | sed -E 's/.*"v([^"]+)".*/\1/')" + [[ -n "${RUNNER_VERSION}" ]] || die "could not resolve latest runner version" +fi +log "runner version: ${RUNNER_VERSION} arch: ${ARCH}" + +missing_gh_help() { + cat >&2 <<'EOF' + +No RUNNER_TOKEN set, and `gh` CLI is not installed. + +Pick one: + + A) Install gh on Ubuntu 24.04 (official apt repo), then re-run: + + sudo apt-get update + sudo apt-get install -y curl ca-certificates + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + sudo chmod a+r /usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + | sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null + sudo apt-get update + sudo apt-get install -y gh + gh auth login # use an account with wavekat org admin + + B) Fetch a registration token elsewhere and export it (valid 1h, can + register multiple runners during that window): + + # on any machine with gh authed as a wavekat admin: + gh api -X POST /orgs/wavekat/actions/runners/registration-token --jq .token + + # then on this host: + RUNNER_TOKEN= ./setup-gha-runners.sh +EOF +} + +get_token() { + if [[ -n "${RUNNER_TOKEN:-}" ]]; then + printf '%s' "${RUNNER_TOKEN}" + return + fi + if ! command -v gh >/dev/null 2>&1; then + missing_gh_help + exit 1 + fi + gh api -X POST "/orgs/${ORG}/actions/runners/registration-token" --jq .token \ + || die "failed to fetch registration token (is gh authed as a wavekat admin?)" +} + +# Install OS packages the runner needs. +log "installing prerequisites" +sudo apt-get update -y +sudo apt-get install -y --no-install-recommends \ + curl ca-certificates tar jq libicu-dev git + +# Create a shared cache dir for the tarball. +sudo mkdir -p "${BASE_DIR}" +sudo chown "${RUNNER_USER}:${RUNNER_USER}" "${BASE_DIR}" + +TARBALL="actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz" +TARBALL_URL="https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/${TARBALL}" +CACHE_TARBALL="${BASE_DIR}/.cache/${TARBALL}" + +if [[ ! -f "${CACHE_TARBALL}" ]]; then + log "downloading ${TARBALL}" + mkdir -p "${BASE_DIR}/.cache" + curl -fsSL -o "${CACHE_TARBALL}" "${TARBALL_URL}" +fi + +TOKEN="$(get_token)" +[[ -n "${TOKEN}" ]] || die "got empty registration token" + +for i in $(seq 1 "${COUNT}"); do + NAME="${PREFIX}-${i}" + DIR="${BASE_DIR}/${NAME}" + log "configuring runner ${NAME} at ${DIR}" + + # If a service already exists for this name, uninstall it first so we can + # cleanly re-register. + if systemctl list-unit-files | grep -q "actions.runner.${ORG}.${NAME}.service"; then + warn "existing service for ${NAME} found — removing" + if [[ -d "${DIR}" ]]; then + ( cd "${DIR}" && sudo ./svc.sh stop || true ) + ( cd "${DIR}" && sudo ./svc.sh uninstall || true ) + ( cd "${DIR}" && ./config.sh remove --token "${TOKEN}" || true ) + fi + fi + + rm -rf "${DIR}" + mkdir -p "${DIR}" + tar -xzf "${CACHE_TARBALL}" -C "${DIR}" + + ( cd "${DIR}" && ./config.sh \ + --unattended \ + --replace \ + --url "https://github.com/${ORG}" \ + --token "${TOKEN}" \ + --name "${NAME}" \ + --runnergroup "Default" \ + --labels "${EXTRA_LABELS}" \ + --work "_work" ) + + log "installing systemd service for ${NAME}" + ( cd "${DIR}" && sudo ./svc.sh install "${RUNNER_USER}" ) + ( cd "${DIR}" && sudo ./svc.sh start ) +done + +log "done — ${COUNT} runner(s) registered to ${ORG}" +log "check status: systemctl list-units 'actions.runner.${ORG}.*'" +log "live logs: journalctl -u 'actions.runner.${ORG}.${PREFIX}-1.service' -f" diff --git a/scripts/uninstall-gha-runners.sh b/scripts/uninstall-gha-runners.sh new file mode 100755 index 0000000..5ee4517 --- /dev/null +++ b/scripts/uninstall-gha-runners.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# +# Tear down self-hosted GitHub Actions runners installed by +# setup-gha-runners.sh. Stops the systemd services, removes them, and +# de-registers each runner from the `wavekat` org. +# +# Usage: +# ./uninstall-gha-runners.sh +# RUNNER_TOKEN=AAAA... ./uninstall-gha-runners.sh # uses a remove-token +# +# A *remove* token can be fetched via: +# gh api -X POST /orgs/wavekat/actions/runners/remove-token --jq .token + +set -euo pipefail + +ORG="${RUNNER_ORG:-wavekat}" +COUNT="${RUNNER_COUNT:-4}" +PREFIX="${RUNNER_PREFIX:-$(hostname -s)}" +BASE_DIR="${RUNNER_BASE_DIR:-/opt/actions-runners}" + +log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m!!\033[0m %s\n' "$*" >&2; } +die() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; } + +get_token() { + if [[ -n "${RUNNER_TOKEN:-}" ]]; then + printf '%s' "${RUNNER_TOKEN}" + return + fi + if ! command -v gh >/dev/null 2>&1; then + cat >&2 <<'EOF' + +No RUNNER_TOKEN set, and `gh` CLI is not installed. + +Install gh on Ubuntu 24.04 and re-run, or fetch a remove-token elsewhere: + + # install gh: + sudo apt-get update && sudo apt-get install -y curl ca-certificates + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + sudo chmod a+r /usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + | sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null + sudo apt-get update && sudo apt-get install -y gh + gh auth login + + # or fetch a remove-token from any machine with gh authed: + gh api -X POST /orgs/wavekat/actions/runners/remove-token --jq .token + RUNNER_TOKEN= ./uninstall-gha-runners.sh +EOF + exit 1 + fi + gh api -X POST "/orgs/${ORG}/actions/runners/remove-token" --jq .token +} + +TOKEN="$(get_token)" + +for i in $(seq 1 "${COUNT}"); do + NAME="${PREFIX}-${i}" + DIR="${BASE_DIR}/${NAME}" + log "removing runner ${NAME}" + + if [[ -d "${DIR}" ]]; then + ( cd "${DIR}" && sudo ./svc.sh stop || true ) + ( cd "${DIR}" && sudo ./svc.sh uninstall || true ) + ( cd "${DIR}" && ./config.sh remove --token "${TOKEN}" || true ) + rm -rf "${DIR}" + else + warn "no directory at ${DIR} — skipping" + fi +done + +log "done" From 139f5cccda77e1bc47c519ecac524f5306500865 Mon Sep 17 00:00:00 2001 From: Eason WaveKat Date: Mon, 18 May 2026 22:29:13 +1200 Subject: [PATCH 2/3] ci: move workflows to self-hosted AOC-M3L runners Switches all jobs from `ubuntu-latest` to `[self-hosted, AOC-M3L]` so CI/preview/release/star-tracker run on the new self-hosted runners on the aoc-m3l host. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 2 +- .github/workflows/preview.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/star-tracker.yml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab23a09..a8023d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: [self-hosted, AOC-M3L] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 8ce7c49..8d54b8d 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -10,7 +10,7 @@ permissions: jobs: deploy: - runs-on: ubuntu-latest + runs-on: [self-hosted, AOC-M3L] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index daa1fb1..5d15bce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ permissions: jobs: release-please: - runs-on: ubuntu-latest + runs-on: [self-hosted, AOC-M3L] outputs: release_created: ${{ steps.release.outputs.release_created }} steps: @@ -22,7 +22,7 @@ jobs: deploy: needs: release-please if: needs.release-please.outputs.release_created == 'true' - runs-on: ubuntu-latest + runs-on: [self-hosted, AOC-M3L] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/star-tracker.yml b/.github/workflows/star-tracker.yml index 599e2f6..f6addb7 100644 --- a/.github/workflows/star-tracker.yml +++ b/.github/workflows/star-tracker.yml @@ -19,7 +19,7 @@ defaults: jobs: build: - runs-on: ubuntu-latest + runs-on: [self-hosted, AOC-M3L] steps: - uses: actions/checkout@v4 @@ -38,7 +38,7 @@ jobs: deploy: needs: build if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest + runs-on: [self-hosted, AOC-M3L] steps: - uses: actions/checkout@v4 From 257b8b29d68372dc3100f78b78a43eb1abd5395e Mon Sep 17 00:00:00 2001 From: Eason WaveKat Date: Mon, 18 May 2026 22:34:05 +1200 Subject: [PATCH 3/3] ci: target runners by role label (wavekat-ci) instead of host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switches workflows from `[self-hosted, AOC-M3L]` to `[self-hosted, wavekat-ci]` and changes the setup script default to register runners with a `wavekat-ci` role label alongside the host label. New runner pools (e.g. AWS) can join the CI fleet by carrying the `wavekat-ci` label — no repo edits required. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 2 +- .github/workflows/preview.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/star-tracker.yml | 4 ++-- scripts/setup-gha-runners.sh | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8023d3..3b04d7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: [self-hosted, AOC-M3L] + runs-on: [self-hosted, wavekat-ci] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 8d54b8d..4dea37c 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -10,7 +10,7 @@ permissions: jobs: deploy: - runs-on: [self-hosted, AOC-M3L] + runs-on: [self-hosted, wavekat-ci] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d15bce..5fd5da2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ permissions: jobs: release-please: - runs-on: [self-hosted, AOC-M3L] + runs-on: [self-hosted, wavekat-ci] outputs: release_created: ${{ steps.release.outputs.release_created }} steps: @@ -22,7 +22,7 @@ jobs: deploy: needs: release-please if: needs.release-please.outputs.release_created == 'true' - runs-on: [self-hosted, AOC-M3L] + runs-on: [self-hosted, wavekat-ci] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/star-tracker.yml b/.github/workflows/star-tracker.yml index f6addb7..ae248f8 100644 --- a/.github/workflows/star-tracker.yml +++ b/.github/workflows/star-tracker.yml @@ -19,7 +19,7 @@ defaults: jobs: build: - runs-on: [self-hosted, AOC-M3L] + runs-on: [self-hosted, wavekat-ci] steps: - uses: actions/checkout@v4 @@ -38,7 +38,7 @@ jobs: deploy: needs: build if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - runs-on: [self-hosted, AOC-M3L] + runs-on: [self-hosted, wavekat-ci] steps: - uses: actions/checkout@v4 diff --git a/scripts/setup-gha-runners.sh b/scripts/setup-gha-runners.sh index 5170c5d..f275af3 100755 --- a/scripts/setup-gha-runners.sh +++ b/scripts/setup-gha-runners.sh @@ -30,7 +30,7 @@ COUNT="${RUNNER_COUNT:-4}" PREFIX="${RUNNER_PREFIX:-$(hostname -s)}" BASE_DIR="${RUNNER_BASE_DIR:-/opt/actions-runners}" RUNNER_USER="${RUNNER_USER:-$(id -un)}" -EXTRA_LABELS="${RUNNER_LABELS:-${PREFIX}}" +EXTRA_LABELS="${RUNNER_LABELS:-wavekat-ci,${PREFIX}}" RUNNER_VERSION="${RUNNER_VERSION:-}" # empty = latest log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }