Apple Time Machine Network Backups with a Raspberry Pi 4

This guide will cover how to leverage a Raspberry Pi and a Linux software RAID 5 to perform automatic Time Machine backups for your Mac over the network. While creating a Time Machine backup with a Raspberry Pi has been covered before, I put my own spin on this as I had some old drives laying around from when I upgraded my Plex server and wanted a reference for setting up a Linux software RAID using mdadm.

The transition to working from home full time has forced a lot of us to re-evaluate our normal workflow. As my laptop now spends nearly 100% of its time plugged in and sitting on the desk, I decided to minimize the number of peripherals that need to be plugged into it. Specifically, I no longer need to carry around a back-up hard drive and I did not want it sitting on my desk 24/7.

This can be done using any model of Raspberry PI however I strongly recommend using a Raspberry Pi 4 to take advantage of its support for full gigabit ethernet and USB 3.0. Backups can take a prolonged period of time and we want to maximize our throughput. 

Hardware needed:

Raid Configuration:

Begin with updating your Pi and installing mdadm.

pi@raspberrypi:~ $ sudo apt update && apt upgrade -y
pi@raspberrypi:~ $ sudo apt install mdadm -y

Add your drives into the drive bays and plug the enclosure into the Pi. SSH to it remotely or open a terminal locally. Once plugged in, list out the available drives using lsblk. Note that the drives will appear as /dev/sdx and differ from the SD card at /dev/mmcblk0.

NAME        MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
sda           8:0    0  1.8T  0 disk
sdb           8:16   0  1.8T  0 disk
sdc           8:32   0  1.8T  0 disk
sdd           8:48   0  1.8T  0 disk
sde           8:64   0  1.8T  0 disk
mmcblk0     179:0    0 28.8G  0 disk
├─mmcblk0p1 179:1    0  256M  0 part  /boot
└─mmcblk0p2 179:2    0 28.5G  0 part  /

With the five available 2TB drives (reported as 1.8T by the OS), we will create the array in a RAID 5 configuration using mdadm.

pi@raspberrypi:~ $ sudo mdadm --create --verbose /dev/md0 --level=5 --raid-devices=5 /dev/sda /dev/sdb /dev/sdc /dev/sdd /dev/sde

The RAID array will start the build process and can be viewed with cat /proc/mdstat.

pi@raspberrypi:~ $ cat /proc/mdstat
Personalities : [raid6] [raid5] [raid4]
md0 : active raid5 sde1[5] sdc1[2] sda1[0] sdd1[3] sdb1[1]
      7813525504 blocks super 1.2 level 5, 512k chunk, algorithm 2 [5/5] [UUUUU]
      [=======>.............]  recovery = 26.3% (1924851/104038498) finish=7.3min speed=200808K/sec

unused devices: <none>

One nice perk of mdadm is that we can continue to interact with the RAID while it is building. Time Machine requires Apple’s proprietary Hierarchical File System (HFS) so we will need to install the required utilities on the Raspberry Pi for interoperability. 

pi@raspberrypi:~ $ sudo apt install hfsutils hfsprogs netatalk -y

With HFS+ support installed, format the drive to HFS+ with mkfs in Linux.

pi@raspberrypi:~ $ sudo mkfs.hfsplus /dev/md0 -v timeCapsule

If we run lsblk again, we should see our five drives configured in a RAID.

NAME        MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
sda           8:0    0  1.8T  0 disk
└─sda1        8:1    0  1.8T  0 part
  └─md0       9:127  0  7.3T  0 raid5 
sdb           8:16   0  1.8T  0 disk
└─sdb1        8:17   0  1.8T  0 part
  └─md0       9:127  0  7.3T  0 raid5 
sdc           8:32   0  1.8T  0 disk
└─sdc1        8:33   0  1.8T  0 part
  └─md0       9:127  0  7.3T  0 raid5 
sdd           8:48   0  1.8T  0 disk
└─sdd1        8:49   0  1.8T  0 part
  └─md0       9:127  0  7.3T  0 raid5 
sde           8:64   0  1.8T  0 disk
└─sde1        8:65   0  1.8T  0 part
  └─md0       9:127  0  7.3T  0 raid5 
mmcblk0     179:0    0 28.8G  0 disk
├─mmcblk0p1 179:1    0  256M  0 part  /boot
└─mmcblk0p2 179:2    0 28.5G  0 part  /

Now we need to create the directory to mount our new RAID disk. Create it using the following command:

pi@raspberrypi:~ $ sudo mkdir /srv/TimeCapsule

Before mounting we need the UUID of the drive. Take note of the entry that points to ../../md0 as we will need to add this to /etc/fstab:

pi@raspberrypi:~ $ ls -alh /dev/disk/by-uuid/
total 0
drwxr-xr-x 2 root root 100 Apr 13 01:08 .
drwxr-xr-x 7 root root 140 Apr 13 01:08 ..
lrwxrwxrwx 1 root root  15 Mar  4 23:04 7581-8A48 -> ../../mmcblk0p1
lrwxrwxrwx 1 root root   9 Apr 13 01:08 e89b1b71-39e3-3632-b4cb-3eed298133e6 -> ../../md0
lrwxrwxrwx 1 root root  15 Mar  4 23:04 fa37d505-e741-4d35-bcec-4580aef395e1 -> ../../mmcblk0p2

Open up /etc/fstab with a text editor and add a line to automatically mount the drive to /srv/TimeCapsule on boot or reboot:

