Skip to content

Commit 615d282

Browse files
committed
nixos/forgejo.runner: initialize module
1 parent 85793c2 commit 615d282

4 files changed

Lines changed: 319 additions & 1 deletion

File tree

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

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

6767
- [Shoko](https://shokoanime.com), an anime management system. Available as [services.shoko](#opt-services.shoko.enable).
6868

69+
- [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).
70+
6971
- [Drasl](https://github.com/unmojang/drasl), an alternative authentication server for Minecraft. Available as [services.drasl](#opt-services.drasl.enable).
7072

7173
## 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
@@ -503,6 +503,7 @@
503503
./services/continuous-integration/buildbot/master.nix
504504
./services/continuous-integration/buildbot/worker.nix
505505
./services/continuous-integration/buildkite-agents.nix
506+
./services/continuous-integration/forgejo-runner.nix
506507
./services/continuous-integration/gitea-actions-runner.nix
507508
./services/continuous-integration/github-runners.nix
508509
./services/continuous-integration/gitlab-runner/runner.nix
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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 an environment file, containing the `TOKEN` environment
93+
variable, that holds a token to register at the configured
94+
Forgejo instance.
95+
'';
96+
};
97+
98+
labels = mkOption {
99+
type = listOf str;
100+
example = literalExpression ''
101+
[
102+
# provide a debian base with nodejs for actions
103+
"debian-latest:docker://node:18-bullseye"
104+
# fake the ubuntu name, because node provides no ubuntu builds
105+
"ubuntu-latest:docker://node:18-bullseye"
106+
# provide native execution on the host
107+
#"native:host"
108+
]
109+
'';
110+
description = ''
111+
Labels used to map jobs to their runtime environment. Changing these
112+
labels currently requires a new registration token.
113+
114+
Many common actions require bash, git and nodejs, as well as a filesystem
115+
that follows the filesystem hierarchy standard.
116+
'';
117+
};
118+
119+
settings = mkOption {
120+
description = ''
121+
Configuration for `forgejo-runner daemon`.
122+
See <https://code.forgejo.org/forgejo/runner/src/branch/main/internal/pkg/config/config.example.yaml> for an example configuration.
123+
'';
124+
125+
type = types.submodule {
126+
freeformType = settingsFormat.type;
127+
};
128+
129+
default = { };
130+
};
131+
132+
hostPackages = mkOption {
133+
type = listOf package;
134+
default = with pkgs; [
135+
bash
136+
coreutils
137+
curl
138+
gawk
139+
gitMinimal
140+
gnused
141+
nodejs
142+
wget
143+
];
144+
defaultText = literalExpression ''
145+
with pkgs; [
146+
bash
147+
coreutils
148+
curl
149+
gawk
150+
gitMinimal
151+
gnused
152+
nodejs
153+
wget
154+
]
155+
'';
156+
description = ''
157+
List of packages that are available to actions, when the runner is configured
158+
with a host execution label.
159+
'';
160+
};
161+
};
162+
}
163+
)
164+
);
165+
};
166+
};
167+
168+
config = mkIf (cfg.instances != { }) {
169+
assertions = [
170+
{
171+
assertion = wantsContainerRuntime -> hasDocker || hasPodman;
172+
message = "Label configuration on forgejo.runner instance requires either docker or podman.";
173+
}
174+
];
175+
176+
systemd.services =
177+
let
178+
mkRunnerInstance =
179+
_: instance:
180+
let
181+
escapedName = escapeSystemdPath instance.name;
182+
wantsContainer = hasDockerScheme instance;
183+
wantsHost = hasHostScheme instance;
184+
wantsDocker = wantsContainer && hasDocker;
185+
wantsPodman = wantsContainer && hasPodman;
186+
configFile = settingsFormat.generate "forgejo-runner-${escapedName}.yaml" instance.settings;
187+
in
188+
nameValuePair "forgejo-runner@${escapedName}" {
189+
overrideStrategy = "asDropin";
190+
inherit (instance) enable;
191+
wants = [
192+
"network-online.target"
193+
]
194+
++ optionals wantsDocker [ "docker.service" ]
195+
++ optionals wantsPodman [ "podman.service" ];
196+
after = [
197+
"network-online.target"
198+
]
199+
++ optionals wantsDocker [ "docker.service" ]
200+
++ optionals wantsPodman [ "podman.service" ];
201+
wantedBy = [ "multi-user.target" ];
202+
203+
environment = optionalAttrs wantsPodman {
204+
DOCKER_HOST = "unix:///run/podman/podman.sock";
205+
};
206+
207+
path = [ pkgs.coreutils ] ++ lib.optionals wantsHost instance.hostPackages;
208+
209+
serviceConfig = {
210+
MemoryDenyWriteExecute = !wantsHost;
211+
212+
ExecStartPre = [
213+
(pkgs.writeShellScript "forgejo-register-runner-${escapedName}" ''
214+
export INSTANCE_DIR="$STATE_DIRECTORY"
215+
mkdir -vp "$INSTANCE_DIR"
216+
cd "$INSTANCE_DIR"
217+
218+
export LABELS_FILE="$INSTANCE_DIR/.labels.sha256"
219+
export LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)"
220+
export LABELS_WANTED_HASH="$(printf '%s' "$LABELS_WANTED" | sha256sum | cut -d' ' -f1)"
221+
export LABELS_CURRENT_HASH="$(cat "$LABELS_FILE" 2>/dev/null || true)"
222+
223+
if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED_HASH" != "$LABELS_CURRENT_HASH" ]; then
224+
rm -vf "$INSTANCE_DIR/.runner" || true
225+
226+
${cfg.package}/bin/forgejo-runner register --no-interactive \
227+
--instance ${escapeShellArg instance.url} \
228+
--token "$TOKEN" \
229+
--name ${escapeShellArg instance.name} \
230+
--labels ${escapeShellArg (concatStringsSep "," instance.labels)} \
231+
--config ${configFile}
232+
233+
printf '%s' "$LABELS_WANTED_HASH" > "$LABELS_FILE"
234+
fi
235+
'')
236+
];
237+
ExecStart = lib.mkForce "${cfg.package}/bin/forgejo-runner daemon --config ${configFile}";
238+
SupplementaryGroups = optionals wantsDocker [ "docker" ] ++ optionals wantsPodman [ "podman" ];
239+
}
240+
// optionalAttrs (instance.tokenFile != null) {
241+
EnvironmentFile = instance.tokenFile;
242+
};
243+
};
244+
in
245+
{
246+
"forgejo-runner@" = {
247+
description = "Forgejo Actions Runner (%I)";
248+
249+
environment = {
250+
HOME = "/var/lib/forgejo-runner/%i";
251+
};
252+
253+
serviceConfig = {
254+
DynamicUser = true;
255+
User = "forgejo-runner-%i";
256+
StateDirectory = "forgejo-runner/%i";
257+
WorkingDirectory = "/var/lib/forgejo-runner/%i";
258+
259+
Restart = "on-failure";
260+
RestartSec = 2;
261+
262+
AmbientCapabilities = "";
263+
CapabilityBoundingSet = "";
264+
LockPersonality = true;
265+
NoNewPrivileges = true;
266+
PrivateDevices = true;
267+
PrivateTmp = true;
268+
ProcSubset = "pid";
269+
ProtectClock = true;
270+
ProtectControlGroups = true;
271+
ProtectHome = true;
272+
ProtectHostname = true;
273+
ProtectKernelLogs = true;
274+
ProtectKernelModules = true;
275+
ProtectKernelTunables = true;
276+
ProtectProc = "invisible";
277+
ProtectSystem = "strict";
278+
RemoveIPC = true;
279+
RestrictAddressFamilies = [
280+
"AF_INET"
281+
"AF_INET6"
282+
"AF_UNIX"
283+
];
284+
RestrictNamespaces = true;
285+
RestrictRealtime = true;
286+
RestrictSUIDSGID = true;
287+
SystemCallArchitectures = "native";
288+
SystemCallFilter = [ "@system-service" ];
289+
UMask = "0077";
290+
};
291+
};
292+
}
293+
// mapAttrs' mkRunnerInstance cfg.instances;
294+
};
295+
}

