diff options
Diffstat (limited to 'nixos/modules')
-rw-r--r-- | nixos/modules/misc/ids.nix | 2 | ||||
-rwxr-xr-x | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/misc/defaultUnicornConfig.rb | 206 | ||||
-rw-r--r-- | nixos/modules/services/misc/gitlab.nix | 283 |
4 files changed, 492 insertions, 0 deletions
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index bdaf85a03ce7..3bb7fdb9b2d6 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -172,6 +172,7 @@ kubernetes = 162; peerflix = 163; chronos = 164; + gitlab = 165; # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399! @@ -308,6 +309,7 @@ bosun = 157; kubernetes = 158; fleet = 159; + gitlab = 160; # When adding a gid, make sure it doesn't match an existing uid. And don't use gids above 399! diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c6980f3208b5..ec5e4e0fab4d 100755 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -176,6 +176,7 @@ ./services/misc/etcd.nix ./services/misc/felix.nix ./services/misc/folding-at-home.nix + ./services/misc/gitlab.nix ./services/misc/gitolite.nix ./services/misc/gpsd.nix ./services/misc/mesos-master.nix diff --git a/nixos/modules/services/misc/defaultUnicornConfig.rb b/nixos/modules/services/misc/defaultUnicornConfig.rb new file mode 100644 index 000000000000..81abaf336dc0 --- /dev/null +++ b/nixos/modules/services/misc/defaultUnicornConfig.rb @@ -0,0 +1,206 @@ +# The following was taken from github.com/crohr/syslogger and is BSD +# licensed. +require 'syslog' +require 'logger' +require 'thread' + +class Syslogger + + VERSION = "1.6.0" + + attr_reader :level, :ident, :options, :facility, :max_octets + attr_accessor :formatter + + MAPPING = { + Logger::DEBUG => Syslog::LOG_DEBUG, + Logger::INFO => Syslog::LOG_INFO, + Logger::WARN => Syslog::LOG_WARNING, + Logger::ERROR => Syslog::LOG_ERR, + Logger::FATAL => Syslog::LOG_CRIT, + Logger::UNKNOWN => Syslog::LOG_ALERT + } + + # + # Initializes default options for the logger + # <tt>ident</tt>:: the name of your program [default=$0]. + # <tt>options</tt>:: syslog options [default=<tt>Syslog::LOG_PID | Syslog::LOG_CONS</tt>]. + # Correct values are: + # LOG_CONS : writes the message on the console if an error occurs when sending the message; + # LOG_NDELAY : no delay before sending the message; + # LOG_PERROR : messages will also be written on STDERR; + # LOG_PID : adds the process number to the message (just after the program name) + # <tt>facility</tt>:: the syslog facility [default=nil] Correct values include: + # Syslog::LOG_DAEMON + # Syslog::LOG_USER + # Syslog::LOG_SYSLOG + # Syslog::LOG_LOCAL2 + # Syslog::LOG_NEWS + # etc. + # + # Usage: + # logger = Syslogger.new("my_app", Syslog::LOG_PID | Syslog::LOG_CONS, Syslog::LOG_LOCAL0) + # logger.level = Logger::INFO # use Logger levels + # logger.warn "warning message" + # logger.debug "debug message" + # + def initialize(ident = $0, options = Syslog::LOG_PID | Syslog::LOG_CONS, facility = nil) + @ident = ident + @options = options || (Syslog::LOG_PID | Syslog::LOG_CONS) + @facility = facility + @level = Logger::INFO + @mutex = Mutex.new + @formatter = Logger::Formatter.new + end + + %w{debug info warn error fatal unknown}.each do |logger_method| + # Accepting *args as message could be nil. + # Default params not supported in ruby 1.8.7 + define_method logger_method.to_sym do |*args, &block| + return true if @level > Logger.const_get(logger_method.upcase) + message = args.first || block && block.call + add(Logger.const_get(logger_method.upcase), message) + end + + unless logger_method == 'unknown' + define_method "#{logger_method}?".to_sym do + @level <= Logger.const_get(logger_method.upcase) + end + end + end + + # Log a message at the Logger::INFO level. Useful for use with Rack::CommonLogger + def write(msg) + add(Logger::INFO, msg) + end + + # Logs a message at the Logger::INFO level. + def <<(msg) + add(Logger::INFO, msg) + end + + # Low level method to add a message. + # +severity+:: the level of the message. One of Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL, Logger::UNKNOWN + # +message+:: the message string. + # If nil, the method will call the block and use the result as the message string. + # If both are nil or no block is given, it will use the progname as per the behaviour of both the standard Ruby logger, and the Rails BufferedLogger. + # +progname+:: optionally, overwrite the program name that appears in the log message. + def add(severity, message = nil, progname = nil, &block) + if message.nil? && block.nil? && !progname.nil? + message, progname = progname, nil + end + progname ||= @ident + + @mutex.synchronize do + Syslog.open(progname, @options, @facility) do |s| + s.mask = Syslog::LOG_UPTO(MAPPING[@level]) + communication = clean(message || block && block.call) + if self.max_octets + buffer = "#{tags_text}" + communication.bytes do |byte| + buffer.concat(byte) + # if the last byte we added is potentially part of an escape, we'll go ahead and add another byte + if buffer.bytesize >= self.max_octets && !['%'.ord,'\\'.ord].include?(byte) + s.log(MAPPING[severity],buffer) + buffer = "" + end + end + s.log(MAPPING[severity],buffer) unless buffer.empty? + else + s.log(MAPPING[severity],"#{tags_text}#{communication}") + end + end + end + end + + # Set the max octets of the messages written to the log + def max_octets=(max_octets) + @max_octets = max_octets + end + + # Sets the minimum level for messages to be written in the log. + # +level+:: one of <tt>Logger::DEBUG</tt>, <tt>Logger::INFO</tt>, <tt>Logger::WARN</tt>, <tt>Logger::ERROR</tt>, <tt>Logger::FATAL</tt>, <tt>Logger::UNKNOWN</tt> + def level=(level) + level = Logger.const_get(level.to_s.upcase) if level.is_a?(Symbol) + + unless level.is_a?(Fixnum) + raise ArgumentError.new("Invalid logger level `#{level.inspect}`") + end + + @level = level + end + + # Sets the ident string passed along to Syslog + def ident=(ident) + @ident = ident + end + + # Tagging code borrowed from ActiveSupport gem + def tagged(*tags) + new_tags = push_tags(*tags) + yield self + ensure + pop_tags(new_tags.size) + end + + def push_tags(*tags) + tags.flatten.reject{ |i| i.respond_to?(:empty?) ? i.empty? : !i }.tap do |new_tags| + current_tags.concat new_tags + end + end + + def pop_tags(size = 1) + current_tags.pop size + end + + def clear_tags! + current_tags.clear + end + + protected + + # Borrowed from SyslogLogger. + def clean(message) + message = message.to_s.dup + message.strip! # remove whitespace + message.gsub!(/\n/, '\\n') # escape newlines + message.gsub!(/%/, '%%') # syslog(3) freaks on % (printf) + message.gsub!(/\e\[[^m]*m/, '') # remove useless ansi color codes + message + end + + private + + def tags_text + tags = current_tags + if tags.any? + tags.collect { |tag| "[#{tag}] " }.join + end + end + + def current_tags + Thread.current[:syslogger_tagged_logging_tags] ||= [] + end +end + +worker_processes 2 +working_directory ENV["GITLAB_PATH"] +pid ENV["UNICORN_PATH"] + "/tmp/pids/unicorn.pid" + +listen ENV["UNICORN_PATH"] + "/tmp/sockets/gitlab.socket", :backlog => 1024 +listen "127.0.0.1:8080", :tcp_nopush => true + +timeout 60 + +logger Syslogger.new + +preload_app true + +GC.respond_to?(:copy_on_write_friendly=) and + GC.copy_on_write_friendly = true + +check_client_connection false + +after_fork do |server, worker| + defined?(ActiveRecord::Base) and + ActiveRecord::Base.establish_connection +end diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix new file mode 100644 index 000000000000..cbef1e434486 --- /dev/null +++ b/nixos/modules/services/misc/gitlab.nix @@ -0,0 +1,283 @@ +{ config, lib, pkgs, ... }: + +# TODO: support non-postgresql + +with lib; + +let + cfg = config.services.gitlab; + + ruby = pkgs.ruby; + rubyLibs = pkgs.rubyLibs; + + databaseYml = '' + production: + adapter: postgresql + database: ${cfg.databaseName} + host: ${cfg.databaseHost} + password: ${cfg.databasePassword} + username: ${cfg.databaseUsername} + encoding: utf8 + ''; + gitlabShellYml = '' + user: gitlab + gitlab_url: "http://localhost:8080/" + http_settings: + self_signed_cert: false + repos_path: "${cfg.stateDir}/repositories" + log_file: "${cfg.stateDir}/log/gitlab-shell.log" + redis: + bin: ${pkgs.redis}/bin/redis-cli + host: 127.0.0.1 + port: 6379 + database: 0 + namespace: resque:gitlab + ''; + + unicornConfig = builtins.readFile ./defaultUnicornConfig.rb; + + gitlab-runner = pkgs.stdenv.mkDerivation rec { + name = "gitlab-runner"; + buildInputs = [ pkgs.gitlab pkgs.rubyLibs.bundler pkgs.makeWrapper ]; + phases = "installPhase fixupPhase"; + buildPhase = ""; + installPhase = '' + mkdir -p $out/bin + makeWrapper ${rubyLibs.bundler}/bin/bundle $out/bin/gitlab-runner\ + --set RAKEOPT '"-f ${pkgs.gitlab}/share/gitlab/Rakefile"'\ + --set UNICORN_PATH "${cfg.stateDir}/"\ + --set GITLAB_PATH "${pkgs.gitlab}/share/gitlab/"\ + --set GITLAB_APPLICATION_LOG_PATH "${cfg.stateDir}/log/application.log"\ + --set GITLAB_SATELLITES_PATH "${cfg.stateDir}/satellites"\ + --set GITLAB_SHELL_PATH "${pkgs.gitlab-shell}"\ + --set GITLAB_REPOSITORIES_PATH "${cfg.stateDir}/repositories"\ + --set GITLAB_SHELL_HOOKS_PATH "${cfg.stateDir}/shell/hooks"\ + --set BUNDLE_GEMFILE "${pkgs.gitlab}/share/gitlab/Gemfile"\ + --set GITLAB_EMAIL_FROM "${cfg.emailFrom}"\ + --set GITLAB_SHELL_CONFIG_PATH "${cfg.stateDir}/shell/config.yml"\ + --set GITLAB_SHELL_SECRET_PATH "${cfg.stateDir}/config/gitlab_shell_secret"\ + --set GITLAB_HOST "${cfg.host}"\ + --set GITLAB_BACKUP_PATH"${cfg.backupPath}"\ + --set RAILS_ENV "production" + ''; + }; + +in { + + options = { + services.gitlab = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable the gitlab service. + ''; + }; + + satelliteDir = mkOption { + type = types.str; + default = "/var/gitlab/git-satellites"; + description = "Directory to store checked out git trees requires for operation."; + }; + + stateDir = mkOption { + type = types.str; + default = "/var/gitlab/state"; + description = "The state directory, logs are stored here."; + }; + + backupPath = mkOption { + type = types.str; + default = cfg.stateDir + "/backup"; + description = "Path for backups."; + }; + + databaseHost = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Database hostname"; + }; + + databasePassword = mkOption { + type = types.str; + default = ""; + description = "Database user password"; + }; + + databaseName = mkOption { + type = types.str; + default = "gitlab"; + description = "Database name"; + }; + + databaseUsername = mkOption { + type = types.str; + default = "gitlab"; + description = "Database user"; + }; + + emailFrom = mkOption { + type = types.str; + default = "example@example.org"; + description = "The source address for emails sent by gitlab."; + }; + + host = mkOption { + type = types.str; + default = config.networking.hostName; + description = "The gitlab host name. Used e.g. for copy-paste URLs."; + }; + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ gitlab-runner pkgs.gitlab-shell ]; + + assertions = [ + { assertion = cfg.databasePassword != ""; + message = "databasePassword must be set"; + } + ]; + + # Redis is required for the sidekiq queue runner. + services.redis.enable = mkDefault true; + # We use postgres as the main data store. + services.postgresql.enable = mkDefault true; + # Use postfix to send out mails. + services.postfix.enable = mkDefault true; + + users.extraUsers = [ + { name = "gitlab"; + group = "gitlab"; + home = "${cfg.stateDir}/home"; + shell = "${pkgs.bash}/bin/bash"; + uid = config.ids.uids.gitlab; + } ]; + + users.extraGroups = [ + { name = "gitlab"; + gid = config.ids.gids.gitlab; + } ]; + + systemd.services.gitlab-sidekiq = { + after = [ "network.target" "redis.service" ]; + wantedBy = [ "multi-user.target" ]; + environment.HOME = "${cfg.stateDir}/home"; + environment.UNICORN_PATH = "${cfg.stateDir}/"; + environment.GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/"; + environment.GITLAB_APPLICATION_LOG_PATH = "${cfg.stateDir}/log/application.log"; + environment.GITLAB_SATELLITES_PATH = "${cfg.stateDir}/satellites"; + environment.GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}"; + environment.GITLAB_REPOSITORIES_PATH = "${cfg.stateDir}/repositories"; + environment.GITLAB_SHELL_HOOKS_PATH = "${cfg.stateDir}/shell/hooks"; + environment.BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile"; + environment.GITLAB_EMAIL_FROM = "${cfg.emailFrom}"; + environment.GITLAB_SHELL_CONFIG_PATH = "${cfg.stateDir}/shell/config.yml"; + environment.GITLAB_SHELL_SECRET_PATH = "${cfg.stateDir}/config/gitlab_shell_secret"; + environment.GITLAB_HOST = "${cfg.host}"; + environment.GITLAB_DATABASE_HOST = "${cfg.databaseHost}"; + environment.GITLAB_DATABASE_PASSWORD = "${cfg.databasePassword}"; + environment.RAILS_ENV = "production"; + path = with pkgs; [ + config.services.postgresql.package + gitAndTools.git + ruby + openssh + ]; + serviceConfig = { + Type = "simple"; + User = "gitlab"; + Group = "gitlab"; + TimeoutSec = "300"; + WorkingDirectory = "${pkgs.gitlab}/share/gitlab"; + ExecStart="${rubyLibs.bundler}/bin/bundle exec \"sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e production -P ${cfg.stateDir}/tmp/sidekiq.pid\""; + }; + }; + + systemd.services.gitlab = { + after = [ "network.target" "postgresql.service" "redis.service" ]; + wantedBy = [ "multi-user.target" ]; + environment.HOME = "${cfg.stateDir}/home"; + environment.UNICORN_PATH = "${cfg.stateDir}/"; + environment.GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/"; + environment.GITLAB_APPLICATION_LOG_PATH = "${cfg.stateDir}/log/application.log"; + environment.GITLAB_SATELLITES_PATH = "${cfg.stateDir}/satellites"; + environment.GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}"; + environment.GITLAB_REPOSITORIES_PATH = "${cfg.stateDir}/repositories"; + environment.GITLAB_SHELL_HOOKS_PATH = "${cfg.stateDir}/shell/hooks"; + environment.BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile"; + environment.GITLAB_EMAIL_FROM = "${cfg.emailFrom}"; + environment.GITLAB_HOST = "${cfg.host}"; + environment.GITLAB_DATABASE_HOST = "${cfg.databaseHost}"; + environment.GITLAB_DATABASE_PASSWORD = "${cfg.databasePassword}"; + environment.RAILS_ENV = "production"; + path = with pkgs; [ + config.services.postgresql.package + gitAndTools.git + ruby + openssh + ]; + preStart = '' + # TODO: use env vars + mkdir -p ${cfg.stateDir} + mkdir -p ${cfg.stateDir}/log + mkdir -p ${cfg.stateDir}/satellites + mkdir -p ${cfg.stateDir}/repositories + mkdir -p ${cfg.stateDir}/shell/hooks + mkdir -p ${cfg.stateDir}/tmp/pids + mkdir -p ${cfg.stateDir}/tmp/sockets + rm -rf ${cfg.stateDir}/config + mkdir -p ${cfg.stateDir}/config + # TODO: What exactly is gitlab-shell doing with the secret? + head -c 20 /dev/urandom > ${cfg.stateDir}/config/gitlab_shell_secret + mkdir -p ${cfg.stateDir}/home/.ssh + touch ${cfg.stateDir}/home/.ssh/authorized_keys + + cp -rf ${pkgs.gitlab}/share/gitlab/config ${cfg.stateDir}/ + cp ${pkgs.gitlab}/share/gitlab/VERSION ${cfg.stateDir}/VERSION + + ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.stateDir}/config/database.yml + ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.stateDir}/config/unicorn.rb + + chown -R gitlab:gitlab ${cfg.stateDir}/ + chmod -R 755 ${cfg.stateDir}/ + + if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then + if ! test -e "${cfg.stateDir}/db-created"; then + psql postgres -c "CREATE ROLE gitlab WITH LOGIN NOCREATEDB NOCREATEROLE NOCREATEUSER ENCRYPTED PASSWORD '${cfg.databasePassword}'" + ${config.services.postgresql.package}/bin/createdb --owner gitlab gitlab || true + touch "${cfg.stateDir}/db-created" + + # force=yes disables the manual-interaction yes/no prompt + # which breaks without an stdin. + force=yes ${rubyLibs.bundler}/bin/bundle exec rake -f ${pkgs.gitlab}/share/gitlab/Rakefile gitlab:setup RAILS_ENV=production + fi + fi + + # Install the shell required to push repositories + ln -fs ${pkgs.writeText "config.yml" gitlabShellYml} ${cfg.stateDir}/shell/config.yml + export GITLAB_SHELL_CONFIG_PATH=""${cfg.stateDir}/shell/config.yml + ${pkgs.gitlab-shell}/bin/install + + # Change permissions in the last step because some of the + # intermediary scripts like to create directories as root. + chown -R gitlab:gitlab ${cfg.stateDir}/ + chmod -R 755 ${cfg.stateDir}/ + ''; + + serviceConfig = { + PermissionsStartOnly = true; # preStart must be run as root + Type = "simple"; + User = "gitlab"; + Group = "gitlab"; + TimeoutSec = "300"; + WorkingDirectory = "${pkgs.gitlab}/share/gitlab"; + ExecStart="${rubyLibs.bundler}/bin/bundle exec \"unicorn -c ${cfg.stateDir}/config/unicorn.rb -E production\""; + }; + + }; + + }; + +} |