feat(runner): dedicated rust-build runner instance for LinuxRust2404
`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.
This commit is contained in:
Executable
+192
@@ -0,0 +1,192 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user