2023/04/26

Linuxのバックアップスクリプト

自宅サーバを運用し始めてから使っていたバックアップスクリプトは、仮想テープ3本(+フルバックアップ用に1本)、それぞれのテープに10世代の差分バックアップを記録するという固定設定だったので、改造することにした。

今のサーバで動いているスクリプトのタイムスタンプを見たら「2012/12/15 12:04:01」となっていたので、かれこれ10年以上使っている。スクリプト内に、バックアップ対象・バックアップ先の設定を直接記入しているので、プログラム本体自体はそれ以上前にできたと思われる。

このブログのGentooカテゴリの最初の記事が2007年7月5日なので、それ以降ではあるものの、10数年の実績のあるスクリプトだった。なので、スクリプトの根幹部分は変えることなく、周辺部分のみを変更することにした。


根幹部分は、修正ハノイの塔アルゴリズムを利用しているが、どこからこのアイデアを持ってきたのかはよくわからない。

今回の改修では、仮想テープの数と、各テープに保存する差分バックアップの数を設定で変更できるようにした。それに伴い、旧版ではバックアップファイルの名称に含まれているテープ名称に上限があり(アルファベットだった)、差分バックアップの番号が10以上の数字になると桁数がばらばらになってしまうため、桁数を動的に変更して揃えられるようにした。

また、旧版では、根幹部分にもほとんどコメントが入っていなかったのでコメントを追記した。

主に設定が必要なのは、

  • バックアップを取りたいディレクトリのパス(絶対パス)
  • バックアップ先(絶対パス)
  • 仮想テープの数(別に仮想テープがあるわけではないが、データの管理上、テープという概念を使っている)
  • 各テープに保存する差分バックアップの数(例では9としているが、0から始まるので実際には10になる。)

くらい。

修正ハノイの塔アルゴリズムについてはスクリプトの途中に図示しているが、差分バックアップ取得の際の差分元の決め方が特徴的。それにより、リストア時に必要な戻し作業が半分で済む。

リストアの手順

  1. フルバックアップである「Tape-Z.0」(Zや0の桁数は様々)を戻す
  2. 最終的に戻したいテープの「.1」のバックアップを戻す。
    注:TAPE-00については「.1」が存在しない場合がある(= フルバックアップの次の差分バックアップ)が、その場合は「.1」は戻す必要はない。
  3. 以下の要領で戻したいところまでの偶数番号のみを小さい順に戻していく
  • 戻す先の差分の番号が偶数の場合は、そのまま偶数だけ戻せば良い(例:戻す先が8の場合は、2、4、6、8を戻す)
  • 奇数の場合は、戻す先の一つ手前の偶数はスキップする(例:戻す先が9の場合は、2、4、6、9を戻す。8は戻さない)


なお、バックアップを戻す際のコマンドは、以下である。

tar xjv -g /dev/null -f バックアップファイル名

-g /dev/null」を忘れないように注意が必要である。tarコマンドの差分バックアップ機能を使ってバックアップを取っているため、戻しの際にも差分バックアップであったことを示すために-gオプションを指定するが、-gオプションは引数を渡すことが必須のため、とりあえず/dev/nullを指定している。なお、「-g /dev/null」を指定している場合、差分バックアップを取った時点で削除されていたファイルは削除してくれる。(逆に指定していない場合は、オリジナルのファイルが削除されていても、リストアした先では残っていることになる。)


# cat daily_backup.sh
#!/bin/bash

####### How to restore ######
# Command:
#   tar xjv -g /dev/null -f backup-file.tbz
# Steps:
#
# 1. Restore Full-backup = backup-level 0 (=TAPE-ZZ.00)
# 2. Restore backup-level 1 (=TAPE-xx.01) of the desired tape where xx is number, if exist
# 3a. If the desired restoration point is at "even number" of backup-level,
#     restore backup data with "even number" of backup-level up to the desired level
#     e.g. if the desired restoration point is backup-level 8, restore 2, 4, 6, 8.
# 3b. If the desired restoration point is at "odd number" of backup-level,
#     restore backup data with "even number" of backup-level up to less than n-1,
#     and then restore the desired level data.
#      e.g. if the desired restoration point is backup-level 7, restore 2, 4,
#           then, 7 (you need skip level 6)
#
#
#                 <-- desired restore level -->
# BU.Lv  Base.L   0  1  2  3  4  5  6  7  8  9
#   0      0      o  o  o  o  o  o  o  o  o  o
#   1      1         o  o  o  o  o  o  o  o  o  (if exist)
#   2     1/0           o     o  o  o  o  o  o
#   3     1/0              o
#   4      2                  o     o  o  o  o
#   5      2                     o
#   6      4                        o     o  o
#   7      4                           o
#   8      6                              o
#   9      6                                 o
#
# "o" means restoration of the backup data is required.
# BU.Lv:  backup-level
# Base.L: base list used for the backup-level
#
# Note:
# If you forgot specifying "-g /dev/null" option, deleted files will not be deleted
# during restoration process.

