Table of Contents
- Getting Started
- Prerequisites
- Step 1: Clone and Setup
- Step 2: Configure Gitea Credentials
- Get the Registration Token
- Get the Admin API Token (optional)
- Create the .env File
- Environment Variables Reference
- Step 3: Install Your First Runner
- Step 4: Verify the Installation
- Step 5: Manage the Runner Lifecycle
- View Logs
- GRM application logs (on your local machine)
- Ansible execution logs
- Runner logs (on the remote host)
- Logging destinations
- Next Steps
Getting Started
This guide walks you through setting up GRM, configuring Gitea credentials, and installing your first runner.
Prerequisites
Before you begin, ensure you have:
- Python 3.12+ on your local machine
- SSH access to the target host(s) where runners will be installed
- Sudo privileges on the target host(s) for the SSH user
- A Gitea instance with admin access to create registration tokens
- Git for cloning the repository
Step 1: Clone and Setup
git clone https://git.oblachno.oblachno.fyi/oblachno-oss/grm.git
cd grm
git checkout $(git describe --tags --abbrev=0) # Checkout latest stable release
make setup
source .venv/bin/activate
Important: Always checkout the latest release tag before running
make setup. Themasterbranch may contain unreleased changes that are not yet stable. Thegit describe --tags --abbrev=0command automatically selects the most recent tagged release. To see all available releases, rungit tag --sort=-version:refnameor check the releases page.
make setup creates a virtualenv, installs all dependencies (including Ansible), creates .env from .env.example, and sets up pre-commit hooks.
If you use pyenv for Python version management:
pyenv install 3.12
pyenv local 3.12
make setup
Step 2: Configure Gitea Credentials
GRM needs two tokens from your Gitea instance: a registration token (required) and an admin API token (optional, for post-install verification).
Get the Registration Token
The registration token tells Gitea to accept the runner when it connects.
- Log in to your Gitea instance as an administrator
- Navigate to Site Administration → Actions → Runners
- Click Create Registration Token
- Copy the token — it starts with
GR
Note: There are three levels of registration tokens:
- Instance-level (Site Administration → Actions → Runners) — registers a runner for all repositories
- Organization-level (Organization → Settings → Actions → Runners) — registers a runner for repos in that organization
- Repository-level (Repository → Settings → Actions → Runners) — registers a runner for a single repository
Use instance-level tokens for shared runners, and repo-level tokens for dedicated runners.
Get the Admin API Token (optional)
The admin API token enables post-install API checks that verify the runner appears in Gitea's runner list. This is purely informational — the integration test primarily verifies the runner by checking:
.runnerregistration file exists and contains valid JSON (proves successful registration)- Systemd user service is active (proves daemon is polling for jobs)
To get an admin API token:
- Go to Settings → Applications → Generate New Token
- Give it a name (e.g., "GRM Install Verification")
- Select the admin scope (or at minimum:
read:user,read:repository,read:admin) - Click Generate Token and copy it immediately (it won't be shown again)
Create the .env File
cp .env.example .env
Edit .env with your tokens:
# Your Gitea instance URL
GITEA_URL=https://git.example.com
# Registration token from Step 1
GITEA_REGISTRATION_TOKEN=GRxxxxxxxxxxxxxxxxxx
# Admin API token from Step 2 (optional)
CI_GITEA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Environment Variables Reference
| Variable | Required | Default | Description |
|---|---|---|---|
GITEA_URL |
Yes | — | Gitea instance URL (e.g., https://git.example.com) |
GITEA_REGISTRATION_TOKEN |
Yes | — | Runner registration token from Gitea admin panel |
CI_GITEA_TOKEN |
No | — | Admin API token for post-install verification |
GITEA_INTEGRATION_RETRIES |
No | 3 |
API check retries (default: 3) |
GITEA_RUNNER_USER |
No | current login | Default SSH user (overrides --user) |
GITEA_RUNNER_KEY |
No | — | Default SSH key path (overrides --key) |
GITEA_RUNNER_LABELS |
No | — | Default runner labels (overrides --labels) |
GRM_LANG |
No | en |
UI language: en, bg, de, ru, zh, pl |
GRM_LOG_LEVEL |
No | INFO |
Console log level: DEBUG, INFO, WARNING, ERROR, CRITICAL |
Step 3: Install Your First Runner
Using the CLI (you will be prompted for the sudo password by default):
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner
This command:
- Connects to
192.168.1.10via SSH as userubuntuusing the specified key - Creates a dedicated system user
grm-prod-runnerwith lingering enabled - Installs Docker in rootless mode under the
grm-prod-runneruser - Downloads and installs the gitea_runner binary
- Creates the runner configuration file at
/etc/gitea-runner/prod-runner/config.yaml - Registers the runner with your Gitea instance
- Creates and starts a systemd user service (
gitea-runner.service) - Sets up a Docker prune timer (daily cleanup)
- Runs an integration test to verify the installation
- Saves the runner to the local registry at
~/.local/share/grm/runners.json
Automation tip: Configure passwordless sudo on the remote host and pass
--no-ask-become-passto skip the password prompt. This is recommended for CI/CD pipelines.
Using Make:
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner
Runner labels
By default, runners are registered with the label docker,ubuntu-latest:docker://runner-images:ubuntu-22.04. You can override this with --labels:
grm install 192.168.1.10 --user ubuntu --name prod-runner \
--labels "docker:docker://gitea/runner-images:ubuntu-latest"
Note: Use an official Gitea runner image with Node.js, Python, and Docker CLI. Avoid bare OS images like
alpine:latestbecauseactions/checkout@v4needs Node.js.
Step 4: Verify the Installation
The installer performs an automated integration test that verifies:
.runnerfile exists with valid JSON containingid,uuid,token,address— this proves successful registration with Gitea- Systemd user service is active — this proves the daemon is polling for jobs
You can also check the Gitea UI under Actions → Runners to confirm the runner appears as Online.
Optional: If CI_GITEA_TOKEN is set, the installer will also query the Gitea API and report whether the runner appears in the admin or repo runners list. This is purely informational.
Check runner status via CLI
grm status prod-runner
This connects to the remote host and checks the systemd user service status.
List all runners
grm list
This displays a table with columns: NAME, HOST, USER, LABELS, STATUS for all runners in the local registry. The status is checked live via an Ansible ad-hoc command.
Step 5: Manage the Runner Lifecycle
Once installed, you can manage the runner by name (connection details are stored in the local registry):
grm stop prod-runner # Stop the runner service
grm start prod-runner # Start the runner service
grm enable prod-runner # Enable the runner to start on boot
grm status prod-runner # Check the runner status
grm update 192.168.1.10 --user ubuntu # Update the runner binary
grm disable prod-runner # Disable and deregister the runner
grm remove prod-runner # Remove the runner completely
See CLI Commands for the full command reference.
View Logs
GRM application logs (on your local machine)
# Application log file (all messages including DEBUG)
cat ~/.local/state/grm/logs/grm.log
# Enable debug logging in the current session
GRM_LOG_LEVEL=DEBUG grm install 192.168.1.10 --user ubuntu --name prod-runner
Ansible execution logs
Each grm command that invokes Ansible creates a timestamped log file:
ls ~/.local/state/grm/logs/ansible-*.log
cat ~/.local/state/grm/logs/ansible-20260622-143012.log
Runner logs (on the remote host)
# Runner logs (via systemd user service)
sudo -u grm-prod-runner journalctl --user -u gitea-runner -f
# Rootless Docker daemon logs
sudo -u grm-prod-runner journalctl --user -u docker -f
Logging destinations
The GRM application writes to three destinations:
| Destination | Level | Content |
|---|---|---|
| Console (stdout) | GRM_LOG_LEVEL (default: INFO) |
Colorised user-facing messages and operation reports |
~/.local/state/grm/logs/grm.log |
DEBUG | All messages with timestamps and severity |
~/.local/state/grm/logs/ansible-<timestamp>.log |
— | Full Ansible playbook output per execution |
Set GRM_LOG_LEVEL to one of DEBUG, INFO, WARNING, ERROR, or CRITICAL to control console verbosity. The log file always captures everything at DEBUG level regardless of the console setting.
Console output is automatically colorised via click.style: operation headers in bright cyan, completed steps in green, failures in red, and status updates in yellow.
Next Steps
- Install more runners on the same or different hosts — see Installation
- Learn all CLI commands — see CLI Commands
- Troubleshoot issues — see Troubleshooting
- Understand the architecture — see Architecture