21x9.org | System Administration | Home Automation | Smart Home
11.07.2023

Statuszusammenfassung in Mails

Häufig führt man Wartungsscripte aus und lässt sich den Status z.B. via Mail zustellen. Oft schleicht es sich dann irgendwann ein, dass man diese zwar zur Kenntnis nimmt, aber nicht mehr wirklich intensiv beachtet. Je nachdem, wie der Inhalt gestaltet ist, können hierbei Fehlermeldungen untergehen.

Daher mag ich es, wenn neben dem vollständigen Output der Aktionen eine Statuszusammenfassung enthalten ist, bei der man auf einen Blick erkennt, ob alle Teilschritte funktioniert haben.

Bestenfalls sollte diese Zusammenfassung am Anfang der Mail stehen, damit man ihn sieht, ohne langwierig in der Mail ans Ende scrollen zu müssen.

Beispiel:

Remove stale locks                                                   [  OK  ]
Create backup                                                        [FAILED]
Prune backup generations                                             [  OK  ]

Wichtig ist mir hierbei, dass nicht jeder einzelne Befehl einen Status ausgibt, sondern ggf. eine Gruppe von Befehlen. So bleibt es Übersichtlich.

Script

Bei Redhat ist bzw. war eine Funktionssammlung in /etc/init.d/functions enthalten, die hierfür sehr hilfreich war. Die Funktionen sind folgende:

BOOTUP="color"
LOGFILE="/tmp/backup.log"
RES_COL=70
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \\033[1;32m"
SETCOLOR_FAILURE="echo -en \\033[1;31m"
SETCOLOR_NORMAL="echo -en \\033[0;39m"

echo_success() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_SUCCESS
    echo -n $"  OK  "
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 0
}

echo_failure() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo -n $"FAILED"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 1
}

