diff --git a/.gitignore b/.gitignore index d03fd0a..eae1d26 100644 --- a/.gitignore +++ b/.gitignore @@ -240,7 +240,8 @@ celerybeat.pid *.sage.py # Environments -.env +.envrc +*.env .venv env/ venv/ diff --git a/docker-compose.yml b/docker-compose.yml index d1d6ac0..5e31592 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: args: POETRY_OPTIONS: "--no-dev" env_file: - - .env + - envs/local.env volumes: - ./db:/db - staticfiles:/app/staticfiles diff --git a/envs/local.env.dist b/envs/local.env.dist new file mode 100644 index 0000000..6154daa --- /dev/null +++ b/envs/local.env.dist @@ -0,0 +1,11 @@ +PYTHONUNBUFFERED=1 +DJANGO_SETTINGS_MODULE=blog.settings +ADMINS="Gabriel Augendre|gabriel@augendre.info" +MAILGUN_API_KEY=API_KEY_HERE +MAILGUN_SENDER_DOMAIN=mg.gabnotes.org +BLOG_BASE_URL=http://localhost:8000/ +SHORTPIXEL_API_KEY=API_KEY_HERE +HOSTS=192.168.0.1,192.168.0.2 +PLAUSIBLE_DOMAIN=localhost:8000 +GOATCOUNTER_DOMAIN=gabnotes-dev.goatcounter.augendre.info +SERVICES_STATUS_URL=https://stats.uptimerobot.com/5wKo4t0XqO diff --git a/poetry.lock b/poetry.lock index db3c93b..951630e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -200,6 +200,19 @@ python-versions = ">=3.6" Django = ">=2.2" sqlparse = ">=0.2.0" +[[package]] +name = "django-environ" +version = "0.8.1" +description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application." +category = "main" +optional = false +python-versions = ">=3.4,<4" + +[package.extras] +develop = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)", "furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] +docs = ["furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] +testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)"] + [[package]] name = "django-formtools" version = "2.3" @@ -836,7 +849,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "de3fbd11622b6f872fc18cd21b78878fe8e0b3e8faa54527400fe98f0ecb6e34" +content-hash = "177905663c5207bbaed9cc8a093d99b96e932297e9901d8f7b7985583583fe14" [metadata.files] asgiref = [ @@ -986,6 +999,10 @@ django-debug-toolbar = [ {file = "django-debug-toolbar-3.2.4.tar.gz", hash = "sha256:644bbd5c428d3283aa9115722471769cac1bec189edf3a0c855fd8ff870375a9"}, {file = "django_debug_toolbar-3.2.4-py3-none-any.whl", hash = "sha256:6b633b6cfee24f232d73569870f19aa86c819d750e7f3e833f2344a9eb4b4409"}, ] +django-environ = [ + {file = "django-environ-0.8.1.tar.gz", hash = "sha256:6f0bc902b43891656b20486938cba0861dc62892784a44919170719572a534cb"}, + {file = "django_environ-0.8.1-py2.py3-none-any.whl", hash = "sha256:42593bee519a527602a467c7b682aee1a051c2597f98c45f4f4f44169ecdb6e5"}, +] django-formtools = [ {file = "django-formtools-2.3.tar.gz", hash = "sha256:9663b6eca64777b68d6d4142efad8597fe9a685924673b25aa8a1dcff4db00c3"}, {file = "django_formtools-2.3-py3-none-any.whl", hash = "sha256:4699937e19ee041d803943714fe0c1c7ad4cab802600eb64bbf4cdd0a1bfe7d9"}, diff --git a/pyproject.toml b/pyproject.toml index 3925a47..557459d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ rcssmin = "^1.0.6" django-csp = "^3.7" django-two-factor-auth = {extras = ["phonenumberslite"], git = "https://github.com/Bouke/django-two-factor-auth.git", rev = "ffe4422e"} beautifulsoup4 = "^4.10.0" +django-environ = "^0.8.1" [tool.poetry.dev-dependencies] pre-commit = "^2.7" diff --git a/src/blog/settings.py b/src/blog/settings.py index 6498c2c..f48e19b 100644 --- a/src/blog/settings.py +++ b/src/blog/settings.py @@ -12,21 +12,43 @@ https://docs.djangoproject.com/en/3.1/ref/settings/ import os from pathlib import Path +import environ + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve(strict=True).parent.parent +env = environ.Env( + DEBUG=(bool, False), + SECRET_KEY=(str, "s#!83!8e$3s89m)r$1ghsgxbndf8=#^qt(_*o%xbq0j2t8#db5"), + ADMINS=(list, []), + MAILGUN_API_KEY=(str, ""), + MAILGUN_SENDER_DOMAIN=(str, ""), + HOSTS=(list, []), + MEMCACHED_LOCATION=(str, ""), + DB_BASE_DIR=(Path, BASE_DIR), + BLOG_BASE_URL=(str, "https://gabnotes.org/"), + SERVICES_STATUS_URL=(str, None), + SHORTPIXEL_API_KEY=(str, None), + SHORTPIXEL_RESIZE_WIDTH=(int, 750), + SHORTPIXEL_RESIZE_HEIGHT=(int, 10000), + GOATCOUNTER_DOMAIN=(str, None), +) + +env_file = os.getenv("ENV_FILE", None) + +if env_file: + environ.Env.read_env(env_file) + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv( - "SECRET_KEY", "s#!83!8e$3s89m)r$1ghsgxbndf8=#^qt(_*o%xbq0j2t8#db5" -) +SECRET_KEY = env("SECRET_KEY") -admins = os.getenv("ADMINS", "") +admins = env("ADMINS") if admins: - ADMINS = list(map(lambda x: tuple(x.split(",")), admins.split(";"))) + ADMINS = list(map(lambda x: tuple(x.split("|")), admins)) DEFAULT_FROM_EMAIL = "Gab's Notes " SERVER_EMAIL = "Gab's Notes " @@ -34,22 +56,20 @@ EMAIL_SUBJECT_PREFIX = "[Blog] " EMAIL_TIMEOUT = 30 ANYMAIL = { - "MAILGUN_API_KEY": os.getenv("MAILGUN_API_KEY", ""), - "MAILGUN_SENDER_DOMAIN": os.getenv("MAILGUN_SENDER_DOMAIN", ""), + "MAILGUN_API_KEY": env("MAILGUN_API_KEY"), + "MAILGUN_SENDER_DOMAIN": env("MAILGUN_SENDER_DOMAIN"), "MAILGUN_API_URL": "https://api.eu.mailgun.net/v3", } EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.getenv("DEBUG", "true").lower() == "true" +DEBUG = env("DEBUG") ALLOWED_HOSTS = ["localhost"] # Required for healthcheck if DEBUG: - ALLOWED_HOSTS.extend(["127.0.0.1"]) + ALLOWED_HOSTS.append("127.0.0.1") -HOSTS = os.getenv("HOSTS") -if HOSTS: - ALLOWED_HOSTS.extend(HOSTS.split(";")) +ALLOWED_HOSTS.extend(env("HOSTS")) SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SESSION_COOKIE_SECURE = not DEBUG @@ -118,7 +138,7 @@ TEMPLATES = [ WSGI_APPLICATION = "blog.wsgi.application" -MEMCACHED_LOCATION = os.getenv("MEMCACHED_LOCATION") +MEMCACHED_LOCATION = env("MEMCACHED_LOCATION") if MEMCACHED_LOCATION: CACHES = { "default": { @@ -137,12 +157,12 @@ else: # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases -DB_BASE_DIR = os.getenv("DB_BASE_DIR", BASE_DIR) +DB_BASE_DIR = env("DB_BASE_DIR") if not DB_BASE_DIR: # Protect against empty strings DB_BASE_DIR = BASE_DIR else: - DB_BASE_DIR = Path(DB_BASE_DIR).resolve(strict=True) + DB_BASE_DIR = DB_BASE_DIR.resolve(strict=True) DATABASES = { "default": { @@ -197,20 +217,20 @@ BLOG = { "title": "Gab's Notes", "author": "Gabriel Augendre", "description": "My take on tech-related subjects (but not only).", - "base_url": os.getenv("BLOG_BASE_URL", "https://gabnotes.org/"), + "base_url": env("BLOG_BASE_URL"), "repo": { "commit_url": "https://git.augendre.info/gaugendre/blog/commit/{commit_sha}", "homepage": "https://git.augendre.info/gaugendre/blog", "log": "https://git.augendre.info/gaugendre/blog/commits/branch/master", }, - "status_url": os.getenv("SERVICES_STATUS_URL"), + "status_url": env("SERVICES_STATUS_URL"), } -SHORTPIXEL_API_KEY = os.getenv("SHORTPIXEL_API_KEY") -SHORTPIXEL_RESIZE_WIDTH = int(os.getenv("SHORTPIXEL_RESIZE_WIDTH", 750)) -SHORTPIXEL_RESIZE_HEIGHT = int(os.getenv("SHORTPIXEL_RESIZE_HEIGHT", 10000)) +SHORTPIXEL_API_KEY = env("SHORTPIXEL_API_KEY") +SHORTPIXEL_RESIZE_WIDTH = env("SHORTPIXEL_RESIZE_WIDTH") +SHORTPIXEL_RESIZE_HEIGHT = env("SHORTPIXEL_RESIZE_HEIGHT") -GOATCOUNTER_DOMAIN = os.getenv("GOATCOUNTER_DOMAIN") +GOATCOUNTER_DOMAIN = env("GOATCOUNTER_DOMAIN") LOGIN_URL = "two_factor:login" LOGIN_REDIRECT_URL = "two_factor:profile"