common.py 16.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
# -*- coding: utf-8 -*-
"""
Django settings for funkwhale_api project.

For more information on this file, see
https://docs.djangoproject.com/en/dev/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/dev/ref/settings/
"""
from __future__ import absolute_import, unicode_literals

Agate's avatar
Agate committed
13 14 15
import datetime
from urllib.parse import urlparse, urlsplit

16
import environ
17 18
from celery.schedules import crontab

Agate's avatar
Agate committed
19
from funkwhale_api import __version__
20 21

ROOT_DIR = environ.Path(__file__) - 3  # (/a/b/myfile.py - 3 = /)
Agate's avatar
Agate committed
22
APPS_DIR = ROOT_DIR.path("funkwhale_api")
23 24 25

env = environ.Env()
try:
Agate's avatar
Agate committed
26
    env.read_env(ROOT_DIR.file(".env"))
27 28
except FileNotFoundError:
    pass
29

30
FUNKWHALE_HOSTNAME = None
Agate's avatar
Agate committed
31 32
FUNKWHALE_HOSTNAME_SUFFIX = env("FUNKWHALE_HOSTNAME_SUFFIX", default=None)
FUNKWHALE_HOSTNAME_PREFIX = env("FUNKWHALE_HOSTNAME_PREFIX", default=None)
33 34
if FUNKWHALE_HOSTNAME_PREFIX and FUNKWHALE_HOSTNAME_SUFFIX:
    # We're in traefik case, in development
Agate's avatar
Agate committed
35 36 37 38
    FUNKWHALE_HOSTNAME = "{}.{}".format(
        FUNKWHALE_HOSTNAME_PREFIX, FUNKWHALE_HOSTNAME_SUFFIX
    )
    FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
39 40
else:
    try:
Agate's avatar
Agate committed
41 42
        FUNKWHALE_HOSTNAME = env("FUNKWHALE_HOSTNAME")
        FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
43
    except Exception:
Agate's avatar
Agate committed
44
        FUNKWHALE_URL = env("FUNKWHALE_URL")
45 46 47 48
        _parsed = urlsplit(FUNKWHALE_URL)
        FUNKWHALE_HOSTNAME = _parsed.netloc
        FUNKWHALE_PROTOCOL = _parsed.scheme

Agate's avatar
Agate committed
49
FUNKWHALE_URL = "{}://{}".format(FUNKWHALE_PROTOCOL, FUNKWHALE_HOSTNAME)
50

51

52
# XXX: deprecated, see #186
Agate's avatar
Agate committed
53 54
FEDERATION_ENABLED = env.bool("FEDERATION_ENABLED", default=True)
FEDERATION_HOSTNAME = env("FEDERATION_HOSTNAME", default=FUNKWHALE_HOSTNAME)
55
# XXX: deprecated, see #186
Agate's avatar
Agate committed
56
FEDERATION_COLLECTION_PAGE_SIZE = env.int("FEDERATION_COLLECTION_PAGE_SIZE", default=50)
57
# XXX: deprecated, see #186
58
FEDERATION_MUSIC_NEEDS_APPROVAL = env.bool(
Agate's avatar
Agate committed
59
    "FEDERATION_MUSIC_NEEDS_APPROVAL", default=True
60
)
61
# XXX: deprecated, see #186
Agate's avatar
Agate committed
62 63
FEDERATION_ACTOR_FETCH_DELAY = env.int("FEDERATION_ACTOR_FETCH_DELAY", default=60 * 12)
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS")
64

