GELI Encrypted USB Backup in FreeBSD 13
source link: https://thornton2.com/unix/freebsd/geli-encrypted-usb-backup.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
GELI Encrypted USB Backup in FreeBSD 13
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
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
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.)
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:
root@braveplumes:~ # geli attach /dev/gpt/backup Enter passphrase: root@braveplumes:~ # zpool import -N backup 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.
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.
The chime is going to be the "Meet George Jetson" part of "The Jetsons" theme, which is also used as the doorbell chime in the Jetsons' apartment.
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." 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 7140 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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK