Bit of a settings fixup to make it work with t.s

This commit is contained in:
Andrew Godwin 2022-11-26 11:33:33 -07:00
parent 849c221aee
commit a3f45a001b
5 changed files with 53 additions and 34 deletions

View file

@ -4,3 +4,4 @@ notes.md
.git .git
.venv .venv
.pre-commit-config.yaml .pre-commit-config.yaml
.env

View file

@ -1,8 +1,8 @@
TAKAHE_DATABASE_URL="postgres://postgres:insecure_password@db/takahe" TAKAHE_DATABASE_SERVER="sqlite://takahe.db"
TAKAHE_DEBUG=true TAKAHE_DEBUG=true
TAKAHE_SECRET_KEY="insecure_secret" TAKAHE_SECRET_KEY="insecure_secret"
TAKAHE_CSRF_TRUSTED_ORIGINS=["http://127.0.0.1:8000", "https://127.0.0.1:8000"] TAKAHE_CSRF_TRUSTED_ORIGINS=["http://127.0.0.1:8000", "https://127.0.0.1:8000"]
TAKAHE_USE_PROXY_HEADERS=true TAKAHE_USE_PROXY_HEADERS=true
TAKAHE_EMAIL_BACKEND="console://console" TAKAHE_EMAIL_SERVER="console://console"
TAKAHE_MAIN_DOMAIN="example.com" TAKAHE_MAIN_DOMAIN="example.com"
TAKAHE_ENVIRONMENT="development" TAKAHE_ENVIRONMENT="development"

View file

@ -11,7 +11,7 @@ COPY . /takahe
WORKDIR /takahe WORKDIR /takahe
RUN TAKAHE_DATABASE_URL="postgres://dummy:dummy@localhost/postgres" python3 manage.py collectstatic RUN TAKAHE_DATABASE_SERVER="postgres://x@example.com/x" python3 manage.py collectstatic
EXPOSE 8000 EXPOSE 8000

View file

