We Are Now On Homebrew

A long row of wooden casks racked in a dim cellar.

Photo by David Goldman on Unsplash

The whole Jorvik catalogue is on Homebrew as of this weekend. Twenty-four apps, games and screen savers, each one a line in a terminal:

brew install --cask perpetualbeta/jorvik/menutidy

No download page, no unzip, no dragging anything to Applications, no right-click-open dance. It fetches, verifies the checksum, and puts the app where it belongs.

That’s the headline. But the reason I actually wanted this — the reason it was worth a weekend — is further down this post, and it has nothing to do with saving three clicks.

Why it’s a tap and not the main repository

If you use Homebrew you’ll notice that command has my name in it. That’s a tap — a third-party repository — rather than the official homebrew/cask, and I want to be straight about why, because “why isn’t it just brew install --cask menutidy?” is a fair question.

(Homebrew’s vocabulary is brewing all the way down, in case you’re new to it. You tap a repository to draw from it, and the packages that install Mac applications are casks. Hence the photograph.)

Homebrew gates new casks on notability. The bar is thirty forks, thirty watchers or seventy-five stars — and if you’re submitting your own software, it triples to ninety forks, ninety watchers or two hundred and twenty-five stars.

My most-starred repository has twelve.

So this isn’t a near miss I could argue my way past, and it would have to be cleared twenty-four separate times. And honestly, the rule is right. Homebrew’s maintainers look after tens of thousands of packages for free; a popularity floor is a reasonable way to stop that becoming infinite. It just means the answer for software like mine is to run my own tap, which Homebrew explicitly supports and which costs you exactly one extra path component.

About that long command

You may be used to tapping a repository first and then installing by short name. Since Homebrew 6.0.0 there’s a wrinkle: a third-party tap has to be trusted before its casks will load, because a tap can run code with your privileges and Homebrew — quite rightly — stopped taking that on faith.

Naming the cask in full is the shortcut. It taps the repository, trusts that one cask and nothing else, and installs, in a single command. That’s why every example here is fully qualified.

If you’d rather type short names, trust it once and drop the prefix:

brew tap perpetualbeta/jorvik
brew trust --cask perpetualbeta/jorvik/menutidy
brew install --cask menutidy

The part I actually care about

Here’s the thing a download page can never do.

If you’ve been using a Mac for any length of time you’ve probably had the experience of setting up a new one, or rebuilding after a wipe, and spending an entire evening in a browser. Download, unzip, drag, launch, dismiss the quarantine warning, hunt for the licence, repeat. Twenty times. And you still forget three things until the day you need them.

The fix is a Brewfile: a plain text file listing everything your machine should have. Homebrew reads it and makes reality match. Most people who live in the terminal keep theirs in a dotfiles repository next to their shell config, and it turns machine setup from an evening into a coffee.

The tooling is built into Homebrew already — nothing to install.

Capture what you have

Start from your current machine rather than a blank page:

brew bundle dump --describe --file=~/dotfiles/Brewfile

That writes out every formula, cask and tap you currently have, with a comment describing each one. It’s the least effort you will ever expend on documentation.

Or write it by hand

A Brewfile is just a list. Mine has a Jorvik section that looks like this:

tap "perpetualbeta/jorvik"

cask "perpetualbeta/jorvik/menutidy"
cask "perpetualbeta/jorvik/activespace"
cask "perpetualbeta/jorvik/clipman"
cask "perpetualbeta/jorvik/hypercaps"
cask "perpetualbeta/jorvik/windowpin"
cask "perpetualbeta/jorvik/rainy-day"
cask "perpetualbeta/jorvik/citadel"

Use the fully-qualified names here too. It means the file works on a machine that has never heard of my tap, with no trust step and no ordering problem — which is the entire point of a file you hand to a fresh install.

Make it so

brew bundle install --file=~/dotfiles/Brewfile

That’s the whole setup. It taps what needs tapping, installs what’s missing, and skips what’s already there. Run it on a machine that’s already correct and it does nothing at all, which means it’s safe to run on a schedule or from a setup script without thinking about it.

Two companions worth knowing:

brew bundle check --file=~/dotfiles/Brewfile      # is this machine in the right state?
brew bundle cleanup --file=~/dotfiles/Brewfile    # what's installed that isn't in the file?

check is the one to put in a script — it exits non-zero when reality has drifted from the file, so it composes with anything. cleanup lists what you’ve installed and never wrote down; add --force when you’ve read the list and agree with it.

If you don’t keep apps in /Applications

Homebrew respects an environment variable for this, and brew bundle honours it:

export HOMEBREW_CASK_OPTS="--appdir=~/Applications --screen-saverdir=~/Library/Screen Savers"

Put that in your shell config and every cask — mine or anyone’s — lands where you want it.

The payoff

New Mac. Install Homebrew, clone your dotfiles, run one command, make coffee. Come back to a machine with everything on it, including the small utilities you’d otherwise have rediscovered one irritation at a time over the following fortnight.

That’s worth far more than the three clicks.

What happens at update time

Most Jorvik apps update themselves through Sparkle — the app notices a new version, tells you, and installs it. That could easily have fought with Homebrew, each convinced it owned the app and each undoing the other.

It doesn’t, because every cask that embeds Sparkle declares auto_updates true. Homebrew installs the app and then deliberately stays out of the way: it won’t reinstall on brew upgrade, and it won’t complain that the version on disk has moved ahead of the version it recorded. The app looks after itself, exactly as it does for everyone else.

If you’d rather Homebrew drove everything anyway — some people want one update mechanism and no surprises — that’s what greedy mode is for:

brew upgrade --cask --greedy

There is one exception, and it’s Reverie. It ships as a .saver bundle, and a screen saver has no process of its own sitting in the background to run an updater, so it has never had automatic updates in any form. For Reverie, Homebrew genuinely is the update path — which makes it the app that benefits most from all this.

ASCII Saver was in exactly the same position when I started writing this, and isn’t any more. It stopped being a .saver and became an ordinary app, which means it can now update itself like everything else. That happened the same day as this post, which tells you something about the order I do things in.

Two things that will trip you up

HawkEye is jorvik-hawkeye. There’s already a formula called hawkeye in Homebrew’s main repository, and a cask can’t share a name with a formula. Prefixing with the vendor name is Homebrew’s own convention for exactly this collision. Nothing about the app changed — it’s still HawkEye, still in the same place — only the string you type.

Ballast and SpaceMan have namesakes. Both collide with unrelated casks by other people. That’s allowed, and the fully-qualified name always resolves to mine, but if you’ve trusted the tap and you’re typing short names, brew install --cask ballast is ambiguous and Homebrew will say so. Qualify it and the ambiguity disappears.

The catalogue

Everything public is there: the eighteen menu-bar utilities, Jorvik Daily News, both games — Citadel and Strataris — and all three screen savers. Every product page now carries its own install line, so if you’re not sure of a name, the page will tell you.

Everything is signed, notarised, and public domain, as it has always been. The tap itself is open on GitHub if you want to read the casks before you run them — and given what a tap is allowed to do on your machine, reading them first is a habit I’d encourage with anyone’s, including mine.

The casks are generated rather than hand-written, from the same release metadata the website uses, and a scheduled job regenerates them daily. So the tap follows new releases on its own, and I can’t forget to update it — which, if I’m honest, was the real design requirement.