diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..fa795eb6c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +htdocs/media/logo +mygpo/settings_prod.py +doc/ +res/ +tools/ +locale/ +venv* +*.pyc +*/*.pyc +*/*/*.pyc +*/*/*/*.pyc diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..720c4f268 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM ubuntu:14.04 +MAINTAINER Stefan Kögl + +# use bash instead of built-in sh, because it does not support "source" during build +RUN rm /bin/sh && ln -s /bin/bash /bin/sh + +# install all packaged dependencies +RUN apt-get update && apt-get install -y \ + git \ + python2.7 \ + python2.7-dev \ + python-virtualenv \ + libpq-dev \ + libjpeg-dev \ + zlib1g-dev \ + libwebp-dev + +# create log directories +RUN mkdir -p /var/log/gunicorn + +# copy source +COPY . /srv/mygpo +WORKDIR /srv/mygpo + +# run everything in a virtualenv +RUN virtualenv venv +RUN source venv/bin/activate + +# install all runtime dependencies +RUN pip install \ + -r /srv/mygpo/requirements.txt \ + -r /srv/mygpo/requirements-setup.txt + +# set up missing environment variables +ENTRYPOINT ["/srv/mygpo/bin/docker-env.sh"] + +# default launch command +CMD ["gunicorn", "mygpo.wsgi:application", "--access-logfile", "-"] + +# HTTP port +EXPOSE 8000 diff --git a/bin/docker-env.sh b/bin/docker-env.sh new file mode 100755 index 000000000..3d7ca6aef --- /dev/null +++ b/bin/docker-env.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Docker doesn't have a great way to set environment variables at startup. +# This scripts will set up some defaults. + +# if a DATABSE_URL is provided from outside, use it +if [[ -z "$DATABASE_URL" ]]; then + # otherwise construct one using a linked "db" container + export DATABASE_URL="postgres://mygpo:mygpo@${DB_PORT_5432_TCP_ADDR}:5432/mygpo" +fi + +# if not SECRET_KEY is provided from outside, create a random one +if [[ -z "$SECRET_KEY" ]]; then + export SECRET_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) +fi + +# Execute the commands passed to this script +exec "$@" diff --git a/contrib/init-db.sql b/contrib/init-db.sql new file mode 100644 index 000000000..5b4dad338 --- /dev/null +++ b/contrib/init-db.sql @@ -0,0 +1,9 @@ +CREATE USER mygpo WITH PASSWORD 'mygpo'; +ALTER USER mygpo CREATEDB; -- required for creating test database +CREATE DATABASE mygpo; +CREATE DATABASE test_mygpo; +GRANT ALL PRIVILEGES ON DATABASE mygpo to mygpo; +GRANT ALL PRIVILEGES ON DATABASE test_mygpo to mygpo; +ALTER DATABASE mygpo OWNER TO mygpo; +ALTER DATABASE test_mygpo OWNER TO mygpo; +ALTER ROLE mygpo SET statement_timeout = 5000; diff --git a/doc/dev/postgres-setup.rst b/doc/dev/postgres-setup.rst index dea968957..1f7929d9d 100644 --- a/doc/dev/postgres-setup.rst +++ b/doc/dev/postgres-setup.rst @@ -3,14 +3,6 @@ PostgreSQL Setup Use the following to set up a local PostgreSQL. -.. code-block:: sql - - CREATE USER mygpo WITH PASSWORD 'mygpo'; - ALTER USER mygpo CREATEDB; -- required for creating test database - CREATE DATABASE mygpo; - CREATE DATABASE test_mygpo; - GRANT ALL PRIVILEGES ON DATABASE mygpo to mygpo; - GRANT ALL PRIVILEGES ON DATABASE test_mygpo to mygpo; - ALTER DATABASE mygpo OWNER TO mygpo; - ALTER DATABASE test_mygpo OWNER TO mygpo; - ALTER ROLE mygpo SET statement_timeout = 5000; +.. literalinclude:: ../../contrib/init-db.sql + :language: sql + :linenos: diff --git a/doc/index.rst b/doc/index.rst index 2bc6fa7e1..be1f76bef 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -24,6 +24,7 @@ Contents publisher/index api/index dev/index + ops/index Translator Documentation diff --git a/doc/ops/configuration.rst b/doc/ops/configuration.rst new file mode 100644 index 000000000..9e4e32848 --- /dev/null +++ b/doc/ops/configuration.rst @@ -0,0 +1,106 @@ +Configuration +============= + +Configuration can be done through the following environment variables. + +``DEBUG`` +--------- +Debug mode shows error pages, enables debug output, etc. + + +``DATABASE_URL`` +---------------- +DB connection string in the form of ``postgres://USER:PASSWORD@HOST:PORT/NAME`` + + +``ACCOUNT_ACTIVATION_DAYS`` +--------------------------- +Number of days that newly registered users have time to activate their account. + + +``DEFAULT_FROM_EMAIL`` +---------------------- +Default sender address for outgoing emails. See `Django documentation +`__. + + +``SECRET_KEY`` +-------------- +See `Django documentation +`__. + + +``GOOGLE_ANALYTICS_PROPERTY_ID`` +-------------------------------- +Setting a Google Analytics Property ID activates GA integration. + + +``DIRECTORY_EXCLUDED_TAGS`` +--------------------------- +A comma-separated list of tags that are excluded from the directory. + + +``FLICKR_API_KEY`` +------------------ +Setting a Flickr API key activates Flickr integration. + + +``MAINTENANCE`` +--------------- +Switches the site into a maintenance mode. + + +* ``PODCAST_SLUG_SUBSCRIBER_LIMIT`` + +* ``MIN_SUBSCRIBERS_CATEGORY``: minimum number of subscribers that a podcast + needs to "push" one of its categories to the top + +* ``API_ACTIONS_MAX_NONBG``: maximum number of episode actions that the API + processes immediatelly before returning the response. Larger requests will + be handled in background. + +* ``ADSENSE_CLIENT`` + +* ``ADSENSE_SLOT_BOTTOM`` + +* ``STAFF_TOKEN``: enabled access to staff-only areas with ?staff= + +* ``FLATTR_KEY`` + +* ``FLATTR_SECRET`` + +* ``FLATTR_MYGPO_THING``: Flattr thing of the webservice. Will be flattr'd + when a user sets the "Auto-Flattr gpodder.net" option + +* ``USER_AGENT``: The User-Agent string used for outgoing HTTP requests + +* ``DEFAULT_BASE_URL``: Base URL of the website that is used if the actually + used parameters is not available. Request handlers, for example, can access + the requested domain. Code that runs in background can not do this, and + therefore requires a default value. This should be set to something like + ``http://example.com`` + +* ``BROKER_URL`` Celery Broker URL + +* ``CELERY_RESULT_BACKEND`` + +* ``CELERY_SEND_TASK_ERROR_EMAILS`` + +* ``SERVER_EMAIL`` + +* ``GOOGLE_CLIENT_ID`` + +* ``GOOGLE_CLIENT_SECRET`` + +* ``SUPPORT_URL``: URL where users of the site can get support + +* ``ELASTICSEARCH_SERVER`` + +* ``ELASTICSEARCH_INDEX`` + +* ``ELASTICSEARCH_TIMEOUT`` + +* ``ACTIVATION_VALID_DAYS`` time for how long an activation is valid; after + that, an unactivated user will be deleted + +* ``INTERNAL_IPS`` diff --git a/doc/ops/docker.rst b/doc/ops/docker.rst new file mode 100644 index 000000000..9ee0907b1 --- /dev/null +++ b/doc/ops/docker.rst @@ -0,0 +1,61 @@ +Using Docker +============ + +mygpo can be run using `Docker `_. + + +Database +-------- + +The image requires a PostgreSQL server, specified either via + +* a `linked container `_ + called ``db`` containing a server with a database called ``mygpo``, a user + called ``mygpo`` with a password ``mygpo``. +* A ``DATABASE_URL`` environment variable (eg + ``postgres://USER:PASSWORD@HOST:PORT/NAME``) + +Using a PostgreSQL Docker container +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Start a container using the `official PostgreSQL image `_. :: + + docker run --name db -d postgres + +Create the schema and a corresponding user :: + + docker exec -it db psql -U postgres + +And enter the following commands (change passwords as required) + +.. literalinclude:: ../../contrib/init-db.sql + :language: sql + :linenos: + +Initialize the tables. This needs needs to be run for every update. :: + + sudo docker run --rm --link db:db -e SECRET_KEY=asdf mygpo/web python manage.py migrate + + +Elasticsearch +------------- + + +Redis +----- + + +Web Frontend +------------ + +The image exposes the web interface on port 8000. + + +Celery Worker +------------- + + +Celery Heartbeat +---------------- + + diff --git a/doc/ops/index.rst b/doc/ops/index.rst new file mode 100644 index 000000000..4b8d278fe --- /dev/null +++ b/doc/ops/index.rst @@ -0,0 +1,9 @@ +Operation Documentation +======================= + + +.. toctree:: + :maxdepth: 1 + + configuration + docker diff --git a/fig.yml b/fig.yml new file mode 100644 index 000000000..d13cdd25e --- /dev/null +++ b/fig.yml @@ -0,0 +1,2 @@ +db: + image: postgres diff --git a/makefile b/makefile index 13c212490..5f11efdd6 100644 --- a/makefile +++ b/makefile @@ -20,6 +20,11 @@ coverage: clean: find -name "*.pyc" -exec rm '{}' \; +docker-build: + sudo docker build -t="mygpo/web" . + +docker-run: + sudo docker run --rm -p 8000:8000 --name web --link db:db -e SECRET_KEY=asdf mygpo/web .PHONY: all help test clean unittest coverage diff --git a/mygpo/settings.py b/mygpo/settings.py index 9bacce642..aa42797db 100644 --- a/mygpo/settings.py +++ b/mygpo/settings.py @@ -20,12 +20,29 @@ import os.path import dj_database_url + +def parse_bool(s): + """ parses a boolean setting """ + if isinstance(s, bool): + return s + s = s.lower.strip() + return s not in ('n', 'no', 'false', '0', 'off') + + +def parse_int(s): + return int(str(s)) + + +def parse_strlist(s): + return [item.strip() for item in s.split(',')] + + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#ChangedthewayURLpathsaredetermined -FORCE_SCRIPT_NAME="" +FORCE_SCRIPT_NAME = "" -DEBUG = True +DEBUG = parse_bool(os.getenv('DEBUG', True)) TEMPLATE_DEBUG = DEBUG ADMINS = () @@ -146,9 +163,12 @@ 'couchdbkit', ) -TEST_RUNNER='mygpo.test.MygpoTestSuiteRunner' -ACCOUNT_ACTIVATION_DAYS = 7 +TEST_RUNNER = 'mygpo.test.MygpoTestSuiteRunner' + + +ACCOUNT_ACTIVATION_DAYS = parse_int(os.getenv('ACCOUNT_ACTIVATION_DAYS', 7)) + AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', @@ -184,17 +204,26 @@ LOGIN_URL = '/login/' -CSRF_FAILURE_VIEW='mygpo.web.views.security.csrf_failure' +CSRF_FAILURE_VIEW = 'mygpo.web.views.security.csrf_failure' + + +DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', '') -# The following entries should be set in settings_prod.py -DEFAULT_FROM_EMAIL = '' SECRET_KEY = os.getenv('SECRET_KEY', '') -GOOGLE_ANALYTICS_PROPERTY_ID='' -DIRECTORY_EXCLUDED_TAGS = () -FLICKR_API_KEY = '' -MAINTENANCE = os.path.exists(os.path.join(BASE_DIR, 'MAINTENANCE')) + +GOOGLE_ANALYTICS_PROPERTY_ID = os.getenv('GOOGLE_ANALYTICS_PROPERTY_ID', '') + + +DIRECTORY_EXCLUDED_TAGS = parse_strlist(os.getenv('DIRECTORY_EXCLUDED_TAGS', + '')) + + +FLICKR_API_KEY = os.getenv('FLICKR_API_KEY', '') + + +MAINTENANCE = parse_bool(os.getenv('MAINTENANCE', False)) LOGGING = {