Gitea Runner Manager (GRM)

A lean command-line tool to automate the installation, configuration, and lifecycle management of Gitea Actions runners on Arch Linux, Ubuntu, and Debian hosts.

By default, runners are deployed as Docker containers using the official gitea/runner image. A traditional binary deployment mode is also available.

Pronunciation note: GRM is short for Gitea Runner Manager, but say it like ГРЪМ (roughly "GRUM" in Latin letters) — the Bulgarian word for thunder. Wherever there are clouds, there may be thunders. This is an open-source project from Oblachno (облачно means cloudy in Bulgarian).

Features

  • Simple and focused — no unnecessary features.
  • Secure — no hardcoded secrets, uses scoped tokens.
  • Idempotent — can be run multiple times safely.
  • Flexible — accepts a plain IP address or hostname, and allows specifying the SSH user and private key.

Supported Operating Systems

  • Arch Linux
  • Ubuntu 22.04 / 24.04 / 26.04
  • Debian 12 / 13

Prerequisites

  • SSH key authentication — The remote host must be reachable via SSH using the user specified with --user and the private key specified with --key. GRM uses Ansible under the hood, which connects to the target host over SSH to execute all installation and configuration tasks. Without valid SSH credentials, Ansible cannot establish a connection and the deployment will fail.
  • Sudo privileges — The specified user must have passwordless or password-prompted (--ask-become-pass) sudo access on the remote host. Runner installation requires root privileges for tasks such as installing packages, creating systemd services, and managing Docker.

Quick Start

Developer Setup

git clone https://git.oblachno.oblachno.com/oblachno/gitea-runner-manager.git
cd gitea-runner-manager
pyenv install 3.11.11
pyenv local 3.11.11
make setup

Configure Gitea Credentials

cp .env.example .env
# Edit .env:
# GITEA_URL=https://git.example.com
# GITEA_REGISTRATION_TOKEN=your-registration-token

GITEA_REGISTRATION_TOKEN is the runner registration token obtained from your Gitea instance (Admin → Actions → Runners → Create Registration Token).

Install a Runner

Using the CLI:

# Docker mode (default) — deploys gitea_runner as a container
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner

# Binary mode — downloads and installs the gitea_runner binary with systemd
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner --mode binary

If the remote user requires a sudo password:

grm install 192.168.1.10 --user ubuntu --ask-become-pass

Using Make:

# Docker mode (default)
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner

# Binary mode
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner MODE=binary

# With sudo password prompt:
make install HOST=192.168.1.10 USER=ubuntu ASK_BECOME_PASS=1

Verify Runner

Check Gitea admin UI under Actions → Runners. The runner should appear as Online.

View Logs

# Binary mode logs
sudo journalctl -u gitea-runner-<name> -f

# Docker mode logs
docker logs gitea-runner-<name> -f

Architecture

GRM consists of two layers:

  1. Python CLI (src/gitea_runner_manager/) — built with Click, handles argument parsing, environment loading, and delegates to Ansible via the ansible-playbook subprocess.
  2. Ansible Role (ansible/roles/gitea-runner/) — idempotent role that installs Docker, configures the runner (binary or container), creates systemd services, and registers the runner with Gitea.
grm install <host>
  └── RunnerManager.install()
      └── ansible-playbook ansible/install-runner.yml
          └── role: gitea-runner
              ├── docker.yml        (Docker installation)
              ├── docker_mode.yml   (container deployment)
              ├── binary_mode.yml   (binary + systemd deployment)
              ├── prune.yml         (Docker prune timer)
              └── integration_test.yml (validate online status)

Configuration

All tunable values are exposed as Ansible variables in ansible/roles/gitea-runner/defaults/main.yml:

Variable Default Description
runner_mode docker Deployment mode: docker or binary
gitea_runner_version 1.0.8 Docker image tag / binary version
gitea_runner_docker_image gitea/runner Docker image name
runner_labels ubuntu-latest:docker://runner-images:ubuntu-22.04 Runner labels
skip_runner_registration false Skip API registration (useful for tests)
gitea_runner_data_dir /var/lib/gitea-runner Runtime data directory
gitea_runner_config_dir /etc/gitea-runner Config directory (binary mode)
gitea_runner_binary_path /usr/local/bin/gitea_runner Binary install path
gitea_runner_prune_until 24h Prune resources older than this
gitea_runner_prune_schedule daily systemd timer schedule
gitea_runner_prune_label gitea-runner=true Docker label for pruning
gitea_runner_service_restart_sec 5 systemd RestartSec value
gitea_runner_service_user {{ ansible_user | default('root') }} Service user
gitea_runner_log_level info Runner log level
gitea_runner_container_label gitea-runner=true Container label
docker_gpg_key_path /etc/apt/keyrings/docker.asc Docker GPG key path

Override any variable by passing it to the CLI with --extra-vars or by setting it in your Ansible inventory.

Development

Project Structure

.
├── src/gitea_runner_manager/      # Python CLI source
│   ├── cli.py                     # Click commands
│   ├── runner_manager.py          # Ansible orchestration
│   └── exceptions.py              # Custom exceptions
├── ansible/
│   ├── roles/gitea-runner/        # Main Ansible role
│   │   ├── defaults/main.yml      # Default variables
│   │   ├── tasks/                 # Task files
│   │   ├── templates/             # Jinja2 templates
│   │   └── molecule/              # Test scenarios
│   ├── install-runner.yml         # Install playbook
│   └── update-runner.yml         # Update playbook
├── tests/                         # Unit tests
├── Makefile                       # Build & test automation
└── pyproject.toml                # Python project metadata

Setup Development Environment

make setup        # Creates venv, installs deps, sets up hooks
source .venv/bin/activate

Running Linters

make lint          # Python (ruff + pyright)
make ansible-lint  # Ansible
make makefile-lint # Makefile

Testing

Unit Tests

make test-unit

Runs pytest with 100% coverage requirement.

Molecule Tests

make molecule

Runs two scenarios:

  • default — Docker mode installation in an Ubuntu 22.04 container
  • binary — Binary mode installation in an Ubuntu 22.04 container

Both scenarios test idempotence (second run produces zero changes).

Full Test Suite

make test-all      # Runs unit tests + linters + molecule

Troubleshooting

"Event loop is closed" warning

This is a harmless cleanup traceback from Molecule's Docker driver when the test process is interrupted. It does not indicate a test failure.

Runner appears offline after installation

  • Check that the GITEA_URL and GITEA_REGISTRATION_TOKEN environment variables are correct.
  • Verify the runner container or service is running: docker ps or systemctl status gitea-runner-<name>.
  • Check logs for registration errors.

Docker mode: container won't start

  • Ensure Docker is installed and running on the host.
  • Verify the Docker socket is accessible: docker version.

Binary mode: systemd service fails

  • Check the service status: systemctl status gitea-runner-<name>.
  • Verify the binary exists at gitea_runner_binary_path.
  • Ensure the service user is in the docker group.

Makefile Targets

Target Description
setup Full environment setup
install Installs a runner on a host (MODE=docker or binary)
update Updates a runner on a host (MODE=docker or binary)
lint Runs Python linters
ansible-lint Runs ansible-lint
test-unit Runs unit tests with coverage
molecule Runs Ansible Molecule tests
test-all Runs all tests

License

GPL-3.0

S
Description
This is the git repository of the Gitea Runner Manager.
Readme GPL-3.0
1.4 MiB
v0.20.0
Latest
2026-08-09 11:16:07 +00:00
Languages
Python 87.6%
Jinja 6.8%
Makefile 4.6%
Shell 1%