Build a macOS Empire 👑

Best practices, inspiration, and guidance for you to get your macOS from zero to hero for software development.

What is a macOS empire?

I define it so that you:

  • Open Safari on your factory-setup macOS.
  • Go to your application configurations repository (Dotfiles).
  • Copy a one-liner command from your repository’s
  • Paste it into your terminal and press enter.
  • Type your sudo password and sit back.
  • Have a complete macOS software development environment set up and ready to use.

That gives you a macOS empire and turn you into a Ruler đź‘‘

In this story, I’ll explain how building a macOS empire came to me, how I built mine, and how you can build yours.

Picture by goklimkin on pixabay

Why did I decide to build my macOS empire?

It originated from health concerns. My wrists started to hurt a few months ago due to pushing way too many buttons for money and too much mouse usage. I fixed the physical aspect of the problem by sitting properly in my chair and purchasing an ergonomic keyboard, but the mouse usage strain remained. So I decided to eliminate mouse usage as much as I can.

Fish shell, Vim text editor, and Tmux terminal-multiplexor were the solutions I picked for myself. This meant hundreds of shortcuts and configurations I had to tweak and manage. So I put my Fish, Vim and Tmux config files in my configurations repository, also known as Dotfiles.

It wasn’t until I was gifted a new MacBook however that I realized there’s a whole lot more to setting up a fresh macOS than just having my most frequently used applications config files in my Dotfiles repository. A package manager, all the applications to install with that package manager, system preferences, shortcuts for those applications and more had to be done. I ended up doing so much manual work to set up the gifted MacBook that my inner-automation-child refused the prospect of having to do it again. I decided to automate it. I decided to build a macOS empire and be the Ruler of it.

How did I build my macOS empire?

I asked myself what do I want in a software that would set up my macOS software development environment entirely, and the rest followed smoothly. The wisdom behind that question doesn’t deserve to be credited to me, however.

If I would be given an opportunity to write the most important concept I learned in software development in 2017, I would write the law of user focus. I quote directly from Fish shell design document:

When designing a program, one should first think about how to make an intuitive and powerful program. Implementation issues should only be considered once a user interface has been designed.
fish_greeting Fish shell function by rkalis on GitHub

So, “What do I want in a software… “ question is essentially similar to asking “What kind of user interface I would like to have?”. The answer was such that I liked the interface to:

  • Be a one-liner => To copy from Dotfiles repository’s
  • Have no dependencies => Should be executable on a fresh macOS.
  • Require no manual intervention => Apart from initial sudo prompting, “it shouldn’t insist on interactive input” as McIlroy puts it.

With those interface requirements in place for the software I was going to write, I created a macOS bootstrapping bash script called in my Dotfiles repository which printed “Hello World!”. macOS comes with bash shell by default so the script will be executable on a fresh macOS. I also updated the with a one-liner command to execute that bash script. Keeping the no manual intervention in mind, all requirements were fulfilled.

macOS bootstrapping script

After setting up a virtual macOS High Sierra and taking a snapshot from it, I was ready to implement the steps necessary to setup my macOS one at a time. Throughout this process and while going through almost all repositories featured on , I learned some best practices and some pitfalls to avoid which I’d like to share. Let’s begin.

Write top-to-bottom, function-based scripts

I found several macOS bootstrapping scripts in featured repositories on that were just a blob of code with an attempt to be readable with visual comments. While visual comments are helpful visually, they offer no code modularity and they’re difficult to debug. With a function-based approach, you can comment out the steps you’d like to disable quickly, you can test each step individually using a virtual macOS and leveraging snapshots to ensure all steps run without errors, and you can make sense out of what you did both now and in the future. Top-to-bottom approach also increases readability of your script and saves people time when they want to familiarize themselves with what your script actually does.

Write command-line options in full in your *scripts*

I promised myself as a 2018-new-year resolution to memorize all the options for grep command-line application. One by one. -a is --text, -bis --byte-offset and so on. Said no one ever.

Do everyone a favor and write command-line application options in full. This makes a visit to the terminal, typing man <APPLICATION>, navigating to the option, and seeing what on earth it does unnecessary if you would be kind to type the options in full. Short-hand options are for running ad-hoc commands. I also write grep -Fxq if I want to find out if a file contains a line in bash, but not when writing a script from which I see a potential of people looking at it and trying to make sense out of it.

Eliminate interactivity in your scripts

“Press RETURN to continue…”. Fairly disappointing. If only the Homebrew installer could parse and respect an option, say, --no-prompt, then I wouldn’t have had to remove its interactivity myself in a custom homebrew_installer script, which is identical to the original installer with the exception of a single line of code removed. So, don’t be afraid to change other people’s code to bend it at your will for your scripts, because when using your macOS bootstrapping script, you’ll want to be singing “I don’t want control, I want to let go” as P!nk puts it; remove the interactivity, so you can sit back and let go.

Design for repetitive execution of your script, and execute it often

Think of the steps of your macOS bootstrapping script and determine which ones prevent you from repeatedly executing the bootstrapping script and write those steps in another script. For example, I use some Vim plugins which need to be compiled to work, and the compilation takes around 2 minutes. There’s no way I could put them in my bootstrapping script and be able to execute it repeatedly as it takes way too long. Instead, I have an executable function in my shell which compiles those plugins which I need to run once for a fresh macOS or if I update the plugins.

This frequent execution of your bootstrapping script will allow it to evolve slowly and steadily, and prevent it from going out of date, if you commit to it. So, for example, if you save a new configuration file and you want to symlink it, do it in your bootstrapping script and then execute it instead of doing it manually.

Install Homebrew first, not xcode-select

Homebrew’s installer can install xcode-select without any GUI popping up, i.e. headlessly, which is nice. Quite a few Dotfiles repositories instructing to install xcode-select before installing brew, which is unnecessary given Homebrew’s installer ability to do it without interactivity. It’ll fall back to GUI installer of xcode-select in case of failure to install it headlessly.

Install packages using Brewfile

Don’t write orchestration logic for all the system-wide packages you would like to install in a bash script. Use a Brewfile and install the packages within it with a simple: brew bundle. This way, you can have a versioned file in your Dotfiles repository and you immediately get all the benefits of git like revert. Combine revert with brew bundle cleanup and you’ll always have exactly what you define in your Brewfile installed on your macOS.

Choose a shell

Make a conscious decision about your primary shell. I had been using bash since it was the default on macOS anyway. I switched to zsh and it blew me away, I was suddenly a lot more productive. I switched to fish after a year of zsh and I couldn’t believe I could be blown away again, but I was wrong. Going through the features of these different shells is outside the scope of this story, but do your own research and choose one. I recommend fish.

Iterate on your *own* configuration files

Configurations are personal. They’re meant to be tweaked to supreme levels. Think twice before forking someone else’s Dotfiles repository and using it for yourself. Remember that applications evolve and so their default options. The tweaks someone has done for their Tmux two years ago could as well be the default for Tmux in its latest version today or have been removed entirely. Use your own configuration files, get inspired by other people’s Dotfiles repositories, understand them, copy them for yourself, and tweak them if necessary.

Remain on the latest-and-greatest

Keep an executable function handy that updates “everything”. You get to decide what to update, but a good starting point would be all software updates from the App Store and all installed packages using brew.

How could you build your *own* macOS empire?

  • Create your own Dotfiles repository, if you don’t have one yet.
  • Write a one-liner command in its to execute a macOS bootstrapper bash script.
  • Save your application configuration files in your repository.
  • Incrementally implement the steps in your bootstrapper bash script that you need in order to setup your macOS environment for software development.
  • Keep the best practices discussed here in mind.