djhworld

thoughts


Syncing a file to a remote server when it changes on OSX

I’m a big fan of plain text accounting and have been for years.

My tooling of choice has always been beancount with fava as the web application to render my beancount file.

I’ve always had one problem though, I edit my beancount file on my Mac using emacs1, but my fava instance runs on a server in my homelab. So if I want to see the latest changes, I’d do stuff like manually copy the file (annoying) or implement stupidly overkill solutions like syncthing - which was a pain to maintain for just one file.

So I was looking for something simpler, my requirements were

  1. Copy the file to my server when it changes
  2. Do this automatically and be enabled all the time, even after reboots

After some very basic research and testing, I settled on a neat solution, using fswatch and launchd to basically monitor my beancount file and sync to my server on change events.

The solution is just a few lines of XML that runs a bash one-liner

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>beancount.sync</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>/opt/homebrew/bin/fswatch -o beancount/finances.beancount  | xargs -n1 -I{} /usr/bin/rsync -avz beancount/finances.beancount server:/opt/data/beancount/</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/tmp/beancount.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/beancount.err</string>
    <key>UserName</key>
    <string>daniel</string>
</dict>
</plist>

It works by fswatch firing some output every time the file changes and rsync does the sync up to the server.

Enabling the service was easy, I just ran this command about 6 months ago

launchctl load ~/Library/LaunchAgents/beancount.sync.plist

…and that’s it!

A remarkably simple solution that’s been working solidly, and given Fava monitors changes to the files it’s rendering - you see your updates reflected in the UI almost instantly or a few seconds at most.

Why fswatch

I’m aware launchd has a directive called WatchPaths, I tried using this initially but it never seemed to work properly and debugging why was depressing. I’ve been a Mac user for nearly 20 years and barely understand most of its internals.

The fswatch solution worked first time and has worked every time, so it gets a 👍 from me.


  1. This is the only thing I use emacs for. I run it with beancount-mode and evil for vim keybindings. It’s the best editor for beancount data but I use Vim for everything else! [return]