diff --git a/backend/Dockerfile b/backend/Dockerfile index 252018e..0622790 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -30,5 +30,8 @@ ENV DJANGO_SETTINGS_MODULE="sophon.settings" # Store the DBM file in a nice place ENV APACHE_PROXY_EXPRESS_DBM="/run/sophon/proxy/proxy.dbm" +# Set the static files directory +ENV STATIC_ROOT="/usr/src/app/static" + # Start the uvicorn server -ENTRYPOINT ["poetry", "run", "gunicorn", "sophon.asgi:application", "-k", "uvicorn.workers.UvicornWorker"] +ENTRYPOINT ["bash", "./docker_start.sh"] diff --git a/backend/docker_start.sh b/backend/docker_start.sh new file mode 100644 index 0000000..55c9d69 --- /dev/null +++ b/backend/docker_start.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +poetry run python ./manage.py migrate --no-input +poetry run python ./manage.py collectstatic --no-input +poetry run python ./manage.py initsuperuser +poetry run gunicorn sophon.asgi:application -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 diff --git a/backend/sophon/core/management/__init__.py b/backend/sophon/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/sophon/core/management/commands/__init__.py b/backend/sophon/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/sophon/core/management/commands/initsuperuser.py b/backend/sophon/core/management/commands/initsuperuser.py new file mode 100644 index 0000000..ada5b00 --- /dev/null +++ b/backend/sophon/core/management/commands/initsuperuser.py @@ -0,0 +1,25 @@ +import logging +from os import environ + +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand + +log = logging.getLogger(__name__) + + +# Based on https://stackoverflow.com/a/39745576/4334568 +class Command(BaseCommand): + help = "Creates the superuser non-interactively if it doesn't exist" + + def handle(self, *args, **options): + User = get_user_model() + log.debug("Checking if an user already exists...") + if not User.objects.exists(): + log.info("Creating superuser...") + User.objects.create_superuser( + username=environ["DJANGO_SU_USERNAME"], + email=environ["DJANGO_SU_EMAIL"], + password=environ["DJANGO_SU_PASSWORD"], + ) + else: + log.info("An user already exists, not creating superuser.") diff --git a/backend/sophon/notebooks/apache.py b/backend/sophon/notebooks/apache.py index e0f4562..295a85e 100644 --- a/backend/sophon/notebooks/apache.py +++ b/backend/sophon/notebooks/apache.py @@ -5,7 +5,7 @@ import socket import typing as t log = logging.getLogger(__name__) -db_name = os.environ.get("APACHE_PROXY_EXPRESS_DBM", "proxy.dbm") +db_name = os.environ.get("APACHE_PROXY_EXPRESS_DBM", "/run/sophon/proxy/proxy.dbm") base_domain = os.environ["APACHE_PROXY_BASE_DOMAIN"] http_protocol = os.environ.get("APACHE_PROXY_HTTP_PROTOCOL", "https") diff --git a/backend/sophon/settings.py b/backend/sophon/settings.py index 6273572..b288d7c 100644 --- a/backend/sophon/settings.py +++ b/backend/sophon/settings.py @@ -28,7 +28,7 @@ SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me-in-production") # SECURITY WARNING: don't run with debug turned on in production! DEBUG = bool(os.environ.get("DJANGO_DEBUG")) -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = os.environ["DJANGO_ALLOWED_HOSTS"].split("|") # Application definition @@ -144,6 +144,7 @@ USE_TZ = True # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' +STATIC_ROOT = '/run/sophon/static/' # Django REST framework diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2f8f206 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,84 @@ +version: "3.9" + + +volumes: + proxy-data: + static-data: + db-data: + + +networks: + bone: + + +services: + db: + image: postgres + volumes: + - db-data:/var/lib/postgresql/data + environment: + # Don't change these. + - POSTGRES_USER=sophon + - POSTGRES_PASSWORD=sophonity + - POSTGRES_DB=sophon + networks: + - bone + + frontend: + image: steffo45/sophon-frontend + environment: + - REACT_APP_DEFAULT_INSTANCE=http://api.dev.sophon.steffo.eu + networks: + - bone + + backend: + image: steffo45/sophon-backend + environment: + # TODO: Set a random secret key! + - DJANGO_SECRET_KEY=change-me-in-production + # TODO: Configure your allowed origins! (* doesn't work) + - DJANGO_CORS_ALLOWED_ORIGINS=http://dev.sophon.steffo.eu + # TODO: Configure your allowed hosts! + - DJANGO_ALLOWED_HOSTS=api.dev.sophon.steffo.eu + # TODO: Configure your proxy details! + - APACHE_PROXY_BASE_DOMAIN=dev.sophon.steffo.eu + - APACHE_PROXY_HTTP_PROTOCOL=http + # TODO: Set your language! + - DJANGO_LANGUAGE_CODE=en-us + # TODO: Set your timezone! + - DJANGO_TIME_ZONE=CET + # TODO: Set the superuser login details! + - DJANGO_SU_USERNAME=root + - DJANGO_SU_EMAIL=root@example.org + - DJANGO_SU_PASSWORD=square + # Don't change these. + - DJANGO_DATABASE_HOST=db + - DJANGO_DATABASE_USER=sophon + - DJANGO_DATABASE_PASSWORD=sophonity + - DJANGO_DATABASE_NAME=sophon + volumes: + - proxy-data:/run/sophon/proxy + - static-data:/run/sophon/static + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - db + networks: + - bone + + proxy: + image: steffo45/sophon-proxy + ports: + - "80:80" + environment: + # TODO: Configure your proxy details! + - APACHE_PROXY_BASE_DOMAIN=dev.sophon.steffo.eu + #`Don't change these. + - SOPHON_BACKEND_NAME=backend:8000 + - SOPHON_FRONTEND_NAME=frontend:5000 + volumes: + - proxy-data:/run/sophon/proxy + depends_on: + - backend + - frontend + networks: + - bone diff --git a/proxy/httpd.conf b/proxy/httpd.conf index 6268a68..06b1689 100644 --- a/proxy/httpd.conf +++ b/proxy/httpd.conf @@ -550,7 +550,10 @@ SSLRandomSeed connect builtin -# Sophon specific settings +# === Sophon specific settings === +# --- DARK SORCERY AHEAD --- +# Regexes used as string comparisions lie beyond this line. + # Enable rewriting (proxying) RewriteEngine on @@ -558,33 +561,32 @@ RewriteEngine on # Preserve host ProxyPreserveHost on -# Pass the base domain from the environment -PassEnv "APACHE_PROXY_BASE_DOMAIN" -# Pass the sophon backend and frontend image name from the environment -PassEnv "SOPHON_BACKEND_NAME" -PassEnv "SOPHON_FRONTEND_NAME" - # Proxy regular requests to the frontend # sophon.steffo.eu → frontend -RewriteCond "%{HTTP_HOST}" "^${ENV:APACHE_PROXY_BASE_DOMAIN}$" [NC] -RewriteRule "/(.*)" "http://${ENV:SOPHON_FRONTEND}/$1" [P,L] +RewriteCond "%{ENV:APACHE_PROXY_BASE_DOMAIN} %{HTTP_HOST}" "^([^ ]+) \1$" [NC] # If ENV:APACHE_PROXY_BASE_DOMAIN equals HTTP_HOST +RewriteCond "%{ENV:SOPHON_FRONTEND_NAME}" "^(.+)$" [NC] # Capture ENV:SOPHON_FRONTEND_NAME for substitution in the rewriterule +RewriteRule "/(.*)" "http://%1/$1" [P,L,E=matched:1] # Rewrite and set the matched flag # Proxy api requests to the backend # api.sophon.steffo.eu → backend -RewriteCond "%{HTTP_HOST}" "^api.${ENV:APACHE_PROXY_BASE_DOMAIN}$" [NC] -RewriteRule "/(.*)" "http://${ENV:SOPHON_BACKEND_NAME}/$1" [P,L] +RewriteCond "api.%{ENV:APACHE_PROXY_BASE_DOMAIN} %{HTTP_HOST}" "^([^ ]+) \1$" [NC] # If api. prefixed to ENV:APACHE_PROXY_BASE_DOMAIN equals HTTP_HOST +RewriteCond "%{ENV:SOPHON_BACKEND_NAME}" "^(.+)$" [NC] # Capture ENV:SOPHON_BACKEND_NAME for substitution in the rewriterule +RewriteRule "/(.*)" "http://%1/$1" [P,L,E=matched:1] # Rewrite and set the matched flag # Create a map between the proxy file generated by Sophon and Apache -RewriteMap "sophonproxy" "dbm=gdbm:/run/sophon/proxy/proxy.dbm" +RewriteMap "sophonproxy" "dbm=gdbm:/run/sophon/proxy/proxy.dbm" # Proxy websockets to the notebooks # *.sophon.steffo.eu → notebook -RewriteCond "%{HTTP_HOST}" ".${ENV:APACHE_PROXY_BASE_DOMAIN}$" [NC] -RewriteCond "%{HTTP:Connection}" "Upgrade" [NC] -RewriteCond "%{HTTP:Upgrade}" "websocket" [NC] -RewriteRule "/(.*)" "ws://${sophonproxy:%{HTTP_HOST}}/$1" [P,L] +RewriteCond "%{ENV:matched}" "! -eq 1" [NC] # If the url hasn't been matched by the previous rules +RewriteCond ".%{ENV:APACHE_PROXY_BASE_DOMAIN} %{HTTP_HOST}" "^([^ ]+) [^ ]+\1$" [NC] # If this is any other subdomain of ENV:APACHE_PROXY_BASE_DOMAIN +RewriteCond "%{HTTP:Connection}" "Upgrade" [NC] # If this is a websocket connection +RewriteCond "%{HTTP:Upgrade}" "websocket" [NC] # If this is a websocket connection +RewriteRule "/(.*)" "ws://${sophonproxy:%{HTTP_HOST}}/$1" [P,L,E=matched:1] # Rewrite and set the matched flag # Proxy regular requests to the notebooks # *.sophon.steffo.eu → notebook -RewriteCond "%{HTTP_HOST}" ".${ENV:APACHE_PROXY_BASE_DOMAIN}$" [NC] -RewriteRule "/(.*)" "http://${sophonproxy:%{HTTP_HOST}}/$1" [P,L] +RewriteCond "%{ENV:matched}" "! -eq 1" [NC] # If the url hasn't been matched by the previous rules +RewriteCond ".%{ENV:APACHE_PROXY_BASE_DOMAIN} %{HTTP_HOST}" "^([^ ]+) [^ ]+\1$" [NC] # If this is any other subdomain of ENV:APACHE_PROXY_BASE_DOMAIN +RewriteCond "%{HTTP_HOST}" ".%{ENV:APACHE_PROXY_BASE_DOMAIN}$" [NC] +RewriteRule "/(.*)" "http://${sophonproxy:%{HTTP_HOST}}/$1" [P,L,E=matched:1]