📬 NTFY.sh: Push Nachrichten an iOS, Android, PC, Command-Line, E-Mail & Telefon

Begonnen von Torxgewinde, 09 Februar 2024, 15:35:12

Vorheriges Thema - Nächstes Thema

isy

Laut git hat die iOS Version keine Möglichkeit, Anhänge zu empfangen.
Info vom Dez. 2024.
Siehe https://github.com/binwiederhier/ntfy/issues/1226

Nachtrag: Ich habe dort mal nachgefragt. Ein Grund sei, dass "PWA" auf iOS keine Attachments erlaubt.
Siehe auch:
https://www.heise.de/news/Apple-bestaetigt-Keine-Progressive-Web-Apps-mehr-fuer-iOS-in-der-EU-9630089.html
Ein Weg wird erst zu einem Weg, wenn man ihn geht

Torxgewinde

Uuuh, das ist ja hart. Ich nutze selbst nur noch Android/GrapheneOS und habe das Problem in iOS nicht mitbekommen.

Am produktivsten und zukunftsgerichtet ist sicherlich dem Entwickler ein Ticket da zu lassen, oder sich dem vorhandenem Ticket anzuschließen. Ich glaube dies hier bezieht sich dann genau auf die richtig iOS-App: https://github.com/binwiederhier/ntfy-ios/pull/8

Unter iOS habe ich früher sehr gerne PushOver genutzt, das war ausgereift, aber eben nicht selbstgehostet. Vielleicht ist das für iOS-Empfänger dann die bessere Wahl. In FHEM kann man beide Dienste (NTFY, PushOver ...) gleichzeitig nutzen und die jeweiligen Nachrichten über die verschiedenen Kanäle raussenden.

isy

Ein Weg wird erst zu einem Weg, wenn man ihn geht

isy

Soll ich das Wiki Update schreiben?
Ein Weg wird erst zu einem Weg, wenn man ihn geht

isy

Die Web App auf MacOS und iOS zeigt die Attachments an.
Beide OS erlauben die Anzeige auf dem Home Bildschirm (sieht aus, wie eine App).
Benachrichtigungen funktionieren

OK, geht also
Ein Weg wird erst zu einem Weg, wenn man ihn geht

Torxgewinde

Zitat von: isy am 15 Februar 2025, 13:47:19Soll ich das Wiki Update?
Sehr gerne, am besten auch mit dem Hinweis, dass/wie die WebApp installiert/gebookmarked werden sollte.

isy

Update ist eingetragen, ich hoffe die Info passt an die Stelle.
Ein Weg wird erst zu einem Weg, wenn man ihn geht

Torxgewinde

Passt, ich habe deinen Link zum GitHub Issue auch noch mit aufgenommen. Danke!

passibe

Nur zur Info, das hier
Zitat von: byterazor am 02 März 2024, 13:00:19Es gibt für NTFY_CLIENT jetzt eine update URL: https://rm.byterazor.de/upd-fhem-ntfy/controls_byterazor-fhem-ntfy.txt
scheint nicht mehr zu funktionieren. Die Seite ist offline (siehe hier).

Vielleicht müsste man darauf im Wiki hinweisen? Oder man kann das Modul irgendwo anders bereitstellen?



Ansonsten hier noch meine eher minimale und vermutlich verbesserungswürdige Implementierung, die aber für meinen Anwendungsfall ausreicht. Sie unterstützt keine Anhänge (weil iOS), aber das Senden von Kommandos über Buttons (ntfy nennt das "Actions").

Die ntfy-App am Handy sendet dazu über eine URL das jeweilige JSON-Objekt an eine Flask-Middleware, die dann wiederum den Befehl an FHEM sendet (und sich um so Dinge wie die Authentifizierung und den CSRF-Token kümmert; den Flask-Part kann ich, falls Interesse besteht, demnächst auch mal teilen).

Hier der Code für 99_myUtils.pm:
sub ntfy {
    my ($message, $priority, $tags, $title, $cmd1, $button1, $cmd2, $button2, $cmd3, $button3) = @_;
   
    # Set defaults
        if (!defined $priority) {$priority = 3}
        if (!defined $tags) {$tags = "house_with_garden"};
        if (!defined $title or $title eq "") {$title = "FHEM"}
        if ($tags !~ m/house_with_garden/) {
            $tags = $tags.",house_with_garden";
        }

    # Prepare headers
        my $param = {
            url => "https://ntfy.example.org/fhem",
            method => "POST",
            timeout => 10,
            callback => sub() {},
            header => "Content-Type: application/json\r\nAuthorization: Bearer REDACTED\r\np: $priority\r\nta: $tags\r\nt: $title",
            data => $message
        };

    # For actions:
        my $actions = "";
        my $url = "https://example.org/REDACTED";
        my $type = "http";
        my $jsonheader = "headers.Content-Type=application/json";

        # Build action header
        if (defined $cmd1) {
            $actions = "$type, $button1, $url, $jsonheader, body={\"cmd\":\"$cmd1\"}";
        }
        if (defined $cmd2) {
            $actions .= "; $type, $button2, $url, $jsonheader, body={\"cmd\":\"$cmd2\"}";
        }
        if (defined $cmd3) {
            $actions .= "; $type, $button3, $url, $jsonheader, body={\"cmd\":\"$cmd3\"}";
        }

        # Add header
        if ($actions ne "") {
            $param->{header} .= "\r\nActions: $actions";
        }

    # Make request
    HttpUtils_NonblockingGet($param);
}

