Linuxの増分バックアップをする(外部ストレージへバックアップ)

いろいろバックアップ方法を模索していく中て今の所うまく行っている方法を備忘録的に残します。
仕組みとしては増分バックアップなのでバックアップ時の容量を減らしつつ複数世代のバックアップを残しておけます。
[増分バックアップ(Wikipediaより)](https://ja.wikipedia.org/wiki/増分バックアップ)

サーバへバックアップする方法も用意しました。詳しくは[こちら](https://qiita.com/ykgeek/items/3317031198867369547e)

~~リストア方法は現在記事を用意しているのでお待ちください。
(rsyncを使って逆のことをすればいいだけなのでそんなに難しくはありませんが…)~~

追記(2020/03/12)
お待たせしました。リストア方法も用意しました。詳しくは[こちら](https://qiita.com/ykgeek/items/6683a8ac6f1497b4eb25)
また、rsyncのコマンドにH,A,Xオプションを追加しました。

**使用する際は必ず復元できるところまで確認してから使用してください。**

# TL;DR
ここで紹介する方法は端末host1のデータを外付けHDDなどにバックアップする方法です。イメージとしては下記のようにhost1のデータを同じ端末につないである外付けHDDなどにコピーする感じです。

host1(バックアップスクリプトを実行する端末) ---> 外付けHDDなど

シンプルな仕組みで単純に１台の端末をバックアップする際に便利です。
rootで実行してください。


```bash:backup
#! /bin/bash

# (Backup all directories and files)
SYSTEMPATH="/"

# Backup destination directory
BACKUPDIR="/path/to/backup"

# Backup exclude flags
EXCLUDEFLAG=`echo "--exclude="{/dev/*,/proc/*,/sys/*,/tmp/*,/run/*,/mnt/*,/media/*,/lost+found}`
#EXCLUDEFLAG="--exclude=/dev/* --exclude=/proc/* --exclude=/sys/* --exclude=/tmp/* --exclude=/run/* --exclude=/mnt/* --exclude=/media/* --exclude=/lost+found"

# Find previous backup
OLDDATE="NewBackup"
if [ -f ${BACKUPDIR}/latest ]; then
  OLDDATE=$(cat ${BACKUPDIR}/latest)
fi
NEWDATE=$(date +%Y%m%d)

# Get backup path
OLDBU=${BACKUPDIR}/${OLDDATE}
NEWBU=${BACKUPDIR}/${NEWDATE}

# Skip backup if already backup today
if [ ${OLDDATE} = ${NEWDATE} ];then
  echo "Already backed up today."
  exit 0
fi

echo "Backing up ${OLDDATE} -> ${NEWDATE}"
echo "Continue after 5sec..."

sleep 5

# Backup using rsync
rsync -aHAX --info=progress2 --numeric-ids ${EXCLUDEFLAG} --link-dest=${OLDBU} ${SYSTEMPATH} ${NEWBU}

# Check backup status
if [ ! "$?" = "0" ];then
  echo "Backup failed."
  exit 1
fi

# Save latest backup date
echo ${NEWDATE} > ${BACKUPDIR}/latest

echo "Backup complete."

```
# メリット、デメリット
メリットは、ハードリンクを使うことで増分バックアップをしつつも各バックアップのディレクトリを見ればその時の状態のディレクトリ構造が維持されています。つまりバックアップは変更点のみで復元するときは戻りたいところのディレクトリから復元するだけでできます。

デメリットはバックアップする際に全てのデータにアクセスして変更がないかを調べるためディスクIOが発生します。RaspberryPiなどで行うと環境によっては数時間かかる可能性があります。
また保存先のディスクがバックアップ元のファイルシステム以上の機能を持っている必要があります。ext4などで初期化して使う必要があります。(裏技的にディスクイメージを用意してそれを使うという手もあります)

# バックアップの流れ
長々と書きましたが、実際にやっていることは非常にシンプルです。

- バックアップする場所と保存先を指定
- バックアップから除外するディレクトリを指定
- 前回と今回のバックアップの保存先を取得
- バックアップ

## バックアップする場所と保存先を指定
```bash:backup
# Backup source directory
# (Backup all directories and files)
SYSTEMPATH="/"

# Backup destination directory
BACKUPDIR="/path/to/backup"
```

SYSTEMPATHにどのディレクトリをバックアップするのかを指定します。今回はシステムを丸ごとバックアップするので"/"を指定します。
BACKUPDIRにはバックアップの保存先を指定します。外付けのHDDのバックアップ用に用意したディレクトリを指定してください。

## バックアップから除外するディレクトリを指定
```bash:backup
# Backup exclude flags
EXCLUDEFLAG=`echo "--exclude="{/dev/*,/proc/*,/sys/*,/tmp/*,/run/*,/mnt/*,/media/*,/lost+found}`
#EXCLUDEFLAG="--exclude=/dev/* --exclude=/proc/* --exclude=/sys/* --exclude=/tmp/* --exclude=/run/* --exclude=/mnt/* --exclude=/media/* --exclude=/lost+found"
```

EXCLUDEFLAGには"--exclude=ディレクトリ1 --exclude=ディレクトリ2"といった形でオプションを指定します。分かりやすくなるようにbashの展開機能を使い`{/dev/*,/proc/*,/sys/*...}`といった形で指定できるようにしました。

## 前回と今回のバックアップの保存先を取得
```bash:backup
# Find previous backup
OLDDATE="NewBackup"
if [ -f ${BACKUPDIR}/latest ]; then
  OLDDATE=$(cat ${BACKUPDIR}/latest)
fi
NEWDATE=$(date +%Y%m%d)

# Get backup path
OLDBU=${BACKUPDIR}/${OLDDATE}
NEWBU=${BACKUPDIR}/${NEWDATE}
```

増分バックアップをするためにこのスクリプトでは前回バックアップした日付を`latest`というファイルで保持しています。ここでは`latest`ファイルから前回バックアップした日付を読み取り前回のバックアップのパスを取得しています。
同時に今日の日付を取得し、新しいバックアップのパスも用意しています。

## バックアップ
```bash:backup
# Skip backup if already backup today
if [ ${OLDDATE} = ${NEWDATE} ];then
  echo "Already backed up today."
  exit 0
fi

echo "Backing up ${OLDDATE} -> ${NEWDATE}"
echo "Continue after 5sec..."

sleep 5

# Backup using rsync
rsync -aHAX --info=progress2 --numeric-ids ${EXCLUDEFLAG} --link-dest=${OLDBU} ${SYSTEMPATH} ${NEWBU}

# Check backup status
if [ ! "$?" = "0" ];then
  echo "Backup failed."
  exit 1
fi

# Save latest backup date
echo ${NEWDATE} > ${BACKUPDIR}/latest

echo "Backup complete."

```

バックアップをする前に同じ日に2回バックアップをしていないか確認します。（単純に同じ日に2回バックアップをすることを想定していないため。）
その後今回の肝である`rsync`コマンドを実行します。
実行後に正常にバックアップができた場合には`latest`ファイルを更新して終了します。

# rsyncコマンドとは
簡単に言えばリモート間でディレクトリを同期する機能です。強力な機能を提供するためバックアップをする際に使われることも多いコマンドです。
`rsync オプション バックアップするディレクトリ バックアップ先のディレクトリ`
で指定します
このコマンドで使用しているオプションは下記の通りです。

| オプション      | 説明               |
|---------------|--------------------|
| -a            | アーカイブモードで保存 |
| --info        | 進行状況の表示方法を指定 |
| --numeric-ids | UIDやGIDを同じ数字のまま維持 |
| --exclude     | 指定したディレクトリなどを処理から除外 |
| --link-dest   | 指定されたディレクトリと比較し変更がなかった場合そのファイルをハードリンクする |
| -H           | ハードリンクも保持する |
| -A           | ACL(アクセスコントロールリスト)も保持する |
| -X           | 拡張属性も保持する |

# あとがき
なかなか分かりにくい説明になってしまいました。もし何か改善点がありましたらコメントや編集リクエストをいただければと思います。
もし間違いなどがあればそちらもご指摘いただければ幸いです。