本页目录

系统盘备份与恢复

系统盘(NVMe ext4)每日 rsync 到 ZFS mirror(tank/backup/system)并打快照,保留 30 份。本文覆盖备份机制、日常检查、三级恢复场景(单文件→数据集回滚→整机裸机恢复),末尾列出不在备份范围内的内容。调度见 timers-and-crons.md

方案总览

/(ext4,vg_sys/lv_root,898.5G)+ /boot(vfat,nvme0n1p1)
目标/tank/backup/system(2×4T WD Red ZFS mirror,lz4 压缩)
方式rsync 文件级同步(首次全量,后续增量)+ 每次完成后 zfs snapshot
版本保留最近 30 个快照,多余自动销毁
调度systemd timer,每日 04:00,Persistent=true + idle IO 优先级
脚本/usr/local/sbin/backup-system.sh

首次全量于 2026-07-09 执行,源约 500G。为什么不用块级镜像:vg_sys 空间已全部分配、无 LVM 快照余量;文件级备份可直接翻看、可单文件恢复,ZFS 快照又补齐了版本化,没有块级方案的必要。

备份机制

脚本每日做三件事:

  1. rsync -aHAXx --delete --numeric-ids / → $DEST/root/,再同步 /boot/ → $DEST/boot/
  2. zfs snapshot tank/backup/system@<时间戳>
  3. 按创建时间清理,只留最近 30 个快照

关键参数:-x 不跨文件系统(自动避开 /proc、/sys、/tank 等,挂载点保留为空目录);-HAX 保留硬链接/ACL/xattr,恢复出来才是完整系统;退出码 24(拷贝期间文件消失)视为正常——在线备份必然遇到。

排除项⁠(只排内容,目录本身保留):

路径原因
/tmp/*/var/tmp/*临时文件,含 portage 编译目录
/var/cache/distfiles/*/var/cache/binpkgs/*Gentoo 源码包/二进制包缓存,可重下重编
/lost+foundext4 元数据

docker 数据(/var/lib/docker)⁠在备份范围内⁠。

一致性⁠:rsync 在线备份是崩溃一致级别。系统盘上没有数据库(immich postgres 在 tank 上),风险可接受;若未来在系统盘上跑有状态服务,备份前应加 dump 步骤。

日常检查

systemctl list-timers backup-system.timer      # 下次触发时间
systemctl is-active backup-system              # activating=运行中 inactive=空闲
journalctl -u backup-system -n 50              # 运行日志
zfs list -t snapshot tank/backup/system        # 快照列表(每天应新增一个)
zfs list -o used,avail tank/backup/system      # 空间占用

长期⁠:备份失败目前无告警,可给 service 加 OnFailure= 钩子接入 Bark 推送(见 monitoring.md)。

已知坑:非 UTF-8 文件名

tankutf8only=on(建池属性,不可改),非 UTF-8 文件名会被 ZFS 拒收,rsync 报错 23,⁠整次备份失败且不打快照⁠。首次全量(2026-07-09)就踩过:Wine 里腾讯游戏装的 GBK 编码目录/快捷方式名,mkdir 报 Invalid or incomplete multibyte or wide character

排查与修复⁠(转成 UTF-8 而非排除,一劳永逸):

# 全盘扫非 UTF-8 文件名(-x 必须带,否则空匹配全过)
find / -xdev \( -path /proc -o -path /sys \) -prune -o -print 2>/dev/null | grep -avx '.*'

# 逐个转名(GBK→UTF-8),在问题目录下:
for f in *; do n=$(printf '%s' "$f" | iconv -f GBK -t UTF-8 2>/dev/null) || continue; [ -n "$n" ] && [ "$n" != "$f" ] && mv -n "$f" "$n"; done

触发源头是 GBK locale 下跑 Wine 中文游戏安装器,以后装新游戏后值得跑一次扫描;备份日志(journalctl -u backup-system)里的 recv_generator: mkdir ... failed 会直接指出问题路径。

场景 1:误删/改坏单个文件

快照以隐藏目录形式在线可读,直接拷回:

ls /tank/backup/system/.zfs/snapshot/                # 列出所有时间点
cp -a /tank/backup/system/.zfs/snapshot/20260709-0400/root/etc/foo.conf /etc/foo.conf

.zfsls -a 里不可见,但可以直接 cd 进去。最新一次备份(未快照的当前状态)在 /tank/backup/system/root/ 直接读。

场景 2:坏状态被同步进了备份

误删/中毒后备份先于发现执行,当前备份树已是坏的——不用 rollback(会销毁更新的快照),直接从更早的快照目录按场景 1 拷回即可。只有确认要整体退回时才:

zfs rollback -r tank/backup/system@<更早快照>   # -r 销毁其后所有快照,慎用

场景 3:整机裸机恢复(系统盘损坏/更换)

需要带 ZFS 支持的 LiveUSB(SystemRescue 即可)。全程约 1–2 小时,大头是 HDD 读回 400G+。

1. 重建分区与 LVM(fstab 用设备名,VG/LV 名保持 vg_sys/lv_root 即可,无 UUID 依赖):

parted /dev/nvme0n1 -- mklabel gpt \
  mkpart ESP fat32 1MiB 1GiB set 1 esp on \
  mkpart lvm 1GiB 100%
mkfs.vfat -F32 /dev/nvme0n1p1
pvcreate /dev/nvme0n1p2
vgcreate vg_sys /dev/nvme0n1p2
lvcreate -L 32G -n lv_swap vg_sys && mkswap /dev/vg_sys/lv_swap
lvcreate -l 100%FREE -n lv_root vg_sys && mkfs.ext4 /dev/vg_sys/lv_root

2. 导入池并回写⁠:

zpool import -o readonly=on tank        # 只读导入,保护备份
mount /dev/vg_sys/lv_root /mnt/root
rsync -aHAX /tank/backup/system/root/ /mnt/root/
mount /dev/nvme0n1p1 /mnt/root/boot
rsync -a /tank/backup/system/boot/ /mnt/root/boot/

3. chroot 重装引导⁠(GRUB EFI,/boot/EFI/Gentoo):

for d in dev proc sys; do mount --rbind /$d /mnt/root/$d; done
chroot /mnt/root /bin/bash
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=Gentoo
grub-mkconfig -o /boot/grub/grub.cfg
exit

4. 收尾⁠:

umount -R /mnt/root
zpool export tank
reboot

重启后确认:systemctl --failed 无异常、zpool import tankzpool status ONLINE、systemctl list-timers 备份 timer 在位。

不在备份范围内

内容现状
tank 池数据(photos/media/immich)仅 mirror 冗余,⁠无第二副本/异地备份⁠,盘全坏或误删即丢
排除项(/tmp、portage 缓存等)可重建,恢复后首次 emerge 会重新下载
swap LV无需备份,恢复时 mkswap 重建

长期⁠:photos 等不可再生数据只有单池 mirror 一份,建议补异地(restic → 云对象存储,或异地机器 zfs send)。

相关文档