VER="2.0.0-beta-4" HOST_NAME=$(hostname) DAY=$(date +%u) PASSPHRASE=1234567890 EXT=tgz.gpg LIST_EXT=txt LOG_RECIPIENT=hb242@pm.me INFO=INFO WARN=WARN ERROR=ERROR DEBUG=DEBUG SUCCESS="*** success" INF="---" WRN=":::" ERR="###" DBG="===" dump_mariadb() { # TODO echo } dump_postgresql() { # TODO echo } # docker compose ausführen # # $1 ff. - compose parameter, z.B. up -d # run_docker() { log $DEBUG "$DBG run_docker $@" #DEBUG ohne docker stop/start return 0 log $DEBUG "$DBG > /usr/bin/docker compose -f /var/docker/run/docker-compose.yml --ansi never $@" /usr/bin/docker compose -f /var/docker/run/docker-compose.yml --ansi never $@ >>$LOG_FILE 2>&1 return $? } start_docker() { log $DEBUG "$DBG start_docker" return $(run_docker up -d) } stop_docker() { log $DEBUG "$DBG stop_docker" return $(run_docker down) } # check if function $1 exists # # returns true/false # isFunction() { declare -Ff "$1" ; } # Log-Datei schreiben. # # $1 - Typ, z.B. INFO, WARN, ERROR, etc. # $2 ff. - die Meldung, die ins Log geschreiben wird # log() { local type=$1 #if [ "$type" == "ERROR" ]; then ERROR=0; fi shift 1 echo "$(date '+%d.%m.%Y %H:%M:%S') - $type $@" | tee -a $LOG_FILE >/dev/null } # Reduziertes Log für automatische Läufe # # Das Log wird am Ende des Laufs als Mail verschickt. Die Meldungen # werden zusätzlich im Standard-Log protokolliert. # Dieses log wird nur geschrieben, wenn $MAIL_LOG_FILE definiert ist. # # $1 - Typ, z.B. INFO, WARN, ERROR, etc. # $2 ff. - die Meldung, die ins Log geschreiben wird # mail_log() { local type=$1 shift if [ "$MAIL_LOG_FILE" ]; then echo "$(date '+%d.%m.%Y %H:%M:%S') - $type $@" >> $MAIL_LOG_FILE fi log "$type" "$@" } # Reduziertes Log senden # send_mail_log() { log $DEBUG "$DBG > $HERE/mail.sh $LOG_RECIPIENT \"[$HOST_NAME] Nightly Backup\" $MAIL_LOG_FILE" $HERE/mail.sh $LOG_RECIPIENT "[$HOST_NAME] Nightly Backup" $MAIL_LOG_FILE >>$LOG_FILE 2>&1 } # check if $1 is error (!=0) and write result to log # # $1 - return code to check # $2 - if set don't log success # # returns 0 if error, 1 otherwise! # is_error() { local rc=$1 local mlog=$2 if [ $rc -eq 0 ]; then if [ "$mlog" ]; then mail_log $INFO "$SUCCESS" fi return 1 else mail_log $ERROR "$ERR an error occured see $LOG_FILE" return 0 fi } # check return code of piped programs # # $1, ff. - return codes # # returns 0 for success, 1 for one or more errors # check_pipe_rc() { local result=0 local count=1 local result_str for rc in $@; do if [ $rc -ne 0 ] ; then result=1 result_str="${result_str}#${count} " fi let "count += 1" done if [ $result -ne 0 ]; then log $ERROR "$ERR in pipe $result_str" fi return $result } # Dateien vom Server zur NAS verschieben # # $1 - Verzeichnis auf Server # $2 - Ziel-Verzeichnis auf der NAS # # returns return code of mv # copy_to_nas() { log $DEBUG "$DBG copy_to_nas $@" local source="$1" local target="$2" delete_dir "$target" if [ $(check_directory "$target") -gt 0 ]; then log $ERROR "$ERR directory \"$target\" not found" return 1 fi log $DEBUG "$DBG > mv \"$source\"/* \"$target\"" mv "$source"/* "$target" >>$LOG_FILE 2>&1 return $? } # Ist $1 in der Liste der restlichen args $2, $3 ... # # zB. if is_in "foo" "foo" "bar" "foo bar" ; then ... # Leere Liste liefert immer false # is_in() { local search="$1" shift 1 local e for e in "$@"; do [[ "$e" == "$search" ]] && return 0; done return 1 } # Alle Verzechnisnamen in einem Verzeichnis holen. # # $1 - Verzeichnis in dem gesucht wird als absoluter Pfad # $2 ff. - auszuschliessende Verzeichnisse (kann leer bleiben) # iterate_dirs_in() { local dirname local root=$1 shift 1 for dir in $(find "$root" -maxdepth 1 -type d \! -wholename "$root"); do dirname=$(basename $dir) if ! is_in "$dirname" "$@"; then echo "$dirname" fi done } # create directory if it not exists # # $1 - dir to check # # returns 0 for success, 1 otherwise # check_directory() { log $DEBUG "$DBG check_directory $@" local dir=$1 if [ -d "$1" ]; then return 0 else log $DEBUG "$DBG > mkdir -p \"$1\"" mkdir -p "$1" >>$LOG_FILE 2>&1 && return 0 log $ERROR "$ERR could not create directory $1!" return 1 fi } # Verzeichnis löschen # # Im Fehlerfall wird rc=1 zurückgegeben # # $1 - zu löschendes Verzeichnis # # returns 0 for success, 1 otherwise # delete_dir() { log $DEBUG "$DBG delete_dir $@" local dir=$1 log $DEBUG "$DBG > rm -rf \"$dir\"/*" rm -rf "$dir"/* >>$LOG_FILE 2>&1 && return 0 # return error, so caller can decide what to do return 1 } # Slashes "/" im Pfadnamen durch Dashes "-" ersetzen, so dass der String als Dateiname verwendet werden kann. # # $1 - path backup_file_prefix() { echo "$1" | sed -E 's/^\///' | sed -E 's/\//-/g' } # Verzeichnise unter $1 in .tgz.gpg-Dateien sichern. # # $1 - Verzeichnis, das gesichert wird: alle Verzeichnisse darunter werden einzeln als .tgz.gpg-Datei gesichert # $2 - Zielverzeichnis für .tgz.gpg-Dateien # $3 ff. - Verzeichnisse, die nicht gesichert werden sollen (kann leer bleiben) # # returns 0 for success, 1 otherwise # backup_all_dirs() { log $DEBUG "$DBG backup_all_dirs $@" local root=$1 local target=$2 shift 2 local prefix=$(backup_file_prefix $root) local tar_file local dir for dir in $(iterate_dirs_in "$root" "$@"); do tar_file="$target/$prefix-$dir.$EXT" mail_log $INFO "$INF directory: $root/$dir" backup_dir "$root" "$tar_file" "$dir" is_error $? false && return 1 #mail_log $INFO "$INF list directory" create_list "$tar_file" "$dir" is_error $? false && return 1 #mail_log $INFO "$INF test integrity of $tar_file" test_integrity "$tar_file" is_error $? false && return 1 done return 0 } # Einzelnes Verzeichnis sichern. # # $1 - Pfad um Verzeichnis, das gesichert werden soll # $2 - Zielpfad und -name für das Archiv # $2 - zu sicherndes Verzeichnis # # returns 0 for success, 1 otherwise # backup_dir() { log $DEBUG "$DBG backup_dir $@" local root=$1 local archive=$2 local dir=$3 log $DEBUG "$DBG > tar -c -z --numeric-owner --one-file-system --exclude \"@eaDir\" -C \"$root\" \"$dir\" | gpg --batch -c --passphrase xxxx -o \"$archive\"" tar -c -z --numeric-owner --one-file-system --exclude "@eaDir" -C "$root" "$dir" 2>>$LOG_FILE | gpg --batch -c --passphrase $PASSPHRASE -o "$archive" 2>>$LOG_FILE check_pipe_rc ${PIPESTATUS[@]} return $? } ## # auto backup handling # # hostname-scripte setzen vars und definieren Funktionen backup_daily1, 2, usw. und backup_weekly1, 2, usw. # in den Funktionen werden die vars für den einzelnen backup gesetzt (target, excludes, usw.) # hier wird dann dass hostname-script gesourced (s.o.) und in einer Schleife werden die # entsprechend en backup_*-Funktionen aufgerufen # # returns 0 for success, 1 otherwise # do_auto_backup() { log $DEBUG "$DBG do_auto_backup $@" local func=$1 local count=1 local rc if ! $func$count 2>/dev/null ; then # Seiteneffekt: $func$count wird ausgeführt mail_log $ERROR "$ERR keine Backup-Function $func$count() für $HOST_NAME." exit 1 fi mail_log $INFO "$INF prepare backup" before_backup is_error $? && return 1 delete_dir "$BACKUP_TARGET" mail_log $INFO "$INF check backup directory" check_directory "$BACKUP_TARGET" is_error $? && return 1 until ! $func$count 2>/dev/null # Seiteneffekt: $func$count wird ausgeführt (für nächste Schleife) do mail_log $INFO "$INF backing up directories in $BACKUP_SOURCE" backup_all_dirs "$BACKUP_SOURCE" "$BACKUP_TARGET" "${BACKUP_EXCLUDE_DIRS[@]}" rc=$? # error logging in backup_all_dirs if [ $rc -gt 0 ]; then return 1 fi let "count += 1" done mail_log $INFO "$INF finishing backup" after_backup is_error $? && return 1 } backup_daily() { mail_log $INFO ">>>>>>>>>> daily auto backup $VER for $HOST_NAME <<<<<<<<<<" do_auto_backup backup_daily mail_log $INFO ">>>>>>>>>> end of auto backup" send_mail_log } backup_weekly() { mail_log $INFO ">>>>>>>>>> weekly auto backup $VER for $HOST_NAME <<<<<<<<<<" do_auto_backuo backup_weekly mail_log $INFO ">>>>>>>>>> end of auto backup" send_mail_log } export_backups() { # TODO echo # check host (only nathan|hamiller?) # check USB drive # del USB drive (?) # rsync USB -> 1|2, video, serien # send_mail_log } # Inhalt des Archivs in eine Textdatei ausgeben. # # $1 - Archiv # # returns 0 for success, 1 otherwise # create_list() { log $DEBUG "$DBG create_list $@" local archive=$1 log $DEBUG "$DBG > gpg --batch --decrypt --passphrase xxxx \"$archive\" | tar -tzv | tee \"$archive.$LIST_EXT\"" gpg --batch --decrypt --passphrase $PASSPHRASE "$archive" 2>>$LOG_FILE | tar -tzv 2>>$LOG_FILE | tee "$archive.$LIST_EXT" >/dev/null 2>>$LOG_FILE check_pipe_rc ${PIPESTATUS[@]} return $? } # Integrität des Archivs testen, indem es entschlüsselt, entpackt # und der Inhalt auf /dev/null ausgegeben wird. # # $1 - Archiv # # returns 0 for success, 1 otherwise # test_integrity() { log $DEBUG "$DBG test_integrity $@" local archive=$1 log $DEBUG "$DBG > gpg --batch --decrypt --passphrase xxxx \"$archive\" | tar -xzO" gpg --batch --decrypt --passphrase $PASSPHRASE "$archive" 2>>$LOG_FILE | tar -xzO >/dev/null 2>>$LOG_FILE check_pipe_rc ${PIPESTATUS[@]} return $? } # Restore aus Archiv # # Wird nach pwd ausgegeben! # # $1 - Archiv, ggf. mit Pfad # $2 ff. - zurückzusichernde Dateien/Verzeichnisse # # returns 0 for success, 1 otherwise # restore() { log $DEBUG "$DBG restore $@" local archive=$1 shift 1 log $DEBUG "$DBG > gpg --batch --decrypt --passphrase xxxx \"$archive\" | tar -xzv $@" gpg --batch --decrypt --passphrase $PASSPHRASE "$archive" 2>>$LOG_FILE | tar -xzv $@ >/dev/null 2>>$LOG_FILE check_pipe_rc ${PIPESTATUS[@]} return $? } # Liste der Archives auf stdout ausgeben # # Zeigt alle Archive incl. Verzeichnis (ohne $1) an # # z.B. # $1 = /volume1/nfs/backups # ... # /daily/synology/backup.3/volume1-nfs-Server.tgz.gpg # /weekly/synology/volume1-media-books.tgz.gpg # # $1 - Verzeichnis unter dem alle Backups liegen # # returns 0 for success, 1 otherwise # list_archives() { log $DEBUG "$DBG list_archives $@" local target=$1 local targ=${target//\//\\\/} # slashes escapen log $DEBUG "$DBG > find \"$target\" -type f -name \"*.$EXT\" | grep \"${HOST_NAME}/\" | sed -E \"s/^$targ//\"" find "$target" -type f -name "*.$EXT" 2>>$LOG_FILE | grep "${HOST_NAME}/" 2>>$LOG_FILE | sed -E "s/^$targ//" 2>>$LOG_FILE check_pipe_rc ${PIPESTATUS[@]} return $? } # Inhalt des Archivs $2 auf stdout ausgeben # # $1 - Verzeichnis unter dem alle Backups liegen # $2 - Archiv-Datei mit Pfad ab $1 # # returns 0 for success, 1 otherwise # list_archive_content() { log $DEBUG "$DBG list_archive_content $@" local root=$1 local archive=$2 log $DEBUG "$DBG > cat \"$root/${archive/#\//}.$LIST_EXT\"" cat "$root/${archive/#\//}.$LIST_EXT" 2>>$LOG_FILE }