GELI Encrypted USB Backup of ZFS Filesystems in FreeBSD 13
Posted 2021-12-22, edited 2021-12-23.
First off, this is mostly the result of two pages I found elsewhere on the 'Net.
I reference manual pages throughout. If you're new-ish to Unix, you'll see
references like "intro(1)": a reference to a manual page (or manpage) and the
section number it's in. To read intro(1), for example, type man 1 intro
at
a shell prompt. Type man man
to read how to use your system's manual.
Do a lot of reading and thinking as you follow along. Before you try out any
commands for yourself, read their manpages and make sure you understand what
my commands did and why. Have an xterm or other terminal window sitting next
to this browser window.
Steps
- Reasoning and System Installation to Back Up
- Assumptions and Preparation
- Choosing ZFS Filesystems to Snapshot Automatically
- Enabling Automatic ZFS Filesystem Snapshotting
- Preparing the Backup Drive
- Performing the Backup for the First Time
- Properly Performing a Backup
- Adding a New Computer to the Backup Drive
- Scripting the Regular Backup
- Conclusion
Reasoning and System Installation to Back Up
My main home computer started out running FreeBSD 11, and I upgraded it to 12 before it came time to replace the hardware. I initially partitioned the HDD using the guided ZFS with encryption in the FreeBSD installer. However, I didn't know how to do proper encrypted backups, so I wound up making and (poorly) managing tarballs of files I didn't want to lose.
The reason I chose full disk encryption is because I use my main computer for sensitive activities, and if it gets stolen, I don't want to worry about the thief stealing my identity and ruining my life in addition. Without my backup drives also being encrypted, I ran that add-on risk from having a backup lost or stolen.
When it came time to replace my computer, I chose an all-in-one with the opposite size HDD and a laptop with an NVMe instead of spinning platters. Because of this, I had to do fresh installs of FreeBSD 13 and muck about with those tarballs. I didn't lose any data, but I did lose an awful lot of time that I didn't need to. Anyway, with the new computers came new USB drives to use for backup and a new determination to do it better if not do it right.
I named my computers "swiftpaw" and "braveplumes," and you'll see their names in the shell prompts prefacing every command.
Assumptions and Preparation
I want to have ZFS snapshots made automatically and covering reasonable stretches of time so that clobbered or deleted files can be recovered when the clobbering is discovered. I want to send those snapshots to my backup drive so that I can recover files, filesystems, or the whole pool of data if I ever need to. At the same time, I want those snapshots managed automatically and old snapshots culled to avoid filling up either main or backup disks. ZFS snapshots are also easier to back up because they're basically a moment of the filesystem frozen in time, and I won't need to worry about files changing or disappearing mid-backup while the snapshot persists.
The most important assumption is that this is the start of a new backup regimen, no snapshots worth keeping exist in the ZFS pool being backed up, and that any backup regimen already in place will be abandoned in favor of this one. Since I was using huge and poorly managed tarballs before, I'm pretty happy to abandon it for snapshot shuttling.
The backup regimen I've adopted needs the following at minimum:
- A blank USB storage device at least as large as the ZFS pool being backed up. The USB drive is going to be repartitioned and reformatted, so don't use a USB drive with anything saved on it.
- The sysutils/zfstools package, installed from either repo or ports. This
package has the
zfs-auto-snapshot
tool that will create and manage ZFS snapshots. - The sysutils/zxfer package, installed from either repo or ports. This package is a robust shellscript that zfs-send(8)s snapshots and zfs-receive(8)s them into the USB drive.
I'm going to use one USB storage device to back up both of my computers. That bumps up the requirement to a storage device at least as big as both computers' ZFS pools combined.
A good backup maintenance policy involves, when possible, backups stored on multiple physical storage media and rotation of those media through on-site and off-site storage locations. That bumps up the number of USB drives for backup from one to two or more. I'll get more as my day job allows, but this guide assumes just one drive, and adding more drives is as simple as repeating these steps for each.
Choosing ZFS Filesystems to Snapshot Automatically
Look at the ZFS filesystems in your pool (see zpool-list(8) and zfs-list(8)), decide which ones hold data you can't easily replace, and decide roughly how frequently their files change. When I installed FreeBSD 13, it gave me these filesystems in my pool:
- zroot/ROOT/default
- zroot/tmp
- zroot/usr/home
- zroot/usr/ports
- zroot/usr/src
- zroot/var/audit
- zroot/var/crash
- zroot/var/log
- zroot/var/mail
- zroot/var/tmp
(It also gave me zroot, zroot/ROOT, zroot/usr, and zroot/var, but these are basically containers for children filesystems, not used directly for data storage.)
Of these, the ones I either can't replace or can't easily replace are:
Filesystem | Reason |
---|---|
zroot/ROOT/default | Configs and root's home directory. |
zroot/usr/home | My home directory lives here. |
zroot/var/log | System logs to figure out what went wrong. |
zroot/var/mail | Mail spools for me and root. |
That means the ones that don't need to be snapshotted are the ones left:
Filesystem | Reason Why Not |
---|---|
zroot/tmp | Temporary files, cleaned at reboot. |
zroot/usr/ports | I can just portsnap(8) a new tree. |
zroot/usr/src | Can get again by following the Handbook. |
zroot/var/audit | Audit logging not enabled by default. |
zroot/var/crash | Don't need kernel core dumps snapshotted. |
zroot/var/tmp | Don't need temporary files snapshotted. |
Of course, your needs and levels of importance may be different, so look at your pool and make your own decisions.
Enabling Automatic ZFS Filesystem Snapshotting
Zfstools looks at the ZFS user property com.sun:auto-snapshot
to determine
whether to snapshot a filesystem or not. On a new system, the property should
be unset, neither true nor false, neither local nor inherited. Use
zfs get com.sun:autosnapshot
to see its status for all ZFS filesystems, and
see zfs-get(8) for details.
Set the ZFS property com.sun:auto-snapshot
to false on ZFS filesystems
zfstools shouldn't snapshot for you. In my case, the command looked like:
root@braveplumes:~ # zfs set com.sun:autosnapshot=false \
zroot/tmp \
zroot/usr/ports \
zroot/usr/src \
zroot/var/audit \
zroot/var/
root@braveplumes:~ #
See zfs-set(8), zfs-inherit(8), and the "User Properties" section of zfsprops(8) for details.
Once you have filesystems set as excluded, turn on snapshotting for the rest of the pool. I used:
root@braveplumes:~ # zfs set com.sun:auto-snapshot=true zroot
root@braveplumes:~ #
Now it looked like:
root@braveplumes:~ # zfs get -r com.sun:auto-snapshot zroot
NAME PROPERTY VALUE SOURCE
zroot com.sun:auto-snapshot true local
zroot/ROOT com.sun:auto-snapshot true inherited from zroot
zroot/ROOT/default com.sun:auto-snapshot true inherited from zroot
zroot/tmp com.sun:auto-snapshot false local
zroot/usr com.sun:auto-snapshot true inherited from zroot
zroot/usr/home com.sun:auto-snapshot true inherited from zroot
zroot/usr/ports com.sun:auto-snapshot false local
zroot/usr/src com.sun:auto-snapshot false local
zroot/var com.sun:auto-snapshot true inherited from zroot
zroot/var/audit com.sun:auto-snapshot false local
zroot/var/crash com.sun:auto-snapshot false local
zroot/var/log com.sun:auto-snapshot true inherited from zroot
zroot/var/mail com.sun:auto-snapshot true inherited from zroot
zroot/var/tmp com.sun:auto-snapshot false local
root@braveplumes:~ #
Now read /usr/local/share/doc/zfstools/README.md
if you haven't already.
Zfstools expects to run as a cron job, and the documentation provides example
crontab(5) lines. The "INTERVAL" names it offers as examples are frequent,
hourly, daily, weekly, and monthly, but these are free-form names solely for
your benefit. You can choose any name you want for each interval, any number
of intervals you want, and any times those intervals will run.
I like all the defaults, but I decided to change the names. Instead of frequent, hourly, daily, weekly, and monthly, I chose the 4-character names 015m, 1hly, 2dly, 3wky, and 4mth respectively because I'm weird like that. So the crontab I installed looks like this:
SHELL=/bin/sh
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
15,30,45 * * * * /usr/local/sbin/zfs-auto-snapshot 015m 4
0 * * * * /usr/local/sbin/zfs-auto-snapshot 1hly 24
7 0 * * * /usr/local/sbin/zfs-auto-snapshot 2dly 7
14 0 * * 7 /usr/local/sbin/zfs-auto-snapshot 3wky 4
28 0 1 * * /usr/local/sbin/zfs-auto-snapshot 4mth 12
Edit: After some thinking, I realized my laptop won't be turned on during the daily, weekly, or monthly runs, so I modified its crontab to have just three intervals (every 15 minutes, every hour, and at each reboot) instead. I named the new interval "boot":
SHELL=/bin/sh
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
# zfstools automatic snapshotting
15,30,45 * * * * /usr/local/sbin/zfs-auto-snapshot 015m 4
0 * * * * /usr/local/sbin/zfs-auto-snapshot 1hly 24
@reboot /usr/local/sbin/zfs-auto-snapshot boot 14
Don't forget to set the $PATH right. Zfstools is a collection of ruby scripts, and weird things happen if env(1) can't find ruby. If cron starts logging or mailing you weird errors like this, check your $PATH.
/usr/local/lib/ruby/site_ruby/2.7/zfstools/dataset.rb:29:in `popen': No such file or directory - zfs (Errno::ENOENT)
from /usr/local/lib/ruby/site_ruby/2.7/zfstools/dataset.rb:29:in `list'
from /usr/local/lib/ruby/site_ruby/2.7/zfstools.rb:132:in `find_eligible_datasets'
from /usr/local/sbin/zfs-auto-snapshot:65:in `<main>'
If all goes right, you'll start seeing snapshots like
zroot/usr/home@zfs-auto-snap_1hly-2021-12-16-12h00
when you run
zfs list -t snap zroot/usr/home
.
Zfstools also lets you exclude filesystems from interval-specific auto
snapshotting without excluding it from other intervals. To do this, set
the ZFS property com.sun:auto-snapshot:INTERVAL
to false, where "INTERVAL" is
the interval name. For example, I turned off the "015m" interval on
zroot/ROOT and zroot/ROOT/default with the command:
root@braveplumes:~ # zfs set com.sun:auto-snapshot:015m=false zroot/ROOT
root@braveplumes:~ #
Preparing the Backup Drive
Now to turn the backup drive into a GELI-encrypted partition containing ZFS pools.
Plug in the new, unprepared backup drive, then run dmesg
to find the device
node name. If plugging in the drive is the most recent thing you did, then
information about it should be at the end of the dump.
On my system, the node name is da0. Double-check you get yours
right, because the first step is destroying its partition table.
For the rest of the preparation examples, I set some variables to help me out. My root shell is tcsh, not the /bin/sh default of users, so I had to use the "set" keyword:
root@braveplumes:~ # set thishost=`hostname -s`
root@braveplumes:~ # set thispool="zroot"
root@braveplumes:~ # set backupdev="da0"
root@braveplumes:~ #
Once again, make sure you change "da0" to your backup drive's actual device node name! Data you want to keep will be deleted in the next step if you don't use your backup drive's actual device node name!
I listed the partition table to verify what I was about to destroy looked right, then I destroyed it and verified it:
root@braveplumes:~ # gpart show $backupdev
=> 63 240353217 da0 MBR (115G)
63 240353217 - free - (115G)
root@braveplumes:~ # gpart destroy -F $backupdev
da0 destroyed
root@braveplumes:~ # gpart destroy -F $backupdev
gpart: arg0 'da0': Invalid argument
root@braveplumes:~ #
Then I created a GPT partition table with the label "backup" for the ZFS partition:
root@braveplumes:~ # gpart create -s gpt $backupdev
da0 created
root@braveplumes:~ # gpart add -a 1m -l backup -t freebsd-zfs $backupdev
da0p1 added
root@braveplumes:~ # gpart show -l $backupdev
=> 40 240353200 da0 GPT (115G)
40 2008 - free - (1.0M)
2048 240349184 1 backup (115G)
240351232 2008 - free - (1.0M)
root@braveplumes:~ #
Next, I encrypted and attached the new partition, using what the FreeBSD installer used as a cheat sheet. See the "init" command of geli(8) for what options I used, didn't use, and why.
root@braveplumes:~ # grep "geli init" /var/log/bsdinstall_log
DEBUG: zfs_create_boot: geli init -bg -e AES-XTS -J - -l 256 -s 4096 "nvd0p4"
root@braveplumes:~ # geli init -e AES-XTS -l 256 -s 4096 "/dev/gpt/backup"
Enter new passphrase:
Reenter new passphrase:
Metadata backup for provider /dev/gpt/backup can be found in /var/backups/gpt_backup.eli
and can be restored with the following command:
# geli restore /var/backups/gpt_backup.eli /dev/gpt/backup
root@braveplumes:~ # geli attach /dev/gpt/backup
Enter passphrase:
root@braveplumes:~ # geli status /dev/gpt/backup.eli
Name Status Components
gpt/backup.eli ACTIVE gpt/backup
root@braveplumes:~ #
Once the partition was attached, I created the ZFS pool for the backup, then a new dataset inside "backup" for the backup of my computer named braveplumes. I could've used the "backup" pool directly, but the backup drive I'm using is big enough to hold backups for both of my computers, so that means a dataset inside for each computer.
root@braveplumes:~ # zpool create backup gpt/backup.eli
root@braveplumes:~ # zfs create backup/$thishost
root@braveplumes:~ # zpool list backup
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
backup 114G 528K 114G - - 0% 0% 1.00x ONLINE -
root@braveplumes:~ # zfs list -r backup
NAME USED AVAIL REFER MOUNTPOINT
backup 528K 110G 96K /backup
backup/braveplumes 96K 110G 96K /backup/braveplumes
root@braveplumes:~ #
The final bit of preparation is backing out of the backup drive, removing it, then reinserting and getting back into it to make sure I can:
root@braveplumes:~ # zpool export backup
root@braveplumes:~ # geli detach gpt/backup.eli
root@braveplumes:~ #
At this point, it was safe to unplug the backup drive. Unplug, count to ten, plug in, then:
root@braveplumes:~ # geli attach /dev/gpt/backup
Enter passphrase:
root@braveplumes:~ # zpool import -N backup
root@braveplumes:~ # sh -c 'if zfs list backup/`hostname -s` >/dev/null; then echo true; else echo false; fi'
true
root@braveplumes:~ # zpool export backup
root@braveplumes:~ # geli detach gpt/backup.eli
root@braveplumes:~ #
Performing the Backup for the First Time
At last, it's time to actually perform the backup. This first one is going to take a very long time because it's a full backup, not an incremental from an existing snapshot to the next.
What I did resulted in some mistakes that you're likely to encounter as well, so keep that in mind as you follow along. After this first backup, I developed a more refined backup procedure to follow the next time.
First, I set up some variables to make the backup command easier to follow, modify, and if necessary repeat:
root@braveplumes:~ # set thishost=`hostname -s`
root@braveplumes:~ # set thispool="zroot"
root@braveplumes:~ #
Next, zxfer(8) has a -I
switch for excluding certain ZFS properties from the
backup dataset copies. I don't want zfs-auto-snapshot to snapshot, manage, and
potentially destroy the already backed up filesystems, so that (and my 015m
relative) is my first exclusion.
root@braveplumes:~ # set exclude="com.sun:auto-snapshot"
root@braveplumes:~ # set exclude="${exclude},com.sun:auto-snapshot:015m"
root@braveplumes:~ #
And now, I performed the backup. The first two examples in zxfer(8)'s examples section are almost exactly what I want; the only differences are that I don't have any snapshots to grandfather in and that I don't need multiple copies on a single backup drive. (I'd need to use a backup drive twice as big if I do want multiple copies per backup drive.)
Zxfer also has beeping options, -b
that will play a tune on failure only, and
-B
that will play one of two tunes when it finishes. These go through the
kernel speaker device /dev/speaker and can be quite loud on modern PCs.
Add one of them if there's no one around to annoy with loud beeps and you think
you'll step away and go do something else while the backup runs.
root@braveplumes:~ # geli attach /dev/gpt/backup
Enter passphrase:
root@braveplumes:~ # zpool import -N backup
root@braveplumes:~ # zxfer -dFkPv -I $exclude -R $thispool backup/$thishost
Creating destination filesystem "backup/braveplumes/zroot" with specified properties.
cannot create 'backup/braveplumes/zroot': 'objsetid' is readonly
Error when creating destination filesystem.
root@braveplumes:~ #
This is a bug in zxfer 1.1.7. The workaround is to add "objsetid" to the exclusion list. Let's try again:
root@braveplumes:~ # set exclude="${exclude},objsetid"
root@braveplumes:~ # zxfer -dFkPv -I $exclude -R $thispool backup/$thishost
Creating destination filesystem "backup/braveplumes/zroot" with specified properties.
cannot create 'backup/braveplumes/zroot': minimum pbkdf2 iterations is 100000
Error when creating destination filesysetm.
root@braveplumes:~ #
This error was annoying, and there's barely anything on the rest of the Internet hinting at what it means. I eventually discovered that ZFS datasets can be encrypted separately from the GELI partitions they're stored on, and this is one of the encryption properties. However, none of mine are using encryption:
root@braveplumes:~ # zfs get -r encryption zroot | grep -v "@"
NAME PROPERTY VALUE SOURCE
zroot encryption off default
zroot/ROOT encryption off default
zroot/ROOT/default encryption off default
zroot/tmp encryption off default
zroot/usr encryption off default
zroot/usr/home encryption off default
zroot/usr/ports encryption off default
zroot/usr/src encryption off default
zroot/var encryption off default
zroot/var/audit encryption off default
zroot/var/crash encryption off default
zroot/var/log encryption off default
zroot/var/mail encryption off default
zroot/var/tmp encryption off default
root@braveplumes:~ #
The workaround is to exclude three ZFS encryption properties:
root@braveplumes:~ # set exclude="${exclude},keylocation"
root@braveplumes:~ # set exclude="${exclude},keyformat"
root@braveplumes:~ # set exclude="${exclude},pbkdf2iters"
root@braveplumes:~ #
I tried again and got success, only to be black flagged on the last lap:
root@braveplumes:~ # zxfer -dFkPv -I $exclude -R $thispool backup/$thishost
Creating destination filesystem "backup/braveplumes/zroot" with specified properties.
Sending zroot@zfs-auto-snap_1hly-2021-12-20-07h00 to backup/braveplumes/zroot.
Sending zroot@zfs-auto-snap_015m-2021-12-20-07h45 to backup/braveplumes/zroot.
(incremental to zroot@zfs-auto-snap_1hly-2021-12-20-07h00.)
Creating destination filesystem "backup/braveplumes/zroot/ROOT" with specified properties.
Creating destination filesystem "backup/braveplumes/zroot/ROOT/default" with specified properties.
Sending zroot/ROOT/default@zfs-auto-snap_1hly-2021-12-18-18h00 to backup/braveplumes/zroot/ROOT/default.
Sending zroot/ROOT/default@zfs-auto-snap_1hly-2021-12-18-19h00 to backup/braveplumes/zroot/ROOT/default.
(incremental to zroot/ROOT/default@zfs-auto-snap_1hly-2021-12-18-18h00.)
Sending zroot/ROOT/default@zfs-auto-snap_1hly-2021-12-19-12h00 to backup/braveplumes/zroot/ROOT/default.
(incremental to zroot/ROOT/default@zfs-auto-snap_1hly-2021-12-18-19h00.)
:
[similar snapshot sending statuses snipped]
:
Creating destination filesystem "backup/braveplumes/zroot/tmp" with specified properties.
Creating destination filesystem "backup/braveplumes/zroot/usr" with specified properties.
Creating destination filesystem "backup/braveplumes/zroot/usr/home" with specified properties.
Sending zroot/usr/home@zfs-auto-snap_1hly-2021-12-18-19h00 to backup/braveplumes/zroot/usr/home.
Sending zroot/usr/home@zfs-auto-snap_1hly-2021-12-19-12h00 to backup/braveplumes/zroot/usr/home.
(incremental to zroot/usr/home@zfs-auto-snap_1hly-2021-12-18-19h00.)
Sending zroot/usr/home@zfs-auto-snap_1hly-2021-12-19-13h00 to backup/braveplumes/zroot/usr/home.
(incremental to zroot/usr/home@zfs-auto-snap_1hly-2021-12-19-12h00.)
:
[similar snapshot sending statuses snipped]
:
Creating destination filesystem "backup/braveplumes/zroot/usr/ports" with specified properties.
Creating destination filesystem "backup/braveplumes/zroot/usr/src" with specified properties.
Creating destination filesystem "backup/braveplumes/zroot/var" with specified properties.
Creating destination filesystem "backup/braveplumes/zroot/var/audit" with specified properties.
Creating destination filesystem "backup/braveplumes/zroot/var/crash" with specified properties.
Creating destination filesystem "backup/braveplumes/zroot/var/log" with specified properties.
Sending zroot/var/log@zfs-auto-snap_1hly-2021-12-18-18h00 to backup/braveplumes/zroot/var/log.
Sending zroot/var/log@zfs-auto-snap_1hly-2021-12-18-19h00 to backup/braveplumes/zroot/var/log.
(incremental to zroot/var/log@zfs-auto-snap_1hly-2021-12-18-18h00.)
Sending zroot/var/log@zfs-auto-snap_1hly-2021-12-19-12h00 to backup/braveplumes/zroot/var/log.
(incremental to zroot/var/log@zfs-auto-snap_1hly-2021-12-18-19h00.)
:
[similar snapshot sending statuses snipped]
:
Creating destination filesystem "backup/braveplumes/zroot/var/mail" with specified properties.
Sending zroot/var/mail@zfs-auto-snap_1hly-2021-12-20-07h00 to backup/braveplumes/zroot/var/mail.
Error when zfs send/receiving.
root@braveplumes:~ #
Well, that was annoying. The command ran through the top of the hour, and the snapshot I was backing up was culled mid-backup. As annoying as that was, I kind of expected it. The fix is to re-run the backup:
root@braveplumes:~ # zxfer -dFkPv -I $exclude -R $thispool backup/$thishost
Destroying destination snapshot backup/braveplumes/zroot@zfs-auto-snap_1hly-2021-12-20-07h00.
Sending zroot@zfs-auto-snap_1hly-2021-12-20-08h00 to backup/braveplumes/zroot.
(incremental to zroot@zfs-auto-snap_015m-2021-12-20-07h45.)
Sending zroot/ROOT/default@zfs-auto-snap_1hly-2021-12-20-08h00 to backup/braveplumes/zroot/ROOT/default.
(incremental to zroot/ROOT/default@zfs-auto-snap_1hly-2021-12-20-07h00.)
Sending zroot/usr/home@zfs-auto-snap_1hly-2021-12-20-08h00 to backup/braveplumes/zroot/usr/home.
(incremental to zroot/usr/home@zfs-auto-snap_015m-2021-12-20-07h45.)
Sending zroot/var/log@zfs-auto-snap_1hly-2021-12-20-08h00 to backup/braveplumes/zroot/var/log.
(incremental to zroot/var/log@zfs-auto-snap_015m-2021-12-20-07h45.)
Sending zroot/var/mail@zfs-auto-snap_1hly-2021-12-20-08h00 to backup/braveplumes/zroot/var/mail.
Creating destination filesystem "backup/braveplumes/zroot/var/tmp" with specified properties.
Writing backup info to location /backup/braveplumes/.zxfer_backup_info.zroot
root@braveplumes:~ #
Now that's more like it. Now if I zfs mount backup/${thishost}/somefilesystem
and less /backup/${thishost}/somefilesystem/.zfs/snapshot/zfs-auto-snap_1hly-2021-12-18-18h00/somefile
,
I'll see the contents of that file if it existed in zroot/somefilesystem
at that time. (Use zfs unmount backup/${thishost}/somefilesystem
to unmount
it after you're done with it.)
Edit: Oops. Zxfer wrote some metadata to
/backup/${thishost}/.zxfer_backup_info.zroot under the assumption it would be a
directory in the backup pool. It wasn't, because the -N
switch on
zpool-import(8) prevented its filesystems from being mounted. I should've
zfs-mount(8)ed backup/${thishost} before running zxfer. I already did that
in my backup script, but I forgot to point it out below or do that in my trials
above.
We could (and I did) scrub the backup drive to make sure everything's there and
good. This will take at least as long as the first backup because it's every
byte of every dataset in the backup pool. I added the -w
switch to wait,
knowing it was done when I got the shell prompt back.
root@braveplumes:~ # zpool scrub -w backup
root@braveplumes:~ #
Since we're done with the first backup attempts, let's remove the backup drive and prepare for an actual backup:
root@braveplumes:~ # zpool export backup
root@braveplumes:~ # geli detach /dev/gpt/backup.eli
root@braveplumes:~ #
Properly Performing a Backup
Set up the variables for the zxfer command. (I'm going to script this later.)
root@braveplumes:~ # set thishost=`hostname -s`
root@braveplumes:~ # set thispool="zroot"
root@braveplumes:~ # set exclude="com.sun:auto-snapshot"
root@braveplumes:~ # set exclude="${exclude},com.sun:auto-snapshot:015m"
root@braveplumes:~ # set exclude="${exclude},objsetid"
root@braveplumes:~ # set exclude="${exclude},keyformat"
root@braveplumes:~ # set exclude="${exclude},keylocation"
root@braveplumes:~ # set exclude="${exclude},pbkdf2iters"
root@braveplumes:~ #
Plug in the backup drive, then attach it (edit: then mount the backup root):
root@braveplumes:~ # geli attach /dev/gpt/backup
Enter passphrase:
root@braveplumes:~ # zpool import -N backup
root@braveplumes:~ # zfs mount "backup/${thishost}"
root@braveplumes:~ #
Perform the backup:
root@braveplumes:~ # zxfer -dFkPv -I ${exclude} -R ${thispool} backup/${thishost}
[output cut]
root@braveplumes:~ #
Detach the backup drive, then unplug it when the last shell prompt reappears. (Edited as noted above.)
root@braveplumes:~ # zfs unmount "backup/${thishost}"
root@braveplumes:~ # zpool export backup
root@braveplumes:~ # geli detach /dev/gpt/backup.eli
root@braveplumes:~ #
Adding a New Computer to the Backup Drive
I'm going to use my backup drive to back up both of my computers, since it's big enough for both. All I needed to do is plug the backup drive into my second computer and do a very simple preparation:
root@swiftpaw:~ # set thishost=`hostname -s`
root@swiftpaw:~ # set thispool="zroot"
root@swiftpaw:~ # geli attach /dev/gpt/backup
Enter passphrase:
root@swiftpaw:~ # zpool import -N backup
root@swiftpaw:~ # zfs create backup/$thishost
root@swiftpaw:~ # zpool list backup
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
backup 114G 2.75G 111G - - 0% 2% 1.00x ONLINE -
root@swiftpaw:~ # zfs list -r backup
NAME USED AVAIL REFER MOUNTPOINT
backup 2.75G 108G 96K /backup
backup/braveplumes 2.75G 108G 96K /backup/braveplumes
backup/braveplumes/zroot 2.75G 108G 96K /backup/braveplumes/zroot
backup/braveplumes/zroot/ROOT 1.92G 108G 96K /backup/braveplumes/zroot/ROOT
backup/braveplumes/zroot/ROOT/default 1.92G 108G 1.91G /backup/braveplumes/zroot/ROOT/default
backup/braveplumes/zroot/tmp 96K 108G 96K /backup/braveplumes/zroot/tmp
backup/braveplumes/zroot/usr 851M 108G 96K /backup/braveplumes/zroot/usr
backup/braveplumes/zroot/usr/home 850M 108G 847M /backup/braveplumes/zroot/usr/home
backup/braveplumes/zroot/usr/ports 96K 108G 96K /backup/braveplumes/zroot/usr/ports
backup/braveplumes/zroot/usr/src 96K 108G 96K /backup/braveplumes/zroot/usr/src
backup/braveplumes/zroot/var 3.30M 108G 96K /backup/braveplumes/zroot/var
backup/braveplumes/zroot/var/audit 96K 108G 96K /backup/braveplumes/zroot/var/audit
backup/braveplumes/zroot/var/crash 96K 108G 96K /backup/braveplumes/zroot/var/crash
backup/braveplumes/zroot/var/log 2.80M 108G 448K /backup/braveplumes/zroot/var/log
backup/braveplumes/zroot/var/mail 128K 108G 128K /backup/braveplumes/zroot/var/mail
backup/braveplumes/zroot/var/tmp 96K 108G 96K /backup/braveplumes/zroot/var/tmp
backup/swiftpaw 96K 108G 96K /backup/swiftpaw
root@swiftpaw:~ # zpool export backup
root@swiftpaw:~ # geli detach gpt/backup.eli
root@swiftpaw:~ #
Next, I have to choose and enable ZFS snapshotting on the second computer just like I did on my first, potentially making different decisions, and wait for the first snapshots to be made.
Now if I run my backup commands on the second computer with the backup drive, I'll have backups of both of my computers.
Scripting the Regular Backup
I put these commands in a shellscript to make backups more convenient. The script is divided into five sections: usage comments, preparation, backup proper, clean-up, and cheat-sheet comments.
root@braveplumes:~ # touch ./backup.sh
root@braveplumes:~ # chmod u+x ./backup.sh
root@braveplumes:~ # ed ./backup.sh
0
a
Usage comments
#!/bin/sh
# Exports snapshots to the backup disk.
#
# Plug in the backup disk, then be ready to type its GELI key to attach
# it when prompted.
#
# Usage:
#
# ./backup.sh
# Perform a backup.
#
# ./backup.sh -b
# Perform a backup, then play a chime through the
# PC speaker when done.
#
# Snapshots are made automatically via zfstools.
# The transfer is via zxfer.
# See this script's end comments for preparation howto.
Preparation
First is setting up the variables for the host being backed up (thus the backup filesystem receiving the backups), the pool being backed up ("zroot" on both of my computers), and the zxfer exclusion options:
# ######################################################################
thishost="$( hostname -s )"
thispool="zroot"
# Don't let zfs-auto-snapshot snapshot backups
exclude="com.sun:auto-snapshot"
exclude="${exclude},com.sun:auto-snapshot:015m"
# Other zxfer exclusions
exclude="${exclude},special_small_blocks" # feat. not in usb disk
exclude="${exclude},keylocation,keyformat" # not encrypted zfs
exclude="${exclude},pbkdf2iters" # not encrypted zfs
exclude="${exclude},objsetid" # zxfer bug workaround
# ######################################################################
Processing the command line switch:
playchime=0
while getopts 'b' c
do
case $c in
b) playchime=1 ;;
*) : ;; # nop
esac
done
Making sure zxfer is installed and the backup drive is plugged in:
if ! which zxfer >/dev/null 2>&1
then printf "%s\n" "Please install zxfer." >&2; exit 1
fi
if [ ! -e "/dev/gpt/backup" ]
then
printf "%s\t" "[FAIL]"
printf "%s\n" "Backup drive not inserted." >&2
exit 1
fi
Attaching the backup drive:
gelidetach=1
if [ -e "/dev/gpt/backup.eli" ]
then
printf "%s\t" "[Note]"
printf "%s\n" "Backup drive already attached."
gelidetach=0
else
printf "%s\t" "[ OK ]"
printf "%s\n" "Attaching backup drive."
geli attach "/dev/gpt/backup"
fi
if [ ! -e "/dev/gpt/backup.eli" ]
then
printf "%s\t" "[FAIL]"
printf "%s\n" "Backup drive not mounted." >&2
exit 1
fi
Importing the backup pool:
zpoolexport=1
if zfs list -H "backup" >/dev/null 2>&1
then
printf "%s\t" "[Note]"
printf "%s\n" "Backup pool already imported."
zpoolexport=0
else
printf "%s\t" "[ OK ]"
printf "%s\n" "Importing backup pool."
zpool import -N backup
fi
if zfs list -H "backup/${thishost}" >/dev/null 2>&1
then
printf "%s\t" "[ OK ]"
printf "%s\n" "Mounting 'backup/${thishost}'."
mkdir -p "/backup/${thishost}"
zfs mount "backup/${thishost}"
else
printf "%s\t" "[FAIL]"
printf "%s\n" "Backup directory is missing in backup pool." >&2
if [ $zpoolexport -eq 1 ]
then
printf "%s\t" "[back]"
printf "%s\n" "Exporting backup pool." >&2
zpool export backup
fi
if [ $gelidetach -eq 1 ]
then
printf "%s\t" "[back]"
printf "%s\n" "Detaching backup drive." >&2
geli detach "/dev/gpt/backup.eli"
fi
exit 1
fi
Backup proper
printf "%s\t" "[ OK ]"
printf "%s\n" "Commencing backup."
if zxfer -dFkPv -I "${exclude}" -R "${thispool}" "backup/${thishost}"
then
printf "%s\t" "[ OK ]"
printf "%s\n" "Backup completed successfully."
sync; sync; sync
if zfs unmount "backup/${thishost}"
then
printf "%s\t" "[ OK ]"
printf "%s\n" "Unmounted 'backup/${thishost}' cleanly."
else
printf "%s\t" "[WARN]"
printf "%s\n" "Did not unmount 'backup/${thishost}' cleanly."
fi
rmdir "/backup/${thishost}" >/dev/null 2>&1
rmdir "/backup" >/dev/null 2>&1
else
printf "%s\t" "[FAIL]"
printf "%s\n" "Backup encountered a problem."
sync; sync; sync
fi
Clean-up
Exporting the backup pool:
if [ $zpoolexport -eq 1 ]
then
printf "%s\t" "[ OK ]"
printf "%s\n" "Exporting backup pool."
zpool export backup
else
printf "%s\t" "[Note]"
printf "%s\n" "Leaving backup pool in place."
fi
Detaching the backup drive:
if [ $gelidetach -eq 1 ]
then
printf "%s\t" "[ OK ]"
printf "%s\n" "Detaching backup drive."
geli detach "/dev/gpt/backup.eli"
else
printf "%s\t" "[Note]"
printf "%s\n" "Leaving backup drive attached."
fi
Announcing the end:
printf "%s\t" "[ OK ]"
printf "%s\n" "Done."
if [ "$playchime" -eq 1 ]
then
# Jetsons doorbell chime
echo "T208O3L4FAL8B>L4C." >/dev/speaker 2>/dev/null
fi
Cheat sheet comments
# ######################################################################
#
# Backup cheat sheet
#
# Preparation:
#
# pkg install zfstools zxfer
#
# Creation of snapshots to back up:
#
# zfs get com.sun:auto-snapshot # list auto-snapshot status
# #
# # exclude filesystems from snapshotting
# #
# zfs set com.sun:auto-snapshot=false zroot/tmp
# zfs set com.sun:auto-snapshot=false zroot/usr/ports
# zfs set com.sun:auto-snapshot=false zroot/usr/src
# zfs set com.sun:auto-snapshot=false zroot/var/audit
# zfs set com.sun:auto-snapshot=false zroot/var/crash
# zfs set com.sun:auto-snapshot=false zroot/var/tmp
# #
# # exclude filesystems from super-frequent snapshotting
# #
# zfs set com.sun:auto-snapshot:015m=false zroot/ROOT
# #
# # snapshot root filesystem and all children except excluded above
# #
# zfs set com.sun:auto-snapshot=true zroot
# #
# # Fire off automatic snapshotting via cron
# #
# crontab -e
# a
# SHELL=/bin/sh
# PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
# # zfstools automatic snapshotting
# 15,30,45 * * * * /usr/local/sbin/zfs-auto-snapshot 015m 4
# 0 * * * * /usr/local/sbin/zfs-auto-snapshot 1hly 24
# 7 0 * * * /usr/local/sbin/zfs-auto-snapshot 2dly 7
# 14 0 * * 7 /usr/local/sbin/zfs-auto-snapshot 3wky 4
# 28 0 1 * * /usr/local/sbin/zfs-auto-snapshot 4mth 12
# .
# wq
#
# Backup disk creation:
#
# thishost="$( hostname -s )"
# thispool="zroot"
# backupdev="da0" # see dmesg after plugging in backup drive
#
# gpart destroy -F ${backupdev} # probably da0, see dmesg
# gpart destroy -F ${backupdev} # should error if already destroyed
# gpart create -s gpt ${backupdev}
# gpart add -a 1m -l backup -t freebsd-zfs "${backupdev}"
# gpart show -l ${backupdev} # should list new backup, whole disk
# grep "geli init" /var/log/bsdinstall_log # how was live eli was created
# geli init -e AES-XTS -l 256 -s 4096 "/dev/gpt/backup"
# geli attach /dev/gpt/backup
# geli status # verify
# zpool create backup gpt/backup.eli
# zfs create backup/${thishost}
# zpool list # verify
# zfs list # verify
# zpool export backup
# geli detach gpt/backup.eli
#
# Backup procedure
#
# # don't let zfs-auto-snapshot snapshot backups
# exclude="com.sun:auto-snapshot"
# # other exclusions
# exclude="${exclude},special_small_blocks" # usb disk doesn't have feature
# exclude="${exclude},keylocation,keyformat" # not encrypted zfs
# exclude="${exclude},pbkdf2iters" # not encrypted zfs
# geli attach /dev/gpt/backup
# zpool import backup
# zxfer -B -dFkPv -I ${exclude} -R ${thispool} backup/${thishost}
# zpool scrub -w backup
# zpool export backup
# geli detach gpt/backup.eli
#
And that's it.
.
wq
7164
root@braveplumes:~ #
Conclusion
Now when it comes time to run a backup, all I have to do is plug in the backup
drive, get root, and run ./backup.sh
(or ./backup.sh -b
). When it's done,
I just exit, unplug, and put the backup drive away.
Download the script
Make sure you edit "da0
" on line 211 (in the cheat sheet comments) to match
your system and setup.