proc            /proc           proc    defaults          0       0
PARTUUID=83c4223d-01  /boot           vfat    defaults          0       2
PARTUUID=83c4223d-02  /               ext4    defaults,noatime  0       1
UUID=e89b1b71-39e3-3632-b4cb-3eed298133e6 /srv/TimeCapsule hfsplus force,rw,user,noauto 0 0
# a swapfile is not a swap partition, no line here
#   use  dphys-swapfile swap[on|off]  for that

Mount the RAID disk at the directory:

pi@raspberrypi:~ $ sudo mount /srv/TimeCapsule/

If no output returns, the mount is successful. You can validate it by running lsblk to verify that md0 has a mount point set to /srv/TimeCapsule:

pi@raspberrypi:~ $ lsblk
NAME        MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
sda           8:0    0  1.8T  0 disk
└─sda1        8:1    0  1.8T  0 part
  └─md127     9:127  0  7.3T  0 raid5 /srv/TimeCapsule
sdb           8:16   0  1.8T  0 disk
└─sdb1        8:17   0  1.8T  0 part
  └─md127     9:127  0  7.3T  0 raid5 /srv/TimeCapsule
sdc           8:32   0  1.8T  0 disk
└─sdc1        8:33   0  1.8T  0 part
  └─md127     9:127  0  7.3T  0 raid5 /srv/TimeCapsule
sdd           8:48   0  1.8T  0 disk
└─sdd1        8:49   0  1.8T  0 part
  └─md127     9:127  0  7.3T  0 raid5 /srv/TimeCapsule
sde           8:64   0  1.8T  0 disk
└─sde1        8:65   0  1.8T  0 part
  └─md127     9:127  0  7.3T  0 raid5 /srv/TimeCapsule
mmcblk0     179:0    0 28.8G  0 disk
├─mmcblk0p1 179:1    0  256M  0 part  /boot
└─mmcblk0p2 179:2    0 28.5G  0 part  /

With the drive mounted, configure permissions so that we can write to it without requiring root privileges:

pi@raspberrypi:~ $ sudo chown pi:pi /srv/TimeCapsule

Time Machine Configuration:

Configure /etc/nsswitch.conf and add mdns mdns4 to the end of the hosts: line:

pi@raspberrypi:~ $ sudo vim /etc/nsswitch.conf
# /etc/nsswitch.conf
#
# Example configuration of GNU Name Service Switch functionality.
# If you have the `glibc-doc-reference' and `info' packages installed, try:
# `info libc "Name Service Switch"' for information about this file.

passwd:         files
group:          files
shadow:         files
gshadow:        files

hosts:          files mdns4_minimal [NOTFOUND=return] dns mdns mdns4
networks:       files

protocols:      db files
services:       db files
ethers:         db files
rpc:            db files

netgroup:       nis

Finally, configure Netatalk to mimic Apple’s Time Capsule in /etc/netatalk/afp.conf. Add the mimic model in the [Global] block and the path to our mounted Time Capsule volume in the [My Time Machine Volume] block. Be sure to remove the semi-colon characters in the [My Time Machine Volume] block as well.

pi@raspberrypi:~ $ sudo vim /etc/netatalk/afp.conf
;
; Netatalk 3.x configuration file
;

[Global]
; Global server settings
  mimic model = TimeCapsule6,106

; [Homes]
; basedir regex = /xxxx

; [My AFP Volume]
; path = /path/to/volume

[My Time Machine Volume]
  path = /srv/TimeCapsule
  time machine = yes

Finally, start up the two configured services to broadcast the drive as a Time Capsule and then enable them to start automatically at boot.

pi@raspberrypi:~ $ sudo systemctl start avahi-daemon.service
pi@raspberrypi:~ $ sudo systemctl start netatalk.service
pi@raspberrypi:~ $ sudo systemctl enable avahi-daemon.service
Synchronizing state of avahi-daemon.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable avahi-daemon
pi@raspberrypi:~ $ sudo systemctl enable netatalk.service
Synchronizing state of netatalk.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable netatalk

Note: Occasionally the HFS+ volume will be mounted in read-only mode if the Raspberry Pi restarts. The best explanation I have been able to find is that this is due to journaling with HFS+. I haven’t been able to find solid support for HFS+ journaling on Linux so the best way to fix this is to unmount and then remount the drive in the following way.

pi@raspberrypi:~ $ sudo systemctl stop avahi-daemon.service
pi@raspberrypi:~ $ sudo systemctl stop netatalk.service
pi@raspberrypi:~ $ sudo umount /srv/TimeCapsule
pi@raspberrypi:~ $ sudo mount -t hfsplus -o force,rw /srv/TimeCapsule
pi@raspberrypi:~ $ sudo systemctl start avahi-daemon.service
pi@raspberrypi:~ $ sudo systemctl start netatalk.service

Pivot back to your Mac host and you should be able to directly connect to the Raspberry Pi using the same credentials via the GUI by navigating to Finder > Go > Connect to server…

Once connected, open up Time Machine Preferences, click “Select Disk” and choose your newly created Time Machine volume. For transparency, the screenshot shows my other time machine volume next to the newly created one.

That’s it! You should be up and running now and you can forget about every having to plug a backup drive into your computer again.

Final Thoughts:

This solution works pretty well for me and has been running stable for the last year. In the future, I may add some more granular permissions for each user that will be backing up to this rather than have a single account for everyone to use to connect to the Pi. Additionally, I am interested to hear if anyone has a better solution than un-mounting and re-mounting the volume and restarting the services when it becomes read-only. I’ve tried the cronjob solution without any luck.

References: