2026年07月25日

Ubuntu で Time Machine 的なバックアップ(その2)

 「バックアップは作って戻してワンセット」という格言があります(うそです、今作りました。元ネタは将棋界の「タイトルは取って守ってワンセット」ですが、これって誰のセリフだっけ?)。というわけで、「Ubuntu で Time Machine 的なバックアップ戦略を立てる」の続きは、作成したバックアップからシステムを復旧する手順の確立です。

 作戦としては、以下の通りです。対話的に実行します。

  1. スナップショットの一覧を表示して、復元したいものを選ぶ。
  2. ディスクの一覧を表示して、復元先を選ぶ。
  3. ファイルを復元する。
  4. ディスクで起動できるように必要な情報を書き込む。

 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

 作成した外付けディスクから立ち上げてみます。ちゃんと起動ディスクとして認識されています。

20260725-1.jpg

 無事起動しました!

20260725-2.jpg

 別のマシンでも起動してみます。これは借り物のミニPCです。

20260725-3.jpg

 立ち上がりました。問題なさそうです。

20260725-4.jpg

 なお、MacBookPro 2019 でも起動を試みました。Optionを押しながら起動すると、外付けディスクは "EFI Boot" という名前で認識されています。

20260725-5.jpg

 これを選んで起動しようとすると、「起動ディスクを検証できません。やり直すか、別の起動ディスクを選択してください。」というメッセージが出ます。セキュリティ絡みの何かですかね。これ以上追求するのはやめました。「Macはもういいかな…」という気分になりつつあります。

20260725-6.jpg

タグ:Linux
Posted at 2026年07月25日 23:10:06
email.png