Journal Héberger son instance Mastodon (seulement en profil individuel ?)

Posté par  . Licence CC By‑SA.
18
26
déc.
2022

Sommaire

Avec les différentes affaires qui secouent Twitter, comme beaucoup, j'ai souhaité sauter le pas vers Mastodon. Pour le meilleur (un réseau fédéré) et pour le pire (un réseau fédéré).

J'ai eu un peu (beaucoup) de mal avec la documentation technique de l'outil, alors je me suis dit qu'un article francophone sur ce sujet ne serait pas de trop. Si le sous-jacent protocolaire vous intéresse, je vous invite à lire mon article sur ActivityPub.

Bien que très mis en avant ces dernières semaines, hélas, Mastodon n'est pas exempt des problématiques de vie privée, de sécurité, de censure ou simplement d'administrateurs peu scrupuleux. Du financement des instances, de leur gouvernance et plus généralement, de la gouvernance au sens large de tout la fédération qui gravite autour du Fediverse.

Au choix d'avoir un compte sur une instance Mastodon connue, j'ai préféré mon propre hébergement. Cela n'est pas sans conséquence pour moi et indirectement pour toute la communauté, malgré l'aspect minuscule de ma goutte d'eau - voir la conclusion.

Je vous propose ici une version simplifiée d'une "vraie" version de production (notamment l'usage Certbot, d'avoir des réseaux internes distincts pour la pile de conteneurs de Docker Compose, etc.). L'idée est de se mettre le pied à l'étrier et de chevaucher un énorme mammouth laineux…

Pré-requis

Vous trouverez un fichier docker-compose.yml et un exemple de fichier d'environnement .env.production.sample à la racine du dépôt Github de Mastodon.

Attention : ce dernier n'est pas directement "opérationnel" pour un usage en production (notamment parce que les bases ne sont pas instanciées). De plus pour notre cas, nous ne produirons pas localement les images (nous utiliserons celles en lignes tootsuite).

Si vous êtes en mode "hors ligne" durant l'installation, pensez à télécharger les fichiers avec le combo docker pull / docker export.

Voici les images utilisées durant l'installation (ma version exacte entre parenthèse, mais cela devrait fonctionner en _lastest₎, provenant de docker.io :
- postgres:14-alpine (:14.6) ;
- redis:7-alpine ;
- tootsuite/mastodon (:v4.0.2) ;
- adminer (image optionnelle, si vous souhaitez accéder à vos bases de données en mode Web).

J'ai gardé Docker Compose pour l'exploitation des images - mais rien d'impossible sur un MicroK8s ou tout autre Kx par exemple. Idem pour l'usage de volumes.

J'ai écrit ce tutoriel depuis ma machine de dév (une Ubuntu 22.04) dotée de Docker mais le propos vaut pour toutes les distributions générales. Pensez à Podman pour un usage de production et des comptes sans privilège système / accès SSH.

Partie 1 : installation de la base de données

Je considère que vous êtes dans votre dossier de travail (~/Developpement/mastodon/article pour ma part).

Nous allons initialiser une base Postgres et faire le nécessaire pour que Mastodon soit en capacité de créer sa base durant sa phase d’installation. Comme nous utiliserons dans l'exemple un point de montage, créez le dossier nécessaire :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ mkdir ./postgres14

Pour éviter des mots de passes qui se baladent dans l'historique Bash, mettons cela dans une variable :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ read -s mastodon_mdp # taper le mot de passe suivi de la touche entrée ; dans mon cas "3f5z8BEByTf5"
julien@julien-Vostro-7580:~/Developpement/mastodon/article$ echo $mastodon_mdp
3f5z8BEByTf5

Créez un réseau Docker pour les prochaines étapes :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ docker network create mastodon
98ed9d69e697abc1990c8a4d3319120fc9fb58f4f6be1e18bc87a391f9a7ce2b

