PHP/bash: Konsolenausgabe positionieren

Kürzlich benötigte ich für eine PHP Konsolenanwendung die Ausgabe einer Statuszeile. Die ausgegebene Zeile sollte jedoch nicht (wie sonst üblich) jeweils in einer neuen Zeile landen, sondern immer in der selben Zeile – so dass man einen schnellen Überblick über den Scriptstatus bekommt. Wie auch immer – ich habe mich dann etwas durch die Terminal ANSI Escape Sequenzen gekämpft und das ganze mit meinem PHP Script kombiniert. Folgend die entsprechende Funktion für die Ausgabe:

function echoFixedLine($string) {
     echo "\033[K" . $string . "\033[" . strlen ($string) . "D";
}
 
echo "TEST:";
 
for ($i=0; $i<=100; $i++) {
    echoFixedLine("Number: " . $i);
    sleep(1);
}
 
// Ausgabe nach ca. 101 Sekunden:
TEST:
Number: 100

Die Funktion löscht immer zunächst die ganze Zeile, schreibt dann den gewünschten String und positioniert anschließend den Cursor wieder an den Anfang für die eventuelle nächste Zeile. Denkbar wären damit z.B. auch „grafische“ Fortschrittsanzeigen…

Hier ein paar weiter Informationen zu den Escape-Sequenzen – eine komplette Referenz haben ich leider noch nicht gefunden.

OpenNMS: Services löschen

Wenn man bei OpenNMS einige Services aus der poller-configuration.xml entfernt, werden diese zwar nicht mehr überwacht, tauchen aber nach wie vor in der Serviceliste des jeweiligen Interface auf („Not Monitored“). Theoretisch sollte OpenNMS diese Services nach 5 Tagen (Default) entfernen. Ich bin mir allerdings nicht sicher ob diese dann komplett entfernt werden oder nur auf „Not Monitored“ gesetzt werden. Die Doku bzw. einige Einträge auf der Mailingliste sind da etwas widersprüchlich – und 5 Tage warten wollte ich nicht.

Manuell können die Services im Webinterface gelöscht werden indem man auf das Interface geht, dann den Service auswählt und schließlich auf „Delete“ klickt. Bei hunderten Interfaces ist das etwas mühsam. Daher habe ich folgendes Script gebastelt welches die gewünschten Services bei allen Nodes und Interfaces löscht (Benutzung auf eigene Gefahr!):

#!/bin/bash
 
for i in `psql -qAt -F";" -c 'select ipaddr,nodeid from ipinterface;' -d opennms opennms`; do
  IF=`echo $i | awk -F ";" '{print $1}'`
  NODE=`echo $i | awk -F ";" '{print $2}'`
  echo "deleting services from $IF on $NODE..."
  # entferne den Service "Qmail-queue" von allen Nodes und Interfaces
  /usr/share/opennms/bin/send-event.pl uei.opennms.org/nodes/deleteService \
  --service Qmail-queue \
  --interface $IF \
  --node $NODE
done;

Auf jeden Fall sollte man die zu entfernenden Services auch aus der capsd-configuration.xml herausnehmen – ansonsten werden diese immer wieder neu erkannt (wiederum als „Not Monitored“).

Ubuntu/Centos: /bin/sh Unterschiede

Mal wieder Zeit verplempert: Wenn man Shell-Scripts programmiert sollte man wissen welchen Interpreter man verwendet. Etwas achtlos verwendet man meist den Shebang „#!/bin/sh“. /bin/sh ist meist nur ein Link auf die jeweilige Default-Shell der Distribution.

Unter Centos/RedHat ist /bin/sh ein Link auf /bin/bash (die Bourne-Again-Shell):

[root@centos ~]# ls -la /bin/sh
lrwxrwxrwx 1 root root 4 Apr 28  2010 /bin/sh -> bash

Unter Ubuntu/Debian hingegen verlinkt /bin/sh auf /bin/dash (Debian-Almquist-Shell).

root@debian:~# ls -la /bin/sh
lrwxrwxrwx 1 root root 4 2010-11-02 16:47 /bin/sh -> dash

Aufgefallen ist mir der Unterschied beim Verwenden der Bash-Variable $UID. Die dash enthält diese Variable nämlich nicht. Weitere Unterschiede sind mir bisher nicht bekannt (habe aber auch nicht danach gesucht).

Linux: Updatefälligkeit checken

