"Mastodon" war bisher wahrscheinlich nur Paläontologen ein Begriff. Bis Elon Musk Twitter kaufte und plötzlich alle eine Alternative zu Twitter suchten (die wahrscheinlich genau so schnell wieder vergessen sein wird, wie alle WhatsApp-Alternativen, die sich die Leute installiert hatten, nachdem facebook WhatsApp gekauft hatte).

Nachdem ich um jedes (un)soziale Netzwerk, welches nach facebook kam, einen Bogen gemacht hatte und mich auch schon seit 8-10 Jahre nicht mehr bei facebook eingeloggt hatte, ist es vielleicht mal wieder an der Zeit, eine Vorreiterrolle zu übernehmen und zu den wenigen Trötern zu gehören, die Mastodon nutzen. Vor allem, weil man einen Mastodon-Server selbst hosten kann (was zugegebenermaßen der einzige Anreiz war, mich mit dem Thema zu beschäftigen).

So habe ich meine eigene Mastodon-Instanz mit Hilfe von Docker zum Laufen gebracht.

Als Vorlage diente mir dieser Artikel, welcher allerdings Augenmerk darauf legt, Traefik als Proxy einzubinden.
Ich selbst will Apache als Reverse-Proxy verwenden.

Meine angepasste docker-compose.yml sieht wie folgt aus:

docker-compose.yml
version: '3'
services:
  db:
    restart: unless-stopped
    image: postgres:14-alpine
    shm_size: 256mb
    networks:
      - internal_mastodon
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'postgres']
    volumes:
      - ./postgres14:/var/lib/postgresql/data
    environment:
      - 'POSTGRES_HOST_AUTH_METHOD=trust'

  redis:
    restart: unless-stopped
    image: redis:7-alpine
    networks:
      - internal_mastodon
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
    volumes:
      - ./redis:/data

  web:
    image: tootsuite/mastodon
    restart: unless-stopped
    env_file: .env.production
    command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    networks:
      - internal_mastodon
      - external_mastodon
    healthcheck:
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
    ports:
      - '3000:3000'
    depends_on:
      - db
      - redis
    volumes:
      - ./public/system:/mastodon/public/system
  streaming:
    image: tootsuite/mastodon
    restart: unless-stopped
    env_file: .env.production
    command: node ./streaming
    networks:
      - internal_mastodon
      - external_mastodon
    healthcheck:
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']
    ports:
      - '4000:4000'
    depends_on:
      - db
      - redis
  sidekiq:
    image: tootsuite/mastodon
    restart: unless-stopped
    env_file: .env.production
    command: bundle exec sidekiq
    depends_on:
      - db
      - redis
    networks:
      - internal_mastodon
      - external_mastodon
    volumes:
      - ./public/system:/mastodon/public/system
    healthcheck:
      test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
networks:
    internal_mastodon:
      driver: bridge
    external_mastodon:
      driver: bridge

Jene docker-compose.yml lagert bei mir zusammen mit der leeren Datei .env.production in einem separaten Mastodon-Verzeichnis, wo ich meine anderen Docker-Dateien rumliegen habe. Zum Beispiel:

/opt/containers/mastodon/
  + docker-compose.yml
  + .env.production

Nun sollte man theoretisch alle benötigten Images mit einem Befehl herunterladen können...

sudo docker-compose -f docker-compose.yml pull

...allerdings bekam ich hier einen authentification error.
Wo auch immer da eine Authentification scheitern sollte.

So hatte ich die benötigten 3 Images halt manuell heruntergeladen:

docker pull postgres:14-alpine
docker pull redis:7-alpine
docker pull tootsuite/mastodon

Setup

Wäre das erledigt, starten wir das Setup für den Mastodon-Server:

docker-compose run web bundle exec rake mastodon:setup

Nun werden einige relevante Infos abgefragt, wie etwa, unter welcher Domain der Server erreichbar sein wird.
Bei vielen Fragen, zum Beispiel jene, die die Datenbankverbindung betreffen, kann man einfach nur auf Enter tippen und die Standardeinstelung übernehmen.

Hier das Log meiner Konfiguration, an deren Ende nach dem # Generated with mastodon:setup die eigentliche Konfiguration ausgegeben wird, die wir dann in die (bisher noch leere) .env.production übernehmen müssen.

Ich möchte übrigens nicht, dass sich fremde Leute an meinem Server registrieren können, deshalb habe ich die Frage nach dem Single User Mode mit yes beantwortet und meinen Benutzeraccount lege ich nachher separat an.

Creating mastodon_redis_1 ... done
Creating mastodon_db_1    ... done
Your instance is identified by its domain name. Changing it afterward will break things.
Domain name: mastdarm.apfelz.ned

Single user mode disables registrations and redirects the landing page to your public profile.
Do you want to enable single user mode? yes

