From 5df32939a6ddd66c208ba81a769aa59f02a2595c Mon Sep 17 00:00:00 2001 From: Andrey Arapov Date: Sun, 10 Apr 2016 19:21:26 +0200 Subject: [PATCH] first commit --- Dockerfile | 54 ++++++++++++ README.md | 200 ++++++++++++++++++++++++++++++++++++++++++ TODO.md | 8 ++ db/init-taiga-db.sh | 9 ++ docker-compose.yml | 42 +++++++++ launch | 65 ++++++++++++++ postgres.env | 2 + seeds/circus.ini.tmpl | 31 +++++++ seeds/conf.json.tmpl | 17 ++++ seeds/local.py.tmpl | 42 +++++++++ seeds/taiga.tmpl | 49 +++++++++++ taiga-db.env | 5 ++ taiga.env | 5 ++ 13 files changed, 529 insertions(+) create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 TODO.md create mode 100755 db/init-taiga-db.sh create mode 100644 docker-compose.yml create mode 100755 launch create mode 100644 postgres.env create mode 100644 seeds/circus.ini.tmpl create mode 100644 seeds/conf.json.tmpl create mode 100644 seeds/local.py.tmpl create mode 100644 seeds/taiga.tmpl create mode 100644 taiga-db.env create mode 100644 taiga.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..19bcf2c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +FROM debian:jessie +MAINTAINER "Andrey Arapov " + +# To avoid problems with Dialog and curses wizards +ENV DEBIAN_FRONTEND noninteractive + +# OS & Python env deps for taiga-back +RUN apt-get update -qq \ + && apt-get install -y -- build-essential binutils-doc autoconf flex \ + bison libjpeg-dev libfreetype6-dev zlib1g-dev libzmq3-dev \ + libgdbm-dev libncurses5-dev automake libtool libffi-dev curl git \ + tmux gettext python3.4 python3.4-dev python3-pip libxml2-dev \ + libxslt-dev libpq-dev virtualenv \ + nginx \ + && rm -rf -- /var/lib/apt/lists/* + +RUN pip3 install circus gunicorn + +# Create taiga user +ENV USER taiga +ENV UID 1000 +ENV GROUP www-data +ENV HOME /home/$USER +ENV DATA /usr/local/taiga +RUN useradd -u $UID -m -d $HOME -s /usr/sbin/nologin -g $GROUP $USER +RUN mkdir -p $DATA $DATA/media $DATA/static $DATA/logs /var/log/taiga \ + && chown -Rh $USER:$GROUP $DATA /var/log/taiga + +# Install taiga-back +USER $USER +WORKDIR $DATA +RUN git clone -b stable https://github.com/taigaio/taiga-back.git $DATA/taiga-back \ + && virtualenv -p /usr/bin/python3.4 venvtaiga \ + && . venvtaiga/bin/activate \ + && cd $DATA/taiga-back \ + && pip3 install -r requirements.txt \ + && deactivate + +# Install taiga-front (compiled) +RUN git clone -b stable https://github.com/taigaio/taiga-front-dist.git $DATA/taiga-front-dist + +USER root + +# Cleanups +RUN rm -f /etc/nginx/sites-enabled/default + +# Copy template seeds +COPY seeds/*.tmpl /tmp/ + +VOLUME [ "$DATA/static", \ + "$DATA/media" ] + +COPY launch / +CMD /launch diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbc34eb --- /dev/null +++ b/README.md @@ -0,0 +1,200 @@ +Taiga in Docker +=============== + +This container allows you to run [Taiga](https://taiga.io/) in a Docker +container. + +What is Taiga? +-------------- + +Taiga is a project management application that can handle both simple and +complex projects for startups, software developers, and other target teams. +It tracks the progress of a project. Taiga's design is clean and elegant +design—something that is supposed to be "beautiful to look at all day long." +With Taiga, you can use either Kanban or Scrum template, or both. Backlogs are +shown as a running list of all features and User Stories added to the project. + +Taiga integrates video conferencing functions with the use of third party +services from Talky.io and Appear.in. Group and private chat is done via HipChat. + +Dockerfile +---------- + +The Dockerfile performs the following steps: +* install the OS & Python dependencies required to run Taiga Django backend. +* install nginx - to serve the content including static assests which Django + app should not be handling. +* install circus - a process & socket manager for the Python web application. +* install gunicorn - a Python Web Server Gateway Interface (WSGI) HTTP Server + to serve dynamic content. +* create taiga user for running circus & gunicorn. +* clone stable branches of the taiga backend and frontend. +* copy templates which will be then processed by the launch script when the + container starts. + + +Running Taiga +============= + +I recommend to use Docker Compose for running the Taiga. + +Docker Compose +-------------- + +Below is a docker compose file as example + +docker-compose.yml +``` +version: '2' + +volumes: + postgres_data: {} + taiga_postgres: {} + taiga_static: {} + taiga_media: {} + +networks: + backend: {} + +services: + postgres: + # https://hub.docker.com/_/postgres/ + image: postgres + networks: + - backend + volumes: + - postgres_data:/var/lib/postgresql/data + - ./db:/docker-entrypoint-initdb.d:ro + env_file: + - ./postgres.env + - ./taiga-db.env + + taiga: + # https://hub.docker.com/r/andrey01/taiga + image: andrey01/taiga + # build: . + networks: + - backend + ports: + - 80:80 + volumes: + - taiga_static:/usr/local/taiga/static + - taiga_media:/usr/local/taiga/media + env_file: + - ./taiga.env + - ./taiga-db.env + environment: + - TZ=Europe/Amsterdam + depends_on: + - postgres +``` + +The following file is required so that Postgres will create taiga database that +is used by the Taiga backend. + +db/init-taiga-db.sh +``` +#!/bin/bash +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL + CREATE USER $TAIGA_DB_USER; + CREATE DATABASE $TAIGA_DB_NAME; + GRANT ALL PRIVILEGES ON DATABASE $TAIGA_DB_NAME TO $TAIGA_DB_USER; + ALTER USER $TAIGA_DB_USER WITH PASSWORD '$TAIGA_DB_PASSWORD'; +EOSQL +``` + +The environment variables +------------------------- + +This file will defines a superuser for Postgres. + +postgres.env +``` +POSTGRES_USER=admin +POSTGRES_PASSWORD=admin +``` + +This file defines individual settings for Taiga. + +taiga.env +``` +GUNICORN_WORKERS=1 +SITE_URI=http://taiga.mydomain.com +PUBLIC_REGISTER=true +ADMIN_EMAIL=admin@mydomain.com +NOREPLY_EMAIL=no-reply@mydomain.com +``` + +This file defines the database settings for Taiga. + +taiga-db.env +``` +TAIGA_DB_USER=taiga +TAIGA_DB_NAME=taiga +TAIGA_DB_PASSWORD=secretpassword +TAIGA_DB_HOST=postgres +TAIGA_DB_PORT=5432 +``` + +Run the Taiga +``` +docker-compose up -d taiga +``` + +Now you can access Taiga with your favorite Web Browser. + +I recommend to run nginx reverse proxy in front of this container, so that +you could use TLS. +[Let's Encrypt](https://letsencrypt.org) project is now able to issue free +SSL/TLS certificates. + + +Maintenance +=========== + +Accessing the Taiga Database +---------------------------- + +You can access it using the docker compose or docker as follows + +``` +docker-compose run --rm postgres sh -c 'PGPASSWORD=$TAIGA_DB_PASSWORD exec psql -h "$TAIGA_DB_HOST" -U $TAIGA_DB_USER' +``` + +``` +docker run -ti --rm --net taiga_backend --env-file taiga-db.env postgres sh -c 'PGPASSWORD=$TAIGA_DB_PASSWORD exec psql -h "$TAIGA_DB_HOST" -U $TAIGA_DB_USER' +``` + +Backup the database +------------------- + +Below is an example of how you can make the Taiga PostgreSQL database backup +``` +docker-compose run --rm postgres sh -c 'PGPASSWORD=$TAIGA_DB_PASSWORD exec pg_dump -h "$TAIGA_DB_HOST" -U $TAIGA_DB_USER $TAIGA_DB_NAME' > taiga-db.backup +``` + +To restore it +``` +docker-compose run --rm postgres sh -c 'PGPASSWORD=$TAIGA_DB_PASSWORD exec psql -h "$TAIGA_DB_HOST" -U $TAIGA_DB_USER $TAIGA_DB_NAME' < taiga-db.backup +``` + +There are also volumes containing the data you might want to backup externally +``` +$ docker volume ls +DRIVER VOLUME NAME +local taiga_taiga_postgres +local taiga_taiga_media +local taiga_taiga_static +local taiga_postgres_data +``` + + +Useful links +------------ + +* [Docker Compose file reference](https://docs.docker.com/compose/compose-file/) +* [Taiga: Setup production environment](https://taigaio.github.io/taiga-doc/dist/setup-production.html) +* [Read Taiga upgrade notes](https://taigaio.github.io/taiga-doc/dist/upgrades.html) +* [How to upgrade Taiga](https://taigaio.github.io/taiga-doc/dist/upgrades.html) diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..895a2f6 --- /dev/null +++ b/TODO.md @@ -0,0 +1,8 @@ +TODO +==== + +* make sure Taiga sends emails, e.g. new user registered, password reset, + general Taiga notifications + +* enable Async tasks (leverage RabbitMQ, redis and the Taiga-events websocket + server) diff --git a/db/init-taiga-db.sh b/db/init-taiga-db.sh new file mode 100755 index 0000000..9bdd8a2 --- /dev/null +++ b/db/init-taiga-db.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL + CREATE USER $TAIGA_DB_USER; + CREATE DATABASE $TAIGA_DB_NAME; + GRANT ALL PRIVILEGES ON DATABASE $TAIGA_DB_NAME TO $TAIGA_DB_USER; + ALTER USER $TAIGA_DB_USER WITH PASSWORD '$TAIGA_DB_PASSWORD'; +EOSQL diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9af8120 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ +version: '2' + +volumes: + postgres_data: {} + taiga_postgres: {} + taiga_static: {} + taiga_media: {} + +networks: + backend: {} + +services: + postgres: + # https://hub.docker.com/_/postgres/ + image: postgres + networks: + - backend + volumes: + - postgres_data:/var/lib/postgresql/data + - ./db:/docker-entrypoint-initdb.d:ro + env_file: + - ./postgres.env + - ./taiga-db.env + + taiga: + # https://hub.docker.com/r/andrey01/taiga + image: andrey01/taiga + # build: . + networks: + - backend + ports: + - 80:80 + volumes: + - taiga_static:/usr/local/taiga/static + - taiga_media:/usr/local/taiga/media + env_file: + - ./taiga.env + - ./taiga-db.env + environment: + - TZ=Europe/Amsterdam + depends_on: + - postgres diff --git a/launch b/launch new file mode 100755 index 0000000..4555d58 --- /dev/null +++ b/launch @@ -0,0 +1,65 @@ +#!/bin/bash + +# Debug +# set -x + +# Do Not Change these variables unless you know what you are doing +export ESC=$ +export DJANGO_SECRET_KEY=$(openssl rand -hex 32) + +# Default variables. These shall be overridden :-) +export GUNICORN_WORKERS=${GUNICORN_WORKERS:-1} +export SITE_URI=${SITE_URI:-http://example.com} +export SITE_FQDN=$(echo $SITE_URI | sed -e "s/[^/]*\/\/\([^@]*@\)\?\([^:/]*\).*/\2/") +[ "${SITE_URI::5}" == "https" ] && SITE_SCHEME="https" || SITE_SCHEME="http" +export SITE_SCHEME +[ ${PUBLIC_REGISTER,,} == "false" ] \ +&& { PUBLIC_REGISTER="False"; PUBLIC_REGISTER_JS="false"; } \ +|| { PUBLIC_REGISTER="True"; PUBLIC_REGISTER_JS="true"; } +export PUBLIC_REGISTER PUBLIC_REGISTER_JS +export ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com} +export NOREPLY_EMAIL=${NOREPLY_EMAIL:-no-reply@example.com} +export TAIGA_DB_HOST=${TAIGA_DB_HOST:-postgres} +export TAIGA_DB_PORT=${TAIGA_DB_PORT:-5432} +export TAIGA_DB_NAME=${TAIGA_DB_NAME:-taiga} +export TAIGA_DB_USER=${TAIGA_DB_USER:-taiga} +export TAIGA_DB_PASSWORD=${TAIGA_DB_PASSWORD:-mysecretpassword} + +# Generate configs based on the template seeds +envsubst < /tmp/taiga.tmpl > /etc/nginx/sites-enabled/taiga +envsubst < /tmp/circus.ini.tmpl > $DATA/circus.ini +envsubst < /tmp/conf.json.tmpl > $DATA/taiga-front-dist/dist/conf.json +envsubst < /tmp/local.py.tmpl > $DATA/taiga-back/settings/local.py + +# Keep sensitive information out of here +unset DJANGO_SECRET_KEY TAIGA_DB_PASSWORD + +# Make sure the data is readable +chown -Rh $USER:$GROUP $DATA + +# Allow a little delay on the first run +# to make sure Database is set and ready +[ -e "/tmp/taiga.firstrun" ] || ( sleep 5; touch /tmp/taiga.firstrun ) + +# Upgrade DB schemas, etc... +# This is important when Taiga's codebase gets updated +su -s /bin/sh $USER -c '. $DATA/venvtaiga/bin/activate +cd $DATA/taiga-back +python3 manage.py migrate --noinput +python3 manage.py compilemessages +python3 manage.py collectstatic --noinput +deactivate' + +# (Optional) Create a new user admin with password 123123 and +# fill Taiga with the Sample data +# su -s /bin/sh $USER -c '. $DATA/venvtaiga/bin/activate +# cd $DATA/taiga-back +# python3 manage.py loaddata initial_user +# python3 manage.py loaddata initial_project_templates +# python3 manage.py loaddata initial_role +# python3 manage.py sample_data +# deactivate' + +# Launch the backend +service nginx start +su -s /bin/sh $USER -c '/usr/local/bin/circusd "$DATA/circus.ini"' diff --git a/postgres.env b/postgres.env new file mode 100644 index 0000000..ea23cb0 --- /dev/null +++ b/postgres.env @@ -0,0 +1,2 @@ +POSTGRES_USER=admin +POSTGRES_PASSWORD=admin diff --git a/seeds/circus.ini.tmpl b/seeds/circus.ini.tmpl new file mode 100644 index 0000000..f08cb51 --- /dev/null +++ b/seeds/circus.ini.tmpl @@ -0,0 +1,31 @@ +[circus] +check_delay = 5 +endpoint = tcp://127.0.0.1:5555 +pubsub_endpoint = tcp://127.0.0.1:5556 +statsd = true + +[watcher:taiga] +working_dir = ${DATA}/taiga-back +cmd = gunicorn +args = -w ${GUNICORN_WORKERS} -t 60 --pythonpath=. -b 127.0.0.1:8001 taiga.wsgi +uid = taiga +numprocesses = 1 +autostart = true +send_hup = true +stdout_stream.class = FileStream +stdout_stream.filename = /var/log/taiga/gunicorn.stdout.log +stdout_stream.max_bytes = 10485760 +stdout_stream.backup_count = 4 +stderr_stream.class = FileStream +stderr_stream.filename = /var/log/taiga/gunicorn.stderr.log +stderr_stream.max_bytes = 10485760 +stderr_stream.backup_count = 4 + +[env:taiga] +PATH = ${DATA}/venvtaiga/bin:${ESC}PATH +TERM=rxvt-256color +SHELL=/bin/bash +USER=taiga +LANG=en_US.UTF-8 +HOME=${DATA} +PYTHONPATH=${DATA}/venvtaiga/lib/python3.4/site-packages diff --git a/seeds/conf.json.tmpl b/seeds/conf.json.tmpl new file mode 100644 index 0000000..6add20b --- /dev/null +++ b/seeds/conf.json.tmpl @@ -0,0 +1,17 @@ +{ + "api": "${SITE_URI}/api/v1/", + "eventsUrl": null, + "eventsMaxMissedHeartbeats": 5, + "eventsHeartbeatIntervalTime": 60000, + "debug": true, + "debugInfo": false, + "defaultLanguage": "en", + "themes": ["taiga"], + "defaultTheme": "taiga", + "publicRegisterEnabled": ${PUBLIC_REGISTER_JS}, + "feedbackEnabled": true, + "privacyPolicyUrl": null, + "termsOfServiceUrl": null, + "maxUploadFileSize": null, + "contribPlugins": [] +} diff --git a/seeds/local.py.tmpl b/seeds/local.py.tmpl new file mode 100644 index 0000000..e3991eb --- /dev/null +++ b/seeds/local.py.tmpl @@ -0,0 +1,42 @@ +from .common import * + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': '${TAIGA_DB_NAME}', + 'USER': '${TAIGA_DB_USER}', + 'PASSWORD': '${TAIGA_DB_PASSWORD}', + 'HOST': '${TAIGA_DB_HOST}', + 'PORT': '${TAIGA_DB_PORT}', + } +} + +ADMINS = ( + ("Admin", "${ADMIN_EMAIL}"), +) + +# THROTTLING (Anti-flood) +REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = { + "anon": "20/min", + "user": "200/min", + "import-mode": "20/sec", + "import-dump-mode": "10/minute" +} + +SECRET_KEY = "${DJANGO_SECRET_KEY}" + +MEDIA_ROOT = '${DATA}/media' +STATIC_ROOT = '${DATA}/static' + +MEDIA_URL = "${SITE_URI}/media/" +STATIC_URL = "${SITE_URI}/static/" +ADMIN_MEDIA_PREFIX = "${SITE_URI}/static/admin/" +SITES["front"]["scheme"] = "${SITE_SCHEME}" +SITES["front"]["domain"] = "${SITE_FQDN}" + +DEBUG = False +TEMPLATE_DEBUG = False +PUBLIC_REGISTER_ENABLED = ${PUBLIC_REGISTER} + +DEFAULT_FROM_EMAIL = "${NOREPLY_EMAIL}" +SERVER_EMAIL = DEFAULT_FROM_EMAIL diff --git a/seeds/taiga.tmpl b/seeds/taiga.tmpl new file mode 100644 index 0000000..ab4bbfb --- /dev/null +++ b/seeds/taiga.tmpl @@ -0,0 +1,49 @@ +server { + listen 80 default_server; + server_name _; + + large_client_header_buffers 4 32k; + client_max_body_size 50M; + charset utf-8; + + access_log /var/log/taiga/nginx.access.log; + error_log /var/log/taiga/nginx.error.log; + + # Frontend + location / { + root ${DATA}/taiga-front-dist/dist/; + try_files ${ESC}uri ${ESC}uri/ /index.html; + } + + # Backend + location /api { + proxy_set_header Host ${ESC}http_host; + proxy_set_header X-Real-IP ${ESC}remote_addr; + proxy_set_header X-Scheme ${ESC}scheme; + proxy_set_header X-Forwarded-Proto ${ESC}scheme; + proxy_set_header X-Forwarded-For ${ESC}proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:8001/api; + proxy_redirect off; + } + + # Django admin access (/admin/) + location /admin { + proxy_set_header Host ${ESC}http_host; + proxy_set_header X-Real-IP ${ESC}remote_addr; + proxy_set_header X-Scheme ${ESC}scheme; + proxy_set_header X-Forwarded-Proto ${ESC}scheme; + proxy_set_header X-Forwarded-For ${ESC}proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:8001${ESC}request_uri; + proxy_redirect off; + } + + # Static files + location /static { + alias ${DATA}/static; + } + + # Media files + location /media { + alias ${DATA}/media; + } +} diff --git a/taiga-db.env b/taiga-db.env new file mode 100644 index 0000000..5358a9b --- /dev/null +++ b/taiga-db.env @@ -0,0 +1,5 @@ +TAIGA_DB_USER=taiga +TAIGA_DB_NAME=taiga +TAIGA_DB_PASSWORD=secretpassword +TAIGA_DB_HOST=postgres +TAIGA_DB_PORT=5432 diff --git a/taiga.env b/taiga.env new file mode 100644 index 0000000..fd496c7 --- /dev/null +++ b/taiga.env @@ -0,0 +1,5 @@ +GUNICORN_WORKERS=1 +SITE_URI=http://taiga.mydomain.com +PUBLIC_REGISTER=true +ADMIN_EMAIL=admin@mydomain.com +NOREPLY_EMAIL=no-reply@mydomain.com