Zwei kleine Scripts um unter Ubuntu/Debian oder Centos/RHEL zu checken ob Updates fällig sind. Die Scripts geben jeweils nur die Anzahl der fälligen Updates zurück – eignen sich also prima dazu mittels SNMP die Aktualität der Systeme abzufragen. Ein Beispiel für OpenNMS befindet sich hier (von dort stammt auch das erste Script, das zweite ist IMO eine bessere Variante des dort dargestellten).

Centos/RHEL

#!/bin/bash
yum check-update 2>/dev/null | grep -v Load | grep -v \* | grep -v '^$' \ | wc -l

Ubuntu/Debian

#!/bin/bash
/usr/bin/apt-get update > /dev/null 2>&1
/usr/bin/apt-get -qq -s dist-upgrade --allow-unauthenticated | grep ^Inst | wc -l

Passwörter generieren (mit gnome/zenity)

Immer mal wieder benötige ich eine Reihe neuer Passwörter. Da ich zu faul bin diese selbst zu generieren, lasse ich das besser machen. Dafür kann man z.B. makepasswd verwenden. Für die Bereiche wofür ich die Passwörter brauche reicht mir eine übliche Entropie völlig.

Da ich Gnome benutze habe ich mich gefragt, ob es nicht eine einigermaßen elegante Möglichkeit gibt, die generierten Passwörter „grafisch“ auszugeben.

Gibt es: zenity ist ein Tool mit dem man von der Kommandozeile aus verschiedene Dialoge zur Interaktion mit Scripten etc. öffnen kann. So gibt es z.B. Messageboxen, Textabfragen etc. Eine gute Übersicht findet sich hier. Ich habe mich für die Listenansicht entschieden, da man dort einfach per Copy+Paste an den Text kommt. Hier das komplette Kommando:

makepasswd --count 24 | \
zenity --list --editable --title "Passwörter" --column Password --height 400

inotifysync: Clusternodes synchronisieren

Möchte man die Daten von Cluster-Nodes auf einem gemeinsamen Stand halten gibt es viele Möglichkeiten. Handelt es sich um eine Read-Only Anwendung werden die zugehörigen Dateien meist über einen Master gepflegt und von dort verteilt. Dafür kann man z.B. rsync nehmen – periodisch ausgeführt synchronisiert es zuverlässig die Nodes mit dem Master. Das Problem mit rsync: Die Synchronisation findet nur periodisch statt und nicht live. Außerdem ist bei größeren Datenmengen die Last während der Synchronisation recht hoch.

Warum also nicht nur das synchronisieren was auch synchronisiert werden muss? Genau dafür gibt die inotify-Schnittstelle am Linux-Kernel. Mit entsprechenden Tools überwacht es ganze Dateibäume auf Änderungen und führt bei Bedarf Aktionen aus.

Also habe ich mir ein Script gebastelt welches 1. ein bestimmtes Verzeichnis überwacht und 2. nur die geänderten Dateien/Verzeichnisse unmittelbar mittels ssh verteilt. Voraussetzungen: ssh, scp und inotifywait (enthalten bei den meisten Distributionen im Paket „inotify-tools“). SSH sollte über public keys authentifizieren. Im Kopf von inotifysync.sh müssen die Ziel-Nodes eingetragen werden (diese ändern sich wohl nicht so häufig). Alternativ kann auch eine Datei mit den Ziel-Nodes verwendet werden.

Download: inotifysync.sh

Folgendes Beispiel überwacht das Verzeichnis /home/cluster/htdocs auf Änderungen und synchronisiert diese in das Verzeichnis /var/www/cluster/htdocs auf dem jeweiligen Node:

./inotifysync.sh /home/cluster/htdocs /var/www/cluster/htdocs \
>> /home/cluster/log &

Die Logausgaben werden in die Datei /home/cluster/log geschrieben und das ganze mittels „&“ in den Hintergrund geschickt.

Hinweis: Etwas bash Erfahrung sollte man beim Einsatz dieses Scripts schon haben. Einsatz erfolgt natürlich auf eigene Gefahr 😉

Mediawiki als Managementoberfläche

In letzter Zeit habe ich das Mediawiki als zentrale Managementplatform entdeckt. Eigentlich war es bisher lediglich zu Dokumentationszwecken gedacht. Dadurch, dass es aber eine einfach zu nutzende XML-Exportfunktion bietet, lassen sich damit auch noch andere Dinge anstellen (oder Kombinieren). Man kann damit z.B. prima eine Nodeliste für OpenNMS verwalten (welche nicht durch autodiscovery erfasst werden). Oder gewisse Steuerungsdaten für Bash-Scripte. Oder was auch immer. Zumindest für Aufgaben wofür es sich nicht lohnt ein eigenes Interface zu stricken.

