Funkwhale¶
Installing and Creating the Database¶
First, drop into a sudo shell and install PostgreSQL:
sudo -s
apt update && apt install postgresql postgresql-contrib
Open a database shell:
sudo -u postgres psql
And create the database:
CREATE DATABASE "funkwhale"
WITH ENCODING 'utf8';
CREATE USER funkwhale;
GRANT ALL PRIVILEGES ON DATABASE funkwhale TO funkwhale;
Create the funkwhale system user:
useradd -r -s /usr/sbin/nologin -d /var/www/audio.example.com -m funkwhale
Enable some extensions:
sudo -u postgres psql funkwhale -c 'CREATE EXTENSION "unaccent";'
sudo -u postgres psql funkwhale -c 'CREATE EXTENSION "citext";'
Redis¶
This section assumes that you've already set Redis up during the RSpamd tutorial.
Exit the funkwhale login session:
exit
Generate a password:
pwgen -Bvsc 64 1
This results in something like the following:
rtVh7zMTFHWm7HWVFXfcgNLXXtjC9scTt4mHVwjnrvqwsJ9qv9CjdwcCWtpqNVbL
Create a config:
nano /etc/redis/redis-funkwhale.conf
Enter the following, using the password you generated above:
include /etc/redis/common.conf
# Listen on localhost
bind 127.0.0.1 ::1
port 6383
unixsocket /var/run/redis-funkwhale/redis-server.sock
unixsocketperm 700
daemonize yes
supervised systemd
pidfile /var/run/redis-funkwhale/redis-server.pid
loglevel notice
logfile /var/log/redis/redis-funkwhale.log
dbfilename dump-funkwhale.rdb
requirepass <enter password here>
# maxmemory <bytes>
# maxmemory-policy noeviction
# maxmemory-samples 5
Change permissions on the configuration files:
chown -Rc redis:redis /etc/redis
Enable and start the services
systemctl enable redis-server@funkwhale
systemctl start redis-server@funkwhale.service
Check that the service are running with:
ps aux | grep redis
or
service redis-server@funkwhale status
Installing Funkwhale¶
Install dependencies for Funkwhale:
apt install curl python3-pip python3-venv git unzip libldap2-dev libsasl2-dev gettext-base zlib1g-dev libffi-dev libssl-dev
apt install build-essential ffmpeg libjpeg-dev libmagic-dev libpq-dev postgresql-client python3-dev make
Change to the funkwhale directory and login as the funkwhale user:
cd /var/www/audio.example.com
sudo -u funkwhale -H bash
Create the directory structure:
mkdir -p config api data/static data/media data/music front
Download the funkwhale API:
curl -L -o "api-1.0.1.zip" "https://dev.funkwhale.audio/funkwhale/funkwhale/-/jobs/artifacts/1.0.1/download?job=build_api"
unzip "api-1.0.1.zip" -d extracted
mv extracted/api/* api/
rm -rf extracted
Download the frontend files:
curl -L -o "front-1.0.1.zip" "https://dev.funkwhale.audio/funkwhale/funkwhale/-/jobs/artifacts/1.0.1/download?job=build_front"
unzip "front-1.0.1.zip" -d extracted
mv extracted/front .
ls
Make sure you're still in the base directory:
cd /var/www/audio.example.com
To avoid collisions with other software on your system, Python dependencies will be installed in a dedicated virtualenv.
First, create the virtualenv:
python3 -m venv /var/www/audio.example.com/virtualenv
In the rest of this guide, we’ll need to activate this environment to ensure dependencies are installed within it, and not directly on your host system.
This is done with the following command:
source /var/www/audio.example.com/virtualenv/bin/activate
Finally, install the python dependencies:
pip install wheel
pip install -r api/requirements.txt
Important
Further commands involving python should always be run after you activated the virtualenv, as described earlier, otherwise those commands will raise errors
Environment File¶
Download the sample environment file:
curl -L -o config/.env "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/master/deploy/env.prod.sample"
Generate a secret key for Django:
openssl rand -base64 45
Reduce permissions on the .env file since it contains sensitive data. You can then edit the file: the file is heavily commented, and the most relevant configuration options are mentioned at the top of the file.
chmod 600 /var/www/audio.example.com/config/.env
nano /var/www/audio.example.com/config/.env
Paste the secret key you generated earlier at the entry DJANGO_SECRET_KEY and populate the DATABASE_URL and CACHE_URL values based on how you configured your PostgreSQL and Redis servers.
An example of a .env file follows:
# If you have any doubts about what a setting does,
# check https://docs.funkwhale.audio/configuration.html#configuration-reference
# If you're tweaking this file from the template, ensure you edit at least the
# following variables:
# - DJANGO_SECRET_KEY
# - FUNKWHALE_HOSTNAME
# - EMAIL_CONFIG and DEFAULT_FROM_EMAIL if you plan to send emails)
# On non-docker setup **only**, you'll also have to tweak/uncomment those variables:
# - DATABASE_URL
# - CACHE_URL
#
# You **don't** need to update those variables on pure docker setups.
#
# Additional options you may want to check:
# - MUSIC_DIRECTORY_PATH and MUSIC_DIRECTORY_SERVE_PATH if you plan to use
# in-place import
#
# Docker only
# -----------
# The tag of the image we should use
# (it will be interpolated in docker-compose file)
# You can comment or ignore this if you're not using docker
FUNKWHALE_VERSION=latest
# End of Docker-only configuration
# General configuration
# ---------------------
# Set this variables to bind the API server to another interface/port
# example: FUNKWHALE_API_IP=0.0.0.0
# example: FUNKWHALE_API_PORT=5678
FUNKWHALE_API_IP=127.0.0.1
FUNKWHALE_API_PORT=5678
# The number of web workers to start in parallel. Higher means you can handle
# more concurrent requests, but also leads to higher CPU/Memory usage
FUNKWHALE_WEB_WORKERS=1
# Replace this by the definitive, public domain you will use for
# your instance
FUNKWHALE_HOSTNAME=audio.example.com
FUNKWHALE_PROTOCOL=https
# Configure email sending using this variale
# By default, funkwhale will output emails sent to stdout
# here are a few examples for this setting
# EMAIL_CONFIG=consolemail:// # output emails to console (the default)
# EMAIL_CONFIG=dummymail:// # disable email sending completely
# On a production instance, you'll usually want to use an external SMTP server:
# 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
EMAIL_CONFIG=smtp+tls://user@example.com:your-email-password@mail.example.com:587
# The email address to use to send system emails.
DEFAULT_FROM_EMAIL=Funkwhale <noreply@example.com>
# 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
REVERSE_PROXY_TYPE=nginx
# API/Django configuration
# Database configuration
# Examples:
# DATABASE_URL=postgresql://<user>:<password>@<host>:<port>/<database>
# DATABASE_URL=postgresql://funkwhale:passw0rd@localhost:5432/funkwhale_database
# Use the next one if you followed Debian installation guide
DATABASE_URL=postgresql://funkwhale@:5432/funkwhale
# Cache configuration
# Examples:
# CACHE_URL=redis://<host>:<port>/<database>
# CACHE_URL=redis://localhost:6379/0c
# With a password:
# CACHE_URL=redis://:password@localhost:6379/0
# (the extra semicolon is important)
# Use the next one if you followed Debian installation guide
#
CACHE_URL=redis://:<your-redis-password>@127.0.0.1:6383/0
#
# If you want to use Redis over unix sockets, you'll actually need two variables:
# For the cache part:
# CACHE_URL=redis:///run/redis/redis.sock?db=0
# For the Celery/asynchronous tasks part:
# CELERY_BROKER_URL=redis+socket:///run/redis/redis.sock?virtual_host=0
# Number of worker processes to execute. Defaults to 0, in which case it uses your number of CPUs
# Celery workers handle background tasks (such file imports or federation
# messaging). The more processes a worker gets, the more tasks
# can be processed in parallel. However, more processes also means
# a bigger memory footprint.
CELERYD_CONCURRENCY=0
# Where media files (such as album covers or audio tracks) should be stored
# on your system?
# (Ensure this directory actually exists)
MEDIA_ROOT=/var/www/audio.example.com/data/media
# Where static files (such as API css or icons) should be compiled
# on your system?
# (Ensure this directory actually exists)
STATIC_ROOT=/var/www/audio.example.com/data/static
MEDIA_URL=https://audio.example.com/media/
# which settings module should django use?
# You don't have to touch this unless you really know what you're doing
DJANGO_SETTINGS_MODULE=config.settings.production
# Generate one using `openssl rand -base64 45`, for example
DJANGO_SECRET_KEY=<key that you previously generated>
# You don't have to edit this, but you can put the admin on another URL if you
# want to
# DJANGO_ADMIN_URL=^api/admin/
# Sentry/Raven error reporting (server side)
# Enable Raven if you want to help improve funkwhale by
# automatically sending error reports our Sentry instance.
# This will help us detect and correct bugs
RAVEN_ENABLED=false
RAVEN_DSN=https://876463265989857398579879875986374896263@sentry.eliotberriot.com/5
# In-place import settings
# You can safely leave those settings uncommented if you don't plan to use
# in place imports.
# Typical docker setup:
# MUSIC_DIRECTORY_PATH=/music # docker-only
# MUSIC_DIRECTORY_SERVE_PATH=/srv/funkwhale/data/music
# Typical non-docker setup:
# MUSIC_DIRECTORY_PATH=/srv/funkwhale/data/music
# # MUSIC_DIRECTORY_SERVE_PATH= # stays commented, not needed
MUSIC_DIRECTORY_PATH=/var/www/audio.example.com/data/music
MUSIC_DIRECTORY_SERVE_PATH=/var/www/audio.example.com/data/music
# LDAP settings
# Use the following options to allow authentication on your Funkwhale instance
# using a LDAP directory.
# Have a look at https://docs.funkwhale.audio/installation/ldap.html for
# detailed instructions.
# LDAP_ENABLED=False
# LDAP_SERVER_URI=ldap://your.server:389
# LDAP_BIND_DN=cn=admin,dc=domain,dc=com
# LDAP_BIND_PASSWORD=bindpassword
# LDAP_SEARCH_FILTER=(|(cn={0})(mail={0}))
# LDAP_START_TLS=False
# LDAP_ROOT_DN=dc=domain,dc=com
FUNKWHALE_FRONTEND_PATH=/var/www/audio.example.com/front/dist
# Nginx related configuration
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=
# If you want to serve media directly from your S3 bucket rather than through a proxy,
# set this to true
# PROXY_MEDIA=false
# If you are using Amazon S3 to serve media directly, you will need to specify your region
# name in order to access files. Example:
# AWS_S3_REGION_NAME=eu-west-2
# AWS_S3_REGION_NAME=
# If you are using Amazon S3, use this setting to configure how long generated URLs should stay
# valid. The default value is 3600 (60 minutes). The maximum accepted value is 604800 (7 days)
# AWS_QUERYSTRING_EXPIRE=
Database setup¶
You should now be able to import the initial database structure. First activate the virtual environment, making sure you're still logged in as the funkwhale user, and then run:
~/virtualenv/bin/python api/manage.py migrate
Note
You can safely execute this command any time you want, this will only run unapplied migrations.
Warning
You may sometimes get the following warning while applying migrations:
Your models have changes that are not yet reflected in a migration, and so won't be applied.
Create an admin account¶
You can then create your first user account:
~/virtualenv/bin/python api/manage.py createsuperuser
If you ever want to change a user’s password from the command line, just run:
~/virtualenv/bin/python api/manage.py changepassword <user>
Collect static files¶
Static files are the static assets used by the API server (icon PNGs, CSS, etc.). We need to collect them explicitly, so they can be served by the webserver:
~/virtualenv/bin/python api/manage.py collectstatic
This should populate the directory you choose for the STATIC_ROOT variable in your .env file.
Certificates¶
Exit back to the root sudo shell.
Issue the RSA certificates with:
~/.acme.sh/acme.sh --issue --dns dns_cloudns -d audio.example.com --keylength 4096 --key-file /etc/letsencrypt/rsa-certs/audio.example.com/privkey.pem --ca-file /etc/letsencrypt/rsa-certs/audio.example.com/chain.pem --cert-file /etc/letsencrypt/rsa-certs/audio.example.com/cert.pem --fullchain-file /etc/letsencrypt/rsa-certs/audio.example.com/fullchain.pem --pre-hook "mkdir -p /etc/letsencrypt/rsa-certs/audio.example.com" --post-hook "find /etc/letsencrypt/rsa-certs/audio.example.com/ -name '*.pem' -type f -exec chmod 600 {} \;" --renew-hook "find /etc/letsencrypt/rsa-certs/audio.example.com/ -name '*.pem' -type f -exec chmod 600 {} \; -exec service nginx reload \;"
and ECC certificates with:
~/.acme.sh/acme.sh --issue --dns dns_cloudns -d audio.example.com --keylength ec-384 --key-file /etc/letsencrypt/ecc-certs/audio.example.com/privkey.pem --ca-file /etc/letsencrypt/ecc-certs/audio.example.com/chain.pem --cert-file /etc/letsencrypt/ecc-certs/audio.example.com/cert.pem --fullchain-file /etc/letsencrypt/ecc-certs/audio.example.com/fullchain.pem --pre-hook "mkdir -p /etc/letsencrypt/ecc-certs/audio.example.com" --post-hook "find /etc/letsencrypt/ecc-certs/audio.example.com/ -name '*.pem' -type f -exec chmod 600 {} \;" --renew-hook "find /etc/letsencrypt/ecc-certs/audio.example.com/ -name '*.pem' -type f -exec chmod 600 {} \; -exec service nginx reload \;"
Nginx Reverse Proxy¶
Create the config:
nano /etc/nginx/sites-available/audio.example.com
Enter the following:
# This file was generated from funkwhale.template
upstream funkwhale-api {
# depending on your setup, you may want to update this
server 127.0.0.1:5678;
}
server {
listen 80;
listen [::]:80;
# update this to match your instance name
server_name audio.example.com;
# useful for Let's Encrypt
location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}
# required for websocket support
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name audio.example.com;
ssl_certificate /etc/letsencrypt/rsa-certs/audio.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/rsa-certs/audio.example.com/privkey.pem;
ssl_certificate /etc/letsencrypt/ecc-certs/audio.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/ecc-certs/audio.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/ecc-certs/audio.example.com/chain.pem;
include /etc/nginx/custom-config/ssl.conf;
# HSTS
add_header Strict-Transport-Security "max-age=31536000";
# Nginx Bad Bot Blocker Includes
include /etc/nginx/bots.d/ddos.conf;
include /etc/nginx/bots.d/blockbots.conf;
if ($allowed_country = no) {
set $blockreason '[geo_blocked]';
return 403;
}
if ( $bad_querystring !~* "\[OK\]|\[bad_querystring_rule_33\]" ) {
set $blockreason $bad_querystring;
return 403;
}
if ( $bad_request !~* "\[OK\]|\[bad_request_rule_6\]" ) {
set $blockreason $bad_request;
return 403;
}
if ( $bad_request_method !~* "[\OK\]" ) {
set $blockreason $bad_request_method;
return 403;
}
# If you are using S3 to host your files, remember to add your S3 URL to the
# media-src and img-src headers (e.g. img-src 'self' https://<your-S3-URL> data:)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:";
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin";
root /var/www/audio.example.com/front/dist;
# compression settings
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/javascript
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
# end of compression settings
location / {
include /etc/nginx/custom-config/funkwhale_proxy.conf;
# this is needed if you have file import via upload enabled
client_max_body_size 100M;
proxy_pass http://funkwhale-api/;
}
location /front/ {
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Service-Worker-Allowed "/";
add_header X-Frame-Options "SAMEORIGIN";
alias /var/www/audio.example.com/front/dist/;
expires 30d;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
location /front/embed.html {
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header X-Frame-Options "ALLOW";
alias /var/www/audio.example.com/front/dist/embed.html;
expires 30d;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
location /federation/ {
include /etc/nginx/custom-config/funkwhale_proxy.conf;
proxy_pass http://funkwhale-api/federation/;
}
# You can comment this if you do not plan to use the Subsonic API
location /rest/ {
include /etc/nginx/custom-config/funkwhale_proxy.conf;
proxy_pass http://funkwhale-api/api/subsonic/rest/;
}
location /.well-known/ {
include /etc/nginx/custom-config/funkwhale_proxy.conf;
proxy_pass http://funkwhale-api/.well-known/;
}
location /media/ {
alias /var/www/audio.example.com/data/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;
alias /var/www/audio.example.com/data/media;
}
# Comment the previous location and uncomment this one if you're storing
# media files in a S3 bucket
# location ~ /_protected/media/(.+) {
# internal;
# # Needed to ensure DSub auth isn't forwarded to S3/Minio, see #932
# proxy_set_header Authorization "";
# proxy_pass $1;
# }
location /_protected/music {
# this is an internal location that is used to serve
# audio files once correct permission / authentication
# has been checked on API side
# Set this to the same value as your MUSIC_DIRECTORY_PATH setting
internal;
alias /var/www/audio.example.com/data/music;
}
location /staticfiles/ {
# django static files
alias /var/www/audio.example.com/data/static/;
}
}
Create the proxy config:
nano /etc/nginx/custom-config/funkwhale_proxy.conf
Enter the following:
# global proxy conf
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_redirect off;
# websocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
Enable the site and reload:
ln -s /etc/nginx/sites-available/audio.example.com /etc/nginx/sites-enabled/
service nginx reload
Systemd Services¶
Create the target service file:
nano /etc/systemd/system/funkwhale.target
Enter the following:
[Unit]
Description=Funkwhale
Wants=funkwhale-server.service funkwhale-worker.service funkwhale-beat.service
Create the Funkwhale Server service:
nano /etc/systemd/system/funkwhale-server.service
Enter the following:
[Unit]
Description=Funkwhale application server
After=redis.service postgresql.service
PartOf=funkwhale.target
[Service]
User=funkwhale
# adapt this depending on the path of your funkwhale installation
WorkingDirectory=/var/www/audio.example.com/api
EnvironmentFile=/var/www/audio.example.com/config/.env
ExecStart=/var/www/audio.example.com/virtualenv/bin/gunicorn config.asgi:application -w ${FUNKWHALE_WEB_WORKERS} -k uvicorn.workers.UvicornWorker -b ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}
[Install]
WantedBy=multi-user.target
Create the Funkwhale Worker service:
nano /etc/systemd/system/funkwhale-worker.service
Enter the following:
[Unit]
Description=Funkwhale celery worker
After=redis.service postgresql.service
PartOf=funkwhale.target
[Service]
User=funkwhale
# adapt this depending on the path of your funkwhale installation
WorkingDirectory=/var/www/audio.example.com/api
EnvironmentFile=/var/www/audio.example.com/config/.env
ExecStart=/var/www/audio.example.com/virtualenv/bin/celery -A funkwhale_api.taskapp worker -l INFO --concurrency=${CELERYD_CONCURRENCY}
[Install]
WantedBy=multi-user.target
Create the Funkwhale Beat service:
nano /etc/systemd/system/funkwhale-beat.service
Enter the following:
[Unit]
Description=Funkwhale celery beat process
After=redis.service postgresql.service
PartOf=funkwhale.target
[Service]
User=funkwhale
# adapt this depending on the path of your funkwhale installation
WorkingDirectory=/var/www/audio.example.com/api
EnvironmentFile=/var/www/audio.example.com/config/.env
ExecStart=/var/www/audio.example.com/virtualenv/bin/celery -A funkwhale_api.taskapp beat -l INFO
[Install]
WantedBy=multi-user.target
Reload Systemd:
systemctl daemon-reload
And start the services:
systemctl start funkwhale.target
To ensure all Funkwhale processes are started automatically after a reboot, run:
systemctl enable funkwhale-server
systemctl enable funkwhale-worker
systemctl enable funkwhale-beat
You can check the statuses of all processes like this:
systemctl status funkwhale-\*
Last.FM API¶
Create an account with Last.fm and create an API key at:
Make sure you make a note of the keys, they only display once.
Add the following to your .env file:
FUNKWHALE_PLUGINS=funkwhale_api.contrib.scrobbler
FUNKWHALE_PLUGIN_SCROBBLER_LASTFM_API_KEY=<your_api_key>
FUNKWHALE_PLUGIN_SCROBBLER_LASTFM_API_SECRET=<your_api_secret>
In the UI, go to /settings then manage plugins, and configure the scrobbler plugin for your user.
Apparmor¶
Create the apparmor profile for celery:
nano /etc/apparmor.d/funkwhale-celery
Enter the following:
#include <tunables/global>
profile funkwhale-celery /var/www/audio.example.com/virtualenv/bin/celery flags=(complain) {
#include <abstractions/base>
#include <abstractions/bash>
#include <abstractions/nameservice>
#include <abstractions/python>
#include <abstractions/user-tmp>
capability dac_read_search,
/etc/ldap/ldap.conf r,
/etc/lsb-release r,
/etc/magic r,
/etc/mime.types r,
/etc/python3.*/sitecustomize.py r,
/lib/x86_64-linux-gnu/ld-*.so mr,
/proc/loadavg r,
/run/postgresql/* rw,
/run/uuidd/request rw,
/usr/bin/dash mrix,
/usr/bin/python3.* rix,
/usr/bin/uname mrix,
/usr/sbin/ldconfig{,.real} mrix,
/var/www/audio.example.com/virtualenv/bin/celery r,
owner /dev/shm/* rwl,
owner /proc/*/fd/ r,
owner /var/www/audio.example.com/** r,
owner /var/www/audio.example.com/**/__pycache__/*.pyc{,.*} rw,
owner /var/www/audio.example.com/api/*.db rw,
owner /var/www/audio.example.com/api/celerybeat-schedule rwk,
owner /var/www/audio.example.com/api/celerybeat.pid rw,
owner /var/www/audio.example.com/data/media/** rwk,
owner /var/www/audio.example.com/virtualenv/lib/python3.*/**/*.so{,.*} mr,
}
Create the apparmor profile for gunicorn:
nano /etc/apparmor.d/funkwhale-gunicorn
Enter the following:
#include <tunables/global>
profile funkwhale-gunicorn /var/www/audio.example.com/virtualenv/bin/gunicorn flags=(complain) {
#include <abstractions/base>
#include <abstractions/bash>
#include <abstractions/nameservice>
#include <abstractions/openssl>
#include <abstractions/user-tmp>
capability dac_read_search,
/etc/ldap/ldap.conf r,
/etc/magic r,
/etc/mime.types r,
/etc/python3.*/sitecustomize.py r,
/lib/x86_64-linux-gnu/ld-*.so mr,
/run/postgresql/* rw,
/usr/bin/dash rix,
/usr/bin/python3.* ix,
/usr/bin/uname mrix,
/usr/sbin/ldconfig{,.real} mrix,
/var/www/audio.example.com/virtualenv/bin/gunicorn r,
/{usr/,}lib{,32,64}/** mr,
owner /proc/*/fd/ r,
owner /var/www/audio.example.com/** r,
owner /var/www/audio.example.com/data/media/** rwk,
owner /var/www/audio.example.com/virtualenv/lib/python3.*/**/*.so{,.*} mr,
}
You will also need to add the following two lines to your Nginx profile:
/var/www/audio.example.com/data/media/** r,
/var/www/audio.example.com/front/dist/** r,
/var/www/audio.example.com/data/static/admin/** r,
Use aa-logprof
to monitor and amend whilst using Funkwhale, and enforce with `aa-enforce`` once you're happy it's working.