📚

rsyncを使ったフルバックアップ

2022/04/07に公開

今更rsysncで手動で取るのはどうか?という声もあると思います。
確かにtimeshiftという文明の利器や、baculaのような高度なバックアップサーバーもありますし、
vpsでもサーバーのフルバックアップを簡単にしかも安価に取れるサービスもあります。

しかし、timeshiftはどちらかというとシステムのスナップショットを取るのが仕事で、
/home、/root以下のバックアップは非推奨なのでユーザー配下はrsyncでバックアップを取ることになったり、Gentooだとtimeshiftがまだtestingパッケージで使いづらいなどの欠点があり、

baculaだと個人、小規模、中の小規模程度までならやり過ぎで管理コストのほうが高そう

など帯に短し、たすきに長しという状況だったりします。

ゼロから自分でフルバックアップ,差分バックアップのコードを書くのは
そこそこ大変かつ、スキルが必要になるのでここに書きます。
必要に応じて、ソースコードを書き換えてください。

ソースコード

system_backup.sh
#!/bin/bash
#
# gnu command util.

#######################################
# write stderr syntax. google style(https://google.github.io/styleguide/shellguide.html)
# Globals:
#   BACKUP_FORMAT
#   BACKUP_DESTINATION
# Arguments:
#   All.
# Outputs:
#   format 
# Returns:
#   0 if thing was backup is successed, non-zero on error.
# Example:
#   # .cache/ directory has browser cache file. chrome, edge and more...
#   # --defferental 差分バックアップのオプション
#   system_backup --diffrental --backup-dest /run/media/${USER}/data --add-exclude $HOME/.cache
#######################################
function system_backup() {

  source $(dirname $0)/../function/gnu_alias
  if ! gnu_alias; then
    return 1;
  fi

  if [ ${UID} -ne "0" ] && [ ${EUID} -ne "0" ]; then
    echo 'this function use superuser only' >&2
    return 1
  fi

  # 引数をパースしたときのサブシェルの結果取得
  local tmp_file=$(mktemp)
  echo 0 > $tmp_file
  trap "
    rm $tmp_file
    trap - RETURN
  " RETURN
  local options=$(getopt -o f: -l dry-run,backup-dest:,diffrental,check-exclude,system-only,add-exclude: -- "$@" || echo 1 > $tmp_file)
  local re=$(sed -n '1p' $tmp_file)

  if [ "$re" -ne "0" ]; then
    return $re
  fi
  
  local backup_format
  local dryrun
  local backup_destination
  local check_exclude=1
  local add_exclude
  local system_only=1
  # パースした値を渡す
  eval set -- "$options"

  # 値のセットのみ行う
  while [ $# -gt 0 ]; do
    case $1 in
      -f)
        shift
        backup_format=$1
        ;;
      --check-exclude)
        shift
        check_exclude=0
        ;;
      --dry-run)
        shift
        dryrun='--dry-run'
        ;;
      --system-only)
        shift
        system_only=0
        ;;
      --add-exclude)
        shift
        add_exclude=$1
        ;;
      --backup-dest)
        shift
        backup_destination=$1
        ;;

      --diffrental)
        shift
        diffrental=0
        ;;
        
      --)
        shift
        break
        ;;
      *)
        shift
        ;;
    esac
  done

  # backupのフォーマットが指定されていなかったら、環境変数を設定する。それでもなかったら、
  if [ -z $backup_format ]; then
    backup_format=$BACKUP_FORMAT
    if [ -z $backup_format ]; then
      backup_format="%Y%m%dT%H%M%S%Z"
    fi
  fi

  local suffix_format=$(date +"${backup_format}")

  local exclude_list=("dev" "proc" "sys" "tmp" "run" "mnt" "media" "lost+found")
  
  exclude_list+=($(echo /home/*/.cache | sed 's|^/||'))
  exclude_list+=($(echo /root/.cache | sed 's|^/||' ))

  # 除外リストへの追加
  if [ ! -z $add_exclude ]; then
    # destination 
    exclude_list+=($(
      echo $add_exclude | \
        sed 's/,/ /g' | \
        xargs -n 1 | \
        sed 's|/$||' | \
        sed 's|^/||' | \
        xargs
      )
    )
  fi

  # system only exclude user directory.
  if [ "$system_only" -eq "0" ]; then
    exclude_list+=($(echo /home | sed 's|^/||' ))
    exclude_list+=($(echo /root | sed 's|^/||' ))
  fi
  # output exclude directory
  if [ "$check_exclude" -eq "0" ]; then
    echo "${exclude_list[@]}"
    return
  fi

  if [ -z $backup_destination ]; then
    backup_destination=$BACKUP_DESTINATION
    if [ -z $backup_destination ]; then
      echo 'set backup destination.' >&2
      return 1
    fi
  fi
  # 末尾の/を削除しておく。
  backup_destination=$(echo $backup_destination | sed 's|/$||')

  # ディレクトリの存在確認
  if [ ! -d $backup_destination ]; then
    echo $backup_destination is not exists!
    return 1
  else
    # 除外リストに入っていないかチェック
    local tmp_file2=$(mktemp)
    trap "
      rm $tmp_file
      rm $tmp_file2
      trap - RETURN
    " RETURN
    echo "${exclude_list[@]}" | \
      xargs -n 1 | \
      sed 's|\*$||' > $tmp_file2
    # 再起処理になりそうなものがあったらエラーとして出力
    # exclude pathに
    local re2=1
    for exclude in $(cat $tmp_file2); do
      echo $backup_destination | grep -E ^/${exclude} > /dev/null
      if [ "$?" -eq 0 ]; then
        re2=0
      fi
    done
    if [ "$re2" -eq "1" ]; then
      echo $backup_destination is contain backup_file path. >&2
      return $re2
    fi
  fi
  # rsync -e 'ssh -i '
  # という形で
  # 差分バックアップなので、差分があるところを調べる。
  # logファイルを覗いて、一番大きい行だけ取得する。
  local latest_backup_file=$(ls -1 $backup_destination | grep -v .log | sort | sed -n '1p' | xargs -I {} basename {})

  # 差分指定で差分があったらそれを使う。それ以外はフルバックアップ
  local comparedest
  if [ ! -z $latest_backup_file ] || [ $diffrental -eq "0" ]; then
    comparedest="--compare-dest=${backup_destination}/${latest_backup_file}"
  fi

  # バックアップ元のファイルシステムを判別する
  local backup_src_filesystem=$(findmnt -m --target / | sed -n '2p' | awk '{print $3}')
  # バックアップ先のファイルシステムを判別する
  local backup_dest_filesystem=$(findmnt -m --target ${backup_destination} | sed -n '2p' | awk '{print $3}')

  if [ $backup_src_filesystem != $backup_dest_filesystem ]; then
    echo "backup src filesystem is ${backup_src_filesystem}" >&2
    echo "backup dest filesystem is ${backup_dest_filesystem}" >&2
    echo "filesystem unmatch, acl or user info maybe lost." >&2
    return 1
  fi

  # 別のシステムのバックアップを取る場合
  # --numeric-ids
  rsync -aAXv \
    $comparedest \
    $(echo "${exclude_list[@]}" | xargs -n 1 | xargs -I {} echo --exclude {} | xargs) \
    --log-file ${backup_destination}/${suffix_format}.log \
    --info=progress2 \
    $dryrun \
    / \
    ${backup_destination}/${suffix_format}

}

function usage() {
    cat 1>&2 <<EOF
system_bakup
this command 

USAGE::
    system_backup [FLAGS] [OPTIONS]

FLAGS:
    -c, --check-exclude     check-exclude list
    -f                      format for backup
    --system-only           backup exclude /home directory.
    --diffrental            use differential backup.
    --backup-dest           backup destination. roop not permited.
    --add-exclude           add excludes file or directories comman separeted /foo/bar,/awesome/directory,/aaa/bbb/ccc
    --full                  default is fullbackup. conains user directory /home.
    -h, --help              Prints help information
    --dryrun                dry run. check sync process.

OPTIONS:
    --debug                 Set bash debug Option
    --district              If you error occured and not catched, forcfully return. this flag use debug.
    --check-grammer         check bash grammer
    --time                  calculate execute time
EOF
}

function main {

  local i
  local timef=1
  local new_array=( $@ )
  for ((i=0;i<$#;i++)); do
    if [ "${new_array[$i]}" = "--help" ] || [ "${new_array[$i]}" = "-h" ]; then
      usage
      return
    fi
    if [ "${new_array[$i]}" = "--check-grammer" ]; then
      bash -n ${BASH_SOURCE[0]}
      return
    fi
    # set time flag for calculate script time.
    if [ "${new_array[$i]}" = "--time" ]; then
      timef=0
      unset new_array[$i]
    fi
    # if find --debug flag from args, start debug mode.
    if [ "${new_array[$i]}" = "--debug" ]; then
      set -x
      trap "
        set +x
        trap - RETURN
      " RETURN
      unset new_array[$i]
    fi
  done
  # reindex assign.
  new_array=${new_array[@]}

  if [ "$timef" -eq "0" ]; then
    time system_backup $new_array
  else
    system_backup $new_array
  fi
}

main $@

使い方

下記のコマンドで使い方が表示されます。

system_bakcup -h
# システムでmount済みのディスク/run/media/${USER}/dataへ
# 差分バックアップ(デフォルトだと保存先のフォーマットが%Y%m%dT%H%M%S%Zで一番新しいものとの差分を取る。)
# ユーザーの.cacheディレクトリを除く場合
# (システムとユーザーのデータ両方バックアップ)
system_backup --diffrental --backup-dest /run/media/${USER}/data --add-exclude /home/*/.cache

# システムのみバックアップ(ユーザー配下は除く。)
system_backup --diffrental --system-only --backup-dest /run/media/${USER}/data

## システムだけのフルバックアップ(--differentalがないとフルバックアップになる。)
system_backup --system-only --backup-dest /run/media/${USER}/data

## --dry-runを引数に渡すとdryrun
system_backup --dry-run --diffrental --backup-dest /run/media/${USER}/data --add-exclude /home/*/.cache

## timeを引数に渡すと処理時間を最後に表示
system_backup --time --diffrental --backup-dest /run/media/${USER}/data --add-exclude /home/*/.cache

最新のソースコード

github

参考

arch linux wiki

Discussion