背景

树莓派系统备份有两种常见方式:一种是直接使用镜像工具进行整卡复制,另一种是以目录形式保存完整 Linux 根文件系统。后者的优点是灵活,可以在不同容量的 SD 卡之间迁移,也方便排除临时文件、伪文件系统和缓存内容。

本次操作的目标是:使用 Raspberry Pi 官方写盘工具先写入一个新的可启动系统,让它自动建立 SD 卡分区结构;然后保留这个分区结构,删除新系统文件,再把已有的完整系统备份同步进去。最后修改新 SD 卡的 UUID,使系统能够正常启动。

这种方式适合以下场景:

  • 已有完整的树莓派系统文件备份;
  • 不想手动重新分区;
  • 希望保留官方工具生成的启动分区和根分区结构;
  • 需要把同一套系统恢复到一张或多张新的 SD 卡上。

基本思路

整个流程可以概括为:

使用 Raspberry Pi Imager 写入基础系统
→ 保留 SD 卡分区结构
→ 挂载根分区和启动分区
→ 删除 SD 卡现有系统文件
→ rsync 还原备份文件
→ 获取新 SD 卡 UUID
→ 修改 /etc/fstab
→ 修改 /boot/firmware/cmdline.txt
→ sync 并安全卸载

其中最关键的地方是:还原文件之后,不能继续使用旧备份里的 UUID。必须把 fstabcmdline.txt 中的 UUID 改成当前 SD 卡实际分区的 UUID。

事前确认

假设:

DISK="/dev/sdX"
SRC="/path/to/raspberry-backup"
TARGET="/mnt/rpi-root"

其中:

/dev/sdX                      新 SD 卡整块设备
/path/to/raspberry-backup      已有的完整系统备份目录
/mnt/rpi-root                  临时挂载点

备份目录应当是完整的 Linux 根文件系统,顶层通常能看到:

bin
boot
etc
home
root
usr
var

如果备份使用的是较新的 Raspberry Pi OS 或 Ubuntu for Raspberry Pi,启动文件通常位于:

/boot/firmware/cmdline.txt

对应地,启动分区应挂载到:

/mnt/rpi-root/boot/firmware

第一步:用 Raspberry Pi Imager 写入基础系统

先使用 Raspberry Pi 官方写盘工具写入一个新的系统。这里的目的不是使用这个新系统本身,而是让 Imager 自动建立正确的分区结构和启动分区。

写入完成后,重新插入 SD 卡,然后确认设备名:

lsblk -o NAME,PATH,SIZE,MODEL,FSTYPE,LABEL,UUID,MOUNTPOINTS /dev/sdX

正常情况下,应该看到类似结构:

sdX
├─sdX1  vfat  system-boot
└─sdX2  ext4  writable

其中:

sdX1  启动分区,FAT32
sdX2  根分区,ext4

如果系统自动挂载了分区,需要先卸载:

sudo umount /dev/sdX1 /dev/sdX2 2>/dev/null || true

第二步:确认根分区容量

有些官方镜像写入后,根分区可能只占用了镜像原始大小,没有自动扩展到整张卡。

可以检查:

lsblk -o NAME,PATH,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINTS /dev/sdX

如果 /dev/sdX2 明显小于 SD 卡剩余空间,需要先扩展分区:

sudo parted /dev/sdX --script resizepart 2 100%
sudo partprobe /dev/sdX
sudo e2fsck -f /dev/sdX2
sudo resize2fs /dev/sdX2

再次确认:

lsblk -o NAME,PATH,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINTS /dev/sdX

此时 /dev/sdX2 应当已经扩展到接近整张 SD 卡的剩余容量。

第三步:挂载根分区

创建挂载点并挂载根分区:

sudo mkdir -p /mnt/rpi-root
sudo mount /dev/sdX2 /mnt/rpi-root

确认挂载结果:

findmnt /mnt/rpi-root

第四步:删除 SD 卡根分区现有系统文件

这一步只删除 SD 卡里的新系统文件,不会影响备份目录。

sudo find /mnt/rpi-root -mindepth 1 -maxdepth 1 -exec rm -rf --one-file-system {} +

确认根目录已清空:

ls -la /mnt/rpi-root

第五步:挂载启动分区

由于该类树莓派系统使用 /boot/firmware 结构,因此需要先创建目录,再挂载启动分区:

sudo mkdir -p /mnt/rpi-root/boot/firmware
sudo mount /dev/sdX1 /mnt/rpi-root/boot/firmware

确认挂载:

findmnt /mnt/rpi-root/boot/firmware

第六步:删除启动分区现有文件

sudo find /mnt/rpi-root/boot/firmware -mindepth 1 -exec rm -rf {} +

确认启动分区已清空:

ls -la /mnt/rpi-root/boot/firmware

第七步:同步根文件系统

设置备份源目录:

SRC="/path/to/raspberry-backup"

使用 rsync 同步根文件系统,但排除启动分区内容和伪文件系统:

sudo rsync -aAXHv --numeric-ids \
  --exclude='/boot/firmware/*' \
  --exclude='/dev/*' \
  --exclude='/proc/*' \
  --exclude='/sys/*' \
  --exclude='/tmp/*' \
  --exclude='/run/*' \
  --exclude='/mnt/*' \
  --exclude='/media/*' \
  --exclude='/lost+found' \
  "$SRC"/ /mnt/rpi-root/

这里需要注意源路径末尾的 /

"$SRC"/

这个斜杠表示复制备份目录里面的内容,而不是把整个备份目录本身复制进去。

第八步:单独同步启动分区

