diff --git a/helios/external-backup.sh b/helios/external-backup.sh new file mode 100644 index 0000000..964f516 --- /dev/null +++ b/helios/external-backup.sh @@ -0,0 +1,211 @@ +#!/bin/bash +# +# Script zieht per rsync Backups und rotiert Backup-Verzeichnisse +# +# https://www.heinlein-support.de/howto/backups-und-snapshots-von-linux-servern-mit-rsync-und-ssh +# +# Zusammengeführt und angepasst durch Tobias Strobel (2021) +# + +RDIFFEXIT="$(mktemp)" +GOTIFYURL="https://push.strobeto.de/message?token=Anks7VaBnyf7jCB" +HCBACKUPURL="https://hc.strobeto.de/ping/375f20ea-aaa7-4b34-b279-bafc3ff55fed" +HCROTATEURL="https://hc.strobeto.de/ping/f169adf0-a1b3-4823-8fb6-f3a63546bb42" + +SERVER="$(hostname)" + +# ### Konfiguration +# Sollen wir pruefen, ob noch ein gewisser Prozentsatz +# an Plattenplatz und Inodes frei ist? +HDMINFREE=90 + +# Welcher Pfad soll gesichert werden? +SRC_PATH=/ + +# Unter welchem Pfad wird gesichert? +DATA_PATH=/mnt/backupone + +setLED () { + # Set LED status + echo heartbeat > "/sys/class/leds/helios64:blue:usb3/trigger" + echo 1 > "/sys/class/leds/helios64:blue:usb3/brightness" +} + +clearLED () { + # Clear LED status + echo usbport > "/sys/class/leds/helios64:blue:usb3/trigger" + echo 0 > "/sys/class/leds/helios64:blue:usb3/brightness" +} + +finishUp () { + # Clean up tempfiles + rm "$RDIFFEXIT" + + sync -f $DATA_PATH +} + +healthchecksStart () { + # $1 Healthchecks URL + curl -fsS -m 10 --retry 5 -o /dev/null "$1"/start +} + +healthchecksFinish () { + # $1 Healthchecks URL + # $2 Status Code + # $3 Logs + curl -fsS -m 10 --retry 5 --data-raw "$3" "$1"/"$2" +} + +notify () { + # $1 Action + # $2 Last command succeeded + # $3 Custom Title + # $4 Custom Message + # $5 Custom Priority + NOTIFY_TITLE="[$SERVER] " + NOTIFY_MESSAGE="" + NOTIFY_PRIORITY=1 + case "$1" in + + rotate-start) + NOTIFY_TITLE+="Rotation started" + NOTIFY_MESSAGE="Rotation on external hdd started" + ;; + + rotate-end) + if [ "$2" -eq 0 ]; then + NOTIFY_TITLE+="Rotation finished" + NOTIFY_MESSAGE="Rotation on external hdd finished" + NOTIFY_PRIORITY=5 + else + NOTIFY_TITLE+="Rotation has ERRORS" + NOTIFY_MESSAGE="Exit code $STATUSCODE. See logs attached to healthchecks for more information." + NOTIFY_PRIORITY=8 + fi + ;; + + backup-start) + NOTIFY_TITLE+="Backup started" + NOTIFY_MESSAGE="Backup to external hdd started" + ;; + + backup-end) + if [ "$2" -eq 0 ]; then + NOTIFY_TITLE+="Backup finished" + NOTIFY_MESSAGE="Backup to external hdd finished" + NOTIFY_PRIORITY=5 + else + NOTIFY_TITLE+="Backup has ERRORS" + NOTIFY_MESSAGE="Exit code $STATUSCODE. See logs attached to healthchecks for more information." + NOTIFY_PRIORITY=8 + fi + ;; + *) + NOTIFY_TITLE+="$3" + NOTIFY_MESSAGE="$4" + NOTIFY_PRIORITY="$5" + ;; + esac + + curl "$GOTIFYURL" -F "title=$NOTIFY_TITLE" -F "message=$NOTIFY_MESSAGE" -F "priority=$NOTIFY_PRIORITY" +} + +checkFreeSpace () { +# Pruefe auf freien Plattenplatz +GETPERCENTAGE='s/.* \([0-9]\{1,3\}\)%.*/\1/' +KBISFREE=$(df /$DATA_PATH | tail -n1 | sed -e "$GETPERCENTAGE") +INODEISFREE=$(df -i /$DATA_PATH | tail -n1 | sed -e "$GETPERCENTAGE") + +if [ "$KBISFREE" -ge $HDMINFREE ] || [ "$INODEISFREE" -ge $HDMINFREE ] ; then + echo "Fatal: Not enough space left for rotating backups!" + logger "Fatal: Not enough space left for rotating backups!" + exit +fi +} + +basicChecks () { + if ! [ -d $DATA_PATH ] ; then + echo "Fatal: Data path does not exist: $DATA_PATH" + logger "Fatal: Data path does not exist: $DATA_PATH" + exit + fi + + checkFreeSpace + + # Ggf. Verzeichnis anlegen + if ! [ -d $DATA_PATH/"$SERVER"/daily.0 ] ; then + mkdir -p $DATA_PATH/"$SERVER"/daily.0 + fi +} + +rotate () { + echo "Rotating snapshots of $SERVER..." + healthchecksStart $HCROTATEURL + notify rotate-start + + # Das hoechste Snapshot abloeschen + if [ -d $DATA_PATH/"$SERVER"/daily.7 ] ; then + rm -rf $DATA_PATH/"$SERVER"/daily.7 + fi + + # Alle anderen Snapshots eine Nummer nach oben verschieben + for OLD in 6 5 4 3 2 1 ; do + if [ -d $DATA_PATH/"$SERVER"/daily.$OLD ] ; then + NEW=$($OLD + 1) + # Datum sichern + touch $DATA_PATH/.timestamp -r $DATA_PATH/"$SERVER"/daily.$OLD + mv $DATA_PATH/"$SERVER"/daily.$OLD $DATA_PATH/"$SERVER"/daily."$NEW" + # Datum zurueckspielen + touch $DATA_PATH/"$SERVER"/daily."$NEW" -r $DATA_PATH/.timestamp + fi + done + + # Snapshot von Level-0 per hardlink-Kopie nach Level-1 kopieren + if [ -d $DATA_PATH/"$SERVER"/daily.0 ] ; then + ROTATELOG=$(cp -al $DATA_PATH/"$SERVER"/daily.0 $DATA_PATH/"$SERVER"/daily.1; echo $? > "$RDIFFEXIT") + fi + + STATUSCODE=$(cat "$RDIFFEXIT") + + echo "Finished rotating backups of $SERVER..." + healthchecksFinish $HCROTATEURL "$STATUSCODE" "$ROTATELOG" + notify rotate-end "$STATUSCODE" +} + +backup () { + # Los geht`s: rsync zieht ein Vollbackup + echo "Starting rsync backup from $SERVER..." + healthchecksStart $HCBACKUPURL + notify backup-start + + BACKUPLOG=$(rsync -avz --numeric-ids -e ssh --delete --delete-excluded \ + --include={"/mnt/storage/nc_data","/mnt/storage/media","/mnt/storage/backup"} \ + --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found","/.snapshots"} \ + $SRC_PATH $DATA_PATH/"$SERVER"/daily.0 \ + ; echo $? > "$RDIFFEXIT") + + STATUSCODE=$(cat "$RDIFFEXIT") + + # Rückgabewert prüfen. + # 0 = fehlerfrei, + # 24 ist harmlos; tritt auf, wenn während der Laufzeit + # von rsync noch (/tmp?) Dateien verändert oder gelöscht wurden. + # Alles andere ist fatal -- siehe man (1) rsync + if ! [ "$STATUSCODE" = 24 ] || ! [ "$STATUSCODE" = 0 ] ; then + echo "Fatal: rsync finished $SERVER with errors!" + fi + + # Verzeichnis anfassen, um Backup-Datum zu speichern + touch $DATA_PATH/"$SERVER"/daily.0 + + echo "Finished rsync backup from $SERVER..." + healthchecksFinish $HCBACKUPURL "$STATUSCODE" "$BACKUPLOG" + notify backup-end "$STATUSCODE" +} + +setLED +basicChecks +backup +rotate +finishUp +clearLED \ No newline at end of file