Blog

Nachrichtenverwalter

Meine Freizeitgestaltung unterliegt einer klaren Pendelbewegung, und nach modernen und sogar populären Kommunikationstechnologien wende ich mich wieder einem sehr alten Medium zu, denn: Chat is indeed faster. But email creates evidence. Der Versand von E-Mails an eine überschaubare Anzahl von Empfängerinnen mittels einer einzigen Adresse lässt sich entweder mit einer Zeile in /etc/postfix/virtual bewerkstelligen, oder mit der Mailman Suite, bestehend aus den Projekten Mailman Core, Postorius, Hyperkitty, Mailmanclient, Hyperkitty Mailman plugin und Django-mailman3. Als wäre die Architektur nicht einschüchternd genug, gibt es zwei distinkte Einstiegspunkte in die Dokumentation, von denen nur einer zu einer stabilen Installationsleitung führt. Folgt man dieser Anleitung ohne jede Abweichung, denkt an das percent-encoding der Passwörter und konfiguriert die gewünschte Subdomain samt DKIM-Schlüssel und DNS-Einträgen für SPF, DKIM und DMARC (denn einige Mailprovider ignorieren die Subdomain Policy des bestehenden DMARC-Eintrags), gelangt man erstaunlich rasch zu einer funktionierenden Mailman-Instanz. Die Verwendung des Listennamens als Absenderadresse (DMARC mitigation) erzeugt in Verbindung mit use_esld = false; in /etc/rspamd/local.d/dkim_signing.conf (um die Reduktion von lists.eden.one auf die eSLD eden.one im Rahmen der DKIM-Signatur zu verhindern) wohlgeformte E-Mails, die allerdings unter Umständen mit der DKIM-Signatur der Absenderin verunziert sind:

Authentication-Results: posteo.de; dmarc=pass (p=quarantine dis=none) header.from=lists.eden.one Authentication-Results: posteo.de; spf=pass smtp.mailfrom=eden.one Authentication-Results: posteo.de; dkim=pass (2048-bit key) header.d=lists.eden.one header.i=@lists.eden.one header.b=RohmQgN4; dkim=fail reason="signature verification failed" (2048-bit key) header.d=eden.one header.i=@eden.one header.b=Io5jc3FE; dkim-atps=neutral

Dieses Problem lässt sich mit dem Parameter remove_dkim_headers: yes in /etc/mailman3/mailman.cfg (um die ursprüngliche DKIM-Signatur zu entfernen) leicht beheben:

Authentication-Results: posteo.de; dmarc=pass (p=quarantine dis=none) header.from=lists.eden.one Authentication-Results: posteo.de; spf=pass smtp.mailfrom=eden.one Authentication-Results: posteo.de; dkim=pass (2048-bit key) header.d=lists.eden.one header.i=@lists.eden.one header.b=0VbRo4SC; dkim-atps=neutral

Im nächsten Schritt soll die Kommunikation zwischen Mailman und Postfix, die standardmäßig unverschlüsselt und unauthentifiziert über Port 25 erfolgt, abgesichert werden:

# /etc/mailman3/mailman.cfg [mta] smtp_user: mailman_sender smtp_pass: mypassword smtp_secure_mode: starttls smtp_port: 587 smtp_verify_cert: no

Weil von diesem Moment an reject_sender_login_mismatch und smtpd_sender_login_maps greifen, benötigt mailman_sender einen Freibrief (und keinen Hack in mailman/src/mailman/mta/postfix.py, um die jeweils genutzten Listenadressen einzeln nachzutragen):

# /etc/postfix/controlled_envelope_senders @lists.eden.one mailman_sender

Zum Schluss verschiebe ich /opt/mailman/mm/mailman-hyperkitty.cfg nach /etc/mailman3/mailman-hyperkitty.cfg und vollziehe die Änderung in /etc/mailman3/mailman.cfg nach:

[archiver.hyperkitty] class: mailman_hyperkitty.Archiver enable: yes configuration: /etc/mailman3/mailman-hyperkitty.cfg

Nach diesen harmlosen Optimierungen verfalle ich auf die verhängnisvolle Idee, mailman-web und uwsgi aus Effizienzerwägungen über ein file socket kommunizieren zu lassen. Die initialen Änderungen in /etc/mailman3/uwsgi.ini und /etc/nginx/sites-enabled/mailman sind rasch erledigt, wenn man nicht vergisst, nginx die relevanten Umgebungsvariablen via uwsgi_params zu vermitteln (andernfalls: KeyError: 'REQUEST_METHOD'):

# /etc/mailman3/uwsgi.ini [uwsgi] # http-socket = 0.0.0.0:8000 socket = /opt/mailman/mm/var/mailman.sock chmod-socket = 664 # /etc/nginx/sites-enabled/mailman location / { #proxy_pass http://127.0.0.1:8000; uwsgi_pass unix:/opt/mailman/mm/var/mailman.sock; include /etc/mailman3/uwsgi_params; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; }

Um mailman-web von den neuen Verhältnissen zu überzeugen, müssen zwei weitere Einstellungen angepasst werden:

# /etc/mailman3/settings.py # override http://localhost:8000 in mailman_web/settings/mailman.py POSTORIUS_TEMPLATE_BASE_URL = 'https://lists.eden.one' # /etc/mailman3/mailman-hyperkitty.cfg [general] # base_url: https://127.0.0.1:8000/archives/ base_url: https://lists.eden.one/archives/