Puis lancez un conteneur jetable Postgres :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ docker run --rm --network=mastodon --name=mastodon_db -d -v "`pwd`/postgres14:/var/lib/postgresql/data" -e "POSTGRES_USER=mastodon" -e "POSTGRES_PASSWORD=$mastodon_mdp" postgres:14-alpine
d4e1d6b7be250dc56dda4a896d1b980e9b39234c93e2470e1ac23dff60af9984

Maintenant nous allons corriger la base créée avec le nom d'utilisateur (le comportement par défaut du conteneur). Mais Postgres ne nous autorise pas sa suppression si elle est connectée (cas par défaut) :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ docker run --rm --network=mastodon -it postgres:14-alpine bash
83f69d649625:/# psql -h mastodon_db --user mastodon 
Password for user mastodon: 
psql (14.6)
Type "help" for help.

mastodon=# select 1+1;
 ?column? 
----------
        2
(1 row)

mastodon=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges   
-----------+----------+----------+------------+------------+-----------------------
 mastodon  | mastodon | UTF8     | en_US.utf8 | en_US.utf8 | 
 postgres  | mastodon | UTF8     | en_US.utf8 | en_US.utf8 | 
 template0 | mastodon | UTF8     | en_US.utf8 | en_US.utf8 | =c/mastodon          +
           |          |          |            |            | mastodon=CTc/mastodon
 template1 | mastodon | UTF8     | en_US.utf8 | en_US.utf8 | =c/mastodon          +
           |          |          |            |            | mastodon=CTc/mastodon
(4 rows)

mastodon=# drop database mastodon;
ERROR:  cannot drop the currently open database
mastodon=# \c postgres 
You are now connected to database "postgres" as user "mastodon".
postgres=# drop database mastodon;
DROP DATABASE
postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges   
-----------+----------+----------+------------+------------+-----------------------
 postgres  | mastodon | UTF8     | en_US.utf8 | en_US.utf8 | 
 template0 | mastodon | UTF8     | en_US.utf8 | en_US.utf8 | =c/mastodon          +
           |          |          |            |            | mastodon=CTc/mastodon
 template1 | mastodon | UTF8     | en_US.utf8 | en_US.utf8 | =c/mastodon          +
           |          |          |            |            | mastodon=CTc/mastodon
(3 rows)

mastodon=# ^D\q
83f69d649625:/# 
exit

Vous pouvez désormais lancer la création de votre base de données Mastodon avec un autre conteneur jetable :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ docker run --rm --network=mastodon tootsuite/mastodon bash -c "DB_HOST=mastodon_db DB_USER=mastodon DB_NAME=mastodon DB_PASS=$mastodon_mdp DB_PORT=5432 SECRET_KEY_BASE=JL89YrRmt2c3 OTP_SECRET=BQy6Qivd96G2 RAILS_ENV=production bundle exec rails db:setup"
Created database 'mastodon'

Notre base est prête dans le dossier local. Arrêtons le conteneur Postgres ainsi que le réseau local :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ docker stop mastodon_db
mastodon_db
julien@julien-Vostro-7580:~/Developpement/mastodon/article$ docker network rm mastodon
mastodon

Partie 2 : création du Docker Compose final

Mastodon requiert du TLS pour l'usage du Web. Créez tout d'abord un certificat et sa clé privée avec OpenSSL :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout ./cert-mastodon.key -out ./cert-mastodon.crt
...+...+....+...+.....+.......+...+...++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++(...)
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:fr
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:mastodon.local
Email Address []:admin@mastodon.local

Ajouter maintenant un fichier nginx.conf, en prenant garde aux valeurs utilisées dans "add_header Content-Security-Policy" : tout doit être aligné sur ce que vous allez utiliser (mastodon.local dans notre cas).