65 66 67
# APP CONFIGURATION
# ------------------------------------------------------------------------------
DJANGO_APPS = (
Agate's avatar
Agate committed
68
    "channels",
69
    # Default Django apps:
Agate's avatar
Agate committed
70 71 72 73 74 75 76
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.sites",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "django.contrib.postgres",
77 78 79
    # Useful template tags:
    # 'django.contrib.humanize',
    # Admin
Agate's avatar
Agate committed
80
    "django.contrib.admin",
81 82 83
)
THIRD_PARTY_APPS = (
    # 'crispy_forms',  # Form layouts
Agate's avatar
Agate committed
84 85 86 87 88 89 90 91 92 93 94 95 96
    "allauth",  # registration
    "allauth.account",  # registration
    "allauth.socialaccount",  # registration
    "corsheaders",
    "rest_framework",
    "rest_framework.authtoken",
    "taggit",
    "rest_auth",
    "rest_auth.registration",
    "dynamic_preferences",
    "django_filters",
    "cacheops",
    "django_cleanup",
97 98
)

Agate's avatar
Agate committed
99 100 101

# Sentry
RAVEN_ENABLED = env.bool("RAVEN_ENABLED", default=False)
Agate's avatar
Agate committed
102
RAVEN_DSN = env("RAVEN_DSN", default="")
Agate's avatar
Agate committed
103 104 105

if RAVEN_ENABLED:
    RAVEN_CONFIG = {
Agate's avatar
Agate committed
106
        "dsn": RAVEN_DSN,
Agate's avatar
Agate committed
107 108
        # If you are using git, you can also automatically configure the
        # release based on the git info.
Agate's avatar
Agate committed
109
        "release": __version__,
Agate's avatar
Agate committed
110
    }
Agate's avatar
Agate committed
111
    THIRD_PARTY_APPS += ("raven.contrib.django.raven_compat",)
Agate's avatar
Agate committed
112 113


114 115
# Apps specific for this project go here.
LOCAL_APPS = (
Agate's avatar
Agate committed
116 117 118
    "funkwhale_api.common",
    "funkwhale_api.activity.apps.ActivityConfig",
    "funkwhale_api.users",  # custom users app
119
    # Your stuff: custom apps go here
Agate's avatar
Agate committed
120 121 122 123 124 125 126 127 128 129 130 131
    "funkwhale_api.instance",
    "funkwhale_api.music",
    "funkwhale_api.requests",
    "funkwhale_api.favorites",
    "funkwhale_api.federation",
    "funkwhale_api.radios",
    "funkwhale_api.history",
    "funkwhale_api.playlists",
    "funkwhale_api.providers.audiofile",
    "funkwhale_api.providers.youtube",
    "funkwhale_api.providers.acoustid",
    "funkwhale_api.subsonic",
132 133 134
)

# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
Agate's avatar
Agate committed
135

136 137 138 139
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

# MIDDLEWARE CONFIGURATION
# ------------------------------------------------------------------------------
Agate's avatar
Agate committed
140
MIDDLEWARE = (
141
    # Make sure djangosecure.middleware.SecurityMiddleware is listed first
Agate's avatar
Agate committed
142 143 144 145 146 147 148
    "django.contrib.sessions.middleware.SessionMiddleware",
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
149
    "funkwhale_api.users.middleware.RecordActivityMiddleware",
150 151 152 153
)

# MIGRATIONS CONFIGURATION
# ------------------------------------------------------------------------------
Agate's avatar
Agate committed
154
MIGRATION_MODULES = {"sites": "funkwhale_api.contrib.sites.migrations"}
155 156 157 158 159 160 161 162 163

# DEBUG
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = env.bool("DJANGO_DEBUG", False)

# FIXTURE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS
Agate's avatar
Agate committed
164
FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),)
165 166 167

# EMAIL CONFIGURATION
# ------------------------------------------------------------------------------
168 169 170 171

# EMAIL
# ------------------------------------------------------------------------------
DEFAULT_FROM_EMAIL = env(
Agate's avatar
Agate committed
172 173
    "DEFAULT_FROM_EMAIL", default="Funkwhale <noreply@{}>".format(FUNKWHALE_HOSTNAME)
)
174

