Add first version of deploy scripts
authorymh <ymh.work@gmail.com>
Wed, 28 Nov 2018 15:45:37 +0100
changeset 180 62bffc051e1c
parent 179 e7c7e6e0a8bc
child 181 cda96c025330
Add first version of deploy scripts
.hgignore
deploy/README.md
deploy/Vagrantfile
deploy/deploy.sh
deploy/deploy.yml
deploy/group_vars/group_vars.yml.tmpl
deploy/hosts/hosts.tmpl
deploy/templates/backend_settings.ini.j2
deploy/templates/nginx.static.conf.j2
deploy/templates/nginx.static.ssl.conf.j2
deploy/templates/supervisord_irinotes_backend.ini.j2
deploy/templates/uwsgi_irinotes.yml.j2
deploy/test_playbook.yml
--- a/.hgignore	Tue Nov 27 15:45:45 2018 +0100
+++ b/.hgignore	Wed Nov 28 15:45:37 2018 +0100
@@ -47,3 +47,6 @@
 ^deploy/group_vars/.*\.yml$
 ^deploy/hosts/hosts\.prod$
 ^deploy/hosts/hosts\.test$
+^deploy/.*\.retry$
+
+^.vscode/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/README.md	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,21 @@
+# build and deployment
+
+- install Ansible
+- Create a new deployment profile:
+    - add a new deplyment host list by creating a `hosts/hosts.<profile>` file:
+    - `cp hosts/hosts.tmpl hosts/hosts.<profile>`
+    - define the profile vars: `cp group_vars/group_vars.yml.tmpl group_vars/<profile>.yml`, customize it
+    - create the necessary host vars in `host_vars/<machine>/base.yml`. Use ansible vaults to edit them and do not expose any secret.
+    - deploy with the command `deploy.sh`: `./deploy.sh <profile> <version>`
+
+## Create new version
+Launch the command:
+- `set-version.sh <version>`
+- commit, tag and push as needed.
+
+## Launch local django command on the managed host
+
+```
+DJANGO_SETTINGS_MODULE=irinotes.settings IRINOTES_CONFIG_BASE=/etc/www/irinotes django-admin <command>
+```
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/Vagrantfile	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,34 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant.configure("2") do |config|
+#  config.vm.box = "maier/alpine-3.6-x86_64"
+  config.vm.box = "generic/alpine38"
+  config.vm.provider "virtualbox" do |v|
+    # v.default_nic_type = "Am79C973"
+    v.default_nic_type = "virtio"
+  end
+  config.vm.network "private_network", ip: "172.16.1.7", auto_config: false
+  config.vbguest.auto_update = false
+
+  # config.vm.provider :virtualbox do |vb|
+  #   vb.gui = true
+  # end
+
+  # Enable provisioning with a shell script. Additional provisioners such as
+  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
+  # documentation for more information about their specific syntax and use.
+  # config.vm.provision "shell", inline: <<-SHELL
+  #   apt-get update
+  #   apt-get install -y python python3
+  # SHELL
+  config.vm.provision "shell", inline: <<-SHELL
+    apk update
+    apk upgrade
+    apk add python python3
+  SHELL
+  config.vm.provision "ansible" do |ansible|
+    ansible.playbook = "test_playbook.yml"
+    ansible.compatibility_mode = "2.0"
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/deploy.sh	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+
+SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+SCRIPTNAME=`basename "$0"`
+
+usage() {
+  echo -n "${SCRIPTNAME} [CONFIG] [VERSION]
+
+Deploy the <config> using ansible.
+config must be in the following list : test, prod
+"
+}
+
+if [[ "$#" -ne 2 ]]; then
+    usage
+    exit 1
+fi
+
+config=${1}
+VERSION=${2}
+
+case $config in
+    test) configOK=true;;
+    prod) configOK=true;;
+    *)    configOK=false;;
+esac
+
+if [[ "$configOK" = false ]]; then
+    usage
+    exit 1
+fi
+
+pushd "$SCRIPTPATH"
+
+ANSIBLE_SSH_PIPELINING=1 ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook -v -i "./hosts/hosts.$config" -l "$config" ./deploy.yml --extra-vars "irinotes_version=${VERSION}" --ask-vault-pass
+
+popd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/deploy.yml	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,341 @@
+---
+# - hosts: all
+#   tasks:
+#     - name: Print some debug information
+#       vars:
+#         msg: |
+#             Module Variables ("vars"):
+#             --------------------------------
+#             {{ vars | to_nice_json }}
+
+#             Environment Variables ("environment"):
+#             --------------------------------
+#             {{ environment | to_nice_json }}
+
+#             GROUP NAMES Variables ("group_names"):
+#             --------------------------------
+#             {{ group_names | to_nice_json }}
+
+#             GROUPS Variables ("groups"):
+#             --------------------------------
+#             {{ groups | to_nice_json }}
+
+#             HOST Variables ("hostvars"):
+#             --------------------------------
+#             {{ hostvars | to_nice_json }}
+
+#       debug:
+#         msg: "{{ msg.split('\n') }}"
+#       tags: debug_info
+#
+# Localhost actions
+#
+- hosts: localhost
+  connection: local
+  vars:
+    irinotes_build_dir: "{{playbook_dir}}/build/tmp/irinotes/"
+    backend_build_dir: "{{playbook_dir}}/build/tmp/irinotes/src"
+    client_dir: "{{playbook_dir}}/build/tmp/irinotes/client"
+  tasks:
+
+    - name: mkdir build folder
+      file:
+        path: "{{playbook_dir}}/build/tmp"
+        state: directory
+
+    - name: clear irinotes checkout dir if exists
+      file:
+        path: "{{irinotes_build_dir}}"
+        state: absent
+
+    - name: checkout irinotes
+      hg:
+        repo: "{{irinotes_repo}}"
+        version: "{{irinotes_version}}"
+        force: yes
+        dest: "{{irinotes_build_dir}}"
+
+    - name: make irinotes py package
+      command: python setup.py sdist chdir="{{backend_build_dir}}"
+
+#
+# all actions
+#
+- hosts: remote_backend
+  vars:
+    backend_build_dir: "{{playbook_dir}}/build/tmp/irinotes/src"
+    client_dir: "{{playbook_dir}}/build/tmp/irinotes/client"
+  tasks:
+
+    - name: get backend version
+      local_action: command python setup.py -V chdir="{{backend_build_dir}}"
+      register: backend_version
+
+    - name: get discussion package version
+      set_fact:
+        backend_pkg_version: "{{backend_version.stdout}}"
+
+#
+# Localhost actions
+#
+- hosts: localhost
+  connection: local
+  vars:
+    backend_build_dir: "{{playbook_dir}}/build/tmp/irinotes/src"
+    client_dir: "{{playbook_dir}}/build/tmp/irinotes/client"
+  tasks:
+
+# build client js
+    - name: remove build
+      file: path="{{client_dir}}/build" state=absent
+
+    - name: client yarn install
+      command: npm install --no-save yarn
+      args:
+        chdir: "{{client_dir}}"
+
+    - name: client dependencies install
+      command: npx yarn install
+      args:
+        chdir: "{{client_dir}}"
+
+    - name: client build
+      command: npx yarn build
+      args:
+        chdir: "{{client_dir}}"
+      environment:
+        REACT_APP_API_ROOT_URL: "{{api_root_url}}"
+        REACT_APP_BASENAME: "{{api_root_url}}"
+        REACT_APP_NETWORK_STATUS_INTERVAL: "{{network_status_interval}}"
+        REACT_APP_NETWORK_STATUS_TIMEOUT: "{{network_status_timeout}}"
+        REACT_APP_SYNC_INTERVAL: "{{sync_interval}}"
+
+#
+# Static server
+#
+- hosts: remote_static
+  become: yes
+  vars:
+    backend_build_dir: "{{playbook_dir}}/build/tmp/irinotes/src"
+    client_dir: "{{playbook_dir}}/build/tmp/irinotes/client"
+  tasks:
+
+    - name: create dest static directory content
+      file:
+        path: "{{remote_static_path}}"
+        state: directory
+        owner: "{{static_http_user}}"
+        group: "{{static_http_group}}"
+
+    - name: remove dest static directory content
+      command: /bin/rm -fr "{{remote_static_path}}/*" warn=False
+      when: remote_static_path != ""
+
+    - name: transfert static dist to remote
+      copy:
+        src: "{{client_dir}}/build/"
+        dest: "{{remote_static_path}}/"
+        owner: "{{static_http_user}}"
+        group: "{{static_http_group}}"
+      notify:
+        - restart static nginx
+
+    - name: create refresh nginx config
+      template:
+        src: "{{static_nginx_use_ssl | ternary('nginx.static.ssl.conf.j2', 'nginx.static.conf.j2')}}"
+        dest: "{{static_nginx_config | default('/etc/nginx/site-available/'+static_server_name, true)}}"
+      notify:
+        - restart static nginx
+
+  handlers:
+    - name: restart static nginx
+      service:
+        name: "{{static_http_service}}"
+        state: restarted
+      ignore_errors: yes
+
+#
+# backend servers
+#
+- hosts: remote_backend
+  become: yes
+  vars:
+    backend_build_dir: "{{playbook_dir}}/build/tmp/irinotes/src"
+  tasks:
+
+    - name: add iri group
+      group:
+        name: iri
+
+    - name: add uwsgi user
+      user:
+        name: uwsgi
+        group: iri
+        home: /var/www
+        create_home: no
+        system: yes
+        shell: /bin/false
+
+    - name: create log dir
+      file:
+        path: "{{log_base_path}}"
+        state: directory
+        owner: uwsgi
+        group: iri
+        mode: 0755
+
+    - name: create config dir
+      file:
+        path: "{{backend_config_base}}"
+        state: directory
+        owner: uwsgi
+        group: iri
+        mode: 0755
+
+    - name: create config dir requirements
+      file:
+        path: "{{backend_config_base}}/requirements"
+        state: directory
+        owner: uwsgi
+        group: iri
+        mode: 0755
+
+    - name: create static folder
+      file:
+        path: "{{backend_static_root}}"
+        state: directory
+        owner: uwsgi
+        group: iri
+        mode: 0755
+
+    - name: create media folder
+      file:
+        path: "{{backend_media_root}}"
+        state: directory
+        owner: uwsgi
+        group: iri
+        mode: 0755
+
+    - name: install backend venv
+      block:
+        - name: register backend virtualenv stats
+          stat: path="{{backend_venv}}"
+          register: backend_venv_stats
+
+        - name: copy backend base requirement file
+          copy:
+              src: "{{backend_build_dir}}/requirements/base.txt"
+              dest: "{{backend_config_base}}/requirements/base.txt"
+          register: copy_backend_requirements_base
+
+        - name: copy backend prod requirement file
+          copy:
+              src: "{{backend_build_dir}}/requirements/prod.txt"
+              dest: "{{backend_config_base}}/requirements/prod.txt"
+          register: copy_backend_requirements_prod
+
+
+        - name: remove annotation api venv
+          file:
+              path: "{{backend_venv}}"
+              state: absent
+          when: copy_backend_requirements_base.changed or copy_backend_requirements_prod.changed
+
+        - name: create backend virtualenv
+          pip:
+              virtualenv_command: /usr/bin/python3 -m venv
+              virtualenv: "{{backend_venv}}"
+              name: pip
+              state: latest
+          when: (copy_backend_requirements_base.changed or copy_backend_requirements_prod.changed) or (not backend_venv_stats.stat.exists)
+
+        - name: virtualenv install requirements
+          pip:
+              virtualenv: "{{backend_venv}}"
+              requirements: "{{backend_config_base}}/requirements/prod.txt"
+          when: (copy_backend_requirements_base.changed or copy_backend_requirements_prod.changed) or (not backend_venv_stats.stat.exists)
+          notify: restart irinotes backend service
+
+        - name: copy backend config
+          template:
+            src: "backend_settings.ini.j2"
+            dest: "{{backend_config_base}}/settings.ini"
+          notify: restart irinotes backend service
+
+        # install irinotes
+        - name: create temporary install directory
+          tempfile:
+              state: directory
+              suffix: build
+          register: backend_temp_folder
+
+        - name: copy irinotes backend build
+          copy:
+              src: "{{backend_build_dir}}/dist/irinotes-{{backend_pkg_version}}.tar.gz"
+              dest: "{{backend_temp_folder.path}}/irinotes-{{backend_pkg_version}}.tar.gz"
+
+        - name: virtualenv remove irinotes
+          pip:
+              virtualenv: "{{backend_venv}}"
+              name: irinotes
+              state: absent
+          notify: restart irinotes backend service
+
+        - name: virtualenv install irinotes
+          pip:
+              virtualenv: "{{backend_venv}}"
+              name: "file://{{backend_temp_folder.path}}/irinotes-{{backend_pkg_version}}.tar.gz"
+          notify: restart irinotes backend service
+
+        - name: irinotes migrate
+          command: "{{backend_venv}}/bin/django-admin migrate --noinput"
+          args:
+            chdir: "{{backend_venv}}"
+          environment:
+            DJANGO_SETTINGS_MODULE: "irinotes.settings"
+            IRINOTES_CONFIG_BASE: "{{backend_config_base}}"
+          become: yes
+          become_user: uwsgi
+          notify: restart irinotes backend service
+
+        - name: irinotes collectstatic
+          command: "{{backend_venv}}/bin/django-admin collectstatic --noinput -c"
+          args:
+            chdir: "{{backend_venv}}"
+          environment:
+            DJANGO_SETTINGS_MODULE: "irinotes.settings"
+            IRINOTES_CONFIG_BASE: "{{backend_config_base}}"
+          become: yes
+          become_user: uwsgi
+          notify: restart irinotes backend service
+
+      always:
+        - name: remove temp folder
+          file:
+            state: absent
+            path: "{{backend_temp_folder.path}}"
+          when: backend_temp_folder is defined
+
+
+    - name: copy backend uwsgi config
+      template:
+        src: "uwsgi_irinotes.yml.j2"
+        dest: "{{backend_config_base}}/uwsgi_irinotes.yml"
+      notify: restart irinotes backend service
+
+    - name: copy annotations api supervisor config
+      template:
+        src: "supervisord_irinotes_backend.ini.j2"
+        dest: "{{backend_supervisor_conf_path}}/irinotes_backend.ini"
+      notify: restart irinotes backend service
+
+    - name: Add uwsgi app (reread + add)
+      supervisorctl: name=irinotes_backend state=present
+
+  handlers:
+    - name: reload backend service supervisorctl
+      listen: "restart irinotes backend service"
+      supervisorctl:
+        name: irinotes_backend
+        state: restarted
+      ignore_errors: yes
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/group_vars/group_vars.yml.tmpl	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,102 @@
+---
+# irinotes repository url
+irinotes_repo: "https://www.iri.centrepompidou.fr/dev/hg/irinotes"
+
+# irinites version, should be overriden from command line
+irinotes_version: "HEAD"
+
+# remote path for static files
+# note : must not end with /
+remote_static_path: ""
+
+# remote path for nginx static file config
+static_nginx_config: ""
+
+# use the nginx ssl config template for static webserver of not
+static_nginx_use_ssl: true
+
+# static file domain
+static_server_name: ""
+
+# Remote static nginx service
+static_http_service: "nginx"
+
+# Static server nginx user
+static_http_user: "www-data"
+# Static server nginx group
+static_http_group: "www-data"
+
+# NGINX config
+backend_upstream_name: "irinotes_backend"
+backend_host: 127.0.0.1
+backend_port: 8081
+backend_url: ""
+
+
+# dashboard log base path
+log_base_path: ""
+
+# backend config base path, i.e. the path where all configuration files are stored. (no slash)
+backend_config_base: ""
+
+# backend virtualenv path
+backend_venv: ""
+
+# backend application base url
+backend_base_url: ""
+
+# base url for static resources (ends with "/")
+# default : /static/
+backend_static_url: "{{backend_base_url}}/backend/static/"
+
+# The absolute path to the directory where collectstatic will collect static files for deployment. (https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-STATIC_ROOT)
+backend_static_root: ""
+
+# The absolute path to the directory from where nginx will serve the backend static files
+backend_nginx_static_root: "{{backend_static_root}}"
+
+
+# base url for media (ends with "/")
+# default : /media/
+backend_media_url: "{{backend_base_url}}/backend/media/"
+
+# Absolute filesystem path to the directory that will hold user-uploaded files (https://docs.djangoproject.com/en/1.11/ref/settings/#media-root)
+backend_media_root: ""
+
+# Absolute filesystem path to the directory from where nginx will serve the backend media files
+backend_nginx_media_root: "{{backend_media_root}}"
+
+# Secret key for the application. cf. https://docs.djangoproject.com/en/1.11/ref/settings/#secret-key
+backend_secret_key: "REPLACE_SECRET_KEY"
+
+# Debug the application. Default True
+# DEBUG=<true|False>
+backend_debug: "True"
+
+# Comma separated values of authorized host. cf. https://docs.djangoproject.com/en/1.11/ref/settings/#allowed-hosts
+# default: empty
+backend_allowed_hosts: "{{static_server_name}}"
+
+# 12factor inspired DATABASE_URL environment variable cf.https://github.com/kennethreitz/dj-database-url
+# examples: postgres://<user>:<password>@<host>:<port>/<db_name>
+# examples: sqlite:////full/path/to/your/database/file.sqlite
+backend_database_url: ""
+
+# path for the log file
+backend_log_file: "{{log_base_path}}/backend.log"
+
+# log level one of CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET. Default: ERROR
+backend_log_level: "DEBUG"
+
+# expiration delta for JWT tokens (in seconds)
+# default: 3600
+backend_jwt_expiration_delta: 3600
+
+# expiration refresh delta for JWT tokens (in seconds)
+# default: 3600*24*7 = 604800
+backend_jwt_refresh_expiration_delta: 604800
+
+# annotations api supervisor configuration base path
+backend_supervisor_conf_path: "/etc/supervisor.d"
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/hosts/hosts.tmpl	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,12 @@
+[test]
+localhost
+
+[test:children]
+remote_static
+remote_backend
+
+[remote_static]
+172.16.1.7 ansible_user=vagrant
+
+[remote_backend]
+172.16.1.7 ansible_user=vagrant
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/templates/backend_settings.ini.j2	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,45 @@
+[settings]
+# Base url for the application. default=''
+BASE_URL={{backend_base_url}}
+
+# base url for static resources (ends with "/")
+# default : /static/
+STATIC_URL={{backend_static_url}}
+
+# The absolute path to the directory where collectstatic will collect static files for deployment. (https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-STATIC_ROOT)
+# default: <path to irinotes repository clone>/run/web/static
+STATIC_ROOT={{backend_static_root}}
+
+# base url for static resources (ends with "/")
+# default : /media/
+MEDIA_URL={{backend_media_url}}
+
+# Absolute filesystem path to the directory that will hold user-uploaded files (https://docs.djangoproject.com/en/1.11/ref/settings/#media-root)
+# default: <path to irinotes repository clone>/run/web/media
+MEDIA_ROOT={{backend_media_root}}
+
+# Secret key for the application. cf. https://docs.djangoproject.com/en/1.11/ref/settings/#secret-key
+SECRET_KEY={{backend_secret_key}}
+
+# Debug the application. Default True
+DEBUG={{backend_debug}}
+
+# Comma separated values of authorized host. cf. https://docs.djangoproject.com/en/1.11/ref/settings/#allowed-hosts
+# default: empty
+ALLOWED_HOSTS={{backend_allowed_hosts}}
+
+# 12factor inspired DATABASE_URL environment variable cf.https://github.com/kennethreitz/dj-database-url
+# examples: postgres://<user>:<password>@<host>:<port>/<db_name>
+# examples: sqlite:////full/path/to/your/database/file.sqlite
+# default: sqlite:///<path to irinotes repository clone>/run/db/db.sqlite3
+DATABASE_URL={{backend_database_url}}
+
+# path for the log file
+# default: <path to irinotes repository clone>/run/log/log.txt
+LOG_FILE={{backend_log_file}}
+
+# log level one of CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET. Default: ERROR
+LOG_LEVEL={{backend_log_level}}
+
+JWT_EXPIRATION_DELTA={{backend_jwt_expiration_delta}}
+JWT_REFRESH_EXPIRATION_DELTA={{backend_jwt_refresh_expiration_delta}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/templates/nginx.static.conf.j2	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,46 @@
+upstream {{backend_upstream_name}} {
+    server {{backend_host}}:{{backend_port}};
+    server 127.0.0.1 backup;
+}
+
+
+server {
+    listen 80;
+    listen [::]:80;
+
+    server_name {{static_server_name}};
+
+    access_log /var/log/nginx/{{static_server_name}}-access.log;
+    error_log /var/log/nginx/{{static_server_name}}-error.log;
+
+    root {{remote_static_path}}/;
+    index index.html index.htm;
+
+
+    location {{backend_url}}/api {
+        uwsgi_pass  {{backend_upstream_name}};
+        include /etc/nginx/uwsgi_params;
+    }
+    location {{backend_url}}/admin {
+        uwsgi_pass  {{backend_upstream_name}};
+        include /etc/nginx/uwsgi_params;
+    }
+    location {{backend_url}}/auth {
+        uwsgi_pass  {{backend_upstream_name}};
+        include /etc/nginx/uwsgi_params;
+    }
+
+    location /backend/static {
+        alias {{backend_nginx_static_root}}; # backend static files
+    }
+
+    location /backend/media {
+        alias {{backend_nginx_media_root}};  # backend media files
+    }
+
+    location / {
+        # First attempt to serve request as file, then
+        # as directory, then fall back to displaying a 404.
+        try_files $uri $uri/ /index.html;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/templates/nginx.static.ssl.conf.j2	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,68 @@
+upstream {{backend_upstream_name}} {
+    server {{backend_host}}:{{backend_port}};
+    server 127.0.0.1 backup;
+}
+
+server {
+    listen 80;
+    listen [::]:80;
+
+    server_name {{static_server_name}};
+    return 301 https://$host$request_uri;
+}
+
+server {
+    listen 443 ssl http2;
+    listen [::]:443 ssl http2;
+
+    server_name {{static_server_name}};
+
+    access_log /var/log/nginx/{{static_server_name}}-access.log;
+    error_log /var/log/nginx/{{static_server_name}}-error.log;
+
+    ssl_certificate /etc/letsencrypt/live/{{static_server_name}}/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/{{static_server_name}}/privkey.pem;
+
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_prefer_server_ciphers on;
+    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
+
+    root {{remote_static_path}}/;
+    index index.html index.htm;
+
+    location /.well-known/acme-challenge {
+        alias /var/lib/letsencrypt/.well-known/acme-challenge;
+        default_type "text/plain";
+        try_files $uri =404;
+    }
+
+    location {{backend_url}}/api {
+        uwsgi_pass  {{backend_upstream_name}};
+        include /etc/nginx/uwsgi_params;
+    }
+
+    location {{backend_url}}/admin {
+        uwsgi_pass  {{backend_upstream_name}};
+        include /etc/nginx/uwsgi_params;
+    }
+
+    location {{backend_url}}/auth {
+        uwsgi_pass  {{backend_upstream_name}};
+        include /etc/nginx/uwsgi_params;
+    }
+
+    location /backend/static {
+        alias {{backend_nginx_static_root}}; # backend static files
+    }
+
+    location /backend/media {
+        alias {{backend_nginx_media_root}};  # backend media files
+    }
+
+    location / {
+        # First attempt to serve request as file, then
+        # as directory, then fall back to displaying a 404.
+        try_files $uri $uri/ /index.html;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/templates/supervisord_irinotes_backend.ini.j2	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,10 @@
+[program:irinotes_backend]
+command={{backend_venv}}/bin/uwsgi --yaml {{backend_config_base}}/uwsgi_irinotes.yml
+stdout_logfile={{log_base_path}}/supervisor_stdout.log
+stderr_logfile={{log_base_path}}/supervisor_stderr.log
+autostart=true
+autorestart=true
+stopsignal=QUIT
+user=uwsgi
+redirect_stderr=true
+directory={{backend_venv}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/templates/uwsgi_irinotes.yml.j2	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,13 @@
+uwsgi:
+  uwsgi-socket: :{{backend_port}}
+  virtualenv: {{backend_venv}}
+  chdir: {{backend_venv}}/bin
+  master: true
+  module: irinotes.wsgi
+  processes: 1
+  buffer-size: 65535
+  logto: {{log_base_path}}/backend_wsgi.log
+  env: IRINOTES_CONFIG_BASE={{backend_config_base}}
+  manage-script-name : true
+  uid = uwsgi
+  gid = uwsgi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/test_playbook.yml	Wed Nov 28 15:45:37 2018 +0100
@@ -0,0 +1,38 @@
+---
+#
+# all actions
+#
+- hosts: all
+  become: true
+  tasks:
+    - name: set eth1 interface
+      blockinfile:
+        path: /etc/network/interfaces
+        block: |
+          auto eth1
+          iface eth1 inet static
+              address 172.16.1.7
+              netmask 255.255.255.0
+      register: eth1_added
+    - name: restart network service
+      service:
+        name: networking
+        state: restarted
+      when: eth1_added.changed
+    - name: install deps
+      apk:
+        name: python-dev,python3-dev,py-virtualenv,nginx,supervisor,build-base,musl-dev,gcc,linux-headers,libffi,libffi-dev, shadow, git, postgresql-dev, postgresql-client, libmemcached-dev, zlib-dev
+        update_cache: yes
+    - name: create etc supervisor.d folder
+      file:
+        path: /etc/supervisor.d
+        state: directory
+    - name: starts supervisord
+      service:
+        name: supervisord
+        state: started
+    - name: Set authorized key for user vagrant copying it from current user
+      authorized_key:
+        user: vagrant
+        state: present
+        key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"