python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / misc / gitlab.nix
blob13453d9cc7854ef51de2708c29691fd621f1553f
1 { config, lib, options, pkgs, utils, ... }:
3 with lib;
5 let
6   cfg = config.services.gitlab;
7   opt = options.services.gitlab;
9   toml = pkgs.formats.toml {};
10   yaml = pkgs.formats.yaml {};
12   ruby = cfg.packages.gitlab.ruby;
14   postgresqlPackage = if config.services.postgresql.enable then
15                         config.services.postgresql.package
16                       else
17                         pkgs.postgresql_12;
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       });
35   gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
36   gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
37   pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
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   };
54   # We only want to create a database if we're actually going to connect to it.
55   databaseActuallyCreateLocally = cfg.databaseCreateLocally && cfg.databaseHost == "";
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"
62     [git]
63     bin_path = "${gitPackage}/bin/git"
65     [gitaly-ruby]
66     dir = "${cfg.packages.gitaly.ruby}"
68     [gitlab-shell]
69     dir = "${cfg.packages.gitlab-shell}"
71     [hooks]
72     custom_hooks_dir = "${cfg.statePath}/custom_hooks"
74     [gitlab]
75     secret_file = "${cfg.statePath}/gitlab_shell_secret"
76     url = "http+unix://${pathUrlQuote gitlabSocket}"
78     [gitlab.http-settings]
79     self_signed_cert = false
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   '';
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   };
97   redisConfig.production.url = cfg.redisUrl;
99   cableYml = yaml.generate "cable.yml" {
100     production = {
101       adapter = "redis";
102       url = cfg.redisUrl;
103       channel_prefix = "gitlab_production";
104     };
105   };
107   pagesArgs = [
108     "-pages-domain" gitlabConfig.production.pages.host
109     "-pages-root" "${gitlabConfig.production.shared.path}/pages"
110   ] ++ cfg.pagesExtraArgs;
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   };
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;
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   ];
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   };
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   };
235   extraGitlabRb = pkgs.writeText "extra-gitlab.rb" cfg.extraGitlabRb;
237   smtpSettings = pkgs.writeText "gitlab-smtp-settings.rb" ''
238     if Rails.env.production?
239       Rails.application.config.action_mailer.delivery_method = :smtp
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   '';
257 in {
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   ];
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       };
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       };
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       };
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       };
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       };
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       };
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.
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       };
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       };
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       };
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       };
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       };
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.
387           Refer to <https://docs.gitlab.com/ee/raketasks/backup_restore.html#excluding-specific-directories-from-the-backup>
388           for more information.
389         '';
390       };
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             };
405             # The remote 'directory' to store your backups in.
406             # For S3, this would be the bucket name.
407             remote_directory = "my-gitlab-backups";
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;
413             # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
414             encryption = "AES256";
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.
424           Attributes specified here are added under
425           `production -> backup -> upload` in
426           {file}`config/gitlab.yml`.
427         '';
428       };
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       };
439       databasePasswordFile = mkOption {
440         type = with types; nullOr path;
441         default = null;
442         description = lib.mdDoc ''
443           File containing the GitLab database user password.
445           This should be a string, not a nix path, since nix paths are
446           copied into the world-readable nix store.
447         '';
448       };
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       };
461       databaseName = mkOption {
462         type = types.str;
463         default = "gitlab";
464         description = lib.mdDoc "GitLab database name.";
465       };
467       databaseUsername = mkOption {
468         type = types.str;
469         default = "gitlab";
470         description = lib.mdDoc "GitLab database user.";
471       };
473       databasePool = mkOption {
474         type = types.int;
475         default = 5;
476         description = lib.mdDoc "Database connection pool size.";
477       };
479       extraDatabaseConfig = mkOption {
480         type = types.attrs;
481         default = {};
482         description = lib.mdDoc "Extra configuration in config/database.yml.";
483       };
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       };
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       };
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       };
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       };
528       https = mkOption {
529         type = types.bool;
530         default = false;
531         description = lib.mdDoc "Whether gitlab prints URLs with https as scheme.";
532       };
534       user = mkOption {
535         type = types.str;
536         default = "gitlab";
537         description = lib.mdDoc "User to run gitlab and all related services.";
538       };
540       group = mkOption {
541         type = types.str;
542         default = "gitlab";
543         description = lib.mdDoc "Group to run gitlab and all related services.";
544       };
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       };
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.
561           This should be a string, not a nix path, since nix paths are
562           copied into the world-readable nix store.
563         '';
564       };
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       };
618       smtp = {
619         enable = mkOption {
620           type = types.bool;
621           default = false;
622           description = lib.mdDoc "Enable gitlab mail delivery over SMTP.";
623         };
625         address = mkOption {
626           type = types.str;
627           default = "localhost";
628           description = lib.mdDoc "Address of the SMTP server for GitLab.";
629         };
631         port = mkOption {
632           type = types.int;
633           default = 25;
634           description = lib.mdDoc "Port of the SMTP server for GitLab.";
635         };
637         username = mkOption {
638           type = with types; nullOr str;
639           default = null;
640           description = lib.mdDoc "Username of the SMTP server for GitLab.";
641         };
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.
649             This should be a string, not a nix path, since nix paths
650             are copied into the world-readable nix store.
651           '';
652         };
654         domain = mkOption {
655           type = types.str;
656           default = "localhost";
657           description = lib.mdDoc "HELO domain to use for outgoing mail.";
658         };
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         };
666         enableStartTLSAuto = mkOption {
667           type = types.bool;
668           default = true;
669           description = lib.mdDoc "Whether to try to use StartTLS.";
670         };
672         tls = mkOption {
673           type = types.bool;
674           default = false;
675           description = lib.mdDoc "Whether to use TLS wrapper-mode.";
676         };
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       };
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       };
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.
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.
702           This should be a string, not a nix path, since nix paths are
703           copied into the world-readable nix store.
704         '';
705       };
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.
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.
718           This should be a string, not a nix path, since nix paths are
719           copied into the world-readable nix store.
720         '';
721       };
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.
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.
734           This should be a string, not a nix path, since nix paths are
735           copied into the world-readable nix store.
736         '';
737       };
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.
747           Make sure the secret is an RSA private key in PEM format. You can
748           generate one with
750           openssl genrsa 2048
752           This should be a string, not a nix path, since nix paths are
753           copied into the world-readable nix store.
754         '';
755       };
757       extraShellConfig = mkOption {
758         type = types.attrs;
759         default = {};
760         description = lib.mdDoc "Extra configuration to merge into shell-config.yml";
761       };
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.
772           ::: {.note}
773           Each worker consumes quite a bit of memory, so
774           be careful when increasing this.
775           :::
776         '';
777       };
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.
787           ::: {.note}
788           Each thread consumes memory and contributes to Global VM
789           Lock contention, so be careful when increasing this.
790           :::
791         '';
792       };
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.
805           ::: {.note}
806           Each thread consumes memory and contributes to Global VM
807           Lock contention, so be careful when increasing this.
808           :::
809         '';
810       };
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.
820           See <https://docs.gitlab.com/ee/administration/operations/sidekiq_memory_killer.html>
821           for details.
822         '';
823       };
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       };
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       };
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       };
855       logrotate = {
856         enable = mkOption {
857           type = types.bool;
858           default = true;
859           description = lib.mdDoc ''
860             Enable rotation of log files.
861           '';
862         };
864         frequency = mkOption {
865           type = types.str;
866           default = "daily";
867           description = lib.mdDoc "How often to rotate the logs.";
868         };
870         keep = mkOption {
871           type = types.int;
872           default = 30;
873           description = lib.mdDoc "How many rotations to keep.";
874         };
875       };
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.
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.
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       };
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.
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   };
968   config = mkIf cfg.enable {
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     ];
1005     environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
1007     systemd.targets.gitlab = {
1008       description = "Common target for all GitLab services.";
1009       wantedBy = [ "multi-user.target" ];
1010     };
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     };
1020     # We use postgres as the main data store.
1021     services.postgresql = optionalAttrs databaseActuallyCreateLocally {
1022       enable = true;
1023       ensureUsers = singleton { name = cfg.databaseUsername; };
1024     };
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     };
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
1056         PSQL() {
1057             psql --port=${toString pgsql.port} "$@"
1058         }
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       '';
1076       serviceConfig = {
1077         User = pgsql.superUser;
1078         Type = "oneshot";
1079         RemainAfterExit = true;
1080       };
1081     };
1083     systemd.services.gitlab-registry-cert = optionalAttrs cfg.registry.enable {
1084       path = with pkgs; [ openssl ];
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       '';
1097       unitConfig = {
1098         ConditionPathExists = "!${cfg.registry.certFile}";
1099       };
1100     };
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     };
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     };
1122     # Use postfix to send out mails.
1123     services.postfix.enable = mkDefault (cfg.smtp.enable && cfg.smtp.address == "localhost");
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       };
1132     users.groups.${cfg.group}.gid = config.ids.gids.gitlab;
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"
1166       "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
1167     ];
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;
1188         ExecStartPre = let
1189           preStartFullPrivileges = ''
1190             set -o errexit -o pipefail -o nounset
1191             shopt -s dotglob nullglob inherit_errexit
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}";
1200         ExecStart = pkgs.writeShellScript "gitlab-config" ''
1201           set -o errexit -o pipefail -o nounset
1202           shopt -s inherit_errexit
1204           umask u=rwx,g=rx,o=
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
1215           ${cfg.packages.gitlab-shell}/bin/install
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           ''}
1224           (
1225             umask u=rwx,g=,o=
1227             openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
1229             rm -f '${cfg.statePath}/config/database.yml'
1231             ${if cfg.databasePasswordFile != null then ''
1232                 db_password="$(<'${cfg.databasePasswordFile}')"
1233                 export db_password
1235                 if [[ -z "$db_password" ]]; then
1236                   >&2 echo "Database password was an empty string!"
1237                   exit 1
1238                 fi
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             }
1250             ${utils.genJqSecretsReplacementSnippet
1251                 gitlabConfig
1252                 "${cfg.statePath}/config/gitlab.yml"
1253             }
1255             rm -f '${cfg.statePath}/config/secrets.yml'
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           )
1269           # We remove potentially broken links to old gitlab-shell versions
1270           rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
1272           git config --global core.autocrlf "input"
1273         '';
1274       };
1275     };
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;
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=
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     };
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
1334         # Needed for GitLab project imports
1335         gnutar
1336         gzip
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     };
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     };
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" ];
1383       path = [ pkgs.unzip ];
1385       serviceConfig = {
1386         Type = "simple";
1387         TimeoutSec = "infinity";
1388         Restart = "on-failure";
1390         User = cfg.user;
1391         Group = cfg.group;
1393         ExecStart = "${cfg.packages.pages}/bin/gitlab-pages ${escapeShellArgs pagesArgs}";
1394         WorkingDirectory = gitlabEnv.HOME;
1395       };
1396     };
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
1422           ${utils.genJqSecretsReplacementSnippet
1423               cfg.workhorse.config
1424               "${cfg.statePath}/config/gitlab-workhorse.json"}
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     };
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";
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     };
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       };
1500     };
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     };
1519   };
1521   meta.doc = ./gitlab.xml;