f014d5534a
Central runner on ana-docker (gitea is local; existing fleet tooling already SSHes from there). Playbook is parameterized so future site-local runners (nh3-docker, esh-docker-vm) drop in via --var overrides instead of copy-paste. Includes a workflow template for vh/task-board that calls the existing deploy-task-board.yaml playbook — keeps the playbook as the single source of truth for "how task-board is deployed", manual or automated. Labels embed `:docker://node:20-bookworm-slim` schema; without it, act_runner v0.6+ silently falls back to host-mode and runs job steps inside the Alpine runner container (no apt/python/node), breaking any real workflow. node:20-bookworm-slim is small + has git + node so actions/checkout works out of the box.
140 lines
6.0 KiB
YAML
140 lines
6.0 KiB
YAML
# Deploy a Gitea Actions self-hosted runner to a Docker host.
|
|
#
|
|
# We currently run ONE central runner on ana-docker (gitea is local
|
|
# there and SSH-from-there to the rest of the fleet already works).
|
|
# This playbook is parameterized so the same file can stand up
|
|
# site-local runners later without copy-paste — see
|
|
# stacks/gitea-runner/README.md "Topology" for when to do that.
|
|
#
|
|
# Usage:
|
|
# # Central runner (defaults match ana-docker)
|
|
# scripts/elway ana-docker --playbook playbooks/deploy-gitea-runner.yaml
|
|
#
|
|
# # Site-local runner — override identity at the CLI; the .env on
|
|
# # the host carries the registration token + image pin.
|
|
# scripts/elway nh3-docker --playbook playbooks/deploy-gitea-runner.yaml \
|
|
# --var runner_name=nh3-docker-runner \
|
|
# --var runner_labels=pfi-fleet,nh3-docker
|
|
#
|
|
# scripts/elway esh-docker-vm --playbook playbooks/deploy-gitea-runner.yaml \
|
|
# --var runner_name=esh-runner \
|
|
# --var runner_labels=pfi-fleet,esh
|
|
#
|
|
# Prereqs on the target host:
|
|
# - Docker + compose plugin
|
|
# - traefik-net network exists (external)
|
|
# - /opt/docker/{compose,conf} convention (any PFI host already has this)
|
|
#
|
|
# Prereqs in gitea (do once before the first run):
|
|
# - Generate a registration token at the appropriate scope
|
|
# (admin / org / repo). Paste into .env on the target host
|
|
# under GITEA_RUNNER_REGISTRATION_TOKEN before this playbook runs
|
|
# `docker compose up -d`. See stacks/gitea-runner/README.md.
|
|
|
|
vars:
|
|
compose_dir: /opt/docker/compose/gitea-runner
|
|
data_dir: /opt/docker/conf/gitea-runner/data
|
|
# Optional overrides — leave blank to take whatever's already in the
|
|
# host-side .env. Setting these here patches the .env in place,
|
|
# which is how site-local runners get unique names/labels without
|
|
# editing files on the host by hand.
|
|
runner_name: ""
|
|
runner_labels: ""
|
|
|
|
steps:
|
|
# ── host-side directory prep ────────────────────────────────────────
|
|
- name: Ensure compose dir exists
|
|
shell: mkdir -p {{ compose_dir }}
|
|
creates: "{{ compose_dir }}"
|
|
|
|
- name: Ensure data dir exists
|
|
shell: mkdir -p {{ data_dir }}
|
|
creates: "{{ data_dir }}"
|
|
|
|
# ── compose + config files ──────────────────────────────────────────
|
|
- name: Upload compose.yaml
|
|
upload:
|
|
src: stacks/gitea-runner/compose.yaml
|
|
dest: "{{ compose_dir }}/compose.yaml"
|
|
mode: "0644"
|
|
|
|
- name: Upload runner config.yaml
|
|
upload:
|
|
src: stacks/gitea-runner/conf/config.yaml
|
|
dest: "{{ data_dir }}/config.yaml"
|
|
mode: "0644"
|
|
|
|
- name: Seed .env from template (only if absent)
|
|
upload:
|
|
src: stacks/gitea-runner/.env.example
|
|
dest: "{{ compose_dir }}/.env"
|
|
mode: "0644"
|
|
when: "[ ! -f {{ compose_dir }}/.env ]"
|
|
|
|
# ── optional CLI-driven identity overrides ──────────────────────────
|
|
# If runner_name was passed as --var, patch the line in the host's
|
|
# .env so subsequent compose-up uses it. Idempotent: changed_when
|
|
# checks whether the value differs from what's already there.
|
|
- name: Patch GITEA_RUNNER_NAME in .env (when --var runner_name=)
|
|
shell: sed -i 's|^GITEA_RUNNER_NAME=.*|GITEA_RUNNER_NAME={{ runner_name }}|' {{ compose_dir }}/.env
|
|
when: '[ -n "{{ runner_name }}" ]'
|
|
changed_when: '[ -n "{{ runner_name }}" ] && ! grep -q "^GITEA_RUNNER_NAME={{ runner_name }}$" {{ compose_dir }}/.env'
|
|
|
|
- name: Patch GITEA_RUNNER_LABELS in .env (when --var runner_labels=)
|
|
shell: sed -i 's|^GITEA_RUNNER_LABELS=.*|GITEA_RUNNER_LABELS={{ runner_labels }}|' {{ compose_dir }}/.env
|
|
when: '[ -n "{{ runner_labels }}" ]'
|
|
changed_when: '[ -n "{{ runner_labels }}" ] && ! grep -q "^GITEA_RUNNER_LABELS={{ runner_labels }}$" {{ compose_dir }}/.env'
|
|
|
|
# ── safety: surface missing registration token before bring-up ──────
|
|
- name: Verify registration token is present (or runner is already registered)
|
|
# Either the .env carries a non-empty token (first run) or
|
|
# data_dir/.runner exists (already registered). If neither, the
|
|
# container will start but spin in a registration loop — fail
|
|
# fast with a useful message instead.
|
|
shell: |
|
|
if [ -f {{ data_dir }}/.runner ]; then
|
|
echo "already registered (.runner present)"
|
|
exit 0
|
|
fi
|
|
tok=$(grep -E '^GITEA_RUNNER_REGISTRATION_TOKEN=' {{ compose_dir }}/.env | cut -d= -f2-)
|
|
if [ -z "$tok" ]; then
|
|
echo "ERROR: no .runner cached and GITEA_RUNNER_REGISTRATION_TOKEN is empty in {{ compose_dir }}/.env"
|
|
echo "Generate one at https://gitea.phasefinal.com/-/admin/actions/runners and paste it into the .env, then re-run."
|
|
exit 1
|
|
fi
|
|
echo "registration token present"
|
|
changed_when: "false"
|
|
|
|
# ── bring up + wait for runner to come online ───────────────────────
|
|
- name: docker compose up -d
|
|
shell: cd {{ compose_dir }} && docker compose up -d
|
|
|
|
- name: Wait for runner to register / start polling
|
|
# The runner logs "Runner registered" on first start, then enters
|
|
# a polling loop ("Listening for tasks" / "polling"). Either is a
|
|
# success signal.
|
|
shell: |
|
|
for i in $(seq 1 30); do
|
|
if docker logs gitea-runner 2>&1 | grep -qE "Runner registered|Listening for tasks|poll|Starting runner"; then
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "--- last 80 lines of gitea-runner logs ---"
|
|
docker logs gitea-runner --tail 80
|
|
exit 1
|
|
changed_when: "false"
|
|
|
|
verify:
|
|
- name: Container is running
|
|
shell: docker inspect gitea-runner --format '{{.State.Status}}' | grep -q running
|
|
changed_when: "false"
|
|
|
|
- name: Runner credentials are cached on disk
|
|
shell: test -f {{ data_dir }}/.runner
|
|
changed_when: "false"
|
|
|
|
- name: Runner can reach gitea (no TLS / auth errors in last 50 log lines)
|
|
shell: '! docker logs gitea-runner --tail 50 2>&1 | grep -qiE "tls|x509|unauthorized|forbidden|connection refused"'
|
|
changed_when: "false"
|