# GitLab instance # NOTE instance/software layout is inspired by gitlab omnibus # NOTE all services are interconnected via unix sockets - because of easier # security and performance reasons (unix has 2x less latency and more # throughput compared to tcp over loopback). [buildout] extends = {{ gitlab_parameters_cfg }} parts = directory # gitlab-<prog> # ? mailroom {% set gitlab_progv = 'rails rake unicorn sidekiq' .split() %} {% for prog in gitlab_progv %} gitlab-{{ prog }} {% endfor %} gitlab-work gitlab-shell-work service-postgresql service-redis service-cron # std stuff for slapos instance eggs-directory = {{ eggs_directory }} develop-eggs-directory = {{ develop_eggs_directory }} offline = true ################################## # GitLab instance parameters # ################################## [instance-parameter] # std stuff to fetch slapos instance parameters recipe = slapos.cookbook:slapconfiguration computer= ${slap-connection:computer-id} partition=${slap-connection:partition-id} url = ${slap-connection:server-url} key = ${slap-connection:key-file} cert = ${slap-connection:cert-file} # autogenerated gitlab instance parameters <= gitlab-parameters # for convenience [backend-info] # current slapuserX user = {{ pwd.getpwuid(os.getuid())[0] }} ############################# # GitLab instance setup # ############################# # 1. directories [directory] recipe = slapos.cookbook:mkdirectory home = ${buildout:directory} bin = ${:home}/bin etc = ${:home}/etc var = ${:home}/var log = ${:var}/log run = ${:var}/run srv = ${:home}/srv # slapos startup/service/promise scripts live here: startup = ${:etc}/run service = ${:etc}/service promise = ${:etc}/promise # gitlab: etc/ log/ ... [gitlab-dir] recipe = slapos.cookbook:mkdirectory etc = ${directory:etc}/gitlab log = ${directory:log}/gitlab var = ${directory:var}/gitlab tmp = ${:var}/tmp uploads = ${:var}/uploads assets = ${:var}/assets backup = ${directory:var}/backup [gitlab-repo-dir] recipe = slapos.cookbook:mkdirectory repositories = ${directory:var}/repositories # gitlab wants it to be drwxrws--- # FIXME setting such mode with :mkdirectory is not possible, because mkdir(2) # does & 0777 and also there is umask. So we workaround: [gitlab-repo-xdir] recipe = plone.recipe.command stop-on-error = yes repositories = ${gitlab-repo-dir:repositories} command = chmod 02770 ${:repositories} [gitlab] etc = ${gitlab-dir:etc} log = ${gitlab-dir:log} var = ${gitlab-dir:var} tmp = ${gitlab-dir:tmp} uploads = ${gitlab-dir:uploads} assets = ${gitlab-dir:assets} backup = ${gitlab-dir:backup} repositories = ${gitlab-repo-xdir:repositories} # gitlab-shell: etc/ log/ gitlab_shell_secret ... [gitlab-shell-dir] recipe = slapos.cookbook:mkdirectory etc = ${directory:etc}/gitlab-shell log = ${directory:log}/gitlab-shell [gitlab-shell] etc = ${gitlab-shell-dir:etc} log = ${gitlab-shell-dir:log} secret = ${secrets:secrets}/gitlab_shell_secret # place to keep all secrets [secrets] recipe = slapos.cookbook:mkdirectory secrets = ${directory:var}/secrets mode = 0700 # 2. configuration files [etc-template] recipe = slapos.recipe.template:jinja2 extensions = jinja2.ext.do mode = 0640 import-list = rawfile macrolib.cfg.in {{ macrolib_cfg_in }} context = raw autogenerated # This file was autogenerated. (DO NOT EDIT - changes will be lost) section instance_parameter instance-parameter section backend_info backend-info import urlparse urlparse raw git {{ git }} ${:context-extra} context-extra = [gitlab-etc-template] <= etc-template rendered= ${gitlab:etc}/${:_buildout_section_name_} [config.ru] <= gitlab-etc-template template = {{ config_ru_in }} [database.yml] <= gitlab-etc-template template= {{ database_yml_in }} context-extra = section pgsql service-postgresql [gitlab-shell-config.yml] <= etc-template template= {{ gitlab_shell_config_yml_in }} rendered= ${gitlab-shell:etc}/config.yml [gitlab.yml] <= gitlab-etc-template template= {{ gitlab_yml_in }} context-extra = section gitlab gitlab section gitlab_shell gitlab-shell section gitlab_shell_work gitlab-shell-work [rack_attack.rb] <= gitlab-etc-template template = {{ rack_attack_rb_in }} [resque.yml] <= gitlab-etc-template template= {{ resque_yml_in }} context-extra = section redis service-redis [smtp_settings.rb] <= gitlab-etc-template template= {{ smtp_settings_rb_in }} # contains smtp password mode = 0600 [unicorn.rb] <= gitlab-etc-template template = {{ unicorn_rb_in }} # 3. bin/ # gitlab-<prog> [gitlab-bin] recipe = slapos.cookbook:wrapper wrapper-path = ${directory:bin}/${:_buildout_section_name_} environment = BUNDLE_GEMFILE = {{ gitlab_repository_location }}/Gemfile RAILS_ENV = production # NOTE sys.argv[1:] implicitly appended # (by slapos.recipe.librecipe.execute.generic_exec() at runtime) command-line = {{ bundler_4gitlab }} exec sh -c 'cd ${gitlab-work:location} && ${:prog} "$@"' ${:prog} {% for prog in gitlab_progv %} [gitlab-{{ prog }}] <= gitlab-bin prog = {{ prog }} {% endfor %} # 4. gitlab- & gitlab-shell- work directories # # Gitlab/Rails operation is tightened that config/ lives inside code, which goes # against having ability to create several instances configured differently # from 1 SR. # # One possibility to overcome this could be to make another Gitlab root # symbolically linked to original SR _and_ several configuration files # symbolically linked to instance place. Unfortunately this does not work - # Ruby determines realpath on module import and Gitlab and Rails lookup config # files relative to imported modules. # # we clone cloned gitlab and add proper links to vendor/bundle and instance # config files. # XXX there is no need for full clone - we only need worktree checkout (a-la `git # worktree add`, but without creating files in original clone) # # This way Gitlab/Rails still think they work in 1 code / 1 instance way, # and we can reuse SR. # XXX better do such tricks with bind mounting, but that requires user namespaces [work-base] recipe = plone.recipe.command stop-on-error = yes location = ${directory:home}/${:_buildout_section_name_} command = # make sure we start from well-defined empty state # (needed e.g. if previous install failed in the middle) rm -rf ${:location} && # init work repository and add `software` remote pointing to main repo in SR software/... {{ git }} init ${:location} && cd ${:location} && {{ git }} remote add software ${:software} && ${:update-command} update-command = cd ${:location} && {{ git }} fetch software && {{ git }} reset --hard `cd ${:software} && {{ git }} rev-parse HEAD` && ${:tune-command} # NOTE there is no need to link/create .gitlab_shell_secret - we will set path to it # in gitlab & gitlab-shell configs, and gitlab creates it on its first start [gitlab-work] <= work-base software = {{ gitlab_repository_location }} tune-command = # secret* config.ru tmp/ log/ rm -f .secret && rm -f config.ru && rm -rf log tmp && ln -sf ${secrets:secrets}/gitlab_rails_secret .secret && ln -sf ${config.ru:rendered} config.ru && ln -sf ${gitlab:log} log && ln -sf ${gitlab:tmp} tmp && # config/ cd config && ln -sf ${unicorn.rb:rendered} unicorn.rb && ln -sf ${gitlab.yml:rendered} gitlab.yml && ln -sf ${database.yml:rendered} database.yml && ln -sf ${resque.yml:rendered} resque.yml && ln -sf ${secrets:secrets}/gitlab_secrets.yml secrets.yml && # config/initializers/ cd initializers && ln -sf ${rack_attack.rb:rendered} rack_attack.rb && ln -sf ${smtp_settings.rb:rendered} smtp_settings.rb && # public/ cd ../../public && rm -rf uploads assets && ln -sf ${gitlab:uploads} uploads && ln -sf ${gitlab:assets} assets && true # ----//---- for gitlab-shell [gitlab-shell-work] <= work-base software = {{ gitlab_shell_repository_location }} tune-command = ln -sf ${gitlab-shell-config.yml:rendered} config.yml && true # 5. services # [promise-<something>] to generate promise wrapper <something> [promise-wrapper] recipe = slapos.cookbook:wrapper wrapper-path = !py! '${directory:promise}/' + '${:_buildout_section_name_}'[8:] ##################### # Postgresql db # ##################### # XXX gitlab-omnibus also tunes: # - shared_buffers # - work_mem # - checkpoint_* # - effective_check_size # - lc_* en_US.UTF-8 -> C (?) [service-postgresql] recipe = slapos.cookbook:postgres bin = {{ postgresql_location }}/bin services= ${directory:service} dbname = gitlabhq_production # NOTE db name must match to what was used in KVM on lab.nexedi.com (restore script grants access to this user) superuser = gitlab-psql # no password - pgsql will listen only on unix sockets (see below) thus access # is protected with filesystem-level permissions. # ( besides, if we use slapos.cookbook:generate.password and do `password = ...` # the password is stored in plain text in .installed and thus becomes insecure ) password= pgdata-directory = ${directory:srv}/postgresql # empty addresses - listen only on unix socket ipv4 = !py!set([]) ipv6 = !py!set([]) ipv6-random = port = depend = ${promise-postgresql:recipe} [promise-postgresql] <= promise-wrapper command-line = {{ postgresql_location }}/bin/psql -h ${service-postgresql:pgdata-directory} -U ${service-postgresql:superuser} -d ${service-postgresql:dbname} -c '\q' # postgresql logs to stdout/stderr - logs are handled by slapos not us # [logrotate-entry-postgresql] ############# # Redis # ############# [redis] recipe = slapos.cookbook:mkdirectory srv = ${directory:srv}/redis log = ${directory:log}/redis [service-redis] recipe = slapos.cookbook:redis.server wrapper = ${directory:service}/redis promise_wrapper = ${directory:promise}/redis server_dir = ${redis:srv} config_file = ${directory:etc}/redis.conf log_file = ${redis:log}/redis.log pid_file = ${directory:run}/redis.pid use_passwd = false unixsocket = ${:server_dir}/redis.socket # port = 0 means "don't listen on TCP at all" - listen only on unix socket ipv6 = ::1 port = 0 server_bin = {{ redis_binprefix }}/redis-server depend = ${logrotate-entry-redis:recipe} # NOTE slapos.cookbook:redis.server setups promise automatically [logrotate-entry-redis] <= logrotate-entry log = ${redis:log}/*.log ############# # cron # ############# [cron-dir] recipe = slapos.cookbook:mkdirectory cron.d = ${directory:etc}/cron.d crontabs= ${directory:srv}/cron/crontabs cronstamps = ${directory:var}/cron/cronstamps log = ${directory:log}/cron [service-cron] recipe = slapos.cookbook:cron binary = ${directory:service}/crond cron-entries = ${cron-dir:cron.d} crontabs = ${cron-dir:crontabs} cronstamps = ${cron-dir:cronstamps} catcher = ${cron-simplelogger:wrapper} dcrond-binary = {{ dcron_bin }} depends = ${logrotate-entry-cron:recipe} # "mailer" that cron uses to emit messages to logfile [cron-simplelogger] recipe = slapos.cookbook:simplelogger wrapper = ${directory:bin}/${:_buildout_section_name_} log = ${cron-dir:log}/cron.log # base entry for clients who registers to cron [cron-entry] recipe = slapos.cookbook:cron.d # name = <section-name>.strip_prefix('cron-entry-') # XXX len() is not available in !py! - 11 hardcoded name = !py!'${:_buildout_section_name_}' [11:] # NOTE _not_ ${service-cron:cron-entries} - though the value is the same we do # not want service-cron to be instantiated just if a cron-entry is registered. cron-entries = ${cron-dir:cron.d} # cron logs are also rotated [logrotate-entry-cron] <= logrotate-entry log = ${cron-dir:log}/*.log ####################################### # logrotate base for all services # ####################################### [logrotate-dir] recipe = slapos.cookbook:mkdirectory srv = ${directory:srv}/logrotate entries = ${directory:etc}/logrotate.d [logrotate] recipe = slapos.cookbook:logrotate wrapper = ${directory:bin}/${:_buildout_section_name_} conf = ${directory:etc}/logrotate.conf logrotate-entries = ${logrotate-dir:entries} state-file = ${logrotate-dir:srv}/logrotate.status logrotate-binary = {{ logrotate_bin }} gzip-binary = {{ gzip_bin }} gunzip-binary = {{ gunzip_bin }} depend = ${cron-entry-logrotate:recipe} # base entry for clients who registers to logrotate [logrotate-entry] recipe = slapos.cookbook:logrotate.d logrotate-entries = ${logrotate:logrotate-entries} # name = <section-name>.strip_prefix('logrotate-entry-') # XXX len is not available in !py! - 16 hardcoded name = !py!'${:_buildout_section_name_}'[16:] # NOTE frequency is hardcoded to `daily` in slapos.cookbook:logrotate.d # NOTE backup is also used to add custom logrotate options (hack) backup = ... # TODO settle whether we need/want olddir or not noolddir # override create emitted by slapos.cookbook:logrotate.d nocreate # do not move log file and this way we do not need to signal its program to # reopen the log. There are a lot of bugs when on such reopen / restart / # graceful-restart something bad happens. Even if copytruncate is a bit racy # and can loose some data, it is better to keep the system the stable way. copytruncate # hook logrotate into cron [cron-entry-logrotate] <= cron-entry time = daily command = ${logrotate:wrapper}