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