gogup: 0.27.5 -> 0.27.6 (#373906)
[NixPkgs.git] / nixos / modules / services / continuous-integration / gitea-actions-runner.nix
blobb4bb4d6879200b150a62563d3a83da9ae6e497cf
2   config,
3   lib,
4   pkgs,
5   utils,
6   ...
7 }:
9 let
10   inherit (lib)
11     any
12     attrValues
13     concatStringsSep
14     escapeShellArg
15     hasInfix
16     hasSuffix
17     optionalAttrs
18     optionals
19     literalExpression
20     mapAttrs'
21     mkEnableOption
22     mkOption
23     mkPackageOption
24     mkIf
25     nameValuePair
26     types
27     ;
29   inherit (utils)
30     escapeSystemdPath
31     ;
33   cfg = config.services.gitea-actions-runner;
35   settingsFormat = pkgs.formats.yaml { };
37   # Check whether any runner instance label requires a container runtime
38   # Empty label strings result in the upstream defined defaultLabels, which require docker
39   # https://gitea.com/gitea/act_runner/src/tag/v0.1.5/internal/app/cmd/register.go#L93-L98
40   hasDockerScheme =
41     instance: instance.labels == [ ] || any (label: hasInfix ":docker:" label) instance.labels;
42   wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances);
44   hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels;
46   # provide shorthands for whether container runtimes are enabled
47   hasDocker = config.virtualisation.docker.enable;
48   hasPodman = config.virtualisation.podman.enable;
50   tokenXorTokenFile =
51     instance:
52     (instance.token == null && instance.tokenFile != null)
53     || (instance.token != null && instance.tokenFile == null);
56   meta.maintainers = with lib.maintainers; [
57     hexa
58   ];
60   options.services.gitea-actions-runner = with types; {
61     package = mkPackageOption pkgs "gitea-actions-runner" { };
63     instances = mkOption {
64       default = { };
65       description = ''
66         Gitea Actions Runner instances.
67       '';
68       type = attrsOf (submodule {
69         options = {
70           enable = mkEnableOption "Gitea Actions Runner instance";
72           name = mkOption {
73             type = str;
74             example = literalExpression "config.networking.hostName";
75             description = ''
76               The name identifying the runner instance towards the Gitea/Forgejo instance.
77             '';
78           };
80           url = mkOption {
81             type = str;
82             example = "https://forge.example.com";
83             description = ''
84               Base URL of your Gitea/Forgejo instance.
85             '';
86           };
88           token = mkOption {
89             type = nullOr str;
90             default = null;
91             description = ''
92               Plain token to register at the configured Gitea/Forgejo instance.
93             '';
94           };
96           tokenFile = mkOption {
97             type = nullOr (either str path);
98             default = null;
99             description = ''
100               Path to an environment file, containing the `TOKEN` environment
101               variable, that holds a token to register at the configured
102               Gitea/Forgejo instance.
103             '';
104           };
106           labels = mkOption {
107             type = listOf str;
108             example = literalExpression ''
109               [
110                 # provide a debian base with nodejs for actions
111                 "debian-latest:docker://node:18-bullseye"
112                 # fake the ubuntu name, because node provides no ubuntu builds
113                 "ubuntu-latest:docker://node:18-bullseye"
114                 # provide native execution on the host
115                 #"native:host"
116               ]
117             '';
118             description = ''
119               Labels used to map jobs to their runtime environment. Changing these
120               labels currently requires a new registration token.
122               Many common actions require bash, git and nodejs, as well as a filesystem
123               that follows the filesystem hierarchy standard.
124             '';
125           };
126           settings = mkOption {
127             description = ''
128               Configuration for `act_runner daemon`.
129               See https://gitea.com/gitea/act_runner/src/branch/main/internal/pkg/config/config.example.yaml for an example configuration
130             '';
132             type = types.submodule {
133               freeformType = settingsFormat.type;
134             };
136             default = { };
137           };
139           hostPackages = mkOption {
140             type = listOf package;
141             default = with pkgs; [
142               bash
143               coreutils
144               curl
145               gawk
146               gitMinimal
147               gnused
148               nodejs
149               wget
150             ];
151             defaultText = literalExpression ''
152               with pkgs; [
153                 bash
154                 coreutils
155                 curl
156                 gawk
157                 gitMinimal
158                 gnused
159                 nodejs
160                 wget
161               ]
162             '';
163             description = ''
164               List of packages, that are available to actions, when the runner is configured
165               with a host execution label.
166             '';
167           };
168         };
169       });
170     };
171   };
173   config = mkIf (cfg.instances != { }) {
174     assertions = [
175       {
176         assertion = any tokenXorTokenFile (attrValues cfg.instances);
177         message = "Instances of gitea-actions-runner can have `token` or `tokenFile`, not both.";
178       }
179       {
180         assertion = wantsContainerRuntime -> hasDocker || hasPodman;
181         message = "Label configuration on gitea-actions-runner instance requires either docker or podman.";
182       }
183     ];
185     systemd.services =
186       let
187         mkRunnerService =
188           name: instance:
189           let
190             wantsContainerRuntime = hasDockerScheme instance;
191             wantsHost = hasHostScheme instance;
192             wantsDocker = wantsContainerRuntime && config.virtualisation.docker.enable;
193             wantsPodman = wantsContainerRuntime && config.virtualisation.podman.enable;
194             configFile = settingsFormat.generate "config.yaml" instance.settings;
195           in
196           nameValuePair "gitea-runner-${escapeSystemdPath name}" {
197             inherit (instance) enable;
198             description = "Gitea Actions Runner";
199             wants = [ "network-online.target" ];
200             after =
201               [
202                 "network-online.target"
203               ]
204               ++ optionals (wantsDocker) [
205                 "docker.service"
206               ]
207               ++ optionals (wantsPodman) [
208                 "podman.service"
209               ];
210             wantedBy = [
211               "multi-user.target"
212             ];
213             environment =
214               optionalAttrs (instance.token != null) {
215                 TOKEN = "${instance.token}";
216               }
217               // optionalAttrs (wantsPodman) {
218                 DOCKER_HOST = "unix:///run/podman/podman.sock";
219               }
220               // {
221                 HOME = "/var/lib/gitea-runner/${name}";
222               };
223             path =
224               with pkgs;
225               [
226                 coreutils
227               ]
228               ++ lib.optionals wantsHost instance.hostPackages;
229             serviceConfig =
230               {
231                 DynamicUser = true;
232                 User = "gitea-runner";
233                 StateDirectory = "gitea-runner";
234                 WorkingDirectory = "-/var/lib/gitea-runner/${name}";
236                 # gitea-runner might fail when gitea is restarted during upgrade.
237                 Restart = "on-failure";
238                 RestartSec = 2;
240                 ExecStartPre = [
241                   (pkgs.writeShellScript "gitea-register-runner-${name}" ''
242                     export INSTANCE_DIR="$STATE_DIRECTORY/${name}"
243                     mkdir -vp "$INSTANCE_DIR"
244                     cd "$INSTANCE_DIR"
246                     # force reregistration on changed labels
247                     export LABELS_FILE="$INSTANCE_DIR/.labels"
248                     export LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)"
249                     export LABELS_CURRENT="$(cat $LABELS_FILE 2>/dev/null || echo 0)"
251                     if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED" != "$LABELS_CURRENT" ]; then
252                       # remove existing registration file, so that changing the labels forces a re-registration
253                       rm -v "$INSTANCE_DIR/.runner" || true
255                       # perform the registration
256                       ${cfg.package}/bin/act_runner register --no-interactive \
257                         --instance ${escapeShellArg instance.url} \
258                         --token "$TOKEN" \
259                         --name ${escapeShellArg instance.name} \
260                         --labels ${escapeShellArg (concatStringsSep "," instance.labels)} \
261                         --config ${configFile}
263                       # and write back the configured labels
264                       echo "$LABELS_WANTED" > "$LABELS_FILE"
265                     fi
267                   '')
268                 ];
269                 ExecStart = "${cfg.package}/bin/act_runner daemon --config ${configFile}";
270                 SupplementaryGroups =
271                   optionals (wantsDocker) [
272                     "docker"
273                   ]
274                   ++ optionals (wantsPodman) [
275                     "podman"
276                   ];
277               }
278               // optionalAttrs (instance.tokenFile != null) {
279                 EnvironmentFile = instance.tokenFile;
280               };
281           };
282       in
283       mapAttrs' mkRunnerService cfg.instances;
284   };