GRM-1: feat: initial implementation of Gitea Runner Manager

This commit is contained in:
Emil Simeonov
2026-06-17 18:15:28 +02:00
commit 170b53ad28
52 changed files with 2191 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
---
# Default variables for all hosts
ansible_python_interpreter: /usr/bin/python3
+10
View File
@@ -0,0 +1,10 @@
---
- name: Install Gitea Actions runner
hosts: all
become: true
vars:
gitea_url: "{{ gitea_url | mandatory }}"
registration_token: "{{ registration_token | mandatory }}"
runner_name: "{{ runner_name | default(inventory_hostname) }}"
roles:
- role: gitea-runner
+3
View File
@@ -0,0 +1,3 @@
[runners]
192.168.1.10 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_ed25519
runner.example.com ansible_user=arch
+5
View File
@@ -0,0 +1,5 @@
collections:
- name: community.general
version: ">=13.0.1"
- name: ansible.posix
version: ">=1.5.4"
@@ -0,0 +1,9 @@
---
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart act-runner
ansible.builtin.systemd:
name: "act-runner-{{ runner_name }}"
state: restarted
@@ -0,0 +1,10 @@
---
- name: Converge
hosts: all
become: true
vars:
gitea_url: "http://localhost:3000"
registration_token: "fake-token-for-testing"
runner_name: "molecule-test-runner"
roles:
- role: gitea-runner
@@ -0,0 +1,21 @@
---
driver:
name: docker
platforms:
- name: instance
image: geerlingguy/docker-ubuntu2204-ansible:latest
command: ""
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: converge.yml
verifier:
name: ansible
@@ -0,0 +1,9 @@
---
- name: Prepare
hosts: all
become: true
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
when: ansible_facts['os_family'] == 'Debian'
@@ -0,0 +1,52 @@
---
- name: Verify
hosts: all
become: true
tasks:
- name: Check act_runner binary exists
ansible.builtin.stat:
path: /usr/local/bin/act_runner
register: act_runner_stat
- name: Assert act_runner binary exists
ansible.builtin.assert:
that:
- act_runner_stat.stat.exists
fail_msg: "act_runner binary is missing"
- name: Check Docker is installed
ansible.builtin.command: docker --version
changed_when: false
- name: Check systemd service file exists
ansible.builtin.stat:
path: "/etc/systemd/system/act-runner-molecule-test-runner.service"
register: service_stat
- name: Assert service file exists
ansible.builtin.assert:
that:
- service_stat.stat.exists
fail_msg: "Systemd service file is missing"
- name: Check prune timer exists
ansible.builtin.stat:
path: /etc/systemd/system/docker-prune.timer
register: timer_stat
- name: Assert prune timer exists
ansible.builtin.assert:
that:
- timer_stat.stat.exists
fail_msg: "Docker prune timer is missing"
- name: Check config file exists
ansible.builtin.stat:
path: /etc/act-runner/config.toml
register: config_stat
- name: Assert config file exists
ansible.builtin.assert:
that:
- config_stat.stat.exists
fail_msg: "Config file is missing"
@@ -0,0 +1,13 @@
---
- name: Ensure config directory exists
ansible.builtin.file:
path: /etc/act-runner
state: directory
mode: "0755"
- name: Create act_runner config file
ansible.builtin.template:
src: act-runner-config.toml.j2
dest: /etc/act-runner/config.toml
mode: "0644"
notify: Restart act-runner
@@ -0,0 +1,63 @@
---
- name: Install Docker (Debian/Ubuntu)
when: ansible_facts['os_family'] == 'Debian'
block:
- name: Install prerequisite packages
ansible.builtin.apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
state: present
update_cache: true
- name: Add Docker GPG key
ansible.builtin.apt_key:
url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg
keyring: /etc/apt/keyrings/docker.gpg
when: ansible_distribution != 'Ubuntu' or ansible_distribution_major_version | int >= 22
- name: Add Docker repository
ansible.builtin.apt_repository:
repo: >-
deb [arch={{ ansible_architecture }}
signed-by=/etc/apt/keyrings/docker.gpg]
https://download.docker.com/linux/{{ ansible_distribution | lower }}
{{ ansible_distribution_release }} stable
filename: docker
state: present
update_cache: true
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
- name: Install Docker (Arch Linux)
when: ansible_facts['os_family'] == 'Archlinux'
block:
- name: Install Docker packages
community.general.pacman:
name:
- docker
- docker-compose
state: present
update_cache: true
- name: Ensure Docker service is running
ansible.builtin.systemd:
name: docker
state: started
enabled: true
- name: Add user to docker group
ansible.builtin.user:
name: "{{ ansible_user | default(ansible_user_id) }}"
groups: docker
append: true
when: ansible_user is defined or ansible_user_id is defined
@@ -0,0 +1,35 @@
---
- name: Get latest act_runner release info
ansible.builtin.uri:
url: https://gitea.com/gitea/act_runner/releases/latest
return_content: true
headers:
Accept: application/json
register: act_runner_release
when: act_runner_version | default('latest') == 'latest'
changed_when: false
- name: Set act_runner version from latest release
ansible.builtin.set_fact:
act_runner_version: "{{ act_runner_release.json.tag_name }}"
when: act_runner_version | default('latest') == 'latest'
- name: Set act_runner download URL
ansible.builtin.set_fact:
act_runner_url: >-
https://gitea.com/gitea/act_runner/releases/download/{{ act_runner_version }}/
act_runner-{{ act_runner_version }}-linux-{{ ansible_architecture | regex_replace('x86_64', 'amd64') }}
- name: Ensure /usr/local/bin directory exists
ansible.builtin.file:
path: /usr/local/bin
state: directory
mode: "0755"
- name: Download act_runner binary
ansible.builtin.get_url:
url: "{{ act_runner_url }}"
dest: /usr/local/bin/act_runner
mode: "0755"
force: true
notify: Restart act-runner
@@ -0,0 +1,38 @@
---
- name: Wait for runner to appear in Gitea API
ansible.builtin.uri:
url: "{{ gitea_url }}/api/v1/admin/runners"
headers:
Authorization: "token {{ registration_token }}"
method: GET
status_code: 200
return_content: true
register: runners_response
until: >
runners_response.json.runners | default([]) |
selectattr('name', 'equalto', runner_name) | list | length > 0
retries: 12
delay: 10
when: gitea_url is defined and registration_token is defined
- name: Verify runner is online
ansible.builtin.uri:
url: "{{ gitea_url }}/api/v1/admin/runners"
headers:
Authorization: "token {{ registration_token }}"
method: GET
status_code: 200
return_content: true
register: runners_check
when: gitea_url is defined and registration_token is defined
- name: Fail if runner is not online
ansible.builtin.fail:
msg: "Runner '{{ runner_name }}' is not online in Gitea"
when:
- gitea_url is defined
- registration_token is defined
- >
runners_check.json.runners | default([]) |
selectattr('name', 'equalto', runner_name) |
selectattr('status', 'equalto', 'online') | list | length == 0
+24
View File
@@ -0,0 +1,24 @@
---
- name: Include OS-specific Docker installation
ansible.builtin.include_tasks: docker.yml
- name: Include act_runner download
ansible.builtin.include_tasks: download_act_runner.yml
- name: Include validation
ansible.builtin.include_tasks: validate.yml
- name: Include config creation
ansible.builtin.include_tasks: config.yml
- name: Include registration
ansible.builtin.include_tasks: register.yml
- name: Include service setup
ansible.builtin.include_tasks: service.yml
- name: Include prune setup
ansible.builtin.include_tasks: prune.yml
- name: Include integration test
ansible.builtin.include_tasks: integration_test.yml
@@ -0,0 +1,19 @@
---
- name: Create docker-prune service file
ansible.builtin.template:
src: docker-prune.service.j2
dest: /etc/systemd/system/docker-prune.service
mode: "0644"
- name: Create docker-prune timer file
ansible.builtin.template:
src: docker-prune.timer.j2
dest: /etc/systemd/system/docker-prune.timer
mode: "0644"
- name: Enable and start docker-prune timer
ansible.builtin.systemd:
name: docker-prune.timer
state: started
enabled: true
daemon_reload: true
@@ -0,0 +1,25 @@
---
- name: Ensure work directory exists
ansible.builtin.file:
path: /var/lib/gitea-runner
state: directory
mode: "0755"
- name: Check if runner is already registered
ansible.builtin.stat:
path: /var/lib/gitea-runner/.runner
register: runner_registered
- name: Register runner with Gitea
ansible.builtin.command: >
/usr/local/bin/act_runner register
--token {{ registration_token }}
--name {{ runner_name }}
--instance {{ gitea_url }}
--labels ubuntu-latest:docker://node:16-bullseye
--no-interactive
args:
chdir: /var/lib/gitea-runner
when: not runner_registered.stat.exists
register: register_output
changed_when: "'already exists' not in register_output.stdout | default('')"
@@ -0,0 +1,16 @@
---
- name: Create systemd service file
ansible.builtin.template:
src: act-runner.service.j2
dest: "/etc/systemd/system/act-runner-{{ runner_name }}.service"
mode: "0644"
notify:
- Reload systemd
- Restart act-runner
- name: Enable and start act-runner service
ansible.builtin.systemd:
name: "act-runner-{{ runner_name }}"
state: started
enabled: true
daemon_reload: true
@@ -0,0 +1,24 @@
---
- name: Check act_runner binary exists
ansible.builtin.stat:
path: /usr/local/bin/act_runner
register: act_runner_stat
- name: Fail if act_runner binary is missing
ansible.builtin.fail:
msg: "act_runner binary not found at /usr/local/bin/act_runner"
when: not act_runner_stat.stat.exists
- name: Verify act_runner is executable
ansible.builtin.command: /usr/local/bin/act_runner --version
register: act_runner_version_output
changed_when: false
- name: Verify Docker connectivity
ansible.builtin.command: docker version
register: docker_version_output
changed_when: false
- name: Set runner_validated fact
ansible.builtin.set_fact:
runner_validated: true
@@ -0,0 +1,3 @@
log.level = "info"
runner.file = ".runner"
container.label = "gitea-runner=true"
@@ -0,0 +1,16 @@
[Unit]
Description=Gitea Actions Runner ({{ runner_name }})
After=network.target docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/usr/local/bin/act_runner daemon --config /etc/act-runner/config.toml
WorkingDirectory=/var/lib/gitea-runner
Restart=always
RestartSec=5
User={{ ansible_user | default('root') }}
Group=docker
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,9 @@
[Unit]
Description=Docker prune for Gitea runner resources
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/docker system prune -f --filter "label=gitea-runner=true" --filter "until=24h"
ExecStart=/usr/bin/docker volume prune -f --filter "label=gitea-runner=true" --filter "until=24h"
@@ -0,0 +1,9 @@
[Unit]
Description=Daily Docker prune for Gitea runner resources
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
+2
View File
@@ -0,0 +1,2 @@
---
act_runner_version: "latest"
+17
View File
@@ -0,0 +1,17 @@
---
- name: Update Gitea Actions runner binary
hosts: all
become: true
vars:
act_runner_version: "{{ act_runner_version | default('latest') }}"
tasks:
- name: Include download and validate tasks
ansible.builtin.include_role:
name: gitea-runner
tasks_from: download_act_runner.yml
- name: Restart act-runner service
ansible.builtin.systemd:
name: "act-runner-{{ runner_name | default(inventory_hostname) }}"
state: restarted
daemon_reload: true