Mi, 10. Mai, 2023

Bash-Skript - einfaches Multi-Ping-Monitoring in Fast-Echtzeit

Ich brauchte da auf die Schnelle mal was: ich wollte mehrere Systeme mit Ping antesten, ob sie im Netz sind.
Die Liste der zu testenden Systeme sollte untereinander stehen und pro System angeben; ich bin erreichbar … oder auch nicht.

Ja natürlich haben wir ein System-Monitoring, aber das ist etwas träge - ich wollte das Ganze alle wenige Sekunden aktualisieren. Im Rack wollte ich Kabel ziehen und umstecken - und nebenher sehen, welches Gerät offline geht und wieder da ist.

Hier mein Bash-Skript:

#!/bin/bash
cfgfile=”$0.cfg”
while true; do
    clear
    echo ”>>>>> PING TEST :: $( date )”
    echo 
    cat ”$cfgfile” | grep ”^[a-z1-9]” | while read -r target
    do
        printf ”%-35s > ” $target
        if ! ping -c1 $target 2>&1 | grep ”transmitted”; then
            echo ”FAILED”
        fi
    done
    sleep 1
done

Im Endlos-Loop wird der Bildschirm gelöscht, ein Header mit Datum ausgegeben. Ich lese eine Datei aus, die meine Liste von Geräten enthält.
Ein Gerät wird einmalig angepingt (Parameter -c 1) - genau das geht sehr schnell. Es wird ausgegeben, ob es erreichbar war oder nicht.
Nach Abarbeiten der Liste folgt ein sleep 1 - und nach dieser nur kurzen Wartezeit geht es von vorn los.

Die Configdatei mit der Liste der Server - je einer pro Zeile - Leerzeilen, Kommentare u.ä werden ignoriert:

#
# my server list
#
server-01.example.com
server-02.example.com
server-03.example.com
server-04.example.com
server-05.example.com

Das Ganze sieht in der Ausgabe am Terminal grob aus:

 >>>>> PING TEST :: Mi 10 Mai 2023 16:37:41 CEST
server-01.example.com               > 1 packets transmitted, 1 received, 0% packet loss, time 0ms
server-02.example.com               > 1 packets transmitted, 1 received, 0% packet loss, time 0ms
server-03.example.com               > 1 packets transmitted, 1 received, 0% packet loss, time 0ms
server-04.example.com               > 1 packets transmitted, 1 received, 0% packet loss, time 0ms
server-05.example.com               > FAILED 

A Touch Of Glass - Update

Das war ja ärgerlich: die Loginseite erschien nicht mehr. Ich hatte es nicht bemerkt, weil mein Login gespeichert war und ich mich nicht mehr interaktiv eingeloggt habe.

Ich hatte zuletzt in den (Smarty) Templates alle Includes mit Hochkommas versehen.

{include file=’widgets.tpl’}

Dummerweise auch in der defaults.tpl, wo eine Variable drin war - das hier hingegen funktioniert natürlich nicht:

{include file=’$content’}

Die Hochkomma müssen bei Variablen weg. Das wurde nun korrigiert.

Weiterführende Links:

So, 16. April, 2023

Benachrichtigungen mit notify-send als root und per cron

Mein Backup-Skript (es dumpt lokale Datenbanken, unterstützt zig Hooks, sichert mit Restic/ Duplicity) kennt einen neuen Trick.
Auf meinem Linux-Desktop wollte ich Popup-Informationen zu einem im Hintergrund (Cronjob als root) gestarteten Skript sehen.

Und wie geht das?

Eine gängige Lösung ist notify-send (aus dem Paket libnotify).

$ notify-send –help
Usage:
  notify-send [OPTION?] <SUMMARY> [BODY] - create a notification
Help Options:
  -?, –help                        Show help options
Application Options:
  -u, –urgency=LEVEL               Specifies the urgency level (low, normal, critical).
  -t, –expire-time=TIME            Specifies the timeout in milliseconds at which to expire the notification.
  -a, –app-name=APP_NAME           Specifies the app name for the icon
  -i, –icon=ICON                   Specifies an icon filename or stock icon to display.
  -c, –category=TYPE[,TYPE…]     Specifies the notification category.
  -e, –transient                   Create a transient notification
  -h, –hint=TYPE:NAME:VALUE        Specifies basic extra data to pass. Valid types are boolean, int, double, string, byte and variant.
  -p, –print-id                    Print the notification ID.
  -r, –replace-id=REPLACE_ID       The ID of the notification to replace.
  -w, –wait                        Wait for the notification to be closed before exiting.
  -A, –action=[NAME=]Text…       Specifies the actions to display to the user. Implies –wait to wait for user input. May be set multiple times. The name of the action is output to stdout. If NAME is not specified, the numerical index of the option is used (starting with 0).
  -v, –version                     Version of the package.

