From 143a4a6e8c70557710d1b207a176f169d145ed1e Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Sat, 12 Nov 2022 22:10:06 -0700 Subject: Start some settings work --- Dockerfile | 26 --------- Makefile | 4 ++ core/config.py | 21 ++++++- core/context.py | 4 +- docker-compose.yml | 42 -------------- docker/Dockerfile | 29 ++++++++++ docker/docker-compose.yml | 42 ++++++++++++++ docker/start.sh | 7 +++ manage.py | 2 +- requirements.txt | 1 + scripts/start.sh | 7 --- setup.cfg | 2 +- takahe/asgi.py | 2 +- takahe/settings.py | 123 ----------------------------------------- takahe/settings/__init__.py | 0 takahe/settings/base.py | 107 +++++++++++++++++++++++++++++++++++ takahe/settings/development.py | 13 +++++ takahe/settings/production.py | 17 ++++++ takahe/settings/testing.py | 4 ++ takahe/wsgi.py | 2 +- users/views/identity.py | 4 +- 21 files changed, 251 insertions(+), 208 deletions(-) delete mode 100644 Dockerfile create mode 100644 Makefile delete mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.yml create mode 100644 docker/start.sh delete mode 100644 scripts/start.sh delete mode 100644 takahe/settings.py create mode 100644 takahe/settings/__init__.py create mode 100644 takahe/settings/base.py create mode 100644 takahe/settings/development.py create mode 100644 takahe/settings/production.py create mode 100644 takahe/settings/testing.py diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1f62240..0000000 --- a/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM python:3.9-bullseye as builder - -RUN mkdir -p /takahe -RUN python -m venv /takahe/.venv -RUN apt-get update && apt-get -y install libpq-dev python3-dev - -WORKDIR /takahe - -COPY requirements.txt requirements.txt - -RUN . /takahe/.venv/bin/activate \ - && pip install --upgrade pip \ - && pip install --upgrade -r requirements.txt - - -FROM python:3.9-slim-bullseye - -RUN apt-get update && apt-get install -y libpq5 - -COPY --from=builder /takahe /takahe -COPY . /takahe - -WORKDIR /takahe -EXPOSE 8000 - -CMD ["/takahe/scripts/start.sh"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b87b2ce --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: clean + +image: + docker build -t takahe -f docker/Dockerfile . diff --git a/core/config.py b/core/config.py index 0f09404..b9f6878 100644 --- a/core/config.py +++ b/core/config.py @@ -1,3 +1,20 @@ -class Config: +import pydantic - pass + +class Config(pydantic.BaseModel): + + # Basic configuration options + site_name: str = "takahē" + identity_max_age: int = 24 * 60 * 60 + + # Cached ORM object storage + __singleton__ = None + + class Config: + env_prefix = "takahe_" + + @classmethod + def load(cls) -> "Config": + if cls.__singleton__ is None: + cls.__singleton__ = cls() + return cls.__singleton__ diff --git a/core/context.py b/core/context.py index 026ac11..17617b9 100644 --- a/core/context.py +++ b/core/context.py @@ -1,7 +1,7 @@ -from django.conf import settings +from core.config import Config def config_context(request): return { - "config": {"site_name": settings.SITE_NAME}, + "config": Config.load(), } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f64bfb6..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,42 +0,0 @@ -version: "3" - -services: - db: - image: postgres:15-alpine - healthcheck: - test: ['CMD', 'pg_isready', '-U', 'postgres'] - volumes: - - dbdata:/var/lib/postgresql/data - networks: - - internal_network - restart: always - environment: - - "POSTGRES_DB=tahake" - - "POSTGRES_USER=postgres" - - "POSTGRES_PASSWORD=insecure_password" - - web: - build: . - image: tahake:latest - environment: - - "DJANGO_SETTINGS_MODULE=takahe.settings" - - "SECRET_KEY=insecure_secret" - - "POSTGRES_HOST=db" - - "POSTGRES_DB=tahake" - - "POSTGRES_USER=postgres" - - "POSTGRES_PASSWORD=insecure_password" - networks: - - external_network - - internal_network - restart: always - depends_on: - - db - ports: - - "8000:8000" - -networks: - internal_network: - external_network: - -volumes: - dbdata: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..14e033b --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,29 @@ +# Build stage + +FROM python:3.11.0-buster as builder + +RUN mkdir -p /takahe +RUN python -m venv /takahe/.venv +RUN apt-get update && apt-get -y install libpq-dev python3-dev + +WORKDIR /takahe + +COPY requirements.txt requirements.txt + +RUN . /takahe/.venv/bin/activate \ + && pip install --upgrade pip \ + && pip install --upgrade -r requirements.txt + +# Final image stage + +FROM python:3.11.0-slim-buster + +RUN apt-get update && apt-get install -y libpq5 + +COPY --from=builder /takahe /takahe +COPY . /takahe + +WORKDIR /takahe +EXPOSE 8000 + +CMD ["/takahe/docker/start.sh"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..00463bf --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,42 @@ +version: "3" + +services: + db: + image: postgres:15-alpine + healthcheck: + test: ['CMD', 'pg_isready', '-U', 'postgres'] + volumes: + - dbdata:/var/lib/postgresql/data + networks: + - internal_network + restart: always + environment: + - "POSTGRES_DB=tahake" + - "POSTGRES_USER=postgres" + - "POSTGRES_PASSWORD=insecure_password" + + web: + build: . + image: tahake:latest + environment: + - "DJANGO_SETTINGS_MODULE=takahe.settings.production" + - "SECRET_KEY=insecure_secret" + - "POSTGRES_HOST=db" + - "POSTGRES_DB=tahake" + - "POSTGRES_USER=postgres" + - "POSTGRES_PASSWORD=insecure_password" + networks: + - external_network + - internal_network + restart: always + depends_on: + - db + ports: + - "8000:8000" + +networks: + internal_network: + external_network: + +volumes: + dbdata: diff --git a/docker/start.sh b/docker/start.sh new file mode 100644 index 0000000..99f1ed0 --- /dev/null +++ b/docker/start.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. /takahe/.venv/bin/activate + +python manage.py migrate + +exec gunicorn takahe.asgi:application -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 diff --git a/manage.py b/manage.py index 21eb71e..3985c18 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "takahe.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "takahe.settings.production") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/requirements.txt b/requirements.txt index dbe6fb0..e897cfc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ psycopg2~=2.9.5 bleach~=5.0.1 pytest-django~=4.5.2 pytest-httpx~=0.21 +pydantic~=1.10.2 diff --git a/scripts/start.sh b/scripts/start.sh deleted file mode 100644 index 99f1ed0..0000000 --- a/scripts/start.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -. /takahe/.venv/bin/activate - -python manage.py migrate - -exec gunicorn takahe.asgi:application -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 diff --git a/setup.cfg b/setup.cfg index f70f6f1..a2e3e8f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,7 +9,7 @@ multi_line_output = 3 [tool:pytest] addopts = --tb=short -DJANGO_SETTINGS_MODULE = takahe.settings +DJANGO_SETTINGS_MODULE = takahe.settings.testing filterwarnings = ignore:There is no current event loop diff --git a/takahe/asgi.py b/takahe/asgi.py index 99a9cfb..3424b23 100644 --- a/takahe/asgi.py +++ b/takahe/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "takahe.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "takahe.settings.production") application = get_asgi_application() diff --git a/takahe/settings.py b/takahe/settings.py deleted file mode 100644 index e8982ae..0000000 --- a/takahe/settings.py +++ /dev/null @@ -1,123 +0,0 @@ -import os -from pathlib import Path - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get("SECRET_KEY", "insecure_secret") - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = ["*"] -CSRF_TRUSTED_ORIGINS = ["http://*", "https://*"] - -# Application definition - -INSTALLED_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "crispy_forms", - "core", - "activities", - "users", - "stator", -] - -MIDDLEWARE = [ - "core.middleware.AlwaysSecureMiddleware", - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", - "users.middleware.IdentityMiddleware", -] - -ROOT_URLCONF = "takahe.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [BASE_DIR / "templates"], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - "core.context.config_context", - ], - }, - }, -] - -WSGI_APPLICATION = "takahe.wsgi.application" - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql_psycopg2", - "HOST": os.environ.get("POSTGRES_HOST", "localhost"), - "NAME": os.environ.get("POSTGRES_DB", "takahe"), - "USER": os.environ.get("POSTGRES_USER", "postgres"), - "PASSWORD": os.environ.get("POSTGRES_PASSWORD"), - } -} - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", - }, -] - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_I18N = True - -USE_TZ = True - -STATIC_URL = "static/" - -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -AUTH_USER_MODEL = "users.User" - -LOGIN_URL = "/auth/login/" -LOGOUT_URL = "/auth/logout/" -LOGIN_REDIRECT_URL = "/" -LOGOUT_REDIRECT_URL = "/" - -STATICFILES_FINDERS = [ - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder", -] - -STATICFILES_DIRS = [ - BASE_DIR / "static", -] - -CRISPY_FAIL_SILENTLY = not DEBUG - -SITE_NAME = "takahē" -DEFAULT_DOMAIN = "feditest.aeracode.org" -ALLOWED_DOMAINS = ["feditest.aeracode.org"] -IDENTITY_MAX_AGE = 24 * 60 * 60 diff --git a/takahe/settings/__init__.py b/takahe/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/takahe/settings/base.py b/takahe/settings/base.py new file mode 100644 index 0000000..a2ccb98 --- /dev/null +++ b/takahe/settings/base.py @@ -0,0 +1,107 @@ +import os +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parent.parent.parent + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "crispy_forms", + "core", + "activities", + "users", + "stator", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "users.middleware.IdentityMiddleware", +] + +ROOT_URLCONF = "takahe.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "core.context.config_context", + ], + }, + }, +] + +WSGI_APPLICATION = "takahe.wsgi.application" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "HOST": os.environ.get("POSTGRES_HOST", "localhost"), + "NAME": os.environ.get("POSTGRES_DB", "takahe"), + "USER": os.environ.get("POSTGRES_USER", "postgres"), + "PASSWORD": os.environ.get("POSTGRES_PASSWORD"), + } +} + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + +STATIC_URL = "static/" + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +AUTH_USER_MODEL = "users.User" + +LOGIN_URL = "/auth/login/" +LOGOUT_URL = "/auth/logout/" +LOGIN_REDIRECT_URL = "/" +LOGOUT_REDIRECT_URL = "/" + +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] + +STATICFILES_DIRS = [ + BASE_DIR / "static", +] + +ALLOWED_HOSTS = ["*"] diff --git a/takahe/settings/development.py b/takahe/settings/development.py new file mode 100644 index 0000000..4e0098b --- /dev/null +++ b/takahe/settings/development.py @@ -0,0 +1,13 @@ +import os + +from .base import * # noqa + +# Load secret key from environment with a fallback +SECRET_KEY = os.environ.get("TAKAHE_SECRET_KEY", "insecure_secret") + +# Disable the CRSF origin protection +MIDDLEWARE.insert(0, "core.middleware.AlwaysSecureMiddleware") + +# Ensure debug features are on +DEBUG = True +CRISPY_FAIL_SILENTLY = False diff --git a/takahe/settings/production.py b/takahe/settings/production.py new file mode 100644 index 0000000..2f943f4 --- /dev/null +++ b/takahe/settings/production.py @@ -0,0 +1,17 @@ +import os + +from .base import * # noqa + +# Load secret key from environment +try: + SECRET_KEY = os.environ["TAKAHE_SECRET_KEY"] +except KeyError: + print("You must specify the TAKAHE_SECRET_KEY environment variable!") + os._exit(1) + +# Ensure debug features are off +DEBUG = False +CRISPY_FAIL_SILENTLY = True + +# TODO: Allow better setting of allowed_hosts, if we need to +ALLOWED_HOSTS = ["*"] diff --git a/takahe/settings/testing.py b/takahe/settings/testing.py new file mode 100644 index 0000000..6527333 --- /dev/null +++ b/takahe/settings/testing.py @@ -0,0 +1,4 @@ +from .base import * # noqa + +# Fixed secret key +SECRET_KEY = "testing_secret" diff --git a/takahe/wsgi.py b/takahe/wsgi.py index 05ae06f..c8ad0a0 100644 --- a/takahe/wsgi.py +++ b/takahe/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "takahe.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "takahe.settings.production") application = get_wsgi_application() diff --git a/users/views/identity.py b/users/views/identity.py index 5d11d63..d78bda1 100644 --- a/users/views/identity.py +++ b/users/views/identity.py @@ -1,13 +1,13 @@ import string from django import forms -from django.conf import settings from django.contrib.auth.decorators import login_required from django.http import Http404 from django.shortcuts import redirect from django.utils.decorators import method_decorator from django.views.generic import FormView, TemplateView, View +from core.config import Config from core.forms import FormHelper from users.decorators import identity_required from users.models import Domain, Follow, Identity, IdentityStates @@ -26,7 +26,7 @@ class ViewIdentity(TemplateView): fetch=True, ) posts = identity.posts.all()[:100] - if identity.data_age > settings.IDENTITY_MAX_AGE: + if identity.data_age > Config.load().IDENTITY_MAX_AGE: identity.transition_perform(IdentityStates.outdated) return { "identity": identity, -- cgit v1.2.3