Files
RosterChief/rosterchief/urls.py
Bernard Siebens c0a44093d9 Add a health check and the deployment runbook
/healthz checks the database and does a cache ROUND TRIP, not a ping. Both matter:
a node that cannot reach Postgres serves nothing, and a cache that accepts writes
and returns nothing would have waffle read every feature flag as unset -- so
"healthy" has to mean more than "the process is listening", or the load balancer
will keep feeding traffic to a node that only looks alive.

No auth and no tenant on it: the proxy, and later a load balancer, must reach it on
any host.

DEPLOYMENT.md is the runbook, and leads with the five things that make this app not
a generic Django deploy: the wildcard cert forces DNS-01 (Let's Encrypt will not
issue a wildcard over HTTP-01); Redis is required on one server, not two, because
of the per-process flag cache; SECURE_PROXY_SSL_HEADER plus Caddy's
X-Forwarded-Proto or WebAuthn and the SSL redirect both break; uploads must reach
object storage BEFORE the second app server, not during; and invoices need native
pango.

Also documents why the archive job ships with --commit off, why migrations are run
explicitly rather than from the entrypoint, and how to test the restore before the
day you need it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 09:45:21 +02:00

36 lines
1.5 KiB
Python

"""URL configuration for rosterchief.
``/admin/login/`` is deliberately intercepted *before* ``admin.site.urls`` and
redirected to the allauth login, so Django staff go through the same MFA
challenge as everyone else — Django's own admin login form knows nothing about
second factors. ``RequireMFAMiddleware`` then blocks any staff user who has not
enrolled.
"""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from django.views.generic import RedirectView
from club.views import root
from .health import healthz
urlpatterns = [
# No auth and no tenant: the proxy and the load balancer must reach it on any host.
path("healthz", healthz, name="healthz"),
path("admin/login/", RedirectView.as_view(pattern_name="account_login", query_string=True), name="admin_login_redirect"),
path("admin/", admin.site.urls),
path("accounts/", include("allauth.urls")),
path("controlpanel/", include("controlpanel.urls")),
# "/" resolves per tenant: a club subdomain lands on the club, the base domain
# hands off to the control panel. This is why LOGIN_REDIRECT_URL can stay "/".
path("", root, name="root"),
]
if settings.DEBUG:
urlpatterns += [path("__reload__/", include("django_browser_reload.urls"))]
# Club logos are uploads: runserver has to serve MEDIA_ROOT itself.
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)