From 4581367d04e2f35105ffee0043a593c470108a08 Mon Sep 17 00:00:00 2001
From: Patrick Jentsch <p.jentsch@uni-bielefeld.de>
Date: Mon, 25 Sep 2023 10:11:11 +0200
Subject: [PATCH] Restructure startup procedure

---
 .dockerignore              |   1 +
 .env.tpl                   | 207 +++----------------------------------
 .gitignore                 |   2 +-
 .gitlab-ci.yml             |  42 ++++++++
 Dockerfile                 |  39 +++----
 app/__init__.py            |   1 -
 app/daemon/corpus_utils.py |   2 +-
 app/main/cli.py            |   2 +
 boot.sh                    |   4 +-
 config.py                  |  15 ++-
 db.env.tpl                 |   9 +-
 docker-compose.yml         |  53 +++++++---
 docker-entrypoint.sh       |  55 ++++++++++
 logs/dummy                 |   0
 nopaque.env.tpl            | 203 ++++++++++++++++++++++++++++++++++++
 15 files changed, 399 insertions(+), 236 deletions(-)
 create mode 100644 .gitlab-ci.yml
 create mode 100755 docker-entrypoint.sh
 delete mode 100644 logs/dummy
 create mode 100644 nopaque.env.tpl

diff --git a/.dockerignore b/.dockerignore
index 9960fd26..11064df5 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -8,5 +8,6 @@
 !.flaskenv
 !boot.sh
 !config.py
+!docker-entrypoint.sh
 !nopaque.py
 !requirements.txt
diff --git a/.env.tpl b/.env.tpl
index b63e2920..172f8b6d 100644
--- a/.env.tpl
+++ b/.env.tpl
@@ -1,204 +1,29 @@
-################################################################################
-# Docker                                                                       #
-################################################################################
-# DEFAULT: ./data
-# NOTE: Use `.` as <project-basedir>
-# HOST_DATA_DIR=
-
-# Example: 1000
+##############################################################################
+# Variables for use in Docker Compose YAML files                             #
+##############################################################################
 # HINT: Use this bash command `id -u`
+# NOTE: 0 (= root user) is not allowed
 HOST_UID=
 
-# Example: 1000
 # HINT: Use this bash command `id -g`
 HOST_GID=
 
-# Example: 999
 # HINT: Use this bash command `getent group docker | cut -d: -f3`
 HOST_DOCKER_GID=
 
