gitlab: Set up unicorn service
[slapos.git] / software / gitlab / instance-gitlab.cfg.in
1 # GitLab instance
2 # NOTE instance/software layout is inspired by gitlab omnibus
3 # NOTE all services are interconnected via unix sockets - because of easier
4 #      security and performance reasons (unix has 2x less latency and more
5 #      throughput compared to tcp over loopback).
6 [buildout]
7 extends = {{ gitlab_parameters_cfg }}
8 parts =
9     directory
10
11 #   gitlab-<prog>
12 # ? mailroom
13 {% set gitlab_progv = 'rails rake unicorn sidekiq' .split() %}
14 {% for prog in gitlab_progv %}
15     gitlab-{{ prog }}
16 {% endfor %}
17
18     gitlab-work
19     gitlab-shell-work
20
21     service-unicorn
22
23     service-postgresql
24     service-redis
25
26     service-cron
27
28 # std stuff for slapos instance
29 eggs-directory = {{ eggs_directory }}
30 develop-eggs-directory = {{ develop_eggs_directory }}
31 offline = true
32
33
34 ##################################
35 #   GitLab instance parameters   #
36 ##################################
37
38 [instance-parameter]
39 # std stuff to fetch slapos instance parameters
40 recipe  = slapos.cookbook:slapconfiguration
41 computer= ${slap-connection:computer-id}
42 partition=${slap-connection:partition-id}
43 url     = ${slap-connection:server-url}
44 key     = ${slap-connection:key-file}
45 cert    = ${slap-connection:cert-file}
46
47 # autogenerated gitlab instance parameters
48 <= gitlab-parameters
49
50 # adjust/override some default settings:
51
52 # automatically load all available CPUs
53 configuration.unicorn_worker_processes  = {{ multiprocessing.cpu_count() + 1 }}
54
55
56
57 # for convenience
58 [backend-info]
59 # current slapuserX
60 user    = {{ pwd.getpwuid(os.getuid())[0] }}
61
62
63
64 #############################
65 #   GitLab instance setup   #
66 #############################
67
68 # 1. directories
69 [directory]
70 recipe  = slapos.cookbook:mkdirectory
71 home    = ${buildout:directory}
72 bin     = ${:home}/bin
73 etc     = ${:home}/etc
74 var     = ${:home}/var
75 log     = ${:var}/log
76 run     = ${:var}/run
77 srv     = ${:home}/srv
78 # slapos startup/service/promise scripts live here:
79 startup = ${:etc}/run
80 service = ${:etc}/service
81 promise = ${:etc}/promise
82 promise.slow = ${:promise}.slow
83
84 # gitlab: etc/ log/ ...
85 [gitlab-dir]
86 recipe  = slapos.cookbook:mkdirectory
87 etc     = ${directory:etc}/gitlab
88 log     = ${directory:log}/gitlab
89
90 var     = ${directory:var}/gitlab
91 tmp     = ${:var}/tmp
92 uploads = ${:var}/uploads
93 assets  = ${:var}/assets
94 backup  = ${directory:var}/backup
95
96 [gitlab-repo-dir]
97 recipe  = slapos.cookbook:mkdirectory
98 repositories    = ${directory:var}/repositories
99 # gitlab wants it to be drwxrws---
100 # FIXME setting such mode with :mkdirectory is not possible, because mkdir(2)
101 # does & 0777 and also there is umask. So we workaround:
102 [gitlab-repo-xdir]
103 recipe  = plone.recipe.command
104 stop-on-error = yes
105 repositories = ${gitlab-repo-dir:repositories}
106 command = chmod 02770 ${:repositories}
107
108 [gitlab]
109 etc     = ${gitlab-dir:etc}
110 log     = ${gitlab-dir:log}
111 var     = ${gitlab-dir:var}
112 tmp     = ${gitlab-dir:tmp}
113 uploads = ${gitlab-dir:uploads}
114 assets  = ${gitlab-dir:assets}
115 backup  = ${gitlab-dir:backup}
116 repositories = ${gitlab-repo-xdir:repositories}
117
118
119 # gitlab-shell: etc/ log/ gitlab_shell_secret ...
120 [gitlab-shell-dir]
121 recipe  = slapos.cookbook:mkdirectory
122 etc     = ${directory:etc}/gitlab-shell
123 log     = ${directory:log}/gitlab-shell
124
125 [gitlab-shell]
126 etc     = ${gitlab-shell-dir:etc}
127 log     = ${gitlab-shell-dir:log}
128 secret  = ${secrets:secrets}/gitlab_shell_secret
129
130
131 # place to keep all secrets
132 [secrets]
133 recipe  = slapos.cookbook:mkdirectory
134 secrets = ${directory:var}/secrets
135 mode    = 0700
136
137
138
139
140 # 2. configuration files
141 [etc-template]
142 recipe  = slapos.recipe.template:jinja2
143 extensions = jinja2.ext.do
144 mode    = 0640
145 import-list =
146     rawfile macrolib.cfg.in     {{ macrolib_cfg_in }}
147 context =
148     raw     autogenerated       # This file was autogenerated. (DO NOT EDIT - changes will be lost)
149     section instance_parameter  instance-parameter
150     section backend_info        backend-info
151     import  urlparse            urlparse
152     raw     git                 {{ git }}
153     ${:context-extra}
154 context-extra =
155
156 [gitlab-etc-template]
157 <= etc-template
158 rendered= ${gitlab:etc}/${:_buildout_section_name_}
159
160
161 [config.ru]
162 <= gitlab-etc-template
163 template = {{ config_ru_in }}
164
165 [database.yml]
166 <= gitlab-etc-template
167 template= {{ database_yml_in }}
168 context-extra =
169     section pgsql                   service-postgresql
170
171 [gitlab-shell-config.yml]
172 <= etc-template
173 template= {{ gitlab_shell_config_yml_in }}
174 rendered= ${gitlab-shell:etc}/config.yml
175 context-extra =
176     import  urllib                  urllib
177     section gitlab                  gitlab
178     section gitlab_shell            gitlab-shell
179     section unicorn                 unicorn
180     section service_redis           service-redis
181     raw     redis_binprefix         {{ redis_binprefix }}
182
183 [gitlab.yml]
184 <= gitlab-etc-template
185 template= {{ gitlab_yml_in }}
186 context-extra =
187     section gitlab                  gitlab
188     section gitlab_shell            gitlab-shell
189     section gitlab_shell_work       gitlab-shell-work
190
191 [rack_attack.rb]
192 <= gitlab-etc-template
193 template = {{ rack_attack_rb_in }}
194
195 [resque.yml]
196 <= gitlab-etc-template
197 template= {{ resque_yml_in }}
198 context-extra =
199     section redis                   service-redis
200
201 [smtp_settings.rb]
202 <= gitlab-etc-template
203 template= {{ smtp_settings_rb_in }}
204 # contains smtp password
205 mode    = 0600
206
207 [unicorn.rb]
208 <= gitlab-etc-template
209 template = {{ unicorn_rb_in }}
210 context-extra =
211     section unicorn                 unicorn
212     section directory               directory
213     section gitlab_work             gitlab-work
214
215
216
217 # 3. bin/
218 #   gitlab-<prog>
219 [gitlab-bin]
220 recipe  = slapos.cookbook:wrapper
221 wrapper-path = ${directory:bin}/${:_buildout_section_name_}
222 environment  =
223     BUNDLE_GEMFILE = {{ gitlab_repository_location }}/Gemfile
224     RAILS_ENV = production
225
226 # NOTE sys.argv[1:] implicitly appended
227 # (by slapos.recipe.librecipe.execute.generic_exec() at runtime)
228 command-line =
229     {{ bundler_4gitlab }} exec sh -c
230     'cd ${gitlab-work:location} && ${:prog} "$@"' ${:prog}
231
232 {% for prog in gitlab_progv %}
233 [gitlab-{{ prog }}]
234 <= gitlab-bin
235 prog    = {{ prog }}
236 {% endfor %}
237
238
239 # 4. gitlab- & gitlab-shell- work directories
240 #
241 # Gitlab/Rails operation is tightened that config/ lives inside code, which goes
242 # against having ability to create several instances configured differently
243 # from 1 SR.
244 #
245 # One possibility to overcome this could be to make another Gitlab root
246 # symbolically linked to original SR _and_ several configuration files
247 # symbolically linked to instance place. Unfortunately this does not work -
248 # Ruby determines realpath on module import and Gitlab and Rails lookup config
249 # files relative to imported modules.
250 #
251 # we clone cloned gitlab and add proper links to vendor/bundle and instance
252 # config files.
253 # XXX there is no need for full clone - we only need worktree checkout (a-la `git
254 # worktree add`, but without creating files in original clone)
255 #
256 # This way Gitlab/Rails still think they work in 1 code / 1 instance way,
257 # and we can reuse SR.
258 # XXX better do such tricks with bind mounting, but that requires user namespaces
259
260 [work-base]
261 recipe  = plone.recipe.command
262 stop-on-error = yes
263 location = ${directory:home}/${:_buildout_section_name_}
264 command =
265 # make sure we start from well-defined empty state
266 # (needed e.g. if previous install failed in the middle)
267     rm -rf ${:location}  &&
268 # init work repository and add `software` remote pointing to main repo in SR software/...
269     {{ git }} init ${:location}  &&
270     cd ${:location}  &&
271     {{ git }} remote add software ${:software}  &&
272     ${:update-command}
273
274 update-command =
275     cd ${:location}  &&
276     {{ git }} fetch software  &&
277     {{ git }} reset --hard `cd ${:software} && {{ git }} rev-parse HEAD`  &&
278     ${:tune-command}
279
280
281 # NOTE there is no need to link/create .gitlab_shell_secret - we set path to it
282 # in gitlab & gitlab-shell configs, and gitlab creates it on its first start
283 [gitlab-work]
284 <= work-base
285 software = {{ gitlab_repository_location }}
286 tune-command =
287 # secret* config.ru tmp/ log/
288     rm -f .secret  &&
289     rm -f config.ru  &&
290     rm -rf log tmp  &&
291     ln -sf ${secrets:secrets}/gitlab_rails_secret .secret  &&
292     ln -sf ${config.ru:rendered} config.ru  &&
293     ln -sf ${gitlab:log} log  &&
294     ln -sf ${gitlab:tmp} tmp  &&
295 # config/
296     cd config  &&
297     ln -sf ${unicorn.rb:rendered} unicorn.rb  &&
298     ln -sf ${gitlab.yml:rendered} gitlab.yml  &&
299     ln -sf ${database.yml:rendered} database.yml  &&
300     ln -sf ${resque.yml:rendered} resque.yml  &&
301     ln -sf ${secrets:secrets}/gitlab_secrets.yml secrets.yml  &&
302 # config/initializers/
303     cd initializers  &&
304     ln -sf ${rack_attack.rb:rendered} rack_attack.rb  &&
305     ln -sf ${smtp_settings.rb:rendered} smtp_settings.rb  &&
306 # public/
307     cd ../../public  &&
308     rm -rf uploads assets  &&
309     ln -sf ${gitlab:uploads} uploads  &&
310     ln -sf ${gitlab:assets} assets  &&
311     true
312
313
314 # ----//---- for gitlab-shell
315 [gitlab-shell-work]
316 <= work-base
317 software = {{ gitlab_shell_repository_location }}
318
319 tune-command =
320     ln -sf ${gitlab-shell-config.yml:rendered}   config.yml  &&
321     true
322
323
324
325 # 5. services
326
327 # [promise-<something>] to generate promise wrapper <something>
328 [promise-wrapper]
329 recipe  = slapos.cookbook:wrapper
330 wrapper-path = !py! '${directory:promise}/' + '${:_buildout_section_name_}'[8:]
331
332 # [promise-<something>] to check <something> by url
333 [promise-byurl]
334 recipe  = slapos.cookbook:check_url_available
335 path    = !py! '${directory:promise}/' + '${:_buildout_section_name_}'[8:]
336 dash_path   = {{ bash_bin }}
337 curl_path   = {{ curl_bin }}
338 http_code   = 200
339
340
341
342 #####################
343 #   Postgresql db   #
344 #####################
345
346 # XXX gitlab-omnibus also tunes:
347 # - shared_buffers
348 # - work_mem
349 # - checkpoint_*
350 # - effective_check_size
351 # - lc_* en_US.UTF-8 -> C  (?)
352 [service-postgresql]
353 recipe  = slapos.cookbook:postgres
354 bin     = {{ postgresql_location }}/bin
355 services= ${directory:service}
356
357 dbname  = gitlabhq_production
358 # NOTE db name must match to what was used in KVM on lab.nexedi.com (restore script grants access to this user)
359 superuser = gitlab-psql
360 # no password - pgsql will listen only on unix sockets (see below) thus access
361 # is protected with filesystem-level permissions.
362 # ( besides, if we use slapos.cookbook:generate.password and do `password = ...`
363 #   the password is stored in plain text in .installed and thus becomes insecure )
364 password=
365
366 pgdata-directory = ${directory:srv}/postgresql
367
368 # empty addresses - listen only on unix socket
369 ipv4    = !py!set([])
370 ipv6    = !py!set([])
371 ipv6-random =
372 port    =
373
374 depend  =
375     ${promise-postgresql:recipe}
376
377 [promise-postgresql]
378 <= promise-wrapper
379 command-line =
380     {{ postgresql_location }}/bin/psql
381         -h ${service-postgresql:pgdata-directory}
382         -U ${service-postgresql:superuser}
383         -d ${service-postgresql:dbname}
384         -c '\q'
385
386 # postgresql logs to stdout/stderr - logs are handled by slapos not us
387 # [logrotate-entry-postgresql]
388
389
390 #############
391 #   Redis   #
392 #############
393 [redis]
394 recipe  = slapos.cookbook:mkdirectory
395 srv     = ${directory:srv}/redis
396 log     = ${directory:log}/redis
397
398
399 [service-redis]
400 recipe  = slapos.cookbook:redis.server
401 wrapper = ${directory:service}/redis
402 promise_wrapper = ${directory:promise}/redis
403
404 server_dir  = ${redis:srv}
405 config_file = ${directory:etc}/redis.conf
406 log_file    = ${redis:log}/redis.log
407 pid_file    = ${directory:run}/redis.pid
408 use_passwd  = false
409 unixsocket  = ${:server_dir}/redis.socket
410 # port = 0 means "don't listen on TCP at all" - listen only on unix socket
411 ipv6    = ::1
412 port    = 0
413
414 server_bin  = {{ redis_binprefix }}/redis-server
415 depend  =
416     ${logrotate-entry-redis:recipe}
417
418
419 # NOTE slapos.cookbook:redis.server setups promise automatically
420
421 [logrotate-entry-redis]
422 <= logrotate-entry
423 log     = ${redis:log}/*.log
424
425
426 ######################
427 #   unicorn worker   #
428 ######################
429 [unicorn-dir]
430 recipe  = slapos.cookbook:mkdirectory
431 srv     = ${directory:srv}/unicorn
432 log     = ${directory:log}/unicorn
433
434 [unicorn]
435 srv     = ${unicorn-dir:srv}
436 log     = ${unicorn-dir:log}
437 socket  = ${:srv}/unicorn.socket
438
439 [service-unicorn]
440 recipe  = slapos.cookbook:wrapper
441 wrapper-path    = ${directory:service}/unicorn
442 command-line    = ${gitlab-unicorn:wrapper-path}
443     -E production
444     -c ${unicorn.rb:rendered}
445     ${gitlab-work:location}/config.ru
446
447
448 depend  =
449     ${promise-unicorn:recipe}
450     ${promise-gitlab-app:recipe}
451     ${promise-gitlab-shell:recipe}
452
453     ${logrotate-entry-unicorn:recipe}
454 # gitlab is a service "run" under unicorn
455 # gitlab-shell is called by gitlab
456 # -> associate their logs rotation to here
457     ${logrotate-entry-gitlab:recipe}
458
459
460 [promise-unicorn]
461 <= promise-byurl
462 url     = --unix-socket ${unicorn:socket}  http:/
463
464 [promise-rakebase]
465 recipe  = slapos.cookbook:wrapper
466 # FIXME gitlab-rake is too slow to load and promise timeouts
467 # that's why we instantiate to <promise>.slow/ (and this way promises are not run)
468 wrapper-path    = !py!'${directory:promise.slow}/' + '${:_buildout_section_name_}'[8:]
469 rake    = ${gitlab-rake:wrapper-path}
470
471
472 [promise-gitlab-app]
473 <= promise-rakebase
474 command-line    = ${:rake} gitlab:app:check
475
476 [promise-gitlab-shell]
477 <= promise-rakebase
478 command-line    = ${:rake} gitlab:gitlab_shell:check
479
480 # very slow
481 # rake gitlab:repo:check        (fsck all repos)
482
483
484 [logrotate-entry-unicorn]
485 <= logrotate-entry
486 log     = ${unicorn:log}/*.log
487
488 [logrotate-entry-gitlab]
489 <= logrotate-entry
490 log     = ${gitlab:log}/*.log
491
492 [logrotate-entry-gitlab-shell]
493 <= logrotate-entry
494 log     = ${gitlab-shell:log}/*.log
495
496
497
498 #############
499 #   cron    #
500 #############
501 [cron-dir]
502 recipe  = slapos.cookbook:mkdirectory
503 cron.d  = ${directory:etc}/cron.d
504 crontabs= ${directory:srv}/cron/crontabs
505 cronstamps = ${directory:var}/cron/cronstamps
506 log     = ${directory:log}/cron
507
508 [service-cron]
509 recipe  = slapos.cookbook:cron
510 binary  = ${directory:service}/crond
511 cron-entries    = ${cron-dir:cron.d}
512 crontabs        = ${cron-dir:crontabs}
513 cronstamps      = ${cron-dir:cronstamps}
514 catcher         = ${cron-simplelogger:wrapper}
515
516 dcrond-binary   = {{ dcron_bin }}
517
518 depends =
519     ${logrotate-entry-cron:recipe}
520
521 # "mailer" that cron uses to emit messages to logfile
522 [cron-simplelogger]
523 recipe  = slapos.cookbook:simplelogger
524 wrapper = ${directory:bin}/${:_buildout_section_name_}
525 log     = ${cron-dir:log}/cron.log
526
527
528 # base entry for clients who registers to cron
529 [cron-entry]
530 recipe  = slapos.cookbook:cron.d
531 # name  = <section-name>.strip_prefix('cron-entry-')
532 # XXX len() is not available in !py! - 11 hardcoded
533 name    = !py!'${:_buildout_section_name_}' [11:]
534 # NOTE _not_ ${service-cron:cron-entries}  - though the value is the same we do
535 # not want service-cron to be instantiated just if a cron-entry is registered.
536 cron-entries = ${cron-dir:cron.d}
537
538 # cron logs are also rotated
539 [logrotate-entry-cron]
540 <= logrotate-entry
541 log     = ${cron-dir:log}/*.log
542
543
544 #######################################
545 #   logrotate base for all services   #
546 #######################################
547 [logrotate-dir]
548 recipe  = slapos.cookbook:mkdirectory
549 srv     = ${directory:srv}/logrotate
550 entries = ${directory:etc}/logrotate.d
551
552 [logrotate]
553 recipe  = slapos.cookbook:logrotate
554 wrapper = ${directory:bin}/${:_buildout_section_name_}
555 conf    = ${directory:etc}/logrotate.conf
556 logrotate-entries   = ${logrotate-dir:entries}
557 state-file  = ${logrotate-dir:srv}/logrotate.status
558
559 logrotate-binary    = {{ logrotate_bin }}
560 gzip-binary     = {{ gzip_bin }}
561 gunzip-binary   = {{ gunzip_bin }}
562
563 depend  = ${cron-entry-logrotate:recipe}
564
565
566 # base entry for clients who registers to logrotate
567 [logrotate-entry]
568 recipe  = slapos.cookbook:logrotate.d
569 logrotate-entries   = ${logrotate:logrotate-entries}
570 # name  = <section-name>.strip_prefix('logrotate-entry-')
571 # XXX len is not available in !py! - 16 hardcoded
572 name    = !py!'${:_buildout_section_name_}'[16:]
573 # NOTE frequency is hardcoded to `daily` in slapos.cookbook:logrotate.d
574 # NOTE backup is also used to add custom logrotate options (hack)
575 backup  = ...
576 # TODO settle whether we need/want olddir or not
577     noolddir
578 # override create emitted by slapos.cookbook:logrotate.d
579     nocreate
580 # do not move log file and this way we do not need to signal its program to
581 # reopen the log. There are a lot of bugs when on such reopen / restart /
582 # graceful-restart something bad happens. Even if copytruncate is a bit racy
583 # and can loose some data, it is better to keep the system the stable way.
584     copytruncate
585
586
587 # hook logrotate into cron
588 [cron-entry-logrotate]
589 <= cron-entry
590 time    = daily
591 command = ${logrotate:wrapper}