Laptop Local: A DevOps Oriented Workflow for Mac OS (X)

Baby Edward: ready for configuration management.

I’ve had the same laptop now since 2015, and after 3 years, its become a bit full of things. Old client things, old me things, millions of lines of development logs. I also wanted to experiment with zsh as my shell and revamp my vim setup, so it seemed like a good time to nuke my hard drive and start fresh. Coming off some time as a devops engineer, I also decided that I wanted to think a little differently about how I setup development dependencies like linux utilities, installed GUI programs, frameworks, and other dev tools. That is: treat my laptop with the same kind of idempotence and repeatability that I’d treat the web servers that run my applications. Laptop Local is my current solution. Below are descriptions of the core tools I am using to support this process. My core objective is to have no files or dependencies on my local laptop that exist undefined at some level in code, and no local files that are not replaceable by cloud copies. Mojave is coming, and I’ll be ready! Every new install will be a wipe and replace. At some point I’d like to be in a situation where I can pickup any laptop and with three things, be ready to work:

1) A clean install of OS X

2) My Apple ID login

3) Another device configured on my Keybase account (iPhone, eg)

Following is a list of core tools required to support the workflow.

After recently visiting and working with the folks at Thoughtbot, I was once again invigorated by Vim and willing to try the dotset that they maintain in replacement of Intellij Ultimate, which I’ve quite enjoyed over the last 4 or so years. While reading the README I realized they had an extensible install script (laptop) that setup most of the core dependencies I wanted for development.

I’ve used homebrew extensively in the past to install local versions of core packaged command line utilities. Things like git, imagemagick, Postgres, redis, etc. I was also aware of brew-cask but had not used it too extensively. Most notably, I always just installed these dependencies on the fly. If I realized I didn’t have imagemagick installed, I’d just hop onto the command line and run brew install imagemagick but, I never really kept track of what I added, which meant moving to a new machine provided a certain level of unpredictability.

It also meant that if I encountered strange issues, it was less clear where those issues might have originated.

Cask is an extension of homebrew used for managing installation of software that homebrew considers “outside” core. These are typically things like downloadable dmgs for GUI type applications. Casks follow a very similar DSL for defining the install process but thus far, I have not had to publish my own, as all the software I’ve needed has been available. Cask provides a search mechanism via the cli: brew cask search <term>, but I’ve had the most luck doing a google search for “cask <application>”.

New on my radar is the Mac App Store cli, or “mas” for short. The process is a little awkward in that you need to find the unique identifier of the application in order to install, but it is a small price to pay for managing app store applications in a declarative and repeatable process.

I’ve seen a few other folks using tools like Ansible to manage configuration on their macs, and this might be a direction I go in the future (though I’d probably use Chef) but I like being able to bootstrap the process with Bash as the only core dependency. There are certainly some major advantages to using a higher abstraction tool, like built in mechanisms to handle configuration secrets, but it does add a level of complexity and it is uncertain you could cover all edge cases for personalization of application installs (like adding a license key) without UI automation. For now, I’m OK with doing the final configuration by hand.

By moving my process to a version controlled process, I can better track what things I’ve added, when, and what, might have changed when a problem arises. I use git and store my repository on Github.

A unified process

Most importantly, I have a workflow that unifies how I added software to my computer. Before, I had to remember if I had installed TaskBadges through the Mac App Store or through Cask and I had a multitude of entry points to the process of adding software to my environment. I then had a separate process for configuration of these dependencies, and a final process for managing my development environment, aka dotfiles. Now, the entry point is the same for all changes to my laptop.

The following is an animated gif showing the installation of the tree dependency with homebrew.

Instead of just installing directly with brew install tree, I open up my editor, modify an inline brewfile in my laptop-local script, and then run the laptop/mac script which runs the core installs, and then picks up my customizations and runs those. The script detects a new dependency and installs it. Now I can commit this change to source control and it will be automatically installed in the future when I reinstall. Laptop also keeps this file updated with the most current release of tree.

The script is organized into three core segments:

1) An inline brewfile with any cask installed dependencies, followed by any brew installed dependencies.

brew bundle --file=- <<EOF
cask "1password"
cask "acslogo"
cask "encryptr"
cask "google-chrome"
cask "imageoptim"
cask "iterm2"
cask "keybase"
cask "rescuetime"
cask "semaphor"
cask "todotxt"
cask "zwift"

# fonts
tap "caskroom/fonts"
cask "font-fira-code"
cask "font-source-code-pro"

# for dotfiles
tap "thoughtbot/formulae"
brew "rcm"

brew "mas" # Apple App Store integration

brew "hugo"

brew "go"
brew "terraform"

brew "ttygif"
brew "imageoptim-cli"


2) Apple Store Installed Dependencies

# install Apps From App Store
mas install 568494494 # Pocket
mas install 1225570693 # Ulysses
mas install 1055511498 # Day One
mas install 1142578753 # Omnigraffle 7
mas install 803453959 # slack
mas install 454948164 #TaskBadges

# upgrade existing apps
mas upgrade

3) Any custom gems (for ruby development) not included by Laptop core, note I’m calling out to a pre existing bash function defined by Laptop core. Because it runs before, its scoped.

# gems
gem_install_or_update "middleman"
gem_install_or_update "middleman-blog"

4) Dotfile installation. Note: I have a dotfiles-local as well that provides customization to my dotfiles for my text editor and cli environments.

# bring in or pull from Thoughtbot's dotfiles
if [ ! -d "$HOME/dotfiles" ]; then
git clone git:// ~/dotfiles
git -C ~/dotfiles pull origin master

# source dotfiles
env RCRC=$HOME/dotfiles/rcrc rcup

Thanks for reading! I hope this helps open up some ideas for your own laptop management. I’m really excited about some of the newer options in this space like MAS and the growth of Cask.

A special thanks to Thoughtbot for their extensive open source work so that I can focus on “building on the shoulders of giant robots”. 🍻

Applause, shares, and follows are welcomed!

former pro cyclist turned polyglot programmer, husband and father. Influencer, Tech Leader, and Automator.