Bisher habe ich zentrale Einstellungen immer relativ mühsam in ein SVN gesichert. Zugriff für Änderungen war meist nur über die SSH Konsole möglich. Dokumentiert wurde da auch nicht viel…

Das schöne am Mediawiki ist, dass eine Versionierung schon eingebaut ist. Ausserdem lassen sich z.B. Dokumentationen oder Checklisten verlinken. Durch Kategorisierung lassen sich die Daten auch geordnet ablegen. SSL und Authentifizierung sollten natürlich selbstverständlich sein.

Per simplen wget in bash-scripten oder per curl in in perl/php ist der Zugriff in der Regel problemlos möglich. Das xml lässt sich prima mit dem Tool „xml2“ in Scripten verarbeiten…

Beispiel zum Einlesen einer Liste mit IP-Adressen:

wget -q -O - --http-user admin --http-passwd secret \
https://wiki.example.com/index.php/Spezial:Exportieren/IP_Plan \
| xml2 \
| awk '/(page\/revision\/text)*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ {print $2}'

OpenNMS: Import aus XML

opennmsAutodiscovery in OpenNMS ist ein tolles Feature. Leider ist es nicht immer zu gebrauchen, da es derzeit nur begrenzt konfigurierbar ist. So möchte ich z.B. in einem bestimmten Netzbereich nur die jeweiligen Router der Teilnetze abfragen – und nicht alle Hosts auf Services scannen. Klar kann man auch Bereiche excluden bzw. includen – aber ich wollte ja Autodiscovery ;).

In der kommenden OpenNMS Version 1.8 sollen die Discovery bzw. die Capability-Checks (capsd) von einem neuen Daemon („provisiond“) abgelöst werden welcher sich wohl auch besser an eigene Bedürfnisse anpassen lässt – z.B. ist als nettes Feature dann z.B. auch der Import von DNS-Zonen möglich. Dies aber nur Nebenbei.

Auf der Suche nach einem alternativen Import bin ich bei XML gelandet. Da die zu überwachenden Hosts in einem internen Wiki (Mediawiki) gepflegt werden, stehen diese über die eingebaute Export-Funktion auch als XML zur verfügung. So ist z.B. der OpenNMS Artikel bei Wikipedia auch als reines XML verfügbar: http://en.wikipedia.org/wiki/Special:Export/OpenNMS.

Ein cooles Tool zum verarbeiten von XML auf der Kommandozeile ist „xml2“ (in vielen Distro-Repositories verfügbar). Kombiniert mit ein paar regulären Ausdrücken landen die richtigen IPs in einer Datei für den automatischen Import in OpenNMS:

wget -q -O - --http-user admin --http-passwd secret \
http://wiki.example.com/index.php/Spezial:Exportieren/IP_Plan \
| xml2 \
| awk '/(page\/revision\/text)*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)*(NMS:host)/ {print $2}' \
> /opt/include-hosts

„OpenNMS: Import aus XML“ weiterlesen

Telnet per Bash-Script

Bei etlichen Routern die snmp-Community per telnet zu setzen kann mühsam werden. Ein Script muss her. Das telnet-Kommando ist anscheinend so nicht gut dafür geeignet um eingaben von STDIN zu verarbeiten. Durch google bin ich darauf gekommen, dass viele es mit „expect“ lösen. Hier mein Script:

#!/bin/bash
for host in `cat /root/hosts` ; do 
expect << EOF
spawn telnet $host
expect "Login: "
send "admin\r"
expect "Password: "
send "secret\r"
expect -exact "-->"
send "snmp set communityname xxx manager xxx\r"
expect -exact "-->"
send "user logout\r"
EOF
done;

Die Hosts stehen in der Datei /root/hosts. Je in einer Zeile.

HDD spindown check

Nachdem ich meine Platten jetzt schlafenlegen kann, würde ich auch gerne wissen wann und ob dies erfolgt…

# Usage
 
Output state:
./check_hdd_spindown.sh /dev/sda /dev/sdb /dev/sdc
 
Log state into file:
./check_hdd_spindown.sh /dev/sda /dev/sdb /dev/sdc >> /var/log/spindown.log &
#!/bin/sh
 
while [ 1 ]
do
        i=0
        for DRIVE in $*
        do
                DATE=`date +"%Y-%m-%d %H:%M:%S"`
                RESULT_OLD=${RESULT[i]}
                RESULT[$i]=`hdparm -C $DRIVE | grep state`
 
                if [ "$RESULT_OLD" != "${RESULT[i]}" ]
                        then echo $DATE $DRIVE ${RESULT[i]}
                fi
 
                i=$i+1
        done
 
        sleep 5
done