Since OSX does not offer native protection against bit rot (for example with ZSH or other mature file systems), I've started using PAR2 to manually protect my photos. To do this, I've written some scripts that I'm documenting here for myself and others.

The process seems to work, however it's rather laborious. Some optimization might be useful/necessary.

Create PAR files for one directory

# Directory to create PAR for
PICDIR=./

# Directory containing metadata (e.g. log files) and par2 executable
MAINTDIR=/Users/tim/Pictures/maintenance/

# Go to directory, e.g. '20051203_vacation_trip'
cd $PICDIR
# Extract year from directory name
year=${$(pwd)##*\/}
# Base PAR file on year of the directory
parfile=${year}0000.par2
parlogf=${MAINTDIR}/${year}0000.par2.log

# Remove DS_Store files, these are not important and change often
find . -type f | grep .DS_Store$ | xargs rm

# Init log file with current date
date >> ${parlogf}
# nice: reduce prio for this command 
# caffeinate: prevent disk or computer sleep when running
# par2: create parity files recursively, 1% redundancy, with 10k blocks, for all files
# tee: store output to log file and show on screen
nice -n 10 caffeinate -ms ${MAINTDIR}/par2 c -R -r1 -b10000 ${parfile} ./* | tee -a ${parlogf}

Create PAR files for many directories

Since my pictures are stored in many different directories, and since par2 is not parallelized, the above can be extended to par many directories in parallel as follows.

First we define a function which does the above, in bash:

# We need bash since it supports functions
bash

# Define a function which pars one directory (basically script above)
function create_par {
  CHECKDIR=$1
  CURDIR=$(pwd)
  PHOTODIR=/Users/tim/Pictures/

  parcmd=/Users/tim/Pictures/maintenance/par2  
  parlogf=/Users/tim/Pictures/maintenance/creating.par2.log
  parfile=${CHECKDIR}0000.par2
  
  cd ${PHOTODIR}/${CHECKDIR}

  # Remove DS_Store files, these are not important and change often, polluting
  # the PAR2 archive
  find /Users/tim/Pictures/${CHECKDIR} -type f | grep .DS_Store$ | xargs rm

  # Move existing PAR files to archive dir
  mv *par2 ${PHOTODIR}/maintenance/0-oldpar/

  date
  echo "Now creating PAR in $(pwd)\n========================="
  nice -n 10 caffeinate -ms ${parcmd} c -R -r1 -b10000 ${parfile} ./*

  cd ${CURDIR}
}

Now we can simply run

parlogf=/Users/tim/Pictures/maintenance/creating.par2.log
create_par 2016* | tee -a ${parlogf}

to create par files for any directory. Using the simple command parallel, we can parallelize this:

export -f create_par
parallel -j 4 -k create_par ::: {2015,2010,2009,2008,2007,2006,2005,2003}* | tee -a ${parlogf}

Check PAR files for one directory

After creating PAR files, we need to verify the integrity once in a while, which can be done as follows

bash
function check_par {
  CHECKDIR=$1
  CURDIR=$(pwd)
  PHOTODIR=/Users/tim/Pictures/

  parcmd=/Users/tim/Pictures/maintenance/par2  
  parlogf=/Users/tim/Pictures/maintenance/checking.par2.log
  parfile=${CHECKDIR}0000.par2
  
  cd ${PHOTODIR}/${CHECKDIR}
  date
  echo "Now checking $(pwd)\n========================="
  nice -n 5 caffeinate -ms ${parcmd} v ${parfile} | grep -v "Target.* - found.$|^Load|^Scanning: "
  cd ${CURDIR}
}

then call the command

parlogf=/Users/tim/Pictures/maintenance/checking.par2.log
check_par $PICDIR

Or run in parallel again

export -f check_par
parallel -j 4 -k check_par ::: {0,1,2}* | tee -a ${parlogf}