feat: production settings from MDB_SETTINGS, DEBUG off, whitenoise

This commit is contained in:
ponas committed 2026-11-02 01:05:58 +01:00
1 parent be0e621b28
commit 3ccce3849e
6 files changed
+105 -8

No files matched your search

+12
View File
@@ -20,6 +20,18 @@ Run migrations and start the development server:
uv run python manage.py migrate uv run python manage.py migrate
uv run python manage.py runserver uv run python manage.py runserver
`mise.toml` sets `DJANGO_DEBUG=1`; without it `DEBUG` is off. Local overrides go in
`mdbsite/local_settings.py`.
## Production
MDB runs on sega under edb-infra's `mdb` role (`roles/mdb.md` there): gunicorn from a
release directory per commit, its settings in the file `MDB_SETTINGS` names
(`/etc/mdb/settings.py`), which `mdbsite/settings.py` executes last. A push to `main` that
passes the checks deploys itself while the repository variable `DEPLOY_SEGA` is `true`
(`.gitea/workflows/deploy.yaml`). The DNS and DHCP sync runs as root on sega:
`sudo mdb-sync`.
## Development tasks ## Development tasks
Dump test data: Dump test data:
+19 -7
View File
@@ -2,9 +2,10 @@ import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DEBUG = os.getenv("DJANGO_DEBUG", True) # Off unless DJANGO_DEBUG says otherwise; mise.toml turns it on for development.
DEBUG = os.getenv("DJANGO_DEBUG", "").lower() in ("1", "true", "yes")
ADMINS = (("System Administrator", "sysadmin@example.com"),) ADMINS = ("sysadmin@example.com",)
MANAGERS = ADMINS MANAGERS = ADMINS
@@ -35,10 +36,12 @@ STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.AppDirectoriesFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder",
) )
# Make this unique, and don't share it with anybody. # For development only: the repository is public. Production sets its own (MDB_SETTINGS).
SECRET_KEY = "@4ps=ebofcpv8sb#y7gm896=l&l*u0u-pgo7bcdj%+(k+2lqnl" SECRET_KEY = "@4ps=ebofcpv8sb#y7gm896=l&l*u0u-pgo7bcdj%+(k+2lqnl"
MIDDLEWARE = ( MIDDLEWARE = (
# Serves STATIC_ROOT, so production needs nothing in front but a proxy.
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
@@ -132,7 +135,16 @@ SENTRY_ENVIRONMENT = os.getenv("SENTRY_ENVIRONENT", "dev")
ADMIN_SITE_NAME = "MDB" ADMIN_SITE_NAME = "MDB"
try: # Production's settings live outside the code, in the file MDB_SETTINGS names; the mdb role
from .local_settings import * # in edb-infra writes it. An error in it stops MDB rather than leaving the defaults above.
except ImportError: if os.getenv("MDB_SETTINGS"):
pass with open(os.environ["MDB_SETTINGS"]) as f:
exec(compile(f.read(), os.environ["MDB_SETTINGS"], "exec"))
else:
# Development. Only a missing local_settings is skipped: a local_settings whose own
# imports fail must not leave MDB running on the defaults.
try:
from .local_settings import * # noqa: F403
except ModuleNotFoundError as e:
if e.name != "mdbsite.local_settings":
raise
+4
View File
@@ -1,3 +1,7 @@
[tools] [tools]
python = "3.14" python = "3.14"
uv = "latest" uv = "latest"
[env]
# settings.py leaves DEBUG off unless this says otherwise.
DJANGO_DEBUG = "1"
+2 -1
View File
@@ -15,10 +15,11 @@ dependencies = [
"ipaddress>=1.0.23,<2.0.0", "ipaddress>=1.0.23,<2.0.0",
"requests>=2.32.5", "requests>=2.32.5",
"sentry-sdk>=2.55.0", "sentry-sdk>=2.55.0",
"whitenoise>=6.12,<7",
] ]
[dependency-groups] [dependency-groups]
# Only needed on the server (LDAP is configured in local_settings.py). # Only needed on the server (LDAP is configured in production's settings).
# Kept separate so CI can skip building python-ldap from source. # Kept separate so CI can skip building python-ldap from source.
prod = [ prod = [
"django-auth-ldap>=5.3.0", "django-auth-ldap>=5.3.0",
+57
View File
@@ -0,0 +1,57 @@
"""How mdbsite.settings loads production's settings, in a fresh interpreter each time,
since the settings module is imported once per process."""
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
PRINT = (
"import django, os; os.environ['DJANGO_SETTINGS_MODULE'] = 'mdbsite.settings'; "
"django.setup(); from django.conf import settings as s; print(s.ALLOWED_HOSTS, s.DEBUG)"
)
def run(env):
return subprocess.run(
[sys.executable, "-c", PRINT],
cwd=ROOT,
env={
k: v
for k, v in os.environ.items()
if k not in ("MDB_SETTINGS", "DJANGO_DEBUG")
}
| env,
capture_output=True,
text=True,
)
def test_settings_file_overrides(tmp_path):
f = tmp_path / "settings.py"
f.write_text("ALLOWED_HOSTS = ['mdb.example.org']\n")
result = run({"MDB_SETTINGS": str(f)})
assert result.returncode == 0, result.stderr
assert result.stdout.strip() == "['mdb.example.org'] False"
def test_failing_settings_file_stops_mdb(tmp_path):
# As on sega after the 24.04 upgrade: python-ldap would not load, and the old
# `except ImportError: pass` left MDB running on the defaults.
f = tmp_path / "settings.py"
f.write_text("import a_module_that_does_not_exist\n")
result = run({"MDB_SETTINGS": str(f)})
assert result.returncode != 0
assert "a_module_that_does_not_exist" in result.stderr
def test_missing_settings_file_stops_mdb(tmp_path):
result = run({"MDB_SETTINGS": str(tmp_path / "missing.py")})
assert result.returncode != 0
def test_debug_is_off_unless_asked():
assert run({}).stdout.strip().endswith("False")
assert run({"DJANGO_DEBUG": "1"}).stdout.strip().endswith("True")
Generated
+11
View File
@@ -185,6 +185,7 @@ dependencies = [
{ name = "ipaddress" }, { name = "ipaddress" },
{ name = "requests" }, { name = "requests" },
{ name = "sentry-sdk" }, { name = "sentry-sdk" },
{ name = "whitenoise" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@@ -207,6 +208,7 @@ requires-dist = [
{ name = "ipaddress", specifier = ">=1.0.23,<2.0.0" }, { name = "ipaddress", specifier = ">=1.0.23,<2.0.0" },
{ name = "requests", specifier = ">=2.32.5" }, { name = "requests", specifier = ">=2.32.5" },
{ name = "sentry-sdk", specifier = ">=2.55.0" }, { name = "sentry-sdk", specifier = ">=2.55.0" },
{ name = "whitenoise", specifier = ">=6.12,<7" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
@@ -404,3 +406,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
] ]
[[package]]
name = "whitenoise"
version = "6.12.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cb/2a/55b3f3a4ec326cd077c1c3defeee656b9298372a69229134d930151acd01/whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad", size = 26841, upload-time = "2026-02-27T00:05:42.028Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/eb/d5583a11486211f3ebd4b385545ae787f32363d453c19fffd81106c9c138/whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2", size = 20302, upload-time = "2026-02-27T00:05:40.086Z" },
]