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

Dienstag, 22. November, 2022

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…]

Streamtuner2 - Aufnahmen/ Downloads

Montag, 7. November, 2022

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

PHP-Klasse ahLogger aktualisiert

Samstag, 15. Oktober, 2022

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

Neues Feature beim IML-Backup: Hooks

Donnerstag, 13. Oktober, 2022

Nachdem vor 2 Wochen das Prune und Verify separat ausführbar und konfigurierbar wurden, gibt es nun ein weiteres Feature: Hooks.

Das Backup läuft wie folgt ab

  • Initialisierung
  • Start der Backups der lokalen Datenbanken (z.B. Mysql, Sqlite)
  • Backup der definierten lokalen Verzeichnisse
  • Prune (Ausdünnen und Löschen alter Backup-Daten)
  • Verify

Die Hooks dienen zum Starten von eigenen Skripten … vor dem Start des Backups und danach sowie auch an mehreren Punkten während des Backups.

Es gibt initial diese Verzeichnisse, die du Skripte ablegen kannst, um sie zu definierten Zeitpunkten zu starten:

> tree -d hooks/
hooks/
|-- 10-before-backup
|   `-- always
|-- 12-before-db-service
|   `-- always
|-- 14-before-db-dump (UNUSED)
|   `-- always
|-- 16-after-db-dump (UNUSED)
|   |-- always
|   |-- on-error
|   `-- on-ok
|-- 18-after-db-service
|   |-- always
|   |-- on-error
|   `-- on-ok
|-- 20-before-transfer
|   `-- always
|-- 22-before-folder-transfer
|   `-- always
|-- 24-after-folder-transfer
|   |-- always
|   |-- on-error
|   `-- on-ok
|-- 26-after-prune
|   |-- always
|   |-- on-error
|   `-- on-ok
|-- 28-after-verify
|   |-- always
|   |-- on-error
|   `-- on-ok
`-- 30-post-backup
    |-- always
    |-- on-error
    `-- on-ok

34 directories

Als am ehesten einleuchtende Beispiele:

  • Vor dem Start des Backups kann man Sachen lokal synchronisieren, die ebenfalls ins Backup einfliessen sollen.
  • Nach dem Backup kann man beispielsweise Notifikationen ins Monitoring oder per E-mail auslösen.

Bei “nach einer Aktion” zu startenden Hooks kann man jeweils getrennte Sets von Skripten starten, sobald eine Aktion OK war … oder aber fehlerhaft … oder aber auch immer (unabhängig vom Status).

Und wie ist es implementiert? Schauen wir mal auf die Bash-Interna.

Es gibt zu den definierten Zeitpunkten im Skript einen Aufruf einer Hook-Funktion - mit dem Hook-Verzeichnis als Namen als Parameter 1 und für “nach einer Aktion” zu startende Hooks den Exitcode als optionalen 2. Parameter. Als willkürliches Beispiel:

_j_runHooks "24-after-folder-transfer" "$myrc"

Die Hookfunktion ruft bei Exitcode 0 die Skripte im “on-ok” Untrverzeichnis - und bei Exitcode > 0 jene vom “on-error”. Anschliessend (oder bei Aufruf ohne einen Exitcode) werden vom jeweiligen Hook die Skripte im Unterverzeichnis “always” verarbeitet.
Die Ausführung der Skripte erfolgt in alphabetischer Reihenfolge - mit der Benamung kann man also die Abfolge der Abarbeitung mehrerer Skripte beeinflussen.

Die Hookfunktion sieht so aus (genaugenommen erschreckend einfach):

...
# ------------------------------------------------------------
# execute hook skripts in a given directory in alphabetic order
# param  string   name of hook directory
# param  string   optional: integer of existcode or "" for non-on-result hook
# ------------------------------------------------------------
function _j_runHooks(){
  local _hookbase="$1"
  local _exitcode="$2"
  local _hookdir="$( dirname $0 )/hooks/$_hookbase"

  if [ -z "$_exitcode" ]; then
    _hookdir="$_hookdir/always"
  elif [ "$_exitcode" = "0" ]; then
    _hookdir="$_hookdir/on-ok"
  else
    _hookdir="$_hookdir/on-error"
  fi
  for hookscript in $( ls -1a "$_hookdir" | grep -v "^." | sort )
  do
    if [ -x "$_hookdir/$hookscript" ]; then
      h3 "HOOK: start $_hookdir/$hookscript ..."
      $_hookdir/$hookscript
    else
      h3 "HOOK: SKIP $_hookdir/$hookscript (not executable) ..."
    fi
  done

  # if an exitcode was given as param then run hooks without exitcode 
  # (in subdir "always")
  if [ -n "$_exitcode" ]; then
    _j_runHooks "$_hookbase"
  fi

  echo
}
...

weiterführende Links:

  1. IML OS Docs - IML Backup: Hooks (en)
  2. IML OS Docs - IML Backup: Startseite (en)

Neue grep Version warnt “warning: stray before white space”

Montag, 10. Oktober, 2022

Ich spiele immer fleissig Updates ein.
Plötzlich überraschen mich meine Shellskripte mit der Ausgabe

grep: warning: stray  before white space

Die Ursache ist schnell gefunden … eine neue Grep Version 3.8 ist aufgespielt worden, die sich offensichtlich anders verhält.

> grep --version
grep (GNU grep) 3.8
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Mike Haertel and others; see
<https://git.sv.gnu.org/cgit/grep.git/tree/AUTHORS>.

Ich hielt es bisher immer so: Sonderzeichen MUSS man im Regex maskieren … und beliebige normale Zeichen KANN man immer mit Backslash maskieren und es schadet auch nicht. Letzteres ist nun offensichtlich anders.

> echo "   OK" | grep "\ \ "
grep: warning: stray  before white space
grep: warning: stray  before white space
   OK

Zur Abschaltung der Warnung entferne ich im Regex den einem Space vorangestellte Backslash:

> echo "   OK" | grep "  "
   OK

Yep.
Soweit so gut … alles gut … dachte ich.

Es kamen in anderen Skripten aber auch Meldungen der Art

grep: warning: stray  before -

Also die Suche nach einem Minus muss nun auch nicht mehr mit Backslash maskiert werden? Einmal schnell testen:

> echo "------" | grep "\-\-\-"
grep: warning: stray  before -
grep: warning: stray  before -
------

Backslash weggenommen, hagelt ebenfalls eine Fehlermeldung:

> echo "------" | grep "---"
grep: unrecognized option '---'
Usage: grep [OPTION]... PATTERNS [FILE]...
Try 'grep --help' for more information.

Leider nein.
Die Lösung hier: Nun braucht es als Option ein Doppel-Minus. Und im Regex darf man das Minus nicht maskieren. Also so:

> echo "------" | grep -- "---"
------

Ich fürchte, die neue grep Version wird mich noch in anderen Skripten ärgern.