# 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]
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}



#############################
#   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
context =
    raw     autogenerated       # This file was autogenerated. (DO NOT EDIT - changes will be lost)
    section instance_parameter  instance-parameter
    ${: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 }}

[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 }}

[rack_attack.rb]
<= gitlab-etc-template
template = {{ rack_attack_rb_in }}

[resque.yml]
<= gitlab-etc-template
template= {{ resque_yml_in }}

[smtp_settings.rb]
<= gitlab-etc-template
template= {{ smtp_settings_rb_in }}

[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}
