#!/bin/ksh -u
# Backup of the rsync backups to tape.
#
#      /\
#     /  \            (C) Copyright 2006 Parliament Hill Computers Ltd.
#     \  /            All rights reserved.
#      \/
#       .             Author: Alain Williams, <addw@phcomp.co.uk> April 2006
#       .
#        .            SCCS: @(#)WriteRsyncBackupToTape	1.9 12/29/08 19:10:29
#          .
#


# Tape: control and no rewind devices.
tape_ctl=/dev/st0
tape_rw=/dev/st0
tape_nr=/dev/nst0


# **************** You should not need to change anything below this line ****************

PROGNAME=${0##*/}

yyyymmdd=$( date '+%Y''%m%d' )
hhmm=$( date '+%H''%M' )
logdir=/var/log/tapebackups/ # for help message
DoCopyLog=n		# Copy output to stderr as well as $logdir
ShowLabel=n		# Show the tape tabel & exit
RsyncBackupSuccessFile=RsyncBackupSuccessTime	# File touched on last Rsync backup success - start of rsync
Machines=		# Machine names backed up
verbose=		# Tar verbose option
LocalDir=/arch		# Where to find the rsync backups on this machine
incr=n			# True if increment since last complete archive

# Create Label - this is somewhat Linux specific
# Arguments:
# 1	# times that the tape has been written
# 2	Tape Ident line (line 3)
function CreateLabel {
    (
	echo "Backup for $( hostname ) on $( date )"
	echo
	echo " -	This label"
	echo "Tape contents (as separate tape files):"
	for machine in $Machines
	do	echo " -	Copy ($Type) of backup taken by rsync for: $machine"
	done
	echo "Each tape file is a (gnu) tar archive"
	echo

	echo "The information that follows is for the machine that wrote this tape"
	echo ""

	echo "System information:"
	uname -a
	echo

	echo "Memory & Swap:"
	free
	swapon -s

	echo
	echo "Hard disk parameters:"

	for hd in $(sed -n -e '/[hs]d.$/s/^.* //p' /proc/partitions )
	do	hdparm -i /dev/$hd
	done

	echo
	echo "Disk parititioning:"
	fdisk -l 2>/dev/null

	# If mdadm is installed:
	if type mdadm > /dev/null 2>&1
	then	echo
		echo "Possible mirror config:"
		mdadm --examine --verbose --scan --config=partitions

		# Try to give a bit more info:
		for md in $( mdadm --examine --verbose --scan --config=partitions | awk '/^ARRAY/ { print $2 } ' )
		do	echo
			echo "Mirror device: $md"
			mdadm --misc --detail $md
		done
	fi

	# If LVM is installed:
	if type pvdisplay > /dev/null 2>&1
	then	echo
		echo "LVM config:"
		pvdisplay --verbose 2> /dev/null
		lvdisplay 2> /dev/null
		vgdisplay --verbose 2> /dev/null
	fi

	echo
	echo "Mounted File Systems:"
	mount -v
	echo

	# Don't inspect over NFS - we could hang as a result
	echo "Disk Usage:"
	FSys="$( mount | awk '$1 ~ /:/ {next} {print $3}' )"
	df -Pk  $FSys
	echo
	df -Pki $FSys

	# How are they mounted ?
	echo
	echo "Contents of /etc/fstab:"
	cat /etc/fstab
	echo

	# Lilo information
	if type lilo > /dev/null 2>&1
	then	echo
		echo "Lilo information:"
		lilo -q -v -v -v
		echo
	fi

	echo "Boot info:"
	echo "ls -l /boot"
	ls -l /boot
	echo

	# This might get some duplicates:
	for file in grub.conf menu.lst
	do	if [[ -r /boot/grub/$file ]]
		then	echo
			echo "Contents of: /boot/grub/$file" 
			cat /boot/grub/$file
			echo
		fi
	done

	if [[ -r /etc/Hacks ]]
	then	echo
		echo "Contents of /etc/Hacks, last modified: $( find /etc/Hacks -printf '%Tc' )"
		echo "================================"
		cat /etc/Hacks
		echo "================================"
		echo
	fi

	if type ifconfig > /dev/null 2>&1
	then	echo
		echo "Network config: ifconfig"
		ifconfig
	fi

	if type netstat > /dev/null 2>&1
	then	echo
		echo "Network config: routing tables"
		netstat -rn
	fi

	if type rpm > /dev/null 2>&1
	then	echo
		echo "Installed packages:"
		rpm -qa
	fi

	if type dpkg > /dev/null 2>&1
	then	echo
		echo "Installed packages:"
		dpkg --list
	fi

}

function Usage {
	cat <<-!
	Backup to tape
	-C dev	tape Control device, default $tape_ctl
	-I	Incrememtal backup since last complete tape backup
	-L	Show the tape tabel & exit
	-l dir	root Local directory, default is '$LocalDir'
	-o	Copy output to the current stderr as well as something in $logdir
	-m mach	space separated list of machines for which to write to tape the Rsync backup
	-N dev	tape device (no rewind on close), default $tape_nr
	-T dev	tape device (rewind on close), default $tape_rw
	-v	Tar verbose option
	-x	eXplain
	!
}

# Parse options, recognise --help
while	OPTIND=1
	if [[ $# != 0 && $1 = --help ]]
	then	opt=x	# --help is -x
	else	getopts C:Il:Lom:N:T:vx opt
	fi
do	case "$opt" in
	C)	tape_ctl=$OPTARG ;;
	I)	incr=y ;;
	L)	ShowLabel=y ;;
	l)	LocalDir="$OPTARG" ;;
	o)	DoCopyLog=y ;;
	m)	Machines="$OPTARG" ;;
	N)	tape_nr=$OPTARG ;;
	T)	tape_rw=$OPTARG ;;
	v)	verbose=v ;;
	x)	Usage
		exit;;
	*)	echo "$PROGNAME: Unknown option '$1'" >&2
		Usage >&2
		exit 2;;
	esac
	shift $((OPTIND - 1))