Are you using Docker to run Mastodon? Yes

PostgreSQL host: db
PostgreSQL port: 5432
Name of PostgreSQL database: postgres
Name of PostgreSQL user: postgres
Password of PostgreSQL user:
Database configuration works! 🎆

Redis host: redis
Redis port: 6379
Redis password:
Redis configuration works! 🎆

Do you want to store uploaded files on the cloud? No

Do you want to send e-mails from localhost? No
SMTP server: smtp.strato.de
SMTP port: 465
SMTP username: boeschtler@apfelz.ned
SMTP password:
SMTP authentication: plain
SMTP OpenSSL verify mode: client_once
Enable STARTTLS: auto
E-mail address to send e-mails "from": noreply@apfelz.ned
Send a test e-mail with this configuration right now? Yes
Send test e-mail to: toeschd@apfelz.ned

This configuration will be written to .env.production
Save configuration? Yes
Below is your configuration, save it to an .env.production file outside Docker:

# Generated with mastodon:setup on 2022-12-04 10:09:27 UTC

LOCAL_DOMAIN=mastdarm.apfelz.ned
SINGLE_USER_MODE=true
SECRET_KEY_BASE=xxx
OTP_SECRET=xxx
VAPID_PRIVATE_KEY=xxx
VAPID_PUBLIC_KEY=xxx
DB_HOST=db
DB_PORT=5432
DB_NAME=postgres
DB_USER=postgres
DB_PASS=
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
SMTP_SERVER=smtp.strato.de
SMTP_PORT=587
SMTP_LOGIN=boeschtler@apfelz.ned
SMTP_PASSWORD=xxx
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=client_once
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS=noreply@apfelz.ned

Alles nach dem # Generated with mastodon:setup nun in die .env.production kopieren.

Danach erstellen wir die in der docker-compose.yml erstellten Netzwerke internal_mastodon und external_mastodon

docker network create internal_mastodon
docker network create external_mastodon

Der Mastodon-Server kann so gestartet werden:

docker-compose -f docker-compose.yml up -d

Und so gestoppt werden

docker-compose -f docker-compose.yml down

Erstellen wir mit dem Certbot noch ein SSL-Zertifikat... (das mache ich immer auf die manuelle Art)

sudo mkdir /Library/WebServer/Documents/mastdarm.apfelz.ned/
certonly --webroot -w /Library/WebServer/Documents/mastdarm.apfelz.ned/ -d mastdarm.apfelz.ned

Und die neue Subdomain trage ich in /etc/apache2/extra/httpd-ssl.conf zusammen mit den ReverseProxy-Angaben ein:

/etc/apache2/extra/httpd-ssl.conf
<VirtualHost *:443>
    SSLEngine on
    ServerName mastdarm.apfelz.ned:443
    ServerAlias mastdarm.apfelz.ned
    ServerAdmin webmaster@apfelz.ned

    ErrorLog "/private/var/log/apache2/error_log"
    TransferLog "/private/var/log/apache2/access_log"
    CustomLog "/private/var/log/apache2/ssl_request_log" "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"

    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
    SSLCertificateFile    /etc/letsencrypt/live/mastdarm.apfelz.ned/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/mastdarm.apfelz.ned/privkey.pem

    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Proto "https"
    ProxyPass /api/v1/streaming http://127.0.0.1:4000/
    ProxyPass / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>

Nutzer anlegen

Da ich während des Setups keinen Benutzer hatte anlegen lassen und auch die Registrierung für neue Benutzer unterbunden habe, muss ich nun auf der Kommandozeile einen neuen Benutzer für meine Mastodon-Instanz anlegen.

Als Benutzernamen gebe ich in diesem Beispiel apfelz vor und mache den Benutzer auch gleich zum "Owner". Also Super-Admin.

docker-compose run web /bin/bash -c "RAILS_ENV=production bin/tootctl accounts create apfelz --email 'mastdarm@apfelz.ned' --confirmed --role Owner"

Stolpersteine

Nachdem alles lief und ich mein Profil in Mastodon ausfüllen wollte, stellte ich fest, dass ich kein Profilbild hochladen konnte.

Das Log zeigte folgende Fehlermeldung:

mastodon "rrno::EACCES (Permission denied @ dir_s_mkdir - cd public/system/accounts"

...und ich konnte auch niemand anderem Folgen. Dafür erschien eine ähliche Fehlermeldung im Log:

mastodon "rrno::EACCES (Permission denied @ dir_s_mkdir - cd public/system/cache

Also müssen noch die beiden Verzeichnisse accounts und cache innerhalb der Docker-Instanz angelegt werden:

Auf Kommandozeile innerhalb der Docker-Instanz als root ausführen:

cd /opt/mastodon/public/system
mkdir accounts
chown -R mastodon:mastodon accounts
mkdir cache
chown -R mastodon:mastodon cache