Das Upgrade von Debian Bookworm auf Trixie ist durchgelaufen, der Server bootet, alles sieht gut aus – und dann das: Mastodon startet nicht mehr. Kein Web-Interface, kein Sidekiq, nichts. Ein Blick ins Log zeigt einen Fehler, der auf den ersten Blick ziemlich kryptisch wirkt, aber ein alter Bekannter unter Ruby-Admins ist. Schauen wir uns das mal an.
Wer das Distro-Upgrade selbst noch vor sich hat: Wie wir vorher die komplette VM per QEMU-Snapshot absichern und das Upgrade sauber durchziehen, haben wir uns bereits in einem eigenen Artikel angeschaut.
Das Fehlerbild
Beim Start der Mastodon-Dienste begrüßt uns folgende Meldung:
LoadError: libicudata.so.72: cannot open shared object file: No such file or directory
- .../charlock_holmes-0.7.9/lib/charlock_holmes/charlock_holmes.so
Eine Shared Library fehlt also. Aber warum? Wir haben doch nur ein ganz normales Distro-Upgrade gemacht – und apt hat sich über nichts beschwert. Genau da liegt der Hund begraben.
Was ist charlock_holmes überhaupt?
Kurzer Exkurs, bevor wir loslegen: charlock_holmes ist ein Ruby-Gem zur automatischen Zeichensatz-Erkennung. Es analysiert Texte und findet heraus, ob es sich um UTF-8, ISO-8859-1 oder ein anderes Encoding handelt. Mastodon braucht das an mehreren Stellen – zum Beispiel wenn Link-Vorschauen von fremden Webseiten erzeugt werden, deren Encoding alles Mögliche sein kann. Die eigentliche Arbeit erledigt dabei nicht das Gem selbst, sondern die ICU-Bibliothek (International Components for Unicode), gegen die das Gem als native C++-Extension kompiliert wird.
Kleine Randnotiz: Das Gem wird seit 2020 praktisch nicht mehr gepflegt und baut auf modernen Compilern nicht mehr ohne Nachhilfe – dazu gleich mehr.

