Merge pull request 'staging' (#2) from staging into master

Reviewed-on: #2
This commit is contained in:
Julian Lobbes 2023-08-17 20:18:14 +02:00
commit 53fc63e351
13 changed files with 283 additions and 18 deletions

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) |
@ -80,10 +84,10 @@ sudo docker exec -itu django medwings-django <command>
Run database migrations inside the running container like so:
```bash
sudo docker exec -itu medwings-django python manage.py migrate
sudo docker exec -itu django medwings-django python manage.py migrate
```
To enter django's interactive shell, run:
```bash
sudo docker exec -itu medwings-django python manage.py shell
sudo docker exec -itu django medwings-django python manage.py shell
```

View File

@ -8,14 +8,18 @@
<div class="flex flex-col justify-center items-center gap-2 py-4 mx-4 max-w-4xl">
<h2>Register</h2>
<p>
Something something glad you're signing up.
We're thrilled that you're taking the next step towards a healthier future by signing up for Medwings!
By linking your Withings account, we can seamlessly integrate your health data with Medwings.
</p>
<div class="flex flex-col gap-2 items-center call-to-action-box">
<p class="font-semibold">To get started, please allow us to access your health data</p>
<a class="btn text-lg" href="{{ auth_url }}">Link Withings Account</a>
</div>
<p>
Something something why this is necessary.
The registration process will continue after you've linked your Withings account.
This step is essential as it allows Medwings to securely access your health data from the Withings cloud.
We use OAuth2, a standard and secure method, to ensure that your personal data remains private and under your control.+
Once your accounts are linked, you'll be all set to start exploring your health data and insights through Medwings.
</p>
</div>
{% endblock content %}

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,17 @@ 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 = [ '*' ]
# Force HttpRequest.build_absolute_uri() to generate HTTPS links in production
# This is necessary when running behind a reverse proxy
#USE_X_FORWARDED_HOST = False if DEBUG else True
#SECURE_PROXY_SSL_HEADER = () if DEBUG else ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Application definition
@ -80,8 +84,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'),
}
}
@ -122,6 +126,7 @@ STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
STATIC_ROOT = '/srv/static'
# Default primary key field type

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

10
development.Caddyfile Normal file
View File

@ -0,0 +1,10 @@
:8000 {
handle * {
reverse_proxy * medwings-django:8000
}
log {
output stderr
format console
}
}

View File

@ -16,7 +16,7 @@ RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
# Copy caddy config
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} Caddyfile /app/
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} development.Caddyfile /app/Caddyfile
# Run Caddy in development mode
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}

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

21
production.Caddyfile Normal file
View File

@ -0,0 +1,21 @@
{
servers {
trusted_proxies static private_ranges
}
}
:8000 {
handle_path /static/* {
root * /srv/static
file_server
}
handle * {
reverse_proxy * medwings-django:8000
}
log {
output stderr
format console
}
}

View File

@ -0,0 +1,25 @@
# 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 && \
mkdir -p /srv/static && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /srv/static && chmod 700 /srv/static
# Copy caddy config
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} production.Caddyfile /app/Caddyfile
# 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,35 @@
# 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 && \
mkdir -p /srv/static && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /srv/static && chmod 700 /srv/static
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"]
#CMD ["uvicorn", "core.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--access-log"]

View File

@ -0,0 +1,114 @@
---
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}
volumes:
- /srv/medwings/static:/srv/static:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.medwings.entrypoints=https"
- "traefik.http.routers.medwings.rule=Host(`medwings.lobbes.dev`)"
- "traefik.http.routers.medwings.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}
DJANGO_DEBUG_MODE: ${DJANGO_DEBUG_MODE}
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
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}
volumes:
- /srv/medwings/static:/srv/static
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.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