Das funktioniert problemlos im Kontext des aktuell eingeloggten Benutzers mit einer X-Session.
Aber mit sudo oder als Cronjob als root?

Aber eines nach dem Anderen.

Wenn ich ein Skript mit sudo starte, dann enthält die Umgebungsvariable SUDO_USER denjenigen Benutzer, der das sudo ausgelöst hat. Das Skript läuft dann als User root. Das notify-send soll aber nicht auf einer X-Session des root Users etwas enblenden, sondern einem anderen unprivilegierten Benutzer. Hier kommt die Variable DBUS_SESSION_BUS_ADDRESS ins Spiel. Wenn man die UID des Benutzers kennt, kann man es sich zusammensetzen. Diese bekommt man mit id -u [Benutzername] … und der Benutzername ist ja in SUDO_USER.

Snippet:

if [ -n ”$SUDO_USER” ]; then
  export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $SUDO_USER)/bus
fi

Mit jenem Snippet kann man im selben Skript auslösen

su ”$SUDO_USER” -c ”notify-send ’Titel’ ’Mein Nachrichtentext’”

und es kommt bei … genau: “meinem” Desktop an und nicht bei root. Wunderbar!

Jetzt ist da noch die Sache mit dem Cronjob als root. Hier ist ja kein sudo. Aber das ist gar banaler als man denkt: man setzt einfach die Variable SUDO_USER im Environment - also vor den Deinitionen der Zeitangaben und Aufrufe:

$cat /etc/cron.d/client-backup 
SUDO_USER=axel
17 * * * * root /usr/local/bin/cronwrapper.sh 1440 /opt/imlbackup/client/backup.sh ’iml-backup’

Feintuning:

ein Simples notify-send “Titel” “Mein Nachrichtentext” verschwindet nach ein paar Sekunden. Wenn ein Fehler auftritt, dann möchte ich die Meldung sehen, lesen und proaktiv verschwinden lassen. Genau das erledigt der Parameter –urgency für uns

notify-send –urgency=critical “Fehler” “Da ging etwas schief :-/”

Hier noch ein kompletteres Bash Snippet:

if [ -n ”$SUDO_USER” ]; then
  export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $SUDO_USER)/bus
fi
(…)
# show a desktop notification using notify-send
# param  string   summary (aka title)
# param  string   message text
# paran  integer  optional: exitcode; if set it adds a prefix OK or ERRROR on summary and sets urgency on error
function notify(){
  local _summary=”$1”
  local _body=”$( date +%H:%M:%S ) $2”
  local _rc=”$3”
  local _urgency=”normal”
  if [ -n ”$DBUS_SESSION_BUS_ADDRESS” ]; then
    if [ -n ”$_rc” ]; then
      if [ ”$_rc” = ”0” ]; then
        _summary=”OK: ${_summary}”
      else
        _summary=”ERROR: ${_summary}”
        _urgency=”critical”
      fi
    fi
    su ”$SUDO_USER” -c ”notify-send –urgency=${_urgency} ’${_summary}’ ’${_body}’”
  fi
}

Mi, 29. März, 2023

Theme “A touch of glass” aktualisiert

Ich habe einmal für den Blog Flatpress ein Theme bereitgestellt.

Die schlechte Nachricht: Nach Update auf die aktuelle Flatpress-Version ging leider nicht mehr viel.
Die gute: es war einfach zu beheben.

2023-03-29_atog.png

Weiterführende Links

Mi, 8. Februar, 2023

Ansible git: detected dubious ownership in repository

Wir lassen diverse Applikationen mit Ansible von einem Git-Repository installieren. Etwa so:

- name: ’install - Clone repo {{ repo_url }}’
  ansible.builtin.git:
    repo: ’{{ repo_url }}’
    dest: ’{{ install_dir }}’

Dummerweise kommt dann noch ein Verschenken der Berechtigungen hinterher.

Seit Kurzem - mit einer neueren Git Version - häufen sich Abbrüche beim Git pull

detected dubious ownership in repository

Und auch die Lösung wird in der Fehlermeldung mitgegeben:

git config –global –add safe.directory [Verzeichnis]

Ja denn … packen wir doch einen Schnipsel hierfür dazu:

# set install dir as safedir …
- name: ”FIX - add installdir {{ install_dir }} as safedir”
  ansible.builtin.shell: |
    git config –global –get-all safe.directory &#92;
      | grep ”{{ install_dir }}” &#92;
      || git config –global –add safe.directory ”{{ install_dir }}”

