树莓派之 SD 卡备份与还原

前言

本文主要介绍以最小的方式完整备份树莓派SD卡镜像的方法。

准备工作

在树莓派的终端里运行下面这条命令,安装必备工具:

1
$ sudo aptitude install dosfstools dump parted kpartx screen exfat-fuse fuse-utils ntfs-3g

说明:

dosfstools:FAT32分区格式化工具。

dump:dump 和 restore 备份工具。

screen:ssh远程会话管理工具。

partedkpartx:虚拟磁盘工具。

exfat-fuse:挂载exFAT格式优盘工具。

fuse-utilsntfs-3g:挂载NTFS格式优盘工具。

优盘编号

备份需要一个能够装下整个树莓派镜像的优盘或移动硬盘,在终端中运行 sudo fdisk -l,确认已经插上的优盘或移动硬盘编号为 /dev/sda1。若不是则记下具体编号,待会修改脚本中内容。

脚本配置

备份镜像文件名,修改 raspberrypi.img 即可:

1
IMG_PATH="${USB_PATH}/raspberrypi.img"

挂载优盘,修改 /dev/sda1 为具体的编号:

1
sudo mount -o uid=pi,gid=pi /dev/sda1 ${USB_PATH}

生成镜像文件大小,默认4G=4096×1048576字节(b)。想要生成?G,只需要修改count的值即可,count=?×1024×1024×1024÷4096:

1
sudo dd if=/dev/zero of=${IMG_PATH} bs=4096 count=1048576

boot分区卷标:

1
sudo dosfslabel ${bootPath} "rpi-boot"

系统分区卷标:

1
sudo e2label ${rootPath} "rpi-sys"

备份脚本

将以下内容保存为 backup.sh 文件,并赋予执行权限 sudo chmod +x backup.sh。在终端中输入 screen -S backup 创建backup会话,找到 backup.sh 文件所在位置执行 ./backup.sh 开始备份树莓派的SD卡全部内容,备份后的镜像文件 raspberrypi.img 保存在优盘或移动硬盘的根目录下。最后使用快捷键Ctrl+a+d(即按住Ctrl,依次再按a,d)离开backup会话。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/bin/sh


USB_PATH="/mnt/usb"
IMG_PATH="${USB_PATH}/raspberrypi.img"
TMP_BOOT="/mnt/tmp_boot"
TMP_ROOT="/mnt/tmp_root"


if [ ! -d ${USB_PATH} ]; then
sudo mkdir ${USB_PATH}
fi

sudo mount -o uid=pi,gid=pi /dev/sda1 ${USB_PATH}

if [ -f ${IMG_PATH} ]; then
sudo rm -f ${IMG_PATH}
fi

sudo dd if=/dev/zero of=${IMG_PATH} bs=4096 count=1048576

sudo parted -s ${IMG_PATH} -- mklabel msdos
sudo parted -s ${IMG_PATH} -- mkpart primary fat32 8192s 122879s
sudo parted -s ${IMG_PATH} -- mkpart primary ext4 122880s -1
sudo parted -s ${IMG_PATH} print

loopDev=`sudo losetup -f --show ${IMG_PATH}`
devPath=`sudo kpartx -va ${loopDev} | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1`
devPath="/dev/mapper/${devPath}"

bootPath="${devPath}p1"
sudo mkfs.vfat ${bootPath}
sudo dosfslabel ${bootPath} "rpi-boot"

rootPath="${devPath}p2"
sudo mkfs.ext4 ${rootPath}
sudo e2label ${rootPath} "rpi-sys"

if [ ! -d ${TMP_BOOT} ]; then
sudo mkdir ${TMP_BOOT}
fi

