We’re an iPhone family, and recently bit the bullet and subscribed to iCloud for photo backups (and security cameras, and Apple TV). Our digital backup strategy already included local storage and vendor-neutral cloud storage, so I needed to find a way to pull photos down from iCloud to our home server.

Dataflow

Almost all our family pictures and video are captured on my wife’s iPhone. This has been the case for several years, and we were in the past able to export them to the home server via rsync. Once they were on the server, the files would be renamed with datastamps and sorted into YEAR/Month folders. These folders would be backed up nightly to Backblaze, and periodically added to our Plex library.

iCloud breaks this. When the pictures are uploaded to iCloud, they’re no longer accessible to the rsync tool and we need to pull them straight from iCloud. There’s a tool for this: iCloud Photos Downloader. This runs in Python 3.9, and can be installed via pip.

VM Configuration

I created a SmartOS zone from the 22.4.0 base-64-lts image, and passed in the media volume via lofs. I gave it 512MB RAM, but probably could have gone smaller. From inside the zone, installation and configuration was shockingly simple:

# pkgin in py39-pip
# pip install icloudpd

icloudpd configuration

Before scheduling icloudpd to run as a cronjob, you’ll need to initialize icloud for file access and stash an authentication token to use on subsequent runs. These are both done by running icloudpd interactively:

icloudpd --directory . --only-print-filenames

You’ll get prompted for a username and authentication method, then the program will exit with a note that iCloud need to index some files or something. Try it again in 15-20 minutes.

Running as a Cronjob

I put the backup command into a shell script to simplify the crontab entry:

#!/usr/bin/env bash
# icloud-photo-backup.sh
icloudpd --username xxxxxxxx@xxxxxxxx.xxx \
        --password xxxxxxxxxxxxxxxx \
        --until-found 80 \
        --folder-structure {:%Y/%m} \
        --directory /zones/media/Photos \
        --smtp-host <internalsmtprelay> \
        --smtp-disable-tls \
        --notification-email xxxxxxxx@xxxxxxxx.xxx

After an initial sync (remove the “until-found” flag), run it as a cronjob. I’ve got it set up to run daily, and should get some sort of notification if/when the authentication token expires.

I like it.