`runs-on: rust-build` needs a second act_runner instance, not another label on the existing one: act_runner resolves a single runner.envs block per process and never tells the workflow which label picked the job. A label can select a template only by selecting a runner whose envs name that template. Registered as ci-linux-rust (id 3), online, announcing rust-build. - deploy/runner-rust-config.yaml: label rust-build:host only, envs pointing at LinuxRust2404 + BaseClean-LinuxRust, its own cache dir (two processes must not share one actcache database), capacity 1 on top of the main runner's 2. runner.file is absolute: act_runner resolves a relative `file:` against the process CWD, so registering from elsewhere drops the registration state -- which holds the runner token -- wherever it was invoked, and the daemon then fails with "registration file not found". - scripts/install-rust-runner.sh: idempotent, --dry-run, refuses to touch the main runner directory, requires the template to exist first. Takes --token-file rather than --token so the secret stays out of the process argument list; the dry-run echo redacts it. - tools/gitea-runner-token.py: mints a registration token from the stored GiteaPAT and writes it to a 0600 file. Neither secret is ever printed. Sets an explicit User-Agent because Cloudflare rejects urllib's default with error 1010. - tools/gitea-runner-list.py: --label NAME exits non-zero unless an ONLINE runner announces it. A runs-on value no runner advertises leaves jobs unschedulable with no error anywhere, which is exactly the failure the consumer repo warned about.
193 lines
7.5 KiB
Bash
Executable File
193 lines
7.5 KiB
Bash
Executable File
#!/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 <PATH>
|
|
# [--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 <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 <redacted:${#TOKEN} chars>" \
|
|
"--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."
|