Nun funktioniert alles – außer der Archivierung von Nachrichten mit Hyperkitty (obwohl sie zuvor, mit http-socket, kein Problem war):

# /opt/mailman/web/logs/mailmanweb.log PermissionError at /archives/list/testlist@lists.eden.one/thread/T6MCILAVQYV5QIOGHJB3JNJ26Z7E2Z35/reattach-suggest [Errno 13] Permission denied: 'fulltext_index'

Es stellt sich heraus, dass das Verzeichnis fulltext_index durch das Whoosh-Backend der Haystack-Komponente referenziert wird. Ich probiere es mit einer vollständigen Pfadangabe –

# /etc/mailman3/settings.py # override the faulty PATH in mailman_web/settings/mailman.py HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'PATH': "/opt/mailman/fulltext_index", }, }

– und ernte einen Teilerfolg (eine andere Fehlermeldung):

ERROR 2022-12-01 09:18:00,111 1337915 hyperkitty.views.mailman Access to the archiving API endpoint was forbidden from IP 123.456.789.012, your MAILMAN_ARCHIVER_FROM setting may be misconfigured

Offenbar eine weitere Nebenwirkung des Wechsels von http-socket zu socket, die sich durch die Angabe der öffentlichen IP des Mailservers beheben lässt:

# /etc/mailman3/settings.py # MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1') MAILMAN_ARCHIVER_FROM = ('123.456.789.012', '::1')

Natürlich frage ich mich langsam, ob ein socket wirklich so viel effizienter ist, aber genau in diesem Moment enden die Fehlermeldungen, und Hyperkitty arbeitet wie gewünscht: Threads lassen sich im Archiv suchen und neu zusammenfügen, und kombinierbare Threads werden sogar automatisch vorgeschlagen, wenn sich deren Betreffzeilen gleichen. Fröhliches reattachment führt allerdings dazu, dass Hyperkitty auf den Archivseiten aller Testlisten vergeblich nach anzuzeigenden Threads sucht (hyperkitty.models.thread.Thread.DoesNotExist: Thread matching query does not exist.), aber mein Debugging-Konto für diesen Monat ist schon weit überzogen.

Unterstützung bei der tastenden Fehlersuche erhalte ich ausnahmsweise nicht über eine der vielen Stackexchange-Seiten, sondern über mailman-users[at]mailman3.org, die zu meiner großen Befriedigung weniger sauber konfiguriert ist als meine Listen:

Authentication-Results: posteo.de; dmarc=pass (p=none dis=none) header.from=mailman3.org Authentication-Results: posteo.de; dkim=pass (1024-bit key) header.d=mailman3.org header.i=@mailman3.org header.b=0eJkGPKA; dkim=fail reason="signature verification failed" (2048-bit key) header.d=eden.one header.i=@eden.one header.b=l4srDqkL; dkim-atps=neutral

mailman-users[at]mailman3.org wird exklusiv von Mark Sapiro betreut, der meine Anmerkung zum Hyperkitty-Konfigurationspfad unverzüglich umsetzt und für neue Admins entmutigende Hinweise wie diesen bereithält:

Note in the above, django-admin is the command you use to invoke Django's management which may be django-admin, manage.py, mailman-web or something else depending on how Mailman is installed.

Jedes Projekt sollte einen so freundlichen und geduldigen resident sage haben. Sehr angenehm ist auch der minimale Platzbedarf der Mailman-Datenbanken (< 20KB), vor allem im Vergleich zu gewissen raumgreifenden Sozialanwendungen. Allerdings trägt die Mailman Suite mit ihren verschiedenen Django-Anwendungen allein etwa zwei Prozentpunkte zur Systemlast bei (~ 5% CPU, ~ 15% RAM), obwohl Reichweite und Aktivität meiner Testlisten im nicht messbaren Bereich liegen.

Sozialarchiv

Der Untergang von Twitter wird allerorten mit wohligem Schauer beschworen, und sehr aufgeregte, wohlmeinende Menschen raten dazu, sämtliche Direktnachrichten im Twitter-Account zu löschen und sofort ein Archiv der eigenen Twitter-Aktivitäten anzufordern. Ein Leak meiner Direktnachrichten würde lediglich mein untadeliges Sozialverhalten unterstreichen, aber das Archiv könnte interessant sein – was es nicht ist: Nicht nur der Umfang (1903 Tweets, davon 932 Replies und 254 Retweets, 1369 Likes) ist für den Nutzungszeitraum von 13 Jahren sehr überschaubar, sondern auch der Unterhaltungswert. Mit Hilfe des Archiv-Parsers und einiger regulärer Ausdrücke reformiere ich diesen Teil meiner digitalen Vergangenheit dennoch ein wenig, bevor er in die Website integriert wird. Mit Hilfe des Archivs lässt sich nämlich belegen, dass ich wenigstens einmal im Leben ein early adopter war, der sich schon im August 2018 für Mastodon interessierte, und dass URL shortener Teufelswerk sind: Sämtliche gekürzten URLs (< 2013) führen ins Nichts, während meine eigenen URLs (2009ff.) einwandfrei funktionieren.

Verifiziert

Die Irritation über den fehlenden Verifikationshaken auf meinem Mastodon-Profil und Marco Arments beharrliche Werbung für Linode bringen mich dazu, einen separaten Webserver aufzusetzen, weil das Problem mit der direkten Nachbarschaft von Mastodon und Website zu tun haben könnte.

