diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e18fae4c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +latex/anschreiben/veranstalter.adr +# Ignore all files in db folder +db/* +# But keep the folder +!db/.gitkeep + +*.pyc +htmlcov +pyenv +*.aux +*.log +.idea +*.pdf +*.adr +.project +.pydevproject +.settings/* +.coverage +settings_secret.py +static/* +venv/ +.venv +.vscode/* +.python-version + +node_modules/ + +*.mo + +.env \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..d0a33ace --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +SETTINGS_FILE=settings +DEBUG=False +ALLOWED_HOSTS=.fachschaft.informatik.tu-darmstadt.de,.d120.de +SECRET_KEY= +KEYCLOAK_CLIENT_ID= +KEYCLOAK_SECRET= +KEYCLOAK_SERVER_URL= +EMAIL_HOST= +EMAIL_PORT= +EMAIL_HOST_USER= +EMAIL_HOST_PASSWORD= +GUNICORN_WORKERS= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8e614510..25255289 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,12 @@ pyenv settings_secret.py static/* venv/ +.venv/ .vscode/* .python-version node_modules/ + +*.mo + +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b96f6326 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,74 @@ +## Stage 1: build stage +FROM python:3.13-slim AS builder + +ARG DEBIAN_FRONTEND=noninteractive +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +# install packages and node +RUN apt-get update && apt-get install -y \ + curl \ + gnupg \ + ca-certificates \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y nodejs \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# install pip requirements +COPY requirements.txt . +RUN pip install --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# install node_modules +COPY package*.json ./ +RUN npm i + +## Stage 2: production stage +FROM python:3.13-slim + +ARG DEBIAN_FRONTEND=noninteractive +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +RUN useradd -m -r appuser && \ + mkdir /app && \ + chown -R appuser /app + +# Copy dependencies from builder +COPY --from=builder /usr/local/lib/python3.13/site-packages/ /usr/local/lib/python3.13/site-packages/ +COPY --from=builder /usr/local/bin/ /usr/local/bin/ +COPY --from=builder /app/node_modules/ /app/node_modules/ + +# install gettext for translations +RUN apt-get update && apt-get install -y gettext \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# copy the code +COPY --chown=appuser:appuser . . + +WORKDIR /app/src + +USER appuser + +# compile translations +RUN django-admin compilemessages + +# --- Runtime Setup --- +USER root +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +USER appuser + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] + +EXPOSE 8000 + +# start applicaiton with gunicorn +CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:8000 --workers ${GUNICORN_WORKERS:-3} wsgi:application"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..8320c1b9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + pyfeedback: + build: . + container_name: pyfeedback-docker + ports: + - "8000:8000" + env_file: + - .env + volumes: + - pyfeedback-db:/app/db/ + +volumes: + pyfeedback-db: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..5573e743 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# Exit on error +set -e + +echo "Applying database migrations..." +python manage.py migrate --no-input + +# Start the application +echo "Starting Gunicorn..." +exec "$@" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7cd115d3..eb640240 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ django-debug-toolbar==6.3.0 freezegun==1.5.5 django-formtools==2.5.1 django-allauth[socialaccount,keycloak]==65.16.1 +gunicorn==26.0.0 \ No newline at end of file diff --git a/src/django.wsgi b/src/django.wsgi deleted file mode 100644 index ac4b0cc0..00000000 --- a/src/django.wsgi +++ /dev/null @@ -1,8 +0,0 @@ -import os - -from django.core.wsgi import get_wsgi_application - - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") - -application = get_wsgi_application() diff --git a/src/locale/de/LC_MESSAGES/django.mo b/src/locale/de/LC_MESSAGES/django.mo deleted file mode 100644 index 21fa752f..00000000 Binary files a/src/locale/de/LC_MESSAGES/django.mo and /dev/null differ diff --git a/src/locale/en/LC_MESSAGES/django.mo b/src/locale/en/LC_MESSAGES/django.mo deleted file mode 100644 index fbe24ad2..00000000 Binary files a/src/locale/en/LC_MESSAGES/django.mo and /dev/null differ diff --git a/src/settings_production.py b/src/settings_production.py index ba6f04ea..939eb06e 100644 --- a/src/settings_production.py +++ b/src/settings_production.py @@ -1,11 +1,11 @@ from settings import * -import settings_secret as secrets +import os -DEBUG = False +DEBUG = (os.getenv("DEBUG", "False") == "True") # os.getenv only returns a string -ALLOWED_HOSTS = ['.fachschaft.informatik.tu-darmstadt.de', '.d120.de'] +ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS").split(",") -SECRET_KEY = secrets.SECRET_KEY +SECRET_KEY = os.getenv("SECRET_KEY") URL_PREFIX = 'feedback/' @@ -20,10 +20,10 @@ { "provider_id": "keycloak", "name": "Keycloak", - "client_id": secrets.KEYCLOAK_CLIENT_ID, - "secret": secrets.KEYCLOAK_SECRET, + "client_id": os.getenv("KEYCLOAK_CLIENT_ID"), + "secret": os.getenv("KEYCLOAK_SECRET"), "settings": { - "server_url": secrets.KEYCLOAK_SERVER_URL, + "server_url": os.getenv("KEYCLOAK_SERVER_URL"), }, } ] @@ -33,11 +33,11 @@ # @see https://docs.djangoproject.com/es/1.9/topics/email/ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_HOST = 'mail.d120.de' -EMAIL_PORT = 587 +EMAIL_HOST = os.getenv("EMAIL_HOST") +EMAIL_PORT = int(os.getenv("EMAIL_PORT")) EMAIL_USE_TLS = True -EMAIL_HOST_USER = 'pyfeedback' -EMAIL_HOST_PASSWORD = secrets.EMAIL_HOST_PASSWORD +EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD") SESSION_COOKIE_PATH = '/feedback' SESSION_COOKIE_SECURE = True diff --git a/src/wsgi.py b/src/wsgi.py new file mode 100644 index 00000000..872c4101 --- /dev/null +++ b/src/wsgi.py @@ -0,0 +1,10 @@ +import os + +from django.core.wsgi import get_wsgi_application + +# select in .env which settings to use +settings_file = os.getenv("SETTINGS_FILE", "settings") + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_file) + +application = get_wsgi_application()