nixos/tests/forgejo.nix

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ let
6161
services.openssh.enable = true;
6262

6363
specialisation.runner = {
64+
inheritParentConfig = true;
65+
configuration.services.forgejo.runner = {
66+
package = pkgs.forgejo-runner;
67+
instances."test" = {
68+
enable = true;
69+
url = "http://localhost:3000";
70+
labels = [
71+
# type ":host" does not depend on docker/podman/lxc
72+
"native:host"
73+
];
74+
tokenFile = "/var/lib/forgejo/runner_token";
75+
};
76+
};
77+
};
78+
specialisation.runnerLegacy = {
6479
inheritParentConfig = true;
6580
configuration.services.gitea-actions-runner = {
6681
package = pkgs.forgejo-runner;
@@ -221,10 +236,15 @@ let
221236
server.succeed(
222237
"su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo forgejo actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/forgejo/runner_token"
223238
)
224-
server.succeed("${serverSystem}/specialisation/runner/bin/switch-to-configuration test")
239+
240+
server.succeed("${serverSystem}/specialisation/runnerLegacy/bin/switch-to-configuration test")
225241
server.wait_for_unit("gitea-runner-test.service")
226242
server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'")
227243
244+
server.succeed("${serverSystem}/specialisation/runner/bin/switch-to-configuration test")
245+
server.wait_for_unit("forgejo-runner@test.service")
246+
server.succeed("journalctl -o cat -u forgejo-runner@test.service | grep -q 'Runner registered successfully'")
247+
228248
# enable actions feature for this repository, defaults to disabled
229249
server.succeed(
230250
"curl --fail -X PATCH http://localhost:3000/api/v1/repos/test/repo "

0 commit comments

Comments
 (0)