macOS migrations with BrewfileApril 22, 2019
Perhaps the most-dreaded aspect of setting-up a new machine is the time spent on reinstalling apps and reapplying all of the customizations from the previous one. As my MacBook Pro is about to turn six, I had been looking for a way to automate this process. At least for the applications part, I recently found a good solution (that’s apparently been around for a while).
This post is about using a
Brewfile to migrate macOS packages and applications. If you’re already versed in the world of Homebrew and Homebrew Bundle, you might find it overly verbose. It’s written from a beginner’s perspective as up until recently I wasn’t too familiar with the concept myself.
Brewfile in a nutshell
Brewfile contains instructions on which packages, command-line utilities, and applications to install on a macOS system. Here’s a short snippet:
# Brewfile snippet # install Python and SQLite brew "python" brew "sqlite" # install 1Password, Pages, and Drafts from the Mac App Store mas "com.agilebits.onepassword-osx", id: 443987910 # 1Password mas "com.apple.iWork.Pages", id: 409201541 # Pages mas "com.agiletortoise.Drafts-OSX", id: 1435957248 # Drafts # install the apps below from Homebrew's repository cask "carbon-copy-cloner" cask "dropbox" cask "vlc"
If I were to “run” this
Brewfile, it would install the Python and SQLite packages, then 1Password, Pages, and Drafts from the Mac App Store, and finally Carbon Copy Cloner, Dropbox, and VLC from Homebrew’s repository (which usually pulls them from their respective websites). All apps are installed in the Applications folder by default, but the ability to differentiate between App Store and non App Store applications is significant in my case.
This is already faster than doing any of these steps manually. What’s more, a
Brewfile can be generated automatically so you’d rarely need to write the lines above one-by-one.
Because the alternatives aren’t as good.
Cloning: Using the excellent Carbon Copy Cloner to clone my old HD to the new one would theoretically be the quickest way to get going, but after 6 years, I imagine there’s more than a little cruft in my system files, and recent changes to Apple’s hardware make this option even less attractive. There are also apps on my current machine that I actually don’t want to move over.
Time Machine and/or Migration Assistant: Migration Assistant hasn’t been known for its reliability lately, and Time Machine backups are not less problematic. Listing the advantages and drawbacks is beyond the scope of this post, but if you want to read more about the pros and cons of each migration strategy, Jason Snell does a good job on that.
Starting fresh: Nothing could go wrong, but a lot of time spent on configuration and installing apps.
To understand what a
Brewfile does and how it can fit in a migration strategy, it’s good to be familiar with the moving parts that make it useful. This is not an exhaustive overview, but rather an introduction into each.
In the beginning, there was Homebrew, a package manager created by Max Howell in 2009. After installing
homebrew, you can open the Terminal and install packages easily and quickly:
# installs ffmpeg, a popular command-line package, on macOS $ brew install ffmpeg # now that ffmpeg is installed, we can use it: $ ffmpeg -i input.mp4 output.avi
Behind the scenes,
brew is using what it calls a “formula” to install the
ffmpeg package. This formula is a piece of code that’s responsible for holding all the information required to install
ffmpeg: its name, version, URL to the source files that should be downloaded, and other packages that
ffmpeg needs in order to operate.
Homebrew not only makes it easy to install packages, but also to maintain them:
# upgrades ffmpeg $ brew upgrade ffmpeg # upgrades all outdated formulae $ brew upgrade # update homebrew itself, and all packages $ brew update # uninstalls ffmpeg $ brew uninstall ffmpeg
And to discover them:
# search for youtube-dl $ brew search youtube-dl # get info about youtube-dl $ brew info youtube-dl
homebrew is very nice indeed. It’s lauded for its ease-of-use, documentation and helpful command-line feedback.
homebrew-cask is like
homebrew, but for macOS apps, fonts, plugins, and other non-open source software. If
brew install [formula-name] installs a package corresponding to that formula’s name, then
brew cask install [cask-appname] installs an application with that cask’s name:
# install firefox $ brew cask install firefox # install slack $ brew cask install slack
By default, it places installed apps in the Mac’s Applications directory. You can search for casks the same way you search for formulae:
$ brew search firefox # Output: ==> Casks firefox multifirefox homebrew/cask-versions/firefox-beta homebrew/cask-versions/firefox-developer-edition homebrew/cask-versions/firefox-esr homebrew/cask-versions/firefox-nightly
But where is
firefox coming from here? How does
brew cask install firefox know what to install?
$ brew cask info firefox # Output: firefox: 66.0.3 (auto_updates) https://www.mozilla.org/firefox/ Not installed From: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/firefox.rb ==> Name Mozilla Firefox ==> Languages cs, de, en-GB, en, eo, es-AR, es-CL, es-ES, fi, fr, gl, in, it, ja, ko, nl, pl, pt-BR, pt, ru, tr, uk, zh-TW, zh ==> Artifacts Firefox.app (App)
A few pieces of information here:
firefox: 66.0.3is the version we can expect
From:holds the URL where the
casklives. If you inspect it you’ll see that somewhere in there is also the URL one would go to in the browser when installing Firefox the “regular” way. There’s no magic here.
- Install options, like
brew cask install firefox --language=itwill install Firefox in Italian.
homebrew-cask is very, very nice.
Mac App Store command line interface
There’s one more tool that we need to cover before
mas-cli is a simple command line interface for the Mac App Store (MAS). It can’t install apps that you haven’t downloaded or purchased before, but it will allow you to upgrade those that you have installed, and download apps tied to your iCloud account:
# search for 1Password $ mas search 1Password # Output: 1333542190 1Password 7 - Password Manager (7.2.5) # install 1Password by its app identifier $ mas install 1333542190 # upgrade all apps that have pending updates $ mas upgrade # upgrade 1Password $ mas upgrade 1333542190
mas-cli may not seem terribly useful at first glance, but it was the missing piece in my migration strategy since it provides a way to capture all Mac Store apps currently installed:
# list all apps installed through the Mac App Store $ mas list # Output (truncated) 1225570693 com.ulyssesapp.mac (15.2) 986304488 com.zive.kiwi (2.0.18) 422304217 com.dayoneapp.dayone (1.10.6) # ^identifier # ^bundle name # ^version
mas-cli is nifty.
brew cask install, and
mas install make things a lot faster. The next step is to find a way to automate the generation and execution of these commands.
homebrew-bundle is an extension of
homebrew and is installed as soon as the command
brew bundle is first used. It’s the glue that brings everything together.
brew bundle dump and Homebrew Bundle will generate a file called
Brewfile listing all of the installed brew packages, cask applications, and Mac App Store applications currently on the machine. If, on the other hand, you run
brew bundle from a folder that contains a
Brewfile, it will install everything listed in that file.
So, given a
Brewfile with the following content:
# install Python and SQLite brew "python" brew "sqlite" # install 1Password, Pages, and Drafts from the Mac App Store mas "com.agilebits.onepassword-osx", id: 443987910 # 1Password mas "com.apple.iWork.Pages", id: 409201541 # Pages mas "com.agiletortoise.Drafts-OSX", id: 1435957248 # Drafts # install the apps below from their own respective websites cask "carbon-copy-cloner" cask "dropbox" cask "vlc"
brew bundle from the same directory where
Brewfile is located will install the above packages and applications.
Notice that the
Brewfile syntax differs from the commands you’d usually type in the Terminal. This table should help:
I think you know where this is going by now: run
brew bundle dump on the current machine, copy the
Brewfile generated to the new one, run
brew bundle, and Homebrew will take it from there. If you have lots of apps and packages the process will take some time, but nowhere near the time (or effort) it would have taken to do manually.
A quick-guide on setting up a new macOS using a Brewfile
Here’s an abbreviated guide to set-up a new macOS with Homebrew Bundle. Unless otherwise stated, all commands below are to be typed in the macOS Terminal prompt.
The steps involved are:
- Installing dependencies on the current (source) macOS machine
- Installing Homebrew taps
- Generating a
1. Installing dependencies on the source machine
Check if you already have Homebrew installed:
$ brew help
If Homebrew isn’t installed, the output should be something like
brew: command not found. Homebrew itself depends on the command line tools (CLT) for Xcode, installed like this:
$ xcode-select --install
You can then install Homebrew by pasting the following in your Terminal prompt:
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
If you do have Homebrew, and
help prints a long list of commands, it’s a good idea to run an update before proceeding:
$ brew update
Comes with Homebrew, but it doesn’t hurt to make sure it’s there:
# more on "tap" later $ brew tap caskroom/cask
Will be installed as we run it (later).
Mac App Store CLI
The way to install this
mas-cli varies depending on the OS version. You can find simple instructions in the project’s Github repository, but if you have a recent version this should suffice:
$ brew install mas
2. Installing Homebrew taps
taps as additional sources
brew will look at when searching and installing formulae and casks. Here’s what I recommend if you’re following this tutorial:
# for good measure, I've included the default taps: brew tap homebrew/bundle brew tap homebrew/cask brew tap homebrew/cask-fonts brew tap homebrew/core brew tap homebrew/services brew tap mas-cli/tap
3. Creating the Brewfile
Now that all of the of the dependencies are installed, let’s generate a
navigate to the user's home (~) directory cd "dump" (create) the Brewfile in our home directory based on which packages and apps are installed brew bundle dump
Brewfile may be missing non-MAS applications and packages that you haven’t installed with
brew cask. If you installed Firefox from Mozilla’s website,
homebrew-bundle doesn’t know about it. It’s easy enough to search for those and add them manually. And, it’s something you only have to do once since you’ll never ever again go to a website, find the install link, wait for the download to finish, and then drag the app icon to
Brewfile looks something like this:
tap "homebrew/bundle" tap "homebrew/cask" tap "homebrew/cask-fonts" tap "homebrew/core" tap "homebrew/services" # ... possibly more tap commands here brew "atomicparsley" brew "autoconf" brew "freetype" # ... more brew commands here cask "font-fira-mono" cask "sip" # ... more cask commands here mas "com.acqualia.soulver", id: 413965349 mas "com.agilebits.onepassword-osx", id: 443987910 mas "com.agiletortoise.Drafts-OSX", id: 1435957248 mas "com.apple.dt.Xcode", id: 497799835 mas "com.apple.iWork.Keynote", id: 409183694 mas "com.apple.iWork.Numbers", id: 409203825 mas "com.apple.iWork.Pages", id: 409201541 # ... more mas commands here
If you’d like to omit some packages or otherwise change the
Brewfile that your target macOS will use, you can simply copy the file somewhere else and make your changes there.
I keep my Brewfile in a Github repository, but you can place it in Dropbox, Google Drive, or wherever.
One more change I do is placing all
mas directives before the
cask ones, so the App Store version of an app is preferred in case that app is mistakenly listed in both sections.
The only dependency needed on the new machine is Homebrew (see step 1). That’s because the
Brewfile pulled from the old setup already stages all others for installation.
Once Homebrew is installed and a
Brewfile is present, it’s as simple as running:
$ brew bundle
brew bundle will look for a
Brewfile in the current directory, but you can also specify the path manually:
# will install from a Brewfile in the Dropbox folder $ brew bundle --file=~/Dropbox/
If you enjoyed this post, please consider donating to Homebrew.