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