You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
8.9 KiB
272 lines
8.9 KiB
#! /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
|
|
|