1173 lines
40 KiB
Nix
1173 lines
40 KiB
Nix
{ config, lib, pkgs, utils, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.gitlab;
|
|
|
|
ruby = cfg.packages.gitlab.ruby;
|
|
|
|
postgresqlPackage = if config.services.postgresql.enable then
|
|
config.services.postgresql.package
|
|
else
|
|
pkgs.postgresql;
|
|
|
|
gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
|
|
gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
|
|
pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
|
|
|
|
databaseConfig = {
|
|
production = {
|
|
adapter = "postgresql";
|
|
database = cfg.databaseName;
|
|
host = cfg.databaseHost;
|
|
username = cfg.databaseUsername;
|
|
encoding = "utf8";
|
|
pool = cfg.databasePool;
|
|
} // cfg.extraDatabaseConfig;
|
|
};
|
|
|
|
# We only want to create a database if we're actually going to connect to it.
|
|
databaseActuallyCreateLocally = cfg.databaseCreateLocally && cfg.databaseHost == "";
|
|
|
|
gitalyToml = pkgs.writeText "gitaly.toml" ''
|
|
socket_path = "${lib.escape ["\""] gitalySocket}"
|
|
bin_dir = "${cfg.packages.gitaly}/bin"
|
|
prometheus_listen_addr = "localhost:9236"
|
|
|
|
[git]
|
|
bin_path = "${pkgs.git}/bin/git"
|
|
|
|
[gitaly-ruby]
|
|
dir = "${cfg.packages.gitaly.ruby}"
|
|
|
|
[gitlab-shell]
|
|
dir = "${cfg.packages.gitlab-shell}"
|
|
|
|
[hooks]
|
|
custom_hooks_dir = "${cfg.statePath}/custom_hooks"
|
|
|
|
[gitlab]
|
|
secret_file = "${cfg.statePath}/gitlab_shell_secret"
|
|
url = "http+unix://${pathUrlQuote gitlabSocket}"
|
|
|
|
[gitlab.http-settings]
|
|
self_signed_cert = false
|
|
|
|
${concatStringsSep "\n" (attrValues (mapAttrs (k: v: ''
|
|
[[storage]]
|
|
name = "${lib.escape ["\""] k}"
|
|
path = "${lib.escape ["\""] v.path}"
|
|
'') gitlabConfig.production.repositories.storages))}
|
|
'';
|
|
|
|
gitlabShellConfig = flip recursiveUpdate cfg.extraShellConfig {
|
|
user = cfg.user;
|
|
gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
|
|
http_settings.self_signed_cert = false;
|
|
repos_path = "${cfg.statePath}/repositories";
|
|
secret_file = "${cfg.statePath}/gitlab_shell_secret";
|
|
log_file = "${cfg.statePath}/log/gitlab-shell.log";
|
|
redis = {
|
|
bin = "${pkgs.redis}/bin/redis-cli";
|
|
host = "127.0.0.1";
|
|
port = 6379;
|
|
database = 0;
|
|
namespace = "resque:gitlab";
|
|
};
|
|
};
|
|
|
|
redisConfig.production.url = cfg.redisUrl;
|
|
|
|
pagesArgs = [
|
|
"-pages-domain" gitlabConfig.production.pages.host
|
|
"-pages-root" "${gitlabConfig.production.shared.path}/pages"
|
|
] ++ cfg.pagesExtraArgs;
|
|
|
|
gitlabConfig = {
|
|
# These are the default settings from config/gitlab.example.yml
|
|
production = flip recursiveUpdate cfg.extraConfig {
|
|
gitlab = {
|
|
host = cfg.host;
|
|
port = cfg.port;
|
|
https = cfg.https;
|
|
user = cfg.user;
|
|
email_enabled = true;
|
|
email_display_name = "GitLab";
|
|
email_reply_to = "noreply@localhost";
|
|
default_theme = 2;
|
|
default_projects_features = {
|
|
issues = true;
|
|
merge_requests = true;
|
|
wiki = true;
|
|
snippets = true;
|
|
builds = true;
|
|
container_registry = true;
|
|
};
|
|
};
|
|
repositories.storages.default.path = "${cfg.statePath}/repositories";
|
|
repositories.storages.default.gitaly_address = "unix:${gitalySocket}";
|
|
artifacts.enabled = true;
|
|
lfs.enabled = true;
|
|
gravatar.enabled = true;
|
|
cron_jobs = { };
|
|
gitlab_ci.builds_path = "${cfg.statePath}/builds";
|
|
ldap.enabled = false;
|
|
omniauth.enabled = false;
|
|
shared.path = "${cfg.statePath}/shared";
|
|
gitaly.client_path = "${cfg.packages.gitaly}/bin";
|
|
backup = {
|
|
path = cfg.backup.path;
|
|
keep_time = cfg.backup.keepTime;
|
|
upload = cfg.backup.uploadOptions;
|
|
};
|
|
gitlab_shell = {
|
|
path = "${cfg.packages.gitlab-shell}";
|
|
hooks_path = "${cfg.statePath}/shell/hooks";
|
|
secret_file = "${cfg.statePath}/gitlab_shell_secret";
|
|
upload_pack = true;
|
|
receive_pack = true;
|
|
};
|
|
workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret";
|
|
gitlab_kas.secret_file = "${cfg.statePath}/.gitlab_kas_secret";
|
|
git.bin_path = "git";
|
|
monitoring = {
|
|
ip_whitelist = [ "127.0.0.0/8" "::1/128" ];
|
|
sidekiq_exporter = {
|
|
enable = true;
|
|
address = "localhost";
|
|
port = 3807;
|
|
};
|
|
};
|
|
extra = {};
|
|
uploads.storage_path = cfg.statePath;
|
|
};
|
|
};
|
|
|
|
gitlabEnv = {
|
|
HOME = "${cfg.statePath}/home";
|
|
PUMA_PATH = "${cfg.statePath}/";
|
|
GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
|
|
SCHEMA = "${cfg.statePath}/db/structure.sql";
|
|
GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
|
|
GITLAB_LOG_PATH = "${cfg.statePath}/log";
|
|
GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig);
|
|
prometheus_multiproc_dir = "/run/gitlab";
|
|
RAILS_ENV = "production";
|
|
};
|
|
|
|
gitlab-rake = pkgs.stdenv.mkDerivation {
|
|
name = "gitlab-rake";
|
|
buildInputs = [ pkgs.makeWrapper ];
|
|
dontBuild = true;
|
|
dontUnpack = true;
|
|
installPhase = ''
|
|
mkdir -p $out/bin
|
|
makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \
|
|
${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
|
|
--set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar postgresqlPackage pkgs.coreutils pkgs.procps ]}:$PATH' \
|
|
--set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \
|
|
--run 'cd ${cfg.packages.gitlab}/share/gitlab'
|
|
'';
|
|
};
|
|
|
|
gitlab-rails = pkgs.stdenv.mkDerivation {
|
|
name = "gitlab-rails";
|
|
buildInputs = [ pkgs.makeWrapper ];
|
|
dontBuild = true;
|
|
dontUnpack = true;
|
|
installPhase = ''
|
|
mkdir -p $out/bin
|
|
makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \
|
|
${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
|
|
--set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar postgresqlPackage pkgs.coreutils pkgs.procps ]}:$PATH' \
|
|
--run 'cd ${cfg.packages.gitlab}/share/gitlab'
|
|
'';
|
|
};
|
|
|
|
extraGitlabRb = pkgs.writeText "extra-gitlab.rb" cfg.extraGitlabRb;
|
|
|
|
smtpSettings = pkgs.writeText "gitlab-smtp-settings.rb" ''
|
|
if Rails.env.production?
|
|
Rails.application.config.action_mailer.delivery_method = :smtp
|
|
|
|
ActionMailer::Base.delivery_method = :smtp
|
|
ActionMailer::Base.smtp_settings = {
|
|
address: "${cfg.smtp.address}",
|
|
port: ${toString cfg.smtp.port},
|
|
${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''}
|
|
${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''}
|
|
domain: "${cfg.smtp.domain}",
|
|
${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
|
|
enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto},
|
|
tls: ${boolToString cfg.smtp.tls},
|
|
ca_file: "/etc/ssl/certs/ca-certificates.crt",
|
|
openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
|
|
}
|
|
end
|
|
'';
|
|
|
|
in {
|
|
|
|
imports = [
|
|
(mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
|
|
(mkRenamedOptionModule [ "services" "gitlab" "backupPath" ] [ "services" "gitlab" "backup" "path" ])
|
|
(mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
|
|
];
|
|
|
|
options = {
|
|
services.gitlab = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Enable the gitlab service.
|
|
'';
|
|
};
|
|
|
|
packages.gitlab = mkOption {
|
|
type = types.package;
|
|
default = pkgs.gitlab;
|
|
defaultText = "pkgs.gitlab";
|
|
description = "Reference to the gitlab package";
|
|
example = "pkgs.gitlab-ee";
|
|
};
|
|
|
|
packages.gitlab-shell = mkOption {
|
|
type = types.package;
|
|
default = pkgs.gitlab-shell;
|
|
defaultText = "pkgs.gitlab-shell";
|
|
description = "Reference to the gitlab-shell package";
|
|
};
|
|
|
|
packages.gitlab-workhorse = mkOption {
|
|
type = types.package;
|
|
default = pkgs.gitlab-workhorse;
|
|
defaultText = "pkgs.gitlab-workhorse";
|
|
description = "Reference to the gitlab-workhorse package";
|
|
};
|
|
|
|
packages.gitaly = mkOption {
|
|
type = types.package;
|
|
default = pkgs.gitaly;
|
|
defaultText = "pkgs.gitaly";
|
|
description = "Reference to the gitaly package";
|
|
};
|
|
|
|
packages.pages = mkOption {
|
|
type = types.package;
|
|
default = pkgs.gitlab-pages;
|
|
defaultText = "pkgs.gitlab-pages";
|
|
description = "Reference to the gitlab-pages package";
|
|
};
|
|
|
|
statePath = mkOption {
|
|
type = types.str;
|
|
default = "/var/gitlab/state";
|
|
description = ''
|
|
GitLab state directory. Configuration, repositories and
|
|
logs, among other things, are stored here.
|
|
|
|
The directory will be created automatically if it doesn't
|
|
exist already. Its parent directories must be owned by
|
|
either <literal>root</literal> or the user set in
|
|
<option>services.gitlab.user</option>.
|
|
'';
|
|
};
|
|
|
|
backup.startAt = mkOption {
|
|
type = with types; either str (listOf str);
|
|
default = [];
|
|
example = "03:00";
|
|
description = ''
|
|
The time(s) to run automatic backup of GitLab
|
|
state. Specified in systemd's time format; see
|
|
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
|
<manvolnum>7</manvolnum></citerefentry>.
|
|
'';
|
|
};
|
|
|
|
backup.path = mkOption {
|
|
type = types.str;
|
|
default = cfg.statePath + "/backup";
|
|
description = "GitLab path for backups.";
|
|
};
|
|
|
|
backup.keepTime = mkOption {
|
|
type = types.int;
|
|
default = 0;
|
|
example = 48;
|
|
apply = x: x * 60 * 60;
|
|
description = ''
|
|
How long to keep the backups around, in
|
|
hours. <literal>0</literal> means <quote>keep
|
|
forever</quote>.
|
|
'';
|
|
};
|
|
|
|
backup.skip = mkOption {
|
|
type = with types;
|
|
let value = enum [
|
|
"db"
|
|
"uploads"
|
|
"builds"
|
|
"artifacts"
|
|
"lfs"
|
|
"registry"
|
|
"pages"
|
|
"repositories"
|
|
"tar"
|
|
];
|
|
in
|
|
either value (listOf value);
|
|
default = [];
|
|
example = [ "artifacts" "lfs" ];
|
|
apply = x: if isString x then x else concatStringsSep "," x;
|
|
description = ''
|
|
Directories to exclude from the backup. The example excludes
|
|
CI artifacts and LFS objects from the backups. The
|
|
<literal>tar</literal> option skips the creation of a tar
|
|
file.
|
|
|
|
Refer to <link xlink:href="https://docs.gitlab.com/ee/raketasks/backup_restore.html#excluding-specific-directories-from-the-backup"/>
|
|
for more information.
|
|
'';
|
|
};
|
|
|
|
backup.uploadOptions = mkOption {
|
|
type = types.attrs;
|
|
default = {};
|
|
example = literalExample ''
|
|
{
|
|
# Fog storage connection settings, see http://fog.io/storage/
|
|
connection = {
|
|
provider = "AWS";
|
|
region = "eu-north-1";
|
|
aws_access_key_id = "AKIAXXXXXXXXXXXXXXXX";
|
|
aws_secret_access_key = { _secret = config.deployment.keys.aws_access_key.path; };
|
|
};
|
|
|
|
# The remote 'directory' to store your backups in.
|
|
# For S3, this would be the bucket name.
|
|
remote_directory = "my-gitlab-backups";
|
|
|
|
# Use multipart uploads when file size reaches 100MB, see
|
|
# http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
|
|
multipart_chunk_size = 104857600;
|
|
|
|
# Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
|
|
encryption = "AES256";
|
|
|
|
# Specifies Amazon S3 storage class to use for backups, this is optional
|
|
storage_class = "STANDARD";
|
|
};
|
|
'';
|
|
description = ''
|
|
GitLab automatic upload specification. Tells GitLab to
|
|
upload the backup to a remote location when done.
|
|
|
|
Attributes specified here are added under
|
|
<literal>production -> backup -> upload</literal> in
|
|
<filename>config/gitlab.yml</filename>.
|
|
'';
|
|
};
|
|
|
|
databaseHost = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
description = ''
|
|
GitLab database hostname. An empty string means <quote>use
|
|
local unix socket connection</quote>.
|
|
'';
|
|
};
|
|
|
|
databasePasswordFile = mkOption {
|
|
type = with types; nullOr path;
|
|
default = null;
|
|
description = ''
|
|
File containing the GitLab database user password.
|
|
|
|
This should be a string, not a nix path, since nix paths are
|
|
copied into the world-readable nix store.
|
|
'';
|
|
};
|
|
|
|
databaseCreateLocally = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
Whether a database should be automatically created on the
|
|
local host. Set this to <literal>false</literal> if you plan
|
|
on provisioning a local database yourself. This has no effect
|
|
if <option>services.gitlab.databaseHost</option> is customized.
|
|
'';
|
|
};
|
|
|
|
databaseName = mkOption {
|
|
type = types.str;
|
|
default = "gitlab";
|
|
description = "GitLab database name.";
|
|
};
|
|
|
|
databaseUsername = mkOption {
|
|
type = types.str;
|
|
default = "gitlab";
|
|
description = "GitLab database user.";
|
|
};
|
|
|
|
databasePool = mkOption {
|
|
type = types.int;
|
|
default = 5;
|
|
description = "Database connection pool size.";
|
|
};
|
|
|
|
extraDatabaseConfig = mkOption {
|
|
type = types.attrs;
|
|
default = {};
|
|
description = "Extra configuration in config/database.yml.";
|
|
};
|
|
|
|
redisUrl = mkOption {
|
|
type = types.str;
|
|
default = "redis://localhost:6379/";
|
|
description = "Redis URL for all GitLab services except gitlab-shell";
|
|
};
|
|
|
|
extraGitlabRb = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
example = ''
|
|
if Rails.env.production?
|
|
Rails.application.config.action_mailer.delivery_method = :sendmail
|
|
ActionMailer::Base.delivery_method = :sendmail
|
|
ActionMailer::Base.sendmail_settings = {
|
|
location: "/run/wrappers/bin/sendmail",
|
|
arguments: "-i -t"
|
|
}
|
|
end
|
|
'';
|
|
description = ''
|
|
Extra configuration to be placed in config/extra-gitlab.rb. This can
|
|
be used to add configuration not otherwise exposed through this module's
|
|
options.
|
|
'';
|
|
};
|
|
|
|
host = mkOption {
|
|
type = types.str;
|
|
default = config.networking.hostName;
|
|
description = "GitLab host name. Used e.g. for copy-paste URLs.";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.int;
|
|
default = 8080;
|
|
description = ''
|
|
GitLab server port for copy-paste URLs, e.g. 80 or 443 if you're
|
|
service over https.
|
|
'';
|
|
};
|
|
|
|
https = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Whether gitlab prints URLs with https as scheme.";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "gitlab";
|
|
description = "User to run gitlab and all related services.";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "gitlab";
|
|
description = "Group to run gitlab and all related services.";
|
|
};
|
|
|
|
initialRootEmail = mkOption {
|
|
type = types.str;
|
|
default = "admin@local.host";
|
|
description = ''
|
|
Initial email address of the root account if this is a new install.
|
|
'';
|
|
};
|
|
|
|
initialRootPasswordFile = mkOption {
|
|
type = with types; nullOr path;
|
|
default = null;
|
|
description = ''
|
|
File containing the initial password of the root account if
|
|
this is a new install.
|
|
|
|
This should be a string, not a nix path, since nix paths are
|
|
copied into the world-readable nix store.
|
|
'';
|
|
};
|
|
|
|
smtp = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enable gitlab mail delivery over SMTP.";
|
|
};
|
|
|
|
address = mkOption {
|
|
type = types.str;
|
|
default = "localhost";
|
|
description = "Address of the SMTP server for GitLab.";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.int;
|
|
default = 25;
|
|
description = "Port of the SMTP server for GitLab.";
|
|
};
|
|
|
|
username = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "Username of the SMTP server for GitLab.";
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = ''
|
|
File containing the password of the SMTP server for GitLab.
|
|
|
|
This should be a string, not a nix path, since nix paths
|
|
are copied into the world-readable nix store.
|
|
'';
|
|
};
|
|
|
|
domain = mkOption {
|
|
type = types.str;
|
|
default = "localhost";
|
|
description = "HELO domain to use for outgoing mail.";
|
|
};
|
|
|
|
authentication = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
|
|
};
|
|
|
|
enableStartTLSAuto = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Whether to try to use StartTLS.";
|
|
};
|
|
|
|
tls = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Whether to use TLS wrapper-mode.";
|
|
};
|
|
|
|
opensslVerifyMode = mkOption {
|
|
type = types.str;
|
|
default = "peer";
|
|
description = "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
|
|
};
|
|
};
|
|
|
|
pagesExtraArgs = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ "-listen-proxy" "127.0.0.1:8090" ];
|
|
description = "Arguments to pass to the gitlab-pages daemon";
|
|
};
|
|
|
|
secrets.secretFile = mkOption {
|
|
type = with types; nullOr path;
|
|
default = null;
|
|
description = ''
|
|
A file containing the secret used to encrypt variables in
|
|
the DB. If you change or lose this key you will be unable to
|
|
access variables stored in database.
|
|
|
|
Make sure the secret is at least 30 characters and all random,
|
|
no regular words or you'll be exposed to dictionary attacks.
|
|
|
|
This should be a string, not a nix path, since nix paths are
|
|
copied into the world-readable nix store.
|
|
'';
|
|
};
|
|
|
|
secrets.dbFile = mkOption {
|
|
type = with types; nullOr path;
|
|
default = null;
|
|
description = ''
|
|
A file containing the secret used to encrypt variables in
|
|
the DB. If you change or lose this key you will be unable to
|
|
access variables stored in database.
|
|
|
|
Make sure the secret is at least 30 characters and all random,
|
|
no regular words or you'll be exposed to dictionary attacks.
|
|
|
|
This should be a string, not a nix path, since nix paths are
|
|
copied into the world-readable nix store.
|
|
'';
|
|
};
|
|
|
|
secrets.otpFile = mkOption {
|
|
type = with types; nullOr path;
|
|
default = null;
|
|
description = ''
|
|
A file containing the secret used to encrypt secrets for OTP
|
|
tokens. If you change or lose this key, users which have 2FA
|
|
enabled for login won't be able to login anymore.
|
|
|
|
Make sure the secret is at least 30 characters and all random,
|
|
no regular words or you'll be exposed to dictionary attacks.
|
|
|
|
This should be a string, not a nix path, since nix paths are
|
|
copied into the world-readable nix store.
|
|
'';
|
|
};
|
|
|
|
secrets.jwsFile = mkOption {
|
|
type = with types; nullOr path;
|
|
default = null;
|
|
description = ''
|
|
A file containing the secret used to encrypt session
|
|
keys. If you change or lose this key, users will be
|
|
disconnected.
|
|
|
|
Make sure the secret is an RSA private key in PEM format. You can
|
|
generate one with
|
|
|
|
openssl genrsa 2048
|
|
|
|
This should be a string, not a nix path, since nix paths are
|
|
copied into the world-readable nix store.
|
|
'';
|
|
};
|
|
|
|
extraShellConfig = mkOption {
|
|
type = types.attrs;
|
|
default = {};
|
|
description = "Extra configuration to merge into shell-config.yml";
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = types.attrs;
|
|
default = {};
|
|
example = literalExample ''
|
|
{
|
|
gitlab = {
|
|
default_projects_features = {
|
|
builds = false;
|
|
};
|
|
};
|
|
omniauth = {
|
|
enabled = true;
|
|
auto_sign_in_with_provider = "openid_connect";
|
|
allow_single_sign_on = ["openid_connect"];
|
|
block_auto_created_users = false;
|
|
providers = [
|
|
{
|
|
name = "openid_connect";
|
|
label = "OpenID Connect";
|
|
args = {
|
|
name = "openid_connect";
|
|
scope = ["openid" "profile"];
|
|
response_type = "code";
|
|
issuer = "https://keycloak.example.com/auth/realms/My%20Realm";
|
|
discovery = true;
|
|
client_auth_method = "query";
|
|
uid_field = "preferred_username";
|
|
client_options = {
|
|
identifier = "gitlab";
|
|
secret = { _secret = "/var/keys/gitlab_oidc_secret"; };
|
|
redirect_uri = "https://git.example.com/users/auth/openid_connect/callback";
|
|
};
|
|
};
|
|
}
|
|
];
|
|
};
|
|
};
|
|
'';
|
|
description = ''
|
|
Extra options to be added under
|
|
<literal>production</literal> in
|
|
<filename>config/gitlab.yml</filename>, as a nix attribute
|
|
set.
|
|
|
|
Options containing secret data should be set to an attribute
|
|
set containing the attribute <literal>_secret</literal> - a
|
|
string pointing to a file containing the value the option
|
|
should be set to. See the example to get a better picture of
|
|
this: in the resulting
|
|
<filename>config/gitlab.yml</filename> file, the
|
|
<literal>production.omniauth.providers[0].args.client_options.secret</literal>
|
|
key will be set to the contents of the
|
|
<filename>/var/keys/gitlab_oidc_secret</filename> file.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
assertions = [
|
|
{
|
|
assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.databaseUsername);
|
|
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.'';
|
|
}
|
|
{
|
|
assertion = (cfg.databaseHost != "") -> (cfg.databasePasswordFile != null);
|
|
message = "When services.gitlab.databaseHost is customized, services.gitlab.databasePasswordFile must be set!";
|
|
}
|
|
{
|
|
assertion = cfg.initialRootPasswordFile != null;
|
|
message = "services.gitlab.initialRootPasswordFile must be set!";
|
|
}
|
|
{
|
|
assertion = cfg.secrets.secretFile != null;
|
|
message = "services.gitlab.secrets.secretFile must be set!";
|
|
}
|
|
{
|
|
assertion = cfg.secrets.dbFile != null;
|
|
message = "services.gitlab.secrets.dbFile must be set!";
|
|
}
|
|
{
|
|
assertion = cfg.secrets.otpFile != null;
|
|
message = "services.gitlab.secrets.otpFile must be set!";
|
|
}
|
|
{
|
|
assertion = cfg.secrets.jwsFile != null;
|
|
message = "services.gitlab.secrets.jwsFile must be set!";
|
|
}
|
|
];
|
|
|
|
environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
|
|
|
|
systemd.targets.gitlab = {
|
|
description = "Common target for all GitLab services.";
|
|
wantedBy = [ "multi-user.target" ];
|
|
};
|
|
|
|
# Redis is required for the sidekiq queue runner.
|
|
services.redis.enable = mkDefault true;
|
|
|
|
# We use postgres as the main data store.
|
|
services.postgresql = optionalAttrs databaseActuallyCreateLocally {
|
|
enable = true;
|
|
ensureUsers = singleton { name = cfg.databaseUsername; };
|
|
};
|
|
|
|
# The postgresql module doesn't currently support concepts like
|
|
# objects owners and extensions; for now we tack on what's needed
|
|
# here.
|
|
systemd.services.gitlab-postgresql = let pgsql = config.services.postgresql; in mkIf databaseActuallyCreateLocally {
|
|
after = [ "postgresql.service" ];
|
|
bindsTo = [ "postgresql.service" ];
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
path = [
|
|
pgsql.package
|
|
pkgs.util-linux
|
|
];
|
|
script = ''
|
|
set -eu
|
|
|
|
PSQL() {
|
|
psql --port=${toString pgsql.port} "$@"
|
|
}
|
|
|
|
PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
|
|
current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
|
|
if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
|
|
PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
|
|
if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
|
|
echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
|
|
exit 1
|
|
fi
|
|
touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
|
|
PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
|
|
rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
|
|
fi
|
|
PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
|
|
PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS btree_gist;"
|
|
'';
|
|
|
|
serviceConfig = {
|
|
User = pgsql.superUser;
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
};
|
|
};
|
|
|
|
# Use postfix to send out mails.
|
|
services.postfix.enable = mkDefault (cfg.smtp.enable && cfg.smtp.address == "localhost");
|
|
|
|
users.users.${cfg.user} =
|
|
{ group = cfg.group;
|
|
home = "${cfg.statePath}/home";
|
|
shell = "${pkgs.bash}/bin/bash";
|
|
uid = config.ids.uids.gitlab;
|
|
};
|
|
|
|
users.groups.${cfg.group}.gid = config.ids.gids.gitlab;
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d /run/gitlab 0755 ${cfg.user} ${cfg.group} -"
|
|
"d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -"
|
|
"z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.backup.path} 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/shell 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/tmp 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/tmp/pids 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/tmp/sockets 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/uploads 0700 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/custom_hooks 0700 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/custom_hooks/pre-receive.d 0700 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/custom_hooks/post-receive.d 0700 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.statePath}/custom_hooks/update.d 0700 ${cfg.user} ${cfg.group} -"
|
|
"d ${gitlabConfig.production.shared.path} 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${gitlabConfig.production.shared.path}/artifacts 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -"
|
|
"L+ /run/gitlab/config - - - - ${cfg.statePath}/config"
|
|
"L+ /run/gitlab/log - - - - ${cfg.statePath}/log"
|
|
"L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp"
|
|
"L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads"
|
|
|
|
"L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
|
|
];
|
|
|
|
|
|
systemd.services.gitlab-config = {
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
path = with pkgs; [
|
|
jq
|
|
openssl
|
|
replace
|
|
git
|
|
];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
TimeoutSec = "infinity";
|
|
Restart = "on-failure";
|
|
WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
|
|
RemainAfterExit = true;
|
|
|
|
ExecStartPre = let
|
|
preStartFullPrivileges = ''
|
|
shopt -s dotglob nullglob
|
|
set -eu
|
|
|
|
chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/*
|
|
if [[ -n "$(ls -A '${cfg.statePath}'/config/)" ]]; then
|
|
chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/*
|
|
fi
|
|
'';
|
|
in "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}";
|
|
|
|
ExecStart = pkgs.writeShellScript "gitlab-config" ''
|
|
set -eu
|
|
|
|
umask u=rwx,g=rx,o=
|
|
|
|
cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
|
|
rm -rf ${cfg.statePath}/db/*
|
|
rm -f ${cfg.statePath}/lib
|
|
find '${cfg.statePath}/config/' -maxdepth 1 -mindepth 1 -type d -execdir rm -rf {} \;
|
|
cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
|
|
cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
|
|
ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
|
|
|
|
${cfg.packages.gitlab-shell}/bin/install
|
|
|
|
${optionalString cfg.smtp.enable ''
|
|
install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
|
|
${optionalString (cfg.smtp.passwordFile != null) ''
|
|
smtp_password=$(<'${cfg.smtp.passwordFile}')
|
|
replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb'
|
|
''}
|
|
''}
|
|
|
|
(
|
|
umask u=rwx,g=,o=
|
|
|
|
openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
|
|
|
|
rm -f '${cfg.statePath}/config/database.yml'
|
|
|
|
${if cfg.databasePasswordFile != null then ''
|
|
export db_password="$(<'${cfg.databasePasswordFile}')"
|
|
|
|
if [[ -z "$db_password" ]]; then
|
|
>&2 echo "Database password was an empty string!"
|
|
exit 1
|
|
fi
|
|
|
|
jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
|
|
'.production.password = $ENV.db_password' \
|
|
>'${cfg.statePath}/config/database.yml'
|
|
''
|
|
else ''
|
|
jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
|
|
>'${cfg.statePath}/config/database.yml'
|
|
''
|
|
}
|
|
|
|
${utils.genJqSecretsReplacementSnippet
|
|
gitlabConfig
|
|
"${cfg.statePath}/config/gitlab.yml"
|
|
}
|
|
|
|
rm -f '${cfg.statePath}/config/secrets.yml'
|
|
|
|
export secret="$(<'${cfg.secrets.secretFile}')"
|
|
export db="$(<'${cfg.secrets.dbFile}')"
|
|
export otp="$(<'${cfg.secrets.otpFile}')"
|
|
export jws="$(<'${cfg.secrets.jwsFile}')"
|
|
jq -n '{production: {secret_key_base: $ENV.secret,
|
|
otp_key_base: $ENV.otp,
|
|
db_key_base: $ENV.db,
|
|
openid_connect_signing_key: $ENV.jws}}' \
|
|
> '${cfg.statePath}/config/secrets.yml'
|
|
)
|
|
|
|
# We remove potentially broken links to old gitlab-shell versions
|
|
rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
|
|
|
|
git config --global core.autocrlf "input"
|
|
'';
|
|
};
|
|
};
|
|
|
|
systemd.services.gitlab-db-config = {
|
|
after = [ "gitlab-config.service" "gitlab-postgresql.service" "postgresql.service" ];
|
|
bindsTo = [
|
|
"gitlab-config.service"
|
|
] ++ optional (cfg.databaseHost == "") "postgresql.service"
|
|
++ optional databaseActuallyCreateLocally "gitlab-postgresql.service";
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
TimeoutSec = "infinity";
|
|
Restart = "on-failure";
|
|
WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
|
|
RemainAfterExit = true;
|
|
|
|
ExecStart = pkgs.writeShellScript "gitlab-db-config" ''
|
|
set -eu
|
|
umask u=rwx,g=rx,o=
|
|
|
|
initial_root_password="$(<'${cfg.initialRootPasswordFile}')"
|
|
${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \
|
|
GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null
|
|
'';
|
|
};
|
|
};
|
|
|
|
systemd.services.gitlab-sidekiq = {
|
|
after = [
|
|
"network.target"
|
|
"redis.service"
|
|
"postgresql.service"
|
|
"gitlab-config.service"
|
|
"gitlab-db-config.service"
|
|
];
|
|
bindsTo = [
|
|
"redis.service"
|
|
"gitlab-config.service"
|
|
"gitlab-db-config.service"
|
|
] ++ optional (cfg.databaseHost == "") "postgresql.service";
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
environment = gitlabEnv;
|
|
path = with pkgs; [
|
|
postgresqlPackage
|
|
git
|
|
ruby
|
|
openssh
|
|
nodejs
|
|
gnupg
|
|
|
|
# Needed for GitLab project imports
|
|
gnutar
|
|
gzip
|
|
];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
TimeoutSec = "infinity";
|
|
Restart = "on-failure";
|
|
WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
|
|
ExecStart="${cfg.packages.gitlab.rubyEnv}/bin/sidekiq -C \"${cfg.packages.gitlab}/share/gitlab/config/sidekiq_queues.yml\" -e production";
|
|
};
|
|
};
|
|
|
|
systemd.services.gitaly = {
|
|
after = [ "network.target" "gitlab-config.service" ];
|
|
bindsTo = [ "gitlab-config.service" ];
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
path = with pkgs; [
|
|
openssh
|
|
procps # See https://gitlab.com/gitlab-org/gitaly/issues/1562
|
|
git
|
|
cfg.packages.gitaly.rubyEnv
|
|
cfg.packages.gitaly.rubyEnv.wrappedRuby
|
|
gzip
|
|
bzip2
|
|
];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
TimeoutSec = "infinity";
|
|
Restart = "on-failure";
|
|
WorkingDirectory = gitlabEnv.HOME;
|
|
ExecStart = "${cfg.packages.gitaly}/bin/gitaly ${gitalyToml}";
|
|
};
|
|
};
|
|
|
|
systemd.services.gitlab-pages = mkIf (gitlabConfig.production.pages.enabled or false) {
|
|
description = "GitLab static pages daemon";
|
|
after = [ "network.target" "gitlab-config.service" ];
|
|
bindsTo = [ "gitlab-config.service" ];
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
|
|
path = [ pkgs.unzip ];
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
TimeoutSec = "infinity";
|
|
Restart = "on-failure";
|
|
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
|
|
ExecStart = "${cfg.packages.pages}/bin/gitlab-pages ${escapeShellArgs pagesArgs}";
|
|
WorkingDirectory = gitlabEnv.HOME;
|
|
};
|
|
};
|
|
|
|
systemd.services.gitlab-workhorse = {
|
|
after = [ "network.target" ];
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
path = with pkgs; [
|
|
exiftool
|
|
git
|
|
gnutar
|
|
gzip
|
|
openssh
|
|
gitlab-workhorse
|
|
];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
TimeoutSec = "infinity";
|
|
Restart = "on-failure";
|
|
WorkingDirectory = gitlabEnv.HOME;
|
|
ExecStart =
|
|
"${cfg.packages.gitlab-workhorse}/bin/gitlab-workhorse "
|
|
+ "-listenUmask 0 "
|
|
+ "-listenNetwork unix "
|
|
+ "-listenAddr /run/gitlab/gitlab-workhorse.socket "
|
|
+ "-authSocket ${gitlabSocket} "
|
|
+ "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public "
|
|
+ "-secretPath ${cfg.statePath}/.gitlab_workhorse_secret";
|
|
};
|
|
};
|
|
|
|
systemd.services.gitlab-mailroom = mkIf (gitlabConfig.production.incoming_email.enabled or false) {
|
|
description = "GitLab incoming mail daemon";
|
|
after = [ "network.target" "redis.service" "gitlab-config.service" ];
|
|
bindsTo = [ "gitlab-config.service" ];
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
environment = gitlabEnv;
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
TimeoutSec = "infinity";
|
|
Restart = "on-failure";
|
|
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.statePath}/config/mail_room.yml";
|
|
WorkingDirectory = gitlabEnv.HOME;
|
|
};
|
|
};
|
|
|
|
systemd.services.gitlab = {
|
|
after = [
|
|
"gitlab-workhorse.service"
|
|
"network.target"
|
|
"redis.service"
|
|
"gitlab-config.service"
|
|
"gitlab-db-config.service"
|
|
];
|
|
bindsTo = [
|
|
"redis.service"
|
|
"gitlab-config.service"
|
|
"gitlab-db-config.service"
|
|
] ++ optional (cfg.databaseHost == "") "postgresql.service";
|
|
wantedBy = [ "gitlab.target" ];
|
|
partOf = [ "gitlab.target" ];
|
|
environment = gitlabEnv;
|
|
path = with pkgs; [
|
|
postgresqlPackage
|
|
git
|
|
openssh
|
|
nodejs
|
|
procps
|
|
gnupg
|
|
];
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
TimeoutSec = "infinity";
|
|
Restart = "on-failure";
|
|
WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
|
|
ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/puma -C ${cfg.statePath}/config/puma.rb -e production";
|
|
};
|
|
|
|
};
|
|
|
|
systemd.services.gitlab-backup = {
|
|
after = [ "gitlab.service" ];
|
|
bindsTo = [ "gitlab.service" ];
|
|
startAt = cfg.backup.startAt;
|
|
environment = {
|
|
RAILS_ENV = "production";
|
|
CRON = "1";
|
|
} // optionalAttrs (stringLength cfg.backup.skip > 0) {
|
|
SKIP = cfg.backup.skip;
|
|
};
|
|
serviceConfig = {
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
ExecStart = "${gitlab-rake}/bin/gitlab-rake gitlab:backup:create";
|
|
};
|
|
};
|
|
|
|
};
|
|
|
|
meta.doc = ./gitlab.xml;
|
|
|
|
}
|