####### Configuration - begin ######
TARGET_DIR=(/etc /home /usr/local /root /var/lib /data/www /data/samba)
BACKUP_BASE_DIR='/data2/OneDrive/Home_Server/edo/backup/edo'
LIST_FILE_DIR="${BACKUP_BASE_DIR}/list_files"
LOG_FILE_DIR="${BACKUP_BASE_DIR}/logs"
BACKUP_DATA_DIR="${BACKUP_BASE_DIR}/data"

SED='/bin/sed'
MKDIR='/bin/mkdir'
DATE='/bin/date'
CP='/bin/cp'
RM='/bin/rm'
LS='/bin/ls -Art'
TAR='/bin/tar'

# Number of backup tapes. Minimum value is 1 (Level-0 backup tape and 1x differential backup tape).
AMOUNT_OF_TAPE=3

# maximum level of backup level.
# minimum value is 3. (level-0 and 3x differential backup)
MAX_BACKUP_LEVEL=9

####### Configuration - end ######



###### Next tape - begin ######
# backup tape sequence (when AMOUNT_OF_TAPE=3)
# first tape = 0
# 0 means using TAPE-A(see below)
# 1 means using TAPE-B
# 2 means using TAPE-C
# ex.
# use TAPE-B followed by using TAPE-A: (1 x x)
# use TAPE-C followed by using TAPE-B: (x 2 x)
# use TAPE-A followed by using TAPE-C: (x x 0)
# This will be defined as;
# NEXT_TAPE=(1 2 0)

AMOUNT_OF_TAPE=$(echo ${AMOUNT_OF_TAPE} | ${SED} 's/^0*\(.*\)\(.\)/\1\2/')

LEVEL_0_TAPE=`printf %${#AMOUNT_OF_TAPE}s | sed "s/ /Z/g"`

if [[ ${AMOUNT_OF_TAPE} -le 0 ]]; then
  AMOUNT_OF_TAPE=1