Agate's avatar
Agate committed
175 176
EMAIL_SUBJECT_PREFIX = env("EMAIL_SUBJECT_PREFIX", default="[Funkwhale] ")
SERVER_EMAIL = env("SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
177 178


Agate's avatar
Agate committed
179
EMAIL_CONFIG = env.email_url("EMAIL_CONFIG", default="consolemail://")
180 181

vars().update(EMAIL_CONFIG)
182 183 184 185 186 187

# DATABASE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
    # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
Agate's avatar
Agate committed
188
    "default": env.db("DATABASE_URL")
189
}
Agate's avatar
Agate committed
190
DATABASES["default"]["ATOMIC_REQUESTS"] = True
191 192 193 194 195 196 197 198 199 200 201 202 203
#
# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': 'db.sqlite3',
#     }
# }
# 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.
Agate's avatar
Agate committed
204
TIME_ZONE = "UTC"
205 206

# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
Agate's avatar
Agate committed
207
LANGUAGE_CODE = "en-us"
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226

# 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
Agate's avatar
Agate committed
227
        "BACKEND": "django.template.backends.django.DjangoTemplates",
228
        # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
Agate's avatar
Agate committed
229 230
        "DIRS": [str(APPS_DIR.path("templates"))],
        "OPTIONS": {
231
            # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
Agate's avatar
Agate committed
232
            "debug": DEBUG,
233 234
            # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
            # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
Agate's avatar
Agate committed
235 236 237
            "loaders": [
                "django.template.loaders.filesystem.Loader",
                "django.template.loaders.app_directories.Loader",
238 239
            ],
            # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
Agate's avatar
Agate committed
240 241 242 243 244 245 246 247 248
            "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",
249 250 251
                # Your stuff: custom template context processors go here
            ],
        },
Agate's avatar
Agate committed
252
    }
253 254 255
]

# See: http://django-crispy-forms.readthedocs.org/en/latest/install.html#template-packs
Agate's avatar
Agate committed
256
CRISPY_TEMPLATE_PACK = "bootstrap3"
257 258 259 260

# STATIC FILE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
Agate's avatar
Agate committed
261
STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR("staticfiles")))
262 263

# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
Agate's avatar
Agate committed
264 265
STATIC_URL = env("STATIC_URL", default="/staticfiles/")
DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIFileSystemStorage"
266 267

# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
Agate's avatar
Agate committed
268
STATICFILES_DIRS = (str(APPS_DIR.path("static")),)
269 270 271

# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = (
Agate's avatar
Agate committed
272 273
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
274 275 276 277 278
)

# MEDIA CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
Agate's avatar
Agate committed
279
MEDIA_ROOT = env("MEDIA_ROOT", default=str(APPS_DIR("media")))
280 281

# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
Agate's avatar
Agate committed
282
MEDIA_URL = env("MEDIA_URL", default="/media/")
283 284 285

# URL Configuration
# ------------------------------------------------------------------------------
Agate's avatar
Agate committed
286
ROOT_URLCONF = "config.urls"
287
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
Agate's avatar
Agate committed
288
WSGI_APPLICATION = "config.wsgi.application"
Agate's avatar
Agate committed
289
ASGI_APPLICATION = "config.routing.application"
290

291
# This ensures that Django will be able to detect a secure connection
Agate's avatar
Agate committed
292
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
293

294 295 296
# AUTHENTICATION CONFIGURATION
# ------------------------------------------------------------------------------
AUTHENTICATION_BACKENDS = (
Agate's avatar
Agate committed
297 298
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
299
)
300
SESSION_COOKIE_HTTPONLY = False
301
# Some really nice defaults
Agate's avatar
Agate committed
302
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
303
ACCOUNT_EMAIL_REQUIRED = True
Agate's avatar
Agate committed
304
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
305 306 307

# Custom user app defaults
# Select the correct user model
Agate's avatar
Agate committed
308 309 310
AUTH_USER_MODEL = "users.User"
LOGIN_REDIRECT_URL = "users:redirect"
LOGIN_URL = "account_login"
311 312

