Deep Dive
ntfy: Wer klopft da um fünf Uhr früh?
Wie ein kleiner, selbst betriebener Push-Dienst jede Meldung meines Homelabs aufs Handy bringt, wer dort schreiben darf und warum die gefährlichste Meldung die ist, die nie ankommt.
Sonntag, kurz nach fünf. Auf dem Nachttisch leuchtet das Handy auf und brummt einmal kurz. Ich greife danach, noch halb im Schlaf, und lese: „Update“, darunter eine Liste von Diensten. Bevor ich den Text wirklich verstehe, stelle ich mir zwei Fragen, die wohl jeder kennt, der schon mal eine verdächtige SMS bekommen hat. Wer redet da mit mir? Und warum sollte ich dem glauben?
Diese Woche kam eine dritte Frage dazu, und die ist unangenehmer. Was ist mit den Meldungen, die gar nicht erst ankommen?
Um alle drei geht es hier. Mein Homelab, also die kleine Serverlandschaft bei mir zu Hause, über die diese Serie erzählt, schickt mir jede wichtige Nachricht über denselben Weg: einen kleinen Dienst namens ntfy (gesprochen wie „notify“). In Teil 6 kam er nur in einem Absatz vor. Er hat mehr verdient.
Ein Briefkasten mit Klingel
Fangen wir beim Handy an. Eine Push-Nachricht ist eine Meldung, die ein Server dem Handy von sich aus schickt, statt dass die App alle paar Minuten nachfragen muss. Bei den großen Apps läuft das fast immer über Google (auf Android) oder Apple (auf dem iPhone). Die App-Anbieter geben ihre Nachricht dort ab, und Google oder Apple liefern sie aus.
ntfy ist ein Open-Source-Programm, das diese Rolle selbst übernimmt. Man stellt es auf einen eigenen Server, und es tut im Grunde nur zwei Dinge: Es nimmt Nachrichten an, und es reicht sie an alle weiter, die zuhören. Dazwischen steht ein Thema (englisch topic), ein benannter Kanal. Am besten stellt man sich einen Briefkasten mit Namensschild vor, an dem eine Klingel hängt. Wer etwas einwirft, lässt bei allen Abonnenten des Kastens das Handy klingeln.
Warum nicht einfach den öffentlichen Dienst ntfy.sh nehmen, den der Entwickler betreibt? Dort reicht im Prinzip der Name eines Themas, um mitzulesen. Bei meinem eigenen Server bestimme ich, wer schreiben und wer lesen darf, und die Texte liegen nur bei mir. Die Android-App hält dafür selbst eine Verbindung zu meinem Server offen; eine Weiterleitung über ntfy.sh oder Google habe ich nicht eingerichtet.
Einwerfen kann jedes Skript
Überzeugt hat mich ntfy aber vor allem durch die Art, wie man etwas einwirft. ntfy spricht HTTP, dieselbe Sprache, in der ein Browser Webseiten abruft. Jedes Skript, das eine Webadresse aufrufen kann, kann also auch eine Meldung schicken. Auf Linux reicht dafür curl, ein kleines Kommandozeilenprogramm für genau solche Aufrufe:
curl -H "Authorization: Bearer <token>" \
-H "Title: Test" -d "Hallo vom Server" \
https://ntfy.example/<thema>
Die erste Zeile weist den Absender aus. Ein Token ist ein langes, zufälliges Passwort für Programme. Dann kommen Titel und Text, zuletzt die Adresse des Themas. Mehr braucht es nicht, keine Bibliothek, kein Konto bei irgendwem. Merk dir die Anführungszeichen in diesem Befehl, sie spielen am Ende noch eine Rolle.
Warum es nicht zu Hause läuft
So weit das Prinzip. Die nächste Frage ist, wo dieser Briefkasten steht. Mit Absicht nicht bei mir zu Hause.
Zu Hause läuft fast alles auf einem einzigen Rechner, den ich pve1 nenne. Darauf laufen viele virtuelle Maschinen (VMs), also Rechner, die nur als Software existieren, einer pro Dienst. Fällt pve1 aus, fallen alle VMs mit. Ein Meldedienst auf pve1 wäre wie ein Rauchmelder, der seinen Alarm über das Telefon im brennenden Zimmer absetzen soll.
Deshalb läuft ntfy auf einem kleinen gemieteten Server bei Hetzner, demselben, auf dem auch meine VM-Sicherungen landen. Davor sitzt ein Reverse Proxy. Das ist ein Pförtner, der alle Anfragen aus dem Internet entgegennimmt, die Verschlüsselung (HTTPS) übernimmt und jede Anfrage an das richtige Programm dahinter weiterreicht. ntfy selbst nimmt nur Anfragen an, die durch diesen Pförtner kommen.
Das nächste Bild zeigt den ganzen Weg einer Meldung, von zu Hause bis aufs Handy.
Oben im Bild stehen die Absender, die wir gleich kennenlernen. Sie alle nehmen denselben Weg. ntfy merkt sich jede Nachricht außerdem eine Weile, und war das Handy im Funkloch, holt die App sie nach.
Ehrlicherweise hat der Standort eine Grenze. Der Briefkasten überlebt einen Ausfall zu Hause, die meisten Absender aber nicht. Hängt pve1, schweigen sie mit. Diese Lücke ist noch offen, mehr dazu in Teil 6.
Wer schreiben darf und wer nur lesen
Jetzt zur Frage aus dem Schlafzimmer: Warum glaube ich der Nachricht?
Weil auf meinem Server niemand etwas darf, dem ich es nicht ausdrücklich erlaubt habe. In der Konfiguration steht als Grundregel „deny“: Ohne Erlaubnis kein Lesen und kein Schreiben. Darauf liegen Regeln für genau drei Benutzer, und das Bild zeigt, wer was darf.
Der Sende-Benutzer ist der für die Maschinen. Sein Token steckt in jedem Absender: auf jeder VM in einer Datei, die nur der Administrator der VM lesen kann, dazu bei Uptime Kuma und in der Paperless-Kette. Strenggenommen darf er auch lesen, was er nie braucht.
Der Lese-Benutzer gehört der App auf meinem Handy. Er darf meine Themen abonnieren und sonst nichts. Ginge mein Handy verloren, könnte der Finder darüber mitlesen, aber keine falschen Alarme verschicken. Der dritte, ein Admin, ist für mich, wenn ich Benutzer oder Regeln ändere.
Die Antwort auf „Warum glaube ich das?“ ist also ziemlich nüchtern. In das Thema schreiben kann nur, wer das Token des Sende-Benutzers hat, und der Weg dorthin ist verschlüsselt. Das sagt mir allerdings nur, wer geschrieben hat. Ob das Skript mit seiner Meldung recht hat, ist eine andere Frage.
Fünf Absender, fünf Tonlagen
Wer schreibt mir nun eigentlich? Fünf Absender, und jeder hat seinen eigenen Ton.
Uptime Kuma ist ein Wächter, der jede Minute bei 22 Stellen anklopft, bei Diensten, DNS-Servern, dem Netzlaufwerk und auch bei ntfy selbst. Antwortet etwas nicht, versucht er es noch zweimal, bevor er Alarm schlägt. Zwischen ein und drei Uhr nachts schweigt er für die meisten Dienste, weil dann die VM-Sicherung den Rechner auslastet und kurze Aussetzer normal sind.
Die Wachen auf InfluxDB sind kleine Skripte. InfluxDB ist eine Datenbank für Messwerte über die Zeit, etwa freien Arbeitsspeicher oder Temperaturen. Alle fünf Minuten schauen die Wachen hinein und melden sich nur beim Kippen und bei der Entwarnung: wenn einer der Rechner, die meine Software-Tests ausführen, knapp an Speicher wird, wenn pve1 nicht mehr antwortet, wenn eine Platte zu warm wird.
Das Backup-Skript sichert jede Nacht um halb vier alle Dienste einer VM (wie, steht im Artikel über compose-backup). Das Update-Skript holt sonntags um fünf neue Versionen. Das war meine Nachricht vom Anfang. Und die Paperless-Kette, die eingescannte Briefe liest und einsortiert, meldet jeden fertigen Brief mit Titel, Absender, Dokumenttyp und Link.
Damit das Handy nicht bei allem gleich laut wird, gibt jeder Absender eine Priorität von 1 bis 5 mit. Die App macht daraus Verhalten: 1 und 2 kommen leise an, 3 ist normal, 4 und 5 werden zunehmend aufdringlich. Dazu kommt ein Schlagwort, das die App als kleines Bild vor den Titel setzt, etwa eine Diskette fürs Backup. So weiß ich auf dem Sperrbildschirm schon vor dem Lesen, wer da redet.
| Absender | Meldung | Priorität |
|---|---|---|
| Paperless-Kette | Brief verarbeitet | 2 |
| Backup-Skript | Backup ok · Platte über 85 % · Backup-Fehler | 2 · 4 · 5 |
| Update-Skript | was aktualisiert wurde · Fehler | 3 · 5 |
| Uptime Kuma | Dienst weg oder wieder da | 4 |
| Wachen auf InfluxDB | Speicher knapp · pve1 weg · Entwarnung | 4 · 5 · 3 |
Die Paperless-Kette war am Anfang zu gesprächig. Ich hatte Paperless selbst melden lassen, sobald sich ein Dokument ändert. Nur ändert die Kette jedes Dokument viermal, und so kamen pro Brief vier Nachrichten, die ersten noch ohne Absender und Typ. Jetzt schickt erst der letzte Schritt der Kette eine einzige Nachricht, wenn alles fertig ist.
Was schiefgehen kann
Ein Meldeweg hat eine fiese Eigenschaft: Wenn er kaputt ist, sagt er es einem nicht. Zweimal habe ich das bisher erlebt, einmal laut und einmal sehr leise.
Aus der Praxis: Mitte September behauptete die App plötzlich, mein Lese-Benutzer habe keine Berechtigung. Der Benutzer stimmte, die Regeln auch. Der Fehler saß eine Ebene davor, beim Pförtner. Der Proxy auf dem Server bei Hetzner reichte Anfragen an einen Rechnernamen weiter statt an eine feste Adresse. Diesen Namen löste er mal zu einer IPv6-Adresse auf, die er von seinem Platz aus gar nicht erreichen konnte, und mal zu gar nichts. Dann antwortete er mit 502, „der hinter mir antwortet nicht“, und die App machte aus dem gescheiterten Anmeldeversuch eine fehlende Berechtigung. Seitdem zeigen alle sieben Einträge des Proxys auf eine feste interne Adresse.
Die zweite Geschichte ist ganz frisch, aus der letzten Nacht.
Aus der Praxis: Bei einer Durchsicht der Skripte fiel in der Nacht zum 30. Oktober eine einzige Zeile auf. Im Backup-Skript standen vor den Anführungszeichen des
curl-Aufrufs, genau denen aus dem Beispiel weiter oben, umgekehrte Schrägstriche (Backslashes), also\"…\"statt"…". In einem Shell-Skript halten Anführungszeichen zusammen, was zusammengehört, etwa „Title: Backup ok“ samt Leerzeichen. Ein Backslash davor macht aus dem Anführungszeichen ein gewöhnliches Zeichen.curlbekam also lauter zerschnittene Stücke mit Anführungszeichen dran, darunter eine Adresse, die keine war, und scheiterte. Dahinter stand|| true: Wenn das schiefgeht, tu so, als wäre nichts. Gedacht war das, damit eine verlorene Meldung nie das Backup selbst scheitern lässt. Gewirkt hat es so, dass seit Anfang September keine einzige Backup-Meldung ankam. Kein „Backup ok“, kein Fehler und keine Warnung bei voller Platte, obwohl genau diese Warnung nach einer vollgelaufenen Platte im September dazugekommen war. Das Update-Skript, getrennt davon geschrieben, meldete sich zuverlässig jeden Sonntag. Das Handy brummte, der Kanal wirkte lebendig, und niemand vermisste die leisen Nachrichten von halb vier. Die Korrektur kam noch in derselben Nacht. Beim Test bekam der alte Aufruf gar keine Antwort, der neue ein sauberes OK vom Server, und danach ging das Skript auf alle elf VMs. Bezeichnend: In derselben Nacht standen zwei Systemplatten bei 99 und 100 Prozent, genau das, wovor die verschluckte Warnung hätte warnen sollen.
Was mich daran am meisten ärgert: Bei den Wachen auf InfluxDB hatte ich es richtig gemacht. Dort habe ich die Schwelle zum Test künstlich auf 100 % gesetzt und gewartet, bis der Alarm wirklich auf dem Handy war. Beim Backup-Skript habe ich mir diesen Schritt gespart. Ein stilles || true im Alarmweg verschluckt genau den Fehler, von dem man hören will, und deshalb muss man den Alarmweg selbst testen, nicht nur das, was er melden soll.
Was ich mitnehme
Zurück zum Sonntagmorgen. Wer redet da mit mir? Einer von fünf Absendern, und am Titel und dem kleinen Bild davor sehe ich auch im Halbschlaf, welcher. Warum glaube ich ihm? Weil nur das Token des Sende-Benutzers in meine Themen schreiben darf und der ganze Weg verschlüsselt ist. Das war der einfache Teil.
Die dritte Frage begleitet mich länger. Sieben Wochen Stille vom Backup-Skript haben sich angefühlt wie sieben Wochen „alles gut“. Seitdem lese ich Stille anders. Sie ist keine Meldung, nur das Fehlen einer. Für jeden neuen Absender will ich deshalb künftig zuerst eine echte Testnachricht auf dem Handy brummen hören, bevor ich ihn fertig nenne.
Wie ntfy mit den drei Sicherungsebenen, Uptime Kuma und InfluxDB zu einem Ganzen wird, steht in Teil 6: Sicherung und Überwachung. Und falls du dich fragst, was um halb vier eigentlich gesichert wird und seit der Korrektur auch wieder gemeldet: Das ist die Geschichte von compose-backup.