#!/bin/bash . /usr/LH/bin/library.sh || { echo 1>&2 "Can not load common library!" exit 1 } # You need to be root at least via sudo for the backup utilities to work. must_be_root #---------------------------------------------------------------------------- DATABASE="mythconverg" DATABASE_DIR="/data/srv/mysql/$DATABASE" BACKUP_LIST="./root ./home ./etc ./var/lib/alsa/asound.state" RESTORE_LIST="./root ./home ./etc/mythtv/modules ./etc/lirc ./etc/X11/xorg.conf ./etc/asound.conf ./etc/default/aumix ./var/lib/alsa/asound.state ./etc/mplayer/mplayer.conf ./etc/localtime ./etc/timezone ./etc/asound.state" # Both BLACK_LIST and IGNORE_LIST need to have only one file per line # They also can't have any extra whitespace... # Files and directories we should refuse to restore BLACK_LIST='./etc/fstab ./etc/modules ./proc ./dev ./lib ./var/lib ./myth ./myth/backup ./home/mythtv/.my.cnf ./root/.my.cnf ./home/mythtv/.Xauthority ./root/.Xauthority' # Files we ignore as differences IGNORE_LIST='./home/mythtv/.upgrade ./home/mythtv/.configure ./home/mythtv/.newcard ./home/mythtv/.xscreensaver ./home/mythtv/appletrailer.xml ./root/ati-driver-installer-8-01-x86.x86_64.run ./root/ati-driver-installer-8-3-x86.x86_64.run ./root/mythstreamweb.tar ./home/mythtv/.Xauthority ./root/.Xauthority' BACKUP_DIR="/myth/backup" BACKUP_EXTRAS="$BACKUP_DIR/backup.list" RESTORE_EXTRAS="$BACKUP_DIR/restore.list" BACKUP_TAR="$BACKUP_DIR/savedfiles.tar" BACKUP_SQL="$BACKUP_DIR/$DATABASE.sql" #DROP_SQL="/usr/local/share/knoppmyth/drop.sql" UTIL_DIR="/usr/LH/bin" # Do we really still need to update from myth-0.11 to myth-0.12 ? UPDATE_SQL="/usr/share/mythtv/sql/0.11-to-0.12.sql" UPDATE_FILES="$UTIL_DIR/restore_fixups.sh" COMPRESSION=".gz" SOUNDS="/usr/share/sounds" SILENCE="$SOUNDS/half_second_of_silence.wav" PLAYER="/usr/bin/aplay" #---------------------------------------------------------------------------- AWK=/bin/awk BASH=/bin/bash BUNZIP2=/bin/bunzip2 BZIP2=/bin/bzip2 CAT=/bin/cat CHMOD=/bin/chmod CHOWN=/bin/chown EGREP=/bin/egrep GREP=/bin/grep GUNZIP=/bin/gunzip GZIP=/bin/gzip LS=/bin/ls MV=/bin/mv RM=/bin/rm SED=/bin/sed TAR=/bin/tar TR=/bin/tr DIFF=/usr/bin/diff FIND=/usr/bin/find MD5SUM=/usr/bin/md5sum MYISAMCHK=/usr/bin/myisamchk MYSQL=/usr/bin/mysql MYSQLADMIN=/usr/bin/mysqladmin MYSQLDUMP=/usr/bin/mysqldump MYTHSHUTDOWN=/usr/bin/mythshutdown SORT=/usr/bin/sort WC=/usr/bin/wc SV=/sbin/sv #---------------------------------------------------------------------------- require file+r+x $AWK $BASH $BUNZIP2 $BZIP2 $CAT $CHMOD $CHOWN $EGREP $GREP \ $GUNZIP $GZIP $LS $MV $RM $SED $TAR $TR $DIFF $FIND $MD5SUM \ $MYISAMCHK $MYSQL $MYSQLADMIN $MYSQLDUMP $MYTHSHUTDOWN \ $SORT $WC $SV #require file+r "$DROP_SQL" require dir+r+w+x /tmp "$BACKUP_DIR" "$DATABASE_DIR" play_sound () { ($PLAYER $SILENCE $SILENCE $SOUNDS/$1 >& /dev/null)& } # Filter against an exclude list like the black list or the ignore list above filter_list () { $GREP -vxF "$*" | $SORT -u } # Some people just can't read or follow directions... :-/ # This should track the directory names in the default backup list above # We also use this to short circuit a certain incredibly dumb stunt filter_redundant () { filter_list "$($TR -s ' ' '\n' <<<"$BACKUP_LIST ./myth")" | $EGREP -v '^\./(root|home|etc|myth)/' | $SORT -u } get_extras () { # One entry per line, and normalize the prefix $TR ' ' '\n' <"$1" | $AWK '/^$/ {next} /^\.\// {print $0 ; next} /^\// {print "." $0 ; next} { print "./" $0}' } [ -f "$BACKUP_EXTRAS" ] && BACKUP_LIST="$BACKUP_LIST $(get_extras $BACKUP_EXTRAS | filter_redundant)" [ -f "$RESTORE_EXTRAS" ] && RESTORE_LIST="$RESTORE_LIST $(get_extras $RESTORE_EXTRAS | filter_list "$BLACK_LIST")" # Build tar exclusion parameters out of $BLACK_LIST EXCLUSION="" case $0 in *restore) for file in $BLACK_LIST ; do EXCLUSION="$EXCLUSION --exclude $file " done ;; *) ;; esac shrink () { case "$COMPRESSION" in .gz) $GZIP -9 "$@" ;; .bz2) $BZIP2 -9 "$@" ;; *) ;; esac } expand () { case "$*" in *.gz) $GUNZIP "$@" ;; *.bz2) $BUNZIP2 "$@" ;; -c\ *) $CAT $2 /dev/null ;; -t\ *) return 0 ;; *) echo 1>&2 "Error, unknown file type!" return 1 ;; esac } single_format () { candidates=$($LS -1 "$1.gz" "$1.bz2" "$1" 2>/dev/null) case $($WC -l <<<"$candidates") in 1) return 0 # One is good! ;; 0) echo "Error, no $1 found!" return 1 ;; *) echo "Warning, multiple formats for $1 found!" echo "Candidates are: $candidates" ;; esac } compression_type () { for compression in .gz .bz2 "" ; do if [ -f "$1$compression" ] ; then echo "$compression" return 0 fi done return 1 } backup_roller () { # Gets the rollover sequence to use. prev_i=$1 ; shift for i in "$@" ; do for c in .gz .bz2 "" ; do for f in $BACKUP_SQL $BACKUP_TAR ; do $RM -f $f$c$prev_i if [ -f "$f$c$i" ] ; then echo "Moving $f$c$i to $f$c$prev_i" $MV -f $f$c$i $f$c$prev_i fi done done prev_i="$i" done } # Given "subset A B" return true if is A a subset of B subset () { cnt=$($DIFF $1 $2 | $GREP '^<' | $WC -l) [ "$cnt" -eq 0 ] && return 0 $DIFF $1 $2 } mysql_cmd () { $MYSQL -u root $DATABASE -sBe "$*" } mysql_stdin () { $MYSQL -u root $DATABASE -sB } check_files () { OBJECT_LIST="$*" LIVE_FILES=/tmp/live_files_$$ SAVED_FILES=/tmp/saved_files_$$ echo "Checking for the existance of the backup tar file..." single_format "$BACKUP_TAR" c=$(compression_type "$BACKUP_TAR") || { echo "Error, missing tar file - '$BACKUP_TAR$c'." ; return 1 ; } echo "Using file $BACKUP_TAR$c" echo "Backup tar file exists. Checking the compression..." expand -t $BACKUP_TAR$c || { echo "Error, bad compressed tarball - '$BACKUP_TAR$c'." ; return 1 ; } echo "Compression looks OK. Checking backup tar file contents..." echo "Generating a list of the backup contents..." { expand -c $BACKUP_TAR$c | $TAR tf - $OBJECT_LIST $EXCLUSION | $SED '/\/$/s///' | filter_list "$IGNORE_LIST" >$SAVED_FILES } 2>&1 | $SED -e '/Error exit delayed from previous errors/d' echo "Generating a list of the directory contents..." cd / $FIND $OBJECT_LIST \( -type d -or -type f -or -type l \) -print | filter_list "$IGNORE_LIST" >$LIVE_FILES echo "Comparing directory versus backup contents..." case $0 in *backup) # backup must contain everything selected from the directories subset $LIVE_FILES $SAVED_FILES ;; *restore) # directories must contain everything selected from the backup subset $SAVED_FILES $LIVE_FILES ;; *) $DIFF $LIVE_FILES $SAVED_FILES ;; esac FILE_STATUS=$? $RM $LIVE_FILES $SAVED_FILES if [ $FILE_STATUS -eq 0 ] ; then echo "Live and saved file lists match." else echo "Warning, file lists are not identical!" fi return $FILE_STATUS } has_records () { filename="$1" description="$2" if [ $($WC -l < "$filename") -eq 0 ] ; then echo "Warning, could not get record counts from $description!" return 1 fi if [ $($AWK '{cnt+=$2} END {print cnt}' < "$filename") -eq 0 ] ; then echo "Warning, total record count from $description is zero!" return 1 fi return 0 } check_tables () { LIVE_TABLES=/tmp/live_tables_$$ SAVED_TABLES=/tmp/saved_tables_$$ echo "Checking for the existance of the DB dump file..." single_format "$BACKUP_SQL" c=$(compression_type "$BACKUP_SQL") || { echo "Error, missing DB dump - '$BACKUP_SQL$c'" ; return 1 ; } echo "Using file $BACKUP_SQL$c" echo "DB dump file exists. Checking the compression..." expand -t $BACKUP_SQL$c || { echo "Error, bad compressed DB dump - '$BACKUP_SQL$c'." ; return 1 ; } echo "Compression looks OK. Checking DB dump contents..." echo "Generating a list of tables and record counts in the DB dump..." expand -c "$BACKUP_SQL$c" | $AWK '/CREATE TABLE/ { tbl = $3; gsub("`","",tbl); records[tbl] = 0; } \ /INSERT INTO/ { tbl = $3; gsub("`","",tbl); \ n = split(substr($0,index($0,"VALUES (")+7),vals,"\\),\\("); \ records[tbl] += n; } \ END { for (tbl in records) print tbl, records[tbl]; }' | $SED 's/mythlog [0-9]*/mythlog 0/' | $SORT >$SAVED_TABLES has_records "$SAVED_TABLES" "DB dump" || return 1 echo "Generating a list of tables and record counts in the live DB..." for tbl in $(mysql_cmd "show tables") ; do mysql_cmd "select '$tbl', count(*) from $tbl" done | $TR -s '\t' ' ' | $SED 's/mythlog [0-9]*/mythlog 0/' | $SORT >$LIVE_TABLES has_records "$LIVE_TABLES" "live DB" || return 1 echo "Comparing live versus saved tables..." case $0 in *restore) # database must include everything from the backup subset $SAVED_TABLES $LIVE_TABLES ;; *) # backup must exactly match the database $DIFF $LIVE_TABLES $SAVED_TABLES ;; esac TABLE_STATUS=$? $RM $LIVE_TABLES $SAVED_TABLES if [ $TABLE_STATUS -eq 0 ] ; then echo "Live and saved table lists match." else echo "Warning, table lists are not identical!" fi return $TABLE_STATUS } check_files_and_tables () { STATUS=0 echo check_files "$@" || STATUS=1 echo check_tables || STATUS=1 echo return $STATUS } stop_mysqld () { $SV stop mysql ; } start_mysqld () { $SV start mysql for t in 1 2 4 8 ; do ready=$(mysql_cmd 'select 1 from dual' 2>/dev/null) [ "$ready" = "1" ] && break sleep "$t" done [ "$ready" != "1" ] && echo "Error, DB not available after 15 seconds!" } stop_mythbackend () { $SV stop mythbackend ; } start_mythbackend () { $SV start mythbackend ; } lock_myth () { $MYTHSHUTDOWN --lock ; } unlock_myth () { $MYTHSHUTDOWN --unlock ; } true # Make sure that this shows success