Howto Mastodon
- Documentation : https://docs.joinmastodon.org/
- Administration : https://docs.joinmastodon.org/admin/
- Code : https://github.com/mastodon/mastodon
- Licence : AGPLv3
- Ansible : https://gitea.evolix.org/evolix/ansible-roles/src/branch/stable/webapps/mastodon
Mastodon est un réseau social libre et décentralisé, en alternative à X (anciennement Twitter).
Installation
Nous installons la version 4.5.8 sous Debian 12 (Bookworm).
Mastodon s’appuie sur Ruby, NodeJS, Yarn, Nginx, Redis et PostgreSQL.
On peut installer les dépendances pour Mastodon :
# apt install curl wget gnupg lsb-release ca-certificates nodejs imagemagick ffmpeg libvips-tools libpq-dev libxslt1-dev file git \
protobuf-compiler pkg-config autoconf bison build-essential libssl-dev libyaml-dev libreadline-dev zlib1g-dev libffi-dev \
libgdbm-dev nginx nodejs redis-server postgresql certbot python3-certbot-nginx libidn-dev libicu-dev libjemalloc-dev
Il faut activer corepack pour l’installation automatique de la bonne version de yarn.
# corepack enable
Compte UNIX
Créer un compte UNIX mastodon :
# adduser --disabled-login --gecos 'Mastodon App' mastodon
Note : Assurez-vous d’avoir
DIR_MODE=0750dans/etc/adduser.confpour créer le home en 750.
Ruby
Mastodon 4.5.x nécessite Ruby (3.4.7) qui doit préférablement être compilé avec jemalloc. On le met en place via rbenv :
# sudo -iu mastodon
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ cd ~/.rbenv && src/configure && make -C src
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ echo 'export RAILS_ENV="production"' >> ~/.bash_profile
$ source ~/.bash_profile
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd
$ TMPDIR=~/tmp MAKE_OPTS=-j$(nproc) RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.4.7
$ rbenv local 3.4.7
Note : On met la variable d’environnement
RAILS_ENVdans notre profile bash pour éviter de l’indiquer à chaque commande Ruby/Rails.
PostgreSQL
Mastodon utilise PostgreSQL. On utilise donc la version 15 de Debian 12 :
# apt install postgresql postgresql-client libpq-dev postgresql-contrib
Création de l’utilisateur PostgreSQL :
# sudo -u postgres createuser mastodon -d -P -R
Note : On donne les droits CREATEDB car Mastodon doit faire un DROP DATABASE puis CREATE DATABASE lors de l’installation…
Note : Pensez à conserver le mot de passe pour le mettre par la suite (ou pas si utilisation de l’authentification via ident).
Redis
Installation classique :
# apt install redis-server
Mastodon
On clone le repository et installe avec bundle et yarn :
# sudo -iu mastodon
$ git clone https://github.com/tootsuite/mastodon.git
$ cd mastodon
$ git checkout v4.5.8
$ bundle config deployment 'true'
$ bundle config without 'development test'
$ bundle install
$ yarn install
$ cp .env.production.sample .env.production
Attention, si vous avez un
/homeennoexec, il faudra le passer enexecpour les processus Ruby et NPM. Au risque d’obtenir ce genre d’erreurs :npm[2990]: Error: Compilation of µWebSockets has failed and there is no pre-compiled binary available for your system. Please install a > supported C++11 compiler and reinstall the module 'uws'. Failed at step EXEC spawning /home/mastodon/.rbenv/shims/bundle: Permission denied
Un assistant permet de paramétrer une nouvelle instance de Mastodon en intéractif :
$ bin/rails mastodon:setup
Ce petit programme va s’occuper de produire un fichier
.env.production valide, précompiler les assets et
initialiser la base de données. Si choisissez le mode interactif, vous
pouvez passer à la section « Unités systemd » plus bas lorsque vous
aurez terminé.
Il est aussi possible de le faire en mode manuel et dans ce cas on
peut éditer le fichier .env.production à sa convenance.
Voici un exemple :
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
DB_HOST=127.0.0.1
DB_USER=mastodon
DB_NAME=mastodon_production
DB_PASS=PASSWORD
DB_PORT=5432
LOCAL_DOMAIN=mastodon.example.com
LOCAL_HTTPS=true
EMAIL_DOMAIN_WHITELIST=example.com
DEFAULT_LOCALE=fr
SMTP_SERVER=127.0.0.1
SMTP_PORT=25
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=mastodon@mastodon.example.com
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
SMTP_AUTH_METHOD=none
SMTP_OPENSSL_VERIFY_MODE=none
Toujours pour le mode manuel, on génére des clés d’applications et webpush :
$ echo PAPERCLIP_SECRET=$(bundle exec rake secret) >> .env.production
$ echo SECRET_KEY_BASE=$(bundle exec rake secret) >> .env.production
$ echo OTP_SECRET=$(bundle exec rake secret) >> .env.production
$ bundle exec rake mastodon:webpush:generate_vapid_key >> .env.production
Initialisation de la base de données :
$ SAFETY_ASSURED=1 bundle exec rails db:setup
Précompilation des assets et ajustement des permissions pour le serveur Web (nginx) :
$ bundle exec rails assets:precompile
$ chmod -R u=rwX,g=rwX,o=rX /home/mastodon/mastodon/public
Unités systemd
Il y a trois unités systemd à mettre en place : web (puma), streaming et sidekiq. On peut reprendre les modèles proposés par le développeur ou s’en inspirer :
# cp /home/mastodon/mastodon/dist/mastodon-*.service /etc/systemd/system/
Il faut éditer les fichiers de configuration des trois unités,
notamment pour changer le chemin /home/mastodon/live en
/home/mastodon/mastodon/ :
mastodon-web.service
[Unit]
Description=mastodon-web
After=network.target
[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/mastodon
Environment="RAILS_ENV=production"
Environment="PORT=3000"
Environment="LD_PRELOAD=libjemalloc.so"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb
ExecReload=/bin/kill -SIGUSR1 $MAINPID
TimeoutSec=15
Restart=always
[Install]
WantedBy=multi-user.target
mastodon-sidekiq.service
[Unit]
Description=mastodon-sidekiq
After=network.target
[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/mastodon
Environment="RAILS_ENV=production"
Environment="DB_POOL=25"
Environment="MALLOC_ARENA_MAX=2"
Environment="LD_PRELOAD=libjemalloc.so"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 25
TimeoutSec=15
Restart=always
[Install]
WantedBy=multi-user.target
mastodon-streaming.service
[Unit]
Description=mastodon-streaming
After=network.target
[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/mastodon
Environment="NODE_ENV=production"
Environment="PORT=4000"
Environment="STREAMING_CLUSTER_NUM=1"
ExecStart=/usr/bin/node ./streaming
TimeoutSec=15
Restart=always
[Install]
WantedBy=multi-user.target
Note : les ports 3000 et 4000 ci-haut devront être changés s’il y a plus d’une instance sur le serveur. On pourra par exemple incrémenter de 1, donc 3001, 4001.
On active et on démarre les unités :
# systemctl enable mastodon-{web,sidekiq,streaming}
# systemctl start mastodon-{web,sidekiq,streaming}
Note : Depuis la version 4.0.x, il est aussi possible de configurer la durée de vie du cache (médias, etc.) depuis l’interface d’administration (sous Administration -> Paramètres du serveur -> Rétention du contenu). Vous devrez baisser les valeurs par défaut si le stockage est un enjeu sur votre serveur.
Nginx
On utilise Nginx :
# apt install nginx-full
Exemple de vhost proposé par le développeur (avec quelques ajustements nécessaires à ce Howto) :
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream backend {
server 127.0.0.1:3000 fail_timeout=0;
}
upstream streaming {
# Instruct nginx to send connections to the server with the least number of connections
# to ensure load is distributed evenly.
least_conn;
server 127.0.0.1:4000 fail_timeout=0;
# Uncomment these lines for load-balancing multiple instances of streaming for scaling,
# this assumes your running the streaming server on ports 4000, 4001, and 4002:
# server 127.0.0.1:4001 fail_timeout=0;
# server 127.0.0.1:4002 fail_timeout=0;
}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;
server {
listen 80;
listen [::]:80;
server_name mastodon.example.com;
root /home/mastodon/mastodon/public;
# Useful for Let's Encrypt
location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mastodon.example.com;
ssl_protocols TLSv1.2 TLSv1.3;
# You can use https://ssl-config.mozilla.org/ to generate your cipher set.
# We recommend their "Intermediate" level.
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# Uncomment these lines once you acquire a certificate:
# ssl_certificate /etc/letsencrypt/live/mastodon.example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/mastodon.example.com/privkey.pem;
keepalive_timeout 70;
sendfile on;
client_max_body_size 99m;
root /home/mastodon/mastodon/public;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml image/x-icon;
gzip_static on;
location / {
try_files $uri @proxy;
}
# If Docker is used for deployment and Rails serves static files,
# then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`.
location = /sw.js {
add_header Cache-Control "public, max-age=604800, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/assets/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/avatars/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/emoji/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/headers/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/packs/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/shortcuts/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/sounds/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/system/ {
add_header Cache-Control "public, max-age=2419200, immutable";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'none'; form-action 'none'";
try_files $uri =404;
}
location ^~ /api/v1/streaming {
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 Proxy "";
proxy_pass http://streaming;
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
tcp_nodelay on;
}
location @proxy {
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 Proxy "";
proxy_pass_header Server;
proxy_pass http://backend;
proxy_buffering on;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cache CACHE;
proxy_cache_valid 200 7d;
proxy_cache_valid 410 24h;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cached $upstream_cache_status;
tcp_nodelay on;
}
error_page 404 500 501 502 503 504 /500.html;
}
Note : La partie SSL/TLS n’est pas évoquée. À vous de faire le nécessaire avec un certificat Let’s Encrypt par exemple. N’oubliez donc pas de modifier les directives
ssl_dans le vhost.
Mises à jour
Le principe des mises à jour est basé sur un git pull et
un git checkout.
# sudo -iu mastodon
$ cd mastodon
$ git fetch
$ git checkout <VERSION>
$ bundle install
$ yarn install
$ bundle exec rails db:migrate
$ bundle exec rails assets:precompile
$ chmod -R u=rwX,g=rwX,o=rX /home/mastodon/mastodon/public/{assets,packs}
$ exit
# systemctl restart mastodon-*.service
Note : Ces commandes génériques ne sont parfois pas suffisantes. Vous devez systématiquement lire les notes de versions.
Configuration
Pour restreindre l’inscription à certains domaines de messagerie :
EMAIL_DOMAIN_WHITELIST=example.com|example.org
Utilisation
On peut utiliser différents clients :
- L’interface web via un navigateur (Firefox, etc.)
- Mastodon Android (appli officielle)
- Mastodon iOS (appli officielle)
- Applis tierces
- Applis via F-Droid
FAQ
À propos des logs
Mastodon n’a aucun fichier de logs. Les logs sont gérés via la sortie
standard et vont donc dans journald. On consultera les logs avec
journalctl -u mastodon-web (ou
mastodon-sidekiq ou mastodon-streaming).
Passer un utilisateur existant en admin
~/mastodon$ bin/tootctl accounts modify jdoe --role Admin
Congrats! jdoe is now an admin. \o/
Comment activer la recherche indexée (Elasticsearch ou Opensearch)
Il faut installer Elasticsearch. Voir notre HowtoElasticsearch
Une fois le logiciel Elasticsearch installé, vous devez ajouter un rôle et un utilisateur pour Mastodon :
# curl -X POST -u elastic:MOT_DE_PASSE_ES "localhost:9200/_security/role/mastodon_full_access?pretty" -H 'Content-Type: application/json' -d'
{
"cluster": ["monitor"],
"indices": [{
"names": ["*"],
"privileges": ["read", "monitor", "write", "manage"]
}]
}
'
# apg -n1 -m15 # Pour générer le mot de passe de l'utilisateur mastodon
# curl -X POST -u elastic:MOT_DE_PASSE_ES "localhost:9200/_security/user/mastodon?pretty" -H 'Content-Type: application/json' -d'
{
"password" : "MOT_DE_PASSE_POUR_MASTODON",
"roles" : ["mastodon_full_access"]
}
'
Il faut ensuite ajuster la configuration de Mastodon en éditant le
fichier .env.production :
ES_ENABLED=true
ES_HOST=localhost
ES_PORT=9200
ES_PRESET=single_node_cluster
# ES_USER=mastodon
# ES_PASS=MOT_DE_PASSE_POUR_MASTODON
Cette configuration est pertinente si on installe Elasticsearch sur le même serveur que Mastodon, ce qui est recommandé, sauf si vous gérer une grosse instance.
Finalement, il faut redémarrer deux des trois unités systemd de Mastodon et lancer l’indexation :
# systemctl restart mastodon-sidekiq
# systemctl reload mastodon-web
# sudo -iu mastodon
$ cd mastodon
$ bin/tootctl search deploy