启动分区是 FAT32,不需要保留 Linux 权限、ACL、xattr 等属性,因此单独使用较简单的参数同步:

sudo rsync -rtv --delete \
  "$SRC"/boot/firmware/ /mnt/rpi-root/boot/firmware/

第九步:获取新 SD 卡 UUID

还原后的系统文件里通常仍然保留旧系统的 UUID,因此必须获取当前 SD 卡的新 UUID:

BOOT_UUID=$(sudo blkid -s UUID -o value /dev/sdX1)
ROOT_UUID=$(sudo blkid -s UUID -o value /dev/sdX2)

echo "BOOT_UUID=$BOOT_UUID"
echo "ROOT_UUID=$ROOT_UUID"

其中:

BOOT_UUID  启动分区 UUID
ROOT_UUID  根分区 UUID

第十步:修改 /etc/fstab

直接重写目标系统中的 fstab

sudo tee /mnt/rpi-root/etc/fstab >/dev/null <<EOF
UUID=$ROOT_UUID / ext4 defaults 0 1
UUID=$BOOT_UUID /boot/firmware vfat defaults 0 1
EOF

确认内容:

cat /mnt/rpi-root/etc/fstab

应当类似:

UUID=<ROOT_UUID> / ext4 defaults 0 1
UUID=<BOOT_UUID> /boot/firmware vfat defaults 0 1

第十一步:修改 cmdline.txt

树莓派启动时会读取 cmdline.txt 中的 root=UUID=... 参数。需要把它替换成当前根分区 UUID:

sudo sed -i -E "s#root=UUID=[^ ]+#root=UUID=$ROOT_UUID#" /mnt/rpi-root/boot/firmware/cmdline.txt

确认:

cat /mnt/rpi-root/boot/firmware/cmdline.txt

其中应该包含:

root=UUID=<ROOT_UUID>

注意:cmdline.txt 必须保持为一整行,不要手动拆成多行。

第十二步:同步并卸载

sync
sudo umount -R /mnt/rpi-root

这一步可能需要等待一段时间。大量小文件写入 SD 卡后,Linux 可能仍有写缓存需要刷入设备。syncumount 等待几分钟并不一定异常。

如果需要观察是否仍在写入,可以另开终端查看:

watch -n 2 'grep -E "Dirty|Writeback" /proc/meminfo; echo; lsblk -o NAME,PATH,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINTS /dev/sdX'

如果怀疑设备异常,可以查看内核日志:

dmesg -T | grep -Ei 'sdX|I/O error|buffer I/O|reset|fail|readonly|sense' | tail -80

安全卸载后,再确认挂载点为空:

lsblk -o NAME,PATH,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINTS /dev/sdX

如果 MOUNTPOINTS 为空,就可以拔出 SD 卡,插入树莓派启动。

关于 Raspberry Pi Imager 的异步写入提示

新版 Raspberry Pi Imager 在 Linux 下可能会尝试使用异步写入、direct I/O、zero-copy 等优化路径,以提高写入速度。如果终端中出现类似:

Async write queue failed, falling back to sync

通常表示高速写入路径失败,程序回退到普通同步写入方式继续执行。

这类提示不一定代表 SD 卡损坏。判断 SD 卡是否有问题,更应关注以下情况:

Write failed
Verify failed
I/O error
Buffer I/O error
device reset
read-only

如果 Imager 最终显示写入和验证成功,一般可以继续后续还原操作。

两张 SD 卡写入相同系统时的注意点

如果要把同一份系统备份还原到两张 SD 卡上,并用于两台完全相同的树莓派,需要注意:两台机器不能长期保持完全相同的系统身份。

同源系统可能包含相同的:

hostname
/etc/machine-id
SSH host key
DHCP 相关标识

如果两台设备同时接入同一个局域网,可能造成:

路由器中设备名称重复
SSH 主机指纹冲突
日志和监控难以区分
局域网 hostname 解析混乱

因此建议第一次启动时,一台一台开机并进行身份分离。

例如第一台:

sudo hostnamectl set-hostname rpi-main
sudo rm -f /etc/machine-id
sudo systemd-machine-id-setup
sudo rm -f /etc/ssh/ssh_host_*
sudo dpkg-reconfigure openssh-server
sudo systemctl restart ssh
sudo reboot

第二台:

sudo hostnamectl set-hostname rpi-test
sudo rm -f /etc/machine-id
sudo systemd-machine-id-setup
sudo rm -f /etc/ssh/ssh_host_*
sudo dpkg-reconfigure openssh-server
sudo systemctl restart ssh
sudo reboot

这样,两张 SD 卡虽然来自同一个备份,但两台树莓派在网络和系统层面会被识别为两台独立设备。

总结

这种还原方式的优势在于:

分区结构由 Raspberry Pi Imager 负责
系统内容由已有备份恢复
启动参数和挂载配置手动修正

核心风险点只有几个:

不要写错 SD 卡设备名
根分区容量要足够
rsync 源路径末尾要带 /
fstab 必须写入新 UUID
cmdline.txt 必须写入新 ROOT_UUID
卸载前必须等待 sync 完成
两台克隆系统首次启动后要分离身份

完成这些步骤后,SD 卡就不再是 Raspberry Pi Imager 写入的新系统,而是:

官方工具建立的可启动分区结构
+
已有系统备份的完整文件
+
当前 SD 卡实际 UUID

这是一种比整卡镜像更灵活、也比完全手动分区更稳妥的树莓派系统恢复方式。

Leave a Reply

Your email address will not be published. Required fields are marked *