@ -61,36 +61,33 @@ Environment Variables
All of these variables are *required* for a working installation, and should All of these variables are *required* for a working installation, and should
be provided from the first boot. be provided from the first boot.
* ``PGHOST``, ``PGPORT``, ``PGUSER``, ``PGDATABASE``, and ``PGPASSWORD`` are the * ``TAKAHE_DATABASE_SERVER`` should be a database DSN for your database (you can use
standard PostgreSQL environment variables for configuring your database. the standard ``PG*`` variables too if you want)
* ``TAKAHE_SECRET_KEY`` must be a fixed, random value (it's used for internal * ``TAKAHE_SECRET_KEY`` must be a fixed, random value (it's used for internal
cryptography). Don't change this unless you want to invalidate all sessions. cryptography). Don't change this unless you want to invalidate all sessions.
* ``TAKAHE_MEDIA_BACKEND`` must be one of ``local``, ``s3`` or ``gcs``. * ``TAKAHE_MEDIA_BACKEND`` must be a URI starting with ``local://``, ``s3://`` or ``gcs://``.
* If it is set to ``local``, you must also provide ``TAKAHE_MEDIA_ROOT``, * If it is set to ``local://``, you must also provide ``TAKAHE_MEDIA_ROOT``,
the path to the local media directory, and ``TAKAHE_MEDIA_URL``, a the path to the local media directory, and ``TAKAHE_MEDIA_URL``, a
fully-qualified URL prefix that serves that directory. fully-qualified URL prefix that serves that directory.
* If it is set to ``gcs``, you must also provide ``TAKAHE_MEDIA_BUCKET``, * If it is set to ``gcs://``, it must be in the form ``gcs://bucket-name``
the name of the bucket to store files in. The bucket must be publicly (note the two slashes if you just want a bucket name)
readable and have "uniform access control" enabled.
* If it is set to ``s3``, you must also provide ``TAKAHE_MEDIA_BUCKET``, * If it is set to ``s3://``, it must be in the form ``s3://access-key:secret-key@endpoint-url/bucket-name``
the name of the bucket to store files in.
* ``TAKAHE_MAIN_DOMAIN`` should be the domain name (without ``https://``) that * ``TAKAHE_MAIN_DOMAIN`` should be the domain name (without ``https://``) that
will be used for default links (such as in emails). It does *not* need to be will be used for default links (such as in emails). It does *not* need to be
the same as any domain you are hosting user accounts on. the same as any domain you are hosting user accounts on.
* ``TAKAHE_EMAIL_HOST`` and ``TAKAHE_EMAIL_PORT`` (along with * ``TAKAHE_EMAIL_SERVER`` should be set to an ``smtp://`` or ``sendgrid://`` URI
``TAKAHE_EMAIL_USER`` and ``TAKAHE_EMAIL_PASSWORD``, if needed) should point
to an SMTP server Takahe can use for sending email. Email is *required*, to
allow account creation and password resets.
* If you are using SendGrid, you can just set an API key in * If you are using SMTP, it is ``smtp://username:password@host:port/``. You
``TAKAHE_EMAIL_SENDGRID_KEY`` instead. can also put ``?tls=true`` or ``?ssl=true`` on the end to enable encryption.
* If you are using SendGrid, you should set the URI to ``sendgrid://api-key``
* ``TAKAHE_EMAIL_FROM`` is the email address that emails from the system will * ``TAKAHE_EMAIL_FROM`` is the email address that emails from the system will
appear to come from. appear to come from.
@ -99,12 +96,13 @@ be provided from the first boot.
be automatically promoted to administrator when it signs up. You only need be automatically promoted to administrator when it signs up. You only need
this for initial setup, and can unset it after that if you like. this for initial setup, and can unset it after that if you like.
* ``TAKAHE_STATOR_TOKEN`` should be a random string that you are using to * If you don't want to run Stator as a background process but as a view,
protect the stator (task runner) endpoint. You'll use this value later. set ``TAKAHE_STATOR_TOKEN`` to a random string that you are using to
protect it; you'll use this when setting up the URL to be called.
* If your installation is behind a HTTPS endpoint that is proxying it, set * If your installation is behind a HTTPS endpoint that is proxying it, set
``TAKAHE_SECURE_HEADER`` to the header name used to signify that HTTPS is ``TAKAHE_USE_PROXY_HEADERS`` to ``true``. (The HTTPS proxy header must be called
being used (usually ``X-Forwarded-Proto``) ``X-Forwarded-Proto``).
* If you want to receive emails about internal site errors, set * If you want to receive emails about internal site errors, set
``TAKAHE_ERROR_EMAILS`` to a comma-separated list of email addresses that ``TAKAHE_ERROR_EMAILS`` to a comma-separated list of email addresses that

View file

@ -31,25 +31,32 @@ class Settings(BaseSettings):
""" """
#: The default database. #: The default database.
DATABASE_URL: Optional[PostgresDsn] DATABASE_SERVER: Optional[PostgresDsn]
#: The currently running environment, used for things such as sentry #: The currently running environment, used for things such as sentry
#: error reporting. #: error reporting.
ENVIRONMENT: Environments = "development" ENVIRONMENT: Environments = "development"
#: Should django run in debug mode? #: Should django run in debug mode?
DEBUG: bool = False DEBUG: bool = False
#: Set a secret key used for signing values such as sessions. Randomized #: Set a secret key used for signing values such as sessions. Randomized
#: by default, so you'll logout everytime the process restarts. #: by default, so you'll logout everytime the process restarts.
SECRET_KEY: str = Field(default_factory=lambda: secrets.token_hex(128)) SECRET_KEY: str = Field(default_factory=lambda: secrets.token_hex(128))
#: Set a secret key used to protect the stator. Randomized by default. #: Set a secret key used to protect the stator. Randomized by default.
STATOR_TOKEN: str = Field(default_factory=lambda: secrets.token_hex(128)) STATOR_TOKEN: str = Field(default_factory=lambda: secrets.token_hex(128))
#: If set, a list of allowed values for the HOST header. The default value #: If set, a list of allowed values for the HOST header. The default value
#: of '*' means any host will be accepted. #: of '*' means any host will be accepted.
ALLOWED_HOSTS: List[str] = Field(default_factory=lambda: ["*"]) ALLOWED_HOSTS: List[str] = Field(default_factory=lambda: ["*"])
#: If set, a list of hosts to accept for CORS. #: If set, a list of hosts to accept for CORS.
CORS_HOSTS: List[str] = Field(default_factory=list) CORS_HOSTS: List[str] = Field(default_factory=list)
#: If set, a list of hosts to accept for CSRF. #: If set, a list of hosts to accept for CSRF.
CSRF_HOSTS: List[str] = Field(default_factory=list) CSRF_HOSTS: List[str] = Field(default_factory=list)
#: If enabled, trust the HTTP_X_FORWARDED_FOR header. #: If enabled, trust the HTTP_X_FORWARDED_FOR header.
USE_PROXY_HEADERS: bool = False USE_PROXY_HEADERS: bool = False
@ -59,25 +66,25 @@ class Settings(BaseSettings):
#: Fallback domain for links. #: Fallback domain for links.
MAIN_DOMAIN: str = "example.com" MAIN_DOMAIN: str = "example.com"
EMAIL_DSN: AnyUrl = "console://localhost" EMAIL_SERVER: AnyUrl = "console://localhost"
EMAIL_FROM: EmailStr = "test@example.com" EMAIL_FROM: EmailStr = "test@example.com"
AUTO_ADMIN_EMAIL: Optional[EmailStr] = None AUTO_ADMIN_EMAIL: Optional[EmailStr] = None
ERROR_EMAILS: Optional[List[EmailStr]] = None ERROR_EMAILS: Optional[List[EmailStr]] = None
MEDIA_URL: str = "/media/" MEDIA_URL: str = "/media/"
MEDIA_ROOT: str = str(BASE_DIR / "MEDIA") MEDIA_ROOT: str = str(BASE_DIR / "media")
MEDIA_BACKEND: Optional[AnyUrl] = None MEDIA_BACKEND: Optional[AnyUrl] = None
PGHOST: Optional[str] = None PGHOST: Optional[str] = None
PGPORT: int = 5432 PGPORT: Optional[int] = 5432
PGNAME: str = "takahe" PGNAME: str = "takahe"
PGUSER: str = "postgres" PGUSER: str = "postgres"
PGPASSWORD: Optional[str] = None PGPASSWORD: Optional[str] = None
@validator("PGHOST", always=True) @validator("PGHOST", always=True)
def validate_db(cls, PGHOST, values): # noqa def validate_db(cls, PGHOST, values): # noqa
if not values.get("DATABASE_URL") and not PGHOST: if not values.get("DATABASE_SERVER") and not PGHOST:
raise ValueError("Either DATABASE_URL or PGHOST are required.") raise ValueError("Either DATABASE_SERVER or PGHOST are required.")
return PGHOST return PGHOST
class Config: class Config:
@ -154,8 +161,10 @@ TEMPLATES = [
WSGI_APPLICATION = "takahe.wsgi.application" WSGI_APPLICATION = "takahe.wsgi.application"
if SETUP.DATABASE_URL: if SETUP.DATABASE_SERVER:
DATABASES = {"default": dj_database_url.parse(SETUP.DATABASE_URL, conn_max_age=600)} DATABASES = {
"default": dj_database_url.parse(SETUP.DATABASE_SERVER, conn_max_age=600)
}
else: else:
DATABASES = { DATABASES = {
"default": { "default": {
@ -243,11 +252,17 @@ if SETUP.SENTRY_DSN:
) )
SERVER_EMAIL = SETUP.EMAIL_FROM SERVER_EMAIL = SETUP.EMAIL_FROM
if SETUP.EMAIL_DSN: if SETUP.EMAIL_SERVER:
parsed = urllib.parse.urlparse(SETUP.EMAIL_DSN) parsed = urllib.parse.urlparse(SETUP.EMAIL_SERVER)
query = urllib.parse.parse_qs(parsed.query) query = urllib.parse.parse_qs(parsed.query)
if parsed.scheme == "console": if parsed.scheme == "console":
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
elif parsed.scheme == "sendgrid":
EMAIL_HOST = "smtp.sendgrid.net"
EMAIL_PORT = 587
EMAIL_HOST_USER = "apikey"
EMAIL_HOST_PASSWORD = parsed.hostname
EMAIL_USE_TLS = True
elif parsed.scheme == "smtp": elif parsed.scheme == "smtp":
EMAIL_HOST = parsed.hostname EMAIL_HOST = parsed.hostname
EMAIL_PORT = parsed.port EMAIL_PORT = parsed.port
@ -256,7 +271,7 @@ if SETUP.EMAIL_DSN:
EMAIL_USE_TLS = as_bool(query.get("tls")) EMAIL_USE_TLS = as_bool(query.get("tls"))
EMAIL_USE_SSL = as_bool(query.get("ssl")) EMAIL_USE_SSL = as_bool(query.get("ssl"))
else: else:
raise ValueError("Unknown schema for EMAIL_DSN.") raise ValueError("Unknown schema for EMAIL_SERVER.")
if SETUP.MEDIA_BACKEND: if SETUP.MEDIA_BACKEND:
@ -264,7 +279,10 @@ if SETUP.MEDIA_BACKEND:
query = urllib.parse.parse_qs(parsed.query) query = urllib.parse.parse_qs(parsed.query)
if parsed.scheme == "gcs": if parsed.scheme == "gcs":
DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage" DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
if parsed.path.lstrip("/"):
GS_BUCKET_NAME = parsed.path.lstrip("/") GS_BUCKET_NAME = parsed.path.lstrip("/")
else:
GS_BUCKET_NAME = parsed.hostname
GS_QUERYSTRING_AUTH = False GS_QUERYSTRING_AUTH = False
elif parsed.scheme == "s3": elif parsed.scheme == "s3":
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
@ -273,6 +291,8 @@ if SETUP.MEDIA_BACKEND:
AWS_SECRET_ACCESS_KEY = parsed.password AWS_SECRET_ACCESS_KEY = parsed.password
port = parsed.port or 443 port = parsed.port or 443
AWS_S3_ENDPOINT_URL = f"{parsed.hostname}:{port}" AWS_S3_ENDPOINT_URL = f"{parsed.hostname}:{port}"
else:
raise ValueError(f"Unsupported media backend {parsed.scheme}")
if SETUP.ERROR_EMAILS: if SETUP.ERROR_EMAILS:
ADMINS = [("Admin", e) for e in SETUP.ERROR_EMAILS] ADMINS = [("Admin", e) for e in SETUP.ERROR_EMAILS]