diff --git a/README.md b/README.md index e69de29..57fcf2b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,25 @@ +# Gestion Événementiel + +## Vagrant + +### Production-like environment + +Our Vagrant setup provides two ways of running GestionEvenementiel: + +1. You can run the usual development server with: + + python manage.py runserver 0.0.0.0:8000 + + Please note that we specify the interface `0.0.0.0` to make the server + reachable outside the VM at address `localhost:8000` + +2. A second instance, more similar to the production environment, runs with + Daphne and nginx in the VM. It runs permanently by default but is not + reloaded when you update the code. To restart this server, type: + + python manage.py collectstatic --noinput + sudo systemctl restart daphne.service worker.service + + To query this instance from the host, you have to use the address + `localhost:8080`. It is a good practice to ensure that this instance works + before submitting a merge request (although it might break sometimes). diff --git a/Vagrantfile b/Vagrantfile index c84c3dd..b853e43 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -10,7 +10,7 @@ Vagrant.configure(2) do |config| # For a complete reference, please see the online documentation at # https://docs.vagrantup.com. - config.vm.box = "ubuntu/trusty64" + config.vm.box = "debian/contrib-jessie64" # On associe le port 80 dans la machine virtuelle avec le port 8080 de notre # ordinateur, et le port 8000 avec le port 8000. diff --git a/evenementiel/asgi.py b/evenementiel/asgi.py new file mode 100644 index 0000000..59c5d58 --- /dev/null +++ b/evenementiel/asgi.py @@ -0,0 +1,7 @@ +import os +from channels.asgi import get_channel_layer + +if "DJANGO_SETTINGS_MODULE" not in os.environ: + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evenementiel.settings") + +channel_layer = get_channel_layer() diff --git a/evenementiel/routing.py b/evenementiel/routing.py new file mode 100644 index 0000000..8f9af19 --- /dev/null +++ b/evenementiel/routing.py @@ -0,0 +1,2 @@ +# Nothing yet +channel_routing = [] diff --git a/evenementiel/settings/.gitignore b/evenementiel/settings/.gitignore new file mode 100644 index 0000000..2142506 --- /dev/null +++ b/evenementiel/settings/.gitignore @@ -0,0 +1 @@ +secret.py diff --git a/evenementiel/settings/common.py b/evenementiel/settings/common.py new file mode 100644 index 0000000..61bf2d6 --- /dev/null +++ b/evenementiel/settings/common.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +""" +Django common settings for GestionÉvénementiel + +Everything which is supposed to be identical between the production server and +the local development server should be here. + +We also load the secrets in this file. +""" + +import os +from . import secret + + +def import_secret(name): + """ + Shorthand for importing a value from the secret module and raising an + informative exception if a secret is missing. + """ + try: + return getattr(secret, name) + except AttributeError: + raise RuntimeError("Secret missing: {}".format(name)) + + +SECRET_KEY = import_secret("SECRET_KEY") +ADMINS = import_secret("ADMINS") + +DBNAME = import_secret("DBNAME") +DBUSER = import_secret("DBUSER") +DBPASSWD = import_secret("DBPASSWD") + +REDIS_PASSWD = import_secret("REDIS_PASSWD") +REDIS_DB = import_secret("REDIS_DB") +REDIS_HOST = import_secret("REDIS_HOST") +REDIS_PORT = import_secret("REDIS_PORT") + +CREATE_USER_KEY = import_secret("CREATE_USER_KEY") + + +BASE_DIR = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +) + + +INSTALLED_APPS = [ + 'equipment.apps.EquipmentConfig', + 'event.apps.EventConfig', + 'users.apps.UsersConfig', + 'shared.apps.SharedConfig', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'channels', + 'bootstrapform', + 'widget_tweaks', +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'evenementiel.urls' + +STATIC_URL = "/static/" +MEDIA_URL = "/media/" + +LOGIN_REDIRECT_URL = 'shared:home' +LOGOUT_REDIRECT_URL = 'shared:home' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'shared.shared.context_processor', + ], + }, + }, +] + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': DBNAME, + 'USER': DBUSER, + 'PASSWORD': DBPASSWD, + 'PORT': 5432, + 'HOST': 'localhost', + } +} + +CHANNEL_LAYERS = { + "default": { + "BACKEND": "asgi_redis.RedisChannelLayer", + "CONFIG": { + "hosts": [( + "redis://:{passwd}@{host}:{port}/{db}" + .format(passwd=REDIS_PASSWD, host=REDIS_HOST, + port=REDIS_PORT, db=REDIS_DB) + )], + }, + "ROUTING": "evenementiel.routing.channel_routing", + } +} + +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + {'NAME': 'django.contrib.auth.password_validation' + '.UserAttributeSimilarityValidator'}, + {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, + {'NAME': 'django.contrib.auth.password_validation' + '.CommonPasswordValidator'}, + {'NAME': 'django.contrib.auth.password_validation' + '.NumericPasswordValidator'}, +] + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True diff --git a/evenementiel/settings/dev.py b/evenementiel/settings/dev.py new file mode 100644 index 0000000..f65a54c --- /dev/null +++ b/evenementiel/settings/dev.py @@ -0,0 +1,44 @@ +""" +Django development settings for GestionÉvénementiel +The settings that are not listed here are imported from .common +""" + +from .common import * # NOQA + + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +DEBUG = True + +# Add some debugging tools +INSTALLED_APPS += ["debug_toolbar", "debug_panel"] # NOQA +MIDDLEWARE_CLASSES = ( + ["debug_panel.middleware.DebugPanelMiddleware"] + + MIDDLEWARE_CLASSES # NOQA +) + + +# --- +# Nginx static/media config +# --- + +STATIC_ROOT = "/srv/GE/static/" +MEDIA_ROOT = "/srv/GE/media/" + + +# --- +# Debug tool bar +# --- + +def show_toolbar(request): + """ + On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar + car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la + machine physique n'est pas forcément connue, et peut difficilement être + mise dans les INTERNAL_IPS. + """ + return DEBUG # True + +DEBUG_TOOLBAR_CONFIG = { + 'SHOW_TOOLBAR_CALLBACK': show_toolbar, +} diff --git a/evenementiel/settings/secret_example.py b/evenementiel/settings/secret_example.py new file mode 100644 index 0000000..1bd3a29 --- /dev/null +++ b/evenementiel/settings/secret_example.py @@ -0,0 +1,16 @@ +SECRET_KEY = 'dummy_key3i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah' +ADMINS = None + +# Postgres +DBNAME = "{{DBNAME}}" +DBUSER = "{{DBUSER}}" +DBPASSWD = "{{DBPASSWD}}" + +# Redis +REDIS_PASSWD = "{{REDIS_PASSWD}}" +REDIS_PORT = 6379 +REDIS_DB = 0 +REDIS_HOST = "127.0.0.1" + +# An other secret key used for user creation +CREATE_USER_KEY = "lolilol" diff --git a/evenementiel/settings_dev.py b/evenementiel/settings_dev.py deleted file mode 100644 index 0f07994..0000000 --- a/evenementiel/settings_dev.py +++ /dev/null @@ -1,162 +0,0 @@ -""" -Django settings for evenementiel project. - -Generated by 'django-admin startproject' using Django 1.9.9. - -For more information on this file, see -https://docs.djangoproject.com/en/1.9/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.9/ref/settings/ -""" - -import os -from django.core.urlresolvers import reverse_lazy - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '0@=@$0*2x)x=$6qzf*1a(07she(33zr9vi0+=(yd%3i=i9gp+_' -CREATE_USER_KEY = 'lolilol' # Do not use this one on prod !! - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - -INSTALLED_APPS = [ - 'equipment.apps.EquipmentConfig', - 'event.apps.EventConfig', - 'users.apps.UsersConfig', - 'shared.apps.SharedConfig', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'bootstrapform', - 'debug_toolbar', - 'widget_tweaks', -] - -MIDDLEWARE_CLASSES = [ - 'debug_toolbar.middleware.DebugToolbarMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'evenementiel.urls' - -LOGIN_REDIRECT_URL = reverse_lazy('shared:home') -LOGOUT_REDIRECT_URL = reverse_lazy('shared:home') - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'shared.shared.context_processor', - ], - }, - }, -] - -WSGI_APPLICATION = 'evenementiel.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/1.9/ref/settings/#databases - -# # MySQL -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.sqlite3', -# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), -# } -# } - -# PostGreSQL -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': os.environ['DBNAME'], - 'USER': os.environ['DBUSER'], - 'PASSWORD': os.environ['DBPASSWD'], - 'PORT': 5432, - 'HOST': 'localhost', - } -} - - -# Password validation -# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - {'NAME': 'django.contrib.auth.password_validation' - '.UserAttributeSimilarityValidator'}, - {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, - {'NAME': 'django.contrib.auth.password_validation' - '.CommonPasswordValidator'}, - {'NAME': 'django.contrib.auth.password_validation' - '.NumericPasswordValidator'}, -] - - -# Internationalization -# https://docs.djangoproject.com/en/1.9/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.9/howto/static-files/ - -STATIC_URL = '/static/' - - -def show_toolbar(request): - """ - On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar - car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la - machine physique n'est pas forcément connue, et peut difficilement être - mise dans les INTERNAL_IPS. - """ - if not DEBUG: - return False - if request.is_ajax(): - return False - return True - - -DEBUG_TOOLBAR_CONFIG = { - 'SHOW_TOOLBAR_CALLBACK': show_toolbar, -} diff --git a/provisioning/bootstrap.sh b/provisioning/bootstrap.sh index f0bab20..6526c86 100644 --- a/provisioning/bootstrap.sh +++ b/provisioning/bootstrap.sh @@ -1,45 +1,125 @@ -#!/bin/sh +#!/bin/bash -# Configuration de la base de données. Le mot de passe est constant car c'est -# pour une installation de dév locale qui ne sera accessible que depuis la -# machine virtuelle. +# The credentials of the database. Can be public since they will only be used in +# the local development environment DBUSER="event_gestion" DBNAME="event_gestion" DBPASSWD="4KZt3nGPLVeWSvtBZPsd9jdssdJMds78" -# Installation de paquets utiles -apt-get update && apt-get install -y python3-pip python3-dev \ - libpq-dev postgresql postgresql-contrib libjpeg-dev +# Not critical either +REDIS_PASSWD="dummy" -# Setup Database and User +# It is used in quite a few places +SETTINGS="evenementiel.settings.dev" + +# Fills a "templated file" with the information specified in the variables above +# e.g. every occurrence of {{DBUSER}} in the file will be replaced by the value +# of the variable $DBUSER +function fill_template { + sed "s/{{DBUSER}}/$DBUSER/" -i $1 + sed "s/{{DBNAME}}/$DBNAME/" -i $1 + sed "s/{{DBPASSWD}}/$DBPASSWD/" -i $1 + sed "s/{{REDIS_PASSWD}}/$REDIS_PASSWD/" -i $1 + sed "s/{{SETTINGS}}/$SETTINGS/" -i $1 +} + +# --- +# Installs the dependencies +# --- + +# System packages +apt-get update && apt-get upgrade -y +apt-get install -y python3-pip python3-dev python3-venv libpq-dev postgresql \ + postgresql-contrib libjpeg-dev nginx redis-server + +# Python packages, in a virtual environment +sudo -H -u vagrant python3 -m venv ~vagrant/venv +sudo -H -u vagrant ~vagrant/venv/bin/pip install -U pip wheel +sudo -H -u vagrant ~vagrant/venv/bin/pip install -U -r /vagrant/requirements-devel.txt + + +# --- +# Setup the production-like environment +# --- + +# Database and User sudo -u postgres createdb $DBNAME sudo -u postgres createuser -SDR $DBUSER sudo -u postgres psql -c "ALTER USER $DBUSER WITH PASSWORD '$DBPASSWD';" sudo -u postgres psql -c "ALTER USER $DBUSER CREATEDB;" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DBNAME TO $DBUSER;" -# Mise en place du .bash_profile pour tout configurer lors du `vagrant ssh` +# The working directory for Daphne and cie +mkdir -p /srv/GE/{media,static} +chown -R vagrant:vagrant /srv/GE + +# Nginx +cp /vagrant/provisioning/nginx.conf /etc/nginx/sites-available/ge.conf +if [ ! -h /etc/nginx/sites-enabled/ge.conf ] +then + # If the configuration file is not activated yet, activates it + ln -s /etc/nginx/sites-available/ge.conf /etc/nginx/sites-enabled/ge.conf +fi +rm -f /etc/nginx/sites-enabled/default # We do not need this +service nginx restart + +# Daphne and the worker(s) +for service in {daphne,worker}.service +do + cp /vagrant/provisioning/$service /etc/systemd/system/$service + fill_template /etc/systemd/system/$service + systemctl enable $service + systemctl start $service +done + +# Redis +redis-cli CONFIG SET requirepass $REDIS_PASSWD +if [ ! $? ] +then + # In case the requirepass command failed, checks that it was because the + # password was already set *to the right value*. + redis-cli AUTH $REDIS_PASSWD +fi +redis-cli -a $REDIS_PASSWD CONFIG REWRITE + + +# --- +# Prepare Django +# --- + +cd /vagrant + +# Setup the secrets +sudo -H -u vagrant cp evenementiel/settings/secret_example.py \ + evenementiel/settings/secret.py +fill_template evenementiel/settings/secret.py + +# Run the usual django admin commands +function venv_python { + sudo -H -u vagrant DJANGO_SETTINGS_MODULE=$SETTINGS \ + ~vagrant/venv/bin/python \ + $@ +} + +venv_python manage.py collectstatic --noinput +venv_python manage.py migrate + + +# --- +# Setup a friendly environment for the user +# --- + cat > ~vagrant/.bash_profile <