Wenn es ein paar mehr Server sind, sollte man sich das in in eine Rolle verpacken, um es etwas abstrakter zu halten.

Zum Reparieren packt man es zur einmaligen Ausführung des Playbooks VOR das ansible.builtin.git … aber dann gehört es dahinter.

Der Shell-Aufruf präft, ob der Pfad bereits aufgenommen ist - nur wenn nicht, wird er hinzugefügt. Ansonsten würde ein mehrfaches “add” zu zigfachen Duplikaten desselben Pfades führen.

Di, 3. Januar, 2023

Fehlermuster im Streamripper

Ich schrieb vor nicht allzu langer Zeit einen Blogeintrag, um in Streamripper2 die Aufnahme-Funktion besser zu nutzen.
Diese muss man konfigurieren - also schlusendlich einen Kommandozeilenaufruf hinterlegen. Klassischerweise wird für Radiostreams das Tool Streamripper konfiguriert - und um eine optische Ausgabe zu haben, setzte man einen Konsolenaufruf davor. Ich fand das sehr bescheiden - sehr oft war bei egenen Versuchen war das Konsolenfenster gleich wieder zu und damit auch eine etwaige Fehlermeldung weg. Das ist doch nur unbefriedigend.

So fing alles an. Ich kann zum Glück etwas Shellprogrammierung.
Es sollte zunächst ein kleiner Wrapper sein, der anzeigt, welches Kommando mit welchen Parametern aufgerufen wird - und im Falle eines Abbruchs mich auch den Fehlertext lesen lässt.

streamtuner2-record-helper.jpg

Aber das wurde schnell etwas mehr, weil ich mit den ersten Versionen des Wrapperskripts nun auch die verschiedenen Fehlerkonstellationen von Streamripper sehen konnte. Mit Hilfe von Curl wurden die Http Response Header angezeigt, was weitere Dinge aufzeigt. So ergaben sich diese Fehlermuster:

Problem: Fehler 404 oder 410.
Lösung: keine - der Stream existiert nicht mehr.

Problem: Fehler 50x
Lösung: Ein Streamingserver arbeitet derzeit nicht oder reagiert nicht schnell genug (Timeout). Lösen kann ich das nicht, aber eine Meldung ausgeben, damit man weiss, dass es wohl ein nur temporäres Problem gibt und man es später wieder versuchen kann.

Problem: die URL ist kein abspielbarer Stream, sondern eine Playlist.
Lösung: Die Playlist wird ausgelesen und die erste Streaming-URL daraus extrahiert. Anm.: Es gibt durchaus auch Playlisten-Typen, die Streamripper versteht.

Problem: Streamipper wird mit einem 403 abgewiesen.
Lösung: Manche Streamingserver verweigern den Zugriff je nach Useragent und unterbinden den Abruf durch den Streamripper. Aber im Kommandozeilenaufruf des Streamrippers kann man den Useragent umschalten.

Problem: Streamriupper meldet -28 [SR_ERROR_INVALID_METADATA]
Lösung: Keine - das ist ein Fehler im Streamripper selbst: er fordert Daten mit Http1.1 an, versteht aber selbst nur Http 1.0 und kommt dann mit der Antwort des Streamingservers nicht klar. Neben dem kurzen kryptischen Fehlercode wird dann ein ergänzender Hinweis eingeblendet. Es gibt einen nicht offiziellen Patch, mit dem man Streamripper neu complilieren kann - da die letzte Streamripperversion 2008 erschien, wird es wohl nicht mehr offiziell gefixt.

Weil es im Streamripper noch Plugins auf MODarchive und Jamendo gibt: ich habe noch Downloads mit Curl ergänzt:

  • für Trackerfiles von MODarchive hinzugefügt (die Benamung der Zieldatei hole ich aus dem Http Response Header aus dem Attachment Filenamen)
  • für jamendo MP3s (die Benamung der Zieldati erfolgt nach Aufruf von ffprobe - welches zu ffmpeg gehört - und wird aus Titel, Künstler und Jahr zusammengesetzt)

Im Dezember erschien die Version 1.1 - diese prüft die benötigten Tools und hat eine Erweiterung in der Cleanup-Funktionalität erfahren.

Die kleinen Heilungsfunktionen und verwertbare Meldungen für ein Debugging im Fehlerfalls sind doch immer hilfreich. Das scheint auch anderen zu gefallen. Mario Salzer verlinkte den Wrapper auf fossil.include-once.org - ich setze hiermit einen Link auch gern zu ihm zurück.

