15 unsafeDiscardStringContext
46 cfg = config.services.gitlab-runner;
47 hasDocker = config.virtualisation.docker.enable;
48 hasPodman = config.virtualisation.podman.enable && config.virtualisation.podman.dockerSocket.enable;
51 The whole logic of this module is to diff the hashes of the desired vs existing runners
52 The hash is recorded in the runner's name because we can't do better yet
53 See https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29350 for more details
58 hash = substring 0 12 (hashString "md5" (unsafeDiscardStringContext (toJSON service)));
60 if service ? description && service.description != null then
61 "${hash} ${service.description}"
63 "${name}_${config.networking.hostName}_${hash}";
65 hashedServices = mapAttrs' (
66 name: service: nameValuePair (genRunnerName name service) service
68 configPath = ''"$HOME"/.gitlab-runner/config.toml'';
69 configureScript = pkgs.writeShellApplication {
70 name = "gitlab-runner-configure";
84 if (cfg.configFile != null) then
86 cp ${cfg.configFile} ${configPath}
87 # make config file readable by service
88 chown -R --reference="$HOME" "$(dirname ${configPath})"
92 export CONFIG_FILE=${configPath}
94 mkdir -p "$(dirname ${configPath})"
97 # update global options
98 remarshal --if toml --of json --stringify ${configPath} \
99 | jq -cM 'with_entries(select([.key] | inside(["runners"])))' \
100 | jq -scM '.[0] + .[1]' - <(echo ${escapeShellArg (toJSON cfg.settings)}) \
101 | remarshal --if json --of toml \
102 | sponge ${configPath}
104 # remove no longer existing services
105 gitlab-runner verify --delete
107 ${toShellVar "NEEDED_SERVICES" (lib.mapAttrs (name: value: 1) hashedServices)}
109 declare -A REGISTERED_SERVICES
111 while IFS="," read -r name token;
113 REGISTERED_SERVICES["$name"]="$token"
114 done < <(gitlab-runner --log-format json list 2>&1 | grep Token | jq -r '.msg +"," + .Token')
116 echo "NEEDED_SERVICES: " "''${!NEEDED_SERVICES[@]}"
117 echo "REGISTERED_SERVICES:" "''${!REGISTERED_SERVICES[@]}"
119 # difference between current and desired state
120 declare -A NEW_SERVICES
121 for name in "''${!NEEDED_SERVICES[@]}"; do
122 if [ ! -v 'REGISTERED_SERVICES[$name]' ]; then
123 NEW_SERVICES[$name]=1
127 declare -A OLD_SERVICES
128 # shellcheck disable=SC2034
129 for name in "''${!REGISTERED_SERVICES[@]}"; do
130 if [ ! -v 'NEEDED_SERVICES[$name]' ]; then
131 OLD_SERVICES[$name]=1
135 # register new services
136 ${concatStringsSep "\n" (
137 mapAttrsToList (name: service: ''
138 # TODO so here we should mention NEW_SERVICES
139 if [ -v 'NEW_SERVICES["${name}"]' ] ; then
142 concatStringsSep " \\\n " (
145 if service.registrationConfigFile != null then
146 service.registrationConfigFile
148 service.authenticationTokenConfigFile
150 "gitlab-runner register"
153 "--executor ${service.executor}"
154 "--limit ${toString service.limit}"
155 "--request-concurrency ${toString service.requestConcurrency}"
158 service.authenticationTokenConfigFile == null
159 ) "--maximum-timeout ${toString service.maximumTimeout}"
160 ++ service.registrationFlags
161 ++ optional (service.buildsDir != null) "--builds-dir ${service.buildsDir}"
162 ++ optional (service.cloneUrl != null) "--clone-url ${service.cloneUrl}"
164 service.preGetSourcesScript != null
165 ) "--pre-get-sources-script ${service.preGetSourcesScript}"
167 service.postGetSourcesScript != null
168 ) "--post-get-sources-script ${service.postGetSourcesScript}"
169 ++ optional (service.preBuildScript != null) "--pre-build-script ${service.preBuildScript}"
170 ++ optional (service.postBuildScript != null) "--post-build-script ${service.postBuildScript}"
172 service.authenticationTokenConfigFile == null && service.tagList != [ ]
173 ) "--tag-list ${concatStringsSep "," service.tagList}"
174 ++ optional (service.authenticationTokenConfigFile == null && service.runUntagged) "--run-untagged"
176 service.authenticationTokenConfigFile == null && service.protected
177 ) "--access-level ref_protected"
178 ++ optional service.debugTraceDisabled "--debug-trace-disabled"
179 ++ map (e: "--env ${escapeShellArg e}") (
180 mapAttrsToList (name: value: "${name}=${value}") service.environmentVariables
182 ++ optionals (hasPrefix "docker" service.executor) (
185 service.dockerImage != null
186 ) "dockerImage option is required for ${service.executor} executor (${name})"
188 [ "--docker-image ${service.dockerImage}" ]
189 ++ optional service.dockerDisableCache "--docker-disable-cache"
190 ++ optional service.dockerPrivileged "--docker-privileged"
191 ++ map (v: "--docker-volumes ${escapeShellArg v}") service.dockerVolumes
192 ++ map (v: "--docker-extra-hosts ${escapeShellArg v}") service.dockerExtraHosts
193 ++ map (v: "--docker-allowed-images ${escapeShellArg v}") service.dockerAllowedImages
194 ++ map (v: "--docker-allowed-services ${escapeShellArg v}") service.dockerAllowedServices
198 } && sleep 1 || exit 1
203 # check key is in array https://stackoverflow.com/questions/30353951/how-to-check-if-dictionary-contains-a-key-in-bash
205 echo "NEW_SERVICES: ''${NEW_SERVICES[*]}"
206 echo "OLD_SERVICES: ''${OLD_SERVICES[*]}"
207 # unregister old services
208 for NAME in "''${!OLD_SERVICES[@]}"
210 [ -n "$NAME" ] && gitlab-runner unregister \
211 --name "$NAME" && sleep 1
214 # make config file readable by service
215 chown -R --reference="$HOME" "$(dirname ${configPath})"
218 startScript = pkgs.writeShellScriptBin "gitlab-runner-start" ''
219 export CONFIG_FILE=${configPath}
220 exec gitlab-runner run --working-directory $HOME
224 options.services.gitlab-runner = {
225 enable = mkEnableOption "Gitlab Runner";
226 configFile = mkOption {
227 type = types.nullOr types.path;
230 Configuration file for gitlab-runner.
232 {option}`configFile` takes precedence over {option}`services`.
233 {option}`checkInterval` and {option}`concurrent` will be ignored too.
235 This option is deprecated, please use {option}`services` instead.
236 You can use {option}`registrationConfigFile` and
237 {option}`registrationFlags`
238 for settings not covered by this module.
241 settings = mkOption {
242 type = types.submodule {
243 freeformType = (pkgs.formats.json { }).type;
247 Global gitlab-runner configuration. See
248 <https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section>
249 for supported values.
252 gracefulTermination = mkOption {
256 Finish all remaining jobs before stopping.
257 If not set gitlab-runner will stop immediately without waiting
258 for jobs to finish, which will lead to failed builds.
261 gracefulTimeout = mkOption {
263 default = "infinity";
264 example = "5min 20s";
266 Time to wait until a graceful shutdown is turned into a forceful one.
269 package = mkPackageOption pkgs "gitlab-runner" {
270 example = "gitlab-runner_1_11";
272 extraPackages = mkOption {
273 type = types.listOf types.package;
276 Extra packages to add to PATH for the gitlab-runner process.
279 services = mkOption {
280 description = "GitLab Runner services.";
282 example = literalExpression ''
284 # runner for building in docker via host's nix-daemon
285 # nix store will be readable in runner, might be insecure
287 # File should contain at least these two variables:
289 # - `REGISTRATION_TOKEN`
291 # NOTE: Support for runner registration tokens will be removed in GitLab 18.0.
292 # Please migrate to runner authentication tokens soon. For reference, the example
293 # runners below this one are configured with authentication tokens instead.
294 registrationConfigFile = "/run/secrets/gitlab-runner-registration";
296 dockerImage = "alpine";
298 "/nix/store:/nix/store:ro"
299 "/nix/var/nix/db:/nix/var/nix/db:ro"
300 "/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro"
302 dockerDisableCache = true;
303 preBuildScript = pkgs.writeScript "setup-container" '''
304 mkdir -p -m 0755 /nix/var/log/nix/drvs
305 mkdir -p -m 0755 /nix/var/nix/gcroots
306 mkdir -p -m 0755 /nix/var/nix/profiles
307 mkdir -p -m 0755 /nix/var/nix/temproots
308 mkdir -p -m 0755 /nix/var/nix/userpool
309 mkdir -p -m 1777 /nix/var/nix/gcroots/per-user
310 mkdir -p -m 1777 /nix/var/nix/profiles/per-user
311 mkdir -p -m 0755 /nix/var/nix/profiles/per-user/root
312 mkdir -p -m 0700 "$HOME/.nix-defexpr"
314 . ''${pkgs.nix}/etc/profile.d/nix.sh
316 ''${pkgs.nix}/bin/nix-env -i ''${concatStringsSep " " (with pkgs; [ nix cacert git openssh ])}
318 ''${pkgs.nix}/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
319 ''${pkgs.nix}/bin/nix-channel --update nixpkgs
321 environmentVariables = {
322 ENV = "/etc/profile";
324 NIX_REMOTE = "daemon";
325 PATH = "/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin";
326 NIX_SSL_CERT_FILE = "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt";
330 # runner for building docker images
332 # File should contain at least these two variables:
335 authenticationTokenConfigFile = "/run/secrets/gitlab-runner-docker-images-token-env";
337 dockerImage = "docker:stable";
339 "/var/run/docker.sock:/var/run/docker.sock"
341 tagList = [ "docker-images" ];
343 # runner for executing stuff on host system (very insecure!)
344 # make sure to add required packages (including git!)
345 # to `environment.systemPackages`
347 # File should contain at least these two variables:
350 authenticationTokenConfigFile = "/run/secrets/gitlab-runner-shell-token-env";
353 tagList = [ "shell" ];
355 # runner for everything else
357 # File should contain at least these two variables:
360 authenticationTokenConfigFile = "/run/secrets/gitlab-runner-default-token-env";
361 dockerImage = "debian:stable";
365 type = types.attrsOf (
368 authenticationTokenConfigFile = mkOption {
369 type = with types; nullOr path;
372 Absolute path to a file containing environment variables used for
373 gitlab-runner registrations with *runner authentication tokens*.
374 They replace the deprecated *runner registration tokens*, as
375 outlined in the [GitLab documentation].
377 A list of all supported environment variables can be found with
378 `gitlab-runner register --help`.
380 The ones you probably want to set are:
381 - `CI_SERVER_URL=<CI server URL>`
382 - `CI_SERVER_TOKEN=<runner authentication token secret>`
385 Make sure to use a quoted absolute path,
386 or it is going to be copied to Nix Store.
389 [GitLab documentation]: https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#estimated-time-frame-for-planned-changes
392 registrationConfigFile = mkOption {
393 type = with types; nullOr path;
396 Absolute path to a file with environment variables
397 used for gitlab-runner registration with *runner registration
400 A list of all supported environment variables can be found in
401 `gitlab-runner register --help`.
403 The ones you probably want to set are:
404 - `CI_SERVER_URL=<CI server URL>`
405 - `REGISTRATION_TOKEN=<registration secret>`
407 Support for *runner registration tokens* is deprecated since
408 GitLab 16.0, has been disabled by default in GitLab 17.0 and
409 will be removed in GitLab 18.0, as outlined in the
410 [GitLab documentation]. Please consider migrating to
411 [runner authentication tokens] and check the documentation on
412 {option}`services.gitlab-runner.services.<name>.authenticationTokenConfigFile`.
415 Make sure to use a quoted absolute path,
416 or it is going to be copied to Nix Store.
419 [GitLab documentation]: https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#estimated-time-frame-for-planned-changes
420 [runner authentication tokens]: https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#the-new-runner-registration-workflow
423 registrationFlags = mkOption {
424 type = types.listOf types.str;
426 example = [ "--docker-helper-image my/gitlab-runner-helper" ];
428 Extra command-line flags passed to
429 `gitlab-runner register`.
430 Execute `gitlab-runner register --help`
431 for a list of supported flags.
434 environmentVariables = mkOption {
435 type = types.attrsOf types.str;
441 Custom environment variables injected to build environment.
442 For secrets you can use {option}`registrationConfigFile`
443 with `RUNNER_ENV` variable set.
446 description = mkOption {
447 type = types.nullOr types.str;
450 Name/description of the runner.
453 executor = mkOption {
457 Select executor, eg. shell, docker, etc.
458 See [runner documentation](https://docs.gitlab.com/runner/executors/README.html) for more information.
461 buildsDir = mkOption {
462 type = types.nullOr types.path;
464 example = "/var/lib/gitlab-runner/builds";
466 Absolute path to a directory where builds will be stored
467 in context of selected executor (Locally, Docker, SSH).
470 cloneUrl = mkOption {
471 type = types.nullOr types.str;
473 example = "http://gitlab.example.local";
475 Overwrite the URL for the GitLab instance. Used if the Runner can’t connect to GitLab on the URL GitLab exposes itself.
478 dockerImage = mkOption {
479 type = types.nullOr types.str;
482 Docker image to be used.
485 dockerVolumes = mkOption {
486 type = types.listOf types.str;
488 example = [ "/var/run/docker.sock:/var/run/docker.sock" ];
490 Bind-mount a volume and create it
491 if it doesn't exist prior to mounting.
494 dockerDisableCache = mkOption {
498 Disable all container caching.
501 dockerPrivileged = mkOption {
505 Give extended privileges to container.
508 dockerExtraHosts = mkOption {
509 type = types.listOf types.str;
511 example = [ "other-host:127.0.0.1" ];
513 Add a custom host-to-IP mapping.
516 dockerAllowedImages = mkOption {
517 type = types.listOf types.str;
523 "my.registry.tld:5000/*:*"
526 Whitelist allowed images.
529 dockerAllowedServices = mkOption {
530 type = types.listOf types.str;
538 Whitelist allowed services.
541 preGetSourcesScript = mkOption {
542 type = types.nullOr (types.either types.str types.path);
545 Runner-specific command script executed before code is pulled.
548 postGetSourcesScript = mkOption {
549 type = types.nullOr (types.either types.str types.path);
552 Runner-specific command script executed after code is pulled.
555 preBuildScript = mkOption {
556 type = types.nullOr (types.either types.str types.path);
559 Runner-specific command script executed after code is pulled,
560 just before build executes.
563 postBuildScript = mkOption {
564 type = types.nullOr (types.either types.str types.path);
567 Runner-specific command script executed after code is pulled
568 and just after build executes.
572 type = types.listOf types.str;
577 This option has no effect for runners registered with an runner
578 authentication tokens and will be ignored.
581 runUntagged = mkOption {
585 Register to run untagged builds; defaults to
586 `true` when {option}`tagList` is empty.
588 This option has no effect for runners registered with an runner
589 authentication tokens and will be ignored.
596 Limit how many jobs can be handled concurrently by this service.
597 0 (default) simply means don't limit.
600 requestConcurrency = mkOption {
604 Limit number of concurrent requests for new jobs from GitLab.
607 maximumTimeout = mkOption {
611 What is the maximum timeout (in seconds) that will be set for
612 job when using this Runner. 0 (default) simply means don't limit.
614 This option has no effect for runners registered with an runner
615 authentication tokens and will be ignored.
618 protected = mkOption {
622 When set to true Runner will only run on pipelines
623 triggered on protected branches.
625 This option has no effect for runners registered with an runner
626 authentication tokens and will be ignored.
629 debugTraceDisabled = mkOption {
633 When set to true Runner will disable the possibility of
634 using the `CI_DEBUG_TRACE` feature.
641 clear-docker-cache = {
646 Whether to periodically prune gitlab runner's Docker resources. If
647 enabled, a systemd timer will run {command}`clear-docker-cache` as
648 specified by the `dates` option.
653 type = types.listOf types.str;
655 example = [ "prune" ];
657 Any additional flags passed to {command}`clear-docker-cache`.
665 Specification (in the format described by
666 {manpage}`systemd.time(7)`) of the time at
667 which the prune will occur.
672 default = config.virtualisation.docker.package;
673 defaultText = literalExpression "config.virtualisation.docker.package";
674 example = literalExpression "pkgs.docker";
675 description = "Docker package to use for clearing up docker cache.";
679 config = mkIf cfg.enable {
680 assertions = mapAttrsToList (name: serviceConfig: {
682 serviceConfig.registrationConfigFile == null || serviceConfig.authenticationTokenConfigFile == null;
683 message = "`services.gitlab-runner.${name}.registrationConfigFile` and `services.gitlab-runner.services.${name}.authenticationTokenConfigFile` are mutually exclusive.";
689 "services.gitlab-runner.services.${name}.`registrationConfigFile` points to a file in Nix Store. You should use quoted absolute path to prevent this."
690 ) (filterAttrs (name: serviceConfig: isStorePath serviceConfig.registrationConfigFile) cfg.services)
695 "services.gitlab-runner.services.${name}.`authenticationTokenConfigFile` points to a file in Nix Store. You should use quoted absolute path to prevent this."
699 name: serviceConfig: isStorePath serviceConfig.authenticationTokenConfigFile
704 (name: serviceConfig: ''
705 Runner registration tokens have been deprecated and disabled by default in GitLab >= 17.0.
706 Consider migrating to runner authentication tokens by setting `services.gitlab-runner.services.${name}.authenticationTokenConfigFile`.
707 https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html'')
709 filterAttrs (name: serviceConfig: serviceConfig.authenticationTokenConfigFile == null) cfg.services
715 ''`services.gitlab-runner.services.${name}.protected` with runner authentication tokens has no effect and will be ignored. Please remove it from your configuration.''
720 serviceConfig.authenticationTokenConfigFile != null && serviceConfig.protected == true
727 ''`services.gitlab-runner.services.${name}.runUntagged` with runner authentication tokens has no effect and will be ignored. Please remove it from your configuration.''
732 serviceConfig.authenticationTokenConfigFile != null && serviceConfig.runUntagged == true
739 ''`services.gitlab-runner.services.${name}.maximumTimeout` with runner authentication tokens has no effect and will be ignored. Please remove it from your configuration.''
744 serviceConfig.authenticationTokenConfigFile != null && serviceConfig.maximumTimeout != 0
751 ''`services.gitlab-runner.services.${name}.tagList` with runner authentication tokens has no effect and will be ignored. Please remove it from your configuration.''
755 serviceName: serviceConfig:
756 serviceConfig.authenticationTokenConfigFile != null && serviceConfig.tagList != [ ]
760 environment.systemPackages = [ cfg.package ];
761 systemd.services.gitlab-runner = {
762 description = "Gitlab Runner";
763 documentation = [ "https://docs.gitlab.com/runner/" ];
765 [ "network.target" ] ++ optional hasDocker "docker.service" ++ optional hasPodman "podman.service";
767 requires = optional hasDocker "docker.service" ++ optional hasPodman "podman.service";
768 wantedBy = [ "multi-user.target" ];
769 environment = config.networking.proxy.envVars // {
770 HOME = "/var/lib/gitlab-runner";
783 ++ cfg.extraPackages;
785 reloadIfChanged = true;
788 # Set `DynamicUser` under `systemd.services.gitlab-runner.serviceConfig`
789 # to `lib.mkForce false` in your configuration to run this service as root.
790 # You can also set `User` and `Group` options to run this service as desired user.
791 # Make sure to restart service or changes won't apply.
793 StateDirectory = "gitlab-runner";
794 SupplementaryGroups = optional hasDocker "docker" ++ optional hasPodman "podman";
795 ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure";
796 ExecStart = "${startScript}/bin/gitlab-runner-start";
797 ExecReload = "!${configureScript}/bin/gitlab-runner-configure";
799 // optionalAttrs cfg.gracefulTermination {
800 TimeoutStopSec = "${cfg.gracefulTimeout}";
801 KillSignal = "SIGQUIT";
802 KillMode = "process";
805 # Enable periodic clear-docker-cache script
806 systemd.services.gitlab-runner-clear-docker-cache =
807 mkIf (cfg.clear-docker-cache.enable && (any (s: s.executor == "docker") (attrValues cfg.services)))
809 description = "Prune gitlab-runner docker resources";
810 restartIfChanged = false;
811 unitConfig.X-StopOnRemoval = false;
813 serviceConfig.Type = "oneshot";
816 cfg.clear-docker-cache.package
821 ${pkgs.gitlab-runner}/bin/clear-docker-cache ${toString cfg.clear-docker-cache.flags}
824 startAt = cfg.clear-docker-cache.dates;
826 # Enable docker if `docker` executor is used in any service
827 virtualisation.docker.enable = mkIf (any (s: s.executor == "docker") (attrValues cfg.services)) (
832 (mkRenamedOptionModule
833 [ "services" "gitlab-runner" "packages" ]
834 [ "services" "gitlab-runner" "extraPackages" ]
836 (mkRemovedOptionModule [
840 ] "Use services.gitlab-runner.services option instead")
841 (mkRemovedOptionModule [
845 ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner")
847 (mkRenamedOptionModule
848 [ "services" "gitlab-runner" "checkInterval" ]
849 [ "services" "gitlab-runner" "settings" "check_interval" ]
851 (mkRenamedOptionModule
852 [ "services" "gitlab-runner" "concurrent" ]
853 [ "services" "gitlab-runner" "settings" "concurrent" ]
855 (mkRenamedOptionModule
856 [ "services" "gitlab-runner" "sentryDSN" ]
857 [ "services" "gitlab-runner" "settings" "sentry_dsn" ]
859 (mkRenamedOptionModule
860 [ "services" "gitlab-runner" "prometheusListenAddress" ]
861 [ "services" "gitlab-runner" "settings" "listen_address" ]
864 (mkRenamedOptionModule
865 [ "services" "gitlab-runner" "sessionServer" "listenAddress" ]
866 [ "services" "gitlab-runner" "settings" "session_server" "listen_address" ]
868 (mkRenamedOptionModule
869 [ "services" "gitlab-runner" "sessionServer" "advertiseAddress" ]
870 [ "services" "gitlab-runner" "settings" "session_server" "advertise_address" ]
872 (mkRenamedOptionModule
873 [ "services" "gitlab-runner" "sessionServer" "sessionTimeout" ]
874 [ "services" "gitlab-runner" "settings" "session_server" "session_timeout" ]
878 meta.maintainers = teams.gitlab.members;