#! /bin/bash -E ## @id $Id$ ## 1 2 3 4 5 6 7 8 ## 45678901234567890123456789012345678901234567890123456789012345678901234567890 #set -x # Simple setup of a list of hosts (internal and external): # # setup-backup dev.swisssign.com dev0001 dev0002 dev0004 devmac0001 # # What the setup script really does is: # # HOSTS="dev_swisssign_com dev0001 dev0002 dev0004 devmac0001" # for period in daily weekly monthly; do # cd /etc/cron.$period; # for h in $HOSTS; do # sudo ln -s $0 backup-$h; # done; # done ################################################################################ # Configuration (read from @sysconfdir@/backup.conf and ~/.backup # or host specific backup from backup-HOST.conf and .backup-HOST ################################################################################ # disable: dont' start backup if a certain file exists DISABLE=${DISABLE:-/var/run/nobackup} if [ -e ${DISABLE} ]; then return fi # get host name from filename (name files with _ instead of .) # e.g. # $0 (this file name) is backup-dev_marc_waeckerlin_org # $HOST becomes dev.marc.waeckerlin.org # you can test for $HOST in /etc/backup.conf or ~/.backup.conf HOST=${HOST:-$(basename ${0##*/backup-} .sh | sed 's/_/./g')} # Get defaults from a file /etc/backup.conf or ~/.backup.conf # file is bash syntax and shall define variables, i.e. # # EMAIL="me@freemail.com" # NUM=2 # TARGET="/media/backup" # MORE_ARGS="--exclude atheismus.ch_error_log --exclude marc_error_log" # prefix="@prefix@" test -e "@sysconfdir@/backup.conf" && . "@sysconfdir@/backup.conf" test -e ~/.backup && . ~/.backup test -e "@sysconfdir@/backup-$HOST.conf" && . "@sysconfdir@/backup-$HOST.conf" test -e ~/".backup-$HOST" && . ~/".backup-$HOST" # E-Mail to report errors EMAIL=${EMAIL:-""} # Number of backups to keep per type NUM=${NUM:-5} # Target to backup to TARGET=${TARGET:-"/var/backup"} # The following variables are detected automatically # and setup with good defaulte # You rarely need to change them # get name of host to backup from file name # e.g. /var/cron/backup-my-host-com backups from url my.host.com # if your hostnaim contains dashes '-', then you're lost # the keyword 'backup-generic' defaults to this host here if [ "$HOST" = "generic" -o -z "$HOST" ]; then HOST=$HOSTNAME HOSTADDR=${HOSTADDR:-""} BACKUP=${BACKUP:-""} else BACKUP=${BACKUP:-"-e ssh"} HOSTADDR=${HOSTADDR:-"${HOST}:"} fi SOURCES=${SOURCES:-$(ssh $HOST mount | awk '/^\// && !/'${TARGET//\//\\\/}'/ && $2=="on" && $6!~/bind/ {print $3}')} LOGFILE=${LOGFILE:-"/var/log/backup.log"} LOCK=${LOCK:-${TARGET}/${HOST}.lock} TMPFILE=${TMPFILE:-/tmp/${0##*/}.$$} RSYNC_CMD=${RSYNC_CMD:-"nice -n 19 ionice -c 2 -n 7 rsync"} LOCAL_RSYNC_CMD=${LOCAL_RSYNC_CMD:-$RSYNC_CMD} REMOTE_RSYNC_CMD=${REMOTE_RSYNC_CMD:-$RSYNC_CMD} ARGS=${ARGS:-"-aqx --delete"} ARGS="$ARGS --rsync-path='${REMOTE_RSYNC_CMD}' $BACKUP --exclude ${TARGET} ${MORE_ARGS}" ################################################################################ function error() { if test -f "${TMPFILE}"; then if test -n "$EMAIL"; then mail -s "Backup ERROR: $0:$1 ret:$2" "${EMAIL}" < "${TMPFILE}" fi rm "${TMPFILE}" 2> /dev/null || true echo "$(date) $0 $$ ******** Backup ERROR: $0:$1 ret:$2" >> "${LOGFILE}" else if test -n "$EMAIL"; then echo "Aborted without logfile $TMPFILE" | \ mail -s "Backup ERROR: $0:$1 ret:$2" "${EMAIL}" fi echo "$(date) $0 $$ ******** Backup ERROR: $0:$1 ret:$2" >> "${LOGFILE}" fi exit 1 } trap 'error ${LINENO} $?' ERR umask 022 export PATH=/sbin:/bin:/usr/sbin:/usr/bin # check rsync capabilities RSYNC_VERSION=$(rsync --version | sed -n 's,.*rsync version \([0-9.]*\).*,\1,p' | awk -F. '{printf "%04d%04d", $1, $2}') if [ $RSYNC_VERSION -ge 00020006 ]; then HAS_MULTIDEST=1 else HAS_MULTIDEST=0 fi ( echo "$(date) $0 $$ ########################################################" # Prüfen, ob Server erreichbar ist if ! ping -q -w 5 -c 1 $HOST 2> /dev/null > /dev/null; then echo "$(date) $0 Server $HOST ist nicht erreichbar!" 1>&2 error exit 2 else echo "$(date) $0 $$ Server $HOST is reachable" fi # locking echo "$(date) $0 $$ ******** Aquire lock for $HOST from $0" flock -x 200 echo "$(date) $0 $$ ******** Got lock for $HOST from $0" ## Prüfen, ob Sicherungsmedium vorhanden ist if [ "$TARGET" != "${TARGET#/media/}" ]; then if ! (mount | grep "$TARGET" 2> /dev/null > /dev/null); then echo "$(date) $0 Die Partition $TARGET ist nicht eingebunden!" 1>&2 error exit 2 fi else if ! test -d "${TARGET}"; then echo "$(date) $0 Der Sicherungspfad $TARGET ist nicht vorhanden!" 1>&2 error exit 2 fi fi echo "$(date) $0 $$ Backup ${HOST} to ${TARGET}" FULL=$(LANG= df -P ${TARGET} | awk '$1 !~ /^File/ {print $5}' | tr -d '%' | tail -1) echo "$(date) $0 $$ Füllgrad: ${FULL}%" if [ "$FULL" -gt 80 ]; then if [ "$FULL" -gt 98 ]; then echo "$(date) $0 **** Alarm! **** ${TARGET} ist $FULL% voll" 1>&2 error exit 2 fi echo "$(date) $0 $$ **** Warnung! **** ${TARGET} ist $FULL% voll" fi # Sicherungstyp eruieren case "$(pwd)/$0" in *hourly*) TYPE="hourly";; *daily*) TYPE="daily";; *weekly*) TYPE="weekly";; *monthly*) TYPE="monthly";; *) TYPE="manual";; esac # 3. Was ist die Referenz? # # Die Sicherung macht Hardlinks entweder zu hourly.1 oder wenn der nicht # existiert zu daily.1 oder wenn der nicht existiert zu manual.1 # # Die Idee ist, immer zum aktuellsten zu verlinken. Auf einem System # mit regelmässigem Backup ist das gestern, auf einem System mit # Backup von Hand, ist das das letzte manuelle. (Beispiel: Mein Server # hat ein tägliches Backup, mein Notebook ein manuelles: Automatisch # wenn ich es am USB angehängt habe.) # # Prüfung daily oder manual? # a) gibt es den hourly.1? -> hourly # b) gibt es den daily.1? -> daily # c) gibt es manual.1? -> manual # d) sonst -> Was ich bin... if [ -e ${TARGET}/${HOST}-hourly.1 ]; then echo "$(date) $0 $$ **** Found an hourly backup for reference" REFTYPE=hourly elif [ -e ${TARGET}/${HOST}-daily.1 ]; then echo "$(date) $0 $$ **** Found an daily backup for reference" REFTYPE=daily elif [ -e ${TARGET}/${HOST}-manual.1 ]; then echo "$(date) $0 $$ **** Found an manual backup for reference" REFTYPE=manual else echo "$(date) $0 $$ **** No reference found, try $TYPE" REFTYPE=$TYPE fi REFNUM=1 echo "$(date) $0 $$ **** SOURCES:" $SOURCES # 4b. Sonst aktuelle Sicherungskopie gegen neueste Sicherung anlegen for S in ${SOURCES}; do SRC=${S%/}/ # Detect possible references, prefered (from above) first REFERENCES= for dest in ${TARGET}/${HOST}-${REFTYPE}.${REFNUM}${SRC} \ ${TARGET}/${HOST}-${REFTYPE}.[0-9]*${SRC} \ ${TARGET}/${HOST}-{hourly,daily,weekly,monthly}.${REFNUM}${SRC} \ ${TARGET}/*-${REFTYPE}.${REFNUM}${SRC}; do if [ -d ${dest} -a "${REFERENCES//$dest/}" = "${REFERENCES}" ]; then REFERENCES="${REFERENCES} $dest" if [ $HAS_MULTIDEST -eq 0 ]; then break; fi fi done DESTINATIONS="${REFERENCES// / --link-dest=}" echo "$(date) $0 $$ **** REFERENCES: ${REFERENCES}" if [ -d ${TARGET}/${HOST}-${TYPE}.new${SRC} ]; then rm -r ${TARGET}/${HOST}-${TYPE}.new${SRC} fi CMD="${LOCAL_RSYNC_CMD} ${ARGS} ${DESTINATIONS} ${HOSTADDR}${SRC%/}/ ${TARGET}/${HOST}-${TYPE}.new${SRC%/}/" echo "$(date) $0 $$ **** BACKUP: ${CMD}" bash -c "$CMD" || true RES=${PIPESTATUS[0]} if [ $RES -eq 0 ]; then echo "$(date) $0 $$ **** SUCCESS: ${CMD}" elif [ $RES -eq 24 ]; then echo "$(date) $0 $$ **** WARNING: ${CMD}" mail -s "Backup Warning: $0" "${EMAIL}" < "${TMPFILE}" else echo "$(date) $0 **** ERROR $RES: ${CMD}" 1>&2 error exit 2 fi done touch ${TARGET}/${HOST}-${TYPE}.new/backup-timestamp # 5. Altern if [ -d "${TARGET}/${HOST}-${TYPE}.1" ]; then # 1. Älteste Sicherungskopie löschen if [ -d "${TARGET}/${HOST}-${TYPE}.${NUM}" ]; then echo "$(date) $0 $$ **** REMOVE: ${TARGET}/${HOST}-${TYPE}.${NUM}" rm -rf "${TARGET}/${HOST}-${TYPE}.${NUM}" fi # 2. Bestehende Sicherungskopien um eine Stufe altern lassen while [ $((--NUM)) -gt 0 ]; do if [ -d "${TARGET}/${HOST}-${TYPE}.${NUM}" ]; then echo "$(date) $0 $$ **** MOVE: ${TARGET}/${HOST}-${TYPE}.${NUM}" mv ${TARGET}/${HOST}-${TYPE}.${NUM} \ ${TARGET}/${HOST}-${TYPE}.$((NUM+1)) 2> /dev/null fi done fi # 6. move xxx.new to xxx.1 mv ${TARGET}/${HOST}-${TYPE}.new ${TARGET}/${HOST}-${TYPE}.1 echo "$(date) $0 $$ ############################################### Finished" ) 200> "${LOCK}" 2>&1 | tee "${TMPFILE}" >> "${LOGFILE}" rm "${TMPFILE}" 2> /dev/null || true