Backup Tapes

Giorgos Keramidas keramida at ceid.upatras.gr
Tue Jun 19 14:31:42 EEST 2007


Έβαλα αριθμούς στις γραμμές παρακάτω, για να είναι πιο εύκολο να
αναφερθεί κανείς σε πολλές μαζί.

     1  #!/bin/sh
     2
     3  #######################################################################
     4  #
     5  # Backup Script. If used without any argument a full
     6  # or incremental backup will be done depending on the day
     7  # ( 1st full, others incremental). If used with an argument
     8  # ( full or incr ) you can force type of backup.
     9  #
    10  #######################################################################

Τα 'όμορφα blocks' στα σχόλια είναι πολύ 'κουραστικά για το μάτι'
μερικές φορές.  Συνήθως αρκεί κάτι σαν:

    #!/bin/sh
    #
    # Backup script for host `jenna'.
    # Copyright (C) 2007 Yiannis Yiakoumis.

Αν θέλεις να φαίνεται και 'usage' στο πάνω μέρος του script, καλό είναι
να ακολουθεί ένα στυλ περίπου σαν αυτό που έχουν τα συνηθισμένα UNIX
manpages:

    #!/bin/sh
    #
    # Backup script for host `jenna'.
    # Copyright (C) 2007 Yiannis Yiakoumis.
    #
    # SYNOPSIS
    #    backup.sh [ mode ]
    #
    # DESCRIPTION
    #    This script can be used to save backup copies of important files,
    #    like MySQL databases, web server log files, etc.
    #
    #    When run without options, the script will take a full backup of all
    #    the files considered ``important'' if the current day is the first
    #    day of the month.  Otherwise an incremental backup will be saved.
    #
    #    When run with a ``mode'' argument, the values accepted for ``mode''
    #    are the following:
    #
    #    full         Full backup of all files.
    #
    #    incr         Incremental backup of only the files modified since
    #                 the last time we got a full backup.

Μετά το script έχει:

    12  HOME_BACKUP=/home
    13  ETC_BACKUP=/etc
    14  LOGS_BACKUP=/var/log
    15  BACKUP_DIR=/root/backup
    16  TMP=$BACKUP_DIR/tmp
    17
    18  mkdir $TMP

Οταν χρησιμοποιείς shell variables σε expanded μορφή (όπως το
$BACKUP_DIR παραπάνω), είναι καλή ιδέα να γράφεις κάτι σαν:

    TMP="${BACKUP_DIR}/tmp"

έτσι είναι πιο "ασφαλές" ότι δε θα γίνει expand στο λάθος μέρος του
string.

