1 { config, lib, options, pkgs, utils, ... }:
6 cfg = config.services.gitlab;
7 opt = options.services.gitlab;
9 toml = pkgs.formats.toml {};
10 yaml = pkgs.formats.yaml {};
12 git = cfg.packages.gitaly.git;
14 postgresqlPackage = if config.services.postgresql.enable then
15 config.services.postgresql.package
19 gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
20 gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
21 pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
23 gitlabVersionAtLeast = version: lib.versionAtLeast (lib.getVersion cfg.packages.gitlab) version;
27 adapter = "postgresql";
28 database = cfg.databaseName;
29 host = cfg.databaseHost;
30 username = cfg.databaseUsername;
32 pool = cfg.databasePool;
33 } // cfg.extraDatabaseConfig;
36 if (gitlabVersionAtLeast "15.0")
39 ) // lib.optionalAttrs (gitlabVersionAtLeast "15.9") {
41 database_tasks = false;
46 # We only want to create a database if we're actually going to connect to it.
47 databaseActuallyCreateLocally = cfg.databaseCreateLocally && cfg.databaseHost == "";
49 gitalyToml = pkgs.writeText "gitaly.toml" ''
50 socket_path = "${lib.escape ["\""] gitalySocket}"
51 runtime_dir = "/run/gitaly"
52 bin_dir = "${cfg.packages.gitaly}/bin"
53 prometheus_listen_addr = "localhost:9236"
56 bin_path = "${git}/bin/git"
59 dir = "${cfg.packages.gitlab-shell}"
62 custom_hooks_dir = "${cfg.statePath}/custom_hooks"
65 secret_file = "${cfg.statePath}/gitlab_shell_secret"
66 url = "http+unix://${pathUrlQuote gitlabSocket}"
68 [gitlab.http-settings]
69 self_signed_cert = false
71 ${concatStringsSep "\n" (attrValues (mapAttrs (k: v: ''
73 name = "${lib.escape ["\""] k}"
74 path = "${lib.escape ["\""] v.path}"
75 '') gitlabConfig.production.repositories.storages))}
78 gitlabShellConfig = flip recursiveUpdate cfg.extraShellConfig {
80 gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
81 http_settings.self_signed_cert = false;
82 repos_path = "${cfg.statePath}/repositories";
83 secret_file = "${cfg.statePath}/gitlab_shell_secret";
84 log_file = "${cfg.statePath}/log/gitlab-shell.log";
87 redisConfig.production.url = cfg.redisUrl;
89 cableYml = yaml.generate "cable.yml" {
93 channel_prefix = "gitlab_production";
97 # Redis configuration file
98 resqueYml = pkgs.writeText "resque.yml" (builtins.toJSON redisConfig);
101 # These are the default settings from config/gitlab.example.yml
102 production = flip recursiveUpdate cfg.extraConfig {
108 email_enabled = true;
109 email_display_name = "GitLab";
110 email_reply_to = "noreply@localhost";
112 default_projects_features = {
114 merge_requests = true;
118 container_registry = true;
121 repositories.storages.default.path = "${cfg.statePath}/repositories";
122 repositories.storages.default.gitaly_address = "unix:${gitalySocket}";
123 artifacts.enabled = true;
125 gravatar.enabled = true;
127 gitlab_ci.builds_path = "${cfg.statePath}/builds";
128 ldap.enabled = false;
129 omniauth.enabled = false;
130 shared.path = "${cfg.statePath}/shared";
131 gitaly.client_path = "${cfg.packages.gitaly}/bin";
133 gitaly_backup_path = "${cfg.packages.gitaly}/bin/gitaly-backup";
134 path = cfg.backup.path;
135 keep_time = cfg.backup.keepTime;
136 } // (optionalAttrs (cfg.backup.uploadOptions != {}) {
137 upload = cfg.backup.uploadOptions;
140 path = "${cfg.packages.gitlab-shell}";
141 hooks_path = "${cfg.statePath}/shell/hooks";
142 secret_file = "${cfg.statePath}/gitlab_shell_secret";
146 workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret";
147 gitlab_kas.secret_file = "${cfg.statePath}/.gitlab_kas_secret";
148 git.bin_path = "git";
150 ip_whitelist = [ "127.0.0.0/8" "::1/128" ];
153 address = "localhost";
157 registry = lib.optionalAttrs cfg.registry.enable {
159 host = cfg.registry.externalAddress;
160 port = cfg.registry.externalPort;
161 key = cfg.registry.keyFile;
162 api_url = "http://${config.services.dockerRegistry.listenAddress}:${toString config.services.dockerRegistry.port}/";
163 issuer = cfg.registry.issuer;
165 elasticsearch.indexer_path = "${pkgs.gitlab-elasticsearch-indexer}/bin/gitlab-elasticsearch-indexer";
167 uploads.storage_path = cfg.statePath;
168 pages = optionalAttrs cfg.pages.enable {
169 enabled = cfg.pages.enable;
171 host = cfg.pages.settings.pages-domain;
172 secret_file = cfg.pages.settings.api-secret-key;
177 gitlabEnv = cfg.packages.gitlab.gitlabEnv // {
178 HOME = "${cfg.statePath}/home";
179 PUMA_PATH = "${cfg.statePath}/";
180 GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
181 SCHEMA = "${cfg.statePath}/db/structure.sql";
182 GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
183 GITLAB_LOG_PATH = "${cfg.statePath}/log";
184 prometheus_multiproc_dir = "/run/gitlab";
185 RAILS_ENV = "production";
186 MALLOC_ARENA_MAX = "2";
189 runtimeDeps = [ git ] ++ (with pkgs; [
196 findutils # Needed for gitlab:cleanup:orphan_job_artifact_files
199 gitlab-rake = pkgs.stdenv.mkDerivation {
200 name = "gitlab-rake";
201 nativeBuildInputs = [ pkgs.makeWrapper ];
206 makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \
207 ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
208 --set PATH '${lib.makeBinPath runtimeDeps}:$PATH' \
209 --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \
210 --chdir '${cfg.packages.gitlab}/share/gitlab'
214 gitlab-rails = pkgs.stdenv.mkDerivation {
215 name = "gitlab-rails";
216 nativeBuildInputs = [ pkgs.makeWrapper ];
221 makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \
222 ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
223 --set PATH '${lib.makeBinPath runtimeDeps}:$PATH' \
224 --chdir '${cfg.packages.gitlab}/share/gitlab'
228 extraGitlabRb = pkgs.writeText "extra-gitlab.rb" cfg.extraGitlabRb;
230 smtpSettings = pkgs.writeText "gitlab-smtp-settings.rb" ''
231 if Rails.env.production?
232 Rails.application.config.action_mailer.delivery_method = :smtp
234 ActionMailer::Base.delivery_method = :smtp
235 ActionMailer::Base.smtp_settings = {
236 address: "${cfg.smtp.address}",
237 port: ${toString cfg.smtp.port},
238 ${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''}
239 ${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''}
240 domain: "${cfg.smtp.domain}",
241 ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
242 enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto},
243 tls: ${boolToString cfg.smtp.tls},
244 ca_file: "/etc/ssl/certs/ca-certificates.crt",
245 openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
253 (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
254 (mkRenamedOptionModule [ "services" "gitlab" "backupPath" ] [ "services" "gitlab" "backup" "path" ])
255 (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
256 (mkRemovedOptionModule [ "services" "gitlab" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.gitlab directly instead")
257 (mkRemovedOptionModule [ "services" "gitlab" "pagesExtraArgs" ] "Use services.gitlab.pages.settings instead")
266 Enable the gitlab service.
270 packages.gitlab = mkPackageOption pkgs "gitlab" {
271 example = "gitlab-ee";
274 packages.gitlab-shell = mkPackageOption pkgs "gitlab-shell" { };
276 packages.gitlab-workhorse = mkPackageOption pkgs "gitlab-workhorse" { };
278 packages.gitaly = mkPackageOption pkgs "gitaly" { };
280 packages.pages = mkPackageOption pkgs "gitlab-pages" { };
282 statePath = mkOption {
284 default = "/var/gitlab/state";
286 GitLab state directory. Configuration, repositories and
287 logs, among other things, are stored here.
289 The directory will be created automatically if it doesn't
290 exist already. Its parent directories must be owned by
291 either `root` or the user set in
292 {option}`services.gitlab.user`.
296 extraEnv = mkOption {
297 type = types.attrsOf types.str;
300 Additional environment variables for the GitLab environment.
304 backup.startAt = mkOption {
305 type = with types; either str (listOf str);
309 The time(s) to run automatic backup of GitLab
310 state. Specified in systemd's time format; see
311 {manpage}`systemd.time(7)`.
315 backup.path = mkOption {
317 default = cfg.statePath + "/backup";
318 defaultText = literalExpression ''config.${opt.statePath} + "/backup"'';
319 description = "GitLab path for backups.";
322 backup.keepTime = mkOption {
326 apply = x: x * 60 * 60;
328 How long to keep the backups around, in
329 hours. `0` means “keep forever”.
333 backup.skip = mkOption {
347 either value (listOf value);
349 example = [ "artifacts" "lfs" ];
350 apply = x: if isString x then x else concatStringsSep "," x;
352 Directories to exclude from the backup. The example excludes
353 CI artifacts and LFS objects from the backups. The
354 `tar` option skips the creation of a tar
357 Refer to <https://docs.gitlab.com/ee/raketasks/backup_restore.html#excluding-specific-directories-from-the-backup>
358 for more information.
362 backup.uploadOptions = mkOption {
365 example = literalExpression ''
367 # Fog storage connection settings, see http://fog.io/storage/
370 region = "eu-north-1";
371 aws_access_key_id = "AKIAXXXXXXXXXXXXXXXX";
372 aws_secret_access_key = { _secret = config.deployment.keys.aws_access_key.path; };
375 # The remote 'directory' to store your backups in.
376 # For S3, this would be the bucket name.
377 remote_directory = "my-gitlab-backups";
379 # Use multipart uploads when file size reaches 100MB, see
380 # http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
381 multipart_chunk_size = 104857600;
383 # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
384 encryption = "AES256";
386 # Specifies Amazon S3 storage class to use for backups, this is optional
387 storage_class = "STANDARD";
391 GitLab automatic upload specification. Tells GitLab to
392 upload the backup to a remote location when done.
394 Attributes specified here are added under
395 `production -> backup -> upload` in
396 {file}`config/gitlab.yml`.
400 databaseHost = mkOption {
404 GitLab database hostname. An empty string means
405 “use local unix socket connection”.
409 databasePasswordFile = mkOption {
410 type = with types; nullOr path;
413 File containing the GitLab database user password.
415 This should be a string, not a nix path, since nix paths are
416 copied into the world-readable nix store.
420 databaseCreateLocally = mkOption {
424 Whether a database should be automatically created on the
425 local host. Set this to `false` if you plan
426 on provisioning a local database yourself. This has no effect
427 if {option}`services.gitlab.databaseHost` is customized.
431 databaseName = mkOption {
434 description = "GitLab database name.";
437 databaseUsername = mkOption {
440 description = "GitLab database user.";
443 databasePool = mkOption {
446 description = "Database connection pool size.";
449 extraDatabaseConfig = mkOption {
452 description = "Extra configuration in config/database.yml.";
455 redisUrl = mkOption {
457 default = "unix:/run/gitlab/redis.sock";
458 example = "redis://localhost:6379/";
459 description = "Redis URL for all GitLab services.";
462 extraGitlabRb = mkOption {
466 if Rails.env.production?
467 Rails.application.config.action_mailer.delivery_method = :sendmail
468 ActionMailer::Base.delivery_method = :sendmail
469 ActionMailer::Base.sendmail_settings = {
470 location: "/run/wrappers/bin/sendmail",
476 Extra configuration to be placed in config/extra-gitlab.rb. This can
477 be used to add configuration not otherwise exposed through this module's
484 default = config.networking.hostName;
485 defaultText = literalExpression "config.networking.hostName";
486 description = "GitLab host name. Used e.g. for copy-paste URLs.";
493 GitLab server port for copy-paste URLs, e.g. 80 or 443 if you're
501 description = "Whether gitlab prints URLs with https as scheme.";
507 description = "User to run gitlab and all related services.";
513 description = "Group to run gitlab and all related services.";
516 initialRootEmail = mkOption {
518 default = "admin@local.host";
520 Initial email address of the root account if this is a new install.
524 initialRootPasswordFile = mkOption {
525 type = with types; nullOr path;
528 File containing the initial password of the root account if
529 this is a new install.
531 This should be a string, not a nix path, since nix paths are
532 copied into the world-readable nix store.
540 description = "Enable GitLab container registry.";
543 type = types.package;
545 if versionAtLeast config.system.stateVersion "23.11"
546 then pkgs.gitlab-container-registry
547 else pkgs.docker-distribution;
548 defaultText = literalExpression "pkgs.docker-distribution";
550 Container registry package to use.
552 External container registries such as `pkgs.docker-distribution` are not supported
553 anymore since GitLab 16.0.0.
558 default = config.services.gitlab.host;
559 defaultText = literalExpression "config.services.gitlab.host";
560 description = "GitLab container registry host name.";
565 description = "GitLab container registry port.";
567 certFile = mkOption {
569 description = "Path to GitLab container registry certificate.";
573 description = "Path to GitLab container registry certificate-key.";
575 defaultForProjects = mkOption {
577 default = cfg.registry.enable;
578 defaultText = literalExpression "config.${opt.registry.enable}";
579 description = "If GitLab container registry should be enabled by default for projects.";
583 default = "gitlab-issuer";
584 description = "GitLab container registry issuer.";
586 serviceName = mkOption {
588 default = "container_registry";
589 description = "GitLab container registry service name.";
591 externalAddress = mkOption {
594 description = "External address used to access registry from the internet";
596 externalPort = mkOption {
598 description = "External port used to access registry from the internet";
606 description = "Enable gitlab mail delivery over SMTP.";
611 default = "localhost";
612 description = "Address of the SMTP server for GitLab.";
618 description = "Port of the SMTP server for GitLab.";
621 username = mkOption {
622 type = with types; nullOr str;
624 description = "Username of the SMTP server for GitLab.";
627 passwordFile = mkOption {
628 type = types.nullOr types.path;
631 File containing the password of the SMTP server for GitLab.
633 This should be a string, not a nix path, since nix paths
634 are copied into the world-readable nix store.
640 default = "localhost";
641 description = "HELO domain to use for outgoing mail.";
644 authentication = mkOption {
645 type = with types; nullOr str;
647 description = "Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
650 enableStartTLSAuto = mkOption {
653 description = "Whether to try to use StartTLS.";
659 description = "Whether to use TLS wrapper-mode.";
662 opensslVerifyMode = mkOption {
665 description = "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
669 pages.enable = mkEnableOption "the GitLab Pages service";
671 pages.settings = mkOption {
672 example = literalExpression ''
674 pages-domain = "example.com";
675 auth-client-id = "generated-id-xxxxxxx";
676 auth-client-secret = { _secret = "/var/keys/auth-client-secret"; };
677 auth-redirect-uri = "https://projects.example.com/auth";
678 auth-secret = { _secret = "/var/keys/auth-secret"; };
679 auth-server = "https://gitlab.example.com";
684 Configuration options to set in the GitLab Pages config
687 Options containing secret data should be set to an attribute
688 set containing the attribute `_secret` - a string pointing
689 to a file containing the value the option should be set
690 to. See the example to get a better picture of this: in the
691 resulting configuration file, the `auth-client-secret` and
692 `auth-secret` keys will be set to the contents of the
693 {file}`/var/keys/auth-client-secret` and
694 {file}`/var/keys/auth-secret` files respectively.
697 type = types.submodule {
698 freeformType = with types; attrsOf (nullOr (oneOf [ str int bool attrs ]));
701 listen-http = mkOption {
702 type = with types; listOf str;
703 apply = x: if x == [] then null else lib.concatStringsSep "," x;
706 The address(es) to listen on for HTTP requests.
710 listen-https = mkOption {
711 type = with types; listOf str;
712 apply = x: if x == [] then null else lib.concatStringsSep "," x;
715 The address(es) to listen on for HTTPS requests.
719 listen-proxy = mkOption {
720 type = with types; listOf str;
721 apply = x: if x == [] then null else lib.concatStringsSep "," x;
722 default = [ "127.0.0.1:8090" ];
724 The address(es) to listen on for proxy requests.
728 artifacts-server = mkOption {
729 type = with types; nullOr str;
730 default = "http${optionalString cfg.https "s"}://${cfg.host}/api/v4";
731 defaultText = "http(s)://<services.gitlab.host>/api/v4";
732 example = "https://gitlab.example.com/api/v4";
734 API URL to proxy artifact requests to.
738 gitlab-server = mkOption {
739 type = with types; nullOr str;
740 default = "http${optionalString cfg.https "s"}://${cfg.host}";
741 defaultText = "http(s)://<services.gitlab.host>";
742 example = "https://gitlab.example.com";
744 Public GitLab server URL.
748 internal-gitlab-server = mkOption {
749 type = with types; nullOr str;
751 defaultText = "http(s)://<services.gitlab.host>";
752 example = "https://gitlab.example.internal";
754 Internal GitLab server used for API requests, useful
755 if you want to send that traffic over an internal load
756 balancer. By default, the value of
757 `services.gitlab.pages.settings.gitlab-server` is
762 api-secret-key = mkOption {
763 type = with types; nullOr str;
764 default = "${cfg.statePath}/gitlab_pages_secret";
767 File with secret key used to authenticate with the
772 pages-domain = mkOption {
773 type = with types; nullOr str;
774 example = "example.com";
776 The domain to serve static pages on.
780 pages-root = mkOption {
782 default = "${gitlabConfig.production.shared.path}/pages";
783 defaultText = literalExpression ''config.${opt.extraConfig}.production.shared.path + "/pages"'';
785 The directory where pages are stored.
792 secrets.secretFile = mkOption {
793 type = with types; nullOr path;
796 A file containing the secret used to encrypt variables in
797 the DB. If you change or lose this key you will be unable to
798 access variables stored in database.
800 Make sure the secret is at least 32 characters and all random,
801 no regular words or you'll be exposed to dictionary attacks.
803 This should be a string, not a nix path, since nix paths are
804 copied into the world-readable nix store.
808 secrets.dbFile = mkOption {
809 type = with types; nullOr path;
812 A file containing the secret used to encrypt variables in
813 the DB. If you change or lose this key you will be unable to
814 access variables stored in database.
816 Make sure the secret is at least 32 characters and all random,
817 no regular words or you'll be exposed to dictionary attacks.
819 This should be a string, not a nix path, since nix paths are
820 copied into the world-readable nix store.
824 secrets.otpFile = mkOption {
825 type = with types; nullOr path;
828 A file containing the secret used to encrypt secrets for OTP
829 tokens. If you change or lose this key, users which have 2FA
830 enabled for login won't be able to login anymore.
832 Make sure the secret is at least 32 characters and all random,
833 no regular words or you'll be exposed to dictionary attacks.
835 This should be a string, not a nix path, since nix paths are
836 copied into the world-readable nix store.
840 secrets.jwsFile = mkOption {
841 type = with types; nullOr path;
844 A file containing the secret used to encrypt session
845 keys. If you change or lose this key, users will be
848 Make sure the secret is an RSA private key in PEM format. You can
853 This should be a string, not a nix path, since nix paths are
854 copied into the world-readable nix store.
858 extraShellConfig = mkOption {
861 description = "Extra configuration to merge into shell-config.yml";
864 puma.workers = mkOption {
867 apply = x: builtins.toString x;
869 The number of worker processes Puma should spawn. This
870 controls the amount of parallel Ruby code can be
871 executed. GitLab recommends `Number of CPU cores - 1`, but at least two.
874 Each worker consumes quite a bit of memory, so
875 be careful when increasing this.
880 puma.threadsMin = mkOption {
883 apply = x: builtins.toString x;
885 The minimum number of threads Puma should use per
889 Each thread consumes memory and contributes to Global VM
890 Lock contention, so be careful when increasing this.
895 puma.threadsMax = mkOption {
898 apply = x: builtins.toString x;
900 The maximum number of threads Puma should use per
901 worker. This limits how many threads Puma will automatically
902 spawn in response to requests. In contrast to workers,
903 threads will never be able to run Ruby code in parallel, but
904 give higher IO parallelism.
907 Each thread consumes memory and contributes to Global VM
908 Lock contention, so be careful when increasing this.
913 sidekiq.concurrency = mkOption {
914 type = with types; nullOr int;
917 How many processor threads to use for processing sidekiq background job queues. When null, the GitLab default is used.
919 See <https://docs.gitlab.com/ee/administration/sidekiq/extra_sidekiq_processes.html#manage-thread-counts-explicitly> for details.
923 sidekiq.memoryKiller.enable = mkOption {
927 Whether the Sidekiq MemoryKiller should be turned
928 on. MemoryKiller kills Sidekiq when its memory consumption
929 exceeds a certain limit.
931 See <https://docs.gitlab.com/ee/administration/operations/sidekiq_memory_killer.html>
936 sidekiq.memoryKiller.maxMemory = mkOption {
939 apply = x: builtins.toString (x * 1024);
941 The maximum amount of memory, in MiB, a Sidekiq worker is
942 allowed to consume before being killed.
946 sidekiq.memoryKiller.graceTime = mkOption {
949 apply = x: builtins.toString x;
951 The time MemoryKiller waits after noticing excessive memory
952 consumption before killing Sidekiq.
956 sidekiq.memoryKiller.shutdownWait = mkOption {
959 apply = x: builtins.toString x;
961 The time allowed for all jobs to finish before Sidekiq is
971 Enable rotation of log files.
975 frequency = mkOption {
978 description = "How often to rotate the logs.";
984 description = "How many rotations to keep.";
988 workhorse.config = mkOption {
991 example = literalExpression ''
993 object_storage.provider = "AWS";
994 object_storage.s3 = {
995 aws_access_key_id = "AKIAXXXXXXXXXXXXXXXX";
996 aws_secret_access_key = { _secret = "/var/keys/aws_secret_access_key"; };
1001 Configuration options to add to Workhorse's configuration
1005 <https://gitlab.com/gitlab-org/gitlab/-/blob/master/workhorse/config.toml.example>
1007 <https://docs.gitlab.com/ee/development/workhorse/configuration.html>
1008 for examples and option documentation.
1010 Options containing secret data should be set to an attribute
1011 set containing the attribute `_secret` - a string pointing
1012 to a file containing the value the option should be set
1013 to. See the example to get a better picture of this: in the
1014 resulting configuration file, the
1015 `object_storage.s3.aws_secret_access_key` key will be set to
1016 the contents of the {file}`/var/keys/aws_secret_access_key`
1021 extraConfig = mkOption {
1024 example = literalExpression ''
1027 default_projects_features = {
1033 auto_sign_in_with_provider = "openid_connect";
1034 allow_single_sign_on = ["openid_connect"];
1035 block_auto_created_users = false;
1038 name = "openid_connect";
1039 label = "OpenID Connect";
1041 name = "openid_connect";
1042 scope = ["openid" "profile"];
1043 response_type = "code";
1044 issuer = "https://keycloak.example.com/auth/realms/My%20Realm";
1046 client_auth_method = "query";
1047 uid_field = "preferred_username";
1049 identifier = "gitlab";
1050 secret = { _secret = "/var/keys/gitlab_oidc_secret"; };
1051 redirect_uri = "https://git.example.com/users/auth/openid_connect/callback";
1060 Extra options to be added under
1062 {file}`config/gitlab.yml`, as a nix attribute
1065 Options containing secret data should be set to an attribute
1066 set containing the attribute `_secret` - a
1067 string pointing to a file containing the value the option
1068 should be set to. See the example to get a better picture of
1069 this: in the resulting
1070 {file}`config/gitlab.yml` file, the
1071 `production.omniauth.providers[0].args.client_options.secret`
1072 key will be set to the contents of the
1073 {file}`/var/keys/gitlab_oidc_secret` file.
1079 config = mkIf cfg.enable {
1082 (cfg.registry.enable && versionAtLeast (getVersion cfg.packages.gitlab) "16.0.0" && cfg.registry.package == pkgs.docker-distribution)
1083 ''Support for container registries other than gitlab-container-registry has ended since GitLab 16.0.0 and is scheduled for removal in a future release.
1084 Please back up your data and migrate to the gitlab-container-registry package.''
1087 (versionAtLeast (getVersion cfg.packages.gitlab) "16.2.0" && versionOlder (getVersion cfg.packages.gitlab) "16.5.0")
1088 ''GitLab instances created or updated between versions [15.11.0, 15.11.2] have an incorrect database schema.
1089 Check the upstream documentation for a workaround: https://docs.gitlab.com/ee/update/versions/gitlab_16_changes.html#undefined-column-error-upgrading-to-162-or-later''
1095 assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.databaseUsername);
1096 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.'';
1099 assertion = (cfg.databaseHost != "") -> (cfg.databasePasswordFile != null);
1100 message = "When services.gitlab.databaseHost is customized, services.gitlab.databasePasswordFile must be set!";
1103 assertion = cfg.initialRootPasswordFile != null;
1104 message = "services.gitlab.initialRootPasswordFile must be set!";
1107 assertion = cfg.secrets.secretFile != null;
1108 message = "services.gitlab.secrets.secretFile must be set!";
1111 assertion = cfg.secrets.dbFile != null;
1112 message = "services.gitlab.secrets.dbFile must be set!";
1115 assertion = cfg.secrets.otpFile != null;
1116 message = "services.gitlab.secrets.otpFile must be set!";
1119 assertion = cfg.secrets.jwsFile != null;
1120 message = "services.gitlab.secrets.jwsFile must be set!";
1123 assertion = versionAtLeast postgresqlPackage.version "14.9";
1124 message = "PostgreSQL >= 14.9 is required to run GitLab 17. Follow the instructions in the manual section for upgrading PostgreSQL here: https://nixos.org/manual/nixos/stable/index.html#module-services-postgres-upgrading";
1128 environment.systemPackages = [ gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
1130 systemd.targets.gitlab = {
1131 description = "Common target for all GitLab services.";
1132 wantedBy = [ "multi-user.target" ];
1135 # Redis is required for the sidekiq queue runner.
1136 services.redis.servers.gitlab = {
1137 enable = mkDefault true;
1138 user = mkDefault cfg.user;
1139 unixSocket = mkDefault "/run/gitlab/redis.sock";
1140 unixSocketPerm = mkDefault 770;
1143 # We use postgres as the main data store.
1144 services.postgresql = optionalAttrs databaseActuallyCreateLocally {
1146 ensureUsers = singleton { name = cfg.databaseUsername; };
1149 # Enable rotation of log files
1150 services.logrotate = {
1151 enable = cfg.logrotate.enable;
1154 files = "${cfg.statePath}/log/*.log";
1155 su = "${cfg.user} ${cfg.group}";
1156 frequency = cfg.logrotate.frequency;
1157 rotate = cfg.logrotate.keep;
1158 copytruncate = true;
1164 # The postgresql module doesn't currently support concepts like
1165 # objects owners and extensions; for now we tack on what's needed
1167 systemd.services.gitlab-postgresql = let pgsql = config.services.postgresql; in mkIf databaseActuallyCreateLocally {
1168 after = [ "postgresql.service" ];
1169 bindsTo = [ "postgresql.service" ];
1170 wantedBy = [ "gitlab.target" ];
1171 partOf = [ "gitlab.target" ];
1180 psql --port=${toString pgsql.settings.port} "$@"
1183 PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
1184 current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
1185 if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
1186 PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
1187 if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
1188 echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
1191 touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
1192 PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
1193 rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
1195 PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
1196 PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS btree_gist;"
1200 User = pgsql.superUser;
1202 RemainAfterExit = true;
1206 systemd.services.gitlab-registry-cert = optionalAttrs cfg.registry.enable {
1207 path = with pkgs; [ openssl ];
1210 mkdir -p $(dirname ${cfg.registry.keyFile})
1211 mkdir -p $(dirname ${cfg.registry.certFile})
1212 openssl req -nodes -newkey rsa:4096 -keyout ${cfg.registry.keyFile} -out /tmp/registry-auth.csr -subj "/CN=${cfg.registry.issuer}"
1213 openssl x509 -in /tmp/registry-auth.csr -out ${cfg.registry.certFile} -req -signkey ${cfg.registry.keyFile} -days 3650
1214 chown ${cfg.user}:${cfg.group} $(dirname ${cfg.registry.keyFile})
1215 chown ${cfg.user}:${cfg.group} $(dirname ${cfg.registry.certFile})
1216 chown ${cfg.user}:${cfg.group} ${cfg.registry.keyFile}
1217 chown ${cfg.user}:${cfg.group} ${cfg.registry.certFile}
1221 ConditionPathExists = "!${cfg.registry.certFile}";
1225 # Ensure Docker Registry launches after the certificate generation job
1226 systemd.services.docker-registry = optionalAttrs cfg.registry.enable {
1227 wants = [ "gitlab-registry-cert.service" ];
1228 after = [ "gitlab-registry-cert.service" ];
1231 # Enable Docker Registry, if GitLab-Container Registry is enabled
1232 services.dockerRegistry = optionalAttrs cfg.registry.enable {
1234 enableDelete = true; # This must be true, otherwise GitLab won't manage it correctly
1235 package = cfg.registry.package;
1238 realm = "http${optionalString (cfg.https == true) "s"}://${cfg.host}/jwt/auth";
1239 service = cfg.registry.serviceName;
1240 issuer = cfg.registry.issuer;
1241 rootcertbundle = cfg.registry.certFile;
1246 # Use postfix to send out mails.
1247 services.postfix.enable = mkDefault (cfg.smtp.enable && cfg.smtp.address == "localhost");
1249 users.users.${cfg.user} =
1250 { group = cfg.group;
1251 home = "${cfg.statePath}/home";
1252 shell = "${pkgs.bash}/bin/bash";
1253 uid = config.ids.uids.gitlab;
1256 users.groups.${cfg.group}.gid = config.ids.gids.gitlab;
1258 systemd.tmpfiles.rules = [
1259 "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -"
1260 "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -"
1261 "z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -"
1262 "d ${cfg.backup.path} 0750 ${cfg.user} ${cfg.group} -"
1263 "d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -"
1264 "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -"
1265 "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -"
1266 "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -"
1267 "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -"
1268 "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -"
1269 "d ${cfg.statePath}/shell 0750 ${cfg.user} ${cfg.group} -"
1270 "d ${cfg.statePath}/tmp 0750 ${cfg.user} ${cfg.group} -"
1271 "d ${cfg.statePath}/tmp/pids 0750 ${cfg.user} ${cfg.group} -"
1272 "d ${cfg.statePath}/tmp/sockets 0750 ${cfg.user} ${cfg.group} -"
1273 "d ${cfg.statePath}/uploads 0700 ${cfg.user} ${cfg.group} -"
1274 "d ${cfg.statePath}/custom_hooks 0700 ${cfg.user} ${cfg.group} -"
1275 "d ${cfg.statePath}/custom_hooks/pre-receive.d 0700 ${cfg.user} ${cfg.group} -"
1276 "d ${cfg.statePath}/custom_hooks/post-receive.d 0700 ${cfg.user} ${cfg.group} -"
1277 "d ${cfg.statePath}/custom_hooks/update.d 0700 ${cfg.user} ${cfg.group} -"
1278 "d ${gitlabConfig.production.shared.path} 0750 ${cfg.user} ${cfg.group} -"
1279 "d ${gitlabConfig.production.shared.path}/artifacts 0750 ${cfg.user} ${cfg.group} -"
1280 "d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -"
1281 "d ${gitlabConfig.production.shared.path}/packages 0750 ${cfg.user} ${cfg.group} -"
1282 "d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -"
1283 "d ${gitlabConfig.production.shared.path}/registry 0750 ${cfg.user} ${cfg.group} -"
1284 "d ${gitlabConfig.production.shared.path}/terraform_state 0750 ${cfg.user} ${cfg.group} -"
1285 "d ${gitlabConfig.production.shared.path}/ci_secure_files 0750 ${cfg.user} ${cfg.group} -"
1286 "d ${gitlabConfig.production.shared.path}/external-diffs 0750 ${cfg.user} ${cfg.group} -"
1287 "L+ /run/gitlab/config - - - - ${cfg.statePath}/config"
1288 "L+ /run/gitlab/log - - - - ${cfg.statePath}/log"
1289 "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp"
1290 "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads"
1292 "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
1296 systemd.services.gitlab-config = {
1297 wantedBy = [ "gitlab.target" ];
1298 partOf = [ "gitlab.target" ];
1299 path = [ git ] ++ (with pkgs; [
1308 TimeoutSec = "infinity";
1309 Restart = "on-failure";
1310 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
1311 RemainAfterExit = true;
1314 preStartFullPrivileges = ''
1315 set -o errexit -o pipefail -o nounset
1316 shopt -s dotglob nullglob inherit_errexit
1318 chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/*
1319 if [[ -n "$(ls -A '${cfg.statePath}'/config/)" ]]; then
1320 chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/*
1323 in "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}";
1325 ExecStart = pkgs.writeShellScript "gitlab-config" ''
1326 set -o errexit -o pipefail -o nounset
1327 shopt -s inherit_errexit
1331 cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
1332 rm -rf ${cfg.statePath}/db/*
1333 rm -f ${cfg.statePath}/lib
1334 find '${cfg.statePath}/config/' -maxdepth 1 -mindepth 1 -type d -execdir rm -rf {} \;
1335 cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
1336 cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
1337 ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
1338 ln -sf ${cableYml} ${cfg.statePath}/config/cable.yml
1339 ln -sf ${resqueYml} ${cfg.statePath}/config/resque.yml
1341 ${cfg.packages.gitlab-shell}/bin/gitlab-shell-install
1343 ${optionalString cfg.smtp.enable ''
1344 install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
1345 ${optionalString (cfg.smtp.passwordFile != null) ''
1346 replace-secret '@smtpPassword@' '${cfg.smtp.passwordFile}' '${cfg.statePath}/config/initializers/smtp_settings.rb'
1353 openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
1354 ${optionalString cfg.pages.enable ''
1355 openssl rand -base64 32 > ${cfg.pages.settings.api-secret-key}
1358 rm -f '${cfg.statePath}/config/database.yml'
1360 ${lib.optionalString (cfg.databasePasswordFile != null) ''
1361 db_password="$(<'${cfg.databasePasswordFile}')"
1364 if [[ -z "$db_password" ]]; then
1365 >&2 echo "Database password was an empty string!"
1370 # GitLab expects the `production.main` section to be the first entry in the file.
1371 jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} '{
1373 ${lib.optionalString (cfg.databasePasswordFile != null) (
1374 builtins.concatStringsSep "\n " (
1375 [ ".production${lib.optionalString (gitlabVersionAtLeast "15.0") ".main"}.password = $ENV.db_password" ]
1376 ++ lib.optional (gitlabVersionAtLeast "15.9") "| .production.ci.password = $ENV.db_password"
1385 }' >'${cfg.statePath}/config/database.yml'
1387 ${utils.genJqSecretsReplacementSnippet
1389 "${cfg.statePath}/config/gitlab.yml"
1392 rm -f '${cfg.statePath}/config/secrets.yml'
1394 secret="$(<'${cfg.secrets.secretFile}')"
1395 db="$(<'${cfg.secrets.dbFile}')"
1396 otp="$(<'${cfg.secrets.otpFile}')"
1397 jws="$(<'${cfg.secrets.jwsFile}')"
1398 export secret db otp jws
1399 jq -n '{production: {secret_key_base: $ENV.secret,
1400 otp_key_base: $ENV.otp,
1401 db_key_base: $ENV.db,
1402 openid_connect_signing_key: $ENV.jws}}' \
1403 > '${cfg.statePath}/config/secrets.yml'
1406 # We remove potentially broken links to old gitlab-shell versions
1407 rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
1409 git config --global core.autocrlf "input"
1414 systemd.services.gitlab-db-config = {
1415 after = [ "gitlab-config.service" "gitlab-postgresql.service" "postgresql.service" ];
1416 wants = optional (cfg.databaseHost == "") "postgresql.service" ++ optional databaseActuallyCreateLocally "gitlab-postgresql.service";
1417 bindsTo = [ "gitlab-config.service" ];
1418 wantedBy = [ "gitlab.target" ];
1419 partOf = [ "gitlab.target" ];
1424 TimeoutSec = "infinity";
1425 Restart = "on-failure";
1426 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
1427 RemainAfterExit = true;
1429 ExecStart = pkgs.writeShellScript "gitlab-db-config" ''
1430 set -o errexit -o pipefail -o nounset
1431 shopt -s inherit_errexit
1434 initial_root_password="$(<'${cfg.initialRootPasswordFile}')"
1435 ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \
1436 GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null
1441 systemd.services.gitlab-sidekiq = {
1444 "redis-gitlab.service"
1445 "postgresql.service"
1446 "gitlab-config.service"
1447 "gitlab-db-config.service"
1450 "gitlab-config.service"
1451 "gitlab-db-config.service"
1453 wants = [ "redis-gitlab.service" ] ++ optional (cfg.databaseHost == "") "postgresql.service";
1454 wantedBy = [ "gitlab.target" ];
1455 partOf = [ "gitlab.target" ];
1456 environment = gitlabEnv // (optionalAttrs cfg.sidekiq.memoryKiller.enable {
1457 SIDEKIQ_MEMORY_KILLER_MAX_RSS = cfg.sidekiq.memoryKiller.maxMemory;
1458 SIDEKIQ_MEMORY_KILLER_GRACE_TIME = cfg.sidekiq.memoryKiller.graceTime;
1459 SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT = cfg.sidekiq.memoryKiller.shutdownWait;
1461 path = [ git ] ++ (with pkgs; [
1468 "${cfg.packages.gitlab}/share/gitlab/vendor/gems/sidekiq-${cfg.packages.gitlab.rubyEnv.gems.sidekiq.version}"
1470 # Needed for GitLab project imports
1474 procps # Sidekiq MemoryKiller
1480 TimeoutSec = "infinity";
1482 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
1483 ExecStart = utils.escapeSystemdExecArgs (
1485 "${cfg.packages.gitlab}/share/gitlab/bin/sidekiq-cluster"
1486 "*" # all queue groups
1487 ] ++ lib.optionals (cfg.sidekiq.concurrency != null) [
1488 "--concurrency" (toString cfg.sidekiq.concurrency)
1490 "--environment" "production"
1497 systemd.services.gitaly = {
1498 after = [ "network.target" "gitlab-config.service" ];
1499 bindsTo = [ "gitlab-config.service" ];
1500 wantedBy = [ "gitlab.target" ];
1501 partOf = [ "gitlab.target" ];
1502 path = [ git ] ++ (with pkgs; [
1511 TimeoutSec = "infinity";
1512 Restart = "on-failure";
1513 WorkingDirectory = gitlabEnv.HOME;
1514 RuntimeDirectory = "gitaly";
1515 ExecStart = "${cfg.packages.gitaly}/bin/gitaly ${gitalyToml}";
1519 services.gitlab.pages.settings = {
1520 api-secret-key = "${cfg.statePath}/gitlab_pages_secret";
1523 systemd.services.gitlab-pages =
1525 filteredConfig = filterAttrs (_: v: v != null) cfg.pages.settings;
1526 isSecret = v: isAttrs v && v ? _secret && isString v._secret;
1527 mkPagesKeyValue = lib.generators.toKeyValue {
1528 mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" rec {
1530 if isInt v then toString v
1531 else if isString v then v
1532 else if true == v then "true"
1533 else if false == v then "false"
1534 else if isSecret v then builtins.hashString "sha256" v._secret
1535 else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
1538 secretPaths = lib.catAttrs "_secret" (lib.collect isSecret filteredConfig);
1539 mkSecretReplacement = file: ''
1540 replace-secret ${lib.escapeShellArgs [ (builtins.hashString "sha256" file) file "/run/gitlab-pages/gitlab-pages.conf" ]}
1542 secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
1543 configFile = pkgs.writeText "gitlab-pages.conf" (mkPagesKeyValue filteredConfig);
1545 mkIf cfg.pages.enable {
1546 description = "GitLab static pages daemon";
1547 after = [ "network.target" "gitlab-config.service" "gitlab.service" ];
1548 bindsTo = [ "gitlab-config.service" "gitlab.service" ];
1549 wantedBy = [ "gitlab.target" ];
1550 partOf = [ "gitlab.target" ];
1559 TimeoutSec = "infinity";
1560 Restart = "on-failure";
1565 ExecStartPre = pkgs.writeShellScript "gitlab-pages-pre-start" ''
1566 set -o errexit -o pipefail -o nounset
1567 shopt -s dotglob nullglob inherit_errexit
1569 install -m u=rw ${configFile} /run/gitlab-pages/gitlab-pages.conf
1570 ${secretReplacements}
1572 ExecStart = "${cfg.packages.pages}/bin/gitlab-pages -config=/run/gitlab-pages/gitlab-pages.conf";
1573 WorkingDirectory = gitlabEnv.HOME;
1574 RuntimeDirectory = "gitlab-pages";
1575 RuntimeDirectoryMode = "0700";
1579 systemd.services.gitlab-workhorse = {
1580 after = [ "network.target" ];
1581 wantedBy = [ "gitlab.target" ];
1582 partOf = [ "gitlab.target" ];
1583 path = [ git ] ++ (with pkgs; [
1590 cfg.packages.gitlab-workhorse
1596 TimeoutSec = "infinity";
1597 Restart = "on-failure";
1598 WorkingDirectory = gitlabEnv.HOME;
1599 ExecStartPre = pkgs.writeShellScript "gitlab-workhorse-pre-start" ''
1600 set -o errexit -o pipefail -o nounset
1601 shopt -s dotglob nullglob inherit_errexit
1603 ${utils.genJqSecretsReplacementSnippet
1604 cfg.workhorse.config
1605 "${cfg.statePath}/config/gitlab-workhorse.json"}
1607 json2toml "${cfg.statePath}/config/gitlab-workhorse.json" "${cfg.statePath}/config/gitlab-workhorse.toml"
1608 rm "${cfg.statePath}/config/gitlab-workhorse.json"
1611 "${cfg.packages.gitlab-workhorse}/bin/${
1612 optionalString (lib.versionAtLeast (lib.getVersion cfg.packages.gitlab-workhorse) "16.10") "gitlab-"
1615 + "-listenNetwork unix "
1616 + "-listenAddr /run/gitlab/gitlab-workhorse.socket "
1617 + "-authSocket ${gitlabSocket} "
1618 + "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public "
1619 + "-config ${cfg.statePath}/config/gitlab-workhorse.toml "
1620 + "-secretPath ${cfg.statePath}/.gitlab_workhorse_secret";
1624 systemd.services.gitlab-mailroom = mkIf (gitlabConfig.production.incoming_email.enabled or false) {
1625 description = "GitLab incoming mail daemon";
1626 after = [ "network.target" "redis-gitlab.service" "gitlab-config.service" ];
1627 bindsTo = [ "gitlab-config.service" ];
1628 wantedBy = [ "gitlab.target" ];
1629 partOf = [ "gitlab.target" ];
1630 environment = gitlabEnv;
1633 TimeoutSec = "infinity";
1634 Restart = "on-failure";
1638 ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.statePath}/config/mail_room.yml";
1639 WorkingDirectory = gitlabEnv.HOME;
1643 systemd.services.gitlab = {
1645 "gitlab-workhorse.service"
1647 "redis-gitlab.service"
1648 "gitlab-config.service"
1649 "gitlab-db-config.service"
1652 "gitlab-config.service"
1653 "gitlab-db-config.service"
1655 wants = [ "redis-gitlab.service" ] ++ optional (cfg.databaseHost == "") "postgresql.service";
1656 requiredBy = [ "gitlab.target" ];
1657 partOf = [ "gitlab.target" ];
1658 environment = gitlabEnv;
1659 path = [ git ] ++ (with pkgs; [
1671 TimeoutSec = "infinity";
1672 Restart = "on-failure";
1673 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
1674 ExecStart = concatStringsSep " " [
1675 "${cfg.packages.gitlab.rubyEnv}/bin/bundle" "exec" "puma"
1677 "-C ${cfg.statePath}/config/puma.rb"
1678 "-w ${cfg.puma.workers}"
1679 "-t ${cfg.puma.threadsMin}:${cfg.puma.threadsMax}"
1685 systemd.services.gitlab-backup = {
1686 after = [ "gitlab.service" ];
1687 bindsTo = [ "gitlab.service" ];
1688 startAt = cfg.backup.startAt;
1690 RAILS_ENV = "production";
1692 } // optionalAttrs (stringLength cfg.backup.skip > 0) {
1693 SKIP = cfg.backup.skip;
1698 ExecStart = "${gitlab-rake}/bin/gitlab-rake gitlab:backup:create";
1704 meta.doc = ./gitlab.md;
1705 meta.maintainers = teams.gitlab.members;