Backing Up My Google Photos Library

Published Oct. 4, 2021

I’ve been using Google Photos for a few years now. I started because it was a default on Android devices, and I stuck with it when I made a switch to iPhone. Yet, I’ve always felt a bit of anxiety because of it. What happens with my data when Google discontinues the product? What if I want to move to another photo library in the future? Recently I decided to take back control of my data and set up a workflow to back up my full Google Photos library.

Picking the Tool for the Job

At first I thought that building a program to synchronize Google Photos to a local disk would be an interesting side project. But life’s too short and there are too many side projects to build. Instead I chose to go with gphotos-sync created by Giles Knap.

When you run it for the first time, gphotos-sync indexes your whole Google Photos library and saves the index in the local SQLite database. It then downloads all the photos and videos from the index. On later runs, it updates the index and downloads only those pictures that you added since the last run.

gphotos-sync also synchronizes photos from albums that anyone shared with you. It is a great option for backing up family pictures, no matter who’s the album owner. You can opt out of this behaviour using the --skip-shared-albums option.

Backing Up the Library

To start backing up your media, you first need to install and configure the script. Configuration is the most involved part of the whole process. You will need to create a Google Cloud Platform project, create credentials, and download them as a JSON file from the Google Cloud console. You can find the setup description in this Google doc (also linked from the project’s README).

ℹ️  You can use any Google account to create the Google Cloud Platform project. It doesn’t need to be account whose photos you wish to back up.

Once you have the client_secret.json file with the credentials, starting the syncing process is a one-liner:

gphotos-sync --secret "/path/to/client_secret.json" "/photos/download/location"

On the first run the program will ask you to open a URL in the browser to authorize access to your Google Photos:

Please go here and authorize, https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=xxx...
Paste the response token here:

Now you need to sign in using the Google account whose photos you want to back up. Once you grant the necessary permissions, you’ll see the authorizatio code that you need to copy and paste in the terminal. Hit enter, and that’s it! The backup duration will depend on the size of your photo collection. In my case it took around 2 hours to sync 90 GB of media.

Moving Backup to the Home Server

I have a home server with a decent amount of storage. I’m already using it to run a couple scripts at regular intervals, so moving the backup script there was a natural choice. The server is running NixOS, and I manage its configuration using NixOps. Here is a piece of config I used to set up the photo backups:

{ config, pkgs, ... }:
let
  photoRootDirectory = "/data/userdata/photos";
  logDirectory = "/var/log/gphotos-sync";
  wrapperScript = pkgs.writeScriptBin "gphotos-sync" ''
    #!${pkgs.stdenv.shell}
    set -euo pipefail

    ${pkgs.gphotos-sync}/bin/gphotos-sync --progress \
      --log-level debug \
      --logfile ${logDirectory}/gphotos-sync-$(date +'%Y-%m-%dT%H%M%S') \
      --secret ${config.sops.secrets.gphotos_client_secret_json.path} \
      ${photoRootDirectory}
  '';
in {
  sops.secrets.gphotos_client_secret_json = { };

  systemd = {
    services.gphotos-sync = {
      description = "Synchronizes Google Photos to local drive";
      serviceConfig = {
        Type = "oneshot";
        ExecStart = "${wrapperScript}/bin/gphotos-sync";
        SupplementaryGroups = [
          config.users.groups.keys.name # access secrets
          config.users.groups.photos.name # access photo directory
        ];
      };
    };

    timers.gphotos-sync = {
      description = "Runs Google Photos sync weekly";
      timerConfig = {
        # Refresh photos every Saturday at 2AM.
        OnCalendar = "Sat *-*-* 02:00";
        Persistent = true;
      };
      wantedBy = [ "timers.target" ];
      partOf = [ "gphotos-sync.service" ];
    };
  };
}
  1. In the let ... in block I define a wrapper script that runs the actual gphotos-sync program. I use a wrapper script so that I can generate a logfile name that includes the current date and time. gphotos-sync is available in the public Nix packages channel, so I didn’t need to build it myself. Since client_secret.json file contains senstivie data, I manage it as a secret using sops-nix.
  2. The second part (services.gphotos-sync) declares a systemd service that invokes the wrapper script. I add supplementary groups to the service so that the script can read the client secret file and write files to my photos directory.
  3. Finally I set up a timer that runs the service at 2 AM every Saturday.

Authorizing the Backup Service

When I deployed this configuration to the machine, I started the service by hand using systemctl start gphotos-sync.service. However, the first run failed, because the script prompted me for the authorization token. The process crashed because systemd services can’t accept any data on the standard input.

I figured I need to run the wrapper script from the interactive shell so that I can paste in the authorization token. On NixOS paths to packages are based on their hashes and they’re not easy to find manually. I ended up searching for the path in the systemd service definition, using:

systemctl show gphotos-sync.service | grep ExecStart
# ExecStart={ path=/nix/store/dh0m8gmlfn5016ijfynmvipifphjj9k1-gphotos-sync/bin/gphotos-sync ; ...

I copied the path to the script from the output above and ran it from the interactive shell. This time the authorization code prompt worked as expected.

Tips & Tricks

Something I wasn’t aware of at the beginning is the fact that on subsequent runs gphotos-sync will only index photos that were added after the time of the last backup. If you are adding older photos to your Google Photos library, make sure to run with the --rescan flag. It will make the program go through all pictures regardless of their date.

Limitations

Regardless of the tool you use to back up your photos, the Google Photos API removes the location tags from images’ EXIF metadata. This means that your downloaded pictures will have no information about where they were taken.

Alternatives

gphotos-sync is a solid tool that does the job as advertised. Regardless of whether you will run it from time to time or create a full-blown incremental backup system, it will suit your needs.

If you need a hassle-free way of backing up all your images and videos, you can use Google Takeout. It allows you to create a one-time backup, or it will back up your data every 2 months for a year. It can either send you a download link with your data, or it can add it to your Google Drive or other storage services. However, the data won’t include media from albums shared with you, unless you add those albums directly to your library, which counts towards your storage quota. You won’t have that problem with gphotos-sync.