8.5 KiB
Installation
Before you start: Make sure you have cloned the repo and checked out the latest stable release tag. See Getting Started for setup instructions. Do not run from
master— it may contain unreleased changes.
Prerequisites
On your local machine (where you run grm)
- Python 3.12+ — GRM targets Python 3.12 and requires it for development setup. Use
pyenvif you need to manage multiple Python versions. - Ansible — Installed automatically by
make setup(via pip). GRM delegates all remote operations toansible-playbook. - SSH key — A private key that grants access to the target host(s) as a user with sudo privileges.
On the target host(s) (where runners will be installed)
- SSH server — The remote host must be reachable via SSH using the user specified with
--userand 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 access — GRM requires root privileges on the remote host to create system users, install packages, and configure rootless Docker. By default, you will be prompted interactively for the sudo password. For automation or uninterrupted workflows, configure passwordless sudo on the remote host and pass
--no-ask-become-pass. - Gitea registration token — You need a runner registration token from your Gitea instance. See Getting Started for detailed instructions on obtaining tokens.
- systemd — Required for user services and lingering. All supported OSes ship with systemd.
- Docker — Installed automatically by the Ansible role (rootless mode). No pre-existing Docker installation is required.
Supported Operating Systems
| OS | Versions | Package manager |
|---|---|---|
| Arch Linux | rolling | pacman |
| Ubuntu | 22.04, 24.04 | apt |
| Debian | 12 | apt |
All supported OSes are tested in CI via Molecule scenarios on every PR that changes Ansible files. The platform matrix is defined in devx.molecule.platforms as the single source of truth.
Installation Methods
Method 1: From source (recommended for full control)
git clone https://git.oblachno.oblachno.fyi/oblachno-oss/grm.git
cd grm
git checkout $(git describe --tags --abbrev=0) # Latest stable release
make setup
source .venv/bin/activate
make setup performs the following:
- Verifies Python 3.12+ is installed
- Creates a virtualenv in
.venv - Installs all Python dependencies (including Ansible, Click, python-dotenv)
- Creates
.envfrom.env.exampleif not present - Installs development tools (actionlint, git-cliff, act_runner, checkmake)
- Sets up pre-commit hooks
Method 2: Via pip
GRM is published to the Gitea PyPI registry at
https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple.
The registry is publicly readable — no authentication required to install.
Quick install (one-off):
pip install grm --index-url https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
Persistent configuration (recommended):
Add the registry to ~/.pip/pip.conf:
[global]
extra-index-url = https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
Then install normally:
pip install grm
This installs the grm CLI and its Python dependencies. The Ansible playbooks
and role are bundled with the package, so grm install works out of the box.
For development or access to Make targets, clone the repository (Method 1).
Post-install configuration
After installation, create your .env file:
cp .env.example .env
# Edit .env with your Gitea URL and registration token
Required variables:
| Variable | Description |
|---|---|
GITEA_URL |
Your Gitea instance URL (e.g., https://git.example.com) |
GITEA_REGISTRATION_TOKEN |
Runner registration token from Gitea (starts with GR) |
See Getting Started for detailed token setup instructions.
Quick Start Install
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
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.
Make Install
Using Make:
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner
The Make target wraps the grm install CLI command. All Make install variables are optional except HOST:
| Variable | Description |
|---|---|
HOST |
Remote host (IP address or hostname) — required |
USER |
SSH user |
KEY |
Path to SSH private key |
NAME |
Gitea Runner name |
TOKEN |
Registration token |
ASK_BECOME_PASS |
Set to 1 to prompt for sudo password |
What Gets Installed on the Target Host
When you run grm install, the Ansible role creates the following on the remote host:
| Resource | Path | Description |
|---|---|---|
| System user | grm-<name> |
Dedicated system user with /bin/bash shell |
| Home directory | /home/grm-<name>/ |
User home with .config/systemd/user/ |
| Data directory | /var/lib/gitea-runner/<name>/ |
Runner data including .runner registration file |
| Config directory | /etc/gitea-runner/<name>/ |
Runner configuration file (config.yaml) |
| Runner binary | /usr/local/bin/gitea_runner |
The gitea_runner executable |
| Docker socket | /run/user/<UID>/docker.sock |
Rootless Docker socket |
| Systemd service | gitea-runner.service |
User service for the runner daemon |
| Docker prune timer | docker-prune.timer |
Daily Docker cleanup timer |
| subuid/subgid | /etc/subuid, /etc/subgid |
User namespace mapping (100000-165535) |
| Lingering | loginctl enable-linger |
Ensures services run without active login |
Runner Registry
After installation, GRM stores each runner's connection details (host, user, SSH key, Gitea URL, labels) in a local JSON registry at ~/.local/share/grm/runners.json. This means you rarely need to repeat connection arguments:
# List all registered runners with live systemd status
grm list
# Manage runners by name — connection details come from the registry
grm status prod-runner
grm stop prod-runner
grm start prod-runner
You can override any stored value by passing the corresponding flag (--host, --user, --key).
Registry file format
{
"prod-runner": {
"host": "192.168.1.10",
"user": "ubuntu",
"key": "/home/user/.ssh/id_ed25519",
"gitea_url": "https://git.example.com",
"labels": "docker:docker://gitea/runner-images:ubuntu-latest",
"created_at": "2026-06-22T14:30:12.000000+00:00"
}
}
Multiple Instances on the Same Host
Each runner instance is fully isolated with its own system user, rootless Docker daemon, data directory, and systemd user service:
# Install two runners on the same host
grm install 192.168.1.10 --user ubuntu --name workflow-runner
grm install 192.168.1.10 --user ubuntu --name build-runner
# Manage them independently by name
grm stop workflow-runner
grm status build-runner
grm list
Each instance gets:
- Dedicated system user:
grm-<name>with its own home directory - Rootless Docker daemon: Isolated Docker socket at
/run/user/<UID>/docker.sock - Data directory:
/var/lib/gitea-runner/<name>/ - Config directory:
/etc/gitea-runner/<name>/ - Systemd user service:
gitea-runner.service(independent start/stop/enable) - Docker prune timer: Per-instance daily cleanup
Runners on the same host never interfere with each other or with the host's Docker installation.
Updating Runners
To update the gitea_runner binary on a remote host:
grm update 192.168.1.10 --user ubuntu
To update to a specific version:
grm update 192.168.1.10 --user ubuntu --version 1.0.8
Using Make:
make update HOST=192.168.1.10 USER=ubuntu VERSION=1.0.8
Removing Runners
To remove a runner entirely (deregisters from Gitea, removes user, directories, and service files):
grm remove prod-runner --token <registration-token>
To skip remote cleanup and only remove the local registry entry (useful when the remote host is already gone):
grm remove prod-runner --force
Using Make:
make remove NAME=prod-runner TOKEN=<registration-token>