step() {
    echo -n "$@" | awk '{printf "%-'${RES_COL}'s", $0}'
    echo "" >> ${LOGFILE}
    echo "------------------------------------------------------------------------------" >> ${LOGFILE}
    echo "$(date)" >> ${LOGFILE}
    echo "$*" >> ${LOGFILE}

    STEP_OK=0
    [[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
}

try() {
    "$@" &>> ${LOGFILE}

    local EXIT_CODE=$?

    if [[ $EXIT_CODE -ne 0 ]]; then
        STEP_OK=$EXIT_CODE
        [[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$

        if [[ -n $LOG_STEPS ]]; then
            local FILE=$(readlink -m "${BASH_SOURCE[1]}")
            local LINE=${BASH_LINENO[0]}

            echo "$FILE: line $LINE: Command \`$*' failed with exit code $EXIT_CODE." >> ${LOGFILE}
        fi
    fi

    return $EXIT_CODE
}

next() {
    [[ -f /tmp/step.$$ ]] && { STEP_OK=$(< /tmp/step.$$); rm -f /tmp/step.$$; }
    [[ $STEP_OK -eq 0 ]]  && echo_success || echo_failure
    echo

    return $STEP_OK
}

Wichtig sind hier insbesondere die Funktionen step, try und next. Die Funktion step gruppiert ggf. mehrere Befehlsaufrufe in einen Status. try führt Befehlsaufrufe aus und speichert deren Rückgabewert. next wertet die Rückgabewerte einer step Gruppe aus.

Befehle werden nun im Wartungsscript wie folgt aufgerufen:

step "Removing stale locks"
    try /usr/bin/restic unlock
next

step "Create Backup"
    try /usr/bin/restic backup /opt /srv /etc
    try echo "The next command will fail to make this example work better:"
    try /opt/foobar
next

step "Prune Backups"
    try /usr/bin/restic forget --keep-daily 30 --prune
next

step "Backup Info"
    try /usr/bin/restic stats
next

Zunächst wird der step definiert, dann die zugehörigen Befehle mittels try ausgeführt. Die Auswertung erfolgt mittels next.

Während der Befehlsausführung wird ein Logfile mit dem jeweiligen Output erzeugt, dieses kann am Ende des Script ausgegeben werden:

echo ""  
echo "-------------------------------------------------------------------------------"  
echo "Logfile:"  
cat ${LOGFILE}
rm -f ${LOGFILE}  
echo "-------------------------------------------------------------------------------"

Cronjob mit Mail

Um den Output (inkl. Zusammenfassung) per Mail zu versenden, tragen wir einfach folgendes in unseren Cronjob ein:

0 1 * * * /root/backup.sh 2>&1 | mail -s "Backup status" root@example.com

Output

Der vollständige Mailoutput könnte dann wie folgt aussehen:

Starting Backup Sun Feb 26 05:37:36 PM CET 2023

Remove stale locks                                                   [  OK  ]
Create backup                                                        [FAILED]
Prune backup generations                                             [  OK  ]

-------------------------------------------------------------------------------  
Logfile:

Remove stale locks
successfully removed locks

Create backup
Files:         406 new,   256 changed, 3239830 unmodified
Dirs:           11 new,   192 changed, 500609 unmodified
Added to the repo: 1.788 GiB

processed 3240492 files, 868.106 GiB in 58:28
snapshot 1ea026a5 saved

The next command will fail to make this example work better:
./backup.sh: line 64: /opt/foobar: command not found

Prune backup generations
Applying Policy: keep 30 daily snapshots

remove 1 snapshots:
[0:00] 100.00%  1 / 1 files deleted

done

Backup Info
scanning...
Stats in restore-size mode:
Snapshots processed:   30
   Total File Count:   98691102
         Total Size:   860.688 GiB
-------------------------------------------------------------------------------  

ANSI in Mails

Verschickt man den Output per Mail, werden ANSI Steuercodes für die Positionierung und Farbe des Status ggf. nicht korrekt angezeigt. In diesem Fall setzt man BOOTUP auf einen anderen Wert als color.

Alternative (trap)

Eine Alternative (oder auch Ergänzung) zu obigen Verfahren kann die Nutzung von trap sein. Mittels Trap kann man in einem Script definieren, was in einem Fehlerfall oder bei einem Abbruch des Scripts passieren soll. Das ist insbesondere dann praktisch, wenn in diesen Fällen gewisse Aufräumarbeiten nötig sind:

trap 'echo "ERROR: Backup interrupted"; do_cleanup; exit 2' INT TERM
trap 'echo "There was an error"; do_cleanup; exit 1' SIGINT SIGQUIT SIGTERM 

Die erste Zeile greift wenn z.B. während der Laufzeit des Scripts Strg+C gedrückt wird. Die zweite Zeile wird ausgeführt, wenn das Script (oder ein vom Script ausgeführter Befehl) einen fatalen Fehler (der zum Abbruch des Scriptes führt) generiert.

In beiden Fällen wird die Funktion do_cleanup aufgerufen, diese könnte z.B. wie folgt aussehen:

do_cleanup () {
    echo "Unmounting and removing snapshots"
    /bin/umount /mnt/backup/data
    /sbin/lvremove /dev/vg0/data-snapshot -f
    cat ${LOGFILE} | mail -s "There was an error" root@example.com
}

Durch die exit Aufrufe würde das Script im Fehlerfall beendet, das führt natürlich die Nutzung von try und next ad-absurdum. Man könnte aber natürlich auch auf den Aufruf von exit verzichten und auch ggf. eine andere Funktion aufrufen. Auch die Übergabe von Parametern an die Funktion sind möglich, um zielgerichtet auf bestimmte Fehler in bestimmten Steps reagieren zu können.

Ich lasse mir z.B. einfach gern eine Fehlermail durch trap senden:

trap 'cat ${LOGFILE} | mail -s "There was an error during backup" root@example.com' ERR

Die Mail wird bei jedem Fehler, der während des Scriptlaufs erzeugt wird, versendet. Wichtig hierbei, nutzt man die o.g. Funktionen, werden hierbei ggf. pro Fehler mehrere Mails generiert. Ich nutze hier daher dann lieber die Möglichkeiten meines Mailservers mir Mails die [FAILURE] enthalten, entsprechend hervorzuheben.

Tags: linux mail bash status trap awk

Mehr

  • Flexible, bootfähige USB-Sticks mit Ventoy
  • Backups mit Borg und ZFS/LVM Snapshots
  • Serverüberwachung mit checkmk
  • SSH-Keys mit TPM
  • Wildcard-Zertifikate mit letsencrypt, lexicon und dehydrated

Tags

linux mail bash status trap awk

Archiv

  • Mar 2025 (2)
  • May 2024 (2)
  • Oct 2023 (1)
  • Aug 2023 (5)
  • Jul 2023 (31)

  • Ältere Einträge (95)

Feeds

Atom 1.0 RSS JSON
  • Datenschutz
  • Impressum
  • Archiv