Warning: Setting up and operating INLOOP in a production environment requires a solid understanding of Linux server administration and familiarity with PostgreSQL, Redis and nginx.
Although INLOOP is a Python application, it cannot simply be installed using pip install
.
Instead, we use a Git based deployment and perform a checkout of the latest stable code from the
main
branch.
INLOOP is a divided into several components and depends on multiple third-party components. Each one will run as a separate operating system process. The following diagram shows their responsibilities and interactions:
Things to note:
- The core components of INLOOP, the web application and the job queue (the blue boxes), are both written in Python and communicate with each other via Redis and the PostgreSQL database. The former one is used as session store and job broker, the latter one for general persistence.
- Client HTTP(S) requests are first buffered and proxied by nginx, a robust, small and fast webserver which, among other advantages, does a better job at handling SSL/TLS encryption and serving static files than Gunicorn/Python.
- The reverse proxy is the only component that is accessible from the "outside" (the internet). All other components communicate via the local loopback interface or a private network.
- For development purposes, Django's
runserver
command incorporates everything that nginx, Gunicorn and PostgreSQL provide in production (the green area). - In fact, INLOOP is a distributed application: it is possible to run the huey queue and Docker runtime on another host. For this to work, one needs to setup a shared filesystem (e.g., NFS).
Supported operating system: This manual is written for Debian 10+ and Ubuntu 18.04+. Any other modern Linux distribution should also do the trick, given that it is able to run Docker and Python (see the README for exact version requirements).
Docker setup: see the Docker installation notes.
Hostname setup: verify that your system's hostname is configured correctly. hostname
and
hostname -f
should print the short and fully qualified hostname of your machine, e.g.:
$ hostname
inloop
$ hostname -f
inloop.inf.tu-dresden.de
SSL/TLS key and certificate: INLOOP relies on HTTPS to safely handle user authentication and protect sensitive user data in transit. For evaluation purposes or a staging site, a self-signed certificate will be enough. For a production deployment, a certificate signed by a browser-accepted CA, such as Let's Encrypt, is needed.
Postgres up and running: ensure that the PostgreSQL server is running and you can connect to
the server using the database administrator role (postgres
):
$ sudo -u postgres -i psql -c '\l'
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | postgres=CTc/postgres
(3 rows)
For increased robustness, we recommend to create a separate partition (or LVM volume) and file
system for the Docker runtime (/var/lib/docker
) and the INLOOP data folder (/var/lib/inloop
).
The data folder may be mounted with stricter mount options (nosuid
and nodev
). How to do this
is out of scope of this manual.
As mentioned in the overview, the core components of INLOOP run under separate,
unprivileged daemon user accounts, gunicorn
and huey
, created with:
sudo adduser --system --group --home /var/lib/inloop huey
sudo adduser --system --group --home / gunicorn
Furthermore, a privileged management user account inloop
must be created with:
sudo adduser inloop
sudo adduser inloop docker
sudo adduser inloop sudo
The inloop
account will be used by you for Git checkouts/pulls and the execution of Django
management commands.
The Git import runs non-interactively and there will be no password prompt. Importing from a public
Git repository works out of the box, but for protected repositories, a SSH key without a passphrase
must be generated for the unix user huey
:
sudo -u huey -H ssh-keygen -N ''
This key can then be used as a deployment key for GitHub.
Create a PostgreSQL user and database, both named inloop
:
sudo -u postgres -i createuser inloop
sudo -u postgres -i createdb --owner=inloop inloop
Because we have chosen the same name for the PostgreSQL user, the unix user inloop
is now able to
use psql
without further authentication. This comes in handy for management tasks, such as the
creation of database backups.
We will place MEDIA_ROOT
(see below) into huey
's home directory: /var/lib/inloop/media
. For
the user uploads to work, gunicorn
must be able to write to <MEDIA_ROOT>/solutions
. Everything
else must be owned by huey
:
sudo mkdir -p /var/lib/inloop/media/solutions
sudo chown -R huey:huey /var/lib/inloop
sudo chown gunicorn:gunicorn /var/lib/inloop/media/solutions
-
Log into the
inloop
user account, either viasu - inloop
orsudo -u inloop -i
. -
Clone the INLOOP Git repository into
inloop
's home:git clone https://github.com/st-tu-dresden/inloop.git ~/inloop cd ~/inloop
-
Install required operating system packages (for Debian and Ubuntu):
./support/scripts/debian_setup.sh --prod
and the poetry package manager (for the
inloop
user only):curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
-
Create a clean virtualenv for INLOOP:
python3 -m venv ~/virtualenv
-
Configure INLOOP through environment variables. We will use the
envdir
tool from djb's daemontools package for this purpose, because it makes it very easy to manage a set of environment variables through files:mkdir ~/envdir chmod 700 ~/envdir
For each required environment variable, a file has to be created:
echo VALUE > ~/envdir/VARIABLE_NAME
For example:
echo inloop.settings > ~/envdir/DJANGO_SETTINGS_MODULE echo en_US.UTF-8 > ~/envdir/LANG echo ~/inloop > ~/envdir/PYTHONPATH echo ~/virtualenv/bin:/usr/bin:/bin > ~/envdir/PATH echo ~/htdocs/static > ~/envdir/STATIC_ROOT echo /var/lib/inloop/media > ~/envdir/MEDIA_ROOT pwgen -s 64 1 > ~/envdir/SECRET_KEY ...
Since the services run under different unix user accounts, a password must be set for the
inloop
database user:psql -c "ALTER ROLE inloop WITH PASSWORD '<password>';"
Be sure to use the same password in the
DATABASE_URL
environment variable:echo postgres://inloop:<password>@localhost:5432/inloop > ~/envdir/DATABASE_URL
A fully populated envdir will look like this:
inloop@inloop:~$ tree ~/envdir /home/inloop/envdir/ ├── ADMINS ├── ALLOWED_HOSTS ├── CACHE_URL ├── DATABASE_URL ├── DJANGO_SETTINGS_MODULE ├── FROM_EMAIL ├── LANG ├── MEDIA_ROOT ├── PATH ├── PROXY_ENABLED ├── PYTHONPATH ├── REDIS_URL ├── SECRET_KEY ├── SPT_NOENV ├── STATIC_ROOT ├── WEB_CONCURRENCY └── X_ACCEL_LOCATION 0 directories, 19 files
You can list the effective configuration using:
envdir ~/envdir env
-
Load the environment:
exec envdir ~/envdir $SHELL
Append the above line to
~/.bash_profile
, to automagically load the envdir whenever you login as userinloop
. -
Install all Python requirements and run database migrations. Before, please verify that the output of
command -v pip
is/home/inloop/virtualenv/bin/pip
and rerun steps 4-6 if this is not the case.cd ~/inloop poetry install --only main django-admin migrate
Tip: perform a PostgreSQL backup before migrating with
pg_dump -Fc -f ~/inloop.pgdump
. -
Collect all static files into
STATIC_ROOT
:mkdir -p $STATIC_ROOT django-admin collectstatic
-
Create a superuser, load initial data and configure Django's contrib.sites app:
django-admin createsuperuser django-admin loaddata about_pages staff_group django-admin set_default_site --system-fqdn --name INLOOP
-
Finally, install the provided systemd service units to their appropriate places and activate them:
sudo cp ~/inloop/support/etc/systemd/system/*.service /etc/systemd/system sudo systemctl daemon-reload sudo systemctl enable --now gunicorn.service huey-helper.service
-
Configure nginx as a reverse proxy by copying and adapting the provided example nginx location to
/etc/nginx/conf.d
(or/etc/nginx/sites-available.d
).
Gunicorn and huey may be started and stopped via sudo service [start|stop|restart] <name>
and
will start automatically at boot.
-
Load the latest code from the INLOOP
main
branch:cd ~/inloop git pull
-
Repeat steps 7 and 8 from the installation chapter.
-
Restart services:
sudo service gunicorn restart
Huey will be restarted automatically as a dependent service.
Got a server error? Look here for hints:
-
Check your mailbox, because Django sends detailed error reports via e-mail.
-
Look for error messages in the nginx error log, usually located in
/var/log/nginx/error.log
. -
Service logs for gunicorn and huey can be viewed using
sudo journalctl -u gunicorn.service
and
sudo journalctl -u huey.service
The most common source of errors are wrong file system permissions. Please double check that you have changed ownership and access rights as described in the preparation section.
The following variables are required:
Name | Description |
---|---|
ADMINS |
Comma-separated list of email addresses which receive error reports |
ALLOWED_HOSTS |
Comma-separated list of allowed hosts |
CACHE_URL |
12factor style cache URL, e.g. redis://localhost:1234/0 |
DATABASE_URL |
12factor style database URL, e.g. postgres://user:pass@host:port/db |
DJANGO_SETTINGS_MODULE |
Set to inloop.settings unless you know what you are doing |
FROM_EMAIL |
Address used for outgoing mail, e.g. [email protected] |
LANG |
Set to en_US.UTF-8 unless you know what you are doing |
MEDIA_ROOT |
Path to the directory for user uploads |
PYTHONPATH |
Set it to the path of the INLOOP git clone |
REDIS_URL |
Redis URL used for the queue, e.g. redis://localhost:1234/1 |
SECRET_KEY |
Set to a long random string |
STATIC_ROOT |
Path to the directory where all static files are collected |
The following variables may be set optionally:
Name | Description (default value) |
---|---|
DEBUG |
Debug mode, don't use this in production (False ) |
EMAIL_URL |
12factor style email URL (smtp://:@localhost:25 ) |
INTERNAL_IPS |
Comma-separated list of IP addresses for which more verbose error reports are shown |
PROXY_ENABLED |
Must be set to True if running behind nginx (False ) |
SECURE_COOKIES |
Enable SSL/TLS protection for session and CSRF cookies (True ) |
TIME_ZONE |
The time zone used for displayed dates (Europe/Berlin ) |
WEB_CONCURRENCY |
The number of Gunicorn workers to start (cpu_count() * 2 ) |
X_ACCEL_LOCATION |
The internal X-Accel-Redirect location for nginx, e.g. /sendfile , must be set if PROXY_ENABLED is True |
Furthermore, you can tweak the logging levels for the inloop
, django
(web framework) and huey
(job queue) packages independently with the following environment variables:
Name | Default value |
---|---|
INLOOP_LOGLEVEL |
INFO |
INLOOP_LOGLEVEL_DJANGO |
INFO |
INLOOP_LOGLEVEL_HUEY |
INFO |
Possible values are DEBUG
, INFO
, WARNING
, ERROR
and CRITICAL
.
Additionally, the setproctitle library (which is used by Gunicorn) recognizes SPT_NOENV
. If set,
it will not overwrite /proc/PID/environ
.