Το $TMP παραείναι γενικό σαν όνομα :-(

Τα κεφαλαία είναι λίγο σαν να φωνάζεις μόνιμα.

Η λίστα με τα backup directories είναι επίσης λίγο «άσχημο» που είναι
ένα μάτσο shell μεταβλητές.  Θα μπορούσε να είναι μόνο μία:

    # List of directories which we save in backup tarballs.
    FLIST='/home /etc /var/log'

    20  DATE=`date +%Y_%m_%d`

Τα backquotes είναι για να θέσει το shell σου ως τιμή της $DATE την
έξοδο της εντολής, αλλά τα arguments της 'date' command είναι εντελώς
unquoted και μπορεί να γίνουν expand με «μη αναμενόμενο» τρόπο.  Επίσης
το $DATE που έχεις εδώ είναι μια χαρά ως «filename», αλλά τα underscores
είναι ελαφρώς κιτς ως διαχωριστικά και δε μπορείς να πάρεις περισσότερα
από ένα backup images σε μια μέρα.  Εγώ θα έβαζα τουλάχιστον και την
ώρα, και τελείες ως separators, με λίγο διαφορετικό όνομα στη μεταβλητή:

    STAMP=`date '+%Y.%m.%d.%H'`

    22  echo "DAILY BACKUP STARTS @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ " > $BACKUP_DIR/backup-log.txt
    23  echo $DATE >> backup-log.txt

Αντί να θυμάσαι κάθε φορά πως λεγόταν το log file θα μπορούσες να το
σώσεις σε μια μεταβλητή, π.χ. με όνομα LOGFILE.  Ακόμα καλύτερα, μπορείς
να φτιάξεις μια shell function με όνομα log() που γράφει στο LOGFILE,
έτσι που η «γνώση» του pathname για το log file να είναι σε ένα και
μοναδικό σημείο.  Αν αργότερα αλλάξεις γνώμη για το πώς θα λέγεται το
log file, δε θα χρειάζεται να αλλάξεις 100 σημεία αλλά ένα και μόνο.

Με τη χρήση μιας log() function μπορείς να κάνεις και άλλα «όμορφα»
πράγματα, όπως π.χ. time-stamped log entries, κάπως έτσι:

    log()
    {
        _now="`date '+%Y-%m-%d %H:%M:%S'` "
        if test -n "${LOGFILE}" ; then
            echo "${_now}$*" >> "${LOGFILE}"
        else
            # No log file; send log output to stderr.
            echo "${_now}$*" >&2
        fi

    }

Τότε θα μπορείς να γράψεις πράγματα όπως:

    # Setup of backup environment.
    BDIR='/root/backup'
    LOGFILE="$BDIR/backup.log"

    log "Backup at ${BDIR} starting."
    runbackup
    log "Backup at ${BDIR} finished."

Με μια σχετικά πιο 'modular' συνάρτηση με όνομα runbackup(), το παραπάνω
μπορεί να είναι ΟΛΟΣ ο κύριος κορμός του backpu script!  Δεν είναι πολύ
πιο ευανάγνωστο έτσι;

    25  # If called with argument force backup type, otherwise check date to decide
    26
    27  if [ "$1" ]; then
    28          if [ $1 = full ]; then
    29                  echo "Forcing Full Backup" >> $BACKUP_DIR/backup-log.txt
    30                  BACKUP_TYPE=FULL
    31          elif [ $1 = incr ]; then
    32                  echo "Forcing Incremental Backup" >> $BACKUP_DIR/backup-log.txt
    33                  BACKUP_TYPE=INCR
    34          fi
    35  else
    36          echo "Checking Backup Type..." >> $BACKUP_DIR/backup-log.txt
    37          if [ `date +%d` = 01 ]; then
    38                  echo "Going for a Full Backup" >> $BACKUP_DIR/backup-log.txt
    39                  BACKUP_TYPE=FULL
    40          else
    41                  echo "Going for an Incremental Backup" >> $BACKUP_DIR/backup-log.txt
    42                  BACKUP_TYPE=INCR
    43
    44          fi
    45  fi

Το παραπάνω είναι λίγο «μπερδεμένο», αλλά ας δούμε πώς μπορεί να
γίνει καλύτερο.  Δε μου αρέσει τόοοσο πολύ το 'text based option'
που μπορεί να είναι 'incr' ή 'full'.  Προτιμώ κάτι σαν τα -i και
-f options:

    # Run in quiet, non-verbose mode by default.
    VERBOSE='no'

    # Full backup mode is off by default, unless today is the
    # first day of the month.
    FULLBACKUP='no'
    if test "`date '+%d'`" = "01" ; then
        FULLBACKUP='yes'
    fi

    # Allow command-line options to override the defaults.
    set -- `getopt fiv $*`
    while [ $# -gt 0 ]; do
        case $1 in
        -i)
            FULLBACKUP='no'
            ;;
        -f)
            FULLBACKUP='yes'
            ;;
        -v)
            VERBOSE='yes'
            ;;
        --)
            shift ; break ;;
        esac
        shift
    done

Έτσι θα μπορεί κανείς να τρέξει το backup.sh script σου με:

    /root/backup.sh -f
    /root/backup.sh -i
    /root/backup.sh

Στην πρώτη περίπτωση θα κάνει full backup, στην άλλη όχι.  Στην
τρίτη περίπτωση θα πάρει incremental backup, εκτός κι αν είναι η
πρώτη μέρα του μήνα (οπότε θα πάρει full backup).

    48  # MySQL Databases & Logs( no matter if full or incr )
    49  echo "->Backing up MySQL Databases..." >> $BACKUP_DIR/backup-log.txt
    50  mysqldump website -u web --password=xxxx > $TMP/db_dump.sql
    51  tar zcfp $TMP/logs_backup.tgz $LOGS_BACKUP