user  nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;
events {
  worker_connections  1024;
}
http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;
  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
  access_log  /var/log/nginx/access.log  main;
  sendfile        on;
  #tcp_nopush     on;
  keepalive_timeout  65;
  #gzip  on;
  server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    ssl_certificate /etc/ssl/certs/mastodon.crt;
    ssl_certificate_key /etc/ssl/private/mastodon.key;
    location / {
      tcp_nodelay on;
      proxy_set_header Host "mastodon.local";
      proxy_set_header Origin "";
      proxy_set_header Referer "";
      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 https;
      proxy_pass_header Server;
      proxy_pass http://web:3000;
      proxy_buffering off;
      proxy_redirect off;
      proxy_http_version 1.1;
      add_header Strict-Transport-Security "max-age=31536000";
      add_header Content-Security-Policy "base-uri 'none'; default-src 'none'; frame-ancestors 'none'; font-src 'self' http://mastodon.local; img-src 'self' http: data: blob: http://mastodon.local; style-src 'self' http://mastodon.local 'nonce-5AF868cpzAN4XYtUIsLI2A=='; media-src 'self' http: data: http://mastodon.local; frame-src 'self' http:; manifest-src 'self' http://mastodon.local; form-action 'self'; connect-src 'self' data: blob: http://mastodon.local http://mastodon.local wss://mastodon.local; script-src 'self' http://mastodon.local 'wasm-unsafe-eval'; child-src 'self' blob: http://mastodon.local; worker-src 'self' blob: http://mastodon.local";
    }
  }
}

Maintenant, il s'agit d'avoir un fichier .env.production qui contiendra les valeurs partagées entre les conteneurs (ne faites pas ça dans une vraie production et éclatez votre fichiers en plusieurs !) :

LOCAL_DOMAIN=mastodon.local

REDIS_HOST=redis
REDIS_PORT=6379

DB_HOST=db
POSTGRES_USER=mastodon
DB_USER=mastodon
DB_NAME=mastodon
POSTGRES_PASSWORD=3f5z8BEByTf5 # générez un mot de passe fort 
DB_PASS=3f5z8BEByTf5 # le même que celui de "POSTGRES_PASSWORD"
DB_PORT=5432

SECRET_KEY_BASE=JL89YrRmt2c3 # générez un mot de passe fort 
OTP_SECRET=BQy6Qivd96G2 # générez un mot de passe fort 

VAPID_PRIVATE_KEY=62S8wFzazA6K # générez un mot de passe fort 
VAPID_PUBLIC_KEY=8h3aiGB8NrW5 # générez un mot de passe fort 

SMTP_SERVER=
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=notifications@example.com

S3_ENABLED=false

IP_RETENTION_PERIOD=31556952
SESSION_RETENTION_PERIOD=31556952

Il ne nous reste plus qu'à créer le fichier docker-compose.yml. Notez le conteneur sidekiq qui fera office de superviseur des traitements. La partie Web de instance peut fonctionner sans… mais vous n'auriez alors qu'aucune mise à jour d'aucune sorte avec l'extérieur (ni même l'ajout de vos abonnements) !

version: '3'
services:

  frontal: 
    container_name: frontal
    image: nginx
    depends_on:
      - web
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./mastodon.crt:/etc/ssl/certs/mastodon.crt
      - ./mastodon.key:/etc/ssl/private/mastodon.key
    ports:
      - 443:443

  db:
    container_name: db
    image: postgres:14-alpine
    shm_size: 256mb
    env_file: .env.production
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'postgres']
    volumes:
      - ./postgres14:/var/lib/postgresql/data

  redis:
    container_name: redis
    image: redis:7-alpine
    env_file: .env.production
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
    volumes:
      - ./redis:/data

  web:
    container_name: web
    image: tootsuite/mastodon
    env_file: .env.production
    command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    depends_on:
      - db
      - redis
    healthcheck:
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
    volumes:
      - ./mastodon:/mastodon/public/system

  sidekiq:
    container_name: sidekiq
    image: tootsuite/mastodon
    env_file: .env.production
    command: bundle exec sidekiq
    depends_on:
      - web
    volumes:
      - ./mastodon:/mastodon/public/system
    healthcheck:
      test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]