weiterführende Links:

  1. Axels Blog: Streamtuner2 - Aufnahmen/ Downloads
  2. Github: mein ST2 record helper (en)
  3. Docs: mein ST2 record helper (en)
  4. sourceforge - Streamtuner2 Diskussion “ST2 does not record” (en)
  5. fossil.include-once.org: Hilfe zu Streamtuner2 (en)
  6. Streamtuner2 (en)
  7. Streamripper (en)

Mi, 14. Dezember, 2022

Update ahCrawler v0.156

Ich habe ein Update des ahCrawler veröffentlicht.
In diesem Release kamen hinzu

  • meine Docker-Entwicklungsumgebung wurde aufgenommen
  • die Dokumentation wurde neu geschrieben als Markdown und ebenfalls im Repo aufgenommen
  • es gibt zahlreiche Updates im Bereich der Suche im Backend und Frontend.
2022-12-12-ahcrawler-update.png

Das führte dazu, dass die Verzeichnisstruktur des Repos verändert werden musste. Alle bisherigen Files konnten unterhalb Webroot in einem beliebigen Unterverzeichnis geklont werden. Nun wurden alle Webfiles in den Unterordner “public_html” verschoben. Git basierte Installationen machen eine laufende Instanz mit einem Update kaputt, aber ich hoffe, ich habe es gut genug dokumentiert.

weiterführende Links:

  1. Github: Sourcecode
  2. Docs (axel-hahn.de): Changelog
  3. Docs (axel-hahn.de): Upgrade auf v0.156 (Zielseite wurde wg. veralteter Version entfernt)
  4. Twitter-Post zum Update
  5. Mastodon: Tröööt zum Update auf fosstodon.org

Di, 22. November, 2022

Bash: starte für zig Server pro Tag etwas auf nur einzelnen Systemen

Ich habe da eine unbekannt lang laufende Aufgabe: ich möchte vom Backup-Tool Restic das Backup-Repository auf Version 2 migrieren. OK, eigentlich ist die Aufgabe ja egal. Alle 100+ Systeme kommen damit nicht in der Nacht durch.

Ich möchte …

  • dass pro Nacht nur einige Systeme eine lang laufende Aufgabe wahrnehmen
  • nach N Tagen soll sichergestellt sein, dass auch alle Systeme den Job 1x gemacht haben.

Mir kam der Modulus in den Sinn. [Weiterlesen…]

Mo, 7. November, 2022

Streamtuner2 - Aufnahmen/ Downloads

Ich habe auf meinem Linux PC Streamtuner2 installiert. Das ist ein Browser von (Radio)Streaming Diensten mit deren Kategorien an Musikrichtungen. Was beim Druck auf [Play] zur Wiedergabe oder [Record] zur Aufnahme passieren soll, lässt sich konfigurieren.

Die Wiedergabe - es ist VLC vorkonfiguriert - funktioniert wunderbar. Das reicht wohl Vielen.

Zur Aufnahmefunktion … huh, da wird es langsam kompliziert. Vorkonfiguriert war das Öffnen eines Terminals. Das nimmt naturgemäss ja noch nichts auf. Das Standardwerkzeug unter Linux ist der Streamripper. Den muss man erst einmal konfigurieren.

Das habe ich gemacht. Einige Radiostreams wieden mir angezeigt, dass sie mitgeschnitten würden. Falls nicht, blitzte - wenn überhaupt - für Sekundenbruchteile ein Fenster hoch. Was nicht geht, warum es nicht geht - das zu analysieren, ist bei dieser Verfahrensweise aussichstlos. Was für ein Frust! Und all die Jahre, die Streamtuner und Streamripper existieren, gab es keine tatsächlich schlaue Lösung in der Doku?

Aber: hey, ich kann ja die Record Taste nach Audio-Typ in der Konfiguration belegen, wie ich will! Dann schreiben wir doch ein Skript, auf dass man sehen kann, was da passiert - und wenn nicht, wieso denn nicht.

Schritt 1

Gesagt - getan: ein Skript zeigt die übergebene URL an und was der Streamripper tut - oder aber bei Abbruch allenfalls an Fehlermeldung zurückwirft. Das Skript ist dann nicht einfach fertig - sondern wartet noch eine vordefinierte Zeit. Das war schön und ein Segen, das endlich einmal tatsächlich sehen zu können, an welcher Stelle und bei welchem Typ Url oder MIME Type er die Segel streicht.

Schritt 2

Manche Muster erkennt man: Streamripper kann bei bestimmten Playlist-URLs diese nicht parsen - er braucht dann hier eine Direkt-URL eines Streams, die man aber aus der Playlist ziehen könnte.

