vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / continuous-integration / gitlab-runner.nix
blob34e9b6200783dd012d182e6a17d55cfd68c6b17a
1 { config, lib, pkgs, ... }:
3 let
4   inherit (builtins)
5     hashString
6     map
7     substring
8     toJSON
9     toString
10     unsafeDiscardStringContext
11     ;
13   inherit (lib)
14     any
15     assertMsg
16     attrValues
17     concatStringsSep
18     escapeShellArg
19     filterAttrs
20     hasPrefix
21     isStorePath
22     literalExpression
23     mapAttrs'
24     mapAttrsToList
25     mkDefault
26     mkEnableOption
27     mkIf
28     mkOption
29     mkPackageOption
30     mkRemovedOptionModule
31     mkRenamedOptionModule
32     nameValuePair
33     optional
34     optionalAttrs
35     optionals
36     teams
37     toShellVar
38     types
39     ;
41   cfg = config.services.gitlab-runner;
42   hasDocker = config.virtualisation.docker.enable;
43   hasPodman = config.virtualisation.podman.enable && config.virtualisation.podman.dockerSocket.enable;
45   /* The whole logic of this module is to diff the hashes of the desired vs existing runners
46   The hash is recorded in the runner's name because we can't do better yet
47   See https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29350 for more details
48   */
49   genRunnerName = name: service: let
50       hash = substring 0 12 (hashString "md5" (unsafeDiscardStringContext (toJSON service)));
51     in if service ? description && service.description != null
52     then "${hash} ${service.description}"
53     else "${name}_${config.networking.hostName}_${hash}";
55   hashedServices = mapAttrs'
56     (name: service: nameValuePair (genRunnerName name service) service) cfg.services;
57   configPath = ''"$HOME"/.gitlab-runner/config.toml'';
58   configureScript = pkgs.writeShellApplication {
59     name = "gitlab-runner-configure";
60     runtimeInputs = [ cfg.package ] ++ (with pkgs; [
61         bash
62         gawk
63         jq
64         moreutils
65         remarshal
66         util-linux
67         perl
68         python3
69     ]);
70     text = if (cfg.configFile != null) then ''
71       cp ${cfg.configFile} ${configPath}
72       # make config file readable by service
73       chown -R --reference="$HOME" "$(dirname ${configPath})"
74     '' else ''
75       export CONFIG_FILE=${configPath}
77       mkdir -p "$(dirname ${configPath})"
78       touch ${configPath}
80       # update global options
81       remarshal --if toml --of json ${configPath} \
82         | jq -cM 'with_entries(select([.key] | inside(["runners"])))' \
83         | jq -scM '.[0] + .[1]' - <(echo ${escapeShellArg (toJSON cfg.settings)}) \
84         | remarshal --if json --of toml \
85         | sponge ${configPath}
87       # remove no longer existing services
88       gitlab-runner verify --delete
90       ${toShellVar "NEEDED_SERVICES" (lib.mapAttrs (name: value: 1) hashedServices)}
92       declare -A REGISTERED_SERVICES
94       while IFS="," read -r name token;
95       do
96         REGISTERED_SERVICES["$name"]="$token"
97       done < <(gitlab-runner --log-format json list 2>&1 | grep Token  | jq -r '.msg +"," + .Token')
99       echo "NEEDED_SERVICES: " "''${!NEEDED_SERVICES[@]}"
100       echo "REGISTERED_SERVICES:" "''${!REGISTERED_SERVICES[@]}"
102       # difference between current and desired state
103       declare -A NEW_SERVICES
104       for name in "''${!NEEDED_SERVICES[@]}"; do
105         if [ ! -v 'REGISTERED_SERVICES[$name]' ]; then
106           NEW_SERVICES[$name]=1
107         fi
108       done
110       declare -A OLD_SERVICES
111       # shellcheck disable=SC2034
112       for name in "''${!REGISTERED_SERVICES[@]}"; do
113         if [ ! -v 'NEEDED_SERVICES[$name]' ]; then
114           OLD_SERVICES[$name]=1
115         fi
116       done
118       # register new services
119       ${concatStringsSep "\n" (mapAttrsToList (name: service: ''
120         # TODO so here we should mention NEW_SERVICES
121         if [ -v 'NEW_SERVICES["${name}"]' ] ; then
122           bash -c ${escapeShellArg (concatStringsSep " \\\n " ([
123             "set -a && source ${
124               if service.registrationConfigFile != null
125               then service.registrationConfigFile
126               else service.authenticationTokenConfigFile} &&"
127             "gitlab-runner register"
128             "--non-interactive"
129             "--name '${name}'"
130             "--executor ${service.executor}"
131             "--limit ${toString service.limit}"
132             "--request-concurrency ${toString service.requestConcurrency}"
133           ]
134             ++ optional (service.authenticationTokenConfigFile == null)
135             "--maximum-timeout ${toString service.maximumTimeout}"
136             ++ service.registrationFlags
137             ++ optional (service.buildsDir != null)
138             "--builds-dir ${service.buildsDir}"
139             ++ optional (service.cloneUrl != null)
140             "--clone-url ${service.cloneUrl}"
141             ++ optional (service.preGetSourcesScript != null)
142             "--pre-get-sources-script ${service.preGetSourcesScript}"
143             ++ optional (service.postGetSourcesScript != null)
144             "--post-get-sources-script ${service.postGetSourcesScript}"
145             ++ optional (service.preBuildScript != null)
146             "--pre-build-script ${service.preBuildScript}"
147             ++ optional (service.postBuildScript != null)
148             "--post-build-script ${service.postBuildScript}"
149             ++ optional (service.authenticationTokenConfigFile == null && service.tagList != [ ])
150             "--tag-list ${concatStringsSep "," service.tagList}"
151             ++ optional (service.authenticationTokenConfigFile == null && service.runUntagged)
152             "--run-untagged"
153             ++ optional (service.authenticationTokenConfigFile == null && service.protected)
154             "--access-level ref_protected"
155             ++ optional service.debugTraceDisabled
156             "--debug-trace-disabled"
157             ++ map (e: "--env ${escapeShellArg e}") (mapAttrsToList (name: value: "${name}=${value}") service.environmentVariables)
158             ++ optionals (hasPrefix "docker" service.executor) (
159               assert (
160                 assertMsg (service.dockerImage != null)
161                   "dockerImage option is required for ${service.executor} executor (${name})");
162               [ "--docker-image ${service.dockerImage}" ]
163               ++ optional service.dockerDisableCache
164               "--docker-disable-cache"
165               ++ optional service.dockerPrivileged
166               "--docker-privileged"
167               ++ map (v: "--docker-volumes ${escapeShellArg v}") service.dockerVolumes
168               ++ map (v: "--docker-extra-hosts ${escapeShellArg v}") service.dockerExtraHosts
169               ++ map (v: "--docker-allowed-images ${escapeShellArg v}") service.dockerAllowedImages
170               ++ map (v: "--docker-allowed-services ${escapeShellArg v}") service.dockerAllowedServices
171             )
172           ))} && sleep 1 || exit 1
173         fi
174       '') hashedServices)}
176       # check key is in array https://stackoverflow.com/questions/30353951/how-to-check-if-dictionary-contains-a-key-in-bash
178       echo "NEW_SERVICES: ''${NEW_SERVICES[*]}"
179       echo "OLD_SERVICES: ''${OLD_SERVICES[*]}"
180       # unregister old services
181       for NAME in "''${!OLD_SERVICES[@]}"
182       do
183         [ -n "$NAME" ] && gitlab-runner unregister \
184           --name "$NAME" && sleep 1
185       done
187       # make config file readable by service
188       chown -R --reference="$HOME" "$(dirname ${configPath})"
189     '';
190   };
191   startScript = pkgs.writeShellScriptBin "gitlab-runner-start" ''
192     export CONFIG_FILE=${configPath}
193     exec gitlab-runner run --working-directory $HOME
194   '';
195 in {
196   options.services.gitlab-runner = {
197     enable = mkEnableOption "Gitlab Runner";
198     configFile = mkOption {
199       type = types.nullOr types.path;
200       default = null;
201       description = ''
202         Configuration file for gitlab-runner.
204         {option}`configFile` takes precedence over {option}`services`.
205         {option}`checkInterval` and {option}`concurrent` will be ignored too.
207         This option is deprecated, please use {option}`services` instead.
208         You can use {option}`registrationConfigFile` and
209         {option}`registrationFlags`
210         for settings not covered by this module.
211       '';
212     };
213     settings = mkOption {
214       type = types.submodule {
215         freeformType = (pkgs.formats.json { }).type;
216       };
217       default = { };
218       description = ''
219         Global gitlab-runner configuration. See
220         <https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section>
221         for supported values.
222       '';
223     };
224     gracefulTermination = mkOption {
225       type = types.bool;
226       default = false;
227       description = ''
228         Finish all remaining jobs before stopping.
229         If not set gitlab-runner will stop immediately without waiting
230         for jobs to finish, which will lead to failed builds.
231       '';
232     };
233     gracefulTimeout = mkOption {
234       type = types.str;
235       default = "infinity";
236       example = "5min 20s";
237       description = ''
238         Time to wait until a graceful shutdown is turned into a forceful one.
239       '';
240     };
241     package = mkPackageOption pkgs "gitlab-runner" {
242       example = "gitlab-runner_1_11";
243     };
244     extraPackages = mkOption {
245       type = types.listOf types.package;
246       default = [ ];
247       description = ''
248         Extra packages to add to PATH for the gitlab-runner process.
249       '';
250     };
251     services = mkOption {
252       description = "GitLab Runner services.";
253       default = { };
254       example = literalExpression ''
255         {
256           # runner for building in docker via host's nix-daemon
257           # nix store will be readable in runner, might be insecure
258           nix = {
259             # File should contain at least these two variables:
260             # - `CI_SERVER_URL`
261             # - `REGISTRATION_TOKEN`
262             #
263             # NOTE: Support for runner registration tokens will be removed in GitLab 18.0.
264             # Please migrate to runner authentication tokens soon. For reference, the example
265             # runners below this one are configured with authentication tokens instead.
266             registrationConfigFile = "/run/secrets/gitlab-runner-registration";
268             dockerImage = "alpine";
269             dockerVolumes = [
270               "/nix/store:/nix/store:ro"
271               "/nix/var/nix/db:/nix/var/nix/db:ro"
272               "/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro"
273             ];
274             dockerDisableCache = true;
275             preBuildScript = pkgs.writeScript "setup-container" '''
276               mkdir -p -m 0755 /nix/var/log/nix/drvs
277               mkdir -p -m 0755 /nix/var/nix/gcroots
278               mkdir -p -m 0755 /nix/var/nix/profiles
279               mkdir -p -m 0755 /nix/var/nix/temproots
280               mkdir -p -m 0755 /nix/var/nix/userpool
281               mkdir -p -m 1777 /nix/var/nix/gcroots/per-user
282               mkdir -p -m 1777 /nix/var/nix/profiles/per-user
283               mkdir -p -m 0755 /nix/var/nix/profiles/per-user/root
284               mkdir -p -m 0700 "$HOME/.nix-defexpr"
286               . ''${pkgs.nix}/etc/profile.d/nix.sh
288               ''${pkgs.nix}/bin/nix-env -i ''${concatStringsSep " " (with pkgs; [ nix cacert git openssh ])}
290               ''${pkgs.nix}/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
291               ''${pkgs.nix}/bin/nix-channel --update nixpkgs
292             ''';
293             environmentVariables = {
294               ENV = "/etc/profile";
295               USER = "root";
296               NIX_REMOTE = "daemon";
297               PATH = "/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin";
298               NIX_SSL_CERT_FILE = "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt";
299             };
300             tagList = [ "nix" ];
301           };
302           # runner for building docker images
303           docker-images = {
304             # File should contain at least these two variables:
305             # `CI_SERVER_URL`
306             # `CI_SERVER_TOKEN`
307             authenticationTokenConfigFile = "/run/secrets/gitlab-runner-docker-images-token-env";
309             dockerImage = "docker:stable";
310             dockerVolumes = [
311               "/var/run/docker.sock:/var/run/docker.sock"
312             ];
313             tagList = [ "docker-images" ];
314           };
315           # runner for executing stuff on host system (very insecure!)
316           # make sure to add required packages (including git!)
317           # to `environment.systemPackages`
318           shell = {
319             # File should contain at least these two variables:
320             # `CI_SERVER_URL`
321             # `CI_SERVER_TOKEN`
322             authenticationTokenConfigFile = "/run/secrets/gitlab-runner-shell-token-env";
324             executor = "shell";
325             tagList = [ "shell" ];
326           };
327           # runner for everything else
328           default = {
329             # File should contain at least these two variables:
330             # `CI_SERVER_URL`
331             # `CI_SERVER_TOKEN`
332             authenticationTokenConfigFile = "/run/secrets/gitlab-runner-default-token-env";
333             dockerImage = "debian:stable";
334           };
335         }
336       '';
337       type = types.attrsOf (types.submodule {
338         options = {
339           authenticationTokenConfigFile = mkOption {
340             type = with types; nullOr path;
341             default = null;
342             description = ''
343               Absolute path to a file containing environment variables used for
344               gitlab-runner registrations with *runner authentication tokens*.
345               They replace the deprecated *runner registration tokens*, as
346               outlined in the [GitLab documentation].
348               A list of all supported environment variables can be found with
349               `gitlab-runner register --help`.
351               The ones you probably want to set are:
352               - `CI_SERVER_URL=<CI server URL>`
353               - `CI_SERVER_TOKEN=<runner authentication token secret>`
355               ::: {.warning}
356               Make sure to use a quoted absolute path,
357               or it is going to be copied to Nix Store.
358               :::
360               [GitLab documentation]: https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#estimated-time-frame-for-planned-changes
361             '';
362           };
363           registrationConfigFile = mkOption {
364             type = with types; nullOr path;
365             default = null;
366             description = ''
367               Absolute path to a file with environment variables
368               used for gitlab-runner registration with *runner registration
369               tokens*.
371               A list of all supported environment variables can be found in
372               `gitlab-runner register --help`.
374               The ones you probably want to set are:
375               - `CI_SERVER_URL=<CI server URL>`
376               - `REGISTRATION_TOKEN=<registration secret>`
378               Support for *runner registration tokens* is deprecated since
379               GitLab 16.0, has been disabled by default in GitLab 17.0 and
380               will be removed in GitLab 18.0, as outlined in the
381               [GitLab documentation]. Please consider migrating to
382               [runner authentication tokens] and check the documentation on
383               {option}`services.gitlab-runner.services.<name>.authenticationTokenConfigFile`.
385               ::: {.warning}
386               Make sure to use a quoted absolute path,
387               or it is going to be copied to Nix Store.
388               :::
390               [GitLab documentation]: https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#estimated-time-frame-for-planned-changes
391               [runner authentication tokens]: https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#the-new-runner-registration-workflow
392             '';
393           };
394           registrationFlags = mkOption {
395             type = types.listOf types.str;
396             default = [ ];
397             example = [ "--docker-helper-image my/gitlab-runner-helper" ];
398             description = ''
399               Extra command-line flags passed to
400               `gitlab-runner register`.
401               Execute `gitlab-runner register --help`
402               for a list of supported flags.
403             '';
404           };
405           environmentVariables = mkOption {
406             type = types.attrsOf types.str;
407             default = { };
408             example = { NAME = "value"; };
409             description = ''
410               Custom environment variables injected to build environment.
411               For secrets you can use {option}`registrationConfigFile`
412               with `RUNNER_ENV` variable set.
413             '';
414           };
415           description = mkOption {
416             type = types.nullOr types.str;
417             default = null;
418             description = ''
419               Name/description of the runner.
420             '';
421           };
422           executor = mkOption {
423             type = types.str;
424             default = "docker";
425             description = ''
426               Select executor, eg. shell, docker, etc.
427               See [runner documentation](https://docs.gitlab.com/runner/executors/README.html) for more information.
428             '';
429           };
430           buildsDir = mkOption {
431             type = types.nullOr types.path;
432             default = null;
433             example = "/var/lib/gitlab-runner/builds";
434             description = ''
435               Absolute path to a directory where builds will be stored
436               in context of selected executor (Locally, Docker, SSH).
437             '';
438           };
439           cloneUrl = mkOption {
440             type = types.nullOr types.str;
441             default = null;
442             example = "http://gitlab.example.local";
443             description = ''
444               Overwrite the URL for the GitLab instance. Used if the Runner can’t connect to GitLab on the URL GitLab exposes itself.
445             '';
446           };
447           dockerImage = mkOption {
448             type = types.nullOr types.str;
449             default = null;
450             description = ''
451               Docker image to be used.
452             '';
453           };
454           dockerVolumes = mkOption {
455             type = types.listOf types.str;
456             default = [ ];
457             example = [ "/var/run/docker.sock:/var/run/docker.sock" ];
458             description = ''
459               Bind-mount a volume and create it
460               if it doesn't exist prior to mounting.
461             '';
462           };
463           dockerDisableCache = mkOption {
464             type = types.bool;
465             default = false;
466             description = ''
467               Disable all container caching.
468             '';
469           };
470           dockerPrivileged = mkOption {
471             type = types.bool;
472             default = false;
473             description = ''
474               Give extended privileges to container.
475             '';
476           };
477           dockerExtraHosts = mkOption {
478             type = types.listOf types.str;
479             default = [ ];
480             example = [ "other-host:127.0.0.1" ];
481             description = ''
482               Add a custom host-to-IP mapping.
483             '';
484           };
485           dockerAllowedImages = mkOption {
486             type = types.listOf types.str;
487             default = [ ];
488             example = [ "ruby:*" "python:*" "php:*" "my.registry.tld:5000/*:*" ];
489             description = ''
490               Whitelist allowed images.
491             '';
492           };
493           dockerAllowedServices = mkOption {
494             type = types.listOf types.str;
495             default = [ ];
496             example = [ "postgres:9" "redis:*" "mysql:*" ];
497             description = ''
498               Whitelist allowed services.
499             '';
500           };
501           preGetSourcesScript = mkOption {
502             type = types.nullOr types.path;
503             default = null;
504             description = ''
505               Runner-specific command script executed before code is pulled.
506             '';
507           };
508           postGetSourcesScript = mkOption {
509             type = types.nullOr types.path;
510             default = null;
511             description = ''
512               Runner-specific command script executed after code is pulled.
513             '';
514           };
515           preBuildScript = mkOption {
516             type = types.nullOr types.path;
517             default = null;
518             description = ''
519               Runner-specific command script executed after code is pulled,
520               just before build executes.
521             '';
522           };
523           postBuildScript = mkOption {
524             type = types.nullOr types.path;
525             default = null;
526             description = ''
527               Runner-specific command script executed after code is pulled
528               and just after build executes.
529             '';
530           };
531           tagList = mkOption {
532             type = types.listOf types.str;
533             default = [ ];
534             description = ''
535               Tag list.
537               This option has no effect for runners registered with an runner
538               authentication tokens and will be ignored.
539             '';
540           };
541           runUntagged = mkOption {
542             type = types.bool;
543             default = false;
544             description = ''
545               Register to run untagged builds; defaults to
546               `true` when {option}`tagList` is empty.
548               This option has no effect for runners registered with an runner
549               authentication tokens and will be ignored.
550             '';
551           };
552           limit = mkOption {
553             type = types.int;
554             default = 0;
555             description = ''
556               Limit how many jobs can be handled concurrently by this service.
557               0 (default) simply means don't limit.
558             '';
559           };
560           requestConcurrency = mkOption {
561             type = types.int;
562             default = 0;
563             description = ''
564               Limit number of concurrent requests for new jobs from GitLab.
565             '';
566           };
567           maximumTimeout = mkOption {
568             type = types.int;
569             default = 0;
570             description = ''
571               What is the maximum timeout (in seconds) that will be set for
572               job when using this Runner. 0 (default) simply means don't limit.
574               This option has no effect for runners registered with an runner
575               authentication tokens and will be ignored.
576             '';
577           };
578           protected = mkOption {
579             type = types.bool;
580             default = false;
581             description = ''
582               When set to true Runner will only run on pipelines
583               triggered on protected branches.
585               This option has no effect for runners registered with an runner
586               authentication tokens and will be ignored.
587             '';
588           };
589           debugTraceDisabled = mkOption {
590             type = types.bool;
591             default = false;
592             description = ''
593               When set to true Runner will disable the possibility of
594               using the `CI_DEBUG_TRACE` feature.
595             '';
596           };
597         };
598       });
599     };
600     clear-docker-cache = {
601       enable = mkOption {
602         type = types.bool;
603         default = false;
604         description = ''
605           Whether to periodically prune gitlab runner's Docker resources. If
606           enabled, a systemd timer will run {command}`clear-docker-cache` as
607           specified by the `dates` option.
608         '';
609       };
611       flags = mkOption {
612         type = types.listOf types.str;
613         default = [ ];
614         example = [ "prune" ];
615         description = ''
616           Any additional flags passed to {command}`clear-docker-cache`.
617         '';
618       };
620       dates = mkOption {
621         default = "weekly";
622         type = types.str;
623         description = ''
624           Specification (in the format described by
625           {manpage}`systemd.time(7)`) of the time at
626           which the prune will occur.
627         '';
628       };
630       package = mkOption {
631         default = config.virtualisation.docker.package;
632         defaultText = literalExpression "config.virtualisation.docker.package";
633         example = literalExpression "pkgs.docker";
634         description = "Docker package to use for clearing up docker cache.";
635       };
636     };
637   };
638   config = mkIf cfg.enable {
639     assertions =
640       mapAttrsToList (name: serviceConfig: {
641         assertion = serviceConfig.registrationConfigFile == null || serviceConfig.authenticationTokenConfigFile == null;
642         message = "`services.gitlab-runner.${name}.registrationConfigFile` and `services.gitlab-runner.services.${name}.authenticationTokenConfigFile` are mutually exclusive.";
643       }) cfg.services;
645     warnings =
646       mapAttrsToList
647         (name: serviceConfig: "services.gitlab-runner.services.${name}.`registrationConfigFile` points to a file in Nix Store. You should use quoted absolute path to prevent this.")
648         (filterAttrs (name: serviceConfig: isStorePath serviceConfig.registrationConfigFile) cfg.services)
649       ++ mapAttrsToList
650         (name: serviceConfig: "services.gitlab-runner.services.${name}.`authenticationTokenConfigFile` points to a file in Nix Store. You should use quoted absolute path to prevent this.")
651         (filterAttrs (name: serviceConfig: isStorePath serviceConfig.authenticationTokenConfigFile) cfg.services)
652       ++ mapAttrsToList
653         (name: serviceConfig: ''
654           Runner registration tokens have been deprecated and disabled by default in GitLab >= 17.0.
655           Consider migrating to runner authentication tokens by setting `services.gitlab-runner.services.${name}.authenticationTokenConfigFile`.
656           https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html''
657         )
658         (
659           filterAttrs (name: serviceConfig:
660             serviceConfig.authenticationTokenConfigFile == null
661           ) cfg.services
662         )
663       ++ mapAttrsToList
664         (name: serviceConfig: ''
665           `services.gitlab-runner.services.${name}.protected` with runner authentication tokens has no effect and will be ignored. Please remove it from your configuration.''
666         )
667         (
668           filterAttrs (name: serviceConfig:
669             serviceConfig.authenticationTokenConfigFile != null && serviceConfig.protected == true
670           ) cfg.services
671         )
672       ++ mapAttrsToList
673         (name: serviceConfig: ''
674           `services.gitlab-runner.services.${name}.runUntagged` with runner authentication tokens has no effect and will be ignored. Please remove it from your configuration.''
675         )
676         (
677           filterAttrs (name: serviceConfig:
678             serviceConfig.authenticationTokenConfigFile != null && serviceConfig.runUntagged == true
679           ) cfg.services
680         )
681       ++ mapAttrsToList
682         (name: v: ''
683           `services.gitlab-runner.services.${name}.maximumTimeout` with runner authentication tokens has no effect and will be ignored. Please remove it from your configuration.''
684         )
685         (
686           filterAttrs (name: serviceConfig:
687             serviceConfig.authenticationTokenConfigFile != null && serviceConfig.maximumTimeout != 0
688           ) cfg.services
689         )
690       ++ mapAttrsToList
691         (name: v: ''
692           `services.gitlab-runner.services.${name}.tagList` with runner authentication tokens has no effect and will be ignored. Please remove it from your configuration.''
693         )
694         (
695           filterAttrs (serviceName: serviceConfig:
696             serviceConfig.authenticationTokenConfigFile != null && serviceConfig.tagList != [ ]
697           ) cfg.services
698         )
699       ;
701     environment.systemPackages = [ cfg.package ];
702     systemd.services.gitlab-runner = {
703       description = "Gitlab Runner";
704       documentation = [ "https://docs.gitlab.com/runner/" ];
705       after = [ "network.target" ]
706         ++ optional hasDocker "docker.service"
707         ++ optional hasPodman "podman.service";
709       requires = optional hasDocker "docker.service"
710         ++ optional hasPodman "podman.service";
711       wantedBy = [ "multi-user.target" ];
712       environment = config.networking.proxy.envVars // {
713         HOME = "/var/lib/gitlab-runner";
714       };
716       path =
717         (with pkgs; [
718           bash
719           gawk
720           jq
721           moreutils
722           remarshal
723           util-linux
724         ])
725         ++ [ cfg.package ]
726         ++ cfg.extraPackages;
728       reloadIfChanged = true;
729       serviceConfig = {
730         # Set `DynamicUser` under `systemd.services.gitlab-runner.serviceConfig`
731         # to `lib.mkForce false` in your configuration to run this service as root.
732         # You can also set `User` and `Group` options to run this service as desired user.
733         # Make sure to restart service or changes won't apply.
734         DynamicUser = true;
735         StateDirectory = "gitlab-runner";
736         SupplementaryGroups = optional hasDocker "docker"
737           ++ optional hasPodman "podman";
738         ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure";
739         ExecStart = "${startScript}/bin/gitlab-runner-start";
740         ExecReload = "!${configureScript}/bin/gitlab-runner-configure";
741       } // optionalAttrs cfg.gracefulTermination {
742         TimeoutStopSec = "${cfg.gracefulTimeout}";
743         KillSignal = "SIGQUIT";
744         KillMode = "process";
745       };
746     };
747     # Enable periodic clear-docker-cache script
748     systemd.services.gitlab-runner-clear-docker-cache = mkIf (cfg.clear-docker-cache.enable && (any (s: s.executor == "docker") (attrValues cfg.services))) {
749       description = "Prune gitlab-runner docker resources";
750       restartIfChanged = false;
751       unitConfig.X-StopOnRemoval = false;
753       serviceConfig.Type = "oneshot";
755       path = [ cfg.clear-docker-cache.package pkgs.gawk ];
757       script = ''
758         ${pkgs.gitlab-runner}/bin/clear-docker-cache ${toString cfg.clear-docker-cache.flags}
759       '';
761       startAt = cfg.clear-docker-cache.dates;
762     };
763     # Enable docker if `docker` executor is used in any service
764     virtualisation.docker.enable = mkIf (
765       any (s: s.executor == "docker") (attrValues cfg.services)
766     ) (mkDefault true);
767   };
768   imports = [
769     (mkRenamedOptionModule [ "services" "gitlab-runner" "packages" ] [ "services" "gitlab-runner" "extraPackages" ] )
770     (mkRemovedOptionModule [ "services" "gitlab-runner" "configOptions" ] "Use services.gitlab-runner.services option instead" )
771     (mkRemovedOptionModule [ "services" "gitlab-runner" "workDir" ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner" )
773     (mkRenamedOptionModule [ "services" "gitlab-runner" "checkInterval" ] [ "services" "gitlab-runner" "settings" "check_interval" ] )
774     (mkRenamedOptionModule [ "services" "gitlab-runner" "concurrent" ] [ "services" "gitlab-runner" "settings" "concurrent" ] )
775     (mkRenamedOptionModule [ "services" "gitlab-runner" "sentryDSN" ] [ "services" "gitlab-runner" "settings" "sentry_dsn" ] )
776     (mkRenamedOptionModule [ "services" "gitlab-runner" "prometheusListenAddress" ] [ "services" "gitlab-runner" "settings" "listen_address" ] )
778     (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "listenAddress" ] [ "services" "gitlab-runner" "settings" "session_server" "listen_address" ] )
779     (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "advertiseAddress" ] [ "services" "gitlab-runner" "settings" "session_server" "advertise_address" ] )
780     (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "sessionTimeout" ] [ "services" "gitlab-runner" "settings" "session_server" "session_timeout" ] )
781   ];
783   meta.maintainers = teams.gitlab.members;