fi
NUM_OF_DIGITS_OF_TAPE=${#AMOUNT_OF_TAPE}

NEXT_TAPE=()

i=0
while [[ ${i} -lt $((AMOUNT_OF_TAPE - 1)) ]];
do
  #NEXT_TAPE+=(`printf "%0${NUM_OF_DIGITS_OF_TAPE}d" $((++i))`)
  NEXT_TAPE+=( $((++i)) )
  i=$((++i))
done
#NEXT_TAPE+=(`printf "%0${NUM_OF_DIGITS_OF_TAPE}d" 0`)
NEXT_TAPE+=( 0 )

#echo ${NEXT_TAPE[@]}

###### Next tape - end ######


###### Next backup level - end ######

# Tower of HANOI sequence (when AMOUNT_OF_TAPE=3, MAX_BACKUP_LEVEL=9)
# TAPE-0 0
# TAPE-A   -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-B 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-C 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-A 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-B 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-C 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->

# next hanoi-num
# ex. next hanoi-num of 3 is 2
#     (x x x 2 x x x x x x)
#NEXT_HANOI=(3 3 5 2 7 4 9 6 1 8)

# minimum value is 3. (level-0 and 3x differential backup)
if [[ ${MAX_BACKUP_LEVEL} -lt 3 ]] ; then
  MAX_BACKUP_LEVEL=3
fi

function getNextHanoi {
  ### how to use
  # RETURN_VALUE=`getNextHanoi $LAST_BACKUP_LEVEL`
  # Then, use $RETURN_VALUE for following steps (because bash function cannot return value except exit statue (0-255))

  LAST_BACKUP_LEVEL=$1
  # MAX_BACKUP_LEVEL : global variable

  #declare -i NEXT_BACKUP_LEVEL
  NEXT_BACKUP_LEVEL=0
  if [[ ${LAST_BACKUP_LEVEL} -lt 0 ]] ; then
    # LAST_BACKUP_LEVEL (=previous backup level) cannot be negative value, but just in case.
    # If previous backup level was minus value, chose 0 as next backup level.
    NEXT_BACKUP_LEVEL=0
  elif [[ ${LAST_BACKUP_LEVEL} -eq 0 || ${LAST_BACKUP_LEVEL} -eq 1 ]] ; then
    # next backup level of level-0 and level-1 is level-3.
    NEXT_BACKUP_LEVEL=3
  elif [[ $((LAST_BACKUP_LEVEL % 2 )) -eq 0 ]] ; then
    # if LAST_BACKUP_LEVEL is even number, next backup level is previous level +3.
    NEXT_BACKUP_LEVEL=$(( LAST_BACKUP_LEVEL + 3 ))
  elif [[ $((LAST_BACKUP_LEVEL % 2 )) -eq 1 ]] ; then
    # if LAST_BACKUP_LEVEL is odd number, next backup level is previous level -1.
    NEXT_BACKUP_LEVEL=$(( LAST_BACKUP_LEVEL - 1 ))
  fi

  if [[ $((MAX_BACKUP_LEVEL % 2 )) -eq 1 ]] ; then
    if [[ ${LAST_BACKUP_LEVEL} -eq $(( MAX_BACKUP_LEVEL - 1 )) ]] ; then
      # if MAX_BACKUP_LEVEL is an odd number and LAST_BACKUP_LEVEL reached MAX_BACKUP_LEVEL -1,
      # the next backup level will be 1 with next tape.
      # Assuming MAX_BACKUP_LEVEL=9, the next of level-9 is level-8, and the next of level-8
      # supposed to be level-11 which is over the MAX_BACKUP_LEVEL(=9).
      # Therefore, it is changed to level-1 of the next tape.
      NEXT_BACKUP_LEVEL=1
    fi
  else
    # when MAX_BACKUP_LEVEL is an even number, the next level of "MAX_BACKUP_LEVEL - 2" supposed to be
    # MAX_BACKUP_LEVEL+3 which is over the limit.
    if [[ ${LAST_BACKUP_LEVEL} -eq $((MAX_BACKUP_LEVEL - 2)) ]] ; then
      # if LAST_BACKUP_LEVEL is MAX_BACKUP_LEVEL-2, the next backup level will be MAX_BACKUP_LEVEL.
      NEXT_BACKUP_LEVEL=${MAX_BACKUP_LEVEL}
    fi
    if [[ ${LAST_BACKUP_LEVEL} -eq ${MAX_BACKUP_LEVEL} ]] ; then
      # if LAST_BACKUP_LEVEL reached MAX_BACKUP_LEVEL, the next backup level will be 1 with next tape.
      NEXT_BACKUP_LEVEL=1
    fi
  fi

  echo ${NEXT_BACKUP_LEVEL}
}


NEXT_HANOI=()

i=0
while [[ ${i} -le ${MAX_BACKUP_LEVEL} ]] ;
do
  NEXT_HANOI+=(`getNextHanoi ${i}`)
  i=$((++i))
done

# echo ${NEXT_HANOI[@]}

###### Next backup level - end ######


###### Tar list file to be used - begin ######

# which list-file should be used
# level-0 = new tape
# level-1 = use level-0 list
# level-2 = use level-1 list (or level-0 list)
# level-3 = use level-1 list (or level-0 list)
# level-4 = use level-2 list
# level-5 = use level-2 list
# level-6 = use level-4 list
# level-7 = use level-4 list
# level-8 = use level-6 list
# level-9 = use level-6 list

#BASE_LIST_BACKUP_LEVEL_LABEL=(0 0 1 1 2 2 4 4 6 6)


function get_BASE_LIST_BACKUP_LEVEL {
  NEXT_BACKUP_LEVEL=$1

  declare -i BASE_LIST_BACKUP_LEVEL
  if [[ ${NEXT_BACKUP_LEVEL} -eq 0 || ${NEXT_BACKUP_LEVEL} -eq 1 ]] ; then
    BASE_LIST_BACKUP_LEVEL=0
  elif [[ ${NEXT_BACKUP_LEVEL} -eq 2 || ${NEXT_BACKUP_LEVEL} -eq 3 ]] ; then
    BASE_LIST_BACKUP_LEVEL=1
  elif [[ $(( NEXT_BACKUP_LEVEL % 2 )) -eq 0 ]] ; then
    BASE_LIST_BACKUP_LEVEL=$(( NEXT_BACKUP_LEVEL - 2 ))
  elif [[ $(( NEXT_BACKUP_LEVEL % 2 )) -eq 1 ]] ; then
    BASE_LIST_BACKUP_LEVEL=$(( NEXT_BACKUP_LEVEL - 3 ))
  else
    # will never come here
    BASE_LIST_BACKUP_LEVEL=""
  fi

  echo ${BASE_LIST_BACKUP_LEVEL}
}


BASE_LIST_BACKUP_LEVEL_LABEL=()

i=0
while [[ ${i} -le ${MAX_BACKUP_LEVEL} ]] ;
do
  BASE_LIST_BACKUP_LEVEL=`get_BASE_LIST_BACKUP_LEVEL ${i}`
  BASE_LIST_BACKUP_LEVEL_LABEL+=(`printf "%0${#AMOUNT_OF_TAPE}d" ${BASE_LIST_BACKUP_LEVEL}`)
  i=$((++i))
done

# echo ${BASE_LIST_BACKUP_LEVEL_LABEL[@]}

###### Tar list file to be used - end ######

###### function to create backup tape label - begin ######
function getTapeLabel {
  BACKUP_TAPE=$1
  declare TAPE_LABEL
  if [[ "${BACKUP_TAPE}" == "${LEVEL_0_TAPE}" ]] ; then
    TAPE_LABEL="TAPE-${LEVEL_0_TAPE}"
  else
    TAPE_LABEL="TAPE-`printf %0${#AMOUNT_OF_TAPE}d ${BACKUP_TAPE}`"
  fi
  echo ${TAPE_LABEL}
}
###### function to create backup tape label - end ######


###### (for debug) check created arrays - begin #######
DEBUG=0

if [[ ${DEBUG} -eq 1 ]] ; then
  echo TARGET_DIR=${TARGET_DIR}
  echo BACKUP_BASE_DIR=${BACKUP_BASE_DIR}
  echo LIST_FILE_DIR=${LIST_FILE_DIR}
  echo LOG_FILE_DIR=${LOG_FILE_DIR}
  echo BACKUP_DATA_DIR=${BACKUP_DATA_DIR}
  echo AMOUNT_OF_TAPE=${AMOUNT_OF_TAPE}
  echo MAX_BACKUP_LEVEL=${MAX_BACKUP_LEVEL}
  echo LEVEL_0_TAPE=${LEVEL_0_TAPE}
  echo
  echo NEXT_TAPE
  echo ${NEXT_TAPE[@]}
  echo
  echo NEXT_HANOI
  echo ${NEXT_HANOI[@]}
  echo
  echo BASE_LIST_BACKUP_LEVEL_LABEL
  echo ${BASE_LIST_BACKUP_LEVEL_LABEL[@]}
  echo
  echo TAPE_LABEL
  for (( i=0 ; i < ${AMOUNT_OF_TAPE} ; i++ )) ; do
    echo -n `getTapeLabel $i` " "
  done
  echo `getTapeLabel ${LEVEL_0_TAPE}`
  exit
fi
###### (for debug) check created arrays - end #######


###### function to delete backup data stored on the specified tape - begin ######
function clear_old_backup {
  BACKUP_TAPE=$1
  BACKUP_FILE_BASE=$2
  TAPE_LABEL=`getTapeLabel ${BACKUP_TAPE}`
  for clear_target_dir in ${LIST_FILE_DIR} ${BACKUP_DATA_DIR} ; do
    echo `${DATE} +%Y/%m/%d-%H:%M:%S` "clearing $clear_target_dir for ${BACKUP_FILE_BASE}.${TAPE_LABEL}" >> ${LOG_FILE}
    for rm_target in `${LS} ${clear_target_dir}/${BACKUP_FILE_BASE}.${TAPE_LABEL}.* 2> /dev/null` ; do
      CMD="${RM} -f ${rm_target}"
      echo `${DATE} +%Y/%m/%d-%H:%M:%S` "${CMD}" >> ${LOG_FILE}
      ${CMD}
    done
  done
  return 1
}

###### function to delete backup data stored on the specified tape - end ######

###### create required directories - begin ######

if [[ ! -d ${LIST_FILE_DIR} ]] ; then
  ${MKDIR} -p ${LIST_FILE_DIR}
fi

if [[ ! -d ${LOG_FILE_DIR} ]] ; then
  ${MKDIR} -p ${LOG_FILE_DIR}
fi

if [[ ! -d ${BAKUP_DATA_DIR} ]] ; then
  ${MKDIR} -p ${BACKUP_DATA_DIR}
fi

###### create required directories - end ######


###### begin main logic ######

for target in ${TARGET_DIR[@]} ; do
  # define the first part of back up filename. e.g. backup filename of directory /data/www will be data-www
  BACKUP_FILE_BASE=`echo "${target}" | ${SED} -e "s/^\///g" -e "s/-/--/g" -e "s/\//-/g"`

  LOG_FILE="${LOG_FILE_DIR}/${BACKUP_FILE_BASE}.log"

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "#########backup script is started##########"  >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "log_file is '${LOG_FILE}'" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup target is '${target}'" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_file_base name is '${BACKUP_FILE_BASE}'" >> ${LOG_FILE}

  STATE_FILE="${LOG_FILE_DIR}/${BACKUP_FILE_BASE}.state"
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "state_file is '${STATE_FILE}'" >> ${LOG_FILE}

  # read state file
  if [[ -e ${STATE_FILE} ]] ; then
    source ${STATE_FILE}
  fi

  # echo state file data
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "last_backup_level = ${LAST_BACKUP_LEVEL}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "last_backup_tape = ${LAST_BACKUP_TAPE}" >> ${LOG_FILE}

  LAST_BACKUP_LEVEL=`echo ${LAST_BACKUP_LEVEL} | ${SED} -e 's/^0*\(.*\)\(.\)/\1\2/'`
  LAST_BACKUP_TAPE=`echo ${LAST_BACKUP_TAPE} | ${SED} -e 's/^0*\(.*\)\(.\)/\1\2/'`

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "last_backup_level(removed leading 0) = ${LAST_BACKUP_LEVEL}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "last_backup_tape(removed leading 0) = ${LAST_BACKUP_TAPE}" >> ${LOG_FILE}

  # find next backup level
  if [[ -z ${LAST_BACKUP_LEVEL} ]] ; then
    # if last backup level is not identified, use 0 which means full backup onto level-0-tape.
    BACKUP_LEVEL=0
  else
    BACKUP_LEVEL=${NEXT_HANOI[${LAST_BACKUP_LEVEL}]}
  fi
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_level = ${BACKUP_LEVEL}" >> ${LOG_FILE}

  # find next backup tape
  if [[ -z ${LAST_BACKUP_TAPE} ]] ; then
    # if last backup tape is not identified, chose the last tape (= tape# 0)
    BACKUP_TAPE=${NEXT_TAPE[((${#NEXT_TAPE[@]}-1))]}
    # This is the first backup of the tape, the tape must be formatted = delete all previous backup data.
    clear_old_backup ${BACKUP_TAPE} ${BACKUP_FILE_BASE}
  else
    if [[ ${LAST_BACKUP_LEVEL} -eq 0 ]] ; then
      # Previous backup tape is level-0-tape (because backup level = 0 means full-backup,
      # and it is made onto level-0-tape only.).
      # This is 1st differential backup and 1st tape (= tape# 1) is chosen, and the tape need be formatted.
      BACKUP_TAPE=${NEXT_TAPE[((${#NEXT_TAPE[@]}-1))]}
      clear_old_backup ${BACKUP_TAPE} ${BACKUP_FILE_BASE}
    elif [[ ${BACKUP_LEVEL} -ne 1 ]] ; then
      # next backup is NOT level-1 backup.
      # Unless level-1 backup, they are following backups stored onto the same tape as previous backup.
      BACKUP_TAPE=${LAST_BACKUP_TAPE}
    else
      # previous backup is not level-0 backup (= full backup onto level-0-tape) and,
      # current backup is level-1 backup (= previous backup should be level-8 backup
      # because following backup of level-8 is level-1),
      #
      # Level-1 backup is the 1st backup on the selected tape. Need format.
      BACKUP_TAPE=${NEXT_TAPE[`echo ${LAST_BACKUP_TAPE} | ${SED} -e 's/^0*\(.*\)\(.\)/\1\2/'`]}
      clear_old_backup ${BACKUP_TAPE} ${BACKUP_FILE_BASE}
    fi
  fi

  if [[ ${BACKUP_LEVEL} -eq 0 ]] ; then
    # if current backup level is 0, it means full backup will be captured onto level-0-tape.
    BACKUP_TAPE=${LEVEL_0_TAPE}
  fi

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_tape = ${BACKUP_TAPE}" >> ${LOG_FILE}

  TAPE_LABEL=`getTapeLabel ${BACKUP_TAPE}`

  # base list file is previous list file (comes from previous backup process).
  BASE_LIST_FILE="${LIST_FILE_DIR}/${BACKUP_FILE_BASE}.${TAPE_LABEL}.${BASE_LIST_BACKUP_LEVEL_LABEL[${BACKUP_LEVEL}]}.lst"
  if [[ ! -e ${BASE_LIST_FILE} || ${BACKUP_LAVEL} -eq 1 ]] ; then
    # if new tape is being selected, use level-0-tape as base of differential backup.
    BASE_LIST_FILE="${LIST_FILE_DIR}/${BACKUP_FILE_BASE}.`getTapeLabel ${LEVEL_0_TAPE}`.`printf %0${#AMOUNT_OF_TAPE}d 0`.lst"
  fi

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "base_list_file is '${BASE_LIST_FILE}'" >> ${LOG_FILE}

  # list file used with current backup process.
  BACKUP_LEVEL_LABEL=`printf "%0${#MAX_BACKUP_LEVEL}d" ${BACKUP_LEVEL}`
  USE_LIST_FILE="${LIST_FILE_DIR}/${BACKUP_FILE_BASE}.${TAPE_LABEL}.${BACKUP_LEVEL_LABEL}.lst"

  if [[ -e $BASE_LIST_FILE ]] ; then
    if [[ ${BASE_LIST_FILE} != ${USE_LIST_FILE} ]] ; then
      # base list file is the list of files which are backed up during previous backup process,
      # and it is used to identify files updated since previous backup.
      # Unless level-0-tape is selected, list file need to be created from the base list file of the selected tape.
      CMD="${CP} -af ${BASE_LIST_FILE} ${USE_LIST_FILE}"
      echo `${DATE} +%Y/%m/%d-%H:%M:%S` "${CMD}" >> ${LOG_FILE}
      ${CMD}
    fi
  else
    # absence of base_list_file at this stage means the list file of level-0 backup does not exist.
    # level-0 backup (=full backup is required)
    BACKUP_LEVEL=0
    echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_level = ${BACKUP_LEVEL}" >> ${LOG_FILE}
    BACKUP_TAPE=${LEVEL_0_TAPE}
    echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_tape = ${BACKUP_TAPE}" >> ${LOG_FILE}
    TAPE_LABEL=`getTapeLabel ${BACKUP_TAPE}`
    BACKUP_LEVEL_LABEL=`printf "%0${#MAX_BACKUP_LEVEL}d" ${BACKUP_LEVEL}`

    USE_LIST_FILE="${LIST_FILE_DIR}/${BACKUP_FILE_BASE}.${TAPE_LABEL}.${BACKUP_LEVEL_LABEL}.lst"
  fi
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "use_list_file is '${USE_LIST_FILE}'" >> ${LOG_FILE}

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "Final state is as below." >> ${LOG_FILE}
  BACKUP_LEVEL_LABEL=`printf "%0${#MAX_BACKUP_LEVEL}d" ${BACKUP_LEVEL}`
  BACKUP_FILE="${BACKUP_DATA_DIR}/${BACKUP_FILE_BASE}.${TAPE_LABEL}.${BACKUP_LEVEL_LABEL}.`${DATE} +%Y%m%d-%H%M`.tbz"
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_file name = '${BACKUP_FILE}'" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_level = ${BACKUP_LEVEL}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_level_label = ${BACKUP_LEVEL_LABEL}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_tape = ${BACKUP_TAPE}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_tape_label = ${TAPE_LABEL}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "list_file = '${USE_LIST_FILE}'" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup target = '${target}'" >> ${LOG_FILE}

  CMD="${TAR} -cj -g ${USE_LIST_FILE} -f ${BACKUP_FILE} ${target} "
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "${CMD}" >> ${LOG_FILE}
  RESULT=`${CMD} 2>&1`
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "${RESULT}" >> ${LOG_FILE}


  echo "LAST_BACKUP_LEVEL=${BACKUP_LEVEL}" > ${STATE_FILE}
  echo "LAST_BACKUP_TAPE=${BACKUP_TAPE}" >> ${STATE_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S`  "backup with level=${BACKUP_LEVEL}, tape=${BACKUP_TAPE} is completed." >> ${LOG_FILE}

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` '#########backup stript is finished##########'  >> ${LOG_FILE}

done



参考までに、2012年から使用していたのはこちら。もはや存在しなくなったディレクトリのバックアップを取ってみたり、新しく作られたディレクトリのバックアップを取っていなかったりの設定上の問題はあるが、ディスク容量が不足しない限りはうまく動いていた。

日次でバックアップしていたので、30日分のバックアップが取られている。


# cat daily_backup.sh
#!/bin/bash

TARGET_DIR=(/etc /data/home /data/svn /data/lib /data/spool /data/www /var/lib /chroot)
LIST_FILE_DIR='/data2/backup/edo/list_files'
LOG_FILE_DIR='/data2/backup/edo/logs'
BACKUP_DATA_DIR='/data2/backup/edo/data'

SED='/bin/sed'
MKDIR='/bin/mkdir'
DATE='/bin/date'
CP='/bin/cp'
RM='/bin/rm'
LS='/bin/ls -Art'
TAR='/bin/tar'

LEVEL_0_TAPE='Z'
# backup tape sequence
# first tape = 0
# 0 means using TAPE-A(see below)
# 1 means using TAPE-B
# 2 means using TAPE-C
# ex.
# use TAPE-B followed by using TAPE-A: (1 x x)
# use TAPE-C followed by using TAPE-B: (x 2 x)
# use TAPE-A followed by using TAPE-C: (x x 0)
NEXT_TAPE=(1 2 0)

# Tower of HANOI sequence
# TAPE-0 0
# TAPE-A   -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-B 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-C 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-A 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-B 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->
# TAPE-C 1 -> 3 -> 2 -> 5 -> 4 -> 7 -> 6 -> 9-> 8 ->

# next hanoi-num
# ex. next hanoi-num of 3 is 2
#     (x x x 2 x x x x x x)
NEXT_HANOI=(3 3 5 2 7 4 9 6 1 8)

# which list-file should be used
# level-0 = new tape
# level-1 = use level-0 list
# level-2 = use level-1 list (or level-0 list)
# level-3 = use level-1 list (or level-0 list)
# level-4 = use level-2 list
# level-5 = use level-2 list
# level-6 = use level-4 list
# level-7 = use level-4 list
# level-8 = use level-6 list
# level-9 = use level-6 list

BASE_LIST_FILE_NUM=(0 0 1 1 2 2 4 4 6 6)

function clear_old_backup {
  BACKUP_TAPE=$1
  BACKUP_FILE_BASE=$2
  TAPE_STR="TAPE-${BACKUP_TAPE}"
  for clear_target_dir in ${LIST_FILE_DIR} ${BACKUP_DATA_DIR} ; do
    echo `${DATE} +%Y/%m/%d-%H:%M:%S` "clearing $clear_target_dir for ${BACKUP_FILE_BASE}.${TAPE_STR}" >> ${LOG_FILE}
    for rm_target in `${LS} ${clear_target_dir}/${BACKUP_FILE_BASE}.${TAPE_STR}.* 2> /dev/null` ; do
      CMD="${RM} -f ${rm_target}"
      echo `${DATE} +%Y/%m/%d-%H:%M:%S` "${CMD}" >> ${LOG_FILE}
      ${CMD}
    done
  done
  return 1
}

if [[ ! -d ${LIST_FILE_DIR} ]] ; then
  ${MKDIR} -p ${LIST_FILE_DIR}
fi

if [[ ! -d ${LOG_FILE_DIR} ]] ; then
  ${MKDIR} -p ${LOG_FILE_DIR}
fi

if [[ ! -d ${BAKUP_DATA_DIR} ]] ; then
  ${MKDIR} -p ${BACKUP_DATA_DIR}
fi

for target in ${TARGET_DIR[@]} ; do
  BACKUP_FILE_BASE=`echo "${target}" | ${SED} -e "s/^\///g" -e "s/-/--/" -e "s/\//-/"`

  LOG_FILE="${LOG_FILE_DIR}/${BACKUP_FILE_BASE}.log"

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "#########backup stript is started##########"  >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "log_file is '${LOG_FILE}'" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup target is '${target}'" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_file_base name is '${BACKUP_FILE_BASE}'" >> ${LOG_FILE}

  STATE_FILE="${LOG_FILE_DIR}/${BACKUP_FILE_BASE}.state"
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "state_file is '${STATE_FILE}'" >> ${LOG_FILE}

  # read state file
  if [[ -e ${STATE_FILE} ]] ; then
    source ${STATE_FILE}
  fi

  # echo state file data
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "last_backup_level = ${LAST_BACKUP_LEVEL}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "last_backup_tape = ${LAST_BACKUP_TAPE}" >> ${LOG_FILE}

  # find next backup level
  if [[ -z ${LAST_BACKUP_LEVEL} ]] ; then
    BACKUP_LEVEL=0
  else
    BACKUP_LEVEL=${NEXT_HANOI[${LAST_BACKUP_LEVEL}]}
  fi
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_level = ${BACKUP_LEVEL}" >> ${LOG_FILE}

  # find next backup tape
  if [[ -z ${LAST_BACKUP_TAPE} ]] ; then
    BACKUP_TAPE=${NEXT_TAPE[((${#NEXT_TAPE[@]}-1))]}
    clear_old_backup ${BACKUP_TAPE} ${BACKUP_FILE_BASE}
  else
    if [[ ${LAST_BACKUP_LEVEL} -eq 0 ]] ; then
      BACKUP_TAPE=${NEXT_TAPE[((${#NEXT_TAPE[@]}-1))]}
      clear_old_backup ${BACKUP_TAPE} ${BACKUP_FILE_BASE}
    elif [[ ${BACKUP_LEVEL} -ne 1 ]] ; then
      BACKUP_TAPE=${LAST_BACKUP_TAPE}
    else
      BACKUP_TAPE=${NEXT_TAPE[${LAST_BACKUP_TAPE}]}
      clear_old_backup ${BACKUP_TAPE} ${BACKUP_FILE_BASE}
    fi
  fi

  if [[ ${BACKUP_LEVEL} -eq 0 ]] ; then
    BACKUP_TAPE=${LEVEL_0_TAPE}
  fi

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_tape = ${BACKUP_TAPE}" >> ${LOG_FILE}

  TAPE_STR="TAPE-${BACKUP_TAPE}"

  BASE_LIST_FILE="${LIST_FILE_DIR}/${BACKUP_FILE_BASE}.${TAPE_STR}.${BASE_LIST_FILE_NUM[${BACKUP_LEVEL}]}.lst"
  if [[ ! -e ${BASE_LIST_FILE} || ${BACKUP_LAVEL} -eq 1 ]] ; then
    BASE_LIST_FILE="${LIST_FILE_DIR}/${BACKUP_FILE_BASE}.TAPE-${LEVEL_0_TAPE}.0.lst"
  fi

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "base_list_file is '${BASE_LIST_FILE}'" >> ${LOG_FILE}

  USE_LIST_FILE="${LIST_FILE_DIR}/${BACKUP_FILE_BASE}.${TAPE_STR}.${BACKUP_LEVEL}.lst"

  if [[ -e $BASE_LIST_FILE ]] ; then
    if [[ ${BASE_LIST_FILE} != ${USE_LIST_FILE} ]] ; then
      CMD="${CP} -af ${BASE_LIST_FILE} ${USE_LIST_FILE}"
      echo `${DATE} +%Y/%m/%d-%H:%M:%S` "${CMD}" >> ${LOG_FILE}
      ${CMD}
    fi
  else
    BACKUP_LEVEL=0
    echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_level = ${BACKUP_LEVEL}" >> ${LOG_FILE}
    BACKUP_TAPE=${LEVEL_0_TAPE}
    echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_tape = ${BACKUP_TAPE}" >> ${LOG_FILE}
    TAPE_STR="TAPE-${BACKUP_TAPE}"

    USE_LIST_FILE="${LIST_FILE_DIR}/${BACKUP_FILE_BASE}.${TAPE_STR}.${BACKUP_LEVEL}.lst"
  fi
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "use_list_file is '${USE_LIST_FILE}'" >> ${LOG_FILE}

  BACKUP_FILE="${BACKUP_DATA_DIR}/${BACKUP_FILE_BASE}.${TAPE_STR}.${BACKUP_LEVEL}.`${DATE} +%Y%m%d`.tbz"
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_file name = '${BACKUP_FILE}'" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_level = ${BACKUP_LEVEL}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup_tape = ${BACKUP_TAPE}" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "list_file = '${USE_LIST_FILE}'" >> ${LOG_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "backup target = '${target}'" >> ${LOG_FILE}

  CMD="${TAR} -cj -g ${USE_LIST_FILE} -f ${BACKUP_FILE} ${target} "
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "${CMD}" >> ${LOG_FILE}
  RESULT=`${CMD} 2>&1`
  echo `${DATE} +%Y/%m/%d-%H:%M:%S` "${RESULT}" >> ${LOG_FILE}


  echo "LAST_BACKUP_LEVEL=${BACKUP_LEVEL}" > ${STATE_FILE}
  echo "LAST_BACKUP_TAPE=${BACKUP_TAPE}" >> ${STATE_FILE}
  echo `${DATE} +%Y/%m/%d-%H:%M:%S`  "backup with level=${BACKUP_LEVEL}, tape=${BACKUP_TAPE} is completed." >> ${LOG_FILE}

  echo `${DATE} +%Y/%m/%d-%H:%M:%S` '#########backup stript is finished##########'  >> ${LOG_FILE}

done





 

0 件のコメント:

コメントを投稿