Allerdings wird die Website auch auf dem neuen Webserver nicht verifiziert, und ich gehe der Mastodon-Community so lange auf die Nerven, bis sich afontenot erbarmt und mich auf eine ungünstig konfigurierte /etc/hosts-Datei aufmerksam macht:

Total shot in the dark here: with certain resolver configurations, your domain name could get resolved locally to 127.0.0.1. So your Mastodon instance queries eden.one, gets the local address back. One possibility is that Mastodon rejects local IPs when DNS returns them. Probably not that unreasonable? Another possibility is that your webserver is not set up to listen on localhost.

Quick way to test this is to run nslookup eden.one on your server. If you get the localhost address back, there's a good chance this is your problem.

If so you could probably fix the problem by making sure that your local domain doesn't get resolved locally. E.g. change your hostname to be something other than eden.one, check the /etc/hosts file to see if it has any relevant entries, etc.

Einerseits sehr peinlich, andererseits: Verifiziert!

Ebenfalls verifiziert habe ich den funktionierenden Wechsel auf einen anderen Server (wenn auch nur mit der simpelsten Komponente des Server-Betriebs; einen Transfer des Mailstacks möchte ich mir lieber nicht ausmalen). Der neue Webserver ist übrigens trotz nominell schlechterer Leistungsdaten exakt gleich schnell, was die Entscheidung für eine statische Website erneut bestätigt.

Graudamensubskription

Bisher habe ich die OAuth-Anmeldung über GitHub oder Google nur genutzt, wenn ich in Eile war, obgleich mein Google-Account vermutlich besser gesichert ist als die Nutzerdatenbank auf cryptobros.com. Heute nehme ich das aktuelle Sonderangebot der New York Times (20$/Jahr!) zum Anlass, ein Digitalabo abzuschließen und zum ersten Mal die Anonymisierungsfunktion des Dienstes Sign in with Apple zu verwenden. Es funktioniert in Verbindung mit Face ID erschreckend gut: Zwar schließe ich die Anmeldung mit meiner Apple ID (= E-Mail-Adresse) ab, der Zeitung wird aber nur ein Alias (abcdefghijk@privaterelay.appleid.com) übermittelt. Bei jeder weiteren Anmeldung auf der Website oder in den Apps der Zeitung kommt dasselbe Alias zum Einsatz, und auf Apple-Geräten mit Face ID/Touch ID ist der Anmeldevorgang kaum spürbar. Zu diesem Preis und im Vergleich zur Anmeldung auf der Website einer gewissen bayerischen Zeitung wirkt das etwas zukunftsträchtiger als die Bemühungen deutscher Zeitungsverlage, Zahlungen von Google zu erzwingen oder öffentlich-rechtliche Webseiten und Apps verbieten zu lassen.

Zu meinem Glück scheitert die Bezahlung des NYT-Abonnements mit Apple Pay. Die Bezahlung mit PayPal (Benutzername/Passwort/TOTP, Zahlungsmethode auswählen, bestätigen) holt mich auf den Boden der Realität zurück und verhindert, dass ich friktionslos durch das Internet gleitend Geld ausgebe. Außerdem hat Rspamd wenig Respekt vor der old gray lady und markiert einige besonders reißerische NYT-Newsletter (Your Monday Briefing: Five dead in Colorado shooting) gnadenlos als Spam.

Langzeitpublikation

Auf Mastodon wird – natürlich sehr gesittet – diskutiert, ob das Netzwerk eher eine unvollständige Blogging-Plattform oder ein Messaging-Service im Gewand einer Website ist. Ältere Menschen verweisen in der Diskussion –

Now is probably a good time to say this:

If you're a blogger or artist, don't trust *any* social media as permanent.

No, not even here.

Create a website.
Keep it updated.
Manage the infrastructure.
Take regular backups.
Post links.

instinktiv

Each new generation on the web needs to learn that there’s no such thing as a permanent web identity on a commercial web service.

The only long-term solution to maintain your identity is:

  1. your own domain name
  2. Your own website/blog
  3. Several backups

Everything else is temporary. Your accounts on myspace, facebook, medium, twitter, google plus, youtube, tiktok, mastodon will one day disappear or become useless.

You don’t have a community on those websites. Only ephemeral discussions.

– auf das POSSE/POSIOP-Prinzip und ein notwendiges Maß an Pessismismus/Paranoia. Noch radikalere Stimmen stellen auch das DNS-System als solide Grundlage einer Netzidentität in Frage und trauen nur ihren Schlüsselpaaren (mit denen sie dann vermutlich jede digitale Aktivität signieren), oder lassen ihre Toots Posts automatisch nach 14 Tagen löschen, um deren Vergänglichkeit zu unterstreichen. Tatsächlich garantieren weder der bewundernswerte Optimismus vieler Fediverse-Verfechterinnen (The Fediverse is the most important revolution in communications – probably since the Internet has been built.) noch der Schlachtruf Protocols, Not Platforms den langfristigen Erfolg von ActivityPub, denn bisher haben sich (oberhalb von TCP/IP und in Verbindung mit DNS) nur die Kombinationen HTTP/HTML/CSS und POP3/IMAP/SMTP/IMF als wirklich langlebig erwiesen.

