#!/bin/bash #================================================== # FILE: mybackup.sh # # USAGE: mybackup.sh [args] # # DESCRIPTION: erzeugt Backup von Ordnern (jede Datei einzeln komprimiert) auf externem Medium # AUTHOR: K.B. VERSION="1.1.0" # CREATED: 2014-02-05 REVISION=$(ls -l --time-style=+%F $0 | cut -d\ -f6) # $Id: $ # https://osdn.net/users/klaus_bernstein/pf/mybackup # # Die endgültige Datei liegt in /usr/local/sbin mit dem Namen mybackup # Die Konfigurationsdatei mybackup.conf in /etc/mybackup/ # dies kann durch Angabe der Option -c überschrieben werden # seit Version 0.7.0 hat diese Konfigurationsdatei die Struktur einer XML-Datei # # HISTORY: # 2018-12-26 (v1.1.1): versuchsweise unter Subversion in OSDN abgelegt # 2018-12-05 (v1.1.0): prüft bei incr Backup ob Vollbackup vorhanden # 2018-05-01 (v1.0.2): LABEL aus conf-Datei gelesen # 2018-02-03 (v1.0.1): max. Anzahl Vollbackup in conf-Datei ausgelagert # 2018-01-17 (v1.0.0): minimale Logdatei angelegt # 2016-10-01 (v0.9.3): Test, ob CONFDIR vorhanden # 2016-09-19 (v0.9.0): aktuelle INCR-Datei vor Neuanlegen gelöscht # # TODO: # # 2016-09-06: # Zeitstempel überdenken # s. http://wiki.linux-club.de/opensuse/Zeitstempel_von_Dateien # ev. doch MODULO (o.ä.) benutzen # # 2016-06-12: # jetzt=$(date +%s) # date --date=@$jetzt # # Datum sechs Monate vorher: # date --date='-6 month' # für das automatische Löschen des ältesten Backup # # 2016-06-04: # PRE und POST-Skripte variabel halten? # die im Konfigurationsfile angegebenen Werte durch Option --pre bzw. --post überschreiben? # Hintergrund: Manche Aktionen (wie zB. Pakete aufräumen nicht immer ausführen) # oder solche Sachen als cronjob laufen lassen (sauberer) # # Logdatei über den Backupvorgang anlegen? # # 2016-04-04: # mount kontrollieren # Überprüfen, ob das Backup-Medium (=ZIEL) ev. schon gemountet ist # # vorhandene Festplatten kontrollieren # gibt es /dev/sda, /dev/sdb usw. -> wird nach POST (ev. PRE) ausgelagert # # DB-Rettung u. Paket-Listing verändern # da verschiedene Datenbanken und auch verschiedene Paketmanager je nach System vorhanden sein können, # den ganzen Mechanismus am besten in die Konfigurationsdatei verlagern (Abteilung POST und/oder PRE) #================================================== # Error upon expanding unset variables: set -u # Standard variables __myname=${0##*/}; ######################################################### # SUBROUTINES. BEGIN. ######################################################### function usage() { cat << EOF ${__myname} [args] mybackup erzeugt Backup von Ordnern (jede Datei einzeln komprimiert) auf externem Medium -h Show this help screen -c Konfigurationsfile -d differentielles Backup -i inkrementelles Backup -s tut nichts, simuliert nur das Backup -v Be very verbose (uses set -x) -V Print version information This is version ${VERSION}, released on ${REVISION} EOF exit 0 } function error_exit() { ERROR_STATUS=${2:-1} echo "ERROR ${ERROR_STATUS}: ${1:-Unbekannter Fehler}" >&2 exit ${ERROR_STATUS} } function log() { $TEST && echo ${1} } function rdom() { # QUELLE: https://schnallich.net/index.php/Bash/Parse_XML export TAG; export VAL; local IFS=\> ; read -d \< TAG VAL ; } function lesen() { while rdom do if [[ $TAG = $1 ]]; then echo ${VAL}; fi; done < ${CONFFILE} } function del_old_backup() { # sorgt dafür, daß nie mehr als MAX Vollbackups existieren local LD="ls -dtr1" # -d Verzeichnisse an sich auflisten, nicht deren Inhalte # -r Umgekehrte Reihenfolge beim Sortieren # -t Nach Änderungszeit sortieren, neueste zuerst # -1 zeigt eine Datei pro Zeile an local DIR=$1 while [[ $($LD $DIR/[01]* | wc -l) -gt $MAX ]] do oldDir=$($LD $DIR/[01]* | head -n1) rm -r $oldDir done } function del_old_incr() { # löscht alte incrementelle Backups # als fn damit es in den Hintergrund geschickt werden kann local DIR=$1 local OLD=$DIR.old mv $DIR $OLD echo "lösche $DIR" rm -r $OLD } ######################################################### # SUBROUTINES. END ######################################################### [[ $EUID -ne 0 ]] && error_exit "This script must be run as root" TEST=true INCREMENTELL=false LABEL="LBACK" # Standard-Label der Backup-Partition, wird in der Konfig-Datei gesetzt CONFDIR="/etc/mybackup" # Standard-Directory für Konfiguration [ -d "$CONFDIR" ] || mkdir "$CONFDIR" # CONFDIR anlegen, wenn nicht vorhanden CONFFILE="${CONFDIR}/mybackup.conf" # Konfigurationsfile, s.a. Option -c LOGDIR="/var/log/mybackup" # Standard-Directory für Logdatei [ -d "$LOGDIR" ] || mkdir "$LOGDIR" # LOGDIR anlegen, wenn nicht vorhanden LOGFILE="${LOGDIR}/mybackup.log" EX_FILE="${CONFDIR}/exclude" # enthält die exclude-Muster, wird temporär erzeugt MNT="/mnt/backup" # Mountpoint für Ziel (kann in der Konfigdatei überschrieben werden) RSYNC_OPT="-aRCP --exclude-from=${EX_FILE}" RSYNC_DRY="" # wird auf -n gesetzt, wenn rsync nur simuliert werden soll RSYNC_INC="" # Bezugsdirectory für incrementelles Backup (Option -i) while getopts ":hc:disvV" options; do case $options in h ) usage ;; c ) if [ -f $OPTARG ] ;then CONFFILE=${OPTARG} log "Konfigurationsfile: ${CONFFILE}" else error_exit "Konfigurationsfile ${OPTARG} nicht vorhanden" 2 fi ;; s ) echo "ACHTUNG: Backup-Lauf wird nur simuliert!" echo "10 Sekunden zum Abbrechen mit Ctrl-C" TEST=false RSYNC_DRY="-n" for i in {1..2} do echo -n . sleep 1s done echo ;; v ) set -v ;; d|i ) log "Option: ${OPTIND}" INCREMENTELL=true ;; V ) echo "${__myname}, Version ${VERSION}, Revision ${REVISION}" exit 0 ;; \? ) error_exit "Unzulässige Option -$OPTARG" 3 ;; esac done shift $((OPTIND-1)) # Optionen "rausschieben" ######################################################### # OPTIONEN. END ######################################################### log "CONFDIR=$CONFDIR" log "CONFFILE=$CONFFILE" [ -f ${CONFFILE} ] || error_exit "${CONFFILE} nicht gefunden" 2 set -f # Globbing ausschalten user_mnt=$(lesen 'MOUNT') MNT=${user_mnt:=${MNT}} log "MNT=${MNT}" NAME=$(lesen 'NAME') log "NAME=${NAME}" LABEL=$(lesen 'LABEL') log "LABEL=${LABEL}" QUELLE=$(lesen 'QUELLE') log "QUELLE=${QUELLE}" MAX=$(lesen 'MAX') log "MAX=${MAX}" if [[ ${MAX} = '' ]] then error_exit "Kein Wert für MAX gefunden" 4 fi EXCLUDE=$(lesen 'EXCLUDE') log "Config for the exclude: ${EXCLUDE}" echo "# File nicht editieren, wird automatisch erzeugt!" > ${EX_FILE} for m in ${EXCLUDE} do echo ${m} >> ${EX_FILE} done POST=${CONFDIR}/$(lesen 'POST') log "Postfile: ${POST}" set +f # Globbing wieder an if [ ! -f ${POST} ] then error_exit "Post-File ${POST} existiert nicht" 5 fi if [[ ! -d ${MNT} ]] then mkdir ${MNT} || error_exit "mountpoint ${MNT} lässt sich nicht anlegen" $? fi mount -L ${LABEL} ${MNT} || mount -l | egrep $MNT.+$LABEL || error_exit "mount ${LABEL} an ${MNT} fehlgeschlagen" $? ZIEL=${MNT}/${NAME}/$(date +%m) if $INCREMENTELL then [ ! -d ${ZIEL} ] && error_exit "Kein Vollbackup für $(date +"%B %Y") vorhanden" 6 lfd_nr=$[$(date +%-d)/4] # Minuszeichen hinter % verhindert führende Null RSYNC_INC="--link-dest=$ZIEL" ZIEL=${MNT}/${NAME}/INCR$lfd_nr del_old_incr ${ZIEL} & else # alte Vollbackups löschen del_old_backup ${MNT}/${NAME} & fi mkdir -p ${ZIEL} || error_exit "Fehler beim anlegen von DIR ${ZIEL}" date +"%F_%X%t$ZIEL" > ${LOGFILE} for v in ${QUELLE}; do log "kopiere von ${v}-->${ZIEL}" rsync ${RSYNC_OPT} ${RSYNC_DRY} ${RSYNC_INC} ${v} ${ZIEL} # || error_exit "rsync" $? K.B. 2011-11-01 done echo "==== PHASE 2 ===" . ${POST} exit