Skip to content

Commit 1c80c49

Browse files
authored
Merge pull request #228422 from mweinelt/gitea-actions-runner-module
nixos/gitea-actions-runner: init
2 parents 7bbda7a + 1c963ce commit 1c80c49

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

nixos/doc/manual/release-notes/rl-2305.section.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ In addition to numerous new and upgraded packages, this release has the followin
5656

5757
- [gemstash](https://github.com/rubygems/gemstash), a RubyGems.org cache and private gem server. Available as [services.gemstash](#opt-services.gemstash.enable).
5858

59+
- [gitea-actions-runner](https://gitea.com/gitea/act_runner), a CI runner for Gitea/Forgejo Actions. Available as [services.gitea-actions-runner](#opt-services.gitea-actions-runner.instances).
60+
5961
- [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer. Available as [services.gmediarender](options.html#opt-services.gmediarender.enable).
6062

6163
- [harmonia](https://github.com/nix-community/harmonia/), Nix binary cache implemented in rust using libnix-store. Available as [services.harmonia](options.html#opt-services.harmonia.enable).

nixos/modules/module-list.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@
374374
./services/continuous-integration/buildbot/master.nix
375375
./services/continuous-integration/buildbot/worker.nix
376376
./services/continuous-integration/buildkite-agents.nix
377+
./services/continuous-integration/gitea-actions-runner.nix
377378
./services/continuous-integration/github-runner.nix
378379
./services/continuous-integration/github-runners.nix
379380
./services/continuous-integration/gitlab-runner.nix
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
{ config
2+
, lib
3+
, pkgs
4+
, utils
5+
, ...
6+
}:
7+
8+
let
9+
inherit (lib)
10+
any
11+
attrValues
12+
concatStringsSep
13+
escapeShellArg
14+
hasInfix
15+
hasSuffix
16+
optionalAttrs
17+
optionals
18+
literalExpression
19+
mapAttrs'
20+
mkEnableOption
21+
mkOption
22+
mkPackageOptionMD
23+
mkIf
24+
nameValuePair
25+
types
26+
;
27+
28+
inherit (utils)
29+
escapeSystemdPath
30+
;
31+
32+
cfg = config.services.gitea-actions-runner;
33+
34+
# Check whether any runner instance label requires a container runtime
35+
# Empty label strings result in the upstream defined defaultLabels, which require docker
36+
# https://gitea.com/gitea/act_runner/src/tag/v0.1.5/internal/app/cmd/register.go#L93-L98
37+
hasDockerScheme = instance:
38+
instance.labels == [] || any (label: hasInfix ":docker:" label) instance.labels;
39+
wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances);
40+
41+
hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels;
42+
43+
# provide shorthands for whether container runtimes are enabled
44+
hasDocker = config.virtualisation.docker.enable;
45+
hasPodman = config.virtualisation.podman.enable;
46+
47+
tokenXorTokenFile = instance:
48+
(instance.token == null && instance.tokenFile != null) ||
49+
(instance.token != null && instance.tokenFile == null);
50+
in
51+
{
52+
meta.maintainers = with lib.maintainers; [
53+
hexa
54+
];
55+
56+
options.services.gitea-actions-runner = with types; {
57+
package = mkPackageOptionMD pkgs "gitea-actions-runner" { };
58+
59+
instances = mkOption {
60+
default = {};
61+
description = lib.mdDoc ''
62+
Gitea Actions Runner instances.
63+
'';
64+
type = attrsOf (submodule {
65+
options = {
66+
enable = mkEnableOption (lib.mdDoc "Gitea Actions Runner instance");
67+
68+
name = mkOption {
69+
type = str;
70+
example = literalExpression "config.networking.hostName";
71+
description = lib.mdDoc ''
72+
The name identifying the runner instance towards the Gitea/Forgejo instance.
73+
'';
74+
};
75+
76+
url = mkOption {
77+
type = str;
78+
example = "https://forge.example.com";
79+
description = lib.mdDoc ''
80+
Base URL of your Gitea/Forgejo instance.
81+
'';
82+
};
83+
84+
token = mkOption {
85+
type = nullOr str;
86+
default = null;
87+
description = lib.mdDoc ''
88+
Plain token to register at the configured Gitea/Forgejo instance.
89+
'';
90+
};
91+
92+
tokenFile = mkOption {
93+
type = nullOr (either str path);
94+
default = null;
95+
description = lib.mdDoc ''
96+
Path to an environment file, containing the `TOKEN` environment
97+
variable, that holds a token to register at the configured
98+
Gitea/Forgejo instance.
99+
'';
100+
};
101+
102+
labels = mkOption {
103+
type = listOf str;
104+
example = literalExpression ''
105+
[
106+
# provide a debian base with nodejs for actions
107+
"debian-latest:docker://node:18-bullseye"
108+
# fake the ubuntu name, because node provides no ubuntu builds
109+
"ubuntu-latest:docker://node:18-bullseye"
110+
# provide native execution on the host
111+
#"native:host"
112+
]
113+
'';
114+
description = lib.mdDoc ''
115+
Labels used to map jobs to their runtime environment. Changing these
116+
labels currently requires a new registration token.
117+
118+
Many common actions require bash, git and nodejs, as well as a filesystem
119+
that follows the filesystem hierarchy standard.
120+
'';
121+
};
122+
123+
hostPackages = mkOption {
124+
type = listOf package;
125+
default = with pkgs; [
126+
bash
127+
coreutils
128+
curl
129+
gawk
130+
gitMinimal
131+
gnused
132+
nodejs
133+
wget
134+
];
135+
defaultText = literalExpression ''
136+
with pkgs; [
137+
bash
138+
coreutils
139+
curl
140+
gawk
141+
gitMinimal
142+
gnused
143+
nodejs
144+
wget
145+
]
146+
'';
147+
description = lib.mdDoc ''
148+
List of packages, that are available to actions, when the runner is configured
149+
with a host execution label.
150+
'';
151+
};
152+
};
153+
});
154+
};
155+
};
156+
157+
config = mkIf (cfg.instances != {}) {
158+
assertions = [ {
159+
assertion = any tokenXorTokenFile (attrValues cfg.instances);
160+
message = "Instances of gitea-actions-runner can have `token` or `tokenFile`, not both.";
161+
} {
162+
assertion = wantsContainerRuntime -> hasDocker || hasPodman;
163+
message = "Label configuration on gitea-actions-runner instance requires either docker or podman.";
164+
} ];
165+
166+
systemd.services = let
167+
mkRunnerService = name: instance: let
168+
wantsContainerRuntime = hasDockerScheme instance;
169+
wantsHost = hasHostScheme instance;
170+
wantsDocker = wantsContainerRuntime && config.virtualisation.docker.enable;
171+
wantsPodman = wantsContainerRuntime && config.virtualisation.podman.enable;
172+
in
173+
nameValuePair "gitea-runner-${escapeSystemdPath name}" {
174+
inherit (instance) enable;
175+
description = "Gitea Actions Runner";
176+
after = [
177+
"network-online.target"
178+
] ++ optionals (wantsDocker) [
179+
"docker.service"
180+
] ++ optionals (wantsPodman) [
181+
"podman.service"
182+
];
183+
wantedBy = [
184+
"multi-user.target"
185+
];
186+
environment = optionalAttrs (instance.token != null) {
187+
TOKEN = "${instance.token}";
188+
} // optionalAttrs (wantsPodman) {
189+
DOCKER_HOST = "unix:///run/podman/podman.sock";
190+
};
191+
path = with pkgs; [
192+
coreutils
193+
] ++ lib.optionals wantsHost instance.hostPackages;
194+
serviceConfig = {
195+
DynamicUser = true;
196+
User = "gitea-runner";
197+
StateDirectory = "gitea-runner";
198+
WorkingDirectory = "-/var/lib/gitea-runner/${name}";
199+
ExecStartPre = pkgs.writeShellScript "gitea-register-runner-${name}" ''
200+
export INSTANCE_DIR="$STATE_DIRECTORY/${name}"
201+
mkdir -vp "$INSTANCE_DIR"
202+
cd "$INSTANCE_DIR"
203+
204+
# force reregistration on changed labels
205+
export LABELS_FILE="$INSTANCE_DIR/.labels"
206+
export LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)"
207+
export LABELS_CURRENT="$(cat $LABELS_FILE 2>/dev/null || echo 0)"
208+
209+
if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED" != "$LABELS_CURRENT" ]; then
210+
# remove existing registration file, so that changing the labels forces a re-registation
211+
rm -v "$INSTANCE_DIR/.runner" || true
212+
213+
# perform the registration
214+
${cfg.package}/bin/act_runner register --no-interactive \
215+
--instance ${escapeShellArg instance.url} \
216+
--token "$TOKEN" \
217+
--name ${escapeShellArg instance.name} \
218+
--labels ${escapeShellArg (concatStringsSep "," instance.labels)}
219+
220+
# and write back the configured labels
221+
echo "$LABELS_WANTED" > "$LABELS_FILE"
222+
fi
223+
224+
'';
225+
ExecStart = "${cfg.package}/bin/act_runner daemon";
226+
SupplementaryGroups = optionals (wantsDocker) [
227+
"docker"
228+
] ++ optionals (wantsPodman) [
229+
"podman"
230+
];
231+
} // optionalAttrs (instance.tokenFile != null) {
232+
EnvironmentFile = instance.tokenFile;
233+
};
234+
};
235+
in mapAttrs' mkRunnerService cfg.instances;
236+
};
237+
}

0 commit comments

Comments
 (0)