diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab23a09..3b04d7b 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, wavekat-ci] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 8ce7c49..4dea37c 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, wavekat-ci] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index daa1fb1..5fd5da2 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, 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: ubuntu-latest + 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 599e2f6..ae248f8 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, 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: ubuntu-latest + 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 new file mode 100755 index 0000000..f275af3 --- /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:-wavekat-ci,${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"