Die Ursache: ICU 72 ist weg, ICU 76 ist da
Debian Bookworm liefert ICU in Version 72 aus (libicu72), Trixie bringt Version 76 (libicu76). Beim Upgrade fliegt das alte Paket raus – Debian hält alte ICU-Hauptversionen nicht automatisch vor, anders als etwa bei glibc.
Unser charlock_holmes wurde aber seinerzeit fest gegen libicudata.so.72 gelinkt. Und weil das Gem über Bundler bzw. RubyGems installiert wurde und nicht über ein Debian-Paket, ist diese Abhängigkeit für apt komplett unsichtbar. Das Paketmanagement kann uns also gar nicht warnen – es weiß schlicht nichts davon. Nach dem Upgrade zeigt die kompilierte .so-Datei des Gems ins Leere, und Mastodon verweigert den Dienst.
Quick-Fix: Das alte libicu72 zurückholen
Muss die Instanz sofort wieder laufen, gibt es eine Abkürzung: Debian benennt die ICU-Pakete bewusst versioniert (libicu72, libicu76, …), gerade damit mehrere Hauptversionen parallel installierbar sind. Das alte Runtime-Paket aus dem Bookworm-Archiv (zu finden über packages.debian.org) lässt sich also per dpkg -i einfach neben libicu76 installieren. Kein schmutziger Hack, sondern offiziell so vorgesehen – aber eben nur eine Zwischenlösung, bis wir das Gem sauber neu gebaut haben. Genau das machen wir jetzt.
Der saubere Weg: Rebuild gegen ICU 76
Schritt 1: Build-Voraussetzungen installieren
Zum Neukompilieren brauchen wir die ICU-Header und eine Build-Umgebung:
sudo apt install -y libicu-dev build-essential
Schritt 2: Compiler-Flag dauerhaft setzen
Jetzt kommt die erwähnte Nachhilfe: Das unmaintainte Gem kompiliert mit aktuellen gcc-Versionen und ICU-Headern nur, wenn wir explizit den C++17-Standard erzwingen. Mastodon dokumentiert diesen Workaround mittlerweile selbst in den offiziellen Release Notes. Statt das Flag bei jedem Update erneut als Umgebungsvariable mitzugeben, setzen wir es einmal dauerhaft in der Bundler-Konfiguration:
cd /home/mastodon/live
sudo -u mastodon bundle config set build.charlock_holmes "--with-cxxflags=-std=c++17"
Damit ist das Flag für alle zukünftigen bundle install-Läufe gesetzt – auch für die, die wir in einem halben Jahr längst wieder vergessen haben.
Schritt 3: Rebuild anstoßen
sudo -u mastodon bundle install
Bundler erkennt, dass die native Extension neu gebaut werden muss, und kompiliert charlock_holmes jetzt gegen das installierte ICU 76.
Stolperfalle: gem pristine ist nicht bundle pristine
Wer beim Recherchieren über den Befehl bundle exec gem pristine charlock_holmes stolpert – Finger weg, zumindest ohne Zusatzargumente. Das klingt zwar fast identisch zu bundle pristine, macht aber etwas völlig anderes: Es ruft RubyGems direkt auf und ignoriert die Bundler-Build-Konfiguration komplett. Unser schönes C++17-Flag aus Schritt 2? Wird schlicht nicht gelesen.
Im schlimmsten Fall löscht der Befehl die alte (noch funktionierende) .so-Datei, der Rebuild scheitert dann mangels Flags – und das Gem landet im Zustand „extensions not built“. Im Log liest sich das dann so:
Ignoring charlock_holmes-0.7.9 because its extensions are not built
Wer den Direktweg über RubyGems trotzdem gehen möchte, muss das Flag explizit hinter dem doppelten Bindestrich mitgeben:
gem pristine charlock_holmes --version 0.7.9 -- --with-cxxflags=-std=c++17
Der Bundler-Weg aus den Schritten oben ist aber der deutlich robustere Reflex.
Verifizieren: Zeigt die .so jetzt auf ICU 76?
Vertrauen ist gut, ldd ist besser. Wir suchen die frisch gebaute Extension und prüfen, gegen welche ICU-Version sie tatsächlich gelinkt ist:
find /home/mastodon/live/vendor/bundle/ruby/4.0.0 -name 'charlock_holmes.so'
ldd <gefundener-pfad> | grep icu
Erwartet werden jetzt libicuuc.so.76, libicudata.so.76 und libicui18n.so.76 – taucht irgendwo noch eine .72 auf, hat der Rebuild nicht gegriffen.
Dienste neu starten
sudo systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming
Und damit sollte die Instanz wieder erreichbar sein. Falls das übergangsweise installierte libicu72 noch auf dem System liegt: Nach erfolgreichem Rebuild kann es per dpkg -r libicu72 wieder entfernt werden.
Und wie sieht das bei Docker aus?
Wer seine Mastodon-Instanz per docker bzw. docker-compose betreibt, kann an dieser Stelle entspannt weiterscrollen: In den offiziellen Images wird charlock_holmes beim Image-Build gegen die ICU-Version innerhalb des Containers kompiliert. Ein Distro-Upgrade auf dem Host ändert daran genau gar nichts – Container-Dateisystem und Host-Bibliotheken sind sauber voneinander getrennt. Relevant wird das Thema dort nur, wenn eigene Images gebaut werden und dabei das Basis-Image auf eine neuere Distribution wechselt. Dann läuft der Rebuild aber ohnehin automatisch beim Image-Build mit.
Wiederholungstäter: Nicht nur Mastodon betroffen
Das Muster ist übrigens kein Mastodon-Spezialfall. charlock_holmes ist ein echter Wiederholungstäter und bricht seit Jahren bei praktisch jedem größeren Debian- und Ubuntu-Upgrade – auch andere Ruby-Anwendungen wie GitLab (bei Installation aus dem Quellcode) oder Discourse setzen bzw. setzten auf das Gem. Generell trifft es alle Ruby-Gems mit nativen Extensions, die gegen System-Bibliotheken gelinkt sind: pg gegen libpq, nokogiri gegen libxml2 (sofern nicht die mitgelieferte Version genutzt wird), ffi, sqlite3 und Co. Ein Major-Bump der jeweiligen Bibliothek beim Distro-Upgrade – und die kompilierte Extension zeigt ins Leere.
Fazit
ICU-Major-Version-Bumps bei Distro-Upgrades brechen regelmäßig kompilierte Ruby-Gems – und apt kann uns nicht warnen, weil es die Gem-Welt schlicht nicht kennt. Der wichtigste Reflex nach jedem Distro-Upgrade bei selbstgehosteten Rails-Anwendungen lautet daher: bundle pristine (nicht gem pristine!) für alle Gems mit nativen Extensions laufen lassen. Das baut sämtliche Extensions gegen die aktuellen System-Bibliotheken neu und respektiert dabei die Bundler-Build-Konfiguration. Fünf Minuten Aufwand – und der nächste LoadError bleibt uns erspart.