PushOver.net: Scriptgesteuerte Push-Notifications für iOS und Android

Letztens durfte ich einen relativ langwierigen Download via SFTP vornehmen. Da ich allerdings nicht vor dem Rechner sitzen bleiben und auf die Beendigung warten wollte, bin ich immer wieder an den PC gegangen, um den aktuellen Status zu sehen. Dabei kam mir die Idee, dass es doch toll wäre, einen Dienst zu haben, den man per einfacher REST-API ansprechen könnte, um einen Status mitzuteilen. Der Dienst könnte es dann ermöglichen, diesen Status z.B. über einen Browser abzurufen.

@servicecase18 hatte jedoch einen viel tolleren Tipp: Er verwendet für soetwas den Dienst PushOver.net, der im Grunde das macht, was ich möchte - nur besser. Sie bieten ebenfalls eine einfache REST-API an, versenden den Status dann jedoch als Push-Notification an ihre iOS- und Android-App. So hat man den Status direkt auf dem Smartphone. 🙂

Wenn man sich registriert, erhält der neue Account einen User Key. Nachrichten, die an den User Key geschickt werden, landen auf jedem Gerät, bei dem man mit dem gleichen Account eingeloggt ist. Zudem kann man mit seinem Account auch Applications registrieren, wobei jede Application ein Application Token erhält. Um nun eine Push-Notification zu senden, übergibt man der REST-API mindestens den User Key, den Application Token und die eigentliche Nachricht. Optional gibt es noch weitere Werte, die man setzen kann. Das war's. So einfach.

pushover_appstore pushover_login

PushOver App

Das ganze ist ziemlich praktisch. Zum Beispiel kann man es dazu nutzen, seine Serverüberwachung, die man mit Mercurial implementiert hat, so abzuändern, dass sie nicht einfach nur eine Statusdatei erzeugt, sondern einem stattdessen lieber eine Push-Notification schreibt, wenn sich der Status ändern sollte. So ist man auf der Höhe der Zeit, wenn es darum geht, ein nettes Monitoring zu implementieren. 😀

pushover_notification pushover_list

Serverüberwachung mit Push-Notifications

Da ich allerdings nicht die Absicht habe, allen Scripten ein eigenes Application Token zu besorgen - obwohl das möglich wäre - habe ich mir etwas anderes überlegt: Ich hole mir pro Server nur ein Application Token und verwende ein Script, das sich zentral um das Versenden der Push Notifications kümmert. Das hat den zusätzlichen Vorteil, dass nicht jedes kleine Script wissen muss, wie der Versand funktioniert. Stattdessen gibt es einen Ordner, in das ein Script einfach eine Datei ablegt. Der Name der Datei ist der Titel des Push Notification und der Inhalt der Datei ist auch gleichzeitig der Inhalt der Push Notification. 🙂

Da ich allerdings nicht wollte, dass das Versandscript - "pushinfo.phs" - als Root läuft, habe ich mich dazu entschieden, dass versandte Nachrichten nicht gelöscht werden. Stattdessen wird in einer Statusdatei das letzte Bearbeitungsdatum der Dateien notiert. Sollte sich die Datei ändern (und damit das entsprechende Datum) wird der Inhalt erneut versendet.

Als erstes muss das ganze in der Datei "pushinfo.conf.phs" konfiguriert werden. Der User und die Gruppe, unter denen das Script laufen, heißen bei mir einfach "pushinfo". Der PUSHINFO_PATH muss von dem Nutzer lesbar, der STATUS_PATH zudem auch schreibbar sein. Beim PUSHOVER_APPLICATION_TOKEN und dem PUSHOVER_USER_KEY handelt es sich um die oben beschriebenen Werte von PushOver.net. Mit TITLE_PREPEND kann man dem Titel der Push Notifications statisch noch einen Text voranstellen - das ist praktisch, um z.B. den sendenden Server zu identifizieren. Das ganze ist zudem so konfiguriert, dass Unterstiche im Dateiname durch Leerzeichen ersetzt werden, bevor die Push Notification verschickt wird.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php

  # defines the pushover.net API path
 define("PUSHOVER_API_PATH", "https://api.pushover.net/1/messages.json");

  # defines the pushover.net application token
 define("PUSHOVER_APPLICATION_TOKEN", "<REPLACE-ME>");

  #defines the pushover.net user key
 define("PUSHOVER_USER_KEY", "<REPLACE-ME>");

  # defines where the push notifications are located
 # do not forget the trailing slash
 define("PUSHINFO_PATH", "/srv/pushinfo/messages/");

  # defines where the script stores its status
 # do not forget the trailing slash
 define("STATUS_PATH", "/srv/pushinfo/status/status/");

  # defines which string shall be prepended to the title
 # do not forget some nice separator like a colon and a space
 define("TITLE_PREPEND", "");

  # defines the user the script shall run as
 define("PROCESS_USER", "pushinfo");

  # defines the group the script shall run as
 define("PROCESS_GROUP", "pushinfo");

  # defines the string in the file name that gets
 # replaced by white spaces for the title
 define("REPLACE_FROM_TITLE", "_");

  # defines the string in the file name the
 # REPLACE_FROM_TITLE gets replaced with for the title
 define("REPLACE_TO_TITLE", " ");

  # defines the user agent string for CURL
 define("USER_AGENT", "PushInfo 0.1");

  // define timeout for PHP execution (as int)
  // alternatively defines a lock file (as string)
  define("TIMEOUT", "/srv/pushinfo/status/lock");

