# 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"