# SLUGLIFIER
Agate's avatar
Agate committed
313
AUTOSLUG_SLUGIFY_FUNCTION = "slugify.slugify"
314

315
CACHE_DEFAULT = "redis://127.0.0.1:6379/0"
Agate's avatar
Agate committed
316
CACHES = {"default": env.cache_url("CACHE_URL", default=CACHE_DEFAULT)}
317

318
CACHES["default"]["BACKEND"] = "django_redis.cache.RedisCache"
Agate's avatar
Agate committed
319 320

cache_url = urlparse(CACHES["default"]["LOCATION"])
Agate's avatar
Agate committed
321 322 323
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
Agate's avatar
Agate committed
324 325
        "CONFIG": {"hosts": [(cache_url.hostname, cache_url.port)]},
    }
Agate's avatar
Agate committed
326 327
}

328 329 330
CACHES["default"]["OPTIONS"] = {
    "CLIENT_CLASS": "django_redis.client.DefaultClient",
    "IGNORE_EXCEPTIONS": True,  # mimics memcache behavior.
Agate's avatar
Agate committed
331
    # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
332 333 334
}


335
# CELERY
Agate's avatar
Agate committed
336
INSTALLED_APPS += ("funkwhale_api.taskapp.celery.CeleryConfig",)
337
CELERY_BROKER_URL = env(
Agate's avatar
Agate committed
338 339
    "CELERY_BROKER_URL", default=env("CACHE_URL", default=CACHE_DEFAULT)
)
340
# END CELERY
341
# Location of root django.contrib.admin URL, use {% url 'admin:index' %}
Agate's avatar
Agate committed
342

343
# Your common stuff: Below this line define 3rd party library settings
344 345
CELERY_TASK_DEFAULT_RATE_LIMIT = 1
CELERY_TASK_TIME_LIMIT = 300
346
CELERYBEAT_SCHEDULE = {
Agate's avatar
Agate committed
347 348 349 350
    "federation.clean_music_cache": {
        "task": "funkwhale_api.federation.tasks.clean_music_cache",
        "schedule": crontab(hour="*/2"),
        "options": {"expires": 60 * 2},
351 352 353
    }
}

354
JWT_AUTH = {
Agate's avatar
Agate committed
355 356 357 358 359
    "JWT_ALLOW_REFRESH": True,
    "JWT_EXPIRATION_DELTA": datetime.timedelta(days=7),
    "JWT_REFRESH_EXPIRATION_DELTA": datetime.timedelta(days=30),
    "JWT_AUTH_HEADER_PREFIX": "JWT",
    "JWT_GET_USER_SECRET_KEY": lambda user: user.secret_key,
360
}
361
OLD_PASSWORD_FIELD_ENABLED = True
Agate's avatar
Agate committed
362
ACCOUNT_ADAPTER = "funkwhale_api.users.adapters.FunkwhaleAccountAdapter"
363 364 365 366 367 368
CORS_ORIGIN_ALLOW_ALL = True
# CORS_ORIGIN_WHITELIST = (
#     'localhost',
#     'funkwhale.localhost',
# )
CORS_ALLOW_CREDENTIALS = True
369

370
REST_FRAMEWORK = {
Agate's avatar
Agate committed
371 372 373 374 375 376 377 378
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
    "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",
379
    ),
Agate's avatar
Agate committed
380 381 382 383 384 385
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS",
        "funkwhale_api.common.authentication.BearerTokenHeaderAuth",
        "rest_framework_jwt.authentication.JSONWebTokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.BasicAuthentication",
386
    ),
Agate's avatar
Agate committed
387 388 389
    "DEFAULT_FILTER_BACKENDS": (
        "rest_framework.filters.OrderingFilter",
        "django_filters.rest_framework.DjangoFilterBackend",
390
    ),
Agate's avatar
Agate committed
391
    "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
392
}
393

