staging #2
10
README.md
10
README.md
@ -29,6 +29,8 @@ The file contains the following environment variables:
|
|||||||
|
|
||||||
```conf
|
```conf
|
||||||
TIMEZONE=Europe/Berlin
|
TIMEZONE=Europe/Berlin
|
||||||
|
DJANGO_DEBUG_MODE=false
|
||||||
|
DJANGO_SECRET_KEY=abc123mySecret
|
||||||
PG_NAME=medwings
|
PG_NAME=medwings
|
||||||
PG_USER=medwings
|
PG_USER=medwings
|
||||||
PG_PASSWORD=secret
|
PG_PASSWORD=secret
|
||||||
@ -46,9 +48,11 @@ You should set the values of the following variables:
|
|||||||
|
|
||||||
| variable | description | value |
|
| variable | description | value |
|
||||||
|----------|-------------|-------|
|
|----------|-------------|-------|
|
||||||
| PG_PASSWORD | password for the PostgreSQL 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 |
|
||||||
| GOTIFY_USER | name of the Gotify admin user | a random string of 32 characters |
|
| DJANGO_SECRET_KEY | private session secret | a random string of 64 characters or more |
|
||||||
| GOTIFY_PASSWORD | password for the Gotify admin user | a random string of 32 characters |
|
| 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 |
|
| 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_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) |
|
| WITHINGS_CLIENT_SECRET | Your Withings API client secret | see [Withings API](./app/withings/README.md#api-access) |
|
||||||
|
@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from os import getenv
|
from os import getenv
|
||||||
|
from utils import parse_string_as_bool
|
||||||
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# 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
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# 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!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = parse_string_as_bool(getenv('DJANGO_DEBUG_MODE', 'false'))
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [ '*' ]
|
||||||
'localhost',
|
|
||||||
'127.0.0.1',
|
|
||||||
'192.168.2.141'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@ -80,8 +77,8 @@ DATABASES = {
|
|||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
'NAME': getenv('PG_NAME', 'medwings'),
|
'NAME': getenv('PG_NAME', 'medwings'),
|
||||||
'USER': getenv('PG_USER', 'medwings'),
|
'USER': getenv('PG_USER', 'medwings'),
|
||||||
'PASSWORD': getenv('PG_PASSWORD', 'medwings'),
|
'PASSWORD': getenv('PG_PASSWORD'),
|
||||||
'HOST': getenv('PG_HOST', 'medwings-postgres'),
|
'HOST': getenv('PG_HOST'),
|
||||||
'PORT': getenv('PG_PORT', '5432'),
|
'PORT': getenv('PG_PORT', '5432'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
app/core/utils.py
Normal file
27
app/core/utils.py
Normal 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}")
|
@ -1,9 +1,11 @@
|
|||||||
asgiref==3.7.2
|
asgiref==3.7.2
|
||||||
certifi==2023.7.22
|
certifi==2023.7.22
|
||||||
charset-normalizer==3.2.0
|
charset-normalizer==3.2.0
|
||||||
|
click==8.1.6
|
||||||
Django==4.2.3
|
Django==4.2.3
|
||||||
django-widget-tweaks==1.4.12
|
django-widget-tweaks==1.4.12
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
|
h11==0.14.0
|
||||||
idna==3.4
|
idna==3.4
|
||||||
psycopg==3.1.9
|
psycopg==3.1.9
|
||||||
psycopg-binary==3.1.9
|
psycopg-binary==3.1.9
|
||||||
@ -12,3 +14,4 @@ requests==2.31.0
|
|||||||
sqlparse==0.4.4
|
sqlparse==0.4.4
|
||||||
typing_extensions==4.7.1
|
typing_extensions==4.7.1
|
||||||
urllib3==2.0.4
|
urllib3==2.0.4
|
||||||
|
uvicorn==0.23.2
|
||||||
|
@ -26,7 +26,7 @@ services:
|
|||||||
- ${PG_HOST}
|
- ${PG_HOST}
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./development.django.Dockerfile
|
dockerfile: development.django.Dockerfile
|
||||||
args:
|
args:
|
||||||
CUSTOM_UID: 1000
|
CUSTOM_UID: 1000
|
||||||
CUSTOM_GID: 1000
|
CUSTOM_GID: 1000
|
||||||
|
24
production.caddy.Dockerfile
Normal file
24
production.caddy.Dockerfile
Normal 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"]
|
33
production.django.Dockerfile
Normal file
33
production.django.Dockerfile
Normal 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"]
|
108
production.docker-compose.yml
Normal file
108
production.docker-compose.yml
Normal 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
|
||||||
|
|
||||||
|
...
|
17
production.supervisord.conf
Normal file
17
production.supervisord.conf
Normal 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
|
Loading…
x
Reference in New Issue
Block a user