Managing preference plists under Chezmoi

Chezmoi handles my dotfiles, and it allows me to painlessly go to great levels of configuration management across machines. There’s one difficult type of configuration to deal with on Mac, though: binary .plist files.

Take Soulver, for example, which stores its font settings in UserDefaults. This results in a binary plist in ~/Library/Preferences which contains information changed on each run of the app. It is a great example of what not to check into source control.

My solution is using a modify script, which Chezmoi calls with the current version of the file looking for a modified version in response. The script for Soulver is stored in modify_private_app.soulver.mac.plist:

source "$(chezmoi source-path)/path/to/"
pl SV_CUSTOM_FONT_POST_SCRIPT_NAME -string "Input-Regular"
pl SV_FONT_SIZE -integer 16

This is relatively stable, but more prone to breaking than a concretely-defined settings file format, of course. This uses a helper script named which acts as a thin wrapper around plutil while minimizing the amount of boilerplate:

set -e
trap "cat $TMPFILE; rm $TMPFILE" EXIT

function pl() {
    # test before setting because plutil _will_ mutate the file
    # macOS 12.+, you can use plutil like:
    # CURRENT=$(plutil -extract $1 raw $TMPFILE 2>/dev/null || :)
    CURRENT=$(/usr/libexec/PlistBuddy -c "Print :$1" $TMPFILE 2>/dev/null || :)
    if [ "$CURRENT" != "$3" ]; then
        plutil -replace $* "$TMPFILE"

cat <&0 >$TMPFILE

if [ ! -s $TMPFILE ]; then
    # plutil will error if it encounters an empty file
    # macOS 12.+ you can use plutil like:
    # plutil -create binary1 $TMPFILE
    echo "{}" | plutil -convert binary1 -o $TMPFILE -

Now my Soulver settings are synced and updated using Chezmoi, and I don’t have to keep track of which apps I need to visit if I decide I want to play with a different font.

I recently looked at every programming font I could find; here are my favorites in order:

  1. Input (Condensed)
  2. MonoLisa
  3. JetBrains Mono