Skip to content

Commit dc3b027

Browse files
committed
nixos/forgejo.runner: initialize module
1 parent 4cd68a8 commit dc3b027

5 files changed

Lines changed: 380 additions & 37 deletions

File tree

dump.tar.zst

499 KB
Binary file not shown.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070

7171
- [perses](https://perses.dev/), the open dashboard tool for Prometheus and other data sources. Available as [services.perses](#opt-services.perses.enable).
7272

73+
- [Forgejo Runner](https://code.forgejo.org/forgejo/runner), a dedicated Forgejo Actions runner module for multi-instance deployments is now available as [services.forgejo.runner.instances](#opt-services.forgejo.runner.instances).
74+
7375
- [Drasl](https://github.com/unmojang/drasl), an alternative authentication server for Minecraft. Available as [services.drasl](#opt-services.drasl.enable).
7476

7577
## Backward Incompatibilities {#sec-release-26.05-incompatibilities}

nixos/modules/module-list.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@
502502
./services/continuous-integration/buildbot/master.nix
503503
./services/continuous-integration/buildbot/worker.nix
504504
./services/continuous-integration/buildkite-agents.nix
505+
./services/continuous-integration/forgejo-runner.nix
505506
./services/continuous-integration/gitea-actions-runner.nix
506507
./services/continuous-integration/github-runners.nix
507508
./services/continuous-integration/gitlab-runner/runner.nix
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
{
2+
config,
3+
lib,
4+
pkgs,
5+
utils,
6+
...
7+
}:
8+
9+
let
10+
inherit (lib)
11+
any
12+
attrValues
13+
concatStringsSep
14+
escapeShellArg
15+
hasInfix
16+
hasSuffix
17+
literalExpression
18+
mapAttrs'
19+
mkEnableOption
20+
mkIf
21+
mkOption
22+
mkPackageOption
23+
mkRenamedOptionModule
24+
nameValuePair
25+
optionalAttrs
26+
optionals
27+
teams
28+
types
29+
;
30+
31+
inherit (utils)
32+
escapeSystemdPath
33+
;
34+
35+
cfg = config.services.forgejo.runner;
36+
37+
settingsFormat = pkgs.formats.yaml { };
38+
39+
# Empty label strings result in upstream default labels, which require docker.
40+
hasDockerScheme =
41+
instance: instance.labels == [ ] || any (label: hasInfix ":docker:" label) instance.labels;
42+
wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances);
43+
44+
hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels;
45+
46+
hasDocker = config.virtualisation.docker.enable;
47+
hasPodman = config.virtualisation.podman.enable;
48+
in
49+
{
50+
meta.maintainers = teams.forgejo.members;
51+
52+
imports = [
53+
(mkRenamedOptionModule [ "services" "forgejo-runner" ] [ "services" "forgejo" "runner" ])
54+
];
55+
56+
options.services.forgejo.runner = with types; {
57+
package = mkPackageOption pkgs "forgejo-runner" { };
58+
59+
instances = mkOption {
60+
default = { };
61+
description = ''
62+
Forgejo Actions Runner instances.
63+
'';
64+
type = attrsOf (
65+
submodule (
66+
{ name, ... }:
67+
{
68+
options = {
69+
enable = mkEnableOption "Forgejo Actions Runner instance";
70+
71+
name = mkOption {
72+
type = str;
73+
example = literalExpression "config.networking.hostName";
74+
description = ''
75+
The name identifying the runner instance towards the Forgejo instance.
76+
'';
77+
default = name;
78+
};
79+
80+
url = mkOption {
81+
type = str;
82+
example = "https://forge.example.com";
83+
description = ''
84+
Base URL of your Forgejo instance.
85+
'';
86+
};
87+
88+
tokenFile = mkOption {
89+
type = nullOr (either str path);
90+
default = null;
91+
description = ''
92+
Path to a file containing only the token that will be used to register
93+
with the the configured Forgejo instance.
94+
'';
95+
};
96+
97+
labels = mkOption {
98+
type = listOf str;
99+
example = literalExpression ''
100+
[
101+
# provide a debian base with nodejs for actions
102+
"debian-latest:docker://node:18-bullseye"
103+
# fake the ubuntu name, because node provides no ubuntu builds
104+
"ubuntu-latest:docker://node:18-bullseye"
105+
# provide native execution on the host
106+
#"native:host"
107+
]
108+
'';
109+
description = ''
110+
Labels used to map jobs to their runtime environment. Changing these
111+
labels currently requires a new registration token.
112+
113+
Many common actions require bash, git and nodejs, as well as a filesystem
114+
that follows the filesystem hierarchy standard.
115+
'';
116+
};
117+
118+
settings = mkOption {
119+
description = ''
120+
Configuration for `forgejo-runner daemon`.
121+
See <https://code.forgejo.org/forgejo/runner/src/branch/main/internal/pkg/config/config.example.yaml> for an example configuration.
122+
'';
123+
124+
type = types.submodule {
125+
freeformType = settingsFormat.type;
126+
};
127+
128+
default = { };
129+
};
130+
131+
hostPackages = mkOption {
132+
type = listOf package;
133+
default = with pkgs; [
134+
bash
135+
coreutils
136+
curl
137+
gawk
138+
gitMinimal
139+
gnused
140+
nodejs
141+
wget
142+
];
143+
defaultText = literalExpression ''
144+
with pkgs; [
145+
bash
146+
coreutils
147+
curl
148+
gawk
149+
gitMinimal
150+
gnused
151+
nodejs
152+
wget
153+
]
154+
'';
155+
description = ''
156+
List of packages that are available to actions, when the runner is configured
157+
with a host execution label.
158+
'';
159+
};
160+
};
161+
}
162+
)
163+
);
164+
};
165+
};
166+
167+
config = mkIf (cfg.instances != { }) {
168+
assertions = [
169+
{
170+
assertion = wantsContainerRuntime -> hasDocker || hasPodman;
171+
message = "Label configuration on forgejo.runner instance requires either docker or podman.";
172+
}
173+
];
174+
175+
systemd.services =
176+
let
177+
mkRunnerInstance =
178+
_: instance:
179+
let
180+
escapedName = escapeSystemdPath instance.name;
181+
wantsContainer = hasDockerScheme instance;
182+
wantsHost = hasHostScheme instance;
183+
wantsDocker = wantsContainer && hasDocker;
184+
wantsPodman = wantsContainer && hasPodman;
185+
configFile = settingsFormat.generate "forgejo-runner-${escapedName}.yaml" instance.settings;
186+
in
187+
nameValuePair "forgejo-runner@${escapedName}" {
188+
overrideStrategy = "asDropin";
189+
inherit (instance) enable;
190+
wants = [
191+
"network-online.target"
192+
]
193+
++ optionals wantsDocker [ "docker.service" ]
194+
++ optionals wantsPodman [ "podman.service" ];
195+
after = [
196+
"network-online.target"
197+
]
198+
++ optionals wantsDocker [ "docker.service" ]
199+
++ optionals wantsPodman [ "podman.service" ];
200+
wantedBy = [ "multi-user.target" ];
201+
202+
environment = optionalAttrs wantsPodman {
203+
DOCKER_HOST = "unix:///run/podman/podman.sock";
204+
};
205+
206+
path = [ pkgs.coreutils ] ++ lib.optionals wantsHost instance.hostPackages;
207+
208+
serviceConfig = {
209+
MemoryDenyWriteExecute = !wantsHost;
210+
211+
LoadCredential = [ "TOKEN:${instance.tokenFile}" ];
212+
213+
ExecStartPre = [
214+
(lib.getExe (
215+
pkgs.writeShellApplication {
216+
name = "forgejo-register-runner-${escapedName}";
217+
text = ''
218+
INSTANCE_DIR="$STATE_DIRECTORY"
219+
mkdir -vp "$INSTANCE_DIR"
220+
cd "$INSTANCE_DIR"
221+
222+
LABELS_FILE="$INSTANCE_DIR/.labels.sha256"
223+
LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)"
224+
LABELS_WANTED_HASH="$(printf '%s' "$LABELS_WANTED" | sha256sum | cut -d' ' -f1)"
225+
LABELS_CURRENT_HASH="$(cat "$LABELS_FILE" 2>/dev/null || true)"
226+
227+
if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED_HASH" != "$LABELS_CURRENT_HASH" ]; then
228+
rm -vf "$INSTANCE_DIR/.runner" || true
229+
230+
${cfg.package}/bin/forgejo-runner register \
231+
--no-interactive \
232+
--instance ${escapeShellArg instance.url} \
233+
--token "$(cat "$CREDENTIALS_DIRECTORY/TOKEN")" \
234+
--name ${escapeShellArg instance.name} \
235+
--labels ${escapeShellArg (concatStringsSep "," instance.labels)} \
236+
--config ${configFile}
237+
238+
printf '%s' "$LABELS_WANTED_HASH" > "$LABELS_FILE"
239+
fi
240+
'';
241+
}
242+
))
243+
];
244+
ExecStart = lib.mkForce "${cfg.package}/bin/forgejo-runner daemon --config ${configFile}";
245+
SupplementaryGroups = optionals wantsDocker [ "docker" ] ++ optionals wantsPodman [ "podman" ];
246+
};
247+
};
248+
in
249+
{
250+
"forgejo-runner@" = {
251+
description = "Forgejo Actions Runner (%I)";
252+
253+
environment = {
254+
HOME = "/var/lib/forgejo-runner/%i";
255+
};
256+
257+
serviceConfig = {
258+
DynamicUser = true;
259+
User = "forgejo-runner-%i";
260+
StateDirectory = "forgejo-runner/%i";
261+
WorkingDirectory = "/var/lib/forgejo-runner/%i";
262+
263+
Restart = "on-failure";
264+
RestartSec = 2;
265+
266+
AmbientCapabilities = "";
267+
CapabilityBoundingSet = "";
268+
LockPersonality = true;
269+
NoNewPrivileges = true;
270+
PrivateDevices = true;
271+
PrivateTmp = true;
272+
ProcSubset = "pid";
273+
ProtectClock = true;
274+
ProtectControlGroups = true;
275+
ProtectHome = true;
276+
ProtectHostname = true;
277+
ProtectKernelLogs = true;
278+
ProtectKernelModules = true;
279+
ProtectKernelTunables = true;
280+
ProtectProc = "invisible";
281+
ProtectSystem = "strict";
282+
RemoveIPC = true;
283+
RestrictAddressFamilies = [
284+
"AF_INET"
285+
"AF_INET6"
286+
"AF_UNIX"
287+
];
288+
RestrictNamespaces = true;
289+
RestrictRealtime = true;
290+
RestrictSUIDSGID = true;
291+
SystemCallArchitectures = "native";
292+
SystemCallFilter = [ "@system-service" ];
293+
UMask = "0077";
294+
};
295+
};
296+
}
297+
// mapAttrs' mkRunnerInstance cfg.instances;
298+
};
299+
}

0 commit comments

Comments
 (0)