sudo umount ${TMP_BOOT}
sudo mount -t vfat ${bootPath} ${TMP_BOOT}
sudo cp -rfp /boot/* ${TMP_BOOT}/
sudo umount ${TMP_BOOT}

if [ ! -d ${TMP_ROOT} ]; then
sudo mkdir ${TMP_ROOT}
fi

sudo umount ${TMP_ROOT}
sudo mount -t ext4 ${rootPath} ${TMP_ROOT}
cd ${TMP_ROOT}
sudo dump -0uaf - / | sudo restore -rf -
cd ~
sudo umount ${TMP_ROOT}

sudo kpartx -d ${loopDev}
sudo losetup -d ${loopDev}

sudo umount ${USB_PATH}
#sudo rm -rf ${TMP_BOOT}
#sudo rm -rf ${TMP_ROOT}

还原镜像

Windows下还原镜像可以使用以下工具: Win32DiskImagerUSB Image Tool(推荐),HDDRawCopy(可以进一步压缩img镜像文件)。

新系统运行

还原后的新系统第一次运行记得执行以下命令,然后选择 Expand Filesystem 扩展新SD卡未使用的空间。

1
$ sudo raspi-config

脚本说明

镜像文件分区

8192s122879s 是根据官方镜像文件分区得到的数据,也就是第一个boot分区大小。-1 代表直到镜像文件结尾。

1
2
3
4
sudo parted -s ${IMG_PATH} -- mklabel msdos
sudo parted -s ${IMG_PATH} -- mkpart primary fat32 8192s 122879s
sudo parted -s ${IMG_PATH} -- mkpart primary ext4 122880s -1
sudo parted -s ${IMG_PATH} print

挂载并格式化虚拟磁盘

1
2
3
4
5
6
7
8
9
10
11
loopDev=`sudo losetup -f --show ${IMG_PATH}`
devPath=`sudo kpartx -va ${loopDev} | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1`
devPath="/dev/mapper/${devPath}"

bootPath="${devPath}p1"
sudo mkfs.vfat ${bootPath}
sudo dosfslabel ${bootPath} "rpi-boot"

rootPath="${devPath}p2"
sudo mkfs.ext4 ${rootPath}
sudo e2label ${rootPath} "rpi-sys"

备份boot分区

1
2
3
4
5
6
7
8
if [ ! -d ${TMP_BOOT} ]; then
sudo mkdir ${TMP_BOOT}
fi

sudo umount ${TMP_BOOT}
sudo mount -t vfat ${bootPath} ${TMP_BOOT}
sudo cp -rfp /boot/* ${TMP_BOOT}/
sudo umount ${TMP_BOOT}

备份系统分区

使用 dumprestore 工具配合备份系统:

1
2
3
4
5
6
7
8
9
10
if [ ! -d ${TMP_ROOT} ]; then
sudo mkdir ${TMP_ROOT}
fi

sudo umount ${TMP_ROOT}
sudo mount -t ext4 ${rootPath} ${TMP_ROOT}
cd ${TMP_ROOT}
sudo dump -0uaf - / | sudo restore -rf -
cd ~
sudo umount ${TMP_ROOT}

卸载虚拟磁盘

1
2
sudo kpartx -d ${loopDev}
sudo losetup -d ${loopDev}

修改镜像文件

明白了以上脚本内容,以后我们想要修改镜像文件里的内容可以使用以下的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/sh

USB_PATH="/mnt/usb"
IMG_PATH="${USB_PATH}/raspberrypi.img"
BOOT_PATH="/mnt/tmp_boot"
ROOT_PATH="/mnt/tmp_root"

if [ ! -d ${USB_PATH} ]; then
sudo mkdir ${USB_PATH}
fi

sudo mount -o uid=pi,gid=pi /dev/sda1 ${USB_PATH}

loopDev=`sudo losetup -f --show ${IMG_PATH}`
devPath=`sudo kpartx -va ${loopDev} | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1`
devPath="/dev/mapper/${devPath}"

bootPath="${devPath}p1"

if [ ! -d ${BOOT_PATH} ]; then
sudo mkdir ${BOOT_PATH}
fi

sudo umount ${BOOT_PATH}
sudo mount -t vfat ${bootPath} ${BOOT_PATH}

rootPath="${devPath}p2"

if [ ! -d ${ROOT_PATH} ]; then
sudo mkdir ${ROOT_PATH}
fi

sudo umount ${ROOT_PATH}
sudo mount -t ext4 ${rootPath} ${ROOT_PATH}

echo "bootPath: ${BOOT_PATH}"
echo "rootPath: ${ROOT_PATH}"

echo "sudo umount ${BOOT_PATH}"
echo "sudo umount ${ROOT_PATH}"

echo "sudo kpartx -d ${loopDev}"
echo "sudo losetup -d ${loopDev}"

echo "sudo umount ${USB_PATH}"

修改完成之后记得执行最后那几行打印的命令,卸载虚拟磁盘,保存结果。