at 23.05-pre 54 kB view raw
1{ config, lib, options, pkgs, utils, ... }: 2 3with lib; 4 5let 6 cfg = config.services.gitlab; 7 opt = options.services.gitlab; 8 9 toml = pkgs.formats.toml {}; 10 yaml = pkgs.formats.yaml {}; 11 12 ruby = cfg.packages.gitlab.ruby; 13 14 postgresqlPackage = if config.services.postgresql.enable then 15 config.services.postgresql.package 16 else 17 pkgs.postgresql_12; 18 19 # Git 2.36.1 seemingly contains a commit-graph related bug which is 20 # easily triggered through GitLab, so we downgrade it to 2.35.x 21 # until this issue is solved. See 22 # https://gitlab.com/gitlab-org/gitlab/-/issues/360783#note_992870101. 23 gitPackage = 24 let 25 version = "2.35.4"; 26 in 27 pkgs.git.overrideAttrs (oldAttrs: rec { 28 inherit version; 29 src = pkgs.fetchurl { 30 url = "https://www.kernel.org/pub/software/scm/git/git-${version}.tar.xz"; 31 sha256 = "sha256-mv13OdNkXggeKQkJ+47QcJ6lYmcw6Qjri1ZJ2ETCTOk="; 32 }; 33 }); 34 35 gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket"; 36 gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket"; 37 pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url; 38 39 databaseConfig = let 40 val = { 41 adapter = "postgresql"; 42 database = cfg.databaseName; 43 host = cfg.databaseHost; 44 username = cfg.databaseUsername; 45 encoding = "utf8"; 46 pool = cfg.databasePool; 47 } // cfg.extraDatabaseConfig; 48 in if lib.versionAtLeast (lib.getVersion cfg.packages.gitlab) "15.0" then { 49 production.main = val; 50 } else { 51 production = val; 52 }; 53 54 # We only want to create a database if we're actually going to connect to it. 55 databaseActuallyCreateLocally = cfg.databaseCreateLocally && cfg.databaseHost == ""; 56 57 gitalyToml = pkgs.writeText "gitaly.toml" '' 58 socket_path = "${lib.escape ["\""] gitalySocket}" 59 bin_dir = "${cfg.packages.gitaly}/bin" 60 prometheus_listen_addr = "localhost:9236" 61 62 [git] 63 bin_path = "${gitPackage}/bin/git" 64 65 [gitaly-ruby] 66 dir = "${cfg.packages.gitaly.ruby}" 67 68 [gitlab-shell] 69 dir = "${cfg.packages.gitlab-shell}" 70 71 [hooks] 72 custom_hooks_dir = "${cfg.statePath}/custom_hooks" 73 74 [gitlab] 75 secret_file = "${cfg.statePath}/gitlab_shell_secret" 76 url = "http+unix://${pathUrlQuote gitlabSocket}" 77 78 [gitlab.http-settings] 79 self_signed_cert = false 80 81 ${concatStringsSep "\n" (attrValues (mapAttrs (k: v: '' 82 [[storage]] 83 name = "${lib.escape ["\""] k}" 84 path = "${lib.escape ["\""] v.path}" 85 '') gitlabConfig.production.repositories.storages))} 86 ''; 87 88 gitlabShellConfig = flip recursiveUpdate cfg.extraShellConfig { 89 user = cfg.user; 90 gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}"; 91 http_settings.self_signed_cert = false; 92 repos_path = "${cfg.statePath}/repositories"; 93 secret_file = "${cfg.statePath}/gitlab_shell_secret"; 94 log_file = "${cfg.statePath}/log/gitlab-shell.log"; 95 }; 96 97 redisConfig.production.url = cfg.redisUrl; 98 99 cableYml = yaml.generate "cable.yml" { 100 production = { 101 adapter = "redis"; 102 url = cfg.redisUrl; 103 channel_prefix = "gitlab_production"; 104 }; 105 }; 106 107 pagesArgs = [ 108 "-pages-domain" gitlabConfig.production.pages.host 109 "-pages-root" "${gitlabConfig.production.shared.path}/pages" 110 ] ++ cfg.pagesExtraArgs; 111 112 gitlabConfig = { 113 # These are the default settings from config/gitlab.example.yml 114 production = flip recursiveUpdate cfg.extraConfig { 115 gitlab = { 116 host = cfg.host; 117 port = cfg.port; 118 https = cfg.https; 119 user = cfg.user; 120 email_enabled = true; 121 email_display_name = "GitLab"; 122 email_reply_to = "noreply@localhost"; 123 default_theme = 2; 124 default_projects_features = { 125 issues = true; 126 merge_requests = true; 127 wiki = true; 128 snippets = true; 129 builds = true; 130 container_registry = true; 131 }; 132 }; 133 repositories.storages.default.path = "${cfg.statePath}/repositories"; 134 repositories.storages.default.gitaly_address = "unix:${gitalySocket}"; 135 artifacts.enabled = true; 136 lfs.enabled = true; 137 gravatar.enabled = true; 138 cron_jobs = { }; 139 gitlab_ci.builds_path = "${cfg.statePath}/builds"; 140 ldap.enabled = false; 141 omniauth.enabled = false; 142 shared.path = "${cfg.statePath}/shared"; 143 gitaly.client_path = "${cfg.packages.gitaly}/bin"; 144 backup = { 145 gitaly_backup_path = "${cfg.packages.gitaly}/bin/gitaly-backup"; 146 path = cfg.backup.path; 147 keep_time = cfg.backup.keepTime; 148 } // (optionalAttrs (cfg.backup.uploadOptions != {}) { 149 upload = cfg.backup.uploadOptions; 150 }); 151 gitlab_shell = { 152 path = "${cfg.packages.gitlab-shell}"; 153 hooks_path = "${cfg.statePath}/shell/hooks"; 154 secret_file = "${cfg.statePath}/gitlab_shell_secret"; 155 upload_pack = true; 156 receive_pack = true; 157 }; 158 workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret"; 159 gitlab_kas.secret_file = "${cfg.statePath}/.gitlab_kas_secret"; 160 git.bin_path = "${gitPackage}/bin/git"; 161 monitoring = { 162 ip_whitelist = [ "127.0.0.0/8" "::1/128" ]; 163 sidekiq_exporter = { 164 enable = true; 165 address = "localhost"; 166 port = 3807; 167 }; 168 }; 169 registry = lib.optionalAttrs cfg.registry.enable { 170 enabled = true; 171 host = cfg.registry.externalAddress; 172 port = cfg.registry.externalPort; 173 key = cfg.registry.keyFile; 174 api_url = "http://${config.services.dockerRegistry.listenAddress}:${toString config.services.dockerRegistry.port}/"; 175 issuer = cfg.registry.issuer; 176 }; 177 extra = {}; 178 uploads.storage_path = cfg.statePath; 179 }; 180 }; 181 182 gitlabEnv = cfg.packages.gitlab.gitlabEnv // { 183 HOME = "${cfg.statePath}/home"; 184 PUMA_PATH = "${cfg.statePath}/"; 185 GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/"; 186 SCHEMA = "${cfg.statePath}/db/structure.sql"; 187 GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads"; 188 GITLAB_LOG_PATH = "${cfg.statePath}/log"; 189 GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig); 190 prometheus_multiproc_dir = "/run/gitlab"; 191 RAILS_ENV = "production"; 192 MALLOC_ARENA_MAX = "2"; 193 } // cfg.extraEnv; 194 195 runtimeDeps = with pkgs; [ 196 nodejs 197 gzip 198 git 199 gnutar 200 postgresqlPackage 201 coreutils 202 procps 203 findutils # Needed for gitlab:cleanup:orphan_job_artifact_files 204 ]; 205 206 gitlab-rake = pkgs.stdenv.mkDerivation { 207 name = "gitlab-rake"; 208 nativeBuildInputs = [ pkgs.makeWrapper ]; 209 dontBuild = true; 210 dontUnpack = true; 211 installPhase = '' 212 mkdir -p $out/bin 213 makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \ 214 ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \ 215 --set PATH '${lib.makeBinPath runtimeDeps}:$PATH' \ 216 --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \ 217 --chdir '${cfg.packages.gitlab}/share/gitlab' 218 ''; 219 }; 220 221 gitlab-rails = pkgs.stdenv.mkDerivation { 222 name = "gitlab-rails"; 223 nativeBuildInputs = [ pkgs.makeWrapper ]; 224 dontBuild = true; 225 dontUnpack = true; 226 installPhase = '' 227 mkdir -p $out/bin 228 makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \ 229 ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \ 230 --set PATH '${lib.makeBinPath runtimeDeps}:$PATH' \ 231 --chdir '${cfg.packages.gitlab}/share/gitlab' 232 ''; 233 }; 234 235 extraGitlabRb = pkgs.writeText "extra-gitlab.rb" cfg.extraGitlabRb; 236 237 smtpSettings = pkgs.writeText "gitlab-smtp-settings.rb" '' 238 if Rails.env.production? 239 Rails.application.config.action_mailer.delivery_method = :smtp 240 241 ActionMailer::Base.delivery_method = :smtp 242 ActionMailer::Base.smtp_settings = { 243 address: "${cfg.smtp.address}", 244 port: ${toString cfg.smtp.port}, 245 ${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''} 246 ${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''} 247 domain: "${cfg.smtp.domain}", 248 ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"} 249 enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto}, 250 tls: ${boolToString cfg.smtp.tls}, 251 ca_file: "/etc/ssl/certs/ca-certificates.crt", 252 openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}' 253 } 254 end 255 ''; 256 257in { 258 259 imports = [ 260 (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ]) 261 (mkRenamedOptionModule [ "services" "gitlab" "backupPath" ] [ "services" "gitlab" "backup" "path" ]) 262 (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "") 263 (mkRemovedOptionModule [ "services" "gitlab" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.gitlab directly instead") 264 ]; 265 266 options = { 267 services.gitlab = { 268 enable = mkOption { 269 type = types.bool; 270 default = false; 271 description = lib.mdDoc '' 272 Enable the gitlab service. 273 ''; 274 }; 275 276 packages.gitlab = mkOption { 277 type = types.package; 278 default = pkgs.gitlab; 279 defaultText = literalExpression "pkgs.gitlab"; 280 description = lib.mdDoc "Reference to the gitlab package"; 281 example = literalExpression "pkgs.gitlab-ee"; 282 }; 283 284 packages.gitlab-shell = mkOption { 285 type = types.package; 286 default = pkgs.gitlab-shell; 287 defaultText = literalExpression "pkgs.gitlab-shell"; 288 description = lib.mdDoc "Reference to the gitlab-shell package"; 289 }; 290 291 packages.gitlab-workhorse = mkOption { 292 type = types.package; 293 default = pkgs.gitlab-workhorse; 294 defaultText = literalExpression "pkgs.gitlab-workhorse"; 295 description = lib.mdDoc "Reference to the gitlab-workhorse package"; 296 }; 297 298 packages.gitaly = mkOption { 299 type = types.package; 300 default = pkgs.gitaly; 301 defaultText = literalExpression "pkgs.gitaly"; 302 description = lib.mdDoc "Reference to the gitaly package"; 303 }; 304 305 packages.pages = mkOption { 306 type = types.package; 307 default = pkgs.gitlab-pages; 308 defaultText = literalExpression "pkgs.gitlab-pages"; 309 description = lib.mdDoc "Reference to the gitlab-pages package"; 310 }; 311 312 statePath = mkOption { 313 type = types.str; 314 default = "/var/gitlab/state"; 315 description = lib.mdDoc '' 316 GitLab state directory. Configuration, repositories and 317 logs, among other things, are stored here. 318 319 The directory will be created automatically if it doesn't 320 exist already. Its parent directories must be owned by 321 either `root` or the user set in 322 {option}`services.gitlab.user`. 323 ''; 324 }; 325 326 extraEnv = mkOption { 327 type = types.attrsOf types.str; 328 default = {}; 329 description = lib.mdDoc '' 330 Additional environment variables for the GitLab environment. 331 ''; 332 }; 333 334 backup.startAt = mkOption { 335 type = with types; either str (listOf str); 336 default = []; 337 example = "03:00"; 338 description = lib.mdDoc '' 339 The time(s) to run automatic backup of GitLab 340 state. Specified in systemd's time format; see 341 {manpage}`systemd.time(7)`. 342 ''; 343 }; 344 345 backup.path = mkOption { 346 type = types.str; 347 default = cfg.statePath + "/backup"; 348 defaultText = literalExpression ''config.${opt.statePath} + "/backup"''; 349 description = lib.mdDoc "GitLab path for backups."; 350 }; 351 352 backup.keepTime = mkOption { 353 type = types.int; 354 default = 0; 355 example = 48; 356 apply = x: x * 60 * 60; 357 description = lib.mdDoc '' 358 How long to keep the backups around, in 359 hours. `0` means keep forever. 360 ''; 361 }; 362 363 backup.skip = mkOption { 364 type = with types; 365 let value = enum [ 366 "db" 367 "uploads" 368 "builds" 369 "artifacts" 370 "lfs" 371 "registry" 372 "pages" 373 "repositories" 374 "tar" 375 ]; 376 in 377 either value (listOf value); 378 default = []; 379 example = [ "artifacts" "lfs" ]; 380 apply = x: if isString x then x else concatStringsSep "," x; 381 description = lib.mdDoc '' 382 Directories to exclude from the backup. The example excludes 383 CI artifacts and LFS objects from the backups. The 384 `tar` option skips the creation of a tar 385 file. 386 387 Refer to <https://docs.gitlab.com/ee/raketasks/backup_restore.html#excluding-specific-directories-from-the-backup> 388 for more information. 389 ''; 390 }; 391 392 backup.uploadOptions = mkOption { 393 type = types.attrs; 394 default = {}; 395 example = literalExpression '' 396 { 397 # Fog storage connection settings, see http://fog.io/storage/ 398 connection = { 399 provider = "AWS"; 400 region = "eu-north-1"; 401 aws_access_key_id = "AKIAXXXXXXXXXXXXXXXX"; 402 aws_secret_access_key = { _secret = config.deployment.keys.aws_access_key.path; }; 403 }; 404 405 # The remote 'directory' to store your backups in. 406 # For S3, this would be the bucket name. 407 remote_directory = "my-gitlab-backups"; 408 409 # Use multipart uploads when file size reaches 100MB, see 410 # http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html 411 multipart_chunk_size = 104857600; 412 413 # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional 414 encryption = "AES256"; 415 416 # Specifies Amazon S3 storage class to use for backups, this is optional 417 storage_class = "STANDARD"; 418 }; 419 ''; 420 description = lib.mdDoc '' 421 GitLab automatic upload specification. Tells GitLab to 422 upload the backup to a remote location when done. 423 424 Attributes specified here are added under 425 `production -> backup -> upload` in 426 {file}`config/gitlab.yml`. 427 ''; 428 }; 429 430 databaseHost = mkOption { 431 type = types.str; 432 default = ""; 433 description = lib.mdDoc '' 434 GitLab database hostname. An empty string means 435 use local unix socket connection. 436 ''; 437 }; 438 439 databasePasswordFile = mkOption { 440 type = with types; nullOr path; 441 default = null; 442 description = lib.mdDoc '' 443 File containing the GitLab database user password. 444 445 This should be a string, not a nix path, since nix paths are 446 copied into the world-readable nix store. 447 ''; 448 }; 449 450 databaseCreateLocally = mkOption { 451 type = types.bool; 452 default = true; 453 description = lib.mdDoc '' 454 Whether a database should be automatically created on the 455 local host. Set this to `false` if you plan 456 on provisioning a local database yourself. This has no effect 457 if {option}`services.gitlab.databaseHost` is customized. 458 ''; 459 }; 460 461 databaseName = mkOption { 462 type = types.str; 463 default = "gitlab"; 464 description = lib.mdDoc "GitLab database name."; 465 }; 466 467 databaseUsername = mkOption { 468 type = types.str; 469 default = "gitlab"; 470 description = lib.mdDoc "GitLab database user."; 471 }; 472 473 databasePool = mkOption { 474 type = types.int; 475 default = 5; 476 description = lib.mdDoc "Database connection pool size."; 477 }; 478 479 extraDatabaseConfig = mkOption { 480 type = types.attrs; 481 default = {}; 482 description = lib.mdDoc "Extra configuration in config/database.yml."; 483 }; 484 485 redisUrl = mkOption { 486 type = types.str; 487 default = "unix:/run/gitlab/redis.sock"; 488 example = "redis://localhost:6379/"; 489 description = lib.mdDoc "Redis URL for all GitLab services."; 490 }; 491 492 extraGitlabRb = mkOption { 493 type = types.str; 494 default = ""; 495 example = '' 496 if Rails.env.production? 497 Rails.application.config.action_mailer.delivery_method = :sendmail 498 ActionMailer::Base.delivery_method = :sendmail 499 ActionMailer::Base.sendmail_settings = { 500 location: "/run/wrappers/bin/sendmail", 501 arguments: "-i -t" 502 } 503 end 504 ''; 505 description = lib.mdDoc '' 506 Extra configuration to be placed in config/extra-gitlab.rb. This can 507 be used to add configuration not otherwise exposed through this module's 508 options. 509 ''; 510 }; 511 512 host = mkOption { 513 type = types.str; 514 default = config.networking.hostName; 515 defaultText = literalExpression "config.networking.hostName"; 516 description = lib.mdDoc "GitLab host name. Used e.g. for copy-paste URLs."; 517 }; 518 519 port = mkOption { 520 type = types.port; 521 default = 8080; 522 description = lib.mdDoc '' 523 GitLab server port for copy-paste URLs, e.g. 80 or 443 if you're 524 service over https. 525 ''; 526 }; 527 528 https = mkOption { 529 type = types.bool; 530 default = false; 531 description = lib.mdDoc "Whether gitlab prints URLs with https as scheme."; 532 }; 533 534 user = mkOption { 535 type = types.str; 536 default = "gitlab"; 537 description = lib.mdDoc "User to run gitlab and all related services."; 538 }; 539 540 group = mkOption { 541 type = types.str; 542 default = "gitlab"; 543 description = lib.mdDoc "Group to run gitlab and all related services."; 544 }; 545 546 initialRootEmail = mkOption { 547 type = types.str; 548 default = "admin@local.host"; 549 description = lib.mdDoc '' 550 Initial email address of the root account if this is a new install. 551 ''; 552 }; 553 554 initialRootPasswordFile = mkOption { 555 type = with types; nullOr path; 556 default = null; 557 description = lib.mdDoc '' 558 File containing the initial password of the root account if 559 this is a new install. 560 561 This should be a string, not a nix path, since nix paths are 562 copied into the world-readable nix store. 563 ''; 564 }; 565 566 registry = { 567 enable = mkOption { 568 type = types.bool; 569 default = false; 570 description = lib.mdDoc "Enable GitLab container registry."; 571 }; 572 host = mkOption { 573 type = types.str; 574 default = config.services.gitlab.host; 575 defaultText = literalExpression "config.services.gitlab.host"; 576 description = lib.mdDoc "GitLab container registry host name."; 577 }; 578 port = mkOption { 579 type = types.int; 580 default = 4567; 581 description = lib.mdDoc "GitLab container registry port."; 582 }; 583 certFile = mkOption { 584 type = types.path; 585 description = lib.mdDoc "Path to GitLab container registry certificate."; 586 }; 587 keyFile = mkOption { 588 type = types.path; 589 description = lib.mdDoc "Path to GitLab container registry certificate-key."; 590 }; 591 defaultForProjects = mkOption { 592 type = types.bool; 593 default = cfg.registry.enable; 594 defaultText = literalExpression "config.${opt.registry.enable}"; 595 description = lib.mdDoc "If GitLab container registry should be enabled by default for projects."; 596 }; 597 issuer = mkOption { 598 type = types.str; 599 default = "gitlab-issuer"; 600 description = lib.mdDoc "GitLab container registry issuer."; 601 }; 602 serviceName = mkOption { 603 type = types.str; 604 default = "container_registry"; 605 description = lib.mdDoc "GitLab container registry service name."; 606 }; 607 externalAddress = mkOption { 608 type = types.str; 609 default = ""; 610 description = lib.mdDoc "External address used to access registry from the internet"; 611 }; 612 externalPort = mkOption { 613 type = types.int; 614 description = lib.mdDoc "External port used to access registry from the internet"; 615 }; 616 }; 617 618 smtp = { 619 enable = mkOption { 620 type = types.bool; 621 default = false; 622 description = lib.mdDoc "Enable gitlab mail delivery over SMTP."; 623 }; 624 625 address = mkOption { 626 type = types.str; 627 default = "localhost"; 628 description = lib.mdDoc "Address of the SMTP server for GitLab."; 629 }; 630 631 port = mkOption { 632 type = types.int; 633 default = 25; 634 description = lib.mdDoc "Port of the SMTP server for GitLab."; 635 }; 636 637 username = mkOption { 638 type = with types; nullOr str; 639 default = null; 640 description = lib.mdDoc "Username of the SMTP server for GitLab."; 641 }; 642 643 passwordFile = mkOption { 644 type = types.nullOr types.path; 645 default = null; 646 description = lib.mdDoc '' 647 File containing the password of the SMTP server for GitLab. 648 649 This should be a string, not a nix path, since nix paths 650 are copied into the world-readable nix store. 651 ''; 652 }; 653 654 domain = mkOption { 655 type = types.str; 656 default = "localhost"; 657 description = lib.mdDoc "HELO domain to use for outgoing mail."; 658 }; 659 660 authentication = mkOption { 661 type = with types; nullOr str; 662 default = null; 663 description = lib.mdDoc "Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; 664 }; 665 666 enableStartTLSAuto = mkOption { 667 type = types.bool; 668 default = true; 669 description = lib.mdDoc "Whether to try to use StartTLS."; 670 }; 671 672 tls = mkOption { 673 type = types.bool; 674 default = false; 675 description = lib.mdDoc "Whether to use TLS wrapper-mode."; 676 }; 677 678 opensslVerifyMode = mkOption { 679 type = types.str; 680 default = "peer"; 681 description = lib.mdDoc "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; 682 }; 683 }; 684 685 pagesExtraArgs = mkOption { 686 type = types.listOf types.str; 687 default = [ "-listen-proxy" "127.0.0.1:8090" ]; 688 description = lib.mdDoc "Arguments to pass to the gitlab-pages daemon"; 689 }; 690 691 secrets.secretFile = mkOption { 692 type = with types; nullOr path; 693 default = null; 694 description = lib.mdDoc '' 695 A file containing the secret used to encrypt variables in 696 the DB. If you change or lose this key you will be unable to 697 access variables stored in database. 698 699 Make sure the secret is at least 32 characters and all random, 700 no regular words or you'll be exposed to dictionary attacks. 701 702 This should be a string, not a nix path, since nix paths are 703 copied into the world-readable nix store. 704 ''; 705 }; 706 707 secrets.dbFile = mkOption { 708 type = with types; nullOr path; 709 default = null; 710 description = lib.mdDoc '' 711 A file containing the secret used to encrypt variables in 712 the DB. If you change or lose this key you will be unable to 713 access variables stored in database. 714 715 Make sure the secret is at least 32 characters and all random, 716 no regular words or you'll be exposed to dictionary attacks. 717 718 This should be a string, not a nix path, since nix paths are 719 copied into the world-readable nix store. 720 ''; 721 }; 722 723 secrets.otpFile = mkOption { 724 type = with types; nullOr path; 725 default = null; 726 description = lib.mdDoc '' 727 A file containing the secret used to encrypt secrets for OTP 728 tokens. If you change or lose this key, users which have 2FA 729 enabled for login won't be able to login anymore. 730 731 Make sure the secret is at least 32 characters and all random, 732 no regular words or you'll be exposed to dictionary attacks. 733 734 This should be a string, not a nix path, since nix paths are 735 copied into the world-readable nix store. 736 ''; 737 }; 738 739 secrets.jwsFile = mkOption { 740 type = with types; nullOr path; 741 default = null; 742 description = lib.mdDoc '' 743 A file containing the secret used to encrypt session 744 keys. If you change or lose this key, users will be 745 disconnected. 746 747 Make sure the secret is an RSA private key in PEM format. You can 748 generate one with 749 750 openssl genrsa 2048 751 752 This should be a string, not a nix path, since nix paths are 753 copied into the world-readable nix store. 754 ''; 755 }; 756 757 extraShellConfig = mkOption { 758 type = types.attrs; 759 default = {}; 760 description = lib.mdDoc "Extra configuration to merge into shell-config.yml"; 761 }; 762 763 puma.workers = mkOption { 764 type = types.int; 765 default = 2; 766 apply = x: builtins.toString x; 767 description = lib.mdDoc '' 768 The number of worker processes Puma should spawn. This 769 controls the amount of parallel Ruby code can be 770 executed. GitLab recommends `Number of CPU cores - 1`, but at least two. 771 772 ::: {.note} 773 Each worker consumes quite a bit of memory, so 774 be careful when increasing this. 775 ::: 776 ''; 777 }; 778 779 puma.threadsMin = mkOption { 780 type = types.int; 781 default = 0; 782 apply = x: builtins.toString x; 783 description = lib.mdDoc '' 784 The minimum number of threads Puma should use per 785 worker. 786 787 ::: {.note} 788 Each thread consumes memory and contributes to Global VM 789 Lock contention, so be careful when increasing this. 790 ::: 791 ''; 792 }; 793 794 puma.threadsMax = mkOption { 795 type = types.int; 796 default = 4; 797 apply = x: builtins.toString x; 798 description = lib.mdDoc '' 799 The maximum number of threads Puma should use per 800 worker. This limits how many threads Puma will automatically 801 spawn in response to requests. In contrast to workers, 802 threads will never be able to run Ruby code in parallel, but 803 give higher IO parallelism. 804 805 ::: {.note} 806 Each thread consumes memory and contributes to Global VM 807 Lock contention, so be careful when increasing this. 808 ::: 809 ''; 810 }; 811 812 sidekiq.memoryKiller.enable = mkOption { 813 type = types.bool; 814 default = true; 815 description = lib.mdDoc '' 816 Whether the Sidekiq MemoryKiller should be turned 817 on. MemoryKiller kills Sidekiq when its memory consumption 818 exceeds a certain limit. 819 820 See <https://docs.gitlab.com/ee/administration/operations/sidekiq_memory_killer.html> 821 for details. 822 ''; 823 }; 824 825 sidekiq.memoryKiller.maxMemory = mkOption { 826 type = types.int; 827 default = 2000; 828 apply = x: builtins.toString (x * 1024); 829 description = lib.mdDoc '' 830 The maximum amount of memory, in MiB, a Sidekiq worker is 831 allowed to consume before being killed. 832 ''; 833 }; 834 835 sidekiq.memoryKiller.graceTime = mkOption { 836 type = types.int; 837 default = 900; 838 apply = x: builtins.toString x; 839 description = lib.mdDoc '' 840 The time MemoryKiller waits after noticing excessive memory 841 consumption before killing Sidekiq. 842 ''; 843 }; 844 845 sidekiq.memoryKiller.shutdownWait = mkOption { 846 type = types.int; 847 default = 30; 848 apply = x: builtins.toString x; 849 description = lib.mdDoc '' 850 The time allowed for all jobs to finish before Sidekiq is 851 killed forcefully. 852 ''; 853 }; 854 855 logrotate = { 856 enable = mkOption { 857 type = types.bool; 858 default = true; 859 description = lib.mdDoc '' 860 Enable rotation of log files. 861 ''; 862 }; 863 864 frequency = mkOption { 865 type = types.str; 866 default = "daily"; 867 description = lib.mdDoc "How often to rotate the logs."; 868 }; 869 870 keep = mkOption { 871 type = types.int; 872 default = 30; 873 description = lib.mdDoc "How many rotations to keep."; 874 }; 875 }; 876 877 workhorse.config = mkOption { 878 type = toml.type; 879 default = {}; 880 example = literalExpression '' 881 { 882 object_storage.provider = "AWS"; 883 object_storage.s3 = { 884 aws_access_key_id = "AKIAXXXXXXXXXXXXXXXX"; 885 aws_secret_access_key = { _secret = "/var/keys/aws_secret_access_key"; }; 886 }; 887 }; 888 ''; 889 description = lib.mdDoc '' 890 Configuration options to add to Workhorse's configuration 891 file. 892 893 See 894 <https://gitlab.com/gitlab-org/gitlab/-/blob/master/workhorse/config.toml.example> 895 and 896 <https://docs.gitlab.com/ee/development/workhorse/configuration.html> 897 for examples and option documentation. 898 899 Options containing secret data should be set to an attribute 900 set containing the attribute `_secret` - a string pointing 901 to a file containing the value the option should be set 902 to. See the example to get a better picture of this: in the 903 resulting configuration file, the 904 `object_storage.s3.aws_secret_access_key` key will be set to 905 the contents of the {file}`/var/keys/aws_secret_access_key` 906 file. 907 ''; 908 }; 909 910 extraConfig = mkOption { 911 type = yaml.type; 912 default = {}; 913 example = literalExpression '' 914 { 915 gitlab = { 916 default_projects_features = { 917 builds = false; 918 }; 919 }; 920 omniauth = { 921 enabled = true; 922 auto_sign_in_with_provider = "openid_connect"; 923 allow_single_sign_on = ["openid_connect"]; 924 block_auto_created_users = false; 925 providers = [ 926 { 927 name = "openid_connect"; 928 label = "OpenID Connect"; 929 args = { 930 name = "openid_connect"; 931 scope = ["openid" "profile"]; 932 response_type = "code"; 933 issuer = "https://keycloak.example.com/auth/realms/My%20Realm"; 934 discovery = true; 935 client_auth_method = "query"; 936 uid_field = "preferred_username"; 937 client_options = { 938 identifier = "gitlab"; 939 secret = { _secret = "/var/keys/gitlab_oidc_secret"; }; 940 redirect_uri = "https://git.example.com/users/auth/openid_connect/callback"; 941 }; 942 }; 943 } 944 ]; 945 }; 946 }; 947 ''; 948 description = lib.mdDoc '' 949 Extra options to be added under 950 `production` in 951 {file}`config/gitlab.yml`, as a nix attribute 952 set. 953 954 Options containing secret data should be set to an attribute 955 set containing the attribute `_secret` - a 956 string pointing to a file containing the value the option 957 should be set to. See the example to get a better picture of 958 this: in the resulting 959 {file}`config/gitlab.yml` file, the 960 `production.omniauth.providers[0].args.client_options.secret` 961 key will be set to the contents of the 962 {file}`/var/keys/gitlab_oidc_secret` file. 963 ''; 964 }; 965 }; 966 }; 967 968 config = mkIf cfg.enable { 969 970 assertions = [ 971 { 972 assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.databaseUsername); 973 message = ''For local automatic database provisioning (services.gitlab.databaseCreateLocally == true) with peer authentication (services.gitlab.databaseHost == "") to work services.gitlab.user and services.gitlab.databaseUsername must be identical.''; 974 } 975 { 976 assertion = (cfg.databaseHost != "") -> (cfg.databasePasswordFile != null); 977 message = "When services.gitlab.databaseHost is customized, services.gitlab.databasePasswordFile must be set!"; 978 } 979 { 980 assertion = cfg.initialRootPasswordFile != null; 981 message = "services.gitlab.initialRootPasswordFile must be set!"; 982 } 983 { 984 assertion = cfg.secrets.secretFile != null; 985 message = "services.gitlab.secrets.secretFile must be set!"; 986 } 987 { 988 assertion = cfg.secrets.dbFile != null; 989 message = "services.gitlab.secrets.dbFile must be set!"; 990 } 991 { 992 assertion = cfg.secrets.otpFile != null; 993 message = "services.gitlab.secrets.otpFile must be set!"; 994 } 995 { 996 assertion = cfg.secrets.jwsFile != null; 997 message = "services.gitlab.secrets.jwsFile must be set!"; 998 } 999 { 1000 assertion = versionAtLeast postgresqlPackage.version "12.0.0"; 1001 message = "PostgreSQL >=12 is required to run GitLab 14. Follow the instructions in the manual section for upgrading PostgreSQL here: https://nixos.org/manual/nixos/stable/index.html#module-services-postgres-upgrading"; 1002 } 1003 ]; 1004 1005 environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ]; 1006 1007 systemd.targets.gitlab = { 1008 description = "Common target for all GitLab services."; 1009 wantedBy = [ "multi-user.target" ]; 1010 }; 1011 1012 # Redis is required for the sidekiq queue runner. 1013 services.redis.servers.gitlab = { 1014 enable = mkDefault true; 1015 user = mkDefault cfg.user; 1016 unixSocket = mkDefault "/run/gitlab/redis.sock"; 1017 unixSocketPerm = mkDefault 770; 1018 }; 1019 1020 # We use postgres as the main data store. 1021 services.postgresql = optionalAttrs databaseActuallyCreateLocally { 1022 enable = true; 1023 ensureUsers = singleton { name = cfg.databaseUsername; }; 1024 }; 1025 1026 # Enable rotation of log files 1027 services.logrotate = { 1028 enable = cfg.logrotate.enable; 1029 settings = { 1030 gitlab = { 1031 files = "${cfg.statePath}/log/*.log"; 1032 su = "${cfg.user} ${cfg.group}"; 1033 frequency = cfg.logrotate.frequency; 1034 rotate = cfg.logrotate.keep; 1035 copytruncate = true; 1036 compress = true; 1037 }; 1038 }; 1039 }; 1040 1041 # The postgresql module doesn't currently support concepts like 1042 # objects owners and extensions; for now we tack on what's needed 1043 # here. 1044 systemd.services.gitlab-postgresql = let pgsql = config.services.postgresql; in mkIf databaseActuallyCreateLocally { 1045 after = [ "postgresql.service" ]; 1046 bindsTo = [ "postgresql.service" ]; 1047 wantedBy = [ "gitlab.target" ]; 1048 partOf = [ "gitlab.target" ]; 1049 path = [ 1050 pgsql.package 1051 pkgs.util-linux 1052 ]; 1053 script = '' 1054 set -eu 1055 1056 PSQL() { 1057 psql --port=${toString pgsql.port} "$@" 1058 } 1059 1060 PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"' 1061 current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'") 1062 if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then 1063 PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"' 1064 if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then 1065 echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..." 1066 exit 1 1067 fi 1068 touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" 1069 PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\"" 1070 rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" 1071 fi 1072 PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm" 1073 PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS btree_gist;" 1074 ''; 1075 1076 serviceConfig = { 1077 User = pgsql.superUser; 1078 Type = "oneshot"; 1079 RemainAfterExit = true; 1080 }; 1081 }; 1082 1083 systemd.services.gitlab-registry-cert = optionalAttrs cfg.registry.enable { 1084 path = with pkgs; [ openssl ]; 1085 1086 script = '' 1087 mkdir -p $(dirname ${cfg.registry.keyFile}) 1088 mkdir -p $(dirname ${cfg.registry.certFile}) 1089 openssl req -nodes -newkey rsa:4096 -keyout ${cfg.registry.keyFile} -out /tmp/registry-auth.csr -subj "/CN=${cfg.registry.issuer}" 1090 openssl x509 -in /tmp/registry-auth.csr -out ${cfg.registry.certFile} -req -signkey ${cfg.registry.keyFile} -days 3650 1091 chown ${cfg.user}:${cfg.group} $(dirname ${cfg.registry.keyFile}) 1092 chown ${cfg.user}:${cfg.group} $(dirname ${cfg.registry.certFile}) 1093 chown ${cfg.user}:${cfg.group} ${cfg.registry.keyFile} 1094 chown ${cfg.user}:${cfg.group} ${cfg.registry.certFile} 1095 ''; 1096 1097 unitConfig = { 1098 ConditionPathExists = "!${cfg.registry.certFile}"; 1099 }; 1100 }; 1101 1102 # Ensure Docker Registry launches after the certificate generation job 1103 systemd.services.docker-registry = optionalAttrs cfg.registry.enable { 1104 wants = [ "gitlab-registry-cert.service" ]; 1105 after = [ "gitlab-registry-cert.service" ]; 1106 }; 1107 1108 # Enable Docker Registry, if GitLab-Container Registry is enabled 1109 services.dockerRegistry = optionalAttrs cfg.registry.enable { 1110 enable = true; 1111 enableDelete = true; # This must be true, otherwise GitLab won't manage it correctly 1112 extraConfig = { 1113 auth.token = { 1114 realm = "http${if cfg.https == true then "s" else ""}://${cfg.host}/jwt/auth"; 1115 service = cfg.registry.serviceName; 1116 issuer = cfg.registry.issuer; 1117 rootcertbundle = cfg.registry.certFile; 1118 }; 1119 }; 1120 }; 1121 1122 # Use postfix to send out mails. 1123 services.postfix.enable = mkDefault (cfg.smtp.enable && cfg.smtp.address == "localhost"); 1124 1125 users.users.${cfg.user} = 1126 { group = cfg.group; 1127 home = "${cfg.statePath}/home"; 1128 shell = "${pkgs.bash}/bin/bash"; 1129 uid = config.ids.uids.gitlab; 1130 }; 1131 1132 users.groups.${cfg.group}.gid = config.ids.gids.gitlab; 1133 1134 systemd.tmpfiles.rules = [ 1135 "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -" 1136 "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -" 1137 "z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -" 1138 "d ${cfg.backup.path} 0750 ${cfg.user} ${cfg.group} -" 1139 "d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -" 1140 "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -" 1141 "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -" 1142 "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -" 1143 "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -" 1144 "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -" 1145 "d ${cfg.statePath}/shell 0750 ${cfg.user} ${cfg.group} -" 1146 "d ${cfg.statePath}/tmp 0750 ${cfg.user} ${cfg.group} -" 1147 "d ${cfg.statePath}/tmp/pids 0750 ${cfg.user} ${cfg.group} -" 1148 "d ${cfg.statePath}/tmp/sockets 0750 ${cfg.user} ${cfg.group} -" 1149 "d ${cfg.statePath}/uploads 0700 ${cfg.user} ${cfg.group} -" 1150 "d ${cfg.statePath}/custom_hooks 0700 ${cfg.user} ${cfg.group} -" 1151 "d ${cfg.statePath}/custom_hooks/pre-receive.d 0700 ${cfg.user} ${cfg.group} -" 1152 "d ${cfg.statePath}/custom_hooks/post-receive.d 0700 ${cfg.user} ${cfg.group} -" 1153 "d ${cfg.statePath}/custom_hooks/update.d 0700 ${cfg.user} ${cfg.group} -" 1154 "d ${gitlabConfig.production.shared.path} 0750 ${cfg.user} ${cfg.group} -" 1155 "d ${gitlabConfig.production.shared.path}/artifacts 0750 ${cfg.user} ${cfg.group} -" 1156 "d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -" 1157 "d ${gitlabConfig.production.shared.path}/packages 0750 ${cfg.user} ${cfg.group} -" 1158 "d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -" 1159 "d ${gitlabConfig.production.shared.path}/registry 0750 ${cfg.user} ${cfg.group} -" 1160 "d ${gitlabConfig.production.shared.path}/terraform_state 0750 ${cfg.user} ${cfg.group} -" 1161 "L+ /run/gitlab/config - - - - ${cfg.statePath}/config" 1162 "L+ /run/gitlab/log - - - - ${cfg.statePath}/log" 1163 "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp" 1164 "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads" 1165 1166 "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}" 1167 ]; 1168 1169 1170 systemd.services.gitlab-config = { 1171 wantedBy = [ "gitlab.target" ]; 1172 partOf = [ "gitlab.target" ]; 1173 path = with pkgs; [ 1174 jq 1175 openssl 1176 replace-secret 1177 git 1178 ]; 1179 serviceConfig = { 1180 Type = "oneshot"; 1181 User = cfg.user; 1182 Group = cfg.group; 1183 TimeoutSec = "infinity"; 1184 Restart = "on-failure"; 1185 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; 1186 RemainAfterExit = true; 1187 1188 ExecStartPre = let 1189 preStartFullPrivileges = '' 1190 set -o errexit -o pipefail -o nounset 1191 shopt -s dotglob nullglob inherit_errexit 1192 1193 chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/* 1194 if [[ -n "$(ls -A '${cfg.statePath}'/config/)" ]]; then 1195 chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/* 1196 fi 1197 ''; 1198 in "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}"; 1199 1200 ExecStart = pkgs.writeShellScript "gitlab-config" '' 1201 set -o errexit -o pipefail -o nounset 1202 shopt -s inherit_errexit 1203 1204 umask u=rwx,g=rx,o= 1205 1206 cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION 1207 rm -rf ${cfg.statePath}/db/* 1208 rm -f ${cfg.statePath}/lib 1209 find '${cfg.statePath}/config/' -maxdepth 1 -mindepth 1 -type d -execdir rm -rf {} \; 1210 cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config 1211 cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db 1212 ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb 1213 ln -sf ${cableYml} ${cfg.statePath}/config/cable.yml 1214 1215 ${cfg.packages.gitlab-shell}/bin/install 1216 1217 ${optionalString cfg.smtp.enable '' 1218 install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb 1219 ${optionalString (cfg.smtp.passwordFile != null) '' 1220 replace-secret '@smtpPassword@' '${cfg.smtp.passwordFile}' '${cfg.statePath}/config/initializers/smtp_settings.rb' 1221 ''} 1222 ''} 1223 1224 ( 1225 umask u=rwx,g=,o= 1226 1227 openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret 1228 1229 rm -f '${cfg.statePath}/config/database.yml' 1230 1231 ${if cfg.databasePasswordFile != null then '' 1232 db_password="$(<'${cfg.databasePasswordFile}')" 1233 export db_password 1234 1235 if [[ -z "$db_password" ]]; then 1236 >&2 echo "Database password was an empty string!" 1237 exit 1 1238 fi 1239 1240 jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \ 1241 '.${if lib.versionAtLeast (lib.getVersion cfg.packages.gitlab) "15.0" then "production.main" else "production"}.password = $ENV.db_password' \ 1242 >'${cfg.statePath}/config/database.yml' 1243 '' 1244 else '' 1245 jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \ 1246 >'${cfg.statePath}/config/database.yml' 1247 '' 1248 } 1249 1250 ${utils.genJqSecretsReplacementSnippet 1251 gitlabConfig 1252 "${cfg.statePath}/config/gitlab.yml" 1253 } 1254 1255 rm -f '${cfg.statePath}/config/secrets.yml' 1256 1257 secret="$(<'${cfg.secrets.secretFile}')" 1258 db="$(<'${cfg.secrets.dbFile}')" 1259 otp="$(<'${cfg.secrets.otpFile}')" 1260 jws="$(<'${cfg.secrets.jwsFile}')" 1261 export secret db otp jws 1262 jq -n '{production: {secret_key_base: $ENV.secret, 1263 otp_key_base: $ENV.otp, 1264 db_key_base: $ENV.db, 1265 openid_connect_signing_key: $ENV.jws}}' \ 1266 > '${cfg.statePath}/config/secrets.yml' 1267 ) 1268 1269 # We remove potentially broken links to old gitlab-shell versions 1270 rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks 1271 1272 git config --global core.autocrlf "input" 1273 ''; 1274 }; 1275 }; 1276 1277 systemd.services.gitlab-db-config = { 1278 after = [ "gitlab-config.service" "gitlab-postgresql.service" "postgresql.service" ]; 1279 bindsTo = [ 1280 "gitlab-config.service" 1281 ] ++ optional (cfg.databaseHost == "") "postgresql.service" 1282 ++ optional databaseActuallyCreateLocally "gitlab-postgresql.service"; 1283 wantedBy = [ "gitlab.target" ]; 1284 partOf = [ "gitlab.target" ]; 1285 serviceConfig = { 1286 Type = "oneshot"; 1287 User = cfg.user; 1288 Group = cfg.group; 1289 TimeoutSec = "infinity"; 1290 Restart = "on-failure"; 1291 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; 1292 RemainAfterExit = true; 1293 1294 ExecStart = pkgs.writeShellScript "gitlab-db-config" '' 1295 set -o errexit -o pipefail -o nounset 1296 shopt -s inherit_errexit 1297 umask u=rwx,g=rx,o= 1298 1299 initial_root_password="$(<'${cfg.initialRootPasswordFile}')" 1300 ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \ 1301 GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null 1302 ''; 1303 }; 1304 }; 1305 1306 systemd.services.gitlab-sidekiq = { 1307 after = [ 1308 "network.target" 1309 "redis-gitlab.service" 1310 "postgresql.service" 1311 "gitlab-config.service" 1312 "gitlab-db-config.service" 1313 ]; 1314 bindsTo = [ 1315 "redis-gitlab.service" 1316 "gitlab-config.service" 1317 "gitlab-db-config.service" 1318 ] ++ optional (cfg.databaseHost == "") "postgresql.service"; 1319 wantedBy = [ "gitlab.target" ]; 1320 partOf = [ "gitlab.target" ]; 1321 environment = gitlabEnv // (optionalAttrs cfg.sidekiq.memoryKiller.enable { 1322 SIDEKIQ_MEMORY_KILLER_MAX_RSS = cfg.sidekiq.memoryKiller.maxMemory; 1323 SIDEKIQ_MEMORY_KILLER_GRACE_TIME = cfg.sidekiq.memoryKiller.graceTime; 1324 SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT = cfg.sidekiq.memoryKiller.shutdownWait; 1325 }); 1326 path = with pkgs; [ 1327 postgresqlPackage 1328 gitPackage 1329 ruby 1330 openssh 1331 nodejs 1332 gnupg 1333 1334 # Needed for GitLab project imports 1335 gnutar 1336 gzip 1337 1338 procps # Sidekiq MemoryKiller 1339 ]; 1340 serviceConfig = { 1341 Type = "simple"; 1342 User = cfg.user; 1343 Group = cfg.group; 1344 TimeoutSec = "infinity"; 1345 Restart = "always"; 1346 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; 1347 ExecStart="${cfg.packages.gitlab.rubyEnv}/bin/sidekiq -C \"${cfg.packages.gitlab}/share/gitlab/config/sidekiq_queues.yml\" -e production"; 1348 }; 1349 }; 1350 1351 systemd.services.gitaly = { 1352 after = [ "network.target" "gitlab-config.service" ]; 1353 bindsTo = [ "gitlab-config.service" ]; 1354 wantedBy = [ "gitlab.target" ]; 1355 partOf = [ "gitlab.target" ]; 1356 path = with pkgs; [ 1357 openssh 1358 procps # See https://gitlab.com/gitlab-org/gitaly/issues/1562 1359 gitPackage 1360 cfg.packages.gitaly.rubyEnv 1361 cfg.packages.gitaly.rubyEnv.wrappedRuby 1362 gzip 1363 bzip2 1364 ]; 1365 serviceConfig = { 1366 Type = "simple"; 1367 User = cfg.user; 1368 Group = cfg.group; 1369 TimeoutSec = "infinity"; 1370 Restart = "on-failure"; 1371 WorkingDirectory = gitlabEnv.HOME; 1372 ExecStart = "${cfg.packages.gitaly}/bin/gitaly ${gitalyToml}"; 1373 }; 1374 }; 1375 1376 systemd.services.gitlab-pages = mkIf (gitlabConfig.production.pages.enabled or false) { 1377 description = "GitLab static pages daemon"; 1378 after = [ "network.target" "gitlab-config.service" ]; 1379 bindsTo = [ "gitlab-config.service" ]; 1380 wantedBy = [ "gitlab.target" ]; 1381 partOf = [ "gitlab.target" ]; 1382 1383 path = [ pkgs.unzip ]; 1384 1385 serviceConfig = { 1386 Type = "simple"; 1387 TimeoutSec = "infinity"; 1388 Restart = "on-failure"; 1389 1390 User = cfg.user; 1391 Group = cfg.group; 1392 1393 ExecStart = "${cfg.packages.pages}/bin/gitlab-pages ${escapeShellArgs pagesArgs}"; 1394 WorkingDirectory = gitlabEnv.HOME; 1395 }; 1396 }; 1397 1398 systemd.services.gitlab-workhorse = { 1399 after = [ "network.target" ]; 1400 wantedBy = [ "gitlab.target" ]; 1401 partOf = [ "gitlab.target" ]; 1402 path = with pkgs; [ 1403 remarshal 1404 exiftool 1405 gitPackage 1406 gnutar 1407 gzip 1408 openssh 1409 gitlab-workhorse 1410 ]; 1411 serviceConfig = { 1412 Type = "simple"; 1413 User = cfg.user; 1414 Group = cfg.group; 1415 TimeoutSec = "infinity"; 1416 Restart = "on-failure"; 1417 WorkingDirectory = gitlabEnv.HOME; 1418 ExecStartPre = pkgs.writeShellScript "gitlab-workhorse-pre-start" '' 1419 set -o errexit -o pipefail -o nounset 1420 shopt -s dotglob nullglob inherit_errexit 1421 1422 ${utils.genJqSecretsReplacementSnippet 1423 cfg.workhorse.config 1424 "${cfg.statePath}/config/gitlab-workhorse.json"} 1425 1426 json2toml "${cfg.statePath}/config/gitlab-workhorse.json" "${cfg.statePath}/config/gitlab-workhorse.toml" 1427 rm "${cfg.statePath}/config/gitlab-workhorse.json" 1428 ''; 1429 ExecStart = 1430 "${cfg.packages.gitlab-workhorse}/bin/workhorse " 1431 + "-listenUmask 0 " 1432 + "-listenNetwork unix " 1433 + "-listenAddr /run/gitlab/gitlab-workhorse.socket " 1434 + "-authSocket ${gitlabSocket} " 1435 + "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public " 1436 + "-config ${cfg.statePath}/config/gitlab-workhorse.toml " 1437 + "-secretPath ${cfg.statePath}/.gitlab_workhorse_secret"; 1438 }; 1439 }; 1440 1441 systemd.services.gitlab-mailroom = mkIf (gitlabConfig.production.incoming_email.enabled or false) { 1442 description = "GitLab incoming mail daemon"; 1443 after = [ "network.target" "redis-gitlab.service" "gitlab-config.service" ]; 1444 bindsTo = [ "gitlab-config.service" ]; 1445 wantedBy = [ "gitlab.target" ]; 1446 partOf = [ "gitlab.target" ]; 1447 environment = gitlabEnv; 1448 serviceConfig = { 1449 Type = "simple"; 1450 TimeoutSec = "infinity"; 1451 Restart = "on-failure"; 1452 1453 User = cfg.user; 1454 Group = cfg.group; 1455 ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.statePath}/config/mail_room.yml"; 1456 WorkingDirectory = gitlabEnv.HOME; 1457 }; 1458 }; 1459 1460 systemd.services.gitlab = { 1461 after = [ 1462 "gitlab-workhorse.service" 1463 "network.target" 1464 "redis-gitlab.service" 1465 "gitlab-config.service" 1466 "gitlab-db-config.service" 1467 ]; 1468 bindsTo = [ 1469 "redis-gitlab.service" 1470 "gitlab-config.service" 1471 "gitlab-db-config.service" 1472 ] ++ optional (cfg.databaseHost == "") "postgresql.service"; 1473 wantedBy = [ "gitlab.target" ]; 1474 partOf = [ "gitlab.target" ]; 1475 environment = gitlabEnv; 1476 path = with pkgs; [ 1477 postgresqlPackage 1478 gitPackage 1479 openssh 1480 nodejs 1481 procps 1482 gnupg 1483 ]; 1484 serviceConfig = { 1485 Type = "notify"; 1486 User = cfg.user; 1487 Group = cfg.group; 1488 TimeoutSec = "infinity"; 1489 Restart = "on-failure"; 1490 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; 1491 ExecStart = concatStringsSep " " [ 1492 "${cfg.packages.gitlab.rubyEnv}/bin/puma" 1493 "-e production" 1494 "-C ${cfg.statePath}/config/puma.rb" 1495 "-w ${cfg.puma.workers}" 1496 "-t ${cfg.puma.threadsMin}:${cfg.puma.threadsMax}" 1497 ]; 1498 }; 1499 1500 }; 1501 1502 systemd.services.gitlab-backup = { 1503 after = [ "gitlab.service" ]; 1504 bindsTo = [ "gitlab.service" ]; 1505 startAt = cfg.backup.startAt; 1506 environment = { 1507 RAILS_ENV = "production"; 1508 CRON = "1"; 1509 } // optionalAttrs (stringLength cfg.backup.skip > 0) { 1510 SKIP = cfg.backup.skip; 1511 }; 1512 serviceConfig = { 1513 User = cfg.user; 1514 Group = cfg.group; 1515 ExecStart = "${gitlab-rake}/bin/gitlab-rake gitlab:backup:create"; 1516 }; 1517 }; 1518 1519 }; 1520 1521 meta.doc = ./gitlab.xml; 1522 1523}