步骤/目录:
1.rpi-backup.sh详解
    (1)准备工作
    (2)挂载img文件并备份
    (3)修改PARTUUID
    (4)收尾工作
    (5)使用备份镜像
2.编写脚本文件
    (1)dump/store法
    (2)tar法
3.验证
    (1)备份系统
    (2)还原系统

本文首发于个人博客https://lisper517.top/index.php/archives/10/,转载请注明出处。
本文实验日期为2022年2月7日。使用的是树莓派4B(内存8G版),cat /etc/os-release查看系统为Raspbian GNU/Linux 10 (buster);待备份系统为Pi OS 32位桌面版(即raspbian。2022年1月28日更新,镜像名 2022-01-28-raspios-bullseye-armhf.img )。步骤参考了制作树莓派img镜像文件,脚本在RaspberryBackup项目原址基础上略微改动。

1.rpi-backup.sh详解

(1)准备工作

注意,如果手动挂载读卡器前,读卡器已自动挂载(用df -h查看时发现挂载到/media/pi/boot和/media/pi/rootfs等位置),那么可能需要umount一下,并新建文件夹、手动挂载。如果备份/boot区时出现cp: 无法保留'./tgt_boot/文件名' 的所有者: 不允许的操作,就可能是这个原因。

待备份系统sd卡用读卡器接上树莓派,使用lsblk查看,待备份系统的/boot目录在设备/dev/sdb1(大小为256M)、/目录在/dev/sdb2(大小为58.2G),但是还没挂载。在media下新建文件夹并挂载:

mkdir /media/boot1 /media/root1
mount /dev/sdb1 /media/boot1
mount /dev/sdb2 /media/root1

df -h查看,已经挂载上了。
接下来安装需要的软件:

apt install dosfstools dump parted kpartx

创建工作目录并进入:

mkdir ~/backupimg
cd ~/backupimg

创建空白img文件(待备份系统大概使用了4G空间,img文件使用5000M):

dd if=/dev/zero of=raspi.img bs=1M count=5000

简单介绍一下dd命令:用于复制文件,复制时可转换格式。这里的bs是指定读取输入文件、写入输出文件时的字节大小,注意这里带不带B的区别(不带B是1024进制,带B是1000进制。img文件大小必须是512 bytes的整数倍);count指定大小。

经过一段时间后,img文件创建完毕。接下来看看待备份sd卡的分区:

fdisk -l
#显示信息如下
...
Device     Boot  Start       End   Sectors  Size Id Type
/dev/sdb1         8192    532479    524288  256M  c W95 FAT32 (LBA)
/dev/sdb2       532480 122138623 121606144   58G 83 Linux

本例中待备份的sd卡为64GB。接下来使用parted给img文件分区(注意分区的起点扇区号是8192的倍数,4K对齐。这里的8192单位是512 bytes):

parted raspi.img --script -- mklabel msdos
parted raspi.img --script -- mkpart primary fat32 8192s 532479s
parted raspi.img --script -- mkpart primary ext4 532480s -1

然后parted raspi.img进入parted命令行,print free检查一下分区并quit
准备工作已完成,接下来挂载img文件并开始备份。

(2)挂载img文件并备份

使用losetup把img文件虚拟成区块设备:

losetup -f --show raspi.img
#显示/dev/loop0
kpartx -av /dev/loop0
#这时find /dev/mapper -name "loop0p*"可以找到loop0p1(/boot)和loop0p2(/)

接下来格式化,创建文件夹并挂载到文件夹:

mkfs.vfat -n boot /dev/mapper/loop0p1
mkfs.ext4 -L rootfs /dev/mapper/loop0p2
mkdir tgt_boot tgt_Root
mount -t vfat -o uid=root,gid=root,umask=0000 /dev/mapper/loop0p1 ./tgt_boot
mount -t ext4 /dev/mapper/loop0p2 ./tgt_Root

这里用root账户,其他账户需要更改uid和gid。

备份/boot目录(直接cp,需要改挂载位置):