-# DEFAULT: ./logs
-# NOTES: Use `.` as <project-basedir>
-# HOST_LOG_DIR=
-
-# DEFAULT: nopaque_default
-# DOCKER_NETWORK_NAME=
-
-################################################################################
-# Flask                                                                        #
-# https://flask.palletsprojects.com/en/1.1.x/config/                           #
-################################################################################
-# CHOOSE ONE: http, https
-# DEFAULT: http
-# PREFERRED_URL_SCHEME=
-
-# DEFAULT: hard to guess string
-# HINT: Use this bash command `python -c "import uuid; print(uuid.uuid4().hex)"`
-# SECRET_KEY=
-
-# DEFAULT: localhost:5000
-# Example: nopaque.example.com/nopaque.example.com:5000
-# HINT: If your instance is publicly available on a different Port then 80/443,
-#       you will have to add this to the server name
-# SERVER_NAME=
-
-# CHOOSE ONE: False, True
-# DEFAULT: False
-# HINT: Set to true if you redirect http to https
-# SESSION_COOKIE_SECURE=
-
-
-################################################################################
-# Flask-Assets                                                                 #
-# https://webassets.readthedocs.io/en/latest/                                  #
-################################################################################
-# CHOOSE ONE: False, True
-# DEFAULT: False
-# ASSETS_DEBUG=
-
-
-################################################################################
-# Flask-Hashids                                                                #
-# https://github.com/Pevtrick/Flask-Hashids                                    #
-################################################################################
-# DEFAULT: 16
-# HASHIDS_MIN_LENGTH=
-
-# NOTE: Use this bash command `python -c "import uuid; print(uuid.uuid4().hex)"`
-#       It is strongly recommended that this is NEVER the same as the SECRET_KEY
-HASHIDS_SALT=
-
-
-################################################################################
-# Flask-Login                                                                  #
-# https://flask-login.readthedocs.io/en/latest/                                #
-################################################################################
-# CHOOSE ONE: False, True
-# DEFAULT: False
-# HINT: Set to true if you redirect http to https
-# REMEMBER_COOKIE_SECURE=
-
-
-################################################################################
-# Flask-Mail                                                                   #
-# https://pythonhosted.org/Flask-Mail/                                         #
-################################################################################
-# EXAMPLE: nopaque Admin <nopaque@example.com>
-MAIL_DEFAULT_SENDER=
-
-MAIL_PASSWORD=
-
-# EXAMPLE: smtp.example.com
-MAIL_SERVER=
-
-# EXAMPLE: 587
-MAIL_PORT=
-
-# CHOOSE ONE: False, True
-# DEFAULT: False
-# MAIL_USE_SSL=
-
-# CHOOSE ONE: False, True
-# DEFAULT: False
-# MAIL_USE_TLS=
-
-# EXAMPLE: nopaque@example.com
-MAIL_USERNAME=
-
-
-################################################################################
-# Flask-SQLAlchemy                                                             #
-# https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/                  #
-################################################################################
-# DEFAULT: 'sqlite:///<nopaque-basedir>/data.sqlite'
-# NOTE: Use `.` as <nopaque-basedir>,
-#       Don't use a SQLite database when using Docker
-# SQLALCHEMY_DATABASE_URI=
-
-
-################################################################################
-# nopaque                                                                      #
-################################################################################
-# An account is registered with this email adress gets automatically assigned
-# the administrator role.
-# EXAMPLE: admin.nopaque@example.com
-NOPAQUE_ADMIN=
-
-# DEFAULT: /mnt/nopaque
-# NOTE: This must be a network share and it must be available on all Docker
-#       Swarm nodes
-# NOPAQUE_DATA_DIR=
-
-# CHOOSE ONE: False, True
-# DEFAULT: True
-# NOPAQUE_IS_PRIMARY_INSTANCE=
-
-# transport://[userid:password]@hostname[:port]/[virtual_host]
-NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI=
-
-# NOTE: Get these from the nopaque development team
-NOPAQUE_DOCKER_REGISTRY_USERNAME=
-NOPAQUE_DOCKER_REGISTRY_PASSWORD=
-
-# DEFAULT: %Y-%m-%d %H:%M:%S
-# NOPAQUE_LOG_DATE_FORMAT=
-
-# DEFAULT: [%(asctime)s] %(levelname)s in %(pathname)s (function: %(funcName)s, line: %(lineno)d): %(message)s
-# NOPAQUE_LOG_FORMAT=
-
-# DEFAULT: INFO
-# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
-# NOPAQUE_LOG_LEVEL=
-
-# CHOOSE ONE: False, True
-# DEFAULT: True
-# NOPAQUE_LOG_FILE_ENABLED=
-
-# DEFAULT: <nopaque-basedir>/logs
-# NOTE: Use `.` as <nopaque-basedir>
-# NOPAQUE_LOG_FILE_DIR=
-
-# DEFAULT: NOPAQUE_LOG_LEVEL
-# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
-# NOPAQUE_LOG_FILE_LEVEL=
-
-# CHOOSE ONE: False, True
-# DEFAULT: False
-# NOPAQUE_LOG_STDERR_ENABLED=
-
-# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
-# DEFAULT: NOPAQUE_LOG_LEVEL
-# NOPAQUE_LOG_STDERR_LEVEL=
-
-# CHOOSE ONE: False, True
-# DEFAULT: False
-# HINT: Set this to True only if you are using a proxy in front of nopaque
-# NOPAQUE_PROXY_FIX_ENABLED=
-
-# DEFAULT: 0
-# Number of values to trust for X-Forwarded-For
-# NOPAQUE_PROXY_FIX_X_FOR=
-
-# DEFAULT: 0
-# Number of values to trust for X-Forwarded-Host
-# NOPAQUE_PROXY_FIX_X_HOST=
-
-# DEFAULT: 0
-# Number of values to trust for X-Forwarded-Port
-# NOPAQUE_PROXY_FIX_X_PORT=
+# DEFAULT: nopaque
+DOCKER_DEFAULT_NETWORK_NAME=nopaque
 
