#+TITLE: Gitea Actions Runner — Workflows & Usage Guide #+DATE: 2026-05-04 #+AUTHOR: homey project #+OPTIONS: toc:2 num:t * 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. #+BEGIN_EXAMPLE 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 #+END_EXAMPLE ** 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-features= flags to the daemon - Use =nix copy= to 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. #+BEGIN_SRC yaml # .gitea/workflows/smoke.yaml on: [push] jobs: smoke: runs-on: native:host steps: - uses: actions/checkout@v3 - run: echo "Runner is alive on $(hostname)" #+END_SRC ** 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. #+BEGIN_SRC yaml # .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" #+END_SRC *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 = to bring in any tool you need. ** Nix flake check Validate a flake on every push — the most common use case for this runner. #+BEGIN_SRC yaml # .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 #+END_SRC ** 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=. #+BEGIN_SRC yaml # .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 #+END_SRC ** NixOS configuration check (this repo) Check that the homey flake evaluates cleanly on every change. Add this to the homey repo itself. #+BEGIN_SRC yaml # .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 ." #+END_SRC ** Multi-step pipeline with artifacts #+BEGIN_SRC yaml # .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 #+END_SRC * Caveats and gotchas ** No apt/brew/yum The host is NixOS. Package managers from other distros do not work. Use =nix-shell -p --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 build= output 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: #+BEGIN_SRC yaml - uses: actions/checkout@v3 # v3 bundles its own Node runtime #+END_SRC ** 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: #+BEGIN_SRC yaml - run: nix --extra-experimental-features "nix-command flakes" flake check #+END_SRC Or ensure =common.nix= has: #+BEGIN_SRC nix nix.settings.experimental-features = [ "nix-command" "flakes" ]; #+END_SRC ** 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.org= is on by default) so pre-built derivations are fetched instead of compiled. - Set =nix.settings.max-jobs= to =4= to 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 #+BEGIN_SRC sh systemctl status gitea-runner-pi-main journalctl -u gitea-runner-pi-main -f #+END_SRC ** Runner registration state #+BEGIN_SRC sh cat /var/lib/gitea-runner/pi-main/.runner #+END_SRC ** Force re-registration #+BEGIN_SRC sh # 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 #+END_SRC ** Test a workflow locally Use =act= (the local runner) to test workflow files before pushing: #+BEGIN_SRC sh nix-shell -p act --run "act push" #+END_SRC 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.