diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..51c6ea37 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,73 @@ +FROM node:12 AS vendors + +COPY . /srv/umap + +WORKDIR /srv/umap + +RUN make installjs + +RUN make vendors + +FROM python:3.8-slim + +ENV PYTHONUNBUFFERED=1 \ + UMAP_SETTINGS=/srv/umap/umap/settings/docker.py \ + PORT=8000 + +RUN mkdir -p /srv/umap/data && \ + mkdir -p /srv/umap/uploads + +RUN mkdir /usr/share/man/man1 + +COPY . /srv/umap + +COPY --from=vendors /srv/umap/umap/static/umap/vendors /srv/umap/umap/static/umap/vendors + +WORKDIR /srv/umap + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + uwsgi \ + libpq-dev \ + build-essential \ + binutils \ + gdal-bin \ + libproj-dev \ + curl \ + git \ + gettext \ + sqlite3 \ + libffi-dev \ + libtiff5-dev \ + libjpeg62-turbo-dev \ + zlib1g-dev \ + libfreetype6-dev \ + liblcms2-dev \ + libwebp-dev \ + && \ + pip install --no-cache -r requirements-docker.txt && pip install . && \ + apt-get remove -y \ + binutils \ + libproj-dev \ + libffi-dev \ + libtiff5-dev \ + libjpeg62-turbo-dev \ + zlib1g-dev \ + libfreetype6-dev \ + liblcms2-dev \ + libwebp-dev \ + && \ + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Add Tini +ENV TINI_VERSION v0.14.0 +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini +RUN chmod +x /tini + +EXPOSE 8000 + +ENTRYPOINT ["/tini", "--"] + +CMD ["/srv/umap/docker-entrypoint.sh"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000..c76416e0 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -eo pipefail + +# default variables +: "${SLEEP:=1}" +: "${TRIES:=60}" + +function wait_for_database {( + echo "Waiting for database to respond..." + tries=0 + while true; do + [[ $tries -lt $TRIES ]] || return + (echo "from django.db import connection; connection.connect()" | umap shell) >/dev/null 2>&1 + [[ $? -eq 0 ]] && return + sleep $SLEEP + tries=$((tries + 1)) + done +)} + +# first wait for the database +wait_for_database +# then migrate the database +umap migrate +# then collect static files +umap collectstatic --noinput +# create languagae files +#umap storagei18n +# compress static files +umap compress +# run uWSGI +exec uwsgi --ini uwsgi.ini diff --git a/requirements-docker.txt b/requirements-docker.txt new file mode 100644 index 00000000..92bb9611 --- /dev/null +++ b/requirements-docker.txt @@ -0,0 +1,4 @@ +-r requirements.txt +django-environ==0.4.1 +django-redis==4.7.0 +uwsgi==2.0.14 diff --git a/umap/settings/docker.py b/umap/settings/docker.py new file mode 100644 index 00000000..097a35da --- /dev/null +++ b/umap/settings/docker.py @@ -0,0 +1,155 @@ +# -*- coding:utf-8 -*- +""" +Settings for Docker development + +Use this file as a base for your local development settings and copy +it to umap/settings/local.py. It should not be checked into +your code repository. +""" +import environ +from umap.settings.base import * # pylint: disable=W0614,W0401 + +env = environ.Env() + +SECRET_KEY = env('SECRET_KEY') +INTERNAL_IPS = env.list('INTERNAL_IPS', default='127.0.0.1') +ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default='*') + +DEBUG = env.bool('DEBUG', default=False) + +ADMIN_EMAILS = env.list('ADMIN_EMAIL', default='') +ADMINS = [(email, email) for email in ADMIN_EMAILS] +MANAGERS = ADMINS + +DATABASES = { + 'default': env.db(default='postgis://localhost:5432/umap') +} + +COMPRESS_ENABLED = True +COMPRESS_OFFLINE = True + +LANGUAGE_CODE = 'en' + +# Set to False if login into django account should not be possible. You can +# administer accounts in the admin interface. +ENABLE_ACCOUNT_LOGIN = env.bool('ENABLE_ACCOUNT_LOGIN', default=True) + +AUTHENTICATION_BACKENDS = () + +# We need email to associate with other Oauth providers +SOCIAL_AUTH_GITHUB_SCOPE = ['user:email'] +SOCIAL_AUTH_GITHUB_KEY = env('GITHUB_KEY', default='') +SOCIAL_AUTH_GITHUB_SECRET = env('GITHUB_SECRET', default='') +if SOCIAL_AUTH_GITHUB_KEY and SOCIAL_AUTH_GITHUB_SECRET: + AUTHENTICATION_BACKENDS += ( + 'social_core.backends.github.GithubOAuth2', + ) +SOCIAL_AUTH_BITBUCKET_KEY = env('BITBUCKET_KEY', default='') +SOCIAL_AUTH_BITBUCKET_SECRET = env('BITBUCKET_SECRET', default='') +if SOCIAL_AUTH_BITBUCKET_KEY and SOCIAL_AUTH_BITBUCKET_SECRET: + AUTHENTICATION_BACKENDS += ( + 'social_core.backends.bitbucket.BitbucketOAuth', + ) + +SOCIAL_AUTH_TWITTER_KEY = env('TWITTER_KEY', default='') +SOCIAL_AUTH_TWITTER_SECRET = env('TWITTER_SECRET', default='') +if SOCIAL_AUTH_TWITTER_KEY and SOCIAL_AUTH_TWITTER_SECRET: + AUTHENTICATION_BACKENDS += ( + 'social_core.backends.twitter.TwitterOAuth', + ) +SOCIAL_AUTH_OPENSTREETMAP_KEY = env('OPENSTREETMAP_KEY', default='') +SOCIAL_AUTH_OPENSTREETMAP_SECRET = env('OPENSTREETMAP_SECRET', default='') +if SOCIAL_AUTH_OPENSTREETMAP_KEY and SOCIAL_AUTH_OPENSTREETMAP_SECRET: + AUTHENTICATION_BACKENDS += ( + 'social_core.backends.openstreetmap.OpenStreetMapOAuth', + ) + +AUTHENTICATION_BACKENDS += ( + 'django.contrib.auth.backends.ModelBackend', +) + +# MIDDLEWARE_CLASSES += ( +# 'social_django.middleware.SocialAuthExceptionMiddleware', +# ) + +SOCIAL_AUTH_RAISE_EXCEPTIONS = False +SOCIAL_AUTH_BACKEND_ERROR_URL = "/" + +# If you want to add a playgroud map, add its primary key +# UMAP_DEMO_PK = 204 +# If you want to add a showcase map on the home page, add its primary key +# UMAP_SHOWCASE_PK = 1156 +# Add a baner to warn people this instance is not production ready. +UMAP_DEMO_SITE = False + +# Whether to allow non authenticated people to create maps. +LEAFLET_STORAGE_ALLOW_ANONYMOUS = env.bool( + 'LEAFLET_STORAGE_ALLOW_ANONYMOUS', + default=False, +) + +# This setting will exclude empty maps (in fact, it will exclude all maps where +# the default center has not been updated) +UMAP_EXCLUDE_DEFAULT_MAPS = False + +# How many maps should be showcased on the main page resp. on the user page +UMAP_MAPS_PER_PAGE = 0 +# How many maps should be showcased on the user page, if owner +UMAP_MAPS_PER_PAGE_OWNER = 10 + +SITE_URL = env('SITE_URL') +SHORT_SITE_URL = env('SHORT_SITE_URL', default=None) + +CACHES = {'default': env.cache('REDIS_URL', default='locmem://')} + +# POSTGIS_VERSION = (2, 1, 0) +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +# You need to unable accent extension before using UMAP_USE_UNACCENT +# python manage.py dbshell +# CREATE EXTENSION unaccent; +UMAP_USE_UNACCENT = False + +# For static deployment +STATIC_ROOT = '/srv/umap/static' + +# For users' statics (geojson mainly) +MEDIA_ROOT = '/srv/umap/uploads' + +# Default map location for new maps +LEAFLET_LONGITUDE = env.int('LEAFLET_LONGITUDE', default=2) +LEAFLET_LATITUDE = env.int('LEAFLET_LATITUDE', default=51) +LEAFLET_ZOOM = env.int('LEAFLET_ZOOM', default=6) + +# Number of old version to keep per datalayer. +LEAFLET_STORAGE_KEEP_VERSIONS = env.int( + 'LEAFLET_STORAGE_KEEP_VERSIONS', + default=10, +) + +import sys + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '[django] %(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + } + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'stream': sys.stdout, + 'formatter': 'verbose' + }, + }, + 'loggers': { + 'django': { + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': True, + }, + }, +} diff --git a/uwsgi.ini b/uwsgi.ini new file mode 100644 index 00000000..2cf2c279 --- /dev/null +++ b/uwsgi.ini @@ -0,0 +1,10 @@ +[uwsgi] +http = :$(PORT) +module = umap.wsgi:application +master = True +vacuum = True +max-requests = 5000 +processes = 4 +enable-threads = true +static-map = /static=/srv/umap/static +static-map = /uploads=/srv/umap/uploads