#!/bin/bash -x # $Id: backup.sh 7 2006-04-15 23:27:00Z root $ # # Introduction: # ----------------------------- --------- ---- - - # This is a simple back-it-up script by Marcus Fritzsch # http://fritschy.de # # This code is lisenced under the GNU GPL Version 2 or any later # version at your option. # # History: # ----------------------------- --------- ---- - - # * 2006-04-10: first version, author: Marcus Fritzsch # * 2006-04-11: more configuration variables and error checks # * 2006-04-13: - more lethal errors, and misc cleanups # - introduced $BACKUP_BASE as base directory # to not abuse / (still possible though!) # - gpg now reads through a pipe from tar # * 2006-04-16: - added $CONFIGURED variable # - added space checking on target and warn/fail # # Prerequisites: # ----------------------------- --------- ---- - - # * the BACKUP_SVC variable vontains user@host info of where to store # the backup files. Setup a backup service user on some secure host # and authorize the user's key that runs this script on that host. # Note: you may set a _complex_ password after this procedure to # avoid abuse of this user account # # * The backup will be temporarily be written to /tmp/backup_$DATE # feel free to change this according to your needs - but make sure # there is enough free space for creating backup gzipped-tarballs # and also enough space for the encryption step (twice the tarball # plus 20% for an ascii armored encrypted file - i guess) # # * This script may be run by a cron job every day or every two days # As you like too. # When there are problems with the locking, i.e. there is a lock # left in the lock-directory the script will wait up to 10h for # the lock to be removed (and/or remove it on it's own!) # If this script should run more than once a day you should be aware # of this! # # my cron-job: # 17 3 * * * test -x /root/backup.sh && /root/backup.sh &> /dev/null # # * Additionally to the $BACKUP_WHAT variable a dpkg --get-selections # will be backed up # # Notes: # ----------------------------- --------- ---- - - # * The check for the storage-host to be available is currently done # by doing a test-ssh-connection with nc(1) and filtering out some # part of the connect string the ssh server sends - feel free to # adopt some more appropriate method. # # Notes On Encryption: # ----------------------------- --------- ---- - - # * Currently gnupg and a public key of some user is used to encrypt # the backup files. If there is another easy way of asynchronous # encryption - this should work as well! # # * Make sure to have the public key of the user installed you want # the files to be encrypted for _and_ make also sure you trust this # user ultimately. To set the trust status of a user: # gpg --edit-key $KEYID # # Configuration: # ----------------------------- --------- ---- - - ENCRYPTKEY=gpg-keyid-here # if undefined/empty: no encryption! # this is a gpg keyid or other _uniq_ # identifier LOCK=/var/lock/backup.sh # lock file to use POSTMASTER=postmaster@localhost # where shall emails go on error BACKUP_SVC=user@host # backup-user@storage-host BACKUP_NAME=backup.sh # a date-string will pe appended! # the backup will be put into a # directory named that way BACKUP_WHAT="/etc /root /var/wiki /var/www /var/spool /boot /home \ /var/lib/filetraq /svn" # which directories should be saved LOCK_RELEASE_WAIT=4 # time to wait before releasing a foreign # lock in hours COPY_WAIT=16 # time to wait for storage-host to become # available and copy files over # If this time is exceeded, fail() is # called TAR_OPTS="cjpslPf" # options passed to tar on compression BACKUP_BASE="/var/lib/backuptmp" # where the backup is temporarily stored # possibly anothe fs to store all that # stuff ;) CONFIGURED= # set this to some value to enable the # script if [ -z "$CONFIGURED" ]; then echo "Please run this script _after_ having configured it" >&2 exit 1 fi # this script is tested only on bash 2.05/3.0 if [ -z "$BASH_VERSINFO" ]; then echo "This script is only tested on BaSH" >&2 echo "" exit 1 fi shopt -s expand_aliases ELOG=/tmp/backup.err exec 2> $ELOG # make sure, backup temp storage being available test -d $BACKUP_BASE || mkdir $BACKUP_BASE chmod 700 $BACKUP_BASE alias log="/usr/bin/logger -t backup.sh[$$] --" START=$(date +%s) DATE=$(date +%Y%m%d-%H%M) BACKUP_NAME=${BACKUP_NAME}_$DATE test "$ENCRYPTKEY" && REMOVE_CMD="rm -f" || REMOVE_CMD="/usr/bin/shred -f -n 7 -u -z" # error status, ! -z if there were errors ERRS= err () { # before exiting this will be tested to be ! -z ERRS="yes" } mail () { log "some errors happened - giving up and mailing home" /usr/bin/mail -s "[BACKUP] backup.sh $DATE failed" -N root@localhost \ < $ELOG } unlock () { rm -f $LOCK || err } cleanup () { cd $BACKUP_BASE/backup_$$_$DATE && $REMOVE_CMD * || err cd $BACKUP_BASE/ || err rmdir $BACKUP_BASE/backup_$$_$DATE || err unlock trap - SIGINT SIGHUP SIGTERM } fail () { cleanup mail exit 1 } signal () { echo "Got a signal - exiting" >&2 fail } # lock checking is mainly to prevent the HD to become full too fast when # some backup runs overlap C=0 while [ $C -lt $((LOCK_RELEASE_WAIT * 10)) ]; do C=$((C+1)) test -f $LOCK || break log "backup lock in /var/lock present - waiting" sleep 360 # 6min, 10times an hour done if [ $C -ge $((LOCK_RELEASE_WAIT * 10)) ]; then err log "LOCK seems to be from a dead backup instance... removing" rm -f $LOCK fi # regiter some signal handlers trap signal SIGINT SIGHUP SIGTERM # make sure that $COPY_WAIT + $LOCK_RELEASE_WAIT < 24 to not overlap with # possible later runs of this script # XXX: when the cron job runs every 2 or 3 days - should i allow a longer # wait time?!? if [ $(($LOCK_RELEASE_WAIT + $COPY_WAIT)) -ge 24 ]; then echo "MISCONFIGURATION" >&2 echo "Script parameters \$LOCK_RELEASE_WAIT and \$COPY_WAIT can make" >&2 echo "it wait longer than 24h - this might lead to overlapping with" >&2 echo "other backup runs" >&2 fail fi date +%s > $LOCK log "starting backup on $DATE" mkdir $BACKUP_BASE/backup_$$_$DATE || fail chmod 700 $BACKUP_BASE/backup_$$_$DATE || err cd $BACKUP_BASE/backup_$$_$DATE || fail # the data collection starts here log "taking dpkg selections list" dpkg --get-selections > dpkg-get-selections.txt for i in $BACKUP_WHAT; do TARB=${i#/} TARB=${TARB//\//_}.tar.bz2 if [ "$ENCRYPTKEY" ]; then log "collecting and encrypting data for $i" (tar $TAR_OPTS - $i | gpg -r $ENCRYPTKEY -e > $TARB.gpg) || fail else log "collecting data for $i" tar $TAR_OPTS $TARB $i || fail fi done DATE2=$(date +%Y%m%d-%H%M) log "finished on $DATE2, runtime: $(expr $(date +%s) - $START) seconds" C=0 COPIED= while [ $C -lt $((COPY_WAIT * 10)) ]; do C=$((C+1)) # wait until storage-hosts becomes available SSH="$(echo ""|nc -w1 nexus 22|head -n1|cut -d- -f1)" if [ "$SSH" = "SSH" ]; then # wait some seconds, to not disturb a bootup in progress! sleep 60 cd $BACKUP_BASE/ RFREE=$(ssh $BACKUP_SVC df --portability -k .|awk 'NR==2{print $4}') BSIZE=$(du -ks $BACKUP_BASE/|awk '{print $1}') # not enough space... roughly ;) if [ $(($RFREE - $BSIZE / 4)) -lt $BSIZE ]; then log "copying of backup not possible, not enough space on target" fail fi # just warn but continue if [ $(($RFREE - $BSIZE * 3)) -lt $BSIZE ]; then log "TARGET SPACE IS GOING LOW" err fi log "start copying backup data to storage host" scp -r $BACKUP_BASE/backup_$$_$DATE $BACKUP_SVC:$BACKUP_NAME || fail COPIED=yes break fi sleep 360 # 6min, 10times an hour done # could not copy - storage host not available test -z "$COPIED" && fail log "copying finished, cleaning up" cleanup log "everything done - exiting" test "$ERRS" && mail rm -f $ELOG exit 0