Agate's avatar
Agate committed
394
BROWSABLE_API_ENABLED = env.bool("BROWSABLE_API_ENABLED", default=False)
395
if BROWSABLE_API_ENABLED:
Agate's avatar
Agate committed
396 397
    REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] += (
        "rest_framework.renderers.BrowsableAPIRenderer",
398 399
    )

400
REST_AUTH_SERIALIZERS = {
Agate's avatar
Agate committed
401
    "PASSWORD_RESET_SERIALIZER": "funkwhale_api.users.serializers.PasswordResetSerializer"  # noqa
402 403 404
}
REST_SESSION_LOGIN = False
REST_USE_JWT = True
405 406

ATOMIC_REQUESTS = False
407 408
USE_X_FORWARDED_HOST = True
USE_X_FORWARDED_PORT = True
409 410 411

# Wether we should use Apache, Nginx (or other) headers when serving audio files
# Default to Nginx
Agate's avatar
Agate committed
412 413
REVERSE_PROXY_TYPE = env("REVERSE_PROXY_TYPE", default="nginx")
assert REVERSE_PROXY_TYPE in ["apache2", "nginx"], "Unsupported REVERSE_PROXY_TYPE"
414

415 416
# Which path will be used to process the internal redirection
# **DO NOT** put a slash at the end
Agate's avatar
Agate committed
417
PROTECT_FILES_PATH = env("PROTECT_FILES_PATH", default="/_protected")
418 419 420 421


# use this setting to tweak for how long you want to cache
# musicbrainz results. (value is in seconds)
Agate's avatar
Agate committed
422 423 424
MUSICBRAINZ_CACHE_DURATION = env.int("MUSICBRAINZ_CACHE_DURATION", default=300)
CACHEOPS_REDIS = env("CACHE_URL", default=CACHE_DEFAULT)
CACHEOPS_ENABLED = env.bool("CACHEOPS_ENABLED", default=True)
425
CACHEOPS = {
Agate's avatar
Agate committed
426 427 428 429 430
    "music.artist": {"ops": "all", "timeout": 60 * 60},
    "music.album": {"ops": "all", "timeout": 60 * 60},
    "music.track": {"ops": "all", "timeout": 60 * 60},
    "music.trackfile": {"ops": "all", "timeout": 60 * 60},
    "taggit.tag": {"ops": "all", "timeout": 60 * 60},
431
}
432

433
# Custom Admin URL, use {% url 'admin:index' %}
Agate's avatar
Agate committed
434
ADMIN_URL = env("DJANGO_ADMIN_URL", default="^api/admin/")
435
CSRF_USE_SESSIONS = True
Agate's avatar
Agate committed
436 437

# Playlist settings
438
# XXX: deprecated, see #186
Agate's avatar
Agate committed
439
PLAYLISTS_MAX_TRACKS = env.int("PLAYLISTS_MAX_TRACKS", default=250)
440 441

ACCOUNT_USERNAME_BLACKLIST = [
Agate's avatar
Agate committed
442 443 444 445 446 447 448 449 450 451 452 453 454
    "funkwhale",
    "library",
    "test",
    "status",
    "root",
    "admin",
    "owner",
    "superuser",
    "staff",
    "service",
] + env.list("ACCOUNT_USERNAME_BLACKLIST", default=[])

EXTERNAL_REQUESTS_VERIFY_SSL = env.bool("EXTERNAL_REQUESTS_VERIFY_SSL", default=True)
455 456
# XXX: deprecated, see #186
API_AUTHENTICATION_REQUIRED = env.bool("API_AUTHENTICATION_REQUIRED", True)
457

Agate's avatar
Agate committed
458
MUSIC_DIRECTORY_PATH = env("MUSIC_DIRECTORY_PATH", default=None)
459 460 461
# on Docker setup, the music directory may not match the host path,
# and we need to know it for it to serve stuff properly
MUSIC_DIRECTORY_SERVE_PATH = env(
Agate's avatar
Agate committed
462 463
    "MUSIC_DIRECTORY_SERVE_PATH", default=MUSIC_DIRECTORY_PATH
)