Update: Nach dem DSM6 Update hatte ich zuerst Probleme mit dem Cron, nachdem ich diesen aber über die DSM Gui eingestellt habe, sind die Probleme nun weg. Einer der großen Vorteile im Bezug auf DSM 6 ist der Ersatz von Busybox durch Bash. Dies sollte Raum für ein paar Verbesserungen im Skript bieten.

Nachdem ich lange mit mir gekämpft habe, war es im letzten Monat nun endlich soweit und ich habe mir ein Synology NAS gekauft. Zwar bietet Synology hier die Möglichkeit, sowohl die Festplatten als auch das NAS selbst, bei Inaktivität in einen Standby-artigen Zustand zu versetzen (Deep Sleep), dennoch finde ich es wenig sinnvoll das Gerät laufen zulassen, wenn kein Zugriff zu erwarten ist. Glücklicherweise scheine ich aber nicht der einzige zu sein, der so denkt. So bin ich dann irgendwann auf einen Blogeintrag von Dominik gestoßen, welcher das deutsche Synology Forum als Quell der Inspiration zurate gezogen hat. Nachdem ich für ein paar Tage erfolgreich eine modifizierte Version von Dominiks Skript laufen hatte, ist es mir leider beim Update auf DSM 4.3 abhanden gekommen. Um den Schmerz des Verlusts zu überwinden habe ich das Skript von Grund auf neu geschrieben und natürlich ein regelmäßiges Backup von /root eingerichtet.

Umgebung Link to heading

Zuerst noch ein bisschen zu meiner Umgebung: Das NAS läuft bei mir als zentraler Speicherort für Medien (Fotos, Musik und Filme), Downloadstation, TV Streaming Server mit Tvheadend und zentrale Datenbank für meine beiden XBMC Systeme. Bei Bedarf wecke ich das NAS per WoL z.B. über ein kleines PHP Skript. Das Shutdown-Skript führe ich per Crontab aus, welcher wie folgt aussieht:

*       21-5,9-16      *       *       1-5     root    /root/shutdown_on_not_used
*       21-5    *       *       0,6     root    /root/shutdown_on_not_used

Werktags läuft das Skript also von 21:00 bis 5:59 und 9:00 bis 16:59, wobei mir am Wochenende eine Überprüfung von 21:00 bis 5:59 reicht. Ebenfalls wichtig zu erwähnen ist, dass das Skript mit der Standardshell (ash) funktioniert und kein installiertes Bash benötigt.

Das Grundgerüst Link to heading

Nun aber zu den einzelnen Bestandteilen des Skripts:  

#!/bin/sh

LOGFILE="/tmp/shutdown-script.log"
#LOGFILE=/dev/null
COUNTFILE=/tmp/shutdown-counter

log() {
        echo `date +%c` $1 >> $LOGFILE
}

cancel() {
	[ -f $COUNTFILE ] && rm $COUNTFILE
	sleep 540
	exit 0
}

##########################################
# Hier ist Platz für die einzelnen Checks
##########################################

# Increment counter if all checks failed
echo >>$COUNTFILE
COUNTER=`ls -la $COUNTFILE | awk '{print $5}'`
log "NAS has been idle for $COUNTER checks"

# Shutdown NAS if counter has already been incremented 10 times
if [ $COUNTER -gt 10 ]; then
	log "updating power on time for tvheadend"
	/root/pvr-poweron.py > /etc/power_sched.conf
	log "shutdown Diskstation"
	rm $COUNTFILE
	/sbin/poweroff
fi

Das Grundgerüst des Skript besteht aus der  Definition des Logfiles und der Definition des Zählers, welcher dafür genutzt wird die erfolgreichen Durchgänge zu zählen. Zusätzlich gibt es im Kopf des Skriptes zwei Funktionen. Die erste Funktion (log) ist dafür zuständig Logmeldungen einheitlich mit einem Zeitstempel zu versehen, die zweite Funktion (cancel) setzt den Counter zurück, hält das Skript für neun Minuten an - und blockiert somit eine weitere Ausführung per Cron - und beendet anschließend den aktuellen Durchlauf. Auf diese Funktion folgen die einzelnen Checks, welche modular eingefügt oder weggelassen werden können. Diese Checks habe ich der Übersichtlichkeit halber oben nicht aufgeführt, sondern werde später näher auf jeden Check einzeln eingehen. Nachdem alle Checks fehlgeschlagen sind, also die einzelnen Prüfbedingungen (z.B. Rechner per Ping erreichbar) nicht zugetroffen haben, wird der Counter erstellt/inkrementiert. Zu guter Letzt wird dann noch überprüft wie oft der Counter inkrementiert wurde, wurde der Counter bereits zehn mal hochgesetzt fährt das System herunter. Da ich das NAS von Zeit zu Zeit als digitalen Videorekorder nutze aktualisieren ich aber vor dem Herunterfahren noch die automatischen Weckzeiten (Zeile 28 und 29). Hierfür verwende ich ein leicht angepasstes Skript, welches ich auf Github gefunden habe.

Stopfile Link to heading

# Terminate early if stopfile exists
STOPFILE=/tmp/shutdown-no
if [ -e $STOPFILE ]; then
        log "Stopfile exists. Doing nothing."
        cancel
fi

In einem kleinen Follow up beschreibe ich einen Weg dieses Stopfile über einen Webbrowser zu setzen.

Minimum Uptime Link to heading

Damit das NAS nicht direkt nach dem Wecken wieder runterfährt gibt es den folgenden Check (verzögert um 30 Minuten):

