How to automate software installation and configuration on macOS using Homebrew and Stow
Tags: programming
Published: 2023-06-22 19:16:10

I'm a person that likes to customize most of my applications in a way that they work as I want them to work, my normal workflow includes several tools that my muscle memory expects to always be there.

I for example, spent many hours customizing my favorite editor [Neovim](https://neovim.io/), I know every shortcut defined on it because I created all of them, this is true for many tools I use every day, so you can imagine how time-consuming it would be to do this over and over every time I get a new computer, or I need to configure it from zero.

That's why I came up with a system to help me to:

- Install all the software I need, like normal shell applications, mac applications and also fonts

- Apply my saved preferred configuration for each of these applications

- Configure all macOS preferences automatically

In this post, I will talk about how I manage each one of the above points

## Installing software automatically
![Homebrew logo](https://images.ctfassets.net/fduzn3bxxb8f/7oq4oJUKalJ86As6EYjEC5/317a4569576652902811bad967de5e03/image.png)
I install almost 80 applications automatically every time I set up a new computer, Mac applications like `Firefox`, `Shottr`, `Anki` and tools like `git`, `node`, etc.

Installing all these tools one by one would be very tiresome, fortunately on Mac we have [Homebrew](https://brew.sh/) (`The Missing Package Manager for macOS`), with this tool we can install each application one by one like:


```bash
brew install git
# Or
brew install --cask firefox
```

But we can also define a file with all the software we want on our computer as a sort of `package.json` for node or a `Gemfile` for Ruby, this file is commonly named `Brewfile` (no file extension).

Let's see an example of a `Brewfile`


```ruby
tap 'homebrew/cask'
tap 'homebrew/cask-fonts'

brew 'neovim' # Improved Vim version

cask 'firefox' # The best browser

cask 'font-fira-code'
```

- The first two lines are to install additional `formulas`, formulas are just the way to tell `homebrew` how to install software, in this case the lines are to:

- Install additional Mac applications aka `casks`

- Install additional fonts

In this file you can put all the software you want, and then you just need to run the following command to install everything.


```bash
brew bundle
```

**PRO TIP**: add the `--v` to get more details of the progress of the installation 😎.

To know the name of the formulas you need to use the [Homebrew](https://brew.sh/) page and to know how to install specific versions or pass flags to the command check this [document](https://github.com/Homebrew/homebrew-bundle#usage).

## Apply saved configuration for each application
Most of the tools store their configuration in a file and most of the names of these files start with a `.` that's why you see this technique of saving configuration files called `dotfiles`, there are plenty of examples on GitHub of `dotfiles` repositories for each specific software, that you can see and take as an inspiration to configure the tools you want.

The main idea here is to configure everything the way you want and then save these files in say a git repository, so when you're configuring a new computer you just need to download this repository and copy the files to the right location.

There's already a tool that we can use to make this process easier, it's called [Stow](https://www.gnu.org/software/stow/manual/stow.html) and it's a UNIX tool that helps us to manage the configuration files, instead of just copying the files, `Stow` will create `Symbolic links` ([More details here](https://en.wikipedia.org/wiki/Symbolic_link)), this means in a nutshell, a file that points to another file, the advantage of this approach is that we can have all our configuration files in the same folder and repo and under `git` and then have a symbolic link file in the places where the final applications are expecting them to find them.

To install Stow just run on your terminal:


```bash
brew install stow
```

Or add it to your `Brewfile`


```ruby
brew 'stow'
```

### Stow usage example
Let's create a folder called `dotfiles` anywhere in our system and let's configure `git` as an example, git stores all its configuration in a file called `.gitconfig` in the user's root folder.

- Adding a new dotfile to our repository

In order to add a new dotfile to our repository, we need to first create a new folder for this new configuration file, **the name can be anything you want**, in our example let's call it `gitconfig/` then there are two ways to add a new `dotfile` to our new folder:

- Move the existing `.gitconfig` file if you already configured git before `mv ~/.gitconfig dotfiles-folder/gitconfig/.gitconfig`

- Manually create the file in the `gitconfig` folder in `dotfiles` if you don't have any configuration yet Put some configuration in this folder, for example:


```toml
[user]
  name = Miguel
  email = pepe@perez.com
```

No mater which way you choose, the folder structure of the `gitconfig/` folder should match exactly the folder structure from the root folder.

**Some examples:**

- if you want to have your dotfile in `~/.gitconfig` you will create the following folder structure `/folder-name/.gitconfig`

- Likewise, `~/.config/nvim/init.lua` `> =folder-name/.config/nvim/init.lua`

Repeat this process for all the configuration files you want to add to your new repository.

- Applying the dotfile configuration

And now to create the symbolic links or the next time you're configuring a new computer, you just need to run:


```bash
stow -nvSt ~ folder-name
```

If you are satisfied with the output, then remove the `-n` flag and that's it.

## Configuring macOS automatically
![Apple script](https://images.ctfassets.net/fduzn3bxxb8f/13GKcofUR4NGboxvpoDMs3/29fc26172f7f2ea0273f0450829a1ad2/apple_script.png)
I think this was the most tricky part of the whole automatization process because there's no much documentation about this, and it's mostly a trial and error process.

Forget about having to open the Mac system preferences one by one, the idea of this step is to save all the macOS configuration you like, so next time you're setting a new mac from zero you don't have to go to the system preferences, you only need to run a command.

This is for example some of my configuration, it configures the size of the Dock and sets `tap to click`, `dark mode`, etc.


```bash
# System Preferences > Dock > Minimize windows into application icon
defaults write com.apple.dock minimize-to-application -bool true

# System Preferences > Dock > Magnification:
defaults write com.apple.dock magnification -bool false

# System Preferences > Dock > Size:
defaults write com.apple.dock tilesize -int 36

# System Preferences > Trackpad > Tap to click
defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true
defaults write com.apple.AppleMultitouchTrackpad Clicking -bool true

defaults write NSGlobalDomain AppleInterfaceStyle Dark  # Use dark menu bar and dock.
```

### How to know the name of the variables
This is the tricky part… You can either:

- Read many `dotfile` repositories on GitHub until you find the setting that you want.

- Find it yourself by opening `System Settings` and run in the `Terminal` app


```bash
defaults read > before.txt
```

This will save all the current configuration in the `before.txt` file

- Make the changes to the configuration settings that you want to detect

- Use the same defaults read command to display the current user defaults.


```bash
defaults read > after.txt
```

- Do a diff to get the name of the key


```bash
diff before.txt after.txt
```

## Extra: Installing software from the App Store
![app store](https://images.ctfassets.net/fduzn3bxxb8f/5SSYfUzej7HMH8D5LHZ5no/59aaeb7354223f516953a0ea932b7951/app_store.png)
`Homebrew` by itself cannot install software from the `App Store` but we can use a tool called [mas](https://github.com/mas-cli/mas)


```bash
brew install mas
```

Or add it to your `Brewfile`


```ruby
brew 'mas' # Mac App Store manager

mas 'Pages', id: 409_201_541
```

You need to get the `id` of the applications manually by running


```bash
mas search Pages # Look for Apple Pages for example
```

## Putting all of this on a single file
To complete the automation, you can put all the commands in a `bash` file, I have a much more complex `setup.sh` file, but this is the basic idea:


```bash
#!/usr/bin/env bash

echo "Setting up this mac"

echo "Installing brew packages and casks"
brew bundle -v

echo "Moving dotfiles"
stow -vSt ~ neovim/

echo "Etc..."
```

Next time you're setting a new computer you will only need to run `bash ./setup.sh`

## Questions?
Let me know!