funkwhale/api/config/settings/common.py

1336 wiersze
45 KiB
Python
Czysty Zwykły widok Historia

# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
2020-08-24 12:11:00 +00:00
from collections import OrderedDict
import logging.config
import sys
from urllib.parse import urlsplit
2018-06-10 09:29:24 +00:00
import environ
from celery.schedules import crontab
logger = logging.getLogger("funkwhale_api.config")
ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /)
2018-06-09 13:36:16 +00:00
APPS_DIR = ROOT_DIR.path("funkwhale_api")
env = environ.Env()
2020-08-25 07:55:57 +00:00
ENV = env
LOGLEVEL = env("LOGLEVEL", default="info").upper()
2020-04-20 11:48:19 +00:00
"""
Default logging level for the Funkwhale processes""" # pylint: disable=W0105
LOGGING_CONFIG = None
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"console": {"format": "%(asctime)s %(name)-12s %(levelname)-8s %(message)s"}
},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "console"},
},
"loggers": {
"funkwhale_api": {
2019-06-24 13:57:01 +00:00
"level": LOGLEVEL,
"handlers": ["console"],
# required to avoid double logging with root logger
"propagate": False,
},
2020-07-03 08:59:12 +00:00
"plugins": {
"level": LOGLEVEL,
"handlers": ["console"],
# required to avoid double logging with root logger
"propagate": False,
},
"": {"level": "WARNING", "handlers": ["console"]},
},
}
)
2020-04-20 11:48:19 +00:00
ENV_FILE = env_file = env("ENV_FILE", default=None)
"""
Path to a .env file to load
"""
if env_file:
logger.info("Loading specified env file at %s", env_file)
# we have an explicitely specified env file
# so we try to load and it fail loudly if it does not exist
env.read_env(env_file)
else:
# we try to load from .env and config/.env
# but do not crash if those files don't exist
paths = [
# /srv/funwhale/api/.env
ROOT_DIR,
# /srv/funwhale/config/.env
((ROOT_DIR - 1) + "config"),
]
for path in paths:
try:
env_path = path.file(".env")
except FileNotFoundError:
logger.debug("No env file found at %s/.env", path)
continue
env.read_env(env_path)
logger.info("Loaded env file at %s/.env", path)
break
FUNKWHALE_PLUGINS_PATH = env(
"FUNKWHALE_PLUGINS_PATH", default="/srv/funkwhale/plugins/"
)
2020-04-20 11:48:19 +00:00
"""
Path to a directory containing Funkwhale plugins.
These will be imported at runtime.
2020-04-20 11:48:19 +00:00
"""
sys.path.append(FUNKWHALE_PLUGINS_PATH)
2020-08-24 12:11:00 +00:00
CORE_PLUGINS = [
"funkwhale_api.contrib.scrobbler",
"funkwhale_api.contrib.listenbrainz",
2020-08-24 12:11:00 +00:00
]
2020-08-24 12:11:00 +00:00
LOAD_CORE_PLUGINS = env.bool("FUNKWHALE_LOAD_CORE_PLUGINS", default=True)
2020-07-03 08:59:12 +00:00
PLUGINS = [p for p in env.list("FUNKWHALE_PLUGINS", default=[]) if p]
"""
List of Funkwhale plugins to load.
"""
2020-08-24 12:11:00 +00:00
if LOAD_CORE_PLUGINS:
PLUGINS = CORE_PLUGINS + PLUGINS
# Remove duplicates
PLUGINS = list(OrderedDict.fromkeys(PLUGINS))
2020-07-03 08:59:12 +00:00
if PLUGINS:
logger.info("Running with the following plugins enabled: %s", ", ".join(PLUGINS))
else:
logger.info("Running with no plugins")
from .. import plugins # noqa
plugins.startup.autodiscover([p + ".funkwhale_startup" for p in PLUGINS])
DEPENDENCIES = plugins.trigger_filter(plugins.PLUGINS_DEPENDENCIES, [], enabled=True)
plugins.install_dependencies(DEPENDENCIES)
FUNKWHALE_HOSTNAME = None
2018-06-09 13:36:16 +00:00
FUNKWHALE_HOSTNAME_SUFFIX = env("FUNKWHALE_HOSTNAME_SUFFIX", default=None)
FUNKWHALE_HOSTNAME_PREFIX = env("FUNKWHALE_HOSTNAME_PREFIX", default=None)
if FUNKWHALE_HOSTNAME_PREFIX and FUNKWHALE_HOSTNAME_SUFFIX:
# We're in traefik case, in development
2018-06-09 13:36:16 +00:00
FUNKWHALE_HOSTNAME = "{}.{}".format(
FUNKWHALE_HOSTNAME_PREFIX, FUNKWHALE_HOSTNAME_SUFFIX
)
FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
else:
try:
2018-06-09 13:36:16 +00:00
FUNKWHALE_HOSTNAME = env("FUNKWHALE_HOSTNAME")
2020-04-20 11:48:19 +00:00
"""
Hostname of your Funkwhale pod, e.g. ``mypod.audio``
2020-04-20 11:48:19 +00:00
"""
2018-06-09 13:36:16 +00:00
FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
2020-04-20 11:48:19 +00:00
"""
Protocol end users will use to access your pod, either
``http`` or ``https``.
2020-04-20 11:48:19 +00:00
"""
except Exception:
2018-06-09 13:36:16 +00:00
FUNKWHALE_URL = env("FUNKWHALE_URL")
_parsed = urlsplit(FUNKWHALE_URL)
FUNKWHALE_HOSTNAME = _parsed.netloc
FUNKWHALE_PROTOCOL = _parsed.scheme
FUNKWHALE_PROTOCOL = FUNKWHALE_PROTOCOL.lower()
FUNKWHALE_HOSTNAME = FUNKWHALE_HOSTNAME.lower()
2018-06-09 13:36:16 +00:00
FUNKWHALE_URL = "{}://{}".format(FUNKWHALE_PROTOCOL, FUNKWHALE_HOSTNAME)
FUNKWHALE_SPA_HTML_ROOT = env(
"FUNKWHALE_SPA_HTML_ROOT", default=FUNKWHALE_URL + "/front/"
)
2020-04-20 11:48:19 +00:00
"""
URL or path to the Web Application files.
Funkwhale needs access to it so that it can inject <meta> tags relevant
to the given page (e.g page title, cover, etc.).
2020-04-20 11:48:19 +00:00
If a URL is specified, the index.html file will be fetched through HTTP.
If a path is provided,
2020-04-20 11:48:19 +00:00
it will be accessed from disk.
Use something like ``/srv/funkwhale/front/dist/`` if the web processes shows
request errors related to this.
2020-04-20 11:48:19 +00:00
"""
FUNKWHALE_SPA_HTML_CACHE_DURATION = env.int(
"FUNKWHALE_SPA_HTML_CACHE_DURATION", default=60 * 15
)
FUNKWHALE_EMBED_URL = env(
"FUNKWHALE_EMBED_URL", default=FUNKWHALE_URL + "/front/embed.html"
)
FUNKWHALE_SPA_REWRITE_MANIFEST = env.bool(
"FUNKWHALE_SPA_REWRITE_MANIFEST", default=True
)
FUNKWHALE_SPA_REWRITE_MANIFEST_URL = env.bool(
"FUNKWHALE_SPA_REWRITE_MANIFEST_URL", default=None
)
APP_NAME = "Funkwhale"
FEDERATION_HOSTNAME = env("FEDERATION_HOSTNAME", default=FUNKWHALE_HOSTNAME).lower()
2019-01-30 10:54:43 +00:00
FEDERATION_SERVICE_ACTOR_USERNAME = env(
"FEDERATION_SERVICE_ACTOR_USERNAME", default="service"
)
# How many pages to fetch when crawling outboxes and third-party collections
FEDERATION_COLLECTION_MAX_PAGES = env.int("FEDERATION_COLLECTION_MAX_PAGES", default=5)
2020-04-20 11:48:19 +00:00
"""
Number of existing pages of content to fetch when discovering/refreshing an
actor or channel.
2020-04-20 11:48:19 +00:00
More pages means more content will be loaded, but will require more resources.
"""
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[]) + [FUNKWHALE_HOSTNAME]
2020-04-20 11:48:19 +00:00
"""
List of allowed hostnames for which the Funkwhale server will answer.
"""
# APP CONFIGURATION
# ------------------------------------------------------------------------------
DJANGO_APPS = (
2018-06-09 13:36:16 +00:00
"channels",
# Default Django apps:
2018-06-09 13:36:16 +00:00
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.postgres",
# Useful template tags:
# 'django.contrib.humanize',
# Admin
2018-06-09 13:36:16 +00:00
"django.contrib.admin",
)
THIRD_PARTY_APPS = (
# 'crispy_forms', # Form layouts
2018-06-09 13:36:16 +00:00
"allauth", # registration
"allauth.account", # registration
"allauth.socialaccount", # registration
"corsheaders",
"oauth2_provider",
2018-06-09 13:36:16 +00:00
"rest_framework",
"rest_framework.authtoken",
"rest_auth",
"rest_auth.registration",
"dynamic_preferences",
"django_filters",
"django_cleanup",
2018-07-13 12:10:39 +00:00
"versatileimagefield",
)
2018-02-17 20:25:21 +00:00
# Apps specific for this project go here.
LOCAL_APPS = (
"funkwhale_api.common.apps.CommonConfig",
2018-06-09 13:36:16 +00:00
"funkwhale_api.activity.apps.ActivityConfig",
"funkwhale_api.users", # custom users app
"funkwhale_api.users.oauth",
# Your stuff: custom apps go here
2018-06-09 13:36:16 +00:00
"funkwhale_api.instance",
"funkwhale_api.audio",
2018-06-09 13:36:16 +00:00
"funkwhale_api.music",
"funkwhale_api.requests",
"funkwhale_api.favorites",
"funkwhale_api.federation",
"funkwhale_api.moderation.apps.ModerationConfig",
2018-06-09 13:36:16 +00:00
"funkwhale_api.radios",
"funkwhale_api.history",
"funkwhale_api.playlists",
"funkwhale_api.subsonic",
2019-07-08 13:26:14 +00:00
"funkwhale_api.tags",
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
2018-02-17 20:25:21 +00:00
ADDITIONAL_APPS = env.list("ADDITIONAL_APPS", default=[])
2020-04-20 11:48:19 +00:00
"""
List of Django apps to load in addition to Funkwhale plugins and apps.
"""
INSTALLED_APPS = (
DJANGO_APPS
+ THIRD_PARTY_APPS
+ LOCAL_APPS
+ tuple(ADDITIONAL_APPS)
2020-07-03 08:59:12 +00:00
+ tuple(plugins.trigger_filter(plugins.PLUGINS_APPS, [], enabled=True))
)
# MIDDLEWARE CONFIGURATION
# ------------------------------------------------------------------------------
ADDITIONAL_MIDDLEWARES_BEFORE = env.list("ADDITIONAL_MIDDLEWARES_BEFORE", default=[])
2020-07-03 08:59:12 +00:00
MIDDLEWARE = (
tuple(plugins.trigger_filter(plugins.MIDDLEWARES_BEFORE, [], enabled=True))
+ tuple(ADDITIONAL_MIDDLEWARES_BEFORE)
+ (
"django.middleware.security.SecurityMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"corsheaders.middleware.CorsMiddleware",
# needs to be before SPA middleware
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
# /end
"funkwhale_api.common.middleware.SPAFallbackMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"funkwhale_api.users.middleware.RecordActivityMiddleware",
"funkwhale_api.common.middleware.ThrottleStatusMiddleware",
)
+ tuple(plugins.trigger_filter(plugins.MIDDLEWARES_AFTER, [], enabled=True))
)
# DEBUG
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
2020-04-20 11:48:19 +00:00
DJANGO_DEBUG = DEBUG = env.bool("DJANGO_DEBUG", False)
"""
Whether to enable debugging info and pages.
Never enable this on a production server, as it can leak very sensitive
information.
2020-04-20 11:48:19 +00:00
"""
# FIXTURE CONFIGURATION
# ------------------------------------------------------------------------------
# See:
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS
2018-06-09 13:36:16 +00:00
FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),)
# EMAIL CONFIGURATION
# ------------------------------------------------------------------------------
# EMAIL
# ------------------------------------------------------------------------------
DEFAULT_FROM_EMAIL = env(
2018-06-09 13:36:16 +00:00
"DEFAULT_FROM_EMAIL", default="Funkwhale <noreply@{}>".format(FUNKWHALE_HOSTNAME)
)
2020-04-20 11:48:19 +00:00
"""
Name and e-mail address used to send system e-mails.
2020-04-20 11:48:19 +00:00
Default: ``Funkwhale <noreply@yourdomain>``
.. note::
Both the forms ``Funkwhale <noreply@yourdomain>`` and
``noreply@yourdomain`` work.
2020-04-20 11:48:19 +00:00
"""
2018-06-09 13:36:16 +00:00
EMAIL_SUBJECT_PREFIX = env("EMAIL_SUBJECT_PREFIX", default="[Funkwhale] ")
2020-04-20 11:48:19 +00:00
"""
Subject prefix for system e-mails.
2020-04-20 11:48:19 +00:00
"""
2018-06-09 13:36:16 +00:00
SERVER_EMAIL = env("SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
2018-06-09 13:36:16 +00:00
EMAIL_CONFIG = env.email_url("EMAIL_CONFIG", default="consolemail://")
2020-04-20 11:48:19 +00:00
"""
SMTP configuration for sending e-mails. Possible values:
2020-04-20 11:48:19 +00:00
- ``EMAIL_CONFIG=consolemail://``: output e-mails to console (the default)
- ``EMAIL_CONFIG=dummymail://``: disable e-mail sending completely
2020-04-20 11:48:19 +00:00
On a production instance, you'll usually want to use an external SMTP server:
2020-04-20 11:48:19 +00:00
- ``EMAIL_CONFIG=smtp://user@:password@youremail.host:25``
- ``EMAIL_CONFIG=smtp+ssl://user@:password@youremail.host:465``
- ``EMAIL_CONFIG=smtp+tls://user@:password@youremail.host:587``
.. note::
If ``user`` or ``password`` contain special characters (eg.
``noreply@youremail.host`` as ``user``), be sure to urlencode them, using
for example the command:
``python3 -c 'import urllib.parse; print(urllib.parse.quote_plus
("noreply@youremail.host"))'``
2020-04-20 11:48:19 +00:00
(returns ``noreply%40youremail.host``)
"""
vars().update(EMAIL_CONFIG)
# DATABASE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
2020-04-20 11:48:19 +00:00
DATABASE_URL = env.db("DATABASE_URL")
"""
URL to connect to the PostgreSQL database. Examples:
- ``postgresql://funkwhale@:5432/funkwhale``
- ``postgresql://<user>:<password>@<host>:<port>/<database>``
- ``postgresql://funkwhale:passw0rd@localhost:5432/funkwhale_database``
"""
DATABASES = {
# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
2020-04-20 11:48:19 +00:00
"default": DATABASE_URL
}
2018-06-09 13:36:16 +00:00
DATABASES["default"]["ATOMIC_REQUESTS"] = True
2020-04-20 11:48:19 +00:00
DB_CONN_MAX_AGE = DATABASES["default"]["CONN_MAX_AGE"] = env(
"DB_CONN_MAX_AGE", default=60 * 5
)
"""
Max time, in seconds, before database connections are closed.
"""
MIGRATION_MODULES = {
# see https://github.com/jazzband/django-oauth-toolkit/issues/634
# swappable models are badly designed in oauth2_provider
# ignore migrations and provide our own models.
2019-04-04 14:05:34 +00:00
"oauth2_provider": None,
"sites": "funkwhale_api.contrib.sites.migrations",
}
# GENERAL CONFIGURATION
# ------------------------------------------------------------------------------
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
2018-06-09 13:36:16 +00:00
TIME_ZONE = "UTC"
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
2018-06-09 13:36:16 +00:00
LANGUAGE_CODE = "en-us"
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
USE_L10N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True
# TEMPLATE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#templates
TEMPLATES = [
{
# See:
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
2018-06-09 13:36:16 +00:00
"BACKEND": "django.template.backends.django.DjangoTemplates",
# See:
# https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
2018-06-09 13:36:16 +00:00
"DIRS": [str(APPS_DIR.path("templates"))],
"OPTIONS": {
# See:
# https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
2018-06-09 13:36:16 +00:00
"debug": DEBUG,
# See:
# https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
# https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
2018-06-09 13:36:16 +00:00
"loaders": [
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
# See:
# https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
2018-06-09 13:36:16 +00:00
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages",
# Your stuff: custom template context processors go here
],
},
2018-06-09 13:36:16 +00:00
}
]
# See:
# http://django-crispy-forms.readthedocs.org/en/latest/install.html#template-packs
2018-06-09 13:36:16 +00:00
CRISPY_TEMPLATE_PACK = "bootstrap3"
# STATIC FILE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
2018-06-09 13:36:16 +00:00
STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR("staticfiles")))
2020-04-20 11:48:19 +00:00
"""
Path were static files should be collected.
"""
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = env("STATIC_URL", default=FUNKWHALE_URL + "/staticfiles/")
2018-06-09 13:36:16 +00:00
DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIFileSystemStorage"
PROXY_MEDIA = env.bool("PROXY_MEDIA", default=True)
2020-04-20 11:48:19 +00:00
"""
Wether to proxy audio files through your reverse proxy.
It's recommended to keep this on, as a way to enforce access control, however,
if you're using S3 storage with :attr:`AWS_QUERYSTRING_AUTH`,
2020-04-20 11:48:19 +00:00
it's safe to disable it.
"""
AWS_DEFAULT_ACL = None
AWS_QUERYSTRING_AUTH = env.bool("AWS_QUERYSTRING_AUTH", default=not PROXY_MEDIA)
2020-04-20 11:48:19 +00:00
"""
Whether to include signatures in S3 urls, as a way to enforce access-control.
Defaults to the inverse of :attr:`PROXY_MEDIA`.
"""
AWS_S3_MAX_MEMORY_SIZE = env.int(
"AWS_S3_MAX_MEMORY_SIZE", default=1000 * 1000 * 1000 * 20
)
2020-04-20 11:48:19 +00:00
AWS_QUERYSTRING_EXPIRE = env.int("AWS_QUERYSTRING_EXPIRE", default=3600)
2020-04-20 11:48:19 +00:00
"""
Expiration delay, in seconds, of signatures generated when
:attr:`AWS_QUERYSTRING_AUTH` is enabled.
2020-04-20 11:48:19 +00:00
"""
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", default=None)
2020-04-20 11:48:19 +00:00
"""
Access-key ID for your S3 storage.
"""
if AWS_ACCESS_KEY_ID:
AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
2020-04-20 11:48:19 +00:00
"""
Secret access key for your S3 storage.
"""
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
2020-04-20 11:48:19 +00:00
"""
Bucket name of your S3 storage.
"""
AWS_S3_CUSTOM_DOMAIN = env("AWS_S3_CUSTOM_DOMAIN", default=None)
2020-04-20 11:48:19 +00:00
"""
Custom domain to use for your S3 storage.
"""
AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL", default=None)
2020-04-20 11:48:19 +00:00
"""
If you use a S3-compatible storage such as minio,
set the following variable to the full URL to the storage server. Example:
2020-04-20 11:48:19 +00:00
- ``https://minio.mydomain.com``
- ``https://s3.wasabisys.com``
"""
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", default=None)
"""If you are using Amazon S3 to serve media directly,
you will need to specify your region name in order to access files.
Example:
2020-04-20 11:48:19 +00:00
- ``eu-west-2``
"""
AWS_S3_SIGNATURE_VERSION = "s3v4"
AWS_LOCATION = env("AWS_LOCATION", default="")
2020-04-20 11:48:19 +00:00
"""
An optional bucket subdirectory were you want to store the files.
This is especially useful if you plan to use share the bucket with other
services.
2020-04-20 11:48:19 +00:00
"""
DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIS3Boto3Storage"
2020-04-20 11:48:19 +00:00
# See:
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
2018-06-09 13:36:16 +00:00
STATICFILES_DIRS = (str(APPS_DIR.path("static")),)
# See:
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = (
2018-06-09 13:36:16 +00:00
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
)
# MEDIA CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
2018-06-09 13:36:16 +00:00
MEDIA_ROOT = env("MEDIA_ROOT", default=str(APPS_DIR("media")))
2020-04-20 11:48:19 +00:00
"""
Path where media files (such as album covers or audio tracks) are stored
on your system. Ensure this directory actually exists.
2020-04-20 11:48:19 +00:00
"""
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = env("MEDIA_URL", default=FUNKWHALE_URL + "/media/")
2020-04-20 11:48:19 +00:00
"""
URL where media files are served. The default value should work fine on most
configurations, but could can tweak this if you are hosting media
files on a separate domain, or if you host Funkwhale on a non-standard port.
2020-04-20 11:48:19 +00:00
"""
FILE_UPLOAD_PERMISSIONS = 0o644
2019-11-25 08:49:06 +00:00
ATTACHMENTS_UNATTACHED_PRUNE_DELAY = env.int(
"ATTACHMENTS_UNATTACHED_PRUNE_DELAY", default=3600 * 24
)
2020-04-20 11:48:19 +00:00
"""
Delay in seconds before uploaded but unattached attachements are pruned
from the system.
2020-04-20 11:48:19 +00:00
"""
2019-11-25 08:49:06 +00:00
# URL Configuration
# ------------------------------------------------------------------------------
2018-06-09 13:36:16 +00:00
ROOT_URLCONF = "config.urls"
SPA_URLCONF = "config.spa_urls"
2018-02-25 12:05:01 +00:00
ASGI_APPLICATION = "config.routing.application"
# This ensures that Django will be able to detect a secure connection
2018-06-09 13:36:16 +00:00
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
# AUTHENTICATION CONFIGURATION
# ------------------------------------------------------------------------------
AUTHENTICATION_BACKENDS = (
"funkwhale_api.users.auth_backends.ModelBackend",
"funkwhale_api.users.auth_backends.AllAuthBackend",
)
SESSION_COOKIE_HTTPONLY = False
2020-07-18 08:30:36 +00:00
SESSION_COOKIE_AGE = env.int("SESSION_COOKIE_AGE", default=3600 * 25 * 60)
# Some really nice defaults
2018-06-09 13:36:16 +00:00
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION_ENFORCE = env.bool(
"ACCOUNT_EMAIL_VERIFICATION_ENFORCE", default=False
)
2020-04-20 11:48:19 +00:00
"""
Determine wether users need to verify their e-mail address before using the service. Enabling this can be useful
to reduce spam or bots accounts, however, you'll need to configure a mail server so that your users can receive the
verification e-mails, using :attr:`EMAIL_CONFIG`.
Note that regardless of the setting value, superusers created through the command line will never require verification.
2020-04-20 11:48:19 +00:00
Note that regardless of the setting value, superusers created through the
command line will never require verification.
2020-04-20 11:48:19 +00:00
"""
ACCOUNT_EMAIL_VERIFICATION = (
"mandatory" if ACCOUNT_EMAIL_VERIFICATION_ENFORCE else "optional"
)
ACCOUNT_USERNAME_VALIDATORS = "funkwhale_api.users.serializers.username_validators"
# Custom user app defaults
# Select the correct user model
2018-06-09 13:36:16 +00:00
AUTH_USER_MODEL = "users.User"
LOGIN_REDIRECT_URL = "users:redirect"
LOGIN_URL = "account_login"
# OAuth configuration
from funkwhale_api.users.oauth import scopes # noqa
OAUTH2_PROVIDER = {
"SCOPES": {s.id: s.label for s in scopes.SCOPES_BY_ID.values()},
"ALLOWED_REDIRECT_URI_SCHEMES": ["http", "https", "urn"],
# we keep expired tokens for 15 days, for tracability
"REFRESH_TOKEN_EXPIRE_SECONDS": 3600 * 24 * 15,
"AUTHORIZATION_CODE_EXPIRE_SECONDS": 5 * 60,
"ACCESS_TOKEN_EXPIRE_SECONDS": 60 * 60 * 10,
2019-05-03 10:23:45 +00:00
"OAUTH2_SERVER_CLASS": "funkwhale_api.users.oauth.server.OAuth2Server",
}
OAUTH2_PROVIDER_APPLICATION_MODEL = "users.Application"
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "users.AccessToken"
OAUTH2_PROVIDER_GRANT_MODEL = "users.Grant"
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "users.RefreshToken"
2021-07-04 04:57:35 +00:00
OAUTH2_PROVIDER_ID_TOKEN_MODEL = "users.IdToken"
SCOPED_TOKENS_MAX_AGE = 60 * 60 * 24 * 3
2018-08-22 18:10:39 +00:00
# LDAP AUTHENTICATION CONFIGURATION
# ------------------------------------------------------------------------------
AUTH_LDAP_ENABLED = env.bool("LDAP_ENABLED", default=False)
2020-04-20 11:48:19 +00:00
"""
Wether to enable LDAP authentication.
See :doc:`/installation/ldap` for more information.
2020-04-20 11:48:19 +00:00
"""
2018-08-22 18:10:39 +00:00
if AUTH_LDAP_ENABLED:
# Import the LDAP modules here.
# This way, we don't need the dependency unless someone
2018-08-22 18:10:39 +00:00
# actually enables the LDAP support
import ldap
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion, GroupOfNamesType
# Add LDAP to the authentication backends
AUTHENTICATION_BACKENDS += ("django_auth_ldap.backend.LDAPBackend",)
# Basic configuration
AUTH_LDAP_SERVER_URI = env("LDAP_SERVER_URI")
AUTH_LDAP_BIND_DN = env("LDAP_BIND_DN", default="")
AUTH_LDAP_BIND_PASSWORD = env("LDAP_BIND_PASSWORD", default="")
AUTH_LDAP_SEARCH_FILTER = env("LDAP_SEARCH_FILTER", default="(uid={0})").format(
"%(user)s"
)
AUTH_LDAP_START_TLS = env.bool("LDAP_START_TLS", default=False)
AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = env(
"AUTH_LDAP_BIND_AS_AUTHENTICATING_USER", default=False
)
2018-08-22 18:10:39 +00:00
DEFAULT_USER_ATTR_MAP = [
"first_name:givenName",
"last_name:sn",
"username:cn",
"email:mail",
]
LDAP_USER_ATTR_MAP = env.list("LDAP_USER_ATTR_MAP", default=DEFAULT_USER_ATTR_MAP)
AUTH_LDAP_USER_ATTR_MAP = {}
for m in LDAP_USER_ATTR_MAP:
funkwhale_field, ldap_field = m.split(":")
AUTH_LDAP_USER_ATTR_MAP[funkwhale_field.strip()] = ldap_field.strip()
# Determine root DN supporting multiple root DNs
AUTH_LDAP_ROOT_DN = env("LDAP_ROOT_DN")
AUTH_LDAP_ROOT_DN_LIST = []
for ROOT_DN in AUTH_LDAP_ROOT_DN.split():
AUTH_LDAP_ROOT_DN_LIST.append(
LDAPSearch(ROOT_DN, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER)
)
# Search for the user in all the root DNs
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*AUTH_LDAP_ROOT_DN_LIST)
# Search for group types
LDAP_GROUP_DN = env("LDAP_GROUP_DN", default="")
if LDAP_GROUP_DN:
AUTH_LDAP_GROUP_DN = LDAP_GROUP_DN
# Get filter
AUTH_LDAP_GROUP_FILTER = env("LDAP_GROUP_FILER", default="")
# Search for the group in the specified DN
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
AUTH_LDAP_GROUP_DN, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_FILTER
)
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
# Configure basic group support
LDAP_REQUIRE_GROUP = env("LDAP_REQUIRE_GROUP", default="")
if LDAP_REQUIRE_GROUP:
AUTH_LDAP_REQUIRE_GROUP = LDAP_REQUIRE_GROUP
LDAP_DENY_GROUP = env("LDAP_DENY_GROUP", default="")
if LDAP_DENY_GROUP:
AUTH_LDAP_DENY_GROUP = LDAP_DENY_GROUP
# SLUGLIFIER
2018-06-09 13:36:16 +00:00
AUTOSLUG_SLUGIFY_FUNCTION = "slugify.slugify"
2017-07-17 20:00:32 +00:00
CACHE_DEFAULT = "redis://127.0.0.1:6379/0"
2020-04-20 11:48:19 +00:00
CACHE_URL = env.cache_url("CACHE_URL", default=CACHE_DEFAULT)
"""
URL to your redis server. Examples:
- ``redis://<host>:<port>/<database>``
- ``redis://127.0.0.1:6379/0``
- ``redis://:password@localhost:6379/0``
for password auth (the extra semicolon is important)
- ``redis:///run/redis/redis.sock?db=0`` over unix sockets
2020-04-20 11:48:19 +00:00
.. note::
If you want to use Redis over unix sockets, you'll also need to update
:attr:`CELERY_BROKER_URL`
2020-04-20 11:48:19 +00:00
"""
CACHES = {
2020-04-20 11:48:19 +00:00
"default": CACHE_URL,
"local": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "local-cache",
},
}
2017-07-17 20:00:32 +00:00
CACHES["default"]["BACKEND"] = "django_redis.cache.RedisCache"
2018-06-09 13:36:16 +00:00
2018-02-25 12:05:01 +00:00
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {"hosts": [CACHES["default"]["LOCATION"]]},
2018-06-09 13:36:16 +00:00
}
2018-02-25 12:05:01 +00:00
}
2017-07-17 20:00:32 +00:00
CACHES["default"]["OPTIONS"] = {
"CLIENT_CLASS": "funkwhale_api.common.cache.RedisClient",
2017-07-17 20:00:32 +00:00
"IGNORE_EXCEPTIONS": True, # mimics memcache behavior.
2018-06-09 13:36:16 +00:00
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
2017-07-17 20:00:32 +00:00
}
2019-10-25 08:23:13 +00:00
CACHEOPS_DURATION = env("CACHEOPS_DURATION", default=0)
CACHEOPS_ENABLED = bool(CACHEOPS_DURATION)
if CACHEOPS_ENABLED:
INSTALLED_APPS += ("cacheops",)
2020-04-20 15:20:51 +00:00
CACHEOPS_REDIS = env("CACHE_URL", default=CACHE_DEFAULT)
CACHEOPS_PREFIX = lambda _: "cacheops" # noqa
CACHEOPS_DEFAULTS = {"timeout": CACHEOPS_DURATION}
CACHEOPS = {
"music.album": {"ops": "count"},
"music.artist": {"ops": "count"},
"music.track": {"ops": "count"},
}
2017-07-17 20:00:32 +00:00
# CELERY
2018-06-09 13:36:16 +00:00
INSTALLED_APPS += ("funkwhale_api.taskapp.celery.CeleryConfig",)
CELERY_BROKER_URL = env(
2018-06-09 13:36:16 +00:00
"CELERY_BROKER_URL", default=env("CACHE_URL", default=CACHE_DEFAULT)
)
2020-04-20 11:48:19 +00:00
"""
URL to celery's task broker. Defaults to :attr:`CACHE_URL`,
so you shouldn't have to tweak this, unless you want
2020-04-20 11:48:19 +00:00
to use a different one, or use Redis sockets to connect.
Exemple:
- ``redis://127.0.0.1:6379/0``
- ``redis+socket:///run/redis/redis.sock?virtual_host=0``
2020-04-20 11:48:19 +00:00
"""
# END CELERY
# Location of root django.contrib.admin URL, use {% url 'admin:index' %}
2018-03-06 16:44:53 +00:00
# Your common stuff: Below this line define 3rd party library settings
CELERY_TASK_DEFAULT_RATE_LIMIT = 1
CELERY_TASK_TIME_LIMIT = 300
CELERY_BEAT_SCHEDULE = {
"audio.fetch_rss_feeds": {
"task": "audio.fetch_rss_feeds",
"schedule": crontab(minute="0", hour="*"),
"options": {"expires": 60 * 60},
},
2019-11-25 08:49:06 +00:00
"common.prune_unattached_attachments": {
"task": "common.prune_unattached_attachments",
"schedule": crontab(minute="0", hour="*"),
"options": {"expires": 60 * 60},
},
2018-06-09 13:36:16 +00:00
"federation.clean_music_cache": {
"task": "federation.clean_music_cache",
"schedule": crontab(minute="0", hour="*/2"),
2018-06-09 13:36:16 +00:00
"options": {"expires": 60 * 2},
2018-10-24 17:44:31 +00:00
},
"music.clean_transcoding_cache": {
"task": "music.clean_transcoding_cache",
"schedule": crontab(minute="0", hour="*"),
2018-10-24 17:44:31 +00:00
"options": {"expires": 60 * 2},
},
"oauth.clear_expired_tokens": {
"task": "oauth.clear_expired_tokens",
"schedule": crontab(minute="0", hour="0"),
"options": {"expires": 60 * 60 * 24},
},
"federation.refresh_nodeinfo_known_nodes": {
"task": "federation.refresh_nodeinfo_known_nodes",
"schedule": crontab(
**env.dict(
"SCHEDULE_FEDERATION_REFRESH_NODEINFO_KNOWN_NODES",
default={"minute": "0", "hour": "*"},
)
),
"options": {"expires": 60 * 60},
},
}
if env.bool("ADD_ALBUM_TAGS_FROM_TRACKS", default=True):
CELERY_BEAT_SCHEDULE["music.albums_set_tags_from_tracks"] = {
"task": "music.albums_set_tags_from_tracks",
"schedule": crontab(minute="0", hour="4", day_of_week="4"),
"options": {"expires": 60 * 60 * 2},
}
if env.bool("ADD_ARTIST_TAGS_FROM_TRACKS", default=True):
CELERY_BEAT_SCHEDULE["music.artists_set_tags_from_tracks"] = {
"task": "music.artists_set_tags_from_tracks",
"schedule": crontab(minute="0", hour="4", day_of_week="4"),
"options": {"expires": 60 * 60 * 2},
}
NODEINFO_REFRESH_DELAY = env.int("NODEINFO_REFRESH_DELAY", default=3600 * 24)
def get_user_secret_key(user):
from django.conf import settings
return settings.SECRET_KEY + str(user.secret_key)
OLD_PASSWORD_FIELD_ENABLED = True
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
"OPTIONS": {"min_length": env.int("PASSWORD_MIN_LENGTH", default=8)},
},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]
2020-04-20 11:48:19 +00:00
DISABLE_PASSWORD_VALIDATORS = env.bool("DISABLE_PASSWORD_VALIDATORS", default=False)
"""
Wether to disable password validators (length, common words,
similarity with username) used during regitration.
2020-04-20 11:48:19 +00:00
"""
if DISABLE_PASSWORD_VALIDATORS:
2020-03-18 10:57:33 +00:00
AUTH_PASSWORD_VALIDATORS = []
2018-06-09 13:36:16 +00:00
ACCOUNT_ADAPTER = "funkwhale_api.users.adapters.FunkwhaleAccountAdapter"
CORS_ORIGIN_ALLOW_ALL = True
# CORS_ORIGIN_WHITELIST = (
# 'localhost',
# 'funkwhale.localhost',
# )
CORS_ALLOW_CREDENTIALS = True
REST_FRAMEWORK = {
2018-06-09 13:36:16 +00:00
"DEFAULT_PAGINATION_CLASS": "funkwhale_api.common.pagination.FunkwhalePagination",
"PAGE_SIZE": 25,
"DEFAULT_PARSER_CLASSES": (
"rest_framework.parsers.JSONParser",
"rest_framework.parsers.FormParser",
"rest_framework.parsers.MultiPartParser",
"funkwhale_api.federation.parsers.ActivityParser",
),
2018-06-09 13:36:16 +00:00
"DEFAULT_AUTHENTICATION_CLASSES": (
"funkwhale_api.common.authentication.OAuth2Authentication",
"funkwhale_api.common.authentication.ApplicationTokenAuthentication",
2018-06-09 13:36:16 +00:00
"rest_framework.authentication.BasicAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": (
"funkwhale_api.users.oauth.permissions.ScopePermission",
),
2018-06-09 13:36:16 +00:00
"DEFAULT_FILTER_BACKENDS": (
"rest_framework.filters.OrderingFilter",
"django_filters.rest_framework.DjangoFilterBackend",
),
2018-06-09 13:36:16 +00:00
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
"NUM_PROXIES": env.int("NUM_PROXIES", default=1),
}
THROTTLING_ENABLED = env.bool("THROTTLING_ENABLED", default=True)
2020-04-20 11:48:19 +00:00
"""
Wether to enable throttling (also known as rate-limiting).
Leaving this enabled is recommended
2020-04-20 11:48:19 +00:00
especially on public pods, to improve the quality of service.
"""
if THROTTLING_ENABLED:
REST_FRAMEWORK["DEFAULT_THROTTLE_CLASSES"] = env.list(
"THROTTLE_CLASSES",
default=["funkwhale_api.common.throttling.FunkwhaleThrottle"],
)
THROTTLING_SCOPES = {
"*": {"anonymous": "anonymous-wildcard", "authenticated": "authenticated-wildcard"},
"create": {
"authenticated": "authenticated-create",
"anonymous": "anonymous-create",
},
"list": {"authenticated": "authenticated-list", "anonymous": "anonymous-list"},
"retrieve": {
"authenticated": "authenticated-retrieve",
"anonymous": "anonymous-retrieve",
},
"destroy": {
"authenticated": "authenticated-destroy",
"anonymous": "anonymous-destroy",
},
"update": {
"authenticated": "authenticated-update",
"anonymous": "anonymous-update",
},
"partial_update": {
"authenticated": "authenticated-update",
"anonymous": "anonymous-update",
},
}
THROTTLING_USER_RATES = env.dict("THROTTLING_RATES", default={})
THROTTLING_RATES = {
"anonymous-wildcard": {
"rate": THROTTLING_USER_RATES.get("anonymous-wildcard", "1000/h"),
"description": "Anonymous requests not covered by other limits",
},
"authenticated-wildcard": {
"rate": THROTTLING_USER_RATES.get("authenticated-wildcard", "2000/h"),
"description": "Authenticated requests not covered by other limits",
},
"authenticated-create": {
"rate": THROTTLING_USER_RATES.get("authenticated-create", "1000/hour"),
"description": "Authenticated POST requests",
},
"anonymous-create": {
"rate": THROTTLING_USER_RATES.get("anonymous-create", "1000/day"),
"description": "Anonymous POST requests",
},
"authenticated-list": {
"rate": THROTTLING_USER_RATES.get("authenticated-list", "10000/hour"),
"description": "Authenticated GET requests on resource lists",
},
"anonymous-list": {
"rate": THROTTLING_USER_RATES.get("anonymous-list", "10000/day"),
"description": "Anonymous GET requests on resource lists",
},
"authenticated-retrieve": {
"rate": THROTTLING_USER_RATES.get("authenticated-retrieve", "10000/hour"),
"description": "Authenticated GET requests on resource detail",
},
"anonymous-retrieve": {
"rate": THROTTLING_USER_RATES.get("anonymous-retrieve", "10000/day"),
"description": "Anonymous GET requests on resource detail",
},
"authenticated-destroy": {
"rate": THROTTLING_USER_RATES.get("authenticated-destroy", "500/hour"),
"description": "Authenticated DELETE requests on resource detail",
},
"anonymous-destroy": {
"rate": THROTTLING_USER_RATES.get("anonymous-destroy", "1000/day"),
"description": "Anonymous DELETE requests on resource detail",
},
"authenticated-update": {
"rate": THROTTLING_USER_RATES.get("authenticated-update", "1000/hour"),
"description": "Authenticated PATCH and PUT requests on resource detail",
},
"anonymous-update": {
"rate": THROTTLING_USER_RATES.get("anonymous-update", "1000/day"),
"description": "Anonymous PATCH and PUT requests on resource detail",
},
"subsonic": {
"rate": THROTTLING_USER_RATES.get("subsonic", "2000/hour"),
"description": "All subsonic API requests",
},
# potentially spammy / dangerous endpoints
"authenticated-reports": {
"rate": THROTTLING_USER_RATES.get("authenticated-reports", "100/day"),
"description": "Authenticated report submission",
},
"anonymous-reports": {
"rate": THROTTLING_USER_RATES.get("anonymous-reports", "10/day"),
"description": "Anonymous report submission",
},
"authenticated-oauth-app": {
"rate": THROTTLING_USER_RATES.get("authenticated-oauth-app", "10/hour"),
"description": "Authenticated OAuth app creation",
},
"anonymous-oauth-app": {
"rate": THROTTLING_USER_RATES.get("anonymous-oauth-app", "10/day"),
"description": "Anonymous OAuth app creation",
},
"oauth-authorize": {
"rate": THROTTLING_USER_RATES.get("oauth-authorize", "100/hour"),
"description": "OAuth app authorization",
},
"oauth-token": {
"rate": THROTTLING_USER_RATES.get("oauth-token", "100/hour"),
"description": "OAuth token creation",
},
"oauth-revoke-token": {
"rate": THROTTLING_USER_RATES.get("oauth-revoke-token", "100/hour"),
"description": "OAuth token deletion",
},
"login": {
"rate": THROTTLING_USER_RATES.get("login", "30/hour"),
"description": "Login",
},
"signup": {
"rate": THROTTLING_USER_RATES.get("signup", "10/day"),
"description": "Account creation",
},
"verify-email": {
"rate": THROTTLING_USER_RATES.get("verify-email", "20/h"),
"description": "Email address confirmation",
},
"password-change": {
"rate": THROTTLING_USER_RATES.get("password-change", "20/h"),
"description": "Password change (when authenticated)",
},
"password-reset": {
"rate": THROTTLING_USER_RATES.get("password-reset", "20/h"),
"description": "Password reset request",
},
"password-reset-confirm": {
"rate": THROTTLING_USER_RATES.get("password-reset-confirm", "20/h"),
"description": "Password reset confirmation",
},
2020-03-02 16:23:03 +00:00
"fetch": {
"rate": THROTTLING_USER_RATES.get("fetch", "200/d"),
"description": "Fetch remote objects",
},
}
2020-04-20 11:48:19 +00:00
THROTTLING_RATES = THROTTLING_RATES
"""
Throttling rates for specific endpoints and features of the app.
You can tweak this if you are encountering to severe rate limiting issues or,
on the contrary, if you want to reduce the consumption on some endpoints.
2020-04-20 11:48:19 +00:00
Example:
- ``signup=5/d,password-reset=2/d,anonymous-reports=5/d``
"""
2018-06-09 13:36:16 +00:00
BROWSABLE_API_ENABLED = env.bool("BROWSABLE_API_ENABLED", default=False)
if BROWSABLE_API_ENABLED:
2018-06-09 13:36:16 +00:00
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] += (
"rest_framework.renderers.BrowsableAPIRenderer",
)
2018-05-06 09:30:41 +00:00
REST_AUTH_SERIALIZERS = {
2018-06-09 13:36:16 +00:00
"PASSWORD_RESET_SERIALIZER": "funkwhale_api.users.serializers.PasswordResetSerializer" # noqa
2018-05-06 09:30:41 +00:00
}
REST_SESSION_LOGIN = False
ATOMIC_REQUESTS = False
USE_X_FORWARDED_HOST = True
USE_X_FORWARDED_PORT = True
# Wether we should use Apache, Nginx (or other) headers
# when serving audio files. Defaults to Nginx.
2018-06-09 13:36:16 +00:00
REVERSE_PROXY_TYPE = env("REVERSE_PROXY_TYPE", default="nginx")
2020-04-20 11:48:19 +00:00
"""
Depending on the reverse proxy used in front of your funkwhale instance,
the API will use different kind of headers to serve audio files
Allowed values: ``nginx``, ``apache2``
"""
2018-06-09 13:36:16 +00:00
assert REVERSE_PROXY_TYPE in ["apache2", "nginx"], "Unsupported REVERSE_PROXY_TYPE"
2018-06-09 13:36:16 +00:00
PROTECT_FILES_PATH = env("PROTECT_FILES_PATH", default="/_protected")
2020-04-20 11:48:19 +00:00
"""
Which path will be used to process the internal redirection
to the reverse proxy **DO NOT** put a slash at the end.
2020-04-20 11:48:19 +00:00
You shouldn't have to tweak this.
"""
2018-06-09 13:36:16 +00:00
MUSICBRAINZ_CACHE_DURATION = env.int("MUSICBRAINZ_CACHE_DURATION", default=300)
2020-04-20 11:48:19 +00:00
"""
How long to cache MusicBrainz results, in seconds.
2020-04-20 11:48:19 +00:00
"""
MUSICBRAINZ_HOSTNAME = env("MUSICBRAINZ_HOSTNAME", default="musicbrainz.org")
2020-04-20 11:48:19 +00:00
"""
Use this setting to change the MusicBrainz hostname, for instance to
2020-04-20 11:48:19 +00:00
use a mirror. The hostname can also contain a port number.
2020-04-20 11:48:19 +00:00
Example:
- ``mymusicbrainz.mirror``
- ``localhost:5000``
"""
2017-07-17 20:00:32 +00:00
# Custom Admin URL, use {% url 'admin:index' %}
2018-06-09 13:36:16 +00:00
ADMIN_URL = env("DJANGO_ADMIN_URL", default="^api/admin/")
2020-04-20 11:48:19 +00:00
"""
Path to the Django admin area.
Examples:
2020-04-20 11:48:19 +00:00
- ``^api/admin/``
- ``^api/mycustompath/``
2020-04-20 11:48:19 +00:00
"""
CSRF_USE_SESSIONS = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
2018-03-19 11:36:15 +00:00
ACCOUNT_USERNAME_BLACKLIST = [
2018-06-09 13:36:16 +00:00
"funkwhale",
"library",
"instance",
2018-06-09 13:36:16 +00:00
"test",
"status",
"root",
"admin",
"owner",
"superuser",
"staff",
"service",
2018-07-13 12:10:39 +00:00
"me",
"ghost",
"_",
2018-09-22 12:29:30 +00:00
"-",
"hello",
"contact",
2018-09-22 12:29:30 +00:00
"inbox",
"outbox",
"shared-inbox",
"shared_inbox",
"actor",
2018-06-09 13:36:16 +00:00
] + env.list("ACCOUNT_USERNAME_BLACKLIST", default=[])
2020-04-20 11:48:19 +00:00
"""
List of usernames that will be unavailable during registration,
given as a list of strings.
2020-04-20 11:48:19 +00:00
"""
2018-06-09 13:36:16 +00:00
EXTERNAL_REQUESTS_VERIFY_SSL = env.bool("EXTERNAL_REQUESTS_VERIFY_SSL", default=True)
2020-04-20 11:48:19 +00:00
"""
Wether to enforce HTTPS certificates verification when doing outgoing HTTP
requests (typically with federation).
2020-04-20 11:48:19 +00:00
Disabling this is not recommended.
"""
2020-03-02 16:23:03 +00:00
EXTERNAL_REQUESTS_TIMEOUT = env.int("EXTERNAL_REQUESTS_TIMEOUT", default=10)
2020-04-20 11:48:19 +00:00
"""
Default timeout for external requests.
"""
2018-06-09 13:36:16 +00:00
MUSIC_DIRECTORY_PATH = env("MUSIC_DIRECTORY_PATH", default=None)
2020-04-20 11:48:19 +00:00
"""
The path on your server where Funkwhale can import files using
:ref:`in-place import <in-place-import>`. It must be readable by the webserver
and Funkwhale api and worker processes.
2020-04-20 11:48:19 +00:00
On docker installations, we recommend you use the default of ``/music``
for this value. For non-docker installation, you can use any absolute path.
``/srv/funkwhale/data/music`` is a safe choice if you don't know what to use.
.. note:: This path should not include any trailing slash.
2020-04-20 11:48:19 +00:00
.. warning::
You need to adapt your :ref:`reverse proxy configuration
<reverse-proxy-setup>` to serve the directory pointed by
``MUSIC_DIRECTORY_PATH`` on ``/_protected/music`` URL.
2020-04-20 11:48:19 +00:00
"""
MUSIC_DIRECTORY_SERVE_PATH = env(
2018-06-09 13:36:16 +00:00
"MUSIC_DIRECTORY_SERVE_PATH", default=MUSIC_DIRECTORY_PATH
)
2020-04-20 11:48:19 +00:00
"""
Default: :attr:`MUSIC_DIRECTORY_PATH`
When using Docker, the value of :attr:`MUSIC_DIRECTORY_PATH` in your containers
may differ from the real path on your host.
Assuming you have the following directive
2020-04-20 11:48:19 +00:00
in your :file:`docker-compose.yml` file::
volumes:
- /srv/funkwhale/data/music:/music:ro
Then, the value of :attr:`MUSIC_DIRECTORY_SERVE_PATH` should be
``/srv/funkwhale/data/music``. This must be readable by the webserver.
On non-docker setup, you don't need to configure this setting.
.. note:: This path should not include any trailing slash.
2020-04-20 11:48:19 +00:00
"""
# When this is set to default=True, we need to reenable migration music/0042
# to ensure data is populated correctly on existing pods
2020-08-04 12:24:12 +00:00
MUSIC_USE_DENORMALIZATION = env.bool("MUSIC_USE_DENORMALIZATION", default=True)
2020-04-20 11:48:19 +00:00
2018-06-19 19:47:43 +00:00
USERS_INVITATION_EXPIRATION_DAYS = env.int(
"USERS_INVITATION_EXPIRATION_DAYS", default=14
)
2020-04-20 11:48:19 +00:00
"""
Expiration delay, in days, for user invitations.
2020-04-20 11:48:19 +00:00
"""
VERSATILEIMAGEFIELD_RENDITION_KEY_SETS = {
"square": [
("original", "url"),
("square_crop", "crop__400x400"),
("medium_square_crop", "crop__200x200"),
("small_square_crop", "crop__50x50"),
2019-11-25 08:49:06 +00:00
],
"attachment_square": [
("original", "url"),
("medium_square_crop", "crop__200x200"),
("large_square_crop", "crop__600x600"),
2019-11-25 08:49:06 +00:00
],
}
VERSATILEIMAGEFIELD_SETTINGS = {
"create_images_on_demand": False,
"jpeg_resize_quality": env.int("THUMBNAIL_JPEG_RESIZE_QUALITY", default=95),
}
RSA_KEY_SIZE = 2048
# for performance gain in tests, since we don't need to actually create the
# thumbnails
CREATE_IMAGE_THUMBNAILS = env.bool("CREATE_IMAGE_THUMBNAILS", default=True)
# we rotate actor keys at most every two days by default
ACTOR_KEY_ROTATION_DELAY = env.int("ACTOR_KEY_ROTATION_DELAY", default=3600 * 48)
SUBSONIC_DEFAULT_TRANSCODING_FORMAT = (
env("SUBSONIC_DEFAULT_TRANSCODING_FORMAT", default="mp3") or None
)
2020-04-20 11:48:19 +00:00
"""
Default format for transcoding when using Subsonic API.
"""
# extra tags will be ignored
TAGS_MAX_BY_OBJ = env.int("TAGS_MAX_BY_OBJ", default=30)
2020-04-20 11:48:19 +00:00
"""
Maximum number of tags that can be associated with an object.
Extra tags will be ignored.
2020-04-20 11:48:19 +00:00
"""
FEDERATION_OBJECT_FETCH_DELAY = env.int(
"FEDERATION_OBJECT_FETCH_DELAY", default=60 * 24 * 3
)
2020-04-20 11:48:19 +00:00
"""
Delay, in minutes, before a remote object will be automatically
refetched when accessed in the UI.
2020-04-20 11:48:19 +00:00
"""
MODERATION_EMAIL_NOTIFICATIONS_ENABLED = env.bool(
"MODERATION_EMAIL_NOTIFICATIONS_ENABLED", default=True
)
2020-04-20 11:48:19 +00:00
"""
Whether to enable e-mail notifications to moderators and pods admins.
2020-04-20 11:48:19 +00:00
"""
2020-03-02 16:23:03 +00:00
FEDERATION_AUTHENTIFY_FETCHES = True
FEDERATION_SYNCHRONOUS_FETCH = env.bool("FEDERATION_SYNCHRONOUS_FETCH", default=True)
FEDERATION_DUPLICATE_FETCH_DELAY = env.int(
"FEDERATION_DUPLICATE_FETCH_DELAY", default=60 * 50
)
2020-04-20 11:48:19 +00:00
"""
Delay, in seconds, between two manual fetch of the same remote object.
"""
INSTANCE_SUPPORT_MESSAGE_DELAY = env.int("INSTANCE_SUPPORT_MESSAGE_DELAY", default=15)
2020-04-20 11:48:19 +00:00
"""
Delay after signup, in days, before the "support your pod" message is shown.
2020-04-20 11:48:19 +00:00
"""
FUNKWHALE_SUPPORT_MESSAGE_DELAY = env.int("FUNKWHALE_SUPPORT_MESSAGE_DELAY", default=15)
2020-04-20 11:48:19 +00:00
"""
Delay after signup, in days, before the "support Funkwhale" message is shown.
2020-04-20 11:48:19 +00:00
"""
MIN_DELAY_BETWEEN_DOWNLOADS_COUNT = env.int(
"MIN_DELAY_BETWEEN_DOWNLOADS_COUNT", default=60 * 60 * 6
)
2020-04-20 11:48:19 +00:00
"""
Minimum required period, in seconds, for two downloads of the same track
by the same IP or user to be recorded in statistics.
2020-04-20 11:48:19 +00:00
"""
MARKDOWN_EXTENSIONS = env.list("MARKDOWN_EXTENSIONS", default=["nl2br", "extra"])
2020-04-20 11:48:19 +00:00
"""
List of markdown extensions to enable.
See `<https://python-markdown.github.io/extensions/>`_.
2020-04-20 11:48:19 +00:00
"""
LINKIFIER_SUPPORTED_TLDS = ["audio"] + env.list("LINKINFIER_SUPPORTED_TLDS", default=[])
2020-04-20 11:48:19 +00:00
"""
Additional TLDs to support with our markdown linkifier.
"""
EXTERNAL_MEDIA_PROXY_ENABLED = env.bool("EXTERNAL_MEDIA_PROXY_ENABLED", default=True)
2020-04-20 11:48:19 +00:00
"""
Wether to proxy attachment files hosted on third party pods and and servers.
Keeping this to true is recommended, to reduce leaking browsing information
of your users, and reduce the bandwidth used on remote pods.
2020-04-20 11:48:19 +00:00
"""
PODCASTS_THIRD_PARTY_VISIBILITY = env("PODCASTS_THIRD_PARTY_VISIBILITY", default="me")
2020-04-20 11:48:19 +00:00
"""
By default, only people who subscribe to a podcast RSS will have access
to their episodes.
Switch to "instance" or "everyone" to change that.
2020-04-20 11:48:19 +00:00
Changing it only affect new podcasts.
"""
PODCASTS_RSS_FEED_REFRESH_DELAY = env.int(
"PODCASTS_RSS_FEED_REFRESH_DELAY", default=60 * 60 * 24
)
2020-04-20 11:48:19 +00:00
"""
Delay, in seconds, between two fetch of RSS feeds.
Reducing this mean you'll receive new episodes faster,
2020-04-20 11:48:19 +00:00
but will require more resources.
"""
# maximum items loaded through XML feed
PODCASTS_RSS_FEED_MAX_ITEMS = env.int("PODCASTS_RSS_FEED_MAX_ITEMS", default=250)
2020-04-20 11:48:19 +00:00
"""
Maximum number of RSS items to load in each podcast feed.
"""
IGNORE_FORWARDED_HOST_AND_PROTO = env.bool(
"IGNORE_FORWARDED_HOST_AND_PROTO", default=True
)
"""
Use :attr:`FUNKWHALE_HOSTNAME` and :attr:`FUNKWHALE_PROTOCOL`
instead of request header.
"""
HASHING_ALGORITHM = "sha256"
HASHING_CHUNK_SIZE = 1024 * 100