December 14, 2015

Day 14 - Manage Your Mac Without Going Insane

Written by: Corey Quinn (@quinnypig)
Edited by: Ben Cotton (@funnelfiasco)

Like many people whose jobs include Linux administration, my corporate workstation is a Mac.

Like many people who work for large financial institutions, I don't do personal work on my corporate machine. So, I have a second Mac that lives at home.

This leads to an interesting pair of problems:

  1. How do I keep preference files or other things I care about synced between these two machines without including data I don't want crossing the wall between personal and corporate systems?
  2. How do I keep the current state of my machine backed up continuously so that if my computer gets hurled into the bay, I lose as little configuration data as possible?

The answer to both of these takes the form of a relatively small collection of open source tools, plus Dropbox. (Although you can easily substitute in Google Drive, iCloud, or, frankly, anything that you like that reliably syncs a local filesystem to a remote source, I'm using Dropbox in my examples for simplicity.)

Philosophically, I'm opposed to writing my own localized tools to solve global problems; therefore my solutions tend to involve stringing together freely available tools with (at most) a couple lines of shell to tie everything together. However, traditional configuration management tools tend to fall down for this use case. Puppet, Chef, or Salt are great for managing fleets of servers, but they require planning out what the finished product is going to look like. For a personal workstation, I often don’t know what tools I’m going to install until I’ve already installed them, so configuration management tools are forever left to play catch-up to what my current state happens to be.

The first thing I do is keep my dotfiles in a variety of different git repositories. I have separate git repositories for my vim, ssh, and git configurations and a few others, as well. This lets me be a bit more granular with respect to what data lives on different nodes-- for instance, I don’t want my work AWS credentials on my personal machine (and whether or not secrets belong in git repositories is an entirely separate conversation as well!). I tie them all together by managing them with vcsh. The specific details of exactly how vcsh works is out of scope for this article. That said, you can skip this step without seeing a meaningful difference for your environment.

Git based systems are all well and good (presuming you remember to push every time you update something!) for the traditional Unix style tools that store their configuration in static flat files, but in 2015 you can’t really assume that this maps to every application you’re using. “I store my Xcode configuration in git” is a recipe for tears before bedtime. Additionally, while git’s configuration lives in ~/.gitconfig, Skype’s configuration lives in a rats nest of a custom hierarchy under the relatively arcane ~/Library/Application Support/Skype/. This is where Mackup enters the picture.

What Mackup does falls squarely into the “so simple it’s genius” category. It copies the configuration of every application that it knows about (and that you have installed) to a location (by default your Dropbox folder, under a “Mackup” directory, but this is easily changed) and replaces the old location with a symlink to the configuration’s new home. As a result, I can make a configuration change to an application on my work machine, and when I start that application at home, that configuration change magically propagates.

For those of us who work in security conscious environments, Mackup features both an application whitelist and an application blacklist, to your preference. I’m thrilled with using Dropbox to store my iTerm2 configs, but I’m not quite so content with storing my GPG keys there. There are also a couple of applications I want different configs on for different machines, so I can exclude those as well. Lastly, Mackup also features an “uninstall” command, so if you try it and discover it’s not for you, it sets things right as if it had never been used.

This handily solves the configuration question, but what about packages themselves? In case you’ve been living in a cave for the past few years, the Mac App Store is a burning dumpster fire of a neglected ecosystem; there’s no way to hook into it via an API, so you’re stuck with manual installation there. As a result, I tend to mostly ignore it in favor of homebrew (for command line utilities on the Mac) and homebrew-cask (like homebrew, except for graphical applications; it functions via installing packages into /opt/homebrew-cask/ and symlinking them into ~/Applications/ for you). Then I go about my day— “Sorry to do this to you, but you’re going to need to brew install gnu-cobol to get this node project to run—“ without caring about updating a manifest of packages that are installed. Then, either manually or via a scheduled task (launchd if you want to drink the OS X kool-aid, cron if you don’t and care to install it) launch a brew-bundle one-line task that dumps out the list of currently installed applications, and copies it to Dropbox:

brew bundle dump --force —file=~/Dropbox/Brewfile

I don’t tend to sync this between machines, as the list of packages I want installed on both differs, but it does make “replacing a computer” a trivial task— I simply install Homebrew and Dropbox, let it sync, then cd ~/Dropbox; brew bundle; mackup restore before stepping out for coffee. I’ll come back to all of my applications installed, configured, and waiting for me.

Note that this entire approach does have its limits. The idea of partial synchronization doesn’t really work here-- you can get granular down to the level of individual applications, but getting more fine-grained than that tends to not work. You can’t, for instance, sync some of your Firefox or Chrome bookmarks, but not include your extensions; at this point, Mackup is an all-or-nothing solution in the context of specific applications. Likewise, if you decide to share brew bundle files between machines, you’re going to have a difficult time reconciling different package build flags (which brew bundle captures!) between machines.

It’s also worth pointing out that this particular approach may not work for everyone. There’s a perfectly valid school of thought that says that you should opt for keeping things as close to pristine as possible-- and there’s a benefit to that. That said, for some use cases this workflow works well for me-- and I hope it’ll get you thinking about ways you can improve your own.


lavaman said...

You should check out boxen:

It was made by github to manage their macbooks with puppet.

Nate Olsen said...

Couldn't you use a file system monitor to push git changes to a specific group of files?

Anonymous said...

The problem that I encountered with using Boxen is that it's great for initial provisioning, but somewhat limited for ongoing configuration maintenance of a functioning system. It tends to not even work well being run on the same box twice in some experiments I ran on it half a year ago...