1{ config, lib, pkgs, ... }:
2
3# TODO: support non-postgresql
4
5with lib;
6
7let
8 cfg = config.services.gitlab;
9
10 ruby = pkgs.gitlab.ruby;
11 bundler = pkgs.bundler;
12
13 gemHome = "${pkgs.gitlab.env}/${ruby.gemPath}";
14
15 databaseYml = ''
16 production:
17 adapter: postgresql
18 database: ${cfg.databaseName}
19 host: ${cfg.databaseHost}
20 password: ${cfg.databasePassword}
21 username: ${cfg.databaseUsername}
22 encoding: utf8
23 '';
24 gitlabShellYml = ''
25 user: gitlab
26 gitlab_url: "http://${cfg.host}:${toString cfg.port}/"
27 http_settings:
28 self_signed_cert: false
29 repos_path: "${cfg.stateDir}/repositories"
30 log_file: "${cfg.stateDir}/log/gitlab-shell.log"
31 redis:
32 bin: ${pkgs.redis}/bin/redis-cli
33 host: 127.0.0.1
34 port: 6379
35 database: 0
36 namespace: resque:gitlab
37 '';
38
39 unicornConfig = builtins.readFile ./defaultUnicornConfig.rb;
40
41 gitlab-runner = pkgs.stdenv.mkDerivation rec {
42 name = "gitlab-runner";
43 buildInputs = [ pkgs.gitlab pkgs.bundler pkgs.makeWrapper ];
44 phases = "installPhase fixupPhase";
45 buildPhase = "";
46 installPhase = ''
47 mkdir -p $out/bin
48 makeWrapper ${bundler}/bin/bundle $out/bin/gitlab-runner\
49 --set RAKEOPT '"-f ${pkgs.gitlab}/share/gitlab/Rakefile"'\
50 --set GEM_HOME '${gemHome}'\
51 --set UNICORN_PATH "${cfg.stateDir}/"\
52 --set GITLAB_PATH "${pkgs.gitlab}/share/gitlab/"\
53 --set GITLAB_APPLICATION_LOG_PATH "${cfg.stateDir}/log/application.log"\
54 --set GITLAB_SATELLITES_PATH "${cfg.stateDir}/satellites"\
55 --set GITLAB_SHELL_PATH "${pkgs.gitlab-shell}"\
56 --set GITLAB_REPOSITORIES_PATH "${cfg.stateDir}/repositories"\
57 --set GITLAB_SHELL_HOOKS_PATH "${cfg.stateDir}/shell/hooks"\
58 --set BUNDLE_GEMFILE "${pkgs.gitlab}/share/gitlab/Gemfile"\
59 --set GITLAB_EMAIL_FROM "${cfg.emailFrom}"\
60 --set GITLAB_SHELL_CONFIG_PATH "${cfg.stateDir}/shell/config.yml"\
61 --set GITLAB_SHELL_SECRET_PATH "${cfg.stateDir}/config/gitlab_shell_secret"\
62 --set GITLAB_HOST "${cfg.host}"\
63 --set GITLAB_PORT "${toString cfg.port}"\
64 --set GITLAB_BACKUP_PATH "${cfg.backupPath}"\
65 --set RAILS_ENV "production"
66 '';
67 };
68
69in {
70
71 options = {
72 services.gitlab = {
73 enable = mkOption {
74 type = types.bool;
75 default = false;
76 description = ''
77 Enable the gitlab service.
78 '';
79 };
80
81 satelliteDir = mkOption {
82 type = types.str;
83 default = "/var/gitlab/git-satellites";
84 description = "Gitlab directory to store checked out git trees requires for operation.";
85 };
86
87 stateDir = mkOption {
88 type = types.str;
89 default = "/var/gitlab/state";
90 description = "Gitlab state directory, logs are stored here.";
91 };
92
93 backupPath = mkOption {
94 type = types.str;
95 default = cfg.stateDir + "/backup";
96 description = "Gitlab path for backups.";
97 };
98
99 databaseHost = mkOption {
100 type = types.str;
101 default = "127.0.0.1";
102 description = "Gitlab database hostname.";
103 };
104
105 databasePassword = mkOption {
106 type = types.str;
107 default = "";
108 description = "Gitlab database user password.";
109 };
110
111 databaseName = mkOption {
112 type = types.str;
113 default = "gitlab";
114 description = "Gitlab database name.";
115 };
116
117 databaseUsername = mkOption {
118 type = types.str;
119 default = "gitlab";
120 description = "Gitlab database user.";
121 };
122
123 emailFrom = mkOption {
124 type = types.str;
125 default = "example@example.org";
126 description = "The source address for emails sent by gitlab.";
127 };
128
129 host = mkOption {
130 type = types.str;
131 default = config.networking.hostName;
132 description = "Gitlab host name. Used e.g. for copy-paste URLs.";
133 };
134
135 port = mkOption {
136 type = types.int;
137 default = 8080;
138 description = "Gitlab server listening port.";
139 };
140 };
141 };
142
143 config = mkIf cfg.enable {
144
145 environment.systemPackages = [ gitlab-runner pkgs.gitlab-shell ];
146
147 assertions = [
148 { assertion = cfg.databasePassword != "";
149 message = "databasePassword must be set";
150 }
151 ];
152
153 # Redis is required for the sidekiq queue runner.
154 services.redis.enable = mkDefault true;
155 # We use postgres as the main data store.
156 services.postgresql.enable = mkDefault true;
157 services.postgresql.package = mkDefault pkgs.postgresql;
158 # Use postfix to send out mails.
159 services.postfix.enable = mkDefault true;
160
161 users.extraUsers = [
162 { name = "gitlab";
163 group = "gitlab";
164 home = "${cfg.stateDir}/home";
165 shell = "${pkgs.bash}/bin/bash";
166 uid = config.ids.uids.gitlab;
167 } ];
168
169 users.extraGroups = [
170 { name = "gitlab";
171 gid = config.ids.gids.gitlab;
172 } ];
173
174 systemd.services.gitlab-sidekiq = {
175 after = [ "network.target" "redis.service" ];
176 wantedBy = [ "multi-user.target" ];
177 environment.HOME = "${cfg.stateDir}/home";
178 environment.GEM_HOME = gemHome;
179 environment.UNICORN_PATH = "${cfg.stateDir}/";
180 environment.GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/";
181 environment.GITLAB_APPLICATION_LOG_PATH = "${cfg.stateDir}/log/application.log";
182 environment.GITLAB_SATELLITES_PATH = "${cfg.stateDir}/satellites";
183 environment.GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}";
184 environment.GITLAB_REPOSITORIES_PATH = "${cfg.stateDir}/repositories";
185 environment.GITLAB_SHELL_HOOKS_PATH = "${cfg.stateDir}/shell/hooks";
186 environment.BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile";
187 environment.GITLAB_EMAIL_FROM = "${cfg.emailFrom}";
188 environment.GITLAB_SHELL_CONFIG_PATH = "${cfg.stateDir}/shell/config.yml";
189 environment.GITLAB_SHELL_SECRET_PATH = "${cfg.stateDir}/config/gitlab_shell_secret";
190 environment.GITLAB_HOST = "${cfg.host}";
191 environment.GITLAB_PORT = "${toString cfg.port}";
192 environment.GITLAB_DATABASE_HOST = "${cfg.databaseHost}";
193 environment.GITLAB_DATABASE_PASSWORD = "${cfg.databasePassword}";
194 environment.RAILS_ENV = "production";
195 path = with pkgs; [
196 config.services.postgresql.package
197 gitAndTools.git
198 ruby
199 openssh
200 nodejs
201 ];
202 serviceConfig = {
203 Type = "simple";
204 User = "gitlab";
205 Group = "gitlab";
206 TimeoutSec = "300";
207 WorkingDirectory = "${pkgs.gitlab}/share/gitlab";
208 ExecStart="${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\"";
209 };
210 };
211
212 systemd.services.gitlab = {
213 after = [ "network.target" "postgresql.service" "redis.service" ];
214 wantedBy = [ "multi-user.target" ];
215 environment.HOME = "${cfg.stateDir}/home";
216 environment.GEM_HOME = gemHome;
217 environment.UNICORN_PATH = "${cfg.stateDir}/";
218 environment.GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/";
219 environment.GITLAB_APPLICATION_LOG_PATH = "${cfg.stateDir}/log/application.log";
220 environment.GITLAB_SATELLITES_PATH = "${cfg.stateDir}/satellites";
221 environment.GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}";
222 environment.GITLAB_REPOSITORIES_PATH = "${cfg.stateDir}/repositories";
223 environment.GITLAB_SHELL_HOOKS_PATH = "${cfg.stateDir}/shell/hooks";
224 environment.BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile";
225 environment.GITLAB_EMAIL_FROM = "${cfg.emailFrom}";
226 environment.GITLAB_HOST = "${cfg.host}";
227 environment.GITLAB_PORT = "${toString cfg.port}";
228 environment.GITLAB_DATABASE_HOST = "${cfg.databaseHost}";
229 environment.GITLAB_DATABASE_PASSWORD = "${cfg.databasePassword}";
230 environment.RAILS_ENV = "production";
231 path = with pkgs; [
232 config.services.postgresql.package
233 gitAndTools.git
234 ruby
235 openssh
236 nodejs
237 ];
238 preStart = ''
239 # TODO: use env vars
240 mkdir -p ${cfg.stateDir}
241 mkdir -p ${cfg.stateDir}/log
242 mkdir -p ${cfg.stateDir}/satellites
243 mkdir -p ${cfg.stateDir}/repositories
244 mkdir -p ${cfg.stateDir}/shell/hooks
245 mkdir -p ${cfg.stateDir}/tmp/pids
246 mkdir -p ${cfg.stateDir}/tmp/sockets
247 rm -rf ${cfg.stateDir}/config
248 mkdir -p ${cfg.stateDir}/config
249 # TODO: What exactly is gitlab-shell doing with the secret?
250 head -c 20 /dev/urandom > ${cfg.stateDir}/config/gitlab_shell_secret
251 mkdir -p ${cfg.stateDir}/home/.ssh
252 touch ${cfg.stateDir}/home/.ssh/authorized_keys
253
254 cp -rf ${pkgs.gitlab}/share/gitlab/config ${cfg.stateDir}/
255 cp ${pkgs.gitlab}/share/gitlab/VERSION ${cfg.stateDir}/VERSION
256
257 ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.stateDir}/config/database.yml
258 ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.stateDir}/config/unicorn.rb
259
260 chown -R gitlab:gitlab ${cfg.stateDir}/
261 chmod -R 755 ${cfg.stateDir}/
262
263 if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
264 if ! test -e "${cfg.stateDir}/db-created"; then
265 psql postgres -c "CREATE ROLE gitlab WITH LOGIN NOCREATEDB NOCREATEROLE NOCREATEUSER ENCRYPTED PASSWORD '${cfg.databasePassword}'"
266 ${config.services.postgresql.package}/bin/createdb --owner gitlab gitlab || true
267 touch "${cfg.stateDir}/db-created"
268
269 # force=yes disables the manual-interaction yes/no prompt
270 # which breaks without an stdin.
271 force=yes ${bundler}/bin/bundle exec rake -f ${pkgs.gitlab}/share/gitlab/Rakefile gitlab:setup RAILS_ENV=production
272 fi
273 fi
274
275 # Install the shell required to push repositories
276 ln -fs ${pkgs.writeText "config.yml" gitlabShellYml} ${cfg.stateDir}/shell/config.yml
277 export GITLAB_SHELL_CONFIG_PATH=""${cfg.stateDir}/shell/config.yml
278 ${pkgs.gitlab-shell}/bin/install
279
280 # Change permissions in the last step because some of the
281 # intermediary scripts like to create directories as root.
282 chown -R gitlab:gitlab ${cfg.stateDir}/
283 chmod -R 755 ${cfg.stateDir}/
284 '';
285
286 serviceConfig = {
287 PermissionsStartOnly = true; # preStart must be run as root
288 Type = "simple";
289 User = "gitlab";
290 Group = "gitlab";
291 TimeoutSec = "300";
292 WorkingDirectory = "${pkgs.gitlab}/share/gitlab";
293 ExecStart="${bundler}/bin/bundle exec \"unicorn -c ${cfg.stateDir}/config/unicorn.rb -E production\"";
294 };
295
296 };
297
298 };
299
300}