Oder es gibt Plugins, die einen Download eines Files anbieten - also gar keine Radiostreams sind, z.B Jamendo und MODarchive. Und beide benötigen unterschiedliche Handhabungsweisen, was bei einem Download zur Benamung der Zieldatei passieren muss.

Oh, es wird ja kompliziert - gut, wenn man noch nen Tick besser skripten kann.

Schritt 3

Wir bauen mal mehr Logik ein. Und Debugging Ausgabe, wie den Http Response Header.

Mein Downloader-Skript bekommt also eine URL. Es muss feststellen, ob es eine Datei oder eine Streaming URL ist. Dazu prüfen wir mal die URL, ob sie Weiterleitungen besitzt. Das letzte “Location:” im Http-Header bei aktiviertem Follow-Redirects ist meine finale URL zum Analysieren.

Jene Url ist entweder ein Stream oder eine Datei. Wenn es eine Datei ist, dann könnte es entweder eine Playlist sein, aus der man eine Streaming URL beziehen muss - oder eine Datei zum Download.

Seine Streaming Url oder ggf. aus der Playlist extrahierte Streaming URL wird an den Streamripper übergeben.
Ein Download File wird hingegen mit Curl heruntergeladen und je nach Typ wird eine Zieldatei geschrieben.

Ich glaube, mit einer solchen Ausgabeform kann man mit der Aufnahmefunktion und etwaigen bereits etwas mehr anfangen:

st2_record_helper.png

UPDTAES:
2022-11-14 - the helper script reached version 1.0 now.

weiterführende Links:

  1. Gihub: axelhahn st2_record_helper
  2. Streamtuner 2 internet radio directory browser
  3. Streamripper record streams as mp3 to your hard drive
  4. Mitteilung auf Fosstodon

Sa, 15. Oktober, 2022

PHP-Klasse ahLogger aktualisiert

Ich habe mal diese Loggerklasse geschrieben, um in einer PHP-Anwendung Dinge während eines Client-Requests zu debuggen. Ich zeichne Meldungen auf, die mit einem Status (OK, Info, Warnung oder Fehler) versehen werden. Auch kann man übergabe-Parameter und sonstige Variablen dort reinblasen.
Eine Rendering Funktion zeichnet eine Tabelle mit allen Meldungen ans untere Ende der Webseite. Alles kein Hexenwerk.

Wenn man schon Meldungen einsammelt, dann wird auch ein Zeitstempel getrackt. Und wenn man Zeitstempel hat, dann kann man die Zeitdauer von einzelnen Aktionen messen und tracken: wie lange läuft eine Datenbank-Abfrage, ein Exec oder eine andere Aktion - das bekommt man gratis dazu: in einer Spalte der Ausgabetabelle wird das Delta zur letzten aufgezeichneten Meldung ausgegeben.

Ein Div rechts oben zeigt die Verarbeitunsdauer des Requests am Server an und kennzeichnet farblich, ob es eine Warnung oder einen Fehler gab. Man kann von hier auch direkt zu den Einträgen mit Warnung oder Fehler springen.

Mit überschaubarem Aufwand entstand ein hilfreiches Werkzeug zum Debuggen, Messen und Bottlenecks finden.

So wird es gemacht:

Nach dem Initialisieren wird mt der Methode add() beliebig oft je eine Meldung erfasst.

$oLog = new logger();
// Beispiel 1: hilfreiche Daten sichtbar machen
$oLog->add(”INFO: all GET params: <pre>” . print_r($_GET,1) . ”</pre>”);
$oLog->add(”INFO: all POST params: <pre>” . print_r($_POST,1) . ”</pre>”);
// Beispiel 2: Zeiten messen geht implizit - durch Schreiben einer neuen Logmeldung
$oLog->add(”start db request”);
$sSql=’select id, label, description from mytable;’;
// … make your query
$oLog->add(”sql query finished: ” . $sSql);

Eine render() Methode gibt am Ende das Ergebnis aus.

// die Ausgabe … aber natürlich nicht für jeden Seitenbesucher
if ($bDebugIsEnabled){
    echo $oLog->render();
}

Was ist neu?

  • Wenn ich bereits Zeiten mitschreibe, ist das Auslesen des Speicherverbrauchs fast ein Nobrainer. So sieht man, wo wärend der Aktionen eines Seitenaufrufs der Speicherverbrauch wie wächst.
  • Balken visualisiern nun den Speichrverbrauch und die gemessenen Zeiten.
  • Emoji Icons machen die Ausgabe etwas locker.

So sieht es aus:

2022-10-15-ahlogger-html-ouput.png

weiterführende Links:

  1. Docs: ahLogger
  2. Github: Sourccode

Abonnieren