staging #2

Merged
jlobbes merged 2 commits from staging into master 2023-08-17 19:18:14 +01:00
9 changed files with 226 additions and 13 deletions
Showing only changes of commit 279c6b7213 - Show all commits

View File

@ -29,6 +29,8 @@ The file contains the following environment variables:
```conf
TIMEZONE=Europe/Berlin
DJANGO_DEBUG_MODE=false
DJANGO_SECRET_KEY=abc123mySecret
PG_NAME=medwings
PG_USER=medwings
PG_PASSWORD=secret
@ -46,9 +48,11 @@ You should set the values of the following variables:
| variable | description | value |
|----------|-------------|-------|
| PG_PASSWORD | password for the PostgreSQL admin user | a random string of 32 characters |
| GOTIFY_USER | name of the Gotify admin user | a random string of 32 characters |
| GOTIFY_PASSWORD | password for the Gotify admin user | a random string of 32 characters |
| DJANGO_DEBUG_MODE | whether or not to enable Django's debug mode | 'true' during development and 'false' in production |
| DJANGO_SECRET_KEY | private session secret | a random string of 64 characters or more |
| PG_PASSWORD | password for the PostgreSQL admin user | a random string of 32 characters or more |
| GOTIFY_USER | name of the Gotify admin user | a random string of 32 characters or more |
| GOTIFY_PASSWORD | password for the Gotify admin user | a random string of 32 characters or more |
| GOTIFY_PUBLIC_URL | URL where your public Gotify server can be reached | this depends on your deployment environment |
| WITHINGS_CLIENT_ID | Your Withings API client id | see [Withings API](./app/withings/README.md#api-access) |
| WITHINGS_CLIENT_SECRET | Your Withings API client secret | see [Withings API](./app/withings/README.md#api-access) |

View File

@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
from pathlib import Path
from os import getenv
from utils import parse_string_as_bool
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -21,14 +22,10 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-s^q)z%f-7=1h5b00ctki2*-w=#3!k@p-#sq%=eajw)x2axx-e5'
SECRET_KEY = getenv('DJANGO_SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = [
'localhost',
'127.0.0.1',
'192.168.2.141'
]
DEBUG = parse_string_as_bool(getenv('DJANGO_DEBUG_MODE', 'false'))
ALLOWED_HOSTS = [ '*' ]
# Application definition
@ -80,8 +77,8 @@ DATABASES = {
'ENGINE': 'django.db.backends.postgresql',
'NAME': getenv('PG_NAME', 'medwings'),
'USER': getenv('PG_USER', 'medwings'),
'PASSWORD': getenv('PG_PASSWORD', 'medwings'),
'HOST': getenv('PG_HOST', 'medwings-postgres'),
'PASSWORD': getenv('PG_PASSWORD'),
'HOST': getenv('PG_HOST'),
'PORT': getenv('PG_PORT', '5432'),
}
}

27
app/core/utils.py Normal file
View File

@ -0,0 +1,27 @@
"""Miscellaneous utility functions."""
import re
def parse_string_as_bool(value: str) -> bool:
"""Parses the given string into a boolean based on its content.
This is used to parse environment variables as boolean values.
The following strings are parsed as `True`: "yes", "Yes", "YES", "true", "True", "TRUE", "1"
The following strings are parsed as `False`: "no", "No", "NO", "false", "False", "FALSE", "0"
In any other case, a `ValueError` is raised.
"""
if not isinstance(value, str):
raise TypeError("Expected a string argument.")
regex_true = re.compile(r"^(YES)|(Yes)|(yes)|(TRUE)|(True)|(true)|(1)$")
regex_false = re.compile(r"^(NO)|(No)|(no)|(FALSE)|(False)|(false)|(0)$")
if regex_true.fullmatch(value):
return True
if regex_false.fullmatch(value):
return False
raise ValueError(f"Failed to parse the supplied value as a boolean: {value!r}")

View File

@ -1,9 +1,11 @@
asgiref==3.7.2
certifi==2023.7.22
charset-normalizer==3.2.0
click==8.1.6
Django==4.2.3
django-widget-tweaks==1.4.12
djangorestframework==3.14.0
h11==0.14.0
idna==3.4
psycopg==3.1.9
psycopg-binary==3.1.9
@ -12,3 +14,4 @@ requests==2.31.0
sqlparse==0.4.4
typing_extensions==4.7.1
urllib3==2.0.4
uvicorn==0.23.2

View File

@ -26,7 +26,7 @@ services:
- ${PG_HOST}
build:
context: .
dockerfile: ./development.django.Dockerfile
dockerfile: development.django.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000

View File

@ -0,0 +1,24 @@
# syntax=docker/dockerfile:1
FROM alpine:latest
# Install caddy
RUN apk add --no-cache caddy
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=webserver
ENV CUSTOM_GROUPNAME=webserver
RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
# Copy caddy config
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} Caddyfile /app/
# Run Caddy in development mode
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
EXPOSE 8000
ENTRYPOINT ["caddy", "run", "--config", "/app/Caddyfile", "--adapter", "caddyfile"]

View File

@ -0,0 +1,33 @@
# syntax=docker/dockerfile:1
FROM python:alpine
# Install cron daemon and supervisord
RUN apk add --no-cache dcron supervisor
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=django
ENV CUSTOM_GROUPNAME=django
RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
ENV PATH "$PATH:/home/${CUSTOM_GROUPNAME}/.local/bin"
# Add supervisord conf
COPY production.supervisord.conf /etc/supervisord.conf
# Add cron job
COPY --chmod=600 django.crontab /etc/crontabs/django
# Copy source files
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} app/ /app/
# Install dependencies
RUN pip install -r requirements.txt
# Run supervisord
EXPOSE 8000/tcp
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

View File

@ -0,0 +1,108 @@
---
version: "3"
services:
medwings-caddy:
container_name: medwings-caddy
restart: unless-stopped
build:
context: .
dockerfile: production.caddy.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
expose:
- "8000"
networks:
- medwings
- proxy
environment:
TZ: ${TIMEZONE}
labels:
- "traefik.enable=true"
- "traefik.http.routers.medwings.entrypoints=https"
- "traefik.http.routers.medwings.rule=Host(`medwings.lobbes.dev`)"
- "traefik.http.routers.medwings-secure.middlewares=default@file"
- "traefik.http.routers.medwings.tls=true"
- "traefik.http.services.medwings.loadbalancer.server.port=8000"
- "traefik.docker.network=proxy"
medwings-django:
container_name: medwings-django
restart: unless-stopped
depends_on:
- medwings-caddy
- ${PG_HOST}
build:
context: .
dockerfile: production.django.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
expose:
- "8000"
networks:
- medwings
environment:
TZ: ${TIMEZONE}
PG_NAME: ${PG_NAME}
PG_USER: ${PG_USER}
PG_PASSWORD: ${PG_PASSWORD}
PG_HOST: ${PG_HOST}
PG_PORT: ${PG_PORT}
WITHINGS_CLIENT_ID: ${WITHINGS_CLIENT_ID}
WITHINGS_CLIENT_SECRET: ${WITHINGS_CLIENT_SECRET}
GOTIFY_USER: ${GOTIFY_USER}
GOTIFY_PASSWORD: ${GOTIFY_PASSWORD}
GOTIFY_HOST: ${GOTIFY_HOST}
GOTIFY_PUBLIC_URL: ${GOTIFY_PUBLIC_URL}
medwings-postgres:
image: postgres:alpine
container_name: ${PG_HOST}
restart: unless-stopped
expose:
- ${PG_PORT}
networks:
- medwings
volumes:
- /srv/medwings/db:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${PG_NAME}
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASSWORD}
TZ: ${TIMEZONE}
medwings-gotify:
image: gotify/server
container_name: medwings-gotify
restart: unless-stopped
expose:
- "80"
networks:
- medwings
- proxy
volumes:
- /srv/medwings/gotify:/app/data
environment:
TZ: ${TIMEZONE}
GOTIFY_SERVER_SSL_REDIRECTTOHTTPS: false
GOTIFY_DEFAULTUSER_NAME: ${GOTIFY_USER}
GOTIFY_DEFAULTUSER_PASS: ${GOTIFY_PASSWORD}
GOTIFY_SERVER_CORS_ALLOWORIGINS: "- \"medwings-django:8000\"\n- \"medwings-notifications.lobbes.dev\"\n- \"medwings.lobbes.dev\""
GOTIFY_SERVER_CORS_ALLOWMETHODS: "- \"GET\"\n- \"POST\""
GOTIFY_SERVER_CORS_ALLOWHEADERS: "- \"Authorization\"\n- \"content-type\""
labels:
- "traefik.enable=true"
- "traefik.http.routers.medwings-notifications.entrypoints=https"
- "traefik.http.routers.medwings-notifications.rule=Host(`medwings-notifications.lobbes.dev`)"
- "traefik.http.routers.medwings-notifications-secure.middlewares=default@file"
- "traefik.http.routers.medwings-notifications.tls=true"
- "traefik.http.services.medwings-notifications.loadbalancer.server.port=80"
- "traefik.docker.network=proxy"
networks:
medwings:
external: false
proxy:
external: true
...

View File

@ -0,0 +1,17 @@
[supervisord]
nodaemon=true
user=root
[program:django]
command=sh -c 'uvicorn core.asgi:application --host 0.0.0.0 --port 8000 --access-log'
directory=/app
user=django
autostart=true
autorestart=true
redirect_stderr=true
[program:crond]
command=crond -f
autostart=true
autorestart=true
redirect_stderr=true