done

[[ $incr = n ]] &&
	Type=full ||
	Type=incremental

# Just display what the label would contain 
if [[ $ShowLabel = y ]]
then	CreateLabel
	exit
fi

[[ -z $Machines ]] &&
	echo "Machines not specified (-m)" >&2 &&
	Usage &&
	exit 2

# Fiddle with where the output of this program goes to.
# The point is that under normal operation we want to capture/redirect
# everything to a log file, when run manually we might want to do that
# but still have everything come to the terminal as well.
[[ -d $logdir ]] || mkdir -p $logdir || exit 2
exec 3>&2
if [[ $DoCopyLog = y ]]
then	tee /proc/$$/fd/3 > $logdir$yyyymmdd-$hhmm |&
	exec 1>&p 2>&1
else
	# If being run non interactively, send output to a log file:
	[[ ! -t 2 ]] && exec > $logdir$yyyymmdd-$hhmm 2>&1
fi


cd / || exit 2

# $df is a date named file that also contains label/system information
# This file gets overwritten for each machine archive
LabelName=DATE=$( date '+%Y.%m.%d-%H:%M:%S' )
df=tmp/$LabelName
st=$( date )
fifo=/tmp/backupfifo.$$
trap "rm -f /$df /$df.start /$df.end $fifo" 0
trap 'exit $?' 2 3

CreateLabel > $df

# Have 2 so that on restore we can tell that we have everything.
ln $df $df.start
ln $df.start $df.end

echo "Tape backup ($Type) starting at $( date )"

echo "Rewinding tape at $( date )"
mt -f $tape_ctl rewind || exit 2
echo "Tape rewound, let it settle for 30 seconds at $( date )"
sleep 30

# Write a tape label:
cd /tmp || exit 2
echo ""
echo "Write tape label & pause 5 seconds, starting at $( date )"
tar c${verbose}Mf $tape_nr $LabelName
sleep 5

# Something so that find can talk to tar:
mkfifo $fifo || exit 2

# Backup each machine archive separately:
for machine in $Machines
do	echo ""
	echo "Preparing for write to tape of backup for $machine at $( date )"

	LocalPath=$LocalDir/$machine

	cd $LocalPath || exit 2

	# Work out what LATEST points at:
	[[ ! -L LATEST ]] &&
		echo "There is no symlink LATEST in $LocalPath - abandoning backup for $machine" &&
		continue

	lastestdir=$(find LATEST -printf %l)

	# If we don't know when the last full backup was done -- complain and abandon
	[[ ! -f FullTapeBackup && $incr = y ]] &&
		echo "Incremental backup requested, but have not done a full one yet - abandon backup for $machine" &&
		continue

	[[ $incr = n ]] &&	# Increment option for tar
		Increment= ||
		Increment="-newer ./FullTapeBackup"

	echo "Starting $Type tape write for backup $lastestdir of $machine at $( date )"
	[[ $incr = y ]] &&
		echo "Incremental tape write is for files changed since: $( find ./FullTapeBackup -printf '%Tc' )"

	# Rewrite the tape label with something suitable for this machine's archive
	(	echo "GNU tar achive for machine $machine written at $( date )"
		echo "The files written to tape are from the rsync image in $lastestdir"
		echo "The backup in the rsync image was started at: $( find $lastestdir/$RsyncBackupSuccessFile -printf '%Tc' )"
		[[ $incr = y ]] &&
			echo "Incremental tape write is for files changed since: $( find ./FullTapeBackup -printf '%Tc' )" &&
			echo "(Files where permissions, etc, have changed will not be backed up.)" &&
			echo "This is a full tape write"
		
		# Can we see the backed up machine's Hacks file ?
		if [[ -r $lastestdir/etc/Hacks ]]
		then	echo ""
			echo "Contents of $machine's /etc/Hacks:"
			echo "================================"
			cat $lastestdir/etc/Hacks
			echo "================================"
			echo
		fi
	) > /$df.start

	# Put the label at the start AND end of tape, under different names.
	# Use find 'cos we can exclude old files/...
	find /$df.start $lastestdir /$df.end -depth $Increment > $fifo &

	# Read from the fifo rather than pipe, else tar gets confused when trying to prompt for tape change.
	# Tar --newer-mtime still outputs directories (even unmodified ones).
	tar c${verbose}Mf $tape_nr -T $fifo --no-recursion &&
		TarSuc=y ||
		TarSuc=n
	
	echo "Ended tape write for backup of $machine at $( date )"

	# If a full backup & it worked: note that the time of the backup is the time of the
	# start of the rsync that created the tree that we have just backed up.
	# The time that we write to tape is immaterial, it is the time that we copied from
	# the original machine that is important.
	[[ $incr = n && $TarSuc = y ]] &&
		touch --reference=$lastestdir/$RsyncBackupSuccessFile FullTapeBackup

	sleep 5
done

echo "Rewinding tape at $( date )"
mt -f $tape_ctl rewind || exit 2
echo "Tape rewound, let it settle for 30 seconds at $( date )"
sleep 30

echo "Ejecting tape at $( date )"
mt -f $tape_ctl eject || exit 2

echo "Tape backup completed at $( date ) - started at $st"

exit

# end
