On this page
System Drive Backup and Recovery
The system drive (NVMe ext4) is rsynced daily to a ZFS mirror (
tank/backup/system) and snapshotted, retaining 30 copies. This article covers the backup mechanism, daily checks, three-level recovery scenarios (single file → dataset rollback → bare-metal full system recovery), and lists items not included in the backup at the end. Scheduling is covered in timers-and-crons.md.
Overview
| Item | Value |
|---|---|
| Source | /(ext4,vg_sys/lv_root,898.5G)+ /boot(vfat,nvme0n1p1) |
| Target | /tank/backup/system(2×4T WD Red ZFS mirror, lz4 compression) |
| Method | rsync file-level sync (full initially, incremental thereafter) + zfs snapshot after each completion |
| Retention | Keep the most recent 30 snapshots; excess are automatically destroyed |
| Schedule | systemd timer, daily at 04:00, Persistent=true + idle IO priority |
| Script | /usr/local/sbin/backup-system.sh |
The initial full sync was executed on 2026-07-09, with the source being approximately 500G. Why not use block-level imaging: vg_sys space is fully allocated with no LVM snapshot capacity remaining; file-level backup allows direct browsing and single-file recovery, while ZFS snapshots add versioning, eliminating the need for a block-level solution.
Backup Mechanism
The script performs three tasks daily:
rsync -aHAXx --delete --numeric-ids / → $DEST/root/, then sync/boot/ → $DEST/boot/zfs snapshot tank/backup/system@<timestamp>- Clean up based on creation time, keeping only the most recent 30 snapshots
Key parameters: -x prevents crossing filesystem boundaries (automatically skipping /proc, /sys, /tank, etc., leaving mount points as empty directories); -HAX preserves hard links/ACLs/xattrs, ensuring a complete system upon restoration; exit code 24 (file disappeared during copy) is considered normal—this is inevitable with online backups.
Exclusions (only content is excluded, directories themselves are preserved):
| Path | Reason |
|---|---|
/tmp/*, /var/tmp/* | Temporary files, including portage build directories |
/var/cache/distfiles/*, /var/cache/binpkgs/* | Gentoo source/binary package caches, can be re-downloaded and recompiled |
/lost+found | ext4 metadata |
Docker data (/var/lib/docker) is included in the backup scope.
Consistency: rsync online backup is crash-consistent. There are no databases on the system drive (immich postgres is on tank), so the risk is acceptable; if stateful services are run on the system drive in the future, a dump step should be added before backup.
Daily Checks
Long-term: There is currently no alert for backup failures. You can add an
OnFailure=hook to the service to integrate with Bark push notifications (see monitoring.md).
Known Pitfalls: Non-UTF-8 Filenames
The tank pool has utf8only=on (pool creation attribute, immutable). Non-UTF-8 filenames will be rejected by ZFS, causing rsync to error with code 23, failing the entire backup and skipping the snapshot. This was encountered during the initial full sync (2026-07-09): GBK-encoded directories/shortcut names installed by Tencent games in Wine, where mkdir reported Invalid or incomplete multibyte or wide character.
Troubleshooting and Fix (convert to UTF-8 rather than excluding, for a permanent solution):
# Scan entire disk for non-UTF-8 filenames (-x is mandatory, otherwise empty matches pass)
|
# Rename one by one (GBK→UTF-8), in the problematic directory:
for; do n= || continue; [ && [ && ; done
The trigger source is running Wine Chinese game installers under a GBK locale. It is worth running a scan after installing new games; the backup log (
journalctl -u backup-system) will directly point out the problematic path withrecv_generator: mkdir ... failed.
Scenario 1: Accidental Deletion/Corruption of a Single File
Snapshots are readable online as hidden directories; simply copy back:
.zfsis not visible inls -a, but you cancdinto it directly. The latest backup (current state before snapshot) can be read directly from/tank/backup/system/root/.
Scenario 2: Bad State Synced into Backup
If accidental deletion/poisoning occurs and the backup runs before discovery, the current backup tree is already bad—no need to rollback (which would destroy newer snapshots); simply copy back from an earlier snapshot directory as in Scenario 1. Only when you confirm you want to revert the entire state:
Scenario 3: Bare-Metal Full System Recovery (System Drive Failure/Replacement)
Requires a LiveUSB with ZFS support (SystemRescue is sufficient). The entire process takes about 1–2 hours, with the bulk being HDD read-back of 400G+.
1. Rebuild Partitions and LVM (fstab uses device names, VG/LV names remain vg_sys/lv_root, no UUID dependency):
&&
&&
2. Import Pool and Restore:
3. Chroot and Reinstall Bootloader (GRUB EFI, /boot/EFI/Gentoo):
for; do ; done
4. Cleanup:
After reboot, confirm: systemctl --failed shows no anomalies, zpool status is ONLINE after zpool import tank, and the backup timer is present in systemctl list-timers.
Not Included in Backup
| Content | Status |
|---|---|
| tank pool data (photos/media/immich) | Mirror redundancy only, no second copy/offsite backup, lost if all drives fail or are accidentally deleted |
| Excluded items (/tmp, portage cache, etc.) | Rebuildable; first emerge after recovery will re-download |
| swap LV | No backup needed; rebuild with mkswap during recovery |
Long-term: Irreplaceable data like photos exists only as a single pool mirror. It is recommended to add offsite backup (restic → cloud object storage, or offsite machine
zfs send).
Related Documentation
- timers-and-crons.md — Global view of scheduled tasks
- monitoring.md — Alert system (backup failure alerts pending integration)
- server-lifecycle.md — Server lifecycle