---
title: 系统盘备份与恢复
url: https://doc.liz6.com/homelab/system-backup
locale: zh
area: homelab
tags:
- homelab
date: 2026-07-09
modified: 2026-07-09
description: 系统盘(NVMe ext4)每日 rsync 到 ZFS mirror(tank/backup/system)并打快照,保留 30 份。本文覆盖备份机制、日常检查、三级恢复场景(单文件→数据集回滚→整机裸机恢复),末尾列出不在备份范围内的内容。调度见 timers-and-crons.md。
---

# 系统盘备份与恢复

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

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

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

## 日常检查

```bash
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](monitoring.md))。

## 已知坑:非 UTF-8 文件名

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

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

```bash
# 全盘扫非 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:误删/改坏单个文件

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

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

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

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

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

```bash
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 依赖):

```bash
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. 导入池并回写**:

```bash
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`):

```bash
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. 收尾**:

```bash
umount -R /mnt/root
zpool export tank
reboot
```

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

## 不在备份范围内

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

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

## 相关文档

- [timers-and-crons.md](timers-and-crons.md) — 定时任务全局视图
- [monitoring.md](monitoring.md) — 告警体系(备份失败告警待接入)
- [server-lifecycle.md](server-lifecycle.md) — 服务器生命周期