cp -rfp /media/boot1/* ./tgt_boot/

这里再提醒一次,如果手动挂载读卡器前,读卡器已自动挂载(用df -h查看时发现挂载到/media/pi/boot和/media/pi/rootfs等位置),那么可能需要umount一下,并新建文件夹、手动挂载。如果备份/boot区时出现cp: 无法保留'./tgt_boot/文件名' 的所有者: 不允许的操作,就可能是这个原因。
备份/目录:

chmod 777 ./tgt_Root
chown root.root ./tgt_Root
rm -rf ./tgt_Root/*
cd tgt_Root
#开始备份/
dump -0uaf - /media/root1 | restore -rf -

上述方法为dump/restore法。实验时在DUMP: dumping (Pass IV) [regular files]后出现Broken pipe错误。于是查询最初的原文,使用tar法(系统中需要有和待备份系统的/已用空间一样大的额外空间):

cd ..
#使用tar法前先恢复一下tgt_Root文件夹。直接用tar法时则从上文的chmod 777 ./tgt_Root开始
rm -rf ./tgt_Root/*
tar --directory=/media/root1 -pcf ~/backupimg/backup.tar . #注意最后的.不要忘记

该tar命令切换到/目录挂载点(记得自行更改)后将该目录下所有文件打包。过程较慢且运行时树莓派较卡,可以另开PuTTY观察backup.tar大小(watch -d -n 5 ls -l ~/backupimg/backup.tar),或者添加-v如下:

tar --directory=/media/root1 -vpcf ~/backupimg/backup.tar .

tar打包时出现socket ignored(忽略套接字)错误可以忽略。
接下来将backup.tar解压到img文件的根文件系统:

tar --directory=/root/backupimg/tgt_Root -vpxf ~/backupimg/backup.tar

这个过程同样也比较长。完成后即可删除backup.tar。

(3)修改PARTUUID

备份已经完成,但Raspbian启动要对应分区的PARTUUID,需要修改2个文件(~/backupimg/tgt_boot/cmdline.txt~/backupimg/tgt_Root/etc/fstab)。
首先看loop0p*对应的PARTUUID:

blkid
#输出结果
/dev/mapper/loop0p1: SEC_TYPE="msdos" LABEL_FATBOOT="boot" LABEL="boot" UUID="6CA0-1B14" TYPE="vfat" PARTUUID="622d2beb-01"
/dev/mapper/loop0p2: LABEL="rootfs" UUID="6b5a3e6b-1c31-4f39-8a07-6bdba7c80282" TYPE="ext4" PARTUUID="622d2beb-02"

修改cmdline.txt文件,将root=PARTUUID=dfd7da0c-02改为:

root=PARTUUID=622d2beb-02

修改fstab文件,改为:

PARTUUID=622d2beb-01  /boot           vfat    defaults          0       2
PARTUUID=622d2beb-02  /               ext4    defaults,noatime  0       1

最后,删除/目录中系统自动产生的文件、临时文件,镜像文件就制作完成了:

cd ~/backupimg/tgt_Root
rm -rf ./.gvfs ./dev/* ./media/* ./mnt/* ./proc/* ./run/* ./sys/* ./tmp/* ./lost+found/ ./restoresymtable
cd ..

(4)收尾工作

删除各种挂载点和临时文件夹:

umount ~/backupimg/tgt_boot ~/backupimg/tgt_Root
kpartx -d /dev/loop0
losetup -d /dev/loop0
rmdir ~/backupimg/tgt_boot ~/backupimg/tgt_Root

(5)使用备份镜像

镜像写好后可以用dd命令或者Win32DiskImager等工具烧录到sd卡中(如果sd卡原先是系统盘可以使用SD Card Formatter)。注意使用备份系统时首先要在raspi-config中Expand Filesystem。

2.编写脚本文件

脚本在RaspberryBackup项目原址基础上略微改动,主要有3点改动:增加了使用tar法的脚本;增加了获取gid的准确性;增加了fdisk -l在boot分区前有无*的判断。另外改正了1处:

bootsz=`df -P | grep $dev_boot | awk '{print $2}'

改为:

bootsz=`df -P | grep $dev_boot | awk '{print $3}'

本文中在树莓派4B上使用tar法脚本进行备份用时16min58s。用PC会快一些。

(1)dump/store法

若运行时出现DUMP: Broken pipe的错误,请使用tar法脚本。

另外再次再次提醒,如果手动挂载读卡器前,读卡器已自动挂载(用df -h查看时发现挂载到/media/pi/boot和/media/pi/rootfs等位置),那么可能需要umount一下,并新建文件夹、手动挂载。如果备份/boot区时出现cp: 无法保留'./tgt_boot/文件名' 的所有者: 不允许的操作,就可能是这个原因。

#!/bin/sh

if [ $# != 2 ]; then
  echo "argument error: Usage: $0 boot_device_name root_device_name"
  echo "example: $0 /dev/mmcblk0p1 /dev/mmcblk0p2"
  exit 0
fi
dev_boot=$1
dev_root=$2
mounted_boot=`df -h | grep $dev_boot | awk '{print $6}'`
mounted_root=`df -h | grep $dev_root | awk '{print $6}'`
img=rpi-`date +%Y%m%d-%H%M`.img

#install tools
sudo apt-get install dosfstools dump parted kpartx

echo =====================   part 1, prepare workspace    ===============================
mkdir ~/backupimg
cd ~/backupimg

echo ===================== part 2, create a new blank img ===============================
echo "creating new blank img..."
bootsz=`df -P | grep $dev_boot | awk '{print $3}'`
rootsz=`df -P | grep $dev_root | awk '{print $3}'`
totalsz=`echo $bootsz $rootsz | awk '{print int(($1+$2)*1.3/1024)}'`
sudo dd if=/dev/zero of=$img bs=1M count=$totalsz
#sync
echo "...created a blank img, size ${totalsz}M "

# format virtual disk
bootstart=`sudo fdisk -l | grep $dev_boot | awk '{print $2}'`
bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $3}'`
#有些系统 sudo fdisk -l 时boot分区的boot标记会标记为*,此时bootstart和bootend最后应改为 $3 和 $4
first_mark=`sudo fdisk -l | grep $dev_boot | awk '{print $1}'`
if [ "$first_mark" == '*' ]; then
    $bootstart=$bootend
    $bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $4}'`
fi
rootstart=`sudo fdisk -l | grep $dev_root | awk '{print $2}'`
echo "boot: $bootstart >>> $bootend, root: $rootstart >>> end"

sudo parted $img --script -- mklabel msdos
sudo parted $img --script -- mkpart primary fat32 ${bootstart}s ${bootend}s
sudo parted $img --script -- mkpart primary ext4 ${rootstart}s -1

echo =====================  part 3, mount img to system  ===============================
loopdevice=`sudo losetup -f --show $img`
device=/dev/mapper/`sudo kpartx -av $loopdevice | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1`
sleep 5
sudo mkfs.vfat -n boot ${device}p1
sudo mkfs.ext4 -L rootfs ${device}p2
#在backupimg文件夹下新建两个文件夹,将两个分区挂载在下面
mkdir tgt_boot tgt_Root

uid=`whoami`
gid=`id $uid | awk '{print $2}' | awk 'BEGIN{FS="("} {print $NF}' | awk 'BEGIN{FS=")"} {print $1}'`
sudo mount -t vfat -o uid=${uid},gid=${gid},umask=0000 ${device}p1 ./tgt_boot
sudo mount -t ext4 ${device}p2 ./tgt_Root

echo ===================== part 4, backup /boot =========================
sudo cp -rfp ${mounted_boot}/* ./tgt_boot/
sync
echo "...Boot partition done"

echo ===================== part 5, backup / =========================
sudo chmod 777 ./tgt_Root
sudo chown ${uid}.${gid} ./tgt_Root
sudo rm -rf ./tgt_Root/*
cd tgt_Root
# start backup
sudo dump -0uaf - ${mounted_root} | sudo restore -rf -
sync
echo "...Root partition done"
cd ..

echo ===================== part 6, replace PARTUUID =========================

# replace PARTUUID
opartuuidb=`sudo blkid -o export $dev_boot | grep PARTUUID`
opartuuidr=`sudo blkid -o export $dev_root | grep PARTUUID`
npartuuidb=`sudo blkid -o export ${device}p1 | grep PARTUUID`
npartuuidr=`sudo blkid -o export ${device}p2 | grep PARTUUID`
sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_boot/cmdline.txt
sudo sed -i "s/$opartuuidb/$npartuuidb/g" ./tgt_Root/etc/fstab
sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_Root/etc/fstab
echo "...replace PARTUUID done"

echo "remove auto generated files"
#下面内容是删除树莓派中系统自动产生的文件、临时文件等
cd ~/backupimg/tgt_Root
sudo rm -rf ./.gvfs ./dev/* ./media/* ./mnt/* ./proc/* ./run/* ./sys/* ./tmp/* ./lost+found/ ./restoresymtable
cd ..

echo ===================== part 7, unmount =========================
sudo umount tgt_boot tgt_Root
sudo kpartx -d $loopdevice
sudo losetup -d $loopdevice
rmdir tgt_boot tgt_Root

echo "==== All done. img file is under ~/backupimg/ "

(2)tar法

再次再次再次提醒,如果手动挂载读卡器前,读卡器已自动挂载(用df -h查看时发现挂载到/media/pi/boot和/media/pi/rootfs等位置),那么可能需要umount一下,并新建文件夹、手动挂载。如果备份/boot区时cp: 无法保留'./tgt_boot/文件名' 的所有者: 不允许的操作,就可能是这个原因。

#!/bin/sh

if [ $# != 2 ]; then
  echo "argument error: Usage: $0 boot_device_name root_device_name"
  echo "example: $0 /dev/mmcblk0p1 /dev/mmcblk0p2"
  exit 0
fi
dev_boot=$1
dev_root=$2
mounted_boot=`df -h | grep $dev_boot | awk '{print $6}'`
mounted_root=`df -h | grep $dev_root | awk '{print $6}'`
img=rpi-`date +%Y%m%d-%H%M`.img

#install tools
sudo apt-get install dosfstools parted kpartx

echo =====================   part 1, prepare workspace    ===============================
mkdir ~/backupimg
cd ~/backupimg

echo ===================== part 2, create a new blank img ===============================
echo "creating new blank img..."
bootsz=`df -P | grep $dev_boot | awk '{print $3}'`
rootsz=`df -P | grep $dev_root | awk '{print $3}'`
totalsz=`echo $bootsz $rootsz | awk '{print int(($1+$2)*1.3/1024)}'`
sudo dd if=/dev/zero of=$img bs=1M count=$totalsz
#sync
echo "...created a blank img, size ${totalsz}M "

# format virtual disk
bootstart=`sudo fdisk -l | grep $dev_boot | awk '{print $2}'`
bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $3}'`
#有些系统 sudo fdisk -l 时boot分区的boot标记会标记为*,此时bootstart和bootend最后应改为 $3 和 $4
first_mark=`sudo fdisk -l | grep $dev_boot | awk '{print $1}'`
if [ "$first_mark" == '*' ]; then
    $bootstart=$bootend
    $bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $4}'`
fi
rootstart=`sudo fdisk -l | grep $dev_root | awk '{print $2}'`
echo "boot: $bootstart >>> $bootend, root: $rootstart >>> end"

sudo parted $img --script -- mklabel msdos
sudo parted $img --script -- mkpart primary fat32 ${bootstart}s ${bootend}s
sudo parted $img --script -- mkpart primary ext4 ${rootstart}s -1

echo =====================  part 3, mount img to system  ===============================
loopdevice=`sudo losetup -f --show $img`
device=/dev/mapper/`sudo kpartx -av $loopdevice | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1`
sleep 5
sudo mkfs.vfat -n boot ${device}p1
sudo mkfs.ext4 -L rootfs ${device}p2
#在backupimg文件夹下新建两个文件夹,将两个分区挂载在下面
mkdir tgt_boot tgt_Root

uid=`whoami`
gid=`id $uid | awk '{print $2}' | awk 'BEGIN{FS="("} {print $NF}' | awk 'BEGIN{FS=")"} {print $1}'`
sudo mount -t vfat -o uid=${uid},gid=${gid},umask=0000 ${device}p1 ./tgt_boot
sudo mount -t ext4 ${device}p2 ./tgt_Root

echo ===================== part 4, backup /boot =========================
sudo cp -rfp ${mounted_boot}/* ./tgt_boot/
sync
echo "...Boot partition done"

echo ===================== part 5, backup / =========================
sudo chmod 777 ./tgt_Root
sudo chown ${uid}.${gid} ./tgt_Root
sudo rm -rf ./tgt_Root/*
cd tgt_Root
# start backup
echo "tar -c running..."
sudo tar --directory=${mounted_root} -pcf ~/backupimg/backup.tar .
sync
cd ~/backupimg/tgt_Root
echo "tar -x running..."
sudo tar -pxf ../backup.tar
sync
cd ..
sudo rm backup.tar
echo "...Root partition done"

echo ===================== part 6, replace PARTUUID =========================

# replace PARTUUID
opartuuidb=`sudo blkid -o export $dev_boot | grep PARTUUID`
opartuuidr=`sudo blkid -o export $dev_root | grep PARTUUID`
npartuuidb=`sudo blkid -o export ${device}p1 | grep PARTUUID`
npartuuidr=`sudo blkid -o export ${device}p2 | grep PARTUUID`
sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_boot/cmdline.txt
sudo sed -i "s/$opartuuidb/$npartuuidb/g" ./tgt_Root/etc/fstab
sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_Root/etc/fstab
echo "...replace PARTUUID done"

echo "remove auto generated files"
#下面内容是删除树莓派中系统自动产生的文件、临时文件等
cd ~/backupimg/tgt_Root
sudo rm -rf ./.gvfs ./dev/* ./media/* ./mnt/* ./proc/* ./run/* ./sys/* ./tmp/* ./lost+found/ ./restoresymtable
cd ..

echo ===================== part 7, unmount =========================
sudo umount tgt_boot tgt_Root
sudo kpartx -d $loopdevice
sudo losetup -d $loopdevice
rmdir tgt_boot tgt_Root

echo "==== All done. img file is under ~/backupimg/ "

3.验证

(1)备份系统

树莓派4B插上sd卡、接上电源,待备份sd卡插到读卡器、通过usb插到树莓派。输入df -h检查:

df -h
#输出结果
文件系统        容量  已用  可用 已用% 挂载点
/dev/root        57G  4.1G   51G    8% /
devtmpfs        3.7G     0  3.7G    0% /dev
tmpfs           3.9G     0  3.9G    0% /dev/shm
tmpfs           1.6G  980K  1.6G    1% /run
tmpfs           5.0M  4.0K  5.0M    1% /run/lock
/dev/mmcblk0p1  256M   49M  207M   20% /boot
tmpfs           790M   20K  790M    1% /run/user/1000
/dev/sdb2        58G  4.0G   51G    8% /media/pi/rootfs
/dev/sdb1       253M   49M  204M   20% /media/pi/boot
tmpfs           790M   16K  790M    1% /run/user/1002

发现sd卡已自动挂载。umount一下,并重新挂载:

umount /media/pi/rootfs
umount /media/pi/boot
mkdir /media/boot1
mkdir /media/root1
mount /dev/sdb1 /media/boot1
mount /dev/sdb2 /media/root1

写入脚本,为脚本增加执行权限,并执行:

nano /tmp/rpi-backup.sh
#复制tar法脚本内容
chmod +x /tmp/rpi-backup.sh #或chmod 100 /tmp/rpi-backup.sh
/tmp/rpi-backup.sh /dev/sdb1 /dev/sdb2

以下为输出结果:

正在读取软件包列表... 完成
正在分析软件包的依赖关系树... 完成
正在读取状态信息... 完成
dosfstools 已经是最新版 (4.2-1)。
kpartx 已经是最新版 (0.8.5-2)。
parted 已经是最新版 (3.4-1)。
升级了 0 个软件包,新安装了 0 个软件包,要卸载 0 个软件包,有 0 个软件包未被升级。
===================== part 1, prepare workspace ===============================
===================== part 2, create a new blank img ===============================
creating new blank img...
记录了5310+0 的读入
记录了5310+0 的写出
5567938560字节(5.6 GB,5.2 GiB)已复制,332.425 s,16.7 MB/s
...created a blank img, size 5310M
/tmp/rpi-backup.sh: 35: [: /dev/sdb1: unexpected operator
boot: 8192 >>> 532479, root: 532480 >>> end
===================== part 3, mount img to system ===============================
mkfs.fat 4.2 (2021-01-31)
mkfs.fat: Warning: lowercase labels might not work properly on some systems
mke2fs 1.46.2 (28-Feb-2021)
Discarding device blocks: done
Creating filesystem with 1292544 4k blocks and 323200 inodes
Filesystem UUID: b5730506-8cbd-4f4b-993c-fb4eaeb2fa1e
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

===================== part 4, backup /boot =========================
...Boot partition done
===================== part 5, backup / =========================
tar -c running...
tar: ./home/pi/.pcsc11/pcscd.comm: 忽略套接字(socket)
tar -x running...
...Root partition done
===================== part 6, replace PARTUUID =========================
...replace PARTUUID done
remove auto generated files
===================== part 7, unmount =========================
==== All done. img file is under ~/backupimg/

运行用时16min58s。
输出中有一行/tmp/rpi-backup.sh: 35: [: /dev/sdb1: unexpected operator,这个可以不管。

(2)还原系统

将~/backupimg/下的备份镜像用Win32DiskImager写入一张新sd卡,插上树莓派后运行。树莓派开机后,在raspi-config中的6 Advanced Options下设置Expand Filesystem。检查用户组、用户名、wifi设置,与原系统一致。

标签: 树莓派, bash

添加新评论