?>

In der Datei "pushinfo.func.phs" ist der eigentliche Code zum Versenden einer Push Notification. Das Tolle daran: Man könnte die Funktionsweise von Pushinfo an dieser Stelle erweitern - z.B. durch den gleichzeitigen Versand von E-Mails. Das aber nur am Rande.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

  function push_message($title, $message) {
    $result = false;

    // set API values
    $apiHeader = array("title"   => $title,
                       "message" => $message,
                       "token"   => PUSHOVER_APPLICATION_TOKEN,
                       "user"    => PUSHOVER_USER_KEY);

    // configure curl
    $options = array(
      CURLOPT_POST           => true,
      CURLOPT_POSTFIELDS     => $apiHeader,
      CURLOPT_RETURNTRANSFER => true,
      //CURLOPT_SAFE_UPLOAD    => true,
      CURLOPT_USERAGENT      => USER_AGENT,
      CURLOPT_VERBOSE        => 1
    );

    // execute API request
    $curl = curl_init(PUSHOVER_API_PATH);
    curl_setopt_array($curl, $options);
    $output = curl_exec($curl);
    $header = curl_getinfo($curl);
    curl_close($curl);

    // check result
    if (200 === $header["http_code"]) {
      $json = json_decode($output, true);
      if (is_array($json)) {
        if (isset($json["status"])) {
          $result = (1 === $json["status"]);
        }
      }
    }

    return $result;
  }

?>

In der Datei "pushinfo.phs" ist dann die eigentliche Hauptlogik versteckt - das Droppen der Privilegien, das Lesen der Dateien, das Prüfen auf Änderung und das Aufrufen des Notification-Versands und Wegschreiben des Status.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php

  require_once(dirname(__FILE__) . "/../unchroot/unchroot.phs");

  require_once(dirname(__FILE__) . "/pushinfo.conf.phs");
  require_once(dirname(__FILE__) . "/pushinfo.func.phs");

  function pushinfo() {
    // first of all drop privileges
    if (force_unroot(PROCESS_USER, PROCESS_GROUP)) {
      // disallow concurrency (either by timeout or by lock file)
      if (disallow_concurrency(TIMEOUT)) {
        // read folder list
        $dir_handle = opendir(PUSHINFO_PATH);
        $files      = array();
        if (false !== $dir_handle) {
          $filename = false;
          do {
            $filename = readdir($dir_handle);

            if (false !== $filename) {
              if (is_file(PUSHINFO_PATH . $filename)) {
                array_push($files, $filename);
              }
            }
          } while (false !== $filename);

          closedir($dir_handle);
        }

        // sort the file array
        sort($files, SORT_STRING | SORT_FLAG_CASE);

        // handle found files
        if (0 < count($files)) {
          for ($index = 0; $index < count($files); $index++) {
            // check if we know the last modification time of this file
            $current_mtime = filemtime(PUSHINFO_PATH . $files[$index]);
            $handle_file   = true;
            if (file_exists(STATUS_PATH . $files[$index])) {
              $last_mtime = file_get_contents(STATUS_PATH . $files[$index]);
              if (false !== $last_mtime) {
                $handle_file = ($current_mtime != $last_mtime);
              }
            }

            // we need to handle this file
            if ($handle_file) {
              // retrieve title and message
              $message = file_get_contents(PUSHINFO_PATH . $files[$index]);
              $title   = TITLE_PREPEND . str_replace(REPLACE_FROM_TITLE, REPLACE_TO_TITLE, $files[$index]);

              // try to push the message
              if (push_message($title, $message)) {
                // save new modification time
                file_put_contents(STATUS_PATH . $files[$index], $current_mtime);
              }
            }
          }
        }

        // free lock
        allow_concurrency(TIMEOUT);
      }
    }
  }

  pushinfo();

?>

Das ganze sollte man nun natürlich regelmäßig ausführen lassen - z.B. per Cron:

1
2
# send pushover.net notifications
*/5 * * * * root php /srv/pushinfo/pushinfo.phs >/dev/null 2>&1

Damit habt ihr alles eingerichtet, was ihr benötigt und könnt wunderschöne neue Spielereien ausprobieren. Noch toller wird es, wenn man das ganze mit einem Monitoring verbindet. So könntet ihr euch z.B. stündlich die neuen, durchschnittlichen Ressourcenauslastungen zuschicken lassen oder euch gar über fehlgeschlagene Loginversuche informieren lassen. 🙂

Wer übrigens nicht so viel copy&paste betreiben will, kann sich Pushinfo auch über das Mercurial-Repository herunterladen. 🙂

1
2
hg clone https://hg.nhg.name/pushinfo/
hg clone https://hg.nhg.name/unchroot/

Drückende Grüße, Kenny

Schreibe einen Kommentar

Um Ihnen beim weiteren Kommentieren auf dieser Webseite die erneute Eingabe Ihrer Daten zu ersparen, wird beim Absenden Ihres Kommentars ein Cookie an Ihren Browser gesendet und von diesem gespeichert. Mit dem Absenden eines Kommentars auf dieser Webseite stimmen Sie der Speicherung und Übertragung dieses Cookies explizit zu.

Pflichtfelder sind mit * markiert.