Votre pile est désormais prête, lancez-là :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ docker compose up -d 
[+] Running 6/6
 ⠿ Network article_default  Created                                                                                               0.0s
 ⠿ Container redis          Started                                                                                               0.8s
 ⠿ Container db             Started                                                                                               0.9s
 ⠿ Container web            Started                                                                                               1.3s
 ⠿ Container sidekiq        Started                                                                                               1.7s
 ⠿ Container frontal        Started                                                                                               1.9s

Partie 3 : compte d'administration et configuration applicative

Nous allons modifier notre fichier /etc/hosts pour ajouter la résolution DNS fixe suivante : 127.0.0.1 mastodon.local.

Vous pouvez désormais accéder à l'interface d'accueil : https://mastodon.local, bravo !

… Mais il reste la problématique du premier compte, qui devra aussi être celui "propriétaire" de l'instance (un administrateur est davantage "limité" en droits qu'un propriétaire d'instance). C'est assez simple :

julien@julien-Vostro-7580:~/Developpement/mastodon/article$ docker exec -u root web bash -c "RAILS_ENV=production bin/tootctl accounts create nothus --email julien.garderon@gmail.com --confirmed --force --role Owner"
OK
New password: c0f372ee29bf5f693f5a53c28013ddcd

Voilà, votre instance est prête à être utilisée.

Conclusions

Après plusieurs jours d'usage, rien de notable à signaler. J'ai rajouté quelques coups de tournevis par-ci par-là pour optimiser le tout. L'occupation mémoire est peu importante malgré la taille modeste de ma machine (à peine 1 Go de RAM pour l'ensemble des conteneurs en usage et moins d'1 vCPU au total). Je prépare par ailleurs un script "RSS vers Mastodon" pour retrouver sur mon instance, les sites habituels que je visite sous le format de profils indépendants (bots) ; j'aurais peut-être l'occasion d'en parler ici.

Évidemment tout n'est pas rose. D'abord sur la taille occupée sur le disque : je compte pour mon seul compte et moins d'une centaine d'abonnements, une sauvegarde (non-compressée) qui progresse d'1 Mo par jour. C'est énorme (imaginez une centaine de profils avec une centaine d’abonnements en moyenne…). En prévision d'une utilisation plus poussée, j'ai rajouté 100 Go sur mon LV /var - je vous conseille donc de prévoir "large".

