「バックアップは作って戻してワンセット」という格言があります(うそです、今作りました。元ネタは将棋界の「タイトルは取って守ってワンセット」ですが、これって誰のセリフだっけ?)。というわけで、「Ubuntu で Time Machine 的なバックアップ戦略を立てる」の続きは、作成したバックアップからシステムを復旧する手順の確立です。
作戦としては、以下の通りです。対話的に実行します。
- スナップショットの一覧を表示して、復元したいものを選ぶ。
- ディスクの一覧を表示して、復元先を選ぶ。
- ファイルを復元する。
- ディスクで起動できるように必要な情報を書き込む。
1.は rsync の --list-only オプションを使います。$SERVER, $MODULE は設定ファイルから読み込むことを想定していますが、これも対話的に入力してもらったほうがいいかもしれません。
echo "List of available snapshots:"
readarray -t ENTRIES < <(rsync --list-only --password-file=/etc/rbackup/rsync.passwd $SERVER/$MODULE/snapshots/ | \
awk ' { print $5 }' | \
grep '....-..-.._..-..-..' | \
sort)
LEN=${#ENTRIES[@]}
if [ "$LEN" -eq 0 ]; then
echo "No snapshots are available."
exit 1
fi
for i in `seq 0 $(( LEN - 1 ))`; do
echo $(( LEN - i ))")" "${ENTRIES[$i]}"
done
while true; do
echo -n "Enter the number [1]: "
read NUM
: ${NUM:=1}
if [ $NUM -lt 1 ] || [ $NUM -gt $LEN ]; then
echo "Snapshot number is out of range. Try again."
else
break
fi
done
ENTRY=${ENTRIES[$(( LEN - NUM ))]}
echo "Selected snapshot: ${NUM}) ${ENTRY}"
2. は lsblk を使います。あとで起動可能なディスクを作れるように、「パーティション1が vfat, パーティション2が ext4」のディスクだけを選んで表示します。
echo "List of available disks (ext4 only):"
readarray -t BLKLIST < <(lsblk --list -o NAME,TYPE,FSTYPE,SIZE,MOUNTPOINT | awk '$2 == "disk" || $2 == "part"')
# List disks that have partition 1 with vfat and partition 2 with ext4
DISKS=() # List of disks
declare -A PARTS1 # PARTS1[diskname] is lsblk line for partition 1
declare -A PARTS2 # PARTS2[diskname] is lsblk line for partition 2
disk=
for blk in "${BLKLIST[@]}"; do
e=( $blk )
if [ "${e[1]}" == "disk" ]; then
disk="${e[0]}" # Will be used in the following "part" lines
DISKS+=( "$disk" )
else
name="${e[0]}"
fstype="${e[2]}"
part=${name: -1} # last one byte
if [ "${name:0:${#disk}}" == "$disk" ]; then
# The disk name matches with the partition name
if [ "$part" == "1" ] && [ "$fstype" == "vfat" ]; then
PARTS1[$disk]="$blk"
elif [ "$part" == "2" ] && [ "$fstype" == "ext4" ]; then
PARTS2[$disk]="$blk"
fi
fi
fi
done
# Select disks that have both vfat and ext4 partitions
TARGET_DISKS=()
for disk in "${DISKS[@]}"; do
if [ -n "${PARTS1[$disk]}" ] && [ -n "${PARTS2[$disk]}" ]; then
TARGET_DISKS+=( "$disk" )
fi
done
LEND=${#TARGET_DISKS[@]}
if [ "$LEND" -eq 0 ]; then
echo "No disks with vfat and ext4 partitions are available."
exit 1
fi
for i in `seq 0 $(( LEND - 1 ))`; do
echo $(( LEND - i ))")" "${PARTS2[${TARGET_DISKS[$i]}]}"
done
while true; do
echo -n "Enter the number: "
read NUMD
: ${NUMD:=0}
if [ $NUMD -lt 1 ] || [ $NUMD -gt $LEND ]; then
echo "Disk number is out of range. Try again."
else
break
fi
done
3. は rsync の呼び出しです。復元先に直接上書きするのではなく、一時フォルダの中にファイルを転送します。
TEMPLATE=rbackup-restore.`date "+%Y%m%d%H%M%S"`.XXXXXXXXX
RESTORE_DIR=`mktemp -d "$MPOINT/$TEMPLATE"`
echo "Invoking rsync at `date`..."
$Q rsync \
$OPTIONS \
-aHAX \
--info=progress2 \
--numeric-ids \
--recursive \
--delete \
--force \
--stats \
--sparse \
--exclude-from="/etc/rbackup/exclude.list" \
"$SERVER/$MODULE/snapshots/$ENTRY/" \
"$RESTORE_DIR/"
RSYNC_STATUS=$?
if [ "$RSYNC_STATUS" != "0" ]; then
echo "rsync failed at `date`."
echo "Cleaning up the intermediate files..."
$Q rm -rf "$RESTORE_DIR"
echo "Completed. The destination $MPOINT is expected to be left untouched."
exit 1
fi
転送が成功したら、元のファイルを別の一時ディレクトリに移し、転送したファイルをルートディレクトリに展開します。単なるファイル移動なんだけど、bash のワイルドカードの扱いに慣れておらず、ちょっと苦戦しました。
TRASH_DIR=`mktemp -d "$MPOINT"/.trash.XXXXXXXXX`
echo "Cleaning up the old files..."
ERR=
for d in "$MPOINT"/.* "$MPOINT"/*; do
if [ -e "$d" ] && [ "$d" != "$TRASH_DIR" ] && [ "$d" != "$RESTORE_DIR" ]; then
$Q mv "$d" "$TRASH_DIR"
if [ "$?" != 0 ]; then
echo "Failed to move the old files."
ERR=1
break
fi
fi
done
if [ -z "$ERR" ]; then
echo "Moving the restored files to the destination..."
for d in "$RESTORE_DIR"/.* "$RESTORE_DIR"/*; do
if [ -e "$d" ]; then
$Q mv "$d" "$MPOINT"
if [ "$?" != 0 ]; then
echo "Failed to move the restored files."
ERR=1
break
fi
fi
done
if [ -z "$ERR" ]; then
rmdir "$RESTORE_DIR"
if [ "$?" != 0 ]; then
echo "Failed to remove the temporary directory."
ERR=1
fi
fi
fi
if [ -z "$ERR" ]; then
echo "Removing the old files..."
$Q rm -rf "$TRASH_DIR"
if [ "$?" != 0 ]; then
echo "Failed to remove the old files."
ERR=1
fi
fi
4. のブート可能なディスクを作るところは、全く知識がなかったため、いろいろ調べました。まず、/etc/fstab を書き換えて、今回復元先に指定したディスクの ext4 パーティションを "/" にマウント、vfat パーティションを "/boot/efi" にマウントするように設定します。元の /etc/fstab の書き方を踏襲しています。違うディストリビューションだと、編集するところの書き方は変えないといけないかもしれません。
if [ -z "$ERR" ]; then
echo "Updating /etc/fstab..."
UUID1=`blkid /dev/$PART_VFAT | awk '
{ if (match($0, / UUID="[-0-9a-fA-F]+/)) print substr($0, RSTART+7, RLENGTH-7); }
'`
UUID2=`blkid /dev/$PART | awk '
{ if (match($0, / UUID="[-0-9a-fA-F]+/)) print substr($0, RSTART+7, RLENGTH-7); }
'`
if [ -z "$UUID1" ] || [ -z "$UUID2" ]; then
echo "Failed to get the UUID of the target partitions."
ERR=1
else
$Q mv "$MPOINT/etc/fstab" "$MPOINT/etc/fstab.orig"
if [ "$?" != 0 ]; then
echo "Failed to rename the restored /etc/fstab."
ERR="$?"
else
awk "{
if (\$0 ~ /^# \/ was on /) {
print(\"# / was on /dev/$PART during rbackup-restore\");
} else if (\$0 ~ /\/dev\/disk\/by-uuid\/[-0-9a-fA-F]+ \/ ext4/) {
print(\"/dev/disk/by-uuid/$UUID2 / ext4 defaults 0 1\");
} else if (\$0 ~ /^# \/boot\/efi was on /) {
print(\"# /boot/efi was on /dev/$PART_VFAT during rbackup-restore\");
} else if (\$0 ~ /\/dev\/disk\/by-uuid\/[-0-9a-fA-F]+ \/boot\/efi vfat/) {
print(\"/dev/disk/by-uuid/$UUID1 /boot/efi vfat defaults 0 1\");
} else {
print
}
}" <"$MPOINT/etc/fstab.orig" >"$MPOINT/etc/fstab"
if [ "$?" != 0 ]; then
echo "Failed to update the restored /etc/fstab."
ERR="$?"
fi
fi
fi
fi
次に、復元したディスクをマウントして、そのルートディレクトリに chroot して、grub-install, update-grub, update-initramfs を実施しました。
if [ -z "$ERR" ]; then
echo "Updating grub..."
cd "$MPOINT"
if [ "$?" != 0 ]; then
echo "Cannot cd to $MPOINT."
ERR="$?"
else
for i in dev dev/pts proc sys run; do
mkdir -p $PWD/$i && mount -B /$i $PWD/$i
if [ "$?" != 0 ]; then
echo "Failed to mount /$i in the restored system."
ERR="$?"
break
fi
done
fi
if [ -z "$ERR" ]; then
chroot "$MPOINT" /bin/bash <<EOS
rm -rf /boot/efi/* &&
mount -t vfat /dev/$PART_VFAT /boot/efi &&
grub-install /dev/$DISK &&
update-grub &&
update-initramfs -u -k all &&
umount /dev/$PART_VFAT
exit \$?
EOS
if [ "$?" != 0 ]; then
echo "Failed to update grub."
ERR="$?"
fi
fi
if [ -z "$ERR" ]; then
for i in run sys proc dev/pts dev; do
umount "$MPOINT/$i"
if [ "$?" != 0 ]; then
echo "Failed to unmount /$i from the restored system."
ERR="$?"
break
fi
done
fi
fi
作成した外付けディスクから立ち上げてみます。ちゃんと起動ディスクとして認識されています。
無事起動しました!
別のマシンでも起動してみます。これは借り物のミニPCです。
立ち上がりました。問題なさそうです。
なお、MacBookPro 2019 でも起動を試みました。Optionを押しながら起動すると、外付けディスクは "EFI Boot" という名前で認識されています。
これを選んで起動しようとすると、「起動ディスクを検証できません。やり直すか、別の起動ディスクを選択してください。」というメッセージが出ます。セキュリティ絡みの何かですかね。これ以上追求するのはやめました。「Macはもういいかな…」という気分になりつつあります。