Cat Cai

Dotfiles and The Life-Changing Magic of Tidying Up

I’m excited to announce that I’m now the proud (and broke) owner of a brand new 15” Macbook Pro (2017).

I know, I know. I should have waited until Fall 2018 for Apple to announce a refresh. My rationale for getting the new laptop is mostly that I had been waiting for a refresh at WWDC. If none came, I was going to get one anyway since I’ve been holding off for a long time. Say what you will about Apple and their current state of hardware, I’ll take a touchbar and a garbage keyboard any day over any of the other hardware offerings out there right now. (I got myself a XPS 15 about a year and a half ago and booting Elementary OS and having to hack around with the graphics driver and the 4K display was a terrible experience.)

Anyway, that’s neither here nor there. I’m here to discuss dotfiles and bootstrapping a new laptop.

A lot of my friends have their development environment bootstrapping down to an art form. Some of them have public repos of well-organized dotfiles. My friend and former colleague Naftuli has an Ansible role set up for his Vim setup and for every single language he develops in (hell, for every language he MIGHT develop in). I’ve never been quite as… diligent about maintaining my setup for bootstrapping my laptop. I forked thoughtbot’s laptop script a while ago to help quickly bootstrap Grindr development environments a couple of years ago, but that’s about the extent of my upkeep for starting a development environment. I just keep my init.vim for Neovim and .zshrc in a secret gist on GitHub and copy paste it whenever I start a new job or get a new laptop. Then I just apt-get or brew whatever it is that I need whenever I’m doing something and run into realizing I don’t have a specific tool/package. It’s a pretty cringe and inefficient setup considering 90% of my job is identifying inefficiencies and writing tooling to automate those processes. (I promise I’m pretty decent at what I do. It’s just like how professional chefs probably don’t want to cook at home, right?)

I decided that since I was willing to invest a ton of money into a laptop for my work, I should also spend some time making it much cleaning up my development setup too. This meant modularizing my .zshrc and my Vim set up, which I hadn’t done before because I thought it was too much of a pain.

To give you a little idea of just how dilipidated/messy my setup was prior to this cleanup… well, my zsh and Vim setup are things I threw together when I was but a wee half-time software engineering intern baby child trying to figure out what Vim even was back in November of 2014. I just grabbed a ton of plugins on the recommendation of my coworkers at the time and got going. Over the years, I just kept tacking on stuff (with some minor cleanups here and there, I’m not a complete trash person) as I learned more languages and needed to expand my toolset. But tl;dr - yes, both my zshrc and init.vim needed a lot of love.

I first looked into something that I could manage and upload my dotfile managers with. I came across yadm, which really is just a nice wrapper around git and uploading dotfiles. There’s a couple other nice things that come with it like encryption, etc. I explored it for a bit, but honestly I didn’t get the sense that there wasn’t anything there that I couldn’t handle on my own with some Bash and git. However, I did come away from yadm discovering Brewfiles.

Having used Homebrew for years, I’m actually a little embarrassed that I’d never heard of Brewfiles or Homebrew-Bundle. Essentially, Brewfiles work like Ruby Bundler’s Gemfile. You dictate a recipe that outlines the brew dependencies that you want to download, run brew bundle and Homebrew will pull all the dependencies for you. Homebrew-Bundle also supports brew cask dependencies, along with Mac App Store downloads. This meant I could literally just install Homebrew and be able to pull everything I’ve ever wanted to use immediately. You can see my current Brewfile that I’m running here, though I’ve slowly been adding to it as I discover more and more desktop apps that I run into. Brewfiles essentially saved me from having to kludge together some sort of horrifying Bash script to curl/wget all of my base software dependencies.

I then just needed to modularize and split up my .zshrc and Neovim configurations.

For my zshrc, I really just needed to split up everything that I had in there. It was something like a 240-lined monster that was impossible to parse (to be fair, most of it was commented out settings from oh-my-zsh). After reading through a couple other people’s setups, I found most people split up their .bashrc or .zshrc by specific functions. For me, I ended up deciding to just split off environment variable exports, aliases, and functions, and then sourcing them into my main .zshrc file. My split up zsh files are here, though I just want to specifically call out these lines of bash (also apologies ahead of time for my not-so-great bash). I essentially declared an array of where each dotfile was, wrote a quick helper function to source the file (or echo an error if the file isn’t found) and looped through the array to source them.

Neovim was actually a little bit more tricky. I wasn’t really sure how to split it up other than knowing that my plugin installation declarations were really long, so that could be something I could pull out. Through a lot of Googling, I came across greg-js’s setup. (greg-js, whoever you are, you’re a G. Thanks for your help.) They ended up having their init.vim be just a file that sources other configs, and then splitting out their configurations into plugin installation, plugin configuration, setting sane Vim defaults, and key remappings. I ended up going with the exact same approach.

The last problem I had to solve was just having it be an automated process. I decided to throw together two quick bash scripts to handle bootstrapping a laptop running MacOS (I’ll write something for automating Linux one day when I get back to using my XPS… maybe…). The first is a bootstrap script that installs Homebrew, Homebrew-Bundle, rbenv and pyenv. It wraps up my copy pasting my .gitconfig and my .zsh over into the $HOME directory. The script isn’t the most elegant solution, but at the very least I can run it as many times as I want to. For now, the script will overwrite whatever’s in the $HOME directory if zsh or gitconfigs are present, though I think over time I’ll adapt it to ask for user input about overwriting certain files (if I ever get the patience to write that in Bash). The other bash script just installs Ruby, Python 2/3, and Node for Neovim. I wish I could have all of this in the same automation script, but I wasn’t sure how to source a new zshrc file to pick up all the changes necessary for pyenv, rbenv, and virtualenv to work right.

In any case, the solution’s a little clumsy, at least in terms of the bootstrapping portion, but I’m going to call the fact that I’ve cleaned up my dotfiles a win.