Autre point noir : mon instance est nouvelle, ne pèse rien et je perds donc une "fonctionnalité" de synchronisation plus efficaces via le principe des relais. On n’y pense pas immédiatement : vous pouvez demander à d'autres de devenir un relais (bidirectionnel) auprès d'autres serveurs, pour faciliter la diffusion des inbox des utilisateurs. Pour l'instant aucun n'a répondu positivement (https://oc.todon.fr ; https://piaille.fr ; https://framapiaf.org ; https://mastodon.social ; https://h4.io).

Cela se comprend : une instance à 1 utilisateur ne pèse rien en public potentiel et devenir un relais dans les deux sens pour une instance Mastodon exposée, est une charge constante. Si chaque instance est à utilisateur unique, la charge totale est colossale si votre propre instance devient un relais indirect pour des centaines / milliers d'autres instances individuelles (plutôt que quelques dizaines ou centaines contenant des centaines ou milliers de profils). Cette "charge de l’unité"" vaut alors donc bien pour toute la communauté.

Ne pas être relais ne veut pas dire pour autant que vous êtes coupé de la fédération ActivityPub / Mastodon : simplement, vous devez récupérer chaque profil que vous suivez individuellement, plutôt qu'avoir à rythme régulier des échanges en masse. Au niveau de Sidekiq, la charge se ressent rapidement si on démultiplie les abonnements vers une même instance. Dit autrement : j'ai davantage de connexions vers ces sites que si nous étions des relais entre nous car je fais des synchronisations individuelles de profils ; mais je ne pèse pas assez pour que cela leur donne de l'intérêt de me mettre comme relais. Ainsi comment mesurer concrément l'effort que demande mon hébergement d'instance individuelle face à un profil sur une plateforme déjà richement dotée en profils… ?

Bref, à mon sens Mastodon dans son fonctionnement, pousse tout de même naturellement la communauté à trouver les voix et moyens d'avoir plutôt des rassemblements de taille raisonnable (= à une relative "centralisation") plutôt qu'une approche strictement "individualiste". Il y a des mécaniques de confiance qui s'acquiert par la taille, comme beaucoup de réseaux sociaux. On aime ce constat ou pas, mais difficile de ne pas comprendre qu'il y a aussi des enjeux de charge système (et réseaux) qui viennent rapidement. Qui dit charge système dit ressources humaines, dit charges financières, dit sources de financement… Imaginer un réseau social totalement horizontal et unitaire me semble lointain si vraiment des centaines de millions de personnes basculent dans le "Fédivers" et décident d'avoir toute une instance individuelle. A l'inverse, n'avoir que quelques grands sites, risquent de faire basculer la logique fédérée dans une version trop centralisées, sujette à davantage de risques de dérives.

Mastodon n'est donc pas une panacée pour tous les sujets. Sa diffusion n'est pas automatiquement un avenir meilleur et profitable à tous.

Avoir sa propre instance offre des libertés et des protections de matière de vie privée (si vous faites réellement de l’auto-hébergement), mais représente aussi (beaucoup) de connexions supplémentaires pour les autres serveurs, notamment les plus exposés. Plus les systèmes "grands publics" seront "chargés" pour offrir leur contenu comme devenir des relais, plus la tentation de la publicité reviendra pour garder l'aspect "gratuit". Certaines instances pourraient devenir alors encore moins vertueuses encore que Twitter sauce Musk - poussées par la popularité, l'absence de formation des administrateurs et le besoin de survivre. Avec des problématiques de territorialité légale (l'instance est hébergée hors d'Union Européenne par exemple).

D'autant que la sécurité et la performance en soi, ne sont pas neutres en coût horaire d'entretien. Il faut aussi avoir un peu de connaissances, pas seulement de la bonne volonté. On peut vite perdre sa vie privée et numérique sociale si on ne maîtrise pas son instance individuelle.

Mastodon est un début de réponse à une problématique qui me semble bien vaste et protéiforme, qui n'est certainement pas un gage automatique de "c'est mieux que Twitter" - quand bien même je m'inscris dans une démarche pour quitter ce réseau.

Alors vient naturellement la question : vais-je ouvrir mon instance à d'autres, pour ne pas "être juste un poids" pour les autres et faire profiter d'une instance "qui tourne" ? La réponse penche aujourd'hui plutôt vers le non : c'est un engagement juridique fort (respect du droit car on agit comme alors comme diffuseur pour d'autres) et du temps pour fournir un service de qualité et fiable. Avoir quelques personnes supplémentaires pourquoi pas, mais je ne peux donner aucune garantie. Cela reposerait en outre sur une confiance mutuelle. J'ai conscience que cela peut paraître égoïste, mais beaucoup dans ceux qui montrons des instances autonomes, se poseront la question. Si l'on veut que Mastodon garde une image de liberté et de sérieux, il faut que ce qui se lancent dans l'aventure ne se comportent pas comme des idiots du temps du far west. Je préfère pour ma part afficher peu d'ambitions et s'inscrire dans une démarche raisonnable.

Aller au-delà impose a minima une structure associative pour collecter des fonds pour l'achat et l'entretien de matériels, du temps à passer pour les mises à jour, une gouvernance sur la question des limites à la liberté de parole (des limites nécessaires en démocratie, dans le respect d'une vision politique partagée, tant comme groupe qu'au sein de la société).

Ce ne sont là que quelques sujets qui émergent et doivent nous faire (en tout cas me font) réfléchir à ce que recouvrent la très large et composite notion de "régulation des réseaux sociaux" et de leur administration. Non Mastodon n'est pas la panacée pour résoudre d'un coup de baguette technique magique, les maux d'Internet et de son sous-ensemble Web. Le diable est dans les détails. Mais qu'est-ce que c'est passionnant !

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.