#!/usr/bin/env bash # install-rust-runner.sh # # Provision the second act_runner instance that owns the `rust-build:host` # label and the LinuxRust2404 template. # # Why a second instance: act_runner has one `runner.envs` block per process and # does not expose the job's runs-on label to the workflow (there is no # RUNNER_LABELS variable — v1.0.4 provides only RUNNER_OS/ARCH/TEMP/ # TOOL_CACHE/DEBUG/VERSION). A label can therefore only select a template by # selecting a runner whose envs name that template. # # Usage: # sudo ./scripts/install-rust-runner.sh --token-file # [--instance https://gitea.emulab.it] [--name ci-linux-rust] # [--capacity 1] [--dry-run] # # The registration token is single-use and short-lived. Get one either from # Gitea (Site Administration -> Actions -> Runners -> Create new Runner) or, # using the stored GiteaPAT, with: # # sudo -u ci-runner /opt/ci/venv/bin/python tools/gitea-runner-token.py \ # --out /var/lib/ci/.rust-runner-regtoken # # Prefer --token-file over --token: an argument passed on the command line is # visible to any user via `ps` for the lifetime of the process. # # Idempotent: re-running with an existing .runner file skips registration and # only refreshes the config and the unit. set -euo pipefail INSTANCE="https://gitea.emulab.it" RUNNER_NAME="ci-linux-rust" TOKEN="" TOKEN_FILE="" CAPACITY="" DRY_RUN=0 RUNNER_DIR="/var/lib/ci/runner-rust" RUNNER_USER="ci-runner" ACT_RUNNER_BIN="/opt/ci/act_runner/act_runner" UNIT_NAME="act-runner-rust.service" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CONFIG_SRC="${REPO_ROOT}/deploy/runner-rust-config.yaml" UNIT_SRC="${REPO_ROOT}/deploy/systemd/${UNIT_NAME}" while [[ $# -gt 0 ]]; do case "$1" in --token) TOKEN="${2:?--token requires a value}"; shift ;; # Preferred over --token: keeps the secret out of the process argument # list (visible in `ps`) and out of shell history. --token-file) TOKEN_FILE="${2:?--token-file requires a value}" [ -r "$TOKEN_FILE" ] || { echo "Token file not readable: $TOKEN_FILE" >&2; exit 1; } TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")" [ -n "$TOKEN" ] || { echo "Token file is empty: $TOKEN_FILE" >&2; exit 1; } shift ;; --instance) INSTANCE="${2:?--instance requires a value}"; shift ;; --name) RUNNER_NAME="${2:?--name requires a value}"; shift ;; --capacity) CAPACITY="${2:?--capacity requires a value}"; shift ;; --dry-run) DRY_RUN=1 ;; *) echo "Unknown option: $1" >&2; exit 1 ;; esac shift done run() { if [ "$DRY_RUN" = "1" ]; then echo " [DRY-RUN] $*" else "$@" fi } echo "=== install-rust-runner.sh ===" echo " instance : ${INSTANCE}" echo " name : ${RUNNER_NAME}" echo " dir : ${RUNNER_DIR}" echo " unit : ${UNIT_NAME}" echo "" # --- Pre-flight ------------------------------------------------------------- echo "--- Pre-flight ---" [ "$(id -u)" -eq 0 ] || { echo "Must run as root (sudo)." >&2; exit 1; } [ -x "$ACT_RUNNER_BIN" ] || { echo "act_runner not found: $ACT_RUNNER_BIN" >&2; exit 1; } [ -f "$CONFIG_SRC" ] || { echo "Config template not found: $CONFIG_SRC" >&2; exit 1; } [ -f "$UNIT_SRC" ] || { echo "Unit file not found: $UNIT_SRC" >&2; exit 1; } id "$RUNNER_USER" >/dev/null 2>&1 || { echo "User $RUNNER_USER does not exist." >&2; exit 1; } # The template this runner is dedicated to must exist, otherwise the first job # fails at clone time with a much less obvious error. RUST_VMX="/var/lib/ci/templates/LinuxRust2404/LinuxRust2404.vmx" if [ ! -f "$RUST_VMX" ]; then echo "Rust template not found: $RUST_VMX" >&2 echo "Run 'ci-orchestrator template deploy-rust --then-prepare' first." >&2 exit 1 fi echo " [OK] act_runner: $ACT_RUNNER_BIN" echo " [OK] template: $RUST_VMX" # A second instance must not reuse the main runner's state directory. if [ "$RUNNER_DIR" = "/var/lib/ci/runner" ]; then echo "Refusing to overwrite the main runner directory." >&2 exit 1 fi # --- Directory + config ----------------------------------------------------- echo "" echo "--- Directory + config ---" run install -d -o "$RUNNER_USER" -g "$RUNNER_USER" -m 0750 "$RUNNER_DIR" run install -d -o "$RUNNER_USER" -g "$RUNNER_USER" -m 0750 "${RUNNER_DIR}/cache" run install -o "$RUNNER_USER" -g "$RUNNER_USER" -m 0644 "$CONFIG_SRC" "${RUNNER_DIR}/config.yaml" if [ -n "$CAPACITY" ]; then run sed -i "s/^ capacity: .*/ capacity: ${CAPACITY}/" "${RUNNER_DIR}/config.yaml" fi echo " [OK] ${RUNNER_DIR}/config.yaml" # --- Registration ----------------------------------------------------------- echo "" echo "--- Registration ---" if [ -f "${RUNNER_DIR}/.runner" ]; then echo " [SKIP] ${RUNNER_DIR}/.runner exists — already registered." echo " Delete it and re-run to register as a new runner." elif [ -z "$TOKEN" ]; then echo " No --token given and no existing .runner." >&2 echo " Get a registration token from Gitea (Actions -> Runners ->" >&2 echo " Create new Runner) and re-run with --token ." >&2 exit 1 else # Never let the token reach stdout: `run` echoes its arguments in dry-run # mode, so the register call is handled separately with a redacted echo. if [ "$DRY_RUN" = "1" ]; then echo " [DRY-RUN] ${ACT_RUNNER_BIN} register --no-interactive" \ "--instance ${INSTANCE} --token " \ "--name ${RUNNER_NAME} --labels rust-build:host" else # cd into the runner dir: act_runner resolves `runner.file` relative to # the CWD. The shipped config uses an absolute path, but staying here # keeps it correct even if that is ever edited back to `.runner`. # --labels is accepted but ignored by `register` when the config file # declares labels; the config is the single source of truth. ( cd "$RUNNER_DIR" && sudo -u "$RUNNER_USER" env HOME="$RUNNER_DIR" \ "$ACT_RUNNER_BIN" register \ --no-interactive \ --instance "$INSTANCE" \ --token "$TOKEN" \ --name "$RUNNER_NAME" \ --config "${RUNNER_DIR}/config.yaml" ) chown "$RUNNER_USER:$RUNNER_USER" "${RUNNER_DIR}/.runner" chmod 0600 "${RUNNER_DIR}/.runner" echo " [OK] registered as ${RUNNER_NAME}" fi fi # --- systemd unit ----------------------------------------------------------- echo "" echo "--- systemd unit ---" run install -o root -g root -m 0644 "$UNIT_SRC" "/etc/systemd/system/${UNIT_NAME}" run systemctl daemon-reload run systemctl enable --now "$UNIT_NAME" echo " [OK] ${UNIT_NAME} enabled and started" # --- Verification ----------------------------------------------------------- echo "" echo "--- Verification ---" if [ "$DRY_RUN" = "1" ]; then echo " [DRY-RUN] skipped" else sleep 3 systemctl is-enabled "$UNIT_NAME" systemctl is-active "$UNIT_NAME" echo "" echo " Labels registered:" sudo -u "$RUNNER_USER" grep -A4 '"labels"' "${RUNNER_DIR}/.runner" || true echo "" echo " Recent log:" journalctl -u "$UNIT_NAME" -n 15 --no-pager || true fi echo "" echo "=== done ===" echo "Confirm the runner shows Online in Gitea with label 'rust-build', then" echo "a workflow can use:" echo "" echo " runs-on: rust-build" echo " # guest-os: 'Linux' -> LinuxRust2404 + BaseClean-LinuxRust" echo "" echo "Check that the label is actually announced BEFORE relying on it: a" echo "runs-on value no runner announces leaves the job unschedulable with no" echo "error anywhere."