9.1 KiB
Gitea Actions Runner — Workflows & Usage Guide
Overview
This document covers the Gitea Actions runner setup on pi-main, how the runner works, the label system, and example workflows for both host-based ("ubuntu") and Nix-native jobs.
Architecture
The runner is configured in modules/services/gitea-runner.nix and uses the
NixOS native services.gitea-actions-runner module. Jobs run with the host
executor: each step executes directly on the Pi 4 as the gitea-runner-pi-main
system user. There is no container isolation per job.
Gitea (podman container) │ HTTPS → Cloudflare tunnel → Caddy → git.zakobar.com │ (runner connects outbound via HTTPS, same path as a browser) ▼ gitea-actions-runner (systemd service) │ host executor ▼ Jobs run as: gitea-runner-pi-main (unprivileged system user) PATH includes: nix, git, bash + system packages
Runner labels
Labels are advertised to Gitea and matched against runs-on: in workflow
files. The default labels configured in this project are:
| Label | Executor | Notes |
|---|---|---|
native:host |
host | Canonical label for "run on this machine" |
ubuntu-latest |
host | Matches common GitHub Actions workflows |
debian-latest |
host | Alternative for Debian-targeting workflows |
nix:host |
host | Explicit label for Nix-native jobs |
All four labels route to the same runner process and the same host environment. The difference is purely semantic — pick the label that makes your workflow's intent clear.
Nix daemon trust
The runner user is added to nix.settings.trusted-users, which means it can:
- Evaluate flakes (
nix flake check,nix build) - Write derivation outputs to the Nix store
- Pass
--extra-experimental-featuresflags to the daemon - Use
nix copyto push/pull store paths to a remote cache
It cannot modify NixOS system configuration or run privileged operations.
Example workflows
Workflow files live in .gitea/workflows/ inside each repository (or
.github/workflows/ — Gitea Actions supports both paths).
Minimal smoke test (host)
The simplest possible workflow — runs a shell command on the runner.
# .gitea/workflows/smoke.yaml
on: [push]
jobs:
smoke:
runs-on: native:host
steps:
- uses: actions/checkout@v3
- run: echo "Runner is alive on $(hostname)"
Standard shell-based CI (ubuntu-latest label)
Use this for repos that want to stay compatible with GitHub Actions. The workflow looks identical to what you'd push to GitHub; it just runs on your Pi.
# .gitea/workflows/ci.yaml
on:
push:
branches: [main, master]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: |
# On the host executor, use nix-shell or system packages.
# apt/yum are NOT available — this is NixOS, not Ubuntu.
# Use nix-shell -p for one-off tools:
nix-shell -p nodejs --run "node --version"
- name: Run tests
run: |
nix-shell -p nodejs --run "npm test"
Important: Despite the label ubuntu-latest, the host is NixOS. apt,
yum, and FHS paths like /usr/bin/python3 are not available. Use
nix-shell -p <pkg> to bring in any tool you need.
Nix flake check
Validate a flake on every push — the most common use case for this runner.
# .gitea/workflows/flake-check.yaml
on: [push, pull_request]
jobs:
check:
runs-on: nix:host
steps:
- uses: actions/checkout@v3
- name: Check flake
run: nix flake check --no-build
- name: Build default package
run: nix build
Nix build with caching
Build a derivation and push the result to a binary cache so subsequent builds
are fast. Requires a Cachix account or an S3-compatible cache configured in
nix.settings.
# .gitea/workflows/build-and-cache.yaml
on:
push:
branches: [main]
jobs:
build:
runs-on: nix:host
steps:
- uses: actions/checkout@v3
- name: Build
run: nix build --print-build-logs
- name: Push to cache
# nix copy requires the runner user to be in trusted-users (already set).
# Replace the URI with your actual cache.
run: |
nix copy --to "s3://your-cache-bucket?region=us-east-1" ./result
NixOS configuration check (this repo)
Check that the homey flake evaluates cleanly on every change. Add this to the homey repo itself.
# .gitea/workflows/nixos-check.yaml
on: [push, pull_request]
jobs:
eval:
runs-on: nix:host
steps:
- uses: actions/checkout@v3
- name: Evaluate NixOS configurations
run: |
nix flake check --no-build
# Optionally build a specific host config (slow on Pi):
# nix build .#nixosConfigurations.pi-main.config.system.build.toplevel
- name: Check formatting (optional)
run: |
nix-shell -p nixpkgs-fmt --run "nixpkgs-fmt --check ."
Multi-step pipeline with artifacts
# .gitea/workflows/pipeline.yaml
on:
push:
tags: ['v*']
jobs:
build:
runs-on: nix:host
steps:
- uses: actions/checkout@v3
- name: Build release
run: nix build --out-link result
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: release-binary
path: result/bin/
deploy:
needs: build
runs-on: native:host
steps:
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: release-binary
- name: Deploy
run: ./deploy.sh
Caveats and gotchas
No apt/brew/yum
The host is NixOS. Package managers from other distros do not work. Use
nix-shell -p <pkg> --run "..." for ad-hoc tools, or add a shell.nix /
flake.nix devShell to your repo and enter it with nix develop.
No Docker/Podman per job
The host executor does not launch a fresh container per job. All jobs share the
same filesystem (under /home/gitea-runner-pi-main/) and the same running
system. This means:
- No isolation between concurrent jobs (though concurrency defaults to 1).
- Side effects (files written, packages installed with nix) persist between runs unless you clean up explicitly.
- Use
nix buildoutput symlinks (./result) rather than writing to system paths.
actions/checkout and git
The actions/checkout@v3 action works fine on the host executor. It clones
into the runner's working directory. Subsequent steps run in that directory by
default.
If you use actions/checkout@v4, note that it requires a newer Node.js. On
NixOS you can't rely on a system Node, so either pin to v3 or use:
- uses: actions/checkout@v3 # v3 bundles its own Node runtime
Nix experimental features
Flake commands require nix-command and flakes experimental features. These
are typically enabled system-wide in nix.settings.experimental-features in
modules/common.nix. If a job fails with "experimental feature not enabled",
you can pass it inline:
- run: nix --extra-experimental-features "nix-command flakes" flake check
Or ensure common.nix has:
nix.settings.experimental-features = [ "nix-command" "flakes" ];
Token rotation
The registration token in gitea/runner_token is consumed on first
registration. The runner then stores its own credentials in
/var/lib/gitea-runner/pi-main/.runner. If you need to re-register (e.g.
after wiping the state directory), generate a new token from Gitea's admin UI
and update the sops secret before restarting the service.
Pi 4 performance
The Pi 4 is capable but not fast for heavy builds. Tips:
- Enable the Nix binary cache (
nixos-cache.nixos.orgis on by default) so pre-built derivations are fetched instead of compiled. - Set
nix.settings.max-jobsto4to use all cores for parallel builds. - Avoid building large packages (LLVM, Chromium) locally — push to a remote builder or use Cachix.
Debugging
Check runner status
systemctl status gitea-runner-pi-main
journalctl -u gitea-runner-pi-main -f
Runner registration state
cat /var/lib/gitea-runner/pi-main/.runner
Force re-registration
# Stop, wipe state, restart (runner will re-register using the token file)
systemctl stop gitea-runner-pi-main
rm /var/lib/gitea-runner/pi-main/.runner
systemctl start gitea-runner-pi-main
Test a workflow locally
Use act (the local runner) to test workflow files before pushing:
nix-shell -p act --run "act push"
Note: act spins up Docker containers for each job, so results may differ
slightly from the host-executor runner, but it is useful for syntax checking
and logic testing.