# Timecheck
uptime=$(cat /proc/uptime)
uptime=${uptime%%.*}
minutes=$(( uptime/60 ))
if [ $minutes -lt 30 ]; then
        log "Online since only $minutes minutes. Doing nothing."
        cancel
fi

SynoBackup Link to heading

# Check if synolocalbkp is running
if [ "$(pidof synolocalbkp)" ]; then
        log "Backup is running"
        cancel
fi

 Verbindung offen Link to heading

# Check if there is a connection via Webinterface or in of the Apps
if netstat | grep 'fozzie:https\|fozzie:5000\|fozzie:5001\|fozzie:5006' | grep ESTABLISHED > /dev/null; then
        log "Active connection to HTTPS, WebDAV or other DSM App"
        cancel
fi

Falls weitere Verbindungsarten das Herunterfahren des NAS verhindern sollen, kann der grep-Befehl entsprechend erweitert werden. In meinem Beispiel steht https für die Photo Station, 5000 und 5001 für unverschlüsselte bzw. verschlüsselte Verbindungen zur Audiostation und dem DSM allgemein und 5006 für https Verbindungen zum WebDAV Dienst. Um False Positive Meldungen zu vermeiden, sollten die Portangaben mit dem Hostname versehen werden, welcher in meinem Fall “fozzie” ist.

Transmission BitTorrent Link to heading

# Check if transmission is downloading
USER=username
PASSWORD=password
TRANSMISSION="/usr/local/transmission/bin/transmission-remote --auth "$USER":"$PASSWORD
if $TRANSMISSION -l | grep 'Downloading\|Up & Down' > /dev/null; then
        log "Transmission currently downloading"
        cancel
fi

SABnzbd Link to heading

# Check if SABnzbd is downloading
URL=localhost
PORT=8080
API=xxxx
if curl --silent "http://$URL:$PORT/api?mode=qstatus&output=json&apikey=$API" | grep Downloading > /dev/null; then
        log "SABnzbd is downloading"
        cancel
fi

Verbindung von bestimmten Rechner Link to heading

# Check if one of the ACTIVEHOSTS has an open connection
ACTIVEHOSTS=""
for host in $ACTIVEHOSTS ; do
        if netstat -n | grep ' '$host':.*ESTABLISHED' > /dev/null; then
                log "$host currently accessing NAS"
                cancel
        fi
done

Die Variable ACTIVEHOSTS nimmt die IPv4 Adressen (separiert mit Leerschritt) von Rechnern die das Herunterfahren bei aktiver Verbindung verhindern sollen.

Tvheadend Status Link to heading

# Check if Tvheadend is recording
URL=localhost
PORT=9981
USERNAME=username
PASSWORD=password
OUTPUT=$(curl -u $USERNAME:$PASSWORD --silent --max-time 5 "http://$URL:$PORT/status.xml")
if [ -z "$OUTPUT" ]; then
        log "Tvheadend is not responding"
elif echo $OUTPUT | grep "<status>Recording</status>" > /dev/null; then
        log "Tvheadend is recording"
        cancel
elif ! echo $OUTPUT | grep "<subscriptions>0</subscriptions>" > /dev/null; then
        log "Tvheadend is active"
        cancel
fi

Update (27.09.13): Ausgabe der Abfrage von status.xml in Variable speichern und diese auswerten statt status.xml erneut abfragen. Außerdem hat curl nun ein Timeout, da Tvheadend bei mir manchmal hängt und so curl zwar eine Verbindung bekommt, aber keine Ausgabe geliefert bekommt.

Update (01.10.13): Das Timeout in Curl gilt leider nur für die Zeit bis der Webserver initial antwortet, wenn Tvheadend “abgestürzt” ist antwortet aber immernoch der Webservice, liefert aber keine Daten zurück. Daher wird statt einem Timeout jetzt “–max-time 5” verwendet, welche die Laufdauer von Curl auf 5 Sekunden begrenzt (ein oder zwei Sekunden würden vermutlich auch reichen).

Zeit bis nächster Aufnahme Link to heading

# Check when the next recording is sheduled
till=$(echo $OUTPUT | grep next |  sed -e 's,.*<next>\([^<]*\)</next>.*,\1,g')
if [ -z "${till##*[!0-9]*}" ]; then
        log "No recording sheduled"
elif [ $till -lt 90 ]; then
        log "Next recording starts in $till minutes. Doing nothing."
        cancel
fi

Update (27.09.13): status.xml wird jetzt aus einer Variablen geholt. Sollte die nächste Aufnahme in weniger als 90 Minuten beginnen, wird das Herunterfahren verhindert. UPDATE: If Abfrage ergänzt um keine Fehlermeldung bei Nichtvorhandensein von geplanten Aufnahmen (Variable $till ist leer) zu bekommen.

Ping Link to heading

# Pingcheck - should be performed last
PINGHOSTS="waldorf astoria"
for host in $PINGHOSTS ; do
        if ping -c 1 -w 1 $host > /dev/null; then
                log "$host isn't offline"
                cancel
        fi
done

Als letzten Check führe ich einen Ping aus. Dieser soll verhindern, dass das NAS herunterfährt, während eines meiner beiden XBMC Systeme angeschaltet sind.  Wie auch bei den ACTIVEHOSTS, werden in der PINGHOSTS Variable mehrere Werte per Leerschritt getrennt.

Alle Skripte im Schnellzugriff: Link to heading