Fix #565: store media files in S3 bucket
This commit is contained in:
parent
31d990499d
commit
101ae27885
|
@ -306,6 +306,28 @@ STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR("staticfiles")))
|
||||||
STATIC_URL = env("STATIC_URL", default="/staticfiles/")
|
STATIC_URL = env("STATIC_URL", default="/staticfiles/")
|
||||||
DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIFileSystemStorage"
|
DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIFileSystemStorage"
|
||||||
|
|
||||||
|
AWS_DEFAULT_ACL = None
|
||||||
|
AWS_QUERYSTRING_AUTH = False
|
||||||
|
# MINIO_ACCESS_KEY_ID = env("MINIO_ACCESS_KEY_ID", default=None)
|
||||||
|
|
||||||
|
# if MINIO_ACCESS_KEY_ID:
|
||||||
|
# AWS_ACCESS_KEY_ID = MINIO_ACCESS_KEY_ID
|
||||||
|
# AWS_SECRET_ACCESS_KEY = env("MINIO_SECRET_KEY")
|
||||||
|
# AWS_STORAGE_BUCKET_NAME = env("MINIO_STORAGE_BUCKET_NAME")
|
||||||
|
# AWS_S3_ENDPOINT_URL = env("MINIO_URL")
|
||||||
|
# AWS_LOCATION = env("MINIO_BUCKET_DIRECTORY", default="")
|
||||||
|
# DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", default=None)
|
||||||
|
|
||||||
|
if AWS_ACCESS_KEY_ID:
|
||||||
|
AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID
|
||||||
|
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
|
||||||
|
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
|
||||||
|
AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL", default=None)
|
||||||
|
AWS_LOCATION = env("AWS_LOCATION", default="")
|
||||||
|
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
|
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
|
||||||
STATICFILES_DIRS = (str(APPS_DIR.path("static")),)
|
STATICFILES_DIRS = (str(APPS_DIR.path("static")),)
|
||||||
|
|
||||||
|
|
|
@ -838,7 +838,7 @@ class AlbumSerializer(MusicEntitySerializer):
|
||||||
d["cover"] = {
|
d["cover"] = {
|
||||||
"type": "Link",
|
"type": "Link",
|
||||||
"href": utils.full_url(instance.cover.url),
|
"href": utils.full_url(instance.cover.url),
|
||||||
"mediaType": mimetypes.guess_type(instance.cover.path)[0]
|
"mediaType": mimetypes.guess_type(instance.cover_path)[0]
|
||||||
or "image/jpeg",
|
or "image/jpeg",
|
||||||
}
|
}
|
||||||
if self.context.get("include_ap_context", self.parent is None):
|
if self.context.get("include_ap_context", self.parent is None):
|
||||||
|
|
|
@ -346,6 +346,16 @@ class Album(APIModelMixin):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cover_path(self):
|
||||||
|
if not self.cover:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return self.cover.path
|
||||||
|
except NotImplementedError:
|
||||||
|
# external storage
|
||||||
|
return self.cover.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tags(self):
|
def tags(self):
|
||||||
t = []
|
t = []
|
||||||
|
|
|
@ -240,6 +240,9 @@ def get_file_path(audio_file):
|
||||||
"MUSIC_DIRECTORY_PATH to serve in-place imported files"
|
"MUSIC_DIRECTORY_PATH to serve in-place imported files"
|
||||||
)
|
)
|
||||||
path = "/music" + audio_file.replace(prefix, "", 1)
|
path = "/music" + audio_file.replace(prefix, "", 1)
|
||||||
|
if path.startswith("http://") or path.startswith("https://"):
|
||||||
|
raise
|
||||||
|
return (settings.PROTECT_FILES_PATH + "/media/" + path).encode("utf-8")
|
||||||
return (settings.PROTECT_FILES_PATH + path).encode("utf-8")
|
return (settings.PROTECT_FILES_PATH + path).encode("utf-8")
|
||||||
if t == "apache2":
|
if t == "apache2":
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -69,3 +69,5 @@ aiohttp==3.5.4
|
||||||
autobahn>=19.3.2
|
autobahn>=19.3.2
|
||||||
|
|
||||||
django-oauth-toolkit==1.2
|
django-oauth-toolkit==1.2
|
||||||
|
django-storages==1.7.1
|
||||||
|
boto3
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Support S3-compatible storages for media files (#565)
|
|
@ -79,6 +79,16 @@ or invalid, and additional debug information to share in your support requests.
|
||||||
|
|
||||||
This information is available in all pages that list uploads, when clicking on the button next to the upload status.
|
This information is available in all pages that list uploads, when clicking on the button next to the upload status.
|
||||||
|
|
||||||
|
Support for S3-compatible storages to store media files
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Storing all media files on the Funkwhale server itself may not be possible or desirable
|
||||||
|
in all scenarios. You can now configure Funkwhale to store those files in a S3
|
||||||
|
bucket instead.
|
||||||
|
|
||||||
|
Check-out `https://docs.funkwhale.audio/admin/external-storages.html`_ if you want to use
|
||||||
|
this feature.
|
||||||
|
|
||||||
Prune library command
|
Prune library command
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -57,13 +57,20 @@ server {
|
||||||
alias ${MEDIA_ROOT}/;
|
alias ${MEDIA_ROOT}/;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# this is an internal location that is used to serve
|
||||||
|
# audio files once correct permission / authentication
|
||||||
|
# has been checked on API side
|
||||||
location /_protected/media {
|
location /_protected/media {
|
||||||
# this is an internal location that is used to serve
|
|
||||||
# audio files once correct permission / authentication
|
|
||||||
# has been checked on API side
|
|
||||||
internal;
|
internal;
|
||||||
alias ${MEDIA_ROOT};
|
alias ${MEDIA_ROOT};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
# Comment the previous location and uncomment this one if you're storing
|
||||||
|
# media files in a S3 bucket
|
||||||
|
# location ~ /_protected/media/(.+) {
|
||||||
|
# internal;
|
||||||
|
# proxy_pass $1;
|
||||||
|
# }
|
||||||
|
|
||||||
location /_protected/music {
|
location /_protected/music {
|
||||||
# this is an internal location that is used to serve
|
# this is an internal location that is used to serve
|
||||||
|
|
|
@ -136,3 +136,19 @@ FUNKWHALE_FRONTEND_PATH=/srv/funkwhale/front/dist
|
||||||
|
|
||||||
# Nginx related configuration
|
# Nginx related configuration
|
||||||
NGINX_MAX_BODY_SIZE=100M
|
NGINX_MAX_BODY_SIZE=100M
|
||||||
|
|
||||||
|
## External storages configuration
|
||||||
|
# Funkwhale can store uploaded files on Amazon S3 and S3-compatible storages (such as Minio)
|
||||||
|
# Uncomment and fill the variables below
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_STORAGE_BUCKET_NAME=
|
||||||
|
# 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
|
||||||
|
# AWS_LOCATION=
|
||||||
|
|
||||||
|
# If you use a S3-compatible storage such as minio, set the following variable
|
||||||
|
# the full URL to the storage server. Example:
|
||||||
|
# AWS_S3_ENDPOINT_URL=https://minio.mydomain.com
|
||||||
|
# AWS_S3_ENDPOINT_URL=
|
||||||
|
|
|
@ -109,9 +109,23 @@ server {
|
||||||
# audio files once correct permission / authentication
|
# audio files once correct permission / authentication
|
||||||
# has been checked on API side
|
# has been checked on API side
|
||||||
internal;
|
internal;
|
||||||
alias ${MEDIA_ROOT};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# this is an internal location that is used to serve
|
||||||
|
# audio files once correct permission / authentication
|
||||||
|
# has been checked on API side
|
||||||
|
location /_protected/media {
|
||||||
|
internal;
|
||||||
|
alias ${MEDIA_ROOT};
|
||||||
|
|
||||||
|
}
|
||||||
|
# Comment the previous location and uncomment this one if you're storing
|
||||||
|
# media files in a S3 bucket
|
||||||
|
# location ~ /_protected/media/(.+) {
|
||||||
|
# internal;
|
||||||
|
# proxy_pass $1;
|
||||||
|
# }
|
||||||
|
|
||||||
location /_protected/music {
|
location /_protected/music {
|
||||||
# this is an internal location that is used to serve
|
# this is an internal location that is used to serve
|
||||||
# audio files once correct permission / authentication
|
# audio files once correct permission / authentication
|
||||||
|
|
19
dev.yml
19
dev.yml
|
@ -63,6 +63,7 @@ services:
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
|
# - minio
|
||||||
- redis
|
- redis
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
|
@ -76,6 +77,7 @@ services:
|
||||||
build: *backend
|
build: *backend
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
|
# - minio
|
||||||
- redis
|
- redis
|
||||||
command: celery -A funkwhale_api.taskapp worker -l debug -B
|
command: celery -A funkwhale_api.taskapp worker -l debug -B
|
||||||
environment:
|
environment:
|
||||||
|
@ -146,6 +148,23 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- "./docs/swagger.yml:/usr/share/nginx/html/swagger.yml"
|
- "./docs/swagger.yml:/usr/share/nginx/html/swagger.yml"
|
||||||
|
|
||||||
|
# minio:
|
||||||
|
# image: minio/minio
|
||||||
|
# command: server /data
|
||||||
|
# volumes:
|
||||||
|
# - "./data/${COMPOSE_PROJECT_NAME-node1}/minio:/data"
|
||||||
|
# environment:
|
||||||
|
# - "MINIO_ACCESS_KEY=${AWS_ACCESS_KEY_ID-access_key}"
|
||||||
|
# - "MINIO_SECRET_KEY=${AWS_SECRET_ACCESS_KEY-secret_key}"
|
||||||
|
# - "MINIO_HTTP_TRACE: /dev/stdout"
|
||||||
|
# ports:
|
||||||
|
# - "9000:9000"
|
||||||
|
# networks:
|
||||||
|
# - federation
|
||||||
|
# - internal
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
? internal
|
? internal
|
||||||
federation:
|
federation:
|
||||||
|
|
|
@ -93,13 +93,21 @@ http {
|
||||||
alias /protected/media/;
|
alias /protected/media/;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# this is an internal location that is used to serve
|
||||||
|
# audio files once correct permission / authentication
|
||||||
|
# has been checked on API side
|
||||||
location /_protected/media {
|
location /_protected/media {
|
||||||
# this is an internal location that is used to serve
|
|
||||||
# audio files once correct permission / authentication
|
|
||||||
# has been checked on API side
|
|
||||||
internal;
|
internal;
|
||||||
alias /protected/media;
|
alias /protected/media;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
# Comment the previous location and uncomment this one if you're storing
|
||||||
|
# media files in a S3 bucket
|
||||||
|
# location ~ /_protected/media/(.+) {
|
||||||
|
# internal;
|
||||||
|
# resolver 127.0.0.11;
|
||||||
|
# proxy_pass $1;
|
||||||
|
# }
|
||||||
|
|
||||||
location /_protected/music {
|
location /_protected/music {
|
||||||
# this is an internal location that is used to serve
|
# this is an internal location that is used to serve
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
Using external storages to store Funkwhale content
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
By default, Funkwhale will store user-uploaded and related media such as audio files,
|
||||||
|
transcoded files, avatars and album covers on a server directory.
|
||||||
|
|
||||||
|
However, for bigger instances or more complex deployment scenarios, you may want
|
||||||
|
to use distributed or external storages.
|
||||||
|
|
||||||
|
S3 and S3-compatible servers
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This feature was released in Funkwhale 0.19 and is still considered experimental.
|
||||||
|
Please let us know if you see anything unusual while using it.
|
||||||
|
|
||||||
|
Funkwhale supports storing media files Amazon S3 and compatible implementations such as Minio or Wasabi.
|
||||||
|
|
||||||
|
In this scenario, the content itself is stored in the S3 bucket. Non-sensitive media such as
|
||||||
|
album covers or user avatars are served directly from the bucket. However, audio files
|
||||||
|
are still served by the reverse proxy, to enforce proper authentication.
|
||||||
|
|
||||||
|
To enable S3 on Funkwhale, add the following environment variables::
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_STORAGE_BUCKET_NAME=
|
||||||
|
# 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
|
||||||
|
# AWS_LOCATION=
|
||||||
|
|
||||||
|
# If you use a S3-compatible storage such as minio, set the following variable
|
||||||
|
# the full URL to the storage server. Example:
|
||||||
|
# AWS_S3_ENDPOINT_URL=https://minio.mydomain.com
|
||||||
|
# AWS_S3_ENDPOINT_URL=
|
||||||
|
|
||||||
|
Then, edit your nginx configuration. On docker setups, the file is located at ``/srv/funkwhale/nginx/funkwhale.template``,
|
||||||
|
and at ``/etc/nginx/sites-available/funkwhale.template`` on non-docker setups.
|
||||||
|
|
||||||
|
Replace the ``location /_protected/media`` block with the following::
|
||||||
|
|
||||||
|
location ~ /_protected/media/(.+) {
|
||||||
|
internal;
|
||||||
|
proxy_pass $1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Then restart Funkwhale and nginx.
|
||||||
|
|
||||||
|
From now on, media files will be stored on the S3 bucket you configured. If you already
|
||||||
|
had media files before configuring the S3 bucket, you also have to move those on the bucket
|
||||||
|
by hand (which is outside the scope of this guide).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
At the moment, we do not support S3 when using Apache as a reverse proxy.
|
||||||
|
|
||||||
|
|
||||||
|
Securing your S3 bucket
|
||||||
|
***********************
|
||||||
|
|
||||||
|
It's important to ensure your the root of your bucket doesn't list its content,
|
||||||
|
which is the default on many S3 servers. Otherwise, anyone could find out the true
|
||||||
|
URLs of your audio files and bypass authentication.
|
||||||
|
|
||||||
|
To avoid that, you can set the following policy on your bucket::
|
||||||
|
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"s3:GetObject"
|
||||||
|
],
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::<yourbucketname>/*"
|
||||||
|
],
|
||||||
|
"Sid": "Public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
If you are using ``awscli``, you can store this policy in a ``/tmp/policy`` file, and
|
||||||
|
apply it using the following command::
|
||||||
|
|
||||||
|
aws s3api put-bucket-policy --bucket <yourbucketname> --policy file:///tmp/policy
|
|
@ -14,6 +14,7 @@ Setup Guides
|
||||||
../installation/index
|
../installation/index
|
||||||
configuration
|
configuration
|
||||||
importing-music
|
importing-music
|
||||||
|
external-storages
|
||||||
|
|
||||||
Administration
|
Administration
|
||||||
--------------
|
--------------
|
||||||
|
|
Loading…
Reference in New Issue