Obwohl ich also in der privilegierten Situation bin, die Daten auf meiner Mastodon-Instanz täglich sichern zu können und daher das plötzliche Verschwinden aller Posts nicht fürchten muss, und obwohl selbst das Internet Archive Mastodon ernst nimmt, möchte ich mich nicht auf die langfristige Relevanz des Protokolls und Verfügbarkeit der Software verlassen. Ich lasse Nginx weiterhin ausgezeichnete Textdateien ausliefern (ohne dem Unicode-Fundamentalismus – You don't need HTML!meines Namensvetters zu verfallen) und warte ab, ob und wie sich ein Netzwerk-Effekt im Fediverse einstellt.

Die Idee, den Web-Werkzeugkasten weiter zu vereinfachen und Django mit all seinen Abhängigkeiten auch als Redaktionssystem zu Gunsten eines Website-Generators wie Hugo oder Pelican hinter mir zu lassen, verwerfe ich angesichts der Implikationen – Umsetzung von Brotkrumenpfaden und Querverweisen in einer völlig neuen Template-Sprache, Re-Implementierung der Zusatzfunktionen meines Backrezeptes, Transfer aller Einzelseiten aus der Datenbank in statische Markdown-Dateien – allerdings sehr schnell.

Privatrüsseltier 3

In den frühen Morgenstunden wurde Mastodon v4.0.2 veröffentlicht, und ich habe immer noch wenig zu verlieren (150 Following, 15 Follower, < 100 Posts):

su - mastodon RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.0.4 rbenv global 3.0.4 git fetch && git checkout v4.0.2 bundle install yarn install SKIP_POST_DEPLOYMENT_MIGRATIONS=true RAILS_ENV=production bundle exec rails db:migrate RAILS_ENV=production bundle exec rails assets:precompile systemctl restart mastodon-sidekiq.service; systemctl restart mastodon-streaming.service; systemctl restart mastodon-web.service RAILS_ENV=production bundle exec rails db:migrate systemctl restart mastodon-sidekiq.service; systemctl restart mastodon-streaming.service; systemctl restart mastodon-web.service

An dieser Stelle profitiere ich von der nahezu unveränderten Konfiguration meiner Instanz (git moniert nur den angepassten throttle-Wert in rack_attack.rb), andere Admins haben Kollateralschäden zu beklagen.

Das regelmäßige Durchlüften des Medien-Caches lässt sich mit der neuen Version besser automatisieren, und Posts lassen sich einfacher editieren, aber Avatare und Header bleiben ein Problem, das man nur auf Umwegen kontrollieren kann.

成功導航

Unter der weisen Führung von Xi Jinping und seinen sorgfältig ausgewählten Mitarbeitern beeindruckt die Führungsmacht des 21. Jahrhunderts die Weltgemeinschaft unaufhörlich mit positiven Nachrichten: Dank der liberalen Pandemiepolitik genießt die chinesische Bevölkerung die Freiheit, sich nach eigenem Ermessen und in großer Zahl mit einem Virus zu infizieren, die Risiken des Raumfahrtprogramms werden – so gut es geht – auf dünn besiedelte Regionen begrenzt, und der wirtschaftlichen Entwicklung wird in vorübergehenden Schwächephasen konsequent Vorrang vor abstrakten Schutzkonzepten eingeräumt. Vor diesem Hintergrund kann auch der Regierungschef eines kleinen Handelspartners seine Bewunderung nicht verhehlen, der westliche Verbündete hat seine Rolle als Juniorpartner der Volksrepublik China schon seit längerem anerkannt und die administrative Eingliederung einer chinesischen Insel ist nur noch eine Frage der Zeit.

Social Space

Je mehr datenverarbeitende Sozialanwendungen auf meinem Server laufen, desto drängender stellt sich die Frage nach einer geeigneten Backup-Strategie. Für Mastodon gibt es eine präzise Anleitung zur Sicherung der wichtigsten Daten (.env.production, Medien, PostgreSQL- und Redis-Datenbank). Weil Redis sein eigenes Backup erzeugt, benötige ich lediglich einen Cronjob für PostgreSQL –

53 12 * * * pg_dump -U mastodon -d mastodon_production > /backup/mastodon_production.sql && gzip -f -9 /backup/mastodon_production.sql

– und einige rbackup.py-Konfigurationen, die aber den Cache für entfernte Medien-Inhalte aussparen sollten (exclude: *cache*), denn:

snafu:~/live$ bin/tootctl media usage Attachments: 1.09 GB (290 KB local) Custom emoji: 40.1 MB (12.7 KB local) Preview cards: 140 MB Avatars: 1.96 GB (96.5 KB local) Headers: 3.91 GB (413 KB local)

Mit bin/tootctl media remove --days=1 (als täglichen Cronjob) lässt sich der Attachments-Cache gut beherrschen, aber das ungebremste Caching von Avatar- und Header-Bildern fremder Menschen könnte ein Problem werden.

Damit der obige Cronjob funktioniert, ändere ich das Anmeldeverfahren für PostgreSQL von peer auf sha-cram256 für alle lokalen Nutzerinnen außer dem Superuser postgres (und hinterlege das Passwort für mastodon in .env.production).Selbst wenn man von Mastodons Caching-Problemen absieht, geht die Software sehr großzügig mit Speicherplatz um: Ein Dump der Datenbank ist schon nach wenigen Wochen deutlich größer als meine 14 Jahre alte Website-Datenbank (300 MB vs. 120 MB).

Das gilt auch für Synapse, deren Datenbank noch stärker zur Fülle neigt und mit großem Aufwand in Schach gehalten werden muss. Ich sichere sie daher nur einmalig (pg_dump synapse_production > /backup/synapse_production.sql && gzip -f -9 /backup/synapse_production.sql) und gemeinsam mit den Einstellungen (/etc/matrix-synapse).

Kommunikation ist schön, braucht aber viel Platz.

Privatrüsseltier 2

Trotz meiner Beschäftigung mit Delegation im Matrix-Kontext bin ich nicht auf die Idee gekommen, das Prinzip auf Mastodon zu übertragen. Erst zwei Toots machen mich auf die simple Konfiguration aufmerksam:

# /etc/nginx/nginx.conf location = /.well-known/host-meta { return 301 https://social.eden.one$request_uri; } # /home/mastodon/live/.env.production LOCAL_DOMAIN=eden.one WEB_DOMAIN=social.eden.one

Nach einem Neustart aller Beteiligten (service restart nginx; systemctl restart mastodon-web.service; systemctl restart mastodon-sidekiq.service) migriere ich den Account @janedenone@eden.one (Follower werden automatisch umgezogen, gefolgte Accounts und Bookmarks müssen per Export/Import mitgenommen werden) und verfüge nun über einen sehr schicken Namen im Fediverse. Zurück bleiben 146 Toots. Das Prinzip lässt sich (teilweise) auch ohne eigene Mastodon-Instanz anwenden, indem die Antwort der genutzten Mastodon-Instanz auf eine WebFinger-Anfrage auf dem eigenen Webserver hinterlegt wird.

Bei dieser Gelegenheit erhöhe ich das rate limit für authentifizierte Anfragen von 300 auf 3000, um die Fehlermeldung Too many requests zu vermeiden:

# /home/mastodon/live/config/initializers/rack_attack.rb throttle('throttle_authenticated_api', limit:3000, period: 5.minutes) do |req| req.authenticated_user_id if req.api_request? end

Außerdem lerne ich, dass Mastodon automatisch RSS-Feeds generiert, so dass ich Accounts grundsätzlich auch in meinem RSS-Reader folgen, das Twitter/Blogosphere-Schisma überwinden und RSS zu neuem Leben verhelfen könnte.

Nur wenige Stunden später verlinkt Dan Hon einen CLI-Client für Mastodon, der kaum Wünsche offen lässt (außer vielleicht der Erweiterung des tui-Kommandos um die Möglichkeit, vim als Editor zu verwenden), und Dr. Drang demonstriert die Verwendung der Mastodon-API mittels eines einfachen Python-Skriptes. Mastodon macht mir schon in den ersten Tagen viel Freude, trotz des bedrohlichen wachsenden Caches.

Rocket.Chat vs. Matrix/Synapse

Seit der Eröffnung von social.eden.one betreibe ich zwei Anwendungen im Small Web, und obwohl ich der radikalen Vision (Jeder muss sein eigener Server sein) eher skeptisch gegenüber stehe – die freien Serverkapazitäten und die schöne Domain eden.one fordern den Betrieb weiterer Anwendungen geradezu heraus. Nachdem vor einigen Monaten viel Zeit in den Serverbetrieb für eine völlig antiquierte Technologie (mit überraschendem Optimierungspotential auf mehreren Ebenen) geflossen ist, steht heute eine jüngere Kommunikationsform auf dem Programm.

Selbst Online-Chats sind älter als ich, und die noch heute verwendeten Protokolle evozieren eine weit zurückliegende Zeit vor dem Siegeszug des WWW. Damals chatteten meist Gruppen von Menschen themenbezogen in rooms oder channels, und Chats waren nur synchron möglich, wenn eine Einwahlmöglichkeit bestand. IRC-Dienste wie Libera haben im Wesentlichen noch dieses Profil. In modernen Chat-Plattformen für Unternehmen/Organisationen (Slack, Mattermost etc) treten neben die thematischen Kanäle Gruppen- oder Teamräume, und die Oberflächen haben sich seit den IRC-Hochzeiten deutlich weiterentwickelt. Gleichzeitig hat sich im privaten Bereich Instant Messaging als dominante Chat-Variante durchgesetzt. Diese Kommunikation (synchron oder asynchron) zwischen (mindestens) zwei Menschen erfolgt vor allem auf mobilen Endgeräten mit Diensten wie WhatsApp, Signal, Telegram oder Threema. Die Grenzen zwischen einem traditionellen Chat-Dienst und einem Messenger sind fließend, denn Kanäle lassen sich auch in einem Messenger einrichten – vor allem auf Telegram sind öffentliche Kanäle sehr populär.

Bei allen Unterschieden im Detail stützen sich fast alle Chat-/Messaging-Dienste auf eine Client-Server-Architektur, in der der Server nicht von den Nutzerinnen kontrolliert wird, und die Ausnahmen (Tox, Briar) richten sich laut Messenger-Matrix an Nerds. Als Nicht-Nerd bleibt mir da nur, testweise eigene Chatserver aufzusetzen, um meinen trust issues und der Abhängigkeit von fremden Admins zu entkommen.

Der erste Testkandidat ist die Slack-Alternative Rocket.Chat. Ich ziehe ausnahmsweise die Installation via Snap dem manuellen Setup vor, weil ich nicht ein weiteres RDBMS (MongoDB) und eine zusätzliche Node.js-Version verwalten möchte. Entsprechend schnell geht alles, nach der Installation müssen lediglich zwei Parameter angepasst werden (u.a. um eine Kollision mit dem Mastodon-Port zu vermeiden):

$ snap install rocketchat-server $ snap set rocketchat-server siteurl=https://chat.eden.one $ snap set rocketchat-server port=4004 $ snap get rocketchat-server Key Value backup-on-refresh disable ignore-errors false mongo-oplog-url mongodb://localhost:27017/local mongo-url mongodb://localhost:27017/parties port 4004 siteurl https://chat.eden.one

Für die neue Subdomain wird ein Zertifikat benötigt (certbot certonly --nginx -d chat.eden.one), und Nginx wird als reverse proxy eingespannt:

server { listen 443 ssl http2; server_name chat.eden.one; client_max_body_size 200M; error_log /var/log/nginx/rocketchat.access.log; ssl_certificate /etc/letsencrypt/live/chat.eden.one/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/chat.eden.one/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://127.0.0.1:4004/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_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 https; proxy_set_header X-Nginx-Proxy true; proxy_redirect off; } }

Alle weiteren Konfigurationsschritte erfolgen über das Web-Interface der Applikation, vor allem die Erstellung eines Admin-Accounts (🫣), der Eintrag der SMTP-Zugangsdaten, die 2FA-Aktivierung und (in meinem Fall) die Beschränkung der Einrichtung neuer Konten (Admin → Accounts → Registration → Registration Form → Secret URL) sowie die Ergänzung des Templates für Einladungen um die Secret URL:

<h2>{Welcome_to Site_Name}</h2><p>{Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today}</p><a class="btn" href="[Site_URL]/register/ynUpX8gKoKwD9j9wY">{Join_Chat}</a>

Grundsätzlich kann man Rocket.Chat als standalone server betreiben, aber um Push-Benachrichtigungen an die mobilen Apps (iOS/Android) zu versenden (kostenfrei bis zu 10.000 pro Monat), muss der Server registriert werden (entgegen der Prämisse des Tests).

Nach einem Neustart des Service (systemctl restart snap.rocketchat-server.rocketchat-server.service) funktioniert alles einwandfrei (TTL < 20m). Der Komfort hat natürlich seinen Preis: Innerhalb von 24 Stunden werde ich zweimal 48 Stunden werde ich viermal vom Rocket.Chat-Vertrieb kontaktiert, basierend auf der Registrierung für die Rocket.Chat Cloud (und der heimtückischen Einstellung Allow Marketing Emails). Der Fokus auf (zahlungsbereite) Unternehmen und auf die interne Kommunikation dieser Unternehmen ist sehr eindeutig. Rocket.Chat unterscheidet zwischen Channels, Teams, Direct Messages und Discussions, was für eine differenzierte unternehmensweite Kommunikation hilfreich, für soziale Kommunikation aber weniger relevant ist.

Ein föderierter Einsatz mit anderen Servern steht ebenfalls nicht im Vordergrund (Federation Support is a work in progress. Use on a production system is not recommended at this time.), obwohl sich Rocket.Chat sogar mit der Matrix verbinden ließe (wenn man ihm einen eigenen Matrix-Homeserver zur Verfügung stellte). E2E-Verschlüsselung gegen maliziöse Admins (This feature is currently in beta!) ist keine Stärke des Systems; nur unter bestimmten Bedingungen können zwei Personen mit Rocket.Chat vertraulich kommunizieren. Aktuell gehöre ich deshalb nicht zur Rocket.Chat-Zielgruppe (sorry, Aditya!).

Die Referenzimplementierung der Matrix-Spezifikation, Synapse, ist in vielerlei Hinsicht das Gegenteil von Rocket.Chat: E2E-Verschlüsselung und Föderation zählen zur Kernfunktionalität, dafür bietet Synapse kein Web-Interface und (mangels entsprechender Spezifikation) keine 2-Faktor-Authentifizierung via TOTP. Stattdessen wird bei der erstmaligen Anmeldung in einem Client ein Schlüssel erzeugt, den man in Form einer Security Phrase bei Anmeldungen in anderen Clients als zweiten Faktor verwenden muss (und den auch die Administratorin nicht kennt).

Die für Synapse empfohlene Installationsmethode ist ein Ansible Playbook, mit dem Synapse (und andere Anwendungen) in einem Docker-Container bereitgestellt werden – eindeutig zu viele Abstraktionsebenen für mich. Ich wähle den traditionellen Weg:

sudo apt install -y lsb-release wget apt-transport-https wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/matrix-org-archive-keyring.gpg] https://packages.matrix.org/debian/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/matrix-org.list sudo apt update sudo apt install matrix-synapse-py3

Im Zuge der Installation werde ich nach dem gewünschten server_name gefragt, dessen Unabänderlichkeit und Bedeutung in den ersten Sätzen der Dokumentation unmissverständlich erklärt wird:

It is important to choose the name for your server before you install Synapse, because it cannot be changed later.

The server name determines the "domain" part of user-ids for users on your server: these will all be of the format @user:my.domain.name. It also determines how other matrix servers will reach yours for federation.

For a test configuration, set this to the hostname of your server. For a more production-ready setup, you will probably want to specify your domain (example.com) rather than a matrix-specific hostname here (in the same way that your email address is probably user@example.com rather than user@email.example.com) - but doing so may require more advanced setup: see Setting up Federation.

Natürlich überlese ich diesen eindringlichen Hinweis und wähle den Servernamen matrix.eden.one, zunächst ohne nachteilige Effekte. Wie üblich benötigt Synapse ein Zertifikat (certbot certonly --nginx -d matrix.eden.one) und einen reverse proxy:

server { listen 443 ssl http2; listen [::]:443 ssl http2; # For the federation port listen 8448 ssl http2 default_server; listen [::]:8448 ssl http2 default_server; server_name matrix.eden.one; ssl_certificate /etc/letsencrypt/live/matrix.eden.one/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/matrix.eden.one/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location ~ ^(/_matrix|/_synapse/client) { proxy_pass http://localhost:8008; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; client_max_body_size 50M; } }

Etwas widerwillig öffne ich Port 8448 in der Firewall, damit mein frisch gestarteter Homeserver (systemctl start matrix-synapse.service) mit anderen Matrix-Servern sprechen kann, und melde mich stolz im Chatraum des Göttinger CCC (#neotopia:matrix.cccgoe.de). Dort werde ich angesichts meines Namens @janedenone:matrix.eden.one freundlich darauf hingewiesen, dass Delegation offenbar nicht richtig konfiguriert wurde (was mich zum vorschnell gewählten server_name zurückführt).

Obwohl die Umbenennung eines Homeservers kein neues Anliegen ist, wird der Wunsch nach einem anderen server_name in den FAQs als einer der wenigen Anwendungsfälle für tabula rasa genannt:

Deleting your database is unlikely to make anything better.

It's easy to make the mistake of thinking that you can start again from a clean slate by dropping your database, but things don't work like that in a federated network: lots of other servers have information about your server.

For example: other servers might think that you are in a room, your server will think that you are not, and you'll probably be unable to interact with that room in a sensible way ever again.

In general, there are better solutions to any problem than dropping the database. Come and seek help in https://matrix.to/#/#synapse:matrix.org.

There are two exceptions when it might be sensible to delete your database and start again:

  • You have never joined any rooms which are federated with other servers. For instance, a local deployment which the outside world can't talk to.
  • You are changing the server_name in the homeserver configuration. In effect this makes your server a completely new one from the point of view of the network, so in this case it makes sense to start with a clean database. (In both cases you probably also want to clear out the media_store.)

Nach Rücksprache mit den netten Menschen in #synapse:matrix.org

Nginx ist jetzt so konfiguriert –

# /etc/nginx/sites-enabled/default location /.well-known/matrix/client { return 200 '{"m.homeserver": {"base_url": "https://matrix.eden.one"}}'; default_type application/json; add_header Access-Control-Allow-Origin *; } location /.well-known/matrix/server { return 200 '{"m.server": "matrix.eden.one:443"}'; # explicit port required, default is 8448 default_type application/json; add_header Access-Control-Allow-Origin *; } # /etc/nginx/sites-enabled/matrix server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name matrix.eden.one; ssl_certificate /etc/letsencrypt/live/matrix.eden.one/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/matrix.eden.one/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://localhost:8008; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; client_max_body_size 50M; } }

– dass der Aufruf von matrix.eden.one ein unmittelbares Erfolgserlebnis generiert –

matrix

It works! Synapse is running

Your Synapse server is listening on this port and is ready for messages.

To use this server you'll need a Matrix client.

Welcome to the Matrix universe :)

– und ich mit Hilfe der Client Well-Known URI Adressen der Form @matrixuser:eden.one verwenden kann. Dank der Synapse-Dokumentation war der Weg zu diesem Ziel etwas verschlungener als nötig: Obwohl die Installationsanleitung eine Nginx-Konfiguration für .well-known/matrix/client enthält, fehlt das Pendant für die Server-Delegation auf der reverse proxy-Seite (sie enthält nur Beispiele für Caddy und HAProxy). Mit der obigen Konfiguration ist der Federation Tester jedenfalls zufrieden.

Zum Abschluss konfiguriere ich den E-Mail-Versand (und stolpere wie üblich ein wenig über die SSL/TLS-Einstellungen). Damit sich Links in E-Mails auf matrix.eden.one (und nicht auf den server_name eden.one) beziehen, muss die passende public_base_url in /etc/matrix-synapse/homeserver.yaml eingetragen werden.

Neben Synapse benötigt man einen Matrix-Client, und es wird dringend vom Betrieb von Element (und implizit anderer Clients) unter der Homeserver-Domain abgeraten:

We do not recommend running Element from the same domain name as your Matrix homeserver. The reason is the risk of XSS (cross-site-scripting) vulnerabilities that could occur if someone caused Element to load and render malicious user generated content from a Matrix API which then had trusted access to Element (or other apps) due to sharing the same domain.

Nach meiner Erfahrung mit dem leichtfertig gewählten server_name bin ich vorsichtig geworden und verwende Element in der gehosteten Web-Version, der darauf basierenden Mac-Version und iOS-Version. Obwohl der Source-Code für Element unter einer Apache 2.0-Lizenz verfügbar ist, liegt der Schwerpunkt der Monetarisierung auf kostenpflichtigen Client-Varianten (Element One verbindet die Nutzung von Matrix, Signal, WhatsApp und Telegram innerhalb einer App für $5/Monat, Stand November 2022) und nicht auf dem SaaS-Chatserver (der Vertrieb hält sich angenehm zurück). Die Alternativen zum Element-Client sind ebenfalls Open Source-Projekte, und die Installation der CLI-basierten Clients scheitert bedauerlicherweise an der verwaisten Bibliothek python-olm.

In Verbindung mit einem Matrix-Homeserver kommen häufig ein oder zwei fremde Server zum Einsatz: Der Notary Server (default: matrix.org) stellt öffentliche Schlüssel von Matrix-Servern bereit, wenn diese Server vorübergehend nicht erreichbar sind, der Identity Server (default: vector.im) ermöglicht die Suche nach Matrix-Nutzerinnen per E-Mail-Adresse (oder Telefonnummer). Die optionale Verknüpfung von E-Mail-Adressen mit Matrix-Accounts erfordert zwei Schritte:

  1. Die E-Mail-Adresse wird in einem Matrix-Client eingetragen – der Homeserver versendet eine Verifikationsmail (mit Link auf matrix.eden.one).
  2. Die E-Mail-Adresse wird für die Suche via Identity Server freigegeben – der Betreiber Element.io versendet eine Verifikationsmail (mit Link auf vector.im).

Wenn man vor der Freigabe einer (verifizierten) E-Mail-Adresse eine Nutzerin über diese Adresse zu einem Chat einlädt, verschickt Element eine E-Mail (mit Link auf app.element.io). Hat man die Adresse freigegeben, wird die Einladung über den Homeserver (mit Link auf matrix.eden.one) zugestellt und erscheint parallel im jeweils verwendeten Client. Das ist sicher technisch und rechtlich sinnvoll gestaltet, aber etwas verwirrend, zumal alle E-Mails den Namen der auslösenden Client-Anwendung (Element) in der Betreffzeile enthalten:

# Vor der Freigabe From: Element <noreply@element.io> To: matrixuser@eden.one Subject: Jan Eden has invited you to a room on Element Hi, Jan Eden (@jan:eden.one) has invited you into a room on Element. To join the conversation please follow the link below. https://app.element.io/... # Nach der Freigabe From: Your Friendly Element homeserver <matrixserver@eden.one> To: matrixuser@eden.one Subject: [Element] @jan:eden.one has invited you to chat on Element... Hi matrix, [Element] @jan:eden.one has invited you to chat on Element...Empty Room You've been invited, join at https://matrix.eden.one/...

Außerdem werden diese Einladungen (und Benachrichtigungen über neue, ungelesene Nachrichten) nur zugestellt, wenn die Nutzerin über einen Web- oder Desktop-Client angemeldet ist und dort die entsprechende Option aktiviert. Verwendet man die mobilen Element-Clients, gibt es nur Push-Benachrichtigungen. Und schließlich werden direct messages, die man über eine E-Mail-Adresse initiiert, zunächst nicht verschlüsselt (auch wenn man die Verschlüsselung nachträglich aktivieren kann), obwohl Einladungen per E-Mail in einen bereits verschlüsselten Raum möglich sind. Insgesamt spricht einiges dafür, Matrix-Kommunikation nicht auf E-Mail-Adressen zu stützen.

Element wird als Instant Messenger kategorisiert, obgleich die Matrix-Spezifikation sich ausschließlich auf öffentliche oder private Räume bezieht, von denen es mittlerweile 10 Versionen mit unterschiedlichem Funktionsumfang gibt. Der Client unterscheidet zwischen

Direct messages (chats) mit einzelnen Menschen lassen sich in der Element-Oberfläche (etwas) einfacher einrichten als rooms, und es gibt Befehle für die Umwandlung von direct messages in rooms und umgekehrt (/converttodm, /convertoroom). Technisch sind direct messages aber ebenfalls rooms, so dass an direct messages auch mehr als zwei Personen teilnehmen können (entgegen der Darstellung im Benutzerhandbuch). Um das Gesamtbild abzurunden, kann man direct messages und rooms in spaces gruppieren, wobei spaces technisch ebenfalls rooms sind. Obwohl diese Beschreibung wie der Fiebertraum einer UX-Designerin klingt, ist das Nebeneinander öffentlicher Chaträume (wie #synapse:matrix.org) und privater Kommunikation (mit @matrixuser:eden.one) gut gelöst und erleichtert die Kombination der beiden Chat-Nutzungsformen. Wenn man den ausbaufähigen Umgang mit E-Mail-Adressen (und das überschaubare Angebot an CLI-Clients) außer Acht lässt, ist ein Matrix-Homeserver eine ideale Lösung für Menschen, die verschlüsselt mit ihrem technikaffinen Bekanntenkreis kommunizieren und sich in Chaträumen weltweit austauschen möchten, ohne sich einer grenzenlosen Paranoia hinzugeben oder den Informationsaustausch ephemeral, low-stakes, and image-heavy zu gestalten.

Die Serverlast hält sich übrigens nach wie vor in Grenzen (~ 1,5% CPU, ~ 10% RAM), was an meinem sparsamen Kommunikationsverhalten liegen mag. Den Aufenthalt in belebten öffentlichen Chaträumen und einen großen Bekanntenkreis kann ich mir angesichts des Speicherhungers der Synapse-Datenbank nämlich nicht leisten.