Ironclad Time Machine backups on FreeBSD

Apple wouldn't be Apple if it didn't have its very own quirky take on the mundane task of backing up your files. It's called Time Machine and what it does, essentially, is store a bazillion incremental backups on a disk until it fills up. From then on it prunes off old backups in favor of new ones. The bigger your drive, the longer the retention. The GUI it uses for this is -while corny- very effective. Sadly though, Apple itself doesn't have anything at all on offer that would allow a self-respecting geek to sleep soundly at night. Sure, the Time Capsule is about as elegant as external storage comes but it's not even capable of RAID1. Savages!

Luckily for us, everything we need software-wise to host Time Machine backups on something that packs a little more oomph is available for free. You can pick the hardware to fit your budget. Just remember you'll need a minimum of 3 equal-size disks for this and something capable of running FreeBSD. When you're done you'll have a location for your Time Machine backups that plays by your rules instead of Apple's:

  • Stable: FreeBSD.. need I say more?
  • Robust: you can lose a disk without endangering your data. Not good enough? You could use double parity and survive 2 dead drives.
  • Lots of space: you get to choose how big your Time Machine can get, not the 3TB max Apple dictates for you. Oh, and did I mention transparent data compression?
  • Networked: it's accessible anywhere in your house, all the time.
  • Fast: Gigabit ethernet is a whole lot faster than Apple's wireless option, even the 'n' version.
  • Compatible: your machine speaks AFP 3.3, on par with the latest from Cupertino. 

FreeBSD: where real men go to store their wives' data

Observant readers may note that I often choose FreeBSD for my geek projects. Usually this is just a matter of preference and Linux would do just as well. Not so in this case though. FreeBSD supports ZFS, which is the current be-all, end-all of filesystem technology. What's cool about ZFS is that it allows you to create RAID-Z arrays from your drives. It's like RAID5 with whipped cream and a cherry on top but without the calories. You'll love it, trust me.

Preparing the disks

This tutorial assumes you know how to install FreeBSD and how to use a shell. If not, you can check the excellent FreeBSD Handbook online to get up to speed.

I find an efficient way to create a storage powerhouse is to boot and run the operating system off a simple USB stick so I can have maximum use of the available SATA ports. The OS won't be doing heavy I/O to its own storage anyway so after a relatively slow bootup there won't be any penalty for running off USB. Note: a clear exception to this is the initial installation phase in which you'll be downloading and compiling oodles of source code. Not nice on a USB stick, but you could move your /usr/ports onto your yummy fast disks once you're done setting up ZFS or use another machine to build binary packages for you.

Let's assume you have a system with said USB stick for the OS (4GB will suffice in a pinch) and five 2TB drives for use as your main storage area. This will give you a /dev/da0 for your USB stick, and /dev/ad[0-4] for your storage array. Once you add 'zfs_enable="YES"' to your /etc/rc.conf file, you can now create the RAID-Z array:

#zpool create srv raidz ad0 ad1 ad2 ad3 ad4

This should create and mount the RAID-Z array for you on /srv. Now you could simply use /srv as one huge storage pool and be done with it, but let's be a little more prudent. 8TB of usable free space is, after all, a lot of space and you'll want to make sure it stays properly organised.

#zfs create srv/timecapsule

This creates a subvolume where your Time Machine backups will live. A simple mkdir would have worked just as well if organising files was all we wanted to do. A ZFS subvolume allows us to apply advanced ZFS settings like transparent gzip compression to parts of the pool, something a mkdir would not have given us. To enable gzip compression for srv/timecapsule you need simply do the following:

#zfs set compression=gzip srv/timecapsule

Let's talk some Mac

Even though Mac OS X is UNIX at its base, it still very passionately prefers to speak its own brand of file sharing protocol: AFP. FreeBSD can use this protocol, but only after you install the net/netatalk port.

#cd /usr/ports/net/netatalk
 #make
 #make install
 #make clean

Right after the first 'make' you'll be given a menu of choices. Make sure you check the 'Bonjour' box there. This pulls in Avahi which allows you to have your server show up in the Mac's Finder automatically without first connecting to it manually. Installing Netatalk takes quite a long time due to the large numer of dependencies Netatalk pulls in. Occasionally you'll be given menus for a depencency port. You can simply accept the defaults there. When all that is done, you'll need to add a few lines to /etc/rc.conf:

dbus_enable="YES"
 netatalk_enable="YES"
 afpd_enable="YES"
 cnid_metad_enable="YES"
 avahi_daemon_enable="YES"
 avahi_dnsconfd_enable="YES"

Turn up the volumes

Let's say you have a family member named Steve and Steve needs to back up the files on his MacBook to someplace safe. You'd need to create a user account, home directory and a directory for Steve's Time Machine data to live in. The home directory will never be used if Steve isn't given a login shell on your server. Still it's wise to keep FreeBSD's notion of a homedir separated from what Steve's MacBook will think is a regular MacOS X Server. This means Steve's home directory will simply be /home/steve and his TimeMachine will live in /srv/timecapsule/steve.

#pw user add steve -d /home/steve -s /bin/nologin

This adds a user and a group named 'steve' to your system, creates /home/steve with the proper permissions and contents and sets Steve's login shell to /bin/nologin, effectively locking him out from the UNIX underpinnings of this system. Now all that remains is to set up the location for Steve's Time Machine:

#mkdir /srv/timecapsule/steve
 #chown steve:steve /srv/timecapsule/steve
 #chmod 770 /srv/timecapsule/steve

Set up a password for Steve using the passwd command, and you're all set.

Put some shine on them Apples

This should be enough to allow Steve to authenticate but it doesn't really give him that famed Apple polish. Mac OS X tends to only use the UNIX user ID below the surface. To the users themselves it exposes the so-called gecos field, which mere mortals refer to as the user's full name. By default FreeBSD fills this with 'user &', which isn't exactly flattering. A quick run of the 'vipw' command should allow you to fix this in your favorite editor as long as your favorite is called VI. For editors other than VI you need to modify the EDITOR environment variable. Refer to the docs of your shell for that. Usually it comes down to:

#EDITOR=emacs
 #export EDITOR

..which should make vipw invoke emacs instead, but your mileage may vary depending on the particular shell you use. If you now replace 'user &' with a right and proper 'Steve Jobs', he'll be able to authenticate to your server under his full name instead of only his lower case UNIX user ID 'steve'. Rinse and repeat this step for all your family members.

Note: This article was written before mr. Jobs, Apple's founding president, passed away. I'm leaving his name in this article. Consider it a homage.

Setting up AFP

Now that we have everything in place it's time to finally configure the AFP daemon so that to the uninitiated eye it will look like a MacPro running Snow Leopard Server. This is a two-step procedure concering a couple of files in /usr/local/etc. First up is afpd.conf. The Netatalk port installed an example afpd.conf file, but I got rid of that in favor of just reading the excellent man page. My homebrew afpd.conf now contains a single line:

"Time Capsule" -defaultvol /usr/local/etc/AppleVolumesTimeMachine.default -systemvol /usr/local/etc/AppleVolumes.system -uamlist uams_randum.so,uams_dhx.so,uams_dhx2.so -maccodepage MAC_ROMAN -unixcodepage UTF8 -savepassword -setpassword -transall -mimicmodel MacPro -ipaddr 10.0.0.49

The design of this site chops this up and sprinkles it over multiple lines, but all of the above really has to be on a single line. I'm not going to explain every option there because you have a man page for that. What you should review, is the quoted value "Time Capsule". This is how your server will be shown in Steve's Finder window.

Also make sure you put the server's proper IPv4 address in place of the example's 10.0.0.49. I've seen mixed reports on whether or not this option is required at all, so maybe you can simply do without it altogether. Try and find out.

The mimicmodel option is funny in that it determines what icon Mac clients will display for your server. There's a whole bunch of models you can choose from and it doesn't do anything for functionality so just leave it for now.

The other file you'll need to create, is /usr/local/etc/AppleVolumesTimeMachine.default. If you prefer a different name for this one, you'll have to change the reference you made to it in afpd.conf.

This file contains a single line for each volume you wish to share. I advise you to create a separate AFP volume for each of your users as this makes it easy to limit their disk usage. For Steve the single line would read:

/srv/timecapsule/steve "TimeMachine for $u" adouble:osx allow:steve allowed_hosts:10.0.0.0/24 cnidscheme:dbd ea:auto options:tm,invisibledots,upriv dperm:0770 fperm:0660 volcharset:UTF8

This line also contains a setting specific to my own network, which uses 10.0.0.0/24 for all its local nodes. Change this where applicable or your clients won't be allowed access.

This should be all there is to it. Netatalk should work properly after you start up all the dbus and avahi business but it's just as easy to simply reboot your server now and have that all taken care of automatically. You do want to be sure that your server returns to business properly after a power outage, so this is as good a time as any to test that.

The proof of the pudding

After your server reboots Steve should be able to connect to it by URL. Make sure to connect your Mac to the fastest network cable you can find, as we'll be performing a test backup for which you'll want all the speed you can get. From the Finder's top menu choose 'Go' and 'Connect to server'. Enter the following URL in the field:

afp://10.0.0.49

If all went as it should, you should now see an icon of a MacPro in the left panel of the Finder and a directory named 'TimeMachine for steve' inside it. Check if Steve can actually write and delete a file there. If not, there's most likely something wrong with UNIX permissions in /srv/timecapsule/steve.

Enable Time Machine to use network volumes

By default Time Machine only works with physically attached disks. A small setting entered through the terminal in Mac OS X allows it to use our server. Logged in as the user Steve, start a terminal and give the following command:

$defaults write com.apple.systempreferences TMShowUnsupportedNetworkVolumes 1

This is, once again, a single line of code. Type it exactly as it is shown here. After you press enter it should give you no output, simply return your terminal prompt.

Now it should be possible to enable Time Machine through System Preferences and select your newly created server volume as a Time Machine disk. If possible you should connect your Mac to the fastest network cable you can find as that will make the initial backup process a whole lot faster than wireless.

Avahi: the icing on the cake

If you drop the lines below into /usr/local/etc/avahi/services/afpd.service and restart the avahi daemon, your Mac clients will be notified automatically of your server's presence. Completing that warm and fuzzy 'just works' experience you're used to from Apple.

<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
 <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
 <service-group>
 <name replace-wildcards="yes">%h</name>
 <service>
 <type>_afpovertcp._tcp</type>
 <port>548</port>
 </service>
 <service>
 <type>_device-info._tcp</type>
 <port>0</port>
 <txt-record>model=MacPro</txt-record>
 </service>
 </service-group>