sub ntfyBat {
    my ($msg, $tags) = @_;
    $tags = defined $tags && length $tags ? ",$tags" : "";
    ntfy($msg,4,"battery".$tags)
}

sub ntfyError {
    my ($msg, $tags) = @_;
    $tags = defined $tags && length $tags ? ",$tags" : "";
    ntfy($msg, 5, "x".$tags);
}

sub ntfyErrorSilent {
    my ($msg, $tags) = @_;
    $tags = defined $tags && length $tags ? ",$tags" : "";
    ntfy($msg,2,"x".$tags)
}

sub ntfyOk {
    my ($msg, $tags) = @_;
    $tags = defined $tags && length $tags ? ",$tags" : "";
    ntfy($msg,2,"white_check_mark".$tags)
}

Nutzung z.B.:
ntfy("Nicht Stören aktiviert.",4,"shushing_face,crescent_moon","","set sys_dnd off","Deaktivieren");Sieht dann aus wie im angehängten Screenshot.

Geht natürlich z.B. auch für eine eigentlich dumme Spülmaschine, die nur über eine Leistungsmesssteckdose smart gemacht wurde, damit man direkt vom Handy aus mit Klick auf die "Spülmaschine fertig"-Benachrichtigung den Status wieder auf "ausgeräumt" setzen kann.

Oder ohne Action dann z.B.:
ntfy("Spülmaschine läuft!",2,"plate_with_cutlery,arrows_counterclockwise");und z.B. für eine Batteriebenachrichtigung:
ntfyBat("Die Batterie von ".AttrVal($NAME,"alias","")." ist fast leer! (".$EVTPART1." %)");

Torxgewinde

Hallo @passibe,
Schade, dass das NTFY device von @Byterazor nicht mehr online ist. Ich werde im Wiki einen Hinweis ergänzen, dass die Seite zeitweise offline ist und die Quelle deswegen nicht verfügbar ist.
Edit #1: Ich habe eine weitere Quelle in @Byterazors gitea gefunden, man muss sich die Module dann allerdings von Hand herunterladen: https://gitea.federationhq.de/byterazor/FHEM-NTFY/src/branch/main/FHEM, Habe es auch im Wiki ergänzt.

Ein paar Gedanken zu deiner Implementation:
Ich sehe, dass den NTFY Server kontaktierst und dabei in der Funktion die Authentifizierung hardgecodet hattest. Das ist IMHO nicht ideal. Das HTTPMOD basierte Device aus dem Wiki zum Beispiel nutzt ggf. vorhandene Readings "username"/"password" oder alternativ Attribute "username"/"password", falls man HTTP-Basic-Auth nutzen will. Im Kommentar steht wie das dann geht. Bei dem Curl-basiertem-E-Mail-Skript (https://wiki.fhem.de/wiki/E-Mail_senden#cURL_(Linux,_Windows)) habe ich es auch verbessert und die verschleierte Ablage mittels "getKeyValue()" und "setKeyValue()" genutzt. Das wäre hier ggf. auch sinnvoll anzuwenden.

Um auf die Action zu reagieren fügst du einen Button hinzu der eine URL beim Anklicken aufruft, also ein klassischer Webhook (bei dir: https://example.org/REDACTED) muss auch vorhanden sein, den der NTFY-Client dann auslösen kann. Sehr wichtig ist bei dem Webhook dann welche Kommandos akzeptiert werden. In deinem Fall sieht das erstmal so aus, als würden FHEM Kommandos ausgeführt. Ich würde bei dem Webhook noch darauf hinweisen, dass dort unbedingt nicht einfach der Input an FHEM zur Ausfürhung übergeben werden soll. Sonst könnte man nur mit Kenntniss der Adresse beliebigen Code in FHEM mit den Nutzerrechten des FHEM-Users ausführen lassen. Da nicht weiter ausgeführt ist wie der Webhook hier realisiert ist, würde ich mindestens den Hinweis als sehr sinnvoll vorschlagen.
Anstelle eines Webhooks, und damit alternativen Kommunikationskanals, wäre es aber auch total praktisch einfach wieder NTFY zu nutzen. NTFY kann ja auch Daten per HTTP-GET in einem Topic erhalten, das kann man doch eigentlich als Action hinterlegen und man braucht damit keinen Webhook anlegen, sondern lauscht auf reinkommende NTFY Messages auf dem "Action-Topic" und reagiert auf die Nachrichten. Das wäre in meinen Augen weniger Angriffsfläche, als einen, vom Internet aus erreichbaren Webhook betreiben zu wollen. Wie man per NTFY-Device reinkommende Befehle auf ein paar RegEx geprüfte Befehle beschränkt, hatte ich mal gepostet: https://forum.fhem.de/index.php?topic=139621.msg1324013#msg1324013

HTH und beste Grüße!

passibe

Danke für deine ausführliche Rückmeldung!



Tatsächlich ist der Pfad der URL die einzige Authentifizierung. Das sind bei mir 64 Zufallszeichen, also vermutlich sogar schwieriger bruteforcebar als das durchschnittliche Passwort.

Aus technischer Sicht ist die Übermittlung im Pfad mE kein großes Problem, weil mit HTTPS der Pfad natürlich auch verschlüsselt ist und es deshalb egal ist, ob ich das "Passwort" im Pfad sende oder als Header (was z.B. der Fall wäre, wenn man Nachrichten an das Action-Topic schicken würde; dann wäre das Passwort im Header statt in der URL). Die Angriffsfläche ist mE deshalb ähnlich. Bei beiden Versionen brauche ich irgendein Secret, nur die Stelle, an der das Secret steht, ist eine andere.

Zu berücksichtigen ist aber in der Tat der psychologische Unterschied, nämlich, dass Menschen dazu neigen, mit URLs weniger vorsichtig umzugehen, als mit Passwörtern. Da ich das aber nur für mich selbst benutze (und jedenfalls in der iOS-App meines Wissens nach die URL aus so einem Button auch gar nicht ausgelesen werden kann), ist das Risiko, dass die URL leaked – für meinen Anwendungsfall – gering. Ich hatte z.B. auch immer meine Terminal History deaktiviert, als ich diese Funktion über curl o.ä. getestet habe, so wie ich es machen würde, wenn ich ein Passwort im Klartext als Teil eines Kommandos in die Shell eingeben müsste.

Insofern ist das etwas ähnlich zur Diskussion über SSH Keys vs. Passwörter für SSH, denn der Key ist eigentlich nichts anderes als ein besonders langes Passwort bzw. Menschen gehen mit dem Key anders um als mit Passwörtern (siehe dazu hier).



Beschränkungen, welche FHEM-Kommandos ausgeführt werden können, wären natürlich problemlos möglich. Sei es über das jeweilige allowed-Device (allowedCommands/allowedDevices) oder besonders granular (ähnlich wie im von dir verlinkten Code) im Flask-Container selbst. Habe das bei mir aber aktuell nicht implementiert, weil ich keine Notwendigkeit sehe. Das kann ich aber gerne noch einbauen, bevor ich den Flask-Code hier poste.



Noch zur verschleierten Ablage irgendwelcher Credentials:
Darin sehe ich auch keinen Mehrwert. Es ist in der Tat nur Verschleierung, also security by obscurity. Denn es ist egal, ob die Credentials direkt in /opt/fhem/FHEM/99_myUtils.pm liegen, oder – wo setKeyValue() sie reinschreibt – in /opt/fhem/FHEM/FhemUtils/uniqueID. Letztlich kommt es bei beiden Dateien darauf an, ob sie world-readable sind oder nicht.

Soweit ich sehe, wird eine xx0-Permission weder für 99_myUtils.pm noch für uniqueID enforced. Ich habe gerade auf einem älteren System (bare-metal-Installation auf einem Pi) entdeckt, dass dort beide Dateien 664 sind. Es ist also ohnehin Sache des jeweiligen Benutzers, die Dateiberechtigungen entsprechend zu ändern, damit nicht jeder Benutzer auf dem System die Credentials auslesen kann.
Nur das Docker-Image macht es richtig und setzt die Berechtigungen für /opt/fhem rekursiv auf 644. Benutzt man Docker, dann sind 99_myUtils.pm und uniqueID damit sowieso gleich sicher, weil sie beide 660 sind.

Vielleicht umgekehrt sogar: Lege ich es direkt in 99_myUtils.pm ab, könnte ich vielleicht noch selbst auf die Idee kommen, dass ich die Berechtigung einschränken müsste. Während mir setKeyValue() ggfs. ein falsches Sicherheitsgefühl vermitteln könnte, nämlich, dass das irgendwo an einem besonders sicheren Ort abgelegt wird.



Das jedenfalls meine Gedanken dazu. Mir ist klar, dass das alles Dinge sind, über die sich ein Durchschnittsnutzer evtl. nicht so viele Gedanken macht. Aber ich meine auch, dass ein Durchschnittsnutzer sowieso erstmal ein paar Schritte von der Tastatur zurückgehen sollte und sich gut überlegen sollte was er macht, bevor er irgendwelche Dienste direkt über das Internet (also ohne VPN) abrufbar macht.

Nicht zuletzt muss ich dir aber Recht geben, dass diese Hinweise durchaus in den ersten Post dazugehört hätten. Deshalb noch einmal vielen Dank für deine Rückmeldung!