树莓派备份-RaspberryBackup详解
步骤/目录:
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设置,与原系统一致。