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