at 23.05-pre 23 kB view raw
1{ config, lib, pkgs, ... }: 2with builtins; 3with lib; 4let 5 cfg = config.services.gitlab-runner; 6 hasDocker = config.virtualisation.docker.enable; 7 hashedServices = mapAttrs' 8 (name: service: nameValuePair 9 "${name}_${config.networking.hostName}_${ 10 substring 0 12 11 (hashString "md5" (unsafeDiscardStringContext (toJSON service)))}" 12 service) 13 cfg.services; 14 configPath = "$HOME/.gitlab-runner/config.toml"; 15 configureScript = pkgs.writeShellScriptBin "gitlab-runner-configure" ( 16 if (cfg.configFile != null) then '' 17 mkdir -p $(dirname ${configPath}) 18 cp ${cfg.configFile} ${configPath} 19 # make config file readable by service 20 chown -R --reference=$HOME $(dirname ${configPath}) 21 '' else '' 22 export CONFIG_FILE=${configPath} 23 24 mkdir -p $(dirname ${configPath}) 25 touch ${configPath} 26 27 # update global options 28 remarshal --if toml --of json ${configPath} \ 29 | jq -cM 'with_entries(select([.key] | inside(["runners"])))' \ 30 | jq -scM '.[0] + .[1]' - <(echo ${escapeShellArg (toJSON cfg.settings)}) \ 31 | remarshal --if json --of toml \ 32 | sponge ${configPath} 33 34 # remove no longer existing services 35 gitlab-runner verify --delete 36 37 # current and desired state 38 NEEDED_SERVICES=$(echo ${concatStringsSep " " (attrNames hashedServices)} | tr " " "\n") 39 REGISTERED_SERVICES=$(gitlab-runner list 2>&1 | grep 'Executor' | awk '{ print $1 }') 40 41 # difference between current and desired state 42 NEW_SERVICES=$(grep -vxF -f <(echo "$REGISTERED_SERVICES") <(echo "$NEEDED_SERVICES") || true) 43 OLD_SERVICES=$(grep -vxF -f <(echo "$NEEDED_SERVICES") <(echo "$REGISTERED_SERVICES") || true) 44 45 # register new services 46 ${concatStringsSep "\n" (mapAttrsToList (name: service: '' 47 if echo "$NEW_SERVICES" | grep -xq "${name}"; then 48 bash -c ${escapeShellArg (concatStringsSep " \\\n " ([ 49 "set -a && source ${service.registrationConfigFile} &&" 50 "gitlab-runner register" 51 "--non-interactive" 52 (if service.description != null then "--description \"${service.description}\"" else "--name '${name}'") 53 "--executor ${service.executor}" 54 "--limit ${toString service.limit}" 55 "--request-concurrency ${toString service.requestConcurrency}" 56 "--maximum-timeout ${toString service.maximumTimeout}" 57 ] ++ service.registrationFlags 58 ++ optional (service.buildsDir != null) 59 "--builds-dir ${service.buildsDir}" 60 ++ optional (service.cloneUrl != null) 61 "--clone-url ${service.cloneUrl}" 62 ++ optional (service.preCloneScript != null) 63 "--pre-clone-script ${service.preCloneScript}" 64 ++ optional (service.preBuildScript != null) 65 "--pre-build-script ${service.preBuildScript}" 66 ++ optional (service.postBuildScript != null) 67 "--post-build-script ${service.postBuildScript}" 68 ++ optional (service.tagList != [ ]) 69 "--tag-list ${concatStringsSep "," service.tagList}" 70 ++ optional service.runUntagged 71 "--run-untagged" 72 ++ optional service.protected 73 "--access-level ref_protected" 74 ++ optional service.debugTraceDisabled 75 "--debug-trace-disabled" 76 ++ map (e: "--env ${escapeShellArg e}") (mapAttrsToList (name: value: "${name}=${value}") service.environmentVariables) 77 ++ optionals (hasPrefix "docker" service.executor) ( 78 assert ( 79 assertMsg (service.dockerImage != null) 80 "dockerImage option is required for ${service.executor} executor (${name})"); 81 [ "--docker-image ${service.dockerImage}" ] 82 ++ optional service.dockerDisableCache 83 "--docker-disable-cache" 84 ++ optional service.dockerPrivileged 85 "--docker-privileged" 86 ++ map (v: "--docker-volumes ${escapeShellArg v}") service.dockerVolumes 87 ++ map (v: "--docker-extra-hosts ${escapeShellArg v}") service.dockerExtraHosts 88 ++ map (v: "--docker-allowed-images ${escapeShellArg v}") service.dockerAllowedImages 89 ++ map (v: "--docker-allowed-services ${escapeShellArg v}") service.dockerAllowedServices 90 ) 91 ))} && sleep 1 || exit 1 92 fi 93 '') hashedServices)} 94 95 # unregister old services 96 for NAME in $(echo "$OLD_SERVICES") 97 do 98 [ ! -z "$NAME" ] && gitlab-runner unregister \ 99 --name "$NAME" && sleep 1 100 done 101 102 # make config file readable by service 103 chown -R --reference=$HOME $(dirname ${configPath}) 104 ''); 105 startScript = pkgs.writeShellScriptBin "gitlab-runner-start" '' 106 export CONFIG_FILE=${configPath} 107 exec gitlab-runner run --working-directory $HOME 108 ''; 109in 110{ 111 options.services.gitlab-runner = { 112 enable = mkEnableOption (lib.mdDoc "Gitlab Runner"); 113 configFile = mkOption { 114 type = types.nullOr types.path; 115 default = null; 116 description = lib.mdDoc '' 117 Configuration file for gitlab-runner. 118 119 {option}`configFile` takes precedence over {option}`services`. 120 {option}`checkInterval` and {option}`concurrent` will be ignored too. 121 122 This option is deprecated, please use {option}`services` instead. 123 You can use {option}`registrationConfigFile` and 124 {option}`registrationFlags` 125 for settings not covered by this module. 126 ''; 127 }; 128 settings = mkOption { 129 type = types.submodule { 130 freeformType = (pkgs.formats.json { }).type; 131 }; 132 default = { }; 133 description = lib.mdDoc '' 134 Global gitlab-runner configuration. See 135 <https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section> 136 for supported values. 137 ''; 138 }; 139 gracefulTermination = mkOption { 140 type = types.bool; 141 default = false; 142 description = lib.mdDoc '' 143 Finish all remaining jobs before stopping. 144 If not set gitlab-runner will stop immediatly without waiting 145 for jobs to finish, which will lead to failed builds. 146 ''; 147 }; 148 gracefulTimeout = mkOption { 149 type = types.str; 150 default = "infinity"; 151 example = "5min 20s"; 152 description = lib.mdDoc '' 153 Time to wait until a graceful shutdown is turned into a forceful one. 154 ''; 155 }; 156 package = mkOption { 157 type = types.package; 158 default = pkgs.gitlab-runner; 159 defaultText = literalExpression "pkgs.gitlab-runner"; 160 example = literalExpression "pkgs.gitlab-runner_1_11"; 161 description = lib.mdDoc "Gitlab Runner package to use."; 162 }; 163 extraPackages = mkOption { 164 type = types.listOf types.package; 165 default = [ ]; 166 description = lib.mdDoc '' 167 Extra packages to add to PATH for the gitlab-runner process. 168 ''; 169 }; 170 services = mkOption { 171 description = lib.mdDoc "GitLab Runner services."; 172 default = { }; 173 example = literalExpression '' 174 { 175 # runner for building in docker via host's nix-daemon 176 # nix store will be readable in runner, might be insecure 177 nix = { 178 # File should contain at least these two variables: 179 # `CI_SERVER_URL` 180 # `REGISTRATION_TOKEN` 181 registrationConfigFile = "/run/secrets/gitlab-runner-registration"; 182 dockerImage = "alpine"; 183 dockerVolumes = [ 184 "/nix/store:/nix/store:ro" 185 "/nix/var/nix/db:/nix/var/nix/db:ro" 186 "/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro" 187 ]; 188 dockerDisableCache = true; 189 preBuildScript = pkgs.writeScript "setup-container" ''' 190 mkdir -p -m 0755 /nix/var/log/nix/drvs 191 mkdir -p -m 0755 /nix/var/nix/gcroots 192 mkdir -p -m 0755 /nix/var/nix/profiles 193 mkdir -p -m 0755 /nix/var/nix/temproots 194 mkdir -p -m 0755 /nix/var/nix/userpool 195 mkdir -p -m 1777 /nix/var/nix/gcroots/per-user 196 mkdir -p -m 1777 /nix/var/nix/profiles/per-user 197 mkdir -p -m 0755 /nix/var/nix/profiles/per-user/root 198 mkdir -p -m 0700 "$HOME/.nix-defexpr" 199 200 . ''${pkgs.nix}/etc/profile.d/nix.sh 201 202 ''${pkgs.nix}/bin/nix-env -i ''${concatStringsSep " " (with pkgs; [ nix cacert git openssh ])} 203 204 ''${pkgs.nix}/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable 205 ''${pkgs.nix}/bin/nix-channel --update nixpkgs 206 '''; 207 environmentVariables = { 208 ENV = "/etc/profile"; 209 USER = "root"; 210 NIX_REMOTE = "daemon"; 211 PATH = "/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin"; 212 NIX_SSL_CERT_FILE = "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"; 213 }; 214 tagList = [ "nix" ]; 215 }; 216 # runner for building docker images 217 docker-images = { 218 # File should contain at least these two variables: 219 # `CI_SERVER_URL` 220 # `REGISTRATION_TOKEN` 221 registrationConfigFile = "/run/secrets/gitlab-runner-registration"; 222 dockerImage = "docker:stable"; 223 dockerVolumes = [ 224 "/var/run/docker.sock:/var/run/docker.sock" 225 ]; 226 tagList = [ "docker-images" ]; 227 }; 228 # runner for executing stuff on host system (very insecure!) 229 # make sure to add required packages (including git!) 230 # to `environment.systemPackages` 231 shell = { 232 # File should contain at least these two variables: 233 # `CI_SERVER_URL` 234 # `REGISTRATION_TOKEN` 235 registrationConfigFile = "/run/secrets/gitlab-runner-registration"; 236 executor = "shell"; 237 tagList = [ "shell" ]; 238 }; 239 # runner for everything else 240 default = { 241 # File should contain at least these two variables: 242 # `CI_SERVER_URL` 243 # `REGISTRATION_TOKEN` 244 registrationConfigFile = "/run/secrets/gitlab-runner-registration"; 245 dockerImage = "debian:stable"; 246 }; 247 } 248 ''; 249 type = types.attrsOf (types.submodule { 250 options = { 251 registrationConfigFile = mkOption { 252 type = types.path; 253 description = lib.mdDoc '' 254 Absolute path to a file with environment variables 255 used for gitlab-runner registration. 256 A list of all supported environment variables can be found in 257 `gitlab-runner register --help`. 258 259 Ones that you probably want to set is 260 261 `CI_SERVER_URL=<CI server URL>` 262 263 `REGISTRATION_TOKEN=<registration secret>` 264 265 WARNING: make sure to use quoted absolute path, 266 or it is going to be copied to Nix Store. 267 ''; 268 }; 269 registrationFlags = mkOption { 270 type = types.listOf types.str; 271 default = [ ]; 272 example = [ "--docker-helper-image my/gitlab-runner-helper" ]; 273 description = lib.mdDoc '' 274 Extra command-line flags passed to 275 `gitlab-runner register`. 276 Execute `gitlab-runner register --help` 277 for a list of supported flags. 278 ''; 279 }; 280 environmentVariables = mkOption { 281 type = types.attrsOf types.str; 282 default = { }; 283 example = { NAME = "value"; }; 284 description = lib.mdDoc '' 285 Custom environment variables injected to build environment. 286 For secrets you can use {option}`registrationConfigFile` 287 with `RUNNER_ENV` variable set. 288 ''; 289 }; 290 description = mkOption { 291 type = types.nullOr types.str; 292 default = null; 293 description = lib.mdDoc '' 294 Name/description of the runner. 295 ''; 296 }; 297 executor = mkOption { 298 type = types.str; 299 default = "docker"; 300 description = lib.mdDoc '' 301 Select executor, eg. shell, docker, etc. 302 See [runner documentation](https://docs.gitlab.com/runner/executors/README.html) for more information. 303 ''; 304 }; 305 buildsDir = mkOption { 306 type = types.nullOr types.path; 307 default = null; 308 example = "/var/lib/gitlab-runner/builds"; 309 description = lib.mdDoc '' 310 Absolute path to a directory where builds will be stored 311 in context of selected executor (Locally, Docker, SSH). 312 ''; 313 }; 314 cloneUrl = mkOption { 315 type = types.nullOr types.str; 316 default = null; 317 example = "http://gitlab.example.local"; 318 description = lib.mdDoc '' 319 Overwrite the URL for the GitLab instance. Used if the Runner cant connect to GitLab on the URL GitLab exposes itself. 320 ''; 321 }; 322 dockerImage = mkOption { 323 type = types.nullOr types.str; 324 default = null; 325 description = lib.mdDoc '' 326 Docker image to be used. 327 ''; 328 }; 329 dockerVolumes = mkOption { 330 type = types.listOf types.str; 331 default = [ ]; 332 example = [ "/var/run/docker.sock:/var/run/docker.sock" ]; 333 description = lib.mdDoc '' 334 Bind-mount a volume and create it 335 if it doesn't exist prior to mounting. 336 ''; 337 }; 338 dockerDisableCache = mkOption { 339 type = types.bool; 340 default = false; 341 description = lib.mdDoc '' 342 Disable all container caching. 343 ''; 344 }; 345 dockerPrivileged = mkOption { 346 type = types.bool; 347 default = false; 348 description = lib.mdDoc '' 349 Give extended privileges to container. 350 ''; 351 }; 352 dockerExtraHosts = mkOption { 353 type = types.listOf types.str; 354 default = [ ]; 355 example = [ "other-host:127.0.0.1" ]; 356 description = lib.mdDoc '' 357 Add a custom host-to-IP mapping. 358 ''; 359 }; 360 dockerAllowedImages = mkOption { 361 type = types.listOf types.str; 362 default = [ ]; 363 example = [ "ruby:*" "python:*" "php:*" "my.registry.tld:5000/*:*" ]; 364 description = lib.mdDoc '' 365 Whitelist allowed images. 366 ''; 367 }; 368 dockerAllowedServices = mkOption { 369 type = types.listOf types.str; 370 default = [ ]; 371 example = [ "postgres:9" "redis:*" "mysql:*" ]; 372 description = lib.mdDoc '' 373 Whitelist allowed services. 374 ''; 375 }; 376 preCloneScript = mkOption { 377 type = types.nullOr types.path; 378 default = null; 379 description = lib.mdDoc '' 380 Runner-specific command script executed before code is pulled. 381 ''; 382 }; 383 preBuildScript = mkOption { 384 type = types.nullOr types.path; 385 default = null; 386 description = lib.mdDoc '' 387 Runner-specific command script executed after code is pulled, 388 just before build executes. 389 ''; 390 }; 391 postBuildScript = mkOption { 392 type = types.nullOr types.path; 393 default = null; 394 description = lib.mdDoc '' 395 Runner-specific command script executed after code is pulled 396 and just after build executes. 397 ''; 398 }; 399 tagList = mkOption { 400 type = types.listOf types.str; 401 default = [ ]; 402 description = lib.mdDoc '' 403 Tag list. 404 ''; 405 }; 406 runUntagged = mkOption { 407 type = types.bool; 408 default = false; 409 description = lib.mdDoc '' 410 Register to run untagged builds; defaults to 411 `true` when {option}`tagList` is empty. 412 ''; 413 }; 414 limit = mkOption { 415 type = types.int; 416 default = 0; 417 description = lib.mdDoc '' 418 Limit how many jobs can be handled concurrently by this service. 419 0 (default) simply means don't limit. 420 ''; 421 }; 422 requestConcurrency = mkOption { 423 type = types.int; 424 default = 0; 425 description = lib.mdDoc '' 426 Limit number of concurrent requests for new jobs from GitLab. 427 ''; 428 }; 429 maximumTimeout = mkOption { 430 type = types.int; 431 default = 0; 432 description = lib.mdDoc '' 433 What is the maximum timeout (in seconds) that will be set for 434 job when using this Runner. 0 (default) simply means don't limit. 435 ''; 436 }; 437 protected = mkOption { 438 type = types.bool; 439 default = false; 440 description = lib.mdDoc '' 441 When set to true Runner will only run on pipelines 442 triggered on protected branches. 443 ''; 444 }; 445 debugTraceDisabled = mkOption { 446 type = types.bool; 447 default = false; 448 description = lib.mdDoc '' 449 When set to true Runner will disable the possibility of 450 using the `CI_DEBUG_TRACE` feature. 451 ''; 452 }; 453 }; 454 }); 455 }; 456 clear-docker-cache = { 457 enable = mkOption { 458 type = types.bool; 459 default = false; 460 description = lib.mdDoc '' 461 Whether to periodically prune gitlab runner's Docker resources. If 462 enabled, a systemd timer will run {command}`clear-docker-cache` as 463 specified by the `dates` option. 464 ''; 465 }; 466 467 flags = mkOption { 468 type = types.listOf types.str; 469 default = [ ]; 470 example = [ "prune" ]; 471 description = lib.mdDoc '' 472 Any additional flags passed to {command}`clear-docker-cache`. 473 ''; 474 }; 475 476 dates = mkOption { 477 default = "weekly"; 478 type = types.str; 479 description = lib.mdDoc '' 480 Specification (in the format described by 481 {manpage}`systemd.time(7)`) of the time at 482 which the prune will occur. 483 ''; 484 }; 485 486 package = mkOption { 487 default = config.virtualisation.docker.package; 488 defaultText = literalExpression "config.virtualisation.docker.package"; 489 example = literalExpression "pkgs.docker"; 490 description = lib.mdDoc "Docker package to use for clearing up docker cache."; 491 }; 492 }; 493 }; 494 config = mkIf cfg.enable { 495 warnings = (mapAttrsToList 496 (n: v: "services.gitlab-runner.services.${n}.`registrationConfigFile` points to a file in Nix Store. You should use quoted absolute path to prevent this.") 497 (filterAttrs (n: v: isStorePath v.registrationConfigFile) cfg.services)); 498 499 environment.systemPackages = [ cfg.package ]; 500 systemd.services.gitlab-runner = { 501 description = "Gitlab Runner"; 502 documentation = [ "https://docs.gitlab.com/runner/" ]; 503 after = [ "network.target" ] 504 ++ optional hasDocker "docker.service"; 505 requires = optional hasDocker "docker.service"; 506 wantedBy = [ "multi-user.target" ]; 507 environment = config.networking.proxy.envVars // { 508 HOME = "/var/lib/gitlab-runner"; 509 }; 510 path = with pkgs; [ 511 bash 512 gawk 513 jq 514 moreutils 515 remarshal 516 util-linux 517 cfg.package 518 ] ++ cfg.extraPackages; 519 reloadIfChanged = true; 520 serviceConfig = { 521 # Set `DynamicUser` under `systemd.services.gitlab-runner.serviceConfig` 522 # to `lib.mkForce false` in your configuration to run this service as root. 523 # You can also set `User` and `Group` options to run this service as desired user. 524 # Make sure to restart service or changes won't apply. 525 DynamicUser = true; 526 StateDirectory = "gitlab-runner"; 527 SupplementaryGroups = optional hasDocker "docker"; 528 ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure"; 529 ExecStart = "${startScript}/bin/gitlab-runner-start"; 530 ExecReload = "!${configureScript}/bin/gitlab-runner-configure"; 531 } // optionalAttrs (cfg.gracefulTermination) { 532 TimeoutStopSec = "${cfg.gracefulTimeout}"; 533 KillSignal = "SIGQUIT"; 534 KillMode = "process"; 535 }; 536 }; 537 # Enable periodic clear-docker-cache script 538 systemd.services.gitlab-runner-clear-docker-cache = { 539 description = "Prune gitlab-runner docker resources"; 540 restartIfChanged = false; 541 unitConfig.X-StopOnRemoval = false; 542 543 serviceConfig.Type = "oneshot"; 544 545 path = [ cfg.clear-docker-cache.package pkgs.gawk ]; 546 547 script = '' 548 ${pkgs.gitlab-runner}/bin/clear-docker-cache ${toString cfg.clear-docker-cache.flags} 549 ''; 550 551 startAt = optional cfg.clear-docker-cache.enable cfg.clear-docker-cache.dates; 552 }; 553 # Enable docker if `docker` executor is used in any service 554 virtualisation.docker.enable = mkIf ( 555 any (s: s.executor == "docker") (attrValues cfg.services) 556 ) (mkDefault true); 557 }; 558 imports = [ 559 (mkRenamedOptionModule [ "services" "gitlab-runner" "packages" ] [ "services" "gitlab-runner" "extraPackages" ] ) 560 (mkRemovedOptionModule [ "services" "gitlab-runner" "configOptions" ] "Use services.gitlab-runner.services option instead" ) 561 (mkRemovedOptionModule [ "services" "gitlab-runner" "workDir" ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner" ) 562 563 (mkRenamedOptionModule [ "services" "gitlab-runner" "checkInterval" ] [ "services" "gitlab-runner" "settings" "check_interval" ] ) 564 (mkRenamedOptionModule [ "services" "gitlab-runner" "concurrent" ] [ "services" "gitlab-runner" "settings" "concurrent" ] ) 565 (mkRenamedOptionModule [ "services" "gitlab-runner" "sentryDSN" ] [ "services" "gitlab-runner" "settings" "sentry_dsn" ] ) 566 (mkRenamedOptionModule [ "services" "gitlab-runner" "prometheusListenAddress" ] [ "services" "gitlab-runner" "settings" "listen_address" ] ) 567 568 (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "listenAddress" ] [ "services" "gitlab-runner" "settings" "session_server" "listen_address" ] ) 569 (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "advertiseAddress" ] [ "services" "gitlab-runner" "settings" "session_server" "advertise_address" ] ) 570 (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "sessionTimeout" ] [ "services" "gitlab-runner" "settings" "session_server" "session_timeout" ] ) 571 ]; 572}