Feature Specification: Component forgejo-runner
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code at 093e68b6f (core/components/dev/default.nix, core/components/dev/forgejo-runner/default.nix, core/components/dev/forgejo-runner/forgejo-runner.nix, core/setups/groups/dev.nix, core/checks/ci.nix), pull requests #1383, #1430, #1452, #1453, #1454, #1466, #1470, #1484, #1524, #1528, #1534, #1547, #1623, #1693, #1708, #1735, #1746, #1822, #1852, #1956, #1967, #1989 and #2017.
This spec describes one component of the dev group. The family spec
../spec.md describes what all components share. The group
roster is in core/setups/groups/dev.nix, see
../../005-setups-and-wiring/spec.md.
User Scenarios & Testing (mandatory)
User Story 1 - CI jobs run on a memory tier (Priority: P1)
A workflow asks for a runner by label, for example ram-8g. The component
runs a pool of Forgejo runner instances on each CI host. Each instance
belongs to one memory tier, and systemd caps the instance at the
MemoryMax of that tier. The instances run jobs natively on the host,
with no Docker.
Why this priority: All CI of the repository runs on this pool.
Independent Test: nix-unit-setups-forgejo-runner-pool
(core/setups/forgejo-runner-pool-test.nix) evaluates the pool from the
roster, and the instances, MemoryMax and labels of each tier on the
realised dev-fediversity-ci node, also with other pools. No check deploys
the runner pool. core/checks/ci.nix reads the tier labels to route checks.
Acceptance Scenarios:
- Given a CI host in the pool, When it deploys, Then it runs per host 3
ram-64g, 2ram-32g, 4ram-16g, 5ram-8g, 15ram-4g, 6ram-2gand 11ram-1ginstances. - Given an instance, When it starts, Then it declares the labels
native:host,ram-<N>g:host,nspawn:hostand onehost-not-<peer>:hostper other pool member. - Given an instance, When it registers, Then it connects to
fediversity.forgejo-runner.forgeUrl(https://git.fediversity.euin thedevgroup) with the token from theforgejo-runner-tokensecret andcapacity = 1.
User Story 2 - A deploy does not cancel running jobs (Priority: P1)
The operator deploys a new runner configuration while jobs run. The
runner units have restartIfChanged = false, so the switch does not stop
them. The forgejo-runner-restarter service then drains and restarts the
runners one at a time.
Why this priority: A cancelled job wastes hours of CI time and gives a false red result.
Independent Test: No check covers the drain.
Acceptance Scenarios:
- Given a change to any rendered runner unit, When the host switches, Then
runnerUnitsStampchanges and the restarter runs. - Given a rotated
forgejo-runner-token.age, When the host switches, ThentokenStampchanges the runner units, and the restarter brings the pool onto the new token. - Given a busy runner, When the restarter drains it, Then the job can run to
jobTimeout(3 hours) before the runner stops.
User Story 3 - The host frees store space without blocking builds (Priority: P2)
CI fills the nix store. The ci-gc service frees space when the free
space falls below a trigger. Builds do not wait for it.
Why this priority: A full disk fails every build on the host.
Independent Test: No check covers ci-gc.
Acceptance Scenarios:
- Given less than 60 GB free on
/nix/store, When theci-gctimer fires, Thenci-gcremoves oldresultroots, deletes system generations older than 14 days, and collects garbage up to 100 GB free. - Given at least 60 GB free, When the timer fires, Then
ci-gcdoes nothing.
Edge Cases
fediversity.forgejo-runner.machineis not infediversity.forgejo-runner.pool: an assertion fails the evaluation.- The timeouts break the order
stopTimeout < jobTimeout <= shutdownTimeout < drainCap: an assertion fails the evaluation. - A roster entry in
dev.nixhas nocd = { deploy; runsJobs; }: the evaluation throws an error that names the host. - A
resultlink under/tmpor/homethat is older than 7 days is removed only whenci-gcruns under its trigger. - An
actworkspace under/var/lib/private/gitea-runner/*/.cache/actis removed after 1 day. - The disk fills before
ci-gcfrees space: builds fail with ENOSPC, becausemin-free = 0.
Requirements (mandatory)
Functional Requirements
- FR-001: The component MUST declare
components.forgejo-runnerwithname = "Forgejo Runner",package = pkgs.forgejo-runnerandnixStore = "own". - FR-002: The component MUST NOT request a domain or TLS, because the host has no DNS entry.
- FR-003: The component MUST request the
secretsentryforgejo-runner-tokenwith ownerroot. - FR-004: The component MUST import
forgejo-runner.nixthrough thenixos-moduleresource. - FR-005: The module MUST expose
fediversity.forgejo-runner.pool, a list of strings with default[ ], that holds every machine in the pool. - FR-005a: The component MUST have the option
forgeUrl, with no default, and MUST setfediversity.forgejo-runner.forgeUrlfrom it. The module MUST also exposefediversity.forgejo-runner.machine, with no default, the roster name of the host. - FR-006:
core/setups/groups/dev.nixMUST deriverunnerPoolfrom the machines withcomponent = "forgejo-runner", MUST setfediversity.forgejo-runner.poolfrom it andfediversity.forgejo-runner.machinefrom the roster key, and MUST setforgeUrltohttps://plus the domain of the forge. - FR-007: Each runner instance MUST be named
runner-<label>-<i>and MUST run with theMemoryMaxof its tier. - FR-008: Each instance MUST build its
server.connectionsfrom.runnerbefore start, with a 30s fetch interval. - FR-009: Runner units MUST set
restartIfChanged = false.forgejo-runner-restarterMUST haverestartTriggers = [ runnerUnitsStamp ]. - FR-010:
runnerUnitsStampMUST hash every rendered runner unit, and the units MUST carrytokenStamp, the hash of the file that thesecretsresource gives forforgejo-runner-token. - FR-011:
nix-daemonMUST run withMemoryMax = "200G". - FR-012: The module MUST set
nix.gc.automatic = falseandmin-free = 0, and MUST runci-gcfrom a timer withOnBootSec = "5min"andOnUnitInactiveSec = "5min". - FR-013:
ci-gcMUST run withNice = 19andIOSchedulingClass = "idle".
Key Entities
runnerTiers: The list of memory tiers, each withlabel,countandmemoryMax.fediversity.forgejo-runner.pool: The pool members. Thehost-not-<peer>labels are the complement offediversity.forgejo-runner.machinein the pool.forgejo-runner-restarter: The service that drains and restarts the runners after a switch.ci-gc: The GC service and timer for the host.- CI hosts: The roster machines
fediversity-ci,dev2,dev3anddev4incore/setups/groups/dev.nix.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: The
devgroup evaluates with all assertions offorgejo-runner.nixtrue. - SC-002: A deploy of a changed runner configuration cancels no running job.
- SC-003: Free space on
/nix/storereturns above 100 GB after aci-gcpass.
Assumptions
- The pool hosts are reached by IPv4 literals and get their host name from the
networkresource. - The component requests
forgejo-runner-tokenfromcore/resources/secrets/forgejo-runner-token.age.nix-unit-resources-secretscovers the secret routing, not the stamp.
Known Deviations
None.