``WTF, why?''

Αν είναι να πάρεις ``incremental'' backups ή πάρε incremental
backups ή μην δίνεις αυτό το option στον χρήστη, που δε φταίει σε
τίποτα ο καημένος να νομίζει ότι θα πάρει incremental backups με
μέγεθος 2 MB, για να ανακαλύψει ότι τα 8192 MB που έχει ελεύθερα
ο δίσκος γέμισαν με full backup της MySQL!!!

Κι εδώ το `echo' αντί για `log' είναι λίγο ενοχλητικό.

Τα quotes λείπουν, και δεν ελέγχονται τα return values από τις
εντολές backup.

Καλύτερα γράψε κάτι σαν:

    backup_mysql()
    {
        # Space-separated list of databases to backup.
        dblist="website"

        if test -z "${BDIR}" ; then
            log "Backup dir \$BDIR is unset or empty."
            return 1
        fi

        log "Saving MySQL dumps"
        cd "${BDIR}"
        if test ! -d mysql ; then
            mkdir mysql
            if test $? -ne 0 ; then
                log "${BDIR}/mysql: cannot create directory."
                return 1
            fi
        fi

        cd mysql
        for db in ${dblist} ; do
            dbtmp="${db}.dump.$$"
            mysqldump "${db}" -u web --password=XXXX > "${dbtmp}" && \
            mv "${dbtmp}" "${db}"
            if test $? -ne 0 ; then
                log "mysqldump: cannot dump database ${db}"
                test -f "${dbtmp}" && rm -f "${dbtmp}"
            fi
        done

        return 0
    }

Τώρα μπορείς σε οποιοδήποτε μέρος του script να γράψεις:

    backup_mysql

και να πάρεις backups από όλες τις databases που έχεις ορίσει στη
λίστα $dblist στην αρχή της backup_mysql() function.

Αντίστοιχους ελέγχους χρειάζεται και σε άλλα μέρη του script, όπως:

    59  # Creating tar files and metadata files for following incrementals
    60          tar zcfp $TMP/home_backup.tgz $HOME_BACKUP --listed-incremental=$BACKUP_DIR/last-home-snapshot >> $BACKUP_DIR/backup-log.txt
    61          tar zcfp $TMP/etc_backup.tgz $ETC_BACKUP --listed-incremental=$BACKUP_DIR/last-etc-snapshot >> $BACKUP_DIR/backup-log.txt

Τί γίνεται αν αποτύχει κάποιο tar εδώ;

    65          cp $BACKUP_DIR/last-home-snapshot $TMP/home-log
    66          cp $BACKUP_DIR/last-etc-snapshot $TMP/etc-log
    67          tar zcfp $TMP/home_backup.tgz $HOME_BACKUP --listed-incremental=$TMP/home-log >> $BACKUP_DIR/backup-log.txt
    68          tar zcfp $TMP/etc_backup.tgz $ETC_BACKUP --listed-incremental=$TMP/etc-log >> $BACKUP_DIR/backup-log.txt

κι εδώ;

    71  cd $TMP
    72  tar zcvf backup_webserver$DATE$BACKUP_TYPE.tar.gz home_backup.tgz etc_backup.tgz logs_backup.tgz db_dump.sql >> $BACKUP_DIR/backup-log.txt

κι εδώ;

Σε όλα αυτά τα σημεία χρειάζονται έξτρα έλεγχοι μετά από ΚΑΘΕ ΜΙΑ
εντολή ξεχωριστά, γιατί αν αποτύχει μία από αυτές, θα πάρει
σβάρνα όλο το υπόλοιπο script.

Όλα τα παραπάνω είναι, βέβαια, καθαρά προσωπική μου γνώμη και δεν
είσαι υποχρεωμένος να ακολουθήσεις το One True Keramidas
Shell-script Style(TM).




More information about the Linux-greek-users mailing list