at 16.09-beta 18 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 description = "Reference to the gitlab package"; 168 }; 169 170 packages.gitlab-shell = mkOption { 171 type = types.package; 172 default = pkgs.gitlab-shell; 173 description = "Reference to the gitlab-shell package"; 174 }; 175 176 packages.gitlab-workhorse = mkOption { 177 type = types.package; 178 default = pkgs.gitlab-workhorse; 179 description = "Reference to the gitlab-workhorse package"; 180 }; 181 182 statePath = mkOption { 183 type = types.str; 184 default = "/var/gitlab/state"; 185 description = "Gitlab state directory, logs are stored here."; 186 }; 187 188 backupPath = mkOption { 189 type = types.str; 190 default = cfg.statePath + "/backup"; 191 description = "Gitlab path for backups."; 192 }; 193 194 databaseHost = mkOption { 195 type = types.str; 196 default = "127.0.0.1"; 197 description = "Gitlab database hostname."; 198 }; 199 200 databasePassword = mkOption { 201 type = types.str; 202 default = ""; 203 description = "Gitlab database user password."; 204 }; 205 206 databaseName = mkOption { 207 type = types.str; 208 default = "gitlab"; 209 description = "Gitlab database name."; 210 }; 211 212 databaseUsername = mkOption { 213 type = types.str; 214 default = "gitlab"; 215 description = "Gitlab database user."; 216 }; 217 218 host = mkOption { 219 type = types.str; 220 default = config.networking.hostName; 221 description = "Gitlab host name. Used e.g. for copy-paste URLs."; 222 }; 223 224 port = mkOption { 225 type = types.int; 226 default = 8080; 227 description = '' 228 Gitlab server port for copy-paste URLs, e.g. 80 or 443 if you're 229 service over https. 230 ''; 231 }; 232 233 https = mkOption { 234 type = types.bool; 235 default = false; 236 description = "Whether gitlab prints URLs with https as scheme."; 237 }; 238 239 user = mkOption { 240 type = types.str; 241 default = "gitlab"; 242 description = "User to run gitlab and all related services."; 243 }; 244 245 group = mkOption { 246 type = types.str; 247 default = "gitlab"; 248 description = "Group to run gitlab and all related services."; 249 }; 250 251 initialRootEmail = mkOption { 252 type = types.str; 253 default = "admin@local.host"; 254 description = '' 255 Initial email address of the root account if this is a new install. 256 ''; 257 }; 258 259 initialRootPassword = mkOption { 260 type = types.str; 261 default = "UseNixOS!"; 262 description = '' 263 Initial password of the root account if this is a new install. 264 ''; 265 }; 266 267 smtp = { 268 enable = mkOption { 269 type = types.bool; 270 default = false; 271 description = "Enable gitlab mail delivery over SMTP."; 272 }; 273 274 address = mkOption { 275 type = types.str; 276 default = "localhost"; 277 description = "Address of the SMTP server for Gitlab."; 278 }; 279 280 port = mkOption { 281 type = types.int; 282 default = 465; 283 description = "Port of the SMTP server for Gitlab."; 284 }; 285 286 username = mkOption { 287 type = types.nullOr types.str; 288 default = null; 289 description = "Username of the SMTP server for Gitlab."; 290 }; 291 292 password = mkOption { 293 type = types.nullOr types.str; 294 default = null; 295 description = "Password of the SMTP server for Gitlab."; 296 }; 297 298 domain = mkOption { 299 type = types.str; 300 default = "localhost"; 301 description = "HELO domain to use for outgoing mail."; 302 }; 303 304 authentication = mkOption { 305 type = types.nullOr types.str; 306 default = null; 307 description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; 308 }; 309 310 enableStartTLSAuto = mkOption { 311 type = types.bool; 312 default = true; 313 description = "Whether to try to use StartTLS."; 314 }; 315 316 opensslVerifyMode = mkOption { 317 type = types.str; 318 default = "peer"; 319 description = "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; 320 }; 321 }; 322 323 secrets.secret = mkOption { 324 type = types.str; 325 description = '' 326 The secret is used to encrypt variables in the DB. If 327 you change or lose this key you will be unable to access variables 328 stored in database. 329 330 Make sure the secret is at least 30 characters and all random, 331 no regular words or you'll be exposed to dictionary attacks. 332 ''; 333 }; 334 335 secrets.db = mkOption { 336 type = types.str; 337 description = '' 338 The secret is used to encrypt variables in the DB. If 339 you change or lose this key you will be unable to access variables 340 stored in database. 341 342 Make sure the secret is at least 30 characters and all random, 343 no regular words or you'll be exposed to dictionary attacks. 344 ''; 345 }; 346 347 secrets.otp = mkOption { 348 type = types.str; 349 description = '' 350 The secret is used to encrypt secrets for OTP tokens. If 351 you change or lose this key, users which have 2FA enabled for login 352 won't be able to login anymore. 353 354 Make sure the secret is at least 30 characters and all random, 355 no regular words or you'll be exposed to dictionary attacks. 356 ''; 357 }; 358 359 extraConfig = mkOption { 360 type = types.attrs; 361 default = {}; 362 example = { 363 gitlab = { 364 default_projects_features = { 365 builds = false; 366 }; 367 }; 368 }; 369 description = '' 370 Extra options to be merged into config/gitlab.yml as nix 371 attribute set. 372 ''; 373 }; 374 }; 375 }; 376 377 config = mkIf cfg.enable { 378 379 environment.systemPackages = [ pkgs.git gitlab-rake cfg.packages.gitlab-shell ]; 380 381 assertions = [ 382 { assertion = cfg.databasePassword != ""; 383 message = "databasePassword must be set"; 384 } 385 ]; 386 387 # Redis is required for the sidekiq queue runner. 388 services.redis.enable = mkDefault true; 389 # We use postgres as the main data store. 390 services.postgresql.enable = mkDefault true; 391 # Use postfix to send out mails. 392 services.postfix.enable = mkDefault true; 393 394 users.extraUsers = [ 395 { name = cfg.user; 396 group = cfg.group; 397 home = "${cfg.statePath}/home"; 398 shell = "${pkgs.bash}/bin/bash"; 399 uid = config.ids.uids.gitlab; 400 } 401 ]; 402 403 users.extraGroups = [ 404 { name = cfg.group; 405 gid = config.ids.gids.gitlab; 406 } 407 ]; 408 409 systemd.services.gitlab-sidekiq = { 410 after = [ "network.target" "redis.service" ]; 411 wantedBy = [ "multi-user.target" ]; 412 partOf = [ "gitlab.service" ]; 413 environment = gitlabEnv; 414 path = with pkgs; [ 415 config.services.postgresql.package 416 gitAndTools.git 417 ruby 418 openssh 419 nodejs 420 ]; 421 serviceConfig = { 422 Type = "simple"; 423 User = cfg.user; 424 Group = cfg.group; 425 TimeoutSec = "300"; 426 Restart = "on-failure"; 427 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; 428 ExecStart="${cfg.packages.gitlab.env}/bin/bundle exec \"sidekiq -q post_receive -q mailers -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e production -P ${cfg.statePath}/tmp/sidekiq.pid\""; 429 }; 430 }; 431 432 systemd.services.gitlab-workhorse = { 433 after = [ "network.target" "gitlab.service" ]; 434 wantedBy = [ "multi-user.target" ]; 435 environment.HOME = gitlabEnv.HOME; 436 environment.GITLAB_SHELL_CONFIG_PATH = gitlabEnv.GITLAB_SHELL_CONFIG_PATH; 437 path = with pkgs; [ 438 gitAndTools.git 439 openssh 440 ]; 441 preStart = '' 442 mkdir -p /run/gitlab 443 chown ${cfg.user}:${cfg.group} /run/gitlab 444 ''; 445 serviceConfig = { 446 PermissionsStartOnly = true; # preStart must be run as root 447 Type = "simple"; 448 User = cfg.user; 449 Group = cfg.group; 450 TimeoutSec = "300"; 451 Restart = "on-failure"; 452 ExecStart = 453 "${cfg.packages.gitlab-workhorse}/bin/gitlab-workhorse " 454 + "-listenUmask 0 " 455 + "-listenNetwork unix " 456 + "-listenAddr /run/gitlab/gitlab-workhorse.socket " 457 + "-authSocket ${gitlabSocket} " 458 + "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public"; 459 }; 460 }; 461 462 systemd.services.gitlab = { 463 after = [ "network.target" "postgresql.service" "redis.service" ]; 464 wantedBy = [ "multi-user.target" ]; 465 environment = gitlabEnv; 466 path = with pkgs; [ 467 config.services.postgresql.package 468 gitAndTools.git 469 openssh 470 nodejs 471 ]; 472 preStart = '' 473 mkdir -p ${cfg.backupPath} 474 mkdir -p ${cfg.statePath}/builds 475 mkdir -p ${cfg.statePath}/repositories 476 mkdir -p ${gitlabConfig.production.shared.path}/artifacts 477 mkdir -p ${gitlabConfig.production.shared.path}/lfs-objects 478 mkdir -p ${cfg.statePath}/log 479 mkdir -p ${cfg.statePath}/shell 480 mkdir -p ${cfg.statePath}/tmp/pids 481 mkdir -p ${cfg.statePath}/tmp/sockets 482 483 rm -rf ${cfg.statePath}/config ${cfg.statePath}/shell/hooks 484 mkdir -p ${cfg.statePath}/config ${cfg.statePath}/shell 485 486 tr -dc A-Za-z0-9 < /dev/urandom | head -c 32 > ${cfg.statePath}/config/gitlab_shell_secret 487 488 # The uploads directory is hardcoded somewhere deep in rails. It is 489 # symlinked in the gitlab package to /run/gitlab/uploads to make it 490 # configurable 491 mkdir -p /run/gitlab 492 mkdir -p ${cfg.statePath}/uploads 493 ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads 494 chown -R ${cfg.user}:${cfg.group} /run/gitlab 495 496 # Prepare home directory 497 mkdir -p ${gitlabEnv.HOME}/.ssh 498 touch ${gitlabEnv.HOME}/.ssh/authorized_keys 499 chown -R ${cfg.user}:${cfg.group} ${gitlabEnv.HOME}/ 500 chmod -R u+rwX,go-rwx+X ${gitlabEnv.HOME}/ 501 502 cp -rf ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config 503 ${optionalString cfg.smtp.enable '' 504 ln -sf ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb 505 ''} 506 ln -sf ${cfg.statePath}/config /run/gitlab/config 507 cp ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION 508 509 # JSON is a subset of YAML 510 ln -fs ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml 511 ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.statePath}/config/database.yml 512 ln -fs ${pkgs.writeText "secrets.yml" secretsYml} ${cfg.statePath}/config/secrets.yml 513 ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.statePath}/config/unicorn.rb 514 515 chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/ 516 chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/ 517 518 # Install the shell required to push repositories 519 ln -fs ${pkgs.writeText "config.yml" gitlabShellYml} "$GITLAB_SHELL_CONFIG_PATH" 520 ln -fs ${cfg.packages.gitlab-shell}/hooks "$GITLAB_SHELL_HOOKS_PATH" 521 ${cfg.packages.gitlab-shell}/bin/install 522 523 if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then 524 if ! test -e "${cfg.statePath}/db-created"; then 525 psql postgres -c "CREATE ROLE gitlab WITH LOGIN NOCREATEDB NOCREATEROLE NOCREATEUSER ENCRYPTED PASSWORD '${cfg.databasePassword}'" 526 ${config.services.postgresql.package}/bin/createdb --owner gitlab gitlab || true 527 touch "${cfg.statePath}/db-created" 528 529 # The gitlab:setup task is horribly broken somehow, these two tasks will do the same for setting up the initial database 530 ${gitlab-rake}/bin/gitlab-rake db:migrate RAILS_ENV=production 531 ${gitlab-rake}/bin/gitlab-rake db:seed_fu RAILS_ENV=production \ 532 GITLAB_ROOT_PASSWORD="${cfg.initialRootPassword}" GITLAB_ROOT_EMAIL="${cfg.initialRootEmail}"; 533 fi 534 fi 535 536 # Always do the db migrations just to be sure the database is up-to-date 537 ${gitlab-rake}/bin/gitlab-rake db:migrate RAILS_ENV=production 538 539 # Change permissions in the last step because some of the 540 # intermediary scripts like to create directories as root. 541 chown -R ${cfg.user}:${cfg.group} ${cfg.statePath} 542 chmod -R u+rwX,go-rwx+X ${cfg.statePath} 543 ''; 544 545 serviceConfig = { 546 PermissionsStartOnly = true; # preStart must be run as root 547 Type = "simple"; 548 User = cfg.user; 549 Group = cfg.group; 550 TimeoutSec = "300"; 551 Restart = "on-failure"; 552 WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; 553 ExecStart = "${cfg.packages.gitlab.env}/bin/bundle exec \"unicorn -c ${cfg.statePath}/config/unicorn.rb -E production\""; 554 }; 555 556 }; 557 558 }; 559 560 meta.doc = ./gitlab.xml; 561 562}