-# DEFAULT: 0
-# Number of values to trust for X-Forwarded-Prefix
-# NOPAQUE_PROXY_FIX_X_PREFIX=
+# DEFAULT: ./volumes/db/data
+DOCKER_DB_SERVICE_DATA_VOLUME_SOURCE_PATH=./volumes/db/data
 
-# DEFAULT: 0
-# Number of values to trust for X-Forwarded-Proto
-# NOPAQUE_PROXY_FIX_X_PROTO=
+# DEFAULT: ./volumes/mq/data
+DOCKER_MQ_SERVICE_DATA_VOLUME_SOURCE_PATH=./volumes/mq/data
 
-# CHOOSE ONE: False, True
-# DEFAULT: False
-# NOPAQUE_TRANSKRIBUS_ENABLED=
+# NOTE: This must be a network share and it must be available on all
+#       Docker Swarm nodes, mounted to the same path with the same
+#       user and group ownership.
+DOCKER_NOPAQUE_SERVICE_DATA_VOLUME_SOURCE_PATH=
 
-# READ-COOP account data: https://readcoop.eu/
-# NOPAQUE_READCOOP_USERNAME=
-# NOPAQUE_READCOOP_PASSWORD=
+# DEFAULT: ./volumes/nopaque/logs
+DOCKER_NOPAQUE_SERVICE_LOGS_VOLUME_SOURCE_PATH=./volumes/nopaque/logs
diff --git a/.gitignore b/.gitignore
index b7a84431..d9a03f48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 # nopaque specifics
 app/static/gen/
-data/
+volumes/
 docker-compose.override.yml
 logs/
 !logs/dummy
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000..f28b1d2a
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,42 @@
+default:
+  image: docker:24.0.6
+  services:
+    - docker:24.0.6-dind
+  tags:
+    - docker
+
+variables:
+  DOCKER_TLS_CERTDIR: /certs
+
+build_image:
+  stage: build
+  rules:
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+      when: on_success
+      variables:
+        IMAGE_TAG: $CI_REGISTRY_IMAGE:latest
+    - if: $CI_COMMIT_TAG
+      when: "on_success"
+      variables:
+        IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
+    - when: never
+  before_script:
+    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+  script:
+    - docker build -t $IMAGE_TAG .
+    - docker push $IMAGE_TAG
+
+include:
+  - template: Security/Container-Scanning.gitlab-ci.yml
+
+container_scanning:
+  rules:
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+      when: on_success
+      variables:
+        CS_IMAGE: $CI_REGISTRY_IMAGE:latest
+    - if: $CI_COMMIT_TAG
+      when: on_success
+      variables:
+        CS_IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}
+    - when: never
diff --git a/Dockerfile b/Dockerfile
index 750fee93..14f14833 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,11 +4,6 @@ FROM python:3.11.5-slim-bookworm
 LABEL authors="Patrick Jentsch <p.jentsch@uni-bielefeld.de>"
 
 
-ARG DOCKER_GID
-ARG UID
-ARG GID
-
-
 ENV LANG="C.UTF-8"
 ENV PYTHONDONTWRITEBYTECODE="1"
 ENV PYTHONUNBUFFERED="1"
@@ -17,34 +12,42 @@ ENV PYTHONUNBUFFERED="1"
 RUN apt-get update \
  && apt-get install --no-install-recommends --yes \
       build-essential \
+      gosu \
       libpq-dev \
  && rm --recursive /var/lib/apt/lists/*
 
 
-RUN groupadd --gid "${DOCKER_GID}" docker \
- && groupadd --gid "${GID}" nopaque \
- && useradd --create-home --gid nopaque --groups "${DOCKER_GID}" --no-log-init --uid "${UID}" nopaque
-USER nopaque
-WORKDIR /home/nopaque
+COPY docker-entrypoint.sh /usr/local/bin/
 
 
-ENV PYTHON3_VENV_PATH="/home/nopaque/venv"
-RUN python3 -m venv "${PYTHON3_VENV_PATH}"
-ENV PATH="${PYTHON3_VENV_PATH}/bin:${PATH}"
+RUN useradd --create-home --no-log-init nopaque \
+ && groupadd docker \
+ && usermod --append --groups docker nopaque
 
 
-COPY --chown=nopaque:nopaque requirements.txt .
-RUN python3 -m pip install --requirement requirements.txt \
- && rm requirements.txt
+USER nopaque
+WORKDIR /home/nopaque
+
+
+ENV NOPAQUE_PYTHON3_VENV_PATH="/home/nopaque/.venv"
+RUN python3 -m venv "${NOPAQUE_PYTHON3_VENV_PATH}"
+ENV PATH="${NOPAQUE_PYTHON3_VENV_PATH}/bin:${PATH}"
 
 
 COPY --chown=nopaque:nopaque app app
 COPY --chown=nopaque:nopaque migrations migrations
 COPY --chown=nopaque:nopaque tests tests
-COPY --chown=nopaque:nopaque .flaskenv boot.sh config.py nopaque.py ./
+COPY --chown=nopaque:nopaque .flaskenv boot.sh config.py nopaque.py requirements.txt ./
+
+
+RUN python3 -m pip install --requirement requirements.txt \
+ && mkdir logs
+
+
+USER root
 
 
 EXPOSE 5000
 
 
-ENTRYPOINT ["./boot.sh"]
+ENTRYPOINT ["docker-entrypoint.sh"]
diff --git a/app/__init__.py b/app/__init__.py
index 41b3eeb1..41a8074d 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -13,7 +13,6 @@ from flask_paranoid import Paranoid
 from flask_socketio import SocketIO
 from flask_sqlalchemy import SQLAlchemy
 from flask_hashids import Hashids
-from werkzeug.exceptions import HTTPException
 
 
 apifairy = APIFairy()
diff --git a/app/daemon/corpus_utils.py b/app/daemon/corpus_utils.py
index 57f0d4df..2819e736 100644
--- a/app/daemon/corpus_utils.py
+++ b/app/daemon/corpus_utils.py
@@ -143,7 +143,7 @@ def _create_cqpserver_container(corpus):
     ''' ## Name ## '''
     name = f'cqpserver_{corpus.id}'
     ''' ## Network ## '''
-    network = f'{current_app.config["DOCKER_NETWORK_NAME"]}'
+    network = f'{current_app.config["NOPAQUE_DOCKER_NETWORK_NAME"]}'
     ''' ## Volumes ## '''
     volumes = []
     ''' ### Corpus data volume ### '''
diff --git a/app/main/cli.py b/app/main/cli.py
index 0284bb88..45fabf38 100644
--- a/app/main/cli.py
+++ b/app/main/cli.py
@@ -43,3 +43,5 @@ def deploy():
     SpaCyNLPPipelineModel.insert_defaults()
     print('Insert/Update default TesseractOCRPipelineModels')
     TesseractOCRPipelineModel.insert_defaults()
+
+    # TODO: Implement checks for if the nopaque network exists
diff --git a/boot.sh b/boot.sh
index f96d7155..91b688b6 100755
--- a/boot.sh
+++ b/boot.sh
@@ -13,7 +13,7 @@ display_help() {
     echo "Run '${script_name} COMMAND --help' for more information on a command."
 }
 
-if [[ "${#}" -eq 0 ]]; then
+if [[ "${#}" == "0" ]]; then
     if [[ "${NOPAQUE_IS_PRIMARY_INSTANCE:-True}" == "True" ]]; then
         while true; do
             flask deploy
@@ -26,7 +26,7 @@ if [[ "${#}" -eq 0 ]]; then
     fi
     python3 nopaque.py
 elif [[ "${1}" == "flask" ]]; then
-    flask "${@:2}"
+    flask ${@:2}
 elif [[ "${1}" == "--help" || "${1}" == "-h" ]]; then
     display_help
 else
diff --git a/config.py b/config.py
index fa1e8a40..0d36e6ae 100644
--- a/config.py
+++ b/config.py
@@ -7,13 +7,10 @@ import os
 
 
 basedir = os.path.abspath(os.path.dirname(__file__))
-load_dotenv(os.path.join(basedir, '.env'))
+load_dotenv(os.path.join(basedir, 'nopaque.env'))
 
 
 class Config:
-    ''' Docker '''
-    DOCKER_NETWORK_NAME = os.environ.get('DOCKER_NETWORK_NAME', 'nopaque_default')
-
     ''' APIFairy '''
     APIFAIRY_TITLE = 'nopaque'
     APIFAIRY_VERSION = '0.0.1'
@@ -35,8 +32,8 @@ class Config:
     ASSETS_DEBUG = os.environ.get('ASSETS_DEBUG', 'false').lower() == 'true'
 
     ''' # Flask-Hashids '''
-    HASHIDS_MIN_LENGTH = 16
-    HASHIDS_SALT=os.environ.get('HASHIDS_SALT')
+    HASHIDS_MIN_LENGTH = int(os.environ.get('HASHIDS_MIN_LENGTH', '16'))
+    HASHIDS_SALT=os.environ.get('HASHIDS_SALT', 'hard to guess string')
 
     ''' # Flask-Login # '''
     REMEMBER_COOKIE_SECURE = \
@@ -61,7 +58,7 @@ class Config:
     ''' # nopaque # '''
     NOPAQUE_ADMIN = os.environ.get('NOPAQUE_ADMIN')
     NOPAQUE_DATA_DIR = \
-        os.path.abspath(os.environ.get('NOPAQUE_DATA_DIR', '/mnt/nopaque'))
+        os.path.abspath(os.environ.get('NOPAQUE_DATA_PATH', '/mnt/nopaque'))
     NOPAQUE_IS_PRIMARY_INSTANCE = \
         os.environ.get('NOPAQUE_IS_PRIMARY_INSTANCE', 'true').lower() == 'true'
     NOPAQUE_MAIL_SUBJECT_PREFIX = '[nopaque]'
@@ -74,6 +71,8 @@ class Config:
 
     NOPAQUE_DOCKER_REGISTRY = 'gitlab.ub.uni-bielefeld.de:4567'
     NOPAQUE_DOCKER_IMAGE_PREFIX = f'{NOPAQUE_DOCKER_REGISTRY}/sfb1288inf/'
+    NOPAQUE_DOCKER_NETWORK_NAME = \
+        os.environ.get('DOCKER_NETWORK_NAME', 'nopaque')
     NOPAQUE_DOCKER_REGISTRY_USERNAME = \
         os.environ.get('NOPAQUE_DOCKER_REGISTRY_USERNAME')
     NOPAQUE_DOCKER_REGISTRY_PASSWORD = \
@@ -90,7 +89,7 @@ class Config:
     NOPAQUE_LOG_FILE_ENABLED = \
         os.environ.get('NOPAQUE_LOG_FILE_ENABLED', 'true').lower() == 'true'
     NOPAQUE_LOG_FILE_DIR = \
-        os.environ.get('NOPAQUE_LOG_FILE_DIR', os.path.join(basedir, 'logs'))
+        os.environ.get('NOPAQUE_LOGS_PATH', os.path.join(basedir, 'logs'))
     NOPAQUE_LOG_FILE_LEVEL = \
         os.environ.get('NOPAQUE_LOG_FILE_LEVEL', NOPAQUE_LOG_LEVEL)
     NOPAQUE_LOG_STDERR_ENABLED = \
diff --git a/db.env.tpl b/db.env.tpl
index 88ee89de..9262172c 100644
--- a/db.env.tpl
+++ b/db.env.tpl
@@ -1,4 +1,11 @@
-POSTGRES_DB_NAME=
+##############################################################################
+# Environment variables to configure the db service in docker-compose.yml.   #
+#                                                                            #
+# More information about the environment variables can be found here:        #
+# https://hub.docker.com/_/postgres                                          #
+##############################################################################
+
+POSTGRES_DB=
 
 POSTGRES_USER=
 
diff --git a/docker-compose.yml b/docker-compose.yml
index 52f011e6..305b4901 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,8 +1,17 @@
 version: "3.5"
 
+# The docker-compose.yml file is not meant to be modified itself. 
+# Instead use the following files for configurations:
+# - .env: Environment variables for the docker-compose.yml file.
+# - db.env: Environment variables for the database service.
+# - nopaque.env: Environment variables for the nopaque service.
+# - docker-compose.override.yml: Override the docker-compose.yml file.
+#   - Don't change too much here, it's meant for configurations like exposing
+#     ports for development or adding labels for e.g. traefik.
+
 networks:
   default:
-    name: "${DOCKER_NETWORK_NAME:-nopaque_default}"
+    name: "${DOCKER_DEFAULT_NETWORK_NAME}"
 
 services:
   db:
@@ -10,28 +19,46 @@ services:
     image: postgres:11
     restart: unless-stopped
     volumes:
-      - "${HOST_DATA_DIR:-./data}/db:/var/lib/postgresql/data"
+      - type: bind
+        source: "${DOCKER_DB_SERVICE_DATA_VOLUME_SOURCE_PATH}"
+        target: "/var/lib/postgresql/data"
 
   mq:
     image: redis:6
     restart: unless-stopped
     volumes:
-      - "${HOST_DATA_DIR:-./data}/mq:/data"
+      - type: bind
+        source: "${DOCKER_MQ_SERVICE_DATA_VOLUME_SOURCE_PATH}"
+        target: "/data"
 
   nopaque:
-    build:
-      args:
-        DOCKER_GID: ${HOST_DOCKER_GID}
-        GID: ${HOST_GID}
-        UID: ${HOST_UID}
-      context: .
+    build: .
     depends_on:
       - db
       - mq
-    env_file: .env
+    env_file:
+      - nopaque.env
+    environment:
+      # This section overrides the values set in the nopaque.env file. Do not
+      # override the environment variables in a docker-compose.override.yml
+      # file unless you really know what you are doing.
+      - NOPAQUE_UID=${HOST_UID}
+      - NOPAQUE_GID=${HOST_GID}
+      - DOCKER_GID=${HOST_DOCKER_GID}
+      - NOPAQUE_DATA_PATH=${DOCKER_NOPAQUE_SERVICE_DATA_VOLUME_SOURCE_PATH}
+      - NOPAQUE_DOCKER_NETWORK_NAME=${DOCKER_DEFAULT_NETWORK_NAME}
+      - NOPAQUE_LOGS_PATH=/home/nopaque/logs
     image: nopaque:latest
     restart: unless-stopped
     volumes:
-      - "/var/run/docker.sock:/var/run/docker.sock"
-      - "${NOPAQUE_DATA_DIR:-/mnt/nopaque}:${NOPAQUE_DATA_DIR:-/mnt/nopaque}"
-      - "${HOST_LOG_DIR-./logs}:${NOPAQUE_LOG_DIR:-/home/nopaque/logs}"
+      - type: bind
+        source: "/var/run/docker.sock"
+        target: "/var/run/docker.sock"
+      # TODO: Make this less quirky. The target path should be variable.
+      #       In order to achieve this, a cifs volume needs to be configured.
+      - type: bind
+        source: "${DOCKER_NOPAQUE_SERVICE_DATA_VOLUME_SOURCE_PATH}"
+        target: "${DOCKER_NOPAQUE_SERVICE_DATA_VOLUME_SOURCE_PATH}"
+      - type: bind
+        source: "${DOCKER_NOPAQUE_SERVICE_LOGS_VOLUME_SOURCE_PATH}"
+        target: "/home/nopaque/logs"
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100755
index 00000000..49d6e4f0
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+GREEN_COLOR="\033[0;32m"
+RED_COLOR="\033[0;31m"
+NO_COLOR="\033[0m"
+CHECK_MARK="\xE2\x9C\x93"
+CROSS_MARK="\xE2\x9D\x8C"
+
+echo -n "Set container UID and GIDs to match the host system..."
+
+if [[ "${NOPAQUE_UID}" == 0 ]]; then
+    echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}"
+    echo "Running as root is not allowed"
+    exit 1
+else
+    echo ""
+fi
+
+echo -n "- Updating docker GID ($(getent group docker | cut -d: -f3) -> ${DOCKER_GID})... "
+groupmod --gid "${DOCKER_GID}" docker > /dev/null
+if [[ "${?}" == "0" ]]; then
+    echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}"
+else
+    echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}"
+    exit 1
+fi
+
+echo -n "- Updating nopaque GID ($(id -g nopaque) -> ${NOPAQUE_GID})... "
+groupmod --gid "${NOPAQUE_GID}" nopaque > /dev/null
+if [[ "${?}" == "0" ]]; then
+    echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}"
+else
+    echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}"
+    exit 1
+fi
+
+echo -n "- Updating nopaque UID ($(id -u nopaque) -> ${NOPAQUE_UID})... "
+usermod --uid "${NOPAQUE_UID}" nopaque > /dev/null
+if [[ "${?}" == "0" ]]; then
+    echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}"
+else
+    echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}"
+    exit 1
+fi
+
+echo -n "- Updating nopaque directory owner and group... "
+chown -R nopaque:nopaque /home/nopaque
+if [[ "${?}" == "0" ]]; then
+    echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}"
+else
+    echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}"
+    exit 1
+fi
+
+exec gosu nopaque ./boot.sh ${@}
diff --git a/logs/dummy b/logs/dummy
deleted file mode 100644
index e69de29b..00000000
diff --git a/nopaque.env.tpl b/nopaque.env.tpl
new file mode 100644
index 00000000..49fcb306
--- /dev/null
+++ b/nopaque.env.tpl
@@ -0,0 +1,203 @@
+##############################################################################
+# Environment variables to configure the nopaque.                            #
+# - When running nopaque with Docker Compose, these variables are set in the #
+#   `docker-compose.yml` file.                                               #
+# - When running nopaque without Docker, these variables are loaded by       #
+#   nopaque in the config.py file                                            #
+##############################################################################
+
+
+##############################################################################
+# Flask                                                                      #
+# https://flask.palletsprojects.com/en/1.1.x/config/                         #
+##############################################################################
+# CHOOSE ONE: http, https
+# DEFAULT: http
+# PREFERRED_URL_SCHEME=
+
+# DEFAULT: hard to guess string
+# HINT: Use this bash command `python -c "import uuid; print(uuid.uuid4().hex)"`
+# SECRET_KEY=
+
+# DEFAULT: localhost:5000
+# EXAMPLES:
+# - nopaque.example.com
+# - nopaque.example.com:5000
+# HINT: If your instance is publicly available on a different Port then 80/443,
+#       you will have to add this to the server name
+# SERVER_NAME=
+
+# CHOOSE ONE: False, True
+# DEFAULT: False
+# HINT: Set to true if you redirect http to https
+# SESSION_COOKIE_SECURE=
+
+
+##############################################################################
+# Flask-Assets                                                               #
+# https://webassets.readthedocs.io/en/latest/                                #
+##############################################################################
+# CHOOSE ONE: False, True
+# DEFAULT: False
+# ASSETS_DEBUG=
+
+
+##############################################################################
+# Flask-Hashids                                                              #
+# https://github.com/Pevtrick/Flask-Hashids                                  #
+##############################################################################
+# DEFAULT: 16
+# HASHIDS_MIN_LENGTH=
+
+# DEFAULT: hard to guess string
+# HINT: Use this bash command `python -c "import uuid; print(uuid.uuid4().hex)"`
+# NOTE: In production it is strongly recommended that this is NEVER the same as
+#       the `SECRET_KEY`
+# HASHIDS_SALT=
+
+
+##############################################################################
+# Flask-Login                                                                #
+# https://flask-login.readthedocs.io/en/latest/                              #
+##############################################################################
+# CHOOSE ONE: False, True
+# DEFAULT: False
+# HINT: Set to true if you redirect http to https
+# REMEMBER_COOKIE_SECURE=
+
+
+##############################################################################
+# Flask-Mail                                                                 #
+# https://pythonhosted.org/Flask-Mail/                                       #
+##############################################################################
+# EXAMPLE: nopaque Admin <nopaque@example.com>
+MAIL_DEFAULT_SENDER=
+
+MAIL_PASSWORD=
+
+# EXAMPLE: smtp.example.com
+MAIL_SERVER=
+
+# EXAMPLE: 587
+MAIL_PORT=
+
+# CHOOSE ONE: False, True
+# DEFAULT: False
+# MAIL_USE_SSL=
+
+# CHOOSE ONE: False, True
+# DEFAULT: False
+# MAIL_USE_TLS=
+
+# EXAMPLE: nopaque@example.com
+MAIL_USERNAME=
+
+
+##############################################################################
+# Flask-SQLAlchemy                                                           #
+# https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/                #
+##############################################################################
+# NOTES:
+# - Use `.` as <nopaque-basedir>
+# - Don't use a SQLite database when using Docker Compose
+SQLALCHEMY_DATABASE_URI=
+
+
+##############################################################################
+# nopaque                                                                    #
+##############################################################################
+# An account is registered with this email adress gets automatically assigned
+# the administrator role
+# EXAMPLE: admin.nopaque@example.com
+NOPAQUE_ADMIN=
+
+# DEFAULT: /mnt/nopaque
+# NOTES: 
+# - This must be a network share and it must be available on all
+#   Docker Swarm nodes, mounted to the same path with the same
+#   user and group ownership
+# - When running with Docker Compose, this gets overwritten in the
+#   `docker-compose.yml` file
+# NOPAQUE_DATA_PATH=
+
+# CHOOSE ONE: False, True
+# DEFAULT: True
+# NOPAQUE_IS_PRIMARY_INSTANCE=
+
+# transport://[userid:password]@hostname[:port]/[virtual_host]
+NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI=
+
+# DEFAULT: nopaque
+# NOTE: When running with Docker Compose, this gets overwritten in the
+#       `docker-compose.yml` file
+# NOPAQUE_DOCKER_NETWORK_NAME=
+
+# NOTE: Get these from the nopaque development team
+NOPAQUE_DOCKER_REGISTRY_USERNAME=
+NOPAQUE_DOCKER_REGISTRY_PASSWORD=
+
+# DEFAULT: %Y-%m-%d %H:%M:%S
+# NOPAQUE_LOG_DATE_FORMAT=
+
+# DEFAULT: [%(asctime)s] %(levelname)s in %(pathname)s (function: %(funcName)s, line: %(lineno)d): %(message)s
+# NOPAQUE_LOG_FORMAT=
+
+# DEFAULT: INFO
+# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
+# NOPAQUE_LOG_LEVEL=
+
+# CHOOSE ONE: False, True
+# DEFAULT: True
+# NOPAQUE_LOG_FILE_ENABLED=
+
+# DEFAULT: <nopaque-basedir>/logs
+# NOTES:
+# - Use `.` as <nopaque-basedir>
+# - When running with Docker Compose, this gets overwritten in the
+#   `docker-compose.yml` file
+# NOPAQUE_LOGS_PATH=
+
+# DEFAULT: NOPAQUE_LOG_LEVEL
+# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
+# NOPAQUE_LOG_FILE_LEVEL=
+
+# CHOOSE ONE: False, True
+# DEFAULT: False
+# NOPAQUE_LOG_STDERR_ENABLED=
+
+# DEFAULT: NOPAQUE_LOG_LEVEL
+# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
+# NOPAQUE_LOG_STDERR_LEVEL=
+
+# CHOOSE ONE: False, True
+# DEFAULT: False
+# HINT: Set this to True only if you are using a proxy in front of nopaque
+# NOPAQUE_PROXY_FIX_ENABLED=
+
+# DEFAULT: 0
+# Number of values to trust for X-Forwarded-For
+# NOPAQUE_PROXY_FIX_X_FOR=
+
+# DEFAULT: 0
+# Number of values to trust for X-Forwarded-Host
+# NOPAQUE_PROXY_FIX_X_HOST=
+
+# DEFAULT: 0
+# Number of values to trust for X-Forwarded-Port
+# NOPAQUE_PROXY_FIX_X_PORT=
+
+# DEFAULT: 0
+# Number of values to trust for X-Forwarded-Prefix
+# NOPAQUE_PROXY_FIX_X_PREFIX=
+
+# DEFAULT: 0
+# Number of values to trust for X-Forwarded-Proto
+# NOPAQUE_PROXY_FIX_X_PROTO=
+
+# CHOOSE ONE: False, True
+# DEFAULT: False
+# NOPAQUE_TRANSKRIBUS_ENABLED=
+
+# READ-COOP account data: https://readcoop.eu/
+# NOPAQUE_READCOOP_USERNAME=
+# NOPAQUE_READCOOP_PASSWORD=
-- 
GitLab