at 17.09-beta 19 kB view raw
1{ config, lib, pkgs, ... }: 2 3# TODO: support non-postgresql 4 5with lib; 6 7let 8 cfg = config.services.gitlab; 9 10 ruby = cfg.packages.gitlab.ruby; 11 bundler = pkgs.bundler; 12 13 gemHome = "${cfg.packages.gitlab.env}/${ruby.gemPath}"; 14 15 gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket"; 16 pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url; 17 18 databaseYml = '' 19 production: 20 adapter: postgresql 21 database: ${cfg.databaseName} 22 host: ${cfg.databaseHost} 23 password: ${cfg.databasePassword} 24 username: ${cfg.databaseUsername} 25 encoding: utf8 26 ''; 27 28 gitlabShellYml = '' 29 user: ${cfg.user} 30 gitlab_url: "http+unix://${pathUrlQuote gitlabSocket}" 31 http_settings: 32 self_signed_cert: false 33 repos_path: "${cfg.statePath}/repositories" 34 secret_file: "${cfg.statePath}/config/gitlab_shell_secret" 35 log_file: "${cfg.statePath}/log/gitlab-shell.log" 36 redis: 37 bin: ${pkgs.redis}/bin/redis-cli 38 host: 127.0.0.1 39 port: 6379 40 database: 0 41 namespace: resque:gitlab 42 ''; 43 44 secretsYml = '' 45 production: 46 secret_key_base: ${cfg.secrets.secret} 47 otp_key_base: ${cfg.secrets.otp} 48 db_key_base: ${cfg.secrets.db} 49 ''; 50 51 gitlabConfig = { 52 # These are the default settings from config/gitlab.example.yml 53 production = flip recursiveUpdate cfg.extraConfig { 54 gitlab = { 55 host = cfg.host; 56 port = cfg.port; 57 https = cfg.https; 58 user = cfg.user; 59 email_enabled = true; 60 email_display_name = "GitLab"; 61 email_reply_to = "noreply@localhost"; 62 default_theme = 2; 63 default_projects_features = { 64 issues = true; 65 merge_requests = true; 66 wiki = true; 67 snippets = true; 68 builds = true; 69 container_registry = true; 70 }; 71 }; 72 repositories.storages.default = "${cfg.statePath}/repositories"; 73 artifacts.enabled = true; 74 lfs.enabled = true; 75 gravatar.enabled = true; 76 cron_jobs = { }; 77 gitlab_ci.builds_path = "${cfg.statePath}/builds"; 78 ldap.enabled = false; 79 omniauth.enabled = false; 80 shared.path = "${cfg.statePath}/shared"; 81 backup.path = "${cfg.backupPath}"; 82 gitlab_shell = { 83 path = "${cfg.packages.gitlab-shell}"; 84 hooks_path = "${cfg.statePath}/shell/hooks"; 85 secret_file = "${cfg.statePath}/config/gitlab_shell_secret"; 86 upload_pack = true; 87 receive_pack = true; 88 }; 89 git = { 90 bin_path = "git"; 91 max_size = 20971520; # 20MB 92 timeout = 10; 93 }; 94 extra = {}; 95 }; 96 }; 97 98 gitlabEnv = { 99 HOME = "${cfg.statePath}/home"; 100 GEM_HOME = gemHome; 101 BUNDLE_GEMFILE = "${cfg.packages.gitlab}/share/gitlab/Gemfile"; 102 UNICORN_PATH = "${cfg.statePath}/"; 103 GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/"; 104 GITLAB_STATE_PATH = "${cfg.statePath}"; 105 GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads"; 106 GITLAB_LOG_PATH = "${cfg.statePath}/log"; 107 GITLAB_SHELL_PATH = "${cfg.packages.gitlab-shell}"; 108 GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/shell/config.yml"; 109 GITLAB_SHELL_SECRET_PATH = "${cfg.statePath}/config/gitlab_shell_secret"; 110 GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/shell/hooks"; 111 RAILS_ENV = "production"; 112 }; 113 114 unicornConfig = builtins.readFile ./defaultUnicornConfig.rb; 115 116 gitlab-rake = pkgs.stdenv.mkDerivation rec { 117 name = "gitlab-rake"; 118 buildInputs = [ cfg.packages.gitlab cfg.packages.gitlab.env pkgs.makeWrapper ]; 119 phases = "installPhase fixupPhase"; 120 buildPhase = ""; 121 installPhase = '' 122 mkdir -p $out/bin 123 makeWrapper ${cfg.packages.gitlab.env}/bin/bundle $out/bin/gitlab-bundle \ 124 ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \ 125 --set GITLAB_CONFIG_PATH '${cfg.statePath}/config' \ 126 --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip config.services.postgresql.package ]}:$PATH' \ 127 --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \ 128 --run 'cd ${cfg.packages.gitlab}/share/gitlab' 129 makeWrapper $out/bin/gitlab-bundle $out/bin/gitlab-rake \ 130 --add-flags "exec rake" 131 ''; 132 }; 133 134 smtpSettings = pkgs.writeText "gitlab-smtp-settings.rb" '' 135 if Rails.env.production? 136 Rails.application.config.action_mailer.delivery_method = :smtp 137 138 ActionMailer::Base.delivery_method = :smtp 139 ActionMailer::Base.smtp_settings = { 140 address: "${cfg.smtp.address}", 141 port: ${toString cfg.smtp.port}, 142 ${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''} 143 ${optionalString (cfg.smtp.password != null) ''password: "${cfg.smtp.password}",''} 144 domain: "${cfg.smtp.domain}", 145 ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"} 146 enable_starttls_auto: ${toString cfg.smtp.enableStartTLSAuto}, 147 openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}' 148 } 149 end 150 ''; 151 152in { 153 154 options = { 155 services.gitlab = { 156 enable = mkOption { 157 type = types.bool; 158 default = false; 159 description = '' 160 Enable the gitlab service. 161 ''; 162 }; 163 164 packages.gitlab = mkOption { 165 type = types.package; 166 default = pkgs.gitlab; 167 defaultText = "pkgs.gitlab"; 168 description = "Reference to the gitlab package"; 169 }; 170 171 packages.gitlab-shell = mkOption { 172 type = types.package; 173 default = pkgs.gitlab-shell; 174 defaultText = "pkgs.gitlab-shell"; 175 description = "Reference to the gitlab-shell package"; 176 }; 177 178 packages.gitlab-workhorse = mkOption { 179 type = types.package; 180 default = pkgs.gitlab-workhorse; 181 defaultText = "pkgs.gitlab-workhorse"; 182 description = "Reference to the gitlab-workhorse package"; 183 }; 184 185 statePath = mkOption { 186 type = types.str; 187 default = "/var/gitlab/state"; 188 description = "Gitlab state directory, logs are stored here."; 189 }; 190 191 backupPath = mkOption { 192 type = types.str; 193 default = cfg.statePath + "/backup"; 194 description = "Gitlab path for backups."; 195 }; 196 197 databaseHost = mkOption { 198 type = types.str; 199 default = "127.0.0.1"; 200 description = "Gitlab database hostname."; 201 }; 202 203 databasePassword = mkOption { 204 type = types.str; 205 default = ""; 206 description = "Gitlab database user password."; 207 }; 208 209 databaseName = mkOption { 210 type = types.str; 211 default = "gitlab"; 212 description = "Gitlab database name."; 213 }; 214 215 databaseUsername = mkOption { 216 type = types.str; 217 default = "gitlab"; 218 description = "Gitlab database user."; 219 }; 220 221 host = mkOption { 222 type = types.str; 223 default = config.networking.hostName; 224 description = "Gitlab host name. Used e.g. for copy-paste URLs."; 225 }; 226 227 port = mkOption { 228 type = types.int; 229 default = 8080; 230 description = '' 231 Gitlab server port for copy-paste URLs, e.g. 80 or 443 if you're 232 service over https. 233 ''; 234 }; 235 236 https = mkOption { 237 type = types.bool; 238 default = false; 239 description = "Whether gitlab prints URLs with https as scheme."; 240 }; 241 242 user = mkOption { 243 type = types.str; 244 default = "gitlab"; 245 description = "User to run gitlab and all related services."; 246 }; 247 248 group = mkOption { 249 type = types.str; 250 default = "gitlab"; 251 description = "Group to run gitlab and all related services."; 252 }; 253 254 initialRootEmail = mkOption { 255 type = types.str; 256 default = "admin@local.host"; 257 description = '' 258 Initial email address of the root account if this is a new install. 259 ''; 260 }; 261 262 initialRootPassword = mkOption { 263 type = types.str; 264 default = "UseNixOS!"; 265 description = '' 266 Initial password of the root account if this is a new install. 267 ''; 268 }; 269 270 smtp = { 271 enable = mkOption { 272 type = types.bool; 273 default = false; 274 description = "Enable gitlab mail delivery over SMTP."; 275 }; 276 277 address = mkOption { 278 type = types.str; 279 default = "localhost"; 280 description = "Address of the SMTP server for Gitlab."; 281 }; 282 283 port = mkOption { 284 type = types.int; 285 default = 465; 286 description = "Port of the SMTP server for Gitlab."; 287 }; 288 289 username = mkOption { 290 type = types.nullOr types.str; 291 default = null; 292 description = "Username of the SMTP server for Gitlab."; 293 }; 294 295 password = mkOption { 296 type = types.nullOr types.str; 297 default = null; 298 description = "Password of the SMTP server for Gitlab."; 299 }; 300 301 domain = mkOption { 302 type = types.str; 303 default = "localhost"; 304 description = "HELO domain to use for outgoing mail."; 305 }; 306 307 authentication = mkOption { 308 type = types.nullOr types.str; 309 default = null; 310 description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; 311 }; 312 313 enableStartTLSAuto = mkOption { 314 type = types.bool; 315 default = true; 316 description = "Whether to try to use StartTLS."; 317 }; 318 319 opensslVerifyMode = mkOption { 320 type = types.str; 321 default = "peer"; 322 description = "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; 323 }; 324 }; 325 326 secrets.secret = mkOption { 327 type = types.str; 328 description = '' 329 The secret is used to encrypt variables in the DB. If 330 you change or lose this key you will be unable to access variables 331 stored in database. 332 333 Make sure the secret is at least 30 characters and all random, 334 no regular words or you'll be exposed to dictionary attacks. 335 ''; 336 }; 337 338 secrets.db = mkOption { 339 type = types.str; 340 description = '' 341 The secret is used to encrypt variables in the DB. If 342 you change or lose this key you will be unable to access variables 343 stored in database. 344 345 Make sure the secret is at least 30 characters and all random, 346 no regular words or you'll be exposed to dictionary attacks. 347 ''; 348 }; 349 350 secrets.otp = mkOption { 351 type = types.str; 352 description = '' 353 The secret is used to encrypt secrets for OTP tokens. If 354 you change or lose this key, users which have 2FA enabled for login 355 won't be able to login anymore. 356 357 Make sure the secret is at least 30 characters and all random, 358 no regular words or you'll be exposed to dictionary attacks. 359 ''; 360 }; 361 362 extraConfig = mkOption { 363 type = types.attrs; 364 default = {}; 365 example = { 366 gitlab = { 367 default_projects_features = { 368 builds = false; 369 }; 370 }; 371 }; 372 description = '' 373 Extra options to be merged into config/gitlab.yml as nix 374 attribute set. 375 ''; 376 }; 377 }; 378 }; 379 380 config = mkIf cfg.enable { 381 382 environment.systemPackages = [ pkgs.git gitlab-rake cfg.packages.gitlab-shell ]; 383 384 assertions = [ 385 { assertion = cfg.databasePassword != ""; 386 message = "databasePassword must be set"; 387 } 388 ]; 389 390 # Redis is required for the sidekiq queue runner. 391 services.redis.enable = mkDefault true; 392 # We use postgres as the main data store. 393 services.postgresql.enable = mkDefault true; 394 # Use postfix to send out mails. 395 services.postfix.enable = mkDefault true; 396 397 users.extraUsers = [ 398 { name = cfg.user; 399 group = cfg.group; 400 home = "${cfg.statePath}/home"; 401 shell = "${pkgs.bash}/bin/bash"; 402 uid = config.ids.uids.gitlab; 403 } 404 ]; 405 406 users.extraGroups = [ 407 { name = cfg.group; 408 gid = config.ids.gids.gitlab; 409 } 410 ]; 411 412 systemd.services.gitlab-sidekiq = { 413 after = [ "network.target" "redis.service" ]; 414 wantedBy = [ "multi-user.target" ]; 415 partOf = [ "gitlab.service" ]; 416 environment = gitlabEnv; 417 path = with pkgs; [ 418 config.services.postgresql.package 419 gitAndTools.git 420 ruby 421 openssh 422 nodejs 423 ]; 424 serviceConfig = { 425 Type = "simple"; 426 User = cfg.user; 427 Group = cfg.group; 428 TimeoutSec = "300"; 429 Restart = "on-failure"; 430 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; 431 ExecStart="${cfg.packages.gitlab.env}/bin/bundle exec \"sidekiq -C \"${cfg.packages.gitlab}/share/gitlab/config/sidekiq_queues.yml\" -e production -P ${cfg.statePath}/tmp/sidekiq.pid\""; 432 }; 433 }; 434 435 systemd.services.gitlab-workhorse = { 436 after = [ "network.target" "gitlab.service" ]; 437 wantedBy = [ "multi-user.target" ]; 438 environment.HOME = gitlabEnv.HOME; 439 environment.GITLAB_SHELL_CONFIG_PATH = gitlabEnv.GITLAB_SHELL_CONFIG_PATH; 440 path = with pkgs; [ 441 gitAndTools.git 442 gnutar 443 gzip 444 openssh 445 gitlab-workhorse 446 ]; 447 preStart = '' 448 mkdir -p /run/gitlab 449 chown ${cfg.user}:${cfg.group} /run/gitlab 450 ''; 451 serviceConfig = { 452 PermissionsStartOnly = true; # preStart must be run as root 453 Type = "simple"; 454 User = cfg.user; 455 Group = cfg.group; 456 TimeoutSec = "300"; 457 Restart = "on-failure"; 458 WorkingDirectory = gitlabEnv.HOME; 459 ExecStart = 460 "${cfg.packages.gitlab-workhorse}/bin/gitlab-workhorse " 461 + "-listenUmask 0 " 462 + "-listenNetwork unix " 463 + "-listenAddr /run/gitlab/gitlab-workhorse.socket " 464 + "-authSocket ${gitlabSocket} " 465 + "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public " 466 + "-secretPath ${cfg.packages.gitlab}/share/gitlab/.gitlab_workhorse_secret"; 467 }; 468 }; 469 470 systemd.services.gitlab = { 471 after = [ "network.target" "postgresql.service" "redis.service" ]; 472 requires = [ "gitlab-sidekiq.service" ]; 473 wantedBy = [ "multi-user.target" ]; 474 environment = gitlabEnv; 475 path = with pkgs; [ 476 config.services.postgresql.package 477 gitAndTools.git 478 openssh 479 nodejs 480 ]; 481 preStart = '' 482 mkdir -p ${cfg.backupPath} 483 mkdir -p ${cfg.statePath}/builds 484 mkdir -p ${cfg.statePath}/repositories 485 mkdir -p ${gitlabConfig.production.shared.path}/artifacts 486 mkdir -p ${gitlabConfig.production.shared.path}/lfs-objects 487 mkdir -p ${gitlabConfig.production.shared.path}/pages 488 mkdir -p ${cfg.statePath}/log 489 mkdir -p ${cfg.statePath}/shell 490 mkdir -p ${cfg.statePath}/tmp/pids 491 mkdir -p ${cfg.statePath}/tmp/sockets 492 493 rm -rf ${cfg.statePath}/config ${cfg.statePath}/shell/hooks 494 mkdir -p ${cfg.statePath}/config ${cfg.statePath}/shell 495 496 tr -dc A-Za-z0-9 < /dev/urandom | head -c 32 > ${cfg.statePath}/config/gitlab_shell_secret 497 498 # The uploads directory is hardcoded somewhere deep in rails. It is 499 # symlinked in the gitlab package to /run/gitlab/uploads to make it 500 # configurable 501 mkdir -p /run/gitlab 502 mkdir -p ${cfg.statePath}/uploads 503 ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads 504 chown -R ${cfg.user}:${cfg.group} /run/gitlab 505 506 # Prepare home directory 507 mkdir -p ${gitlabEnv.HOME}/.ssh 508 touch ${gitlabEnv.HOME}/.ssh/authorized_keys 509 chown -R ${cfg.user}:${cfg.group} ${gitlabEnv.HOME}/ 510 chmod -R u+rwX,go-rwx+X ${gitlabEnv.HOME}/ 511 512 cp -rf ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config 513 ${optionalString cfg.smtp.enable '' 514 ln -sf ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb 515 ''} 516 ln -sf ${cfg.statePath}/config /run/gitlab/config 517 cp ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION 518 519 # JSON is a subset of YAML 520 ln -fs ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml 521 ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.statePath}/config/database.yml 522 ln -fs ${pkgs.writeText "secrets.yml" secretsYml} ${cfg.statePath}/config/secrets.yml 523 ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.statePath}/config/unicorn.rb 524 525 chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/ 526 chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/ 527 528 # Install the shell required to push repositories 529 ln -fs ${pkgs.writeText "config.yml" gitlabShellYml} "$GITLAB_SHELL_CONFIG_PATH" 530 ln -fs ${cfg.packages.gitlab-shell}/hooks "$GITLAB_SHELL_HOOKS_PATH" 531 ${cfg.packages.gitlab-shell}/bin/install 532 533 if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then 534 if ! test -e "${cfg.statePath}/db-created"; then 535 psql postgres -c "CREATE ROLE ${cfg.databaseUsername} WITH LOGIN NOCREATEDB NOCREATEROLE NOCREATEUSER ENCRYPTED PASSWORD '${cfg.databasePassword}'" 536 ${config.services.postgresql.package}/bin/createdb --owner ${cfg.databaseUsername} ${cfg.databaseName} || true 537 touch "${cfg.statePath}/db-created" 538 fi 539 fi 540 541 # enable required pg_trgm extension for gitlab 542 psql gitlab -c "CREATE EXTENSION IF NOT EXISTS pg_trgm" 543 # Always do the db migrations just to be sure the database is up-to-date 544 ${gitlab-rake}/bin/gitlab-rake db:migrate RAILS_ENV=production 545 546 # The gitlab:setup task is horribly broken somehow, the db:migrate 547 # task above and the db:seed_fu below will do the same for setting 548 # up the initial database 549 if ! test -e "${cfg.statePath}/db-seeded"; then 550 ${gitlab-rake}/bin/gitlab-rake db:seed_fu RAILS_ENV=production \ 551 GITLAB_ROOT_PASSWORD="${cfg.initialRootPassword}" GITLAB_ROOT_EMAIL="${cfg.initialRootEmail}" 552 touch "${cfg.statePath}/db-seeded" 553 fi 554 555 # Change permissions in the last step because some of the 556 # intermediary scripts like to create directories as root. 557 chown -R ${cfg.user}:${cfg.group} ${cfg.statePath} 558 chmod -R u+rwX,go-rwx+X ${cfg.statePath} 559 ''; 560 561 serviceConfig = { 562 PermissionsStartOnly = true; # preStart must be run as root 563 Type = "simple"; 564 User = cfg.user; 565 Group = cfg.group; 566 TimeoutSec = "300"; 567 Restart = "on-failure"; 568 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; 569 ExecStart = "${cfg.packages.gitlab.env}/bin/bundle exec \"unicorn -c ${cfg.statePath}/config/unicorn.rb -E production\""; 570 }; 571 572 }; 573 574 }; 575 576 meta.doc = ./gitlab.xml; 577 578}