Using Homebrew to Distribute Early Access Binaries from Private GitHub Reposito

3 hours ago 1

Building for macOS has been… interesting. After falling into the trap of Microsoft’s 10x price gouging for macOS runners on GitHub Actions and ultimately switching to using the Mac Mini under my television as a self-hosted runner, the next thing I wanted to do was distribute my build artifacts.

I’m trying a different approach to building a new project this time around. Maintaining a popular piece of software is very draining, and working on a much-requested port of a popular piece of software to another operating system was not something I wanted to do in public.

So I’m building in private until I’m ready for an initial public release, and as a bonus, I finally have something concrete to offer to people who sponsor me on GitHub (early access).

Homebrew is, for better or worse, the most popular package manager on macOS and it can only be avoided for so long.

After setting up nightly builds on GitHub Actions, I wanted to create a Homebrew Tap and Formula for people who sponsor me on GitHub for early access - being able to brew install is a much more pleasant onboarding experience for a new sponsor than having to compile from source.

For public repositories, it’s possible to just use what the GitHub API refers to as the browser_download_url in a Homebrew Formula, take ripgrep for example:

class Ripgrep < Formula desc "Search tool like grep and The Silver Searcher" homepage "https://github.com/BurntSushi/ripgrep" url "https://github.com/BurntSushi/ripgrep/archive/refs/tags/15.1.0.tar.gz" # so easy! sha256 "046fa01a216793b8bd2750f9d68d4ad43986eb9c0d6122600f993906012972e8" license "Unlicense" head "https://github.com/BurntSushi/ripgrep.git", branch: "master" #... end

However, there is no combination of headers (that I could find) that can be used with a browser_download_url for a release artifact from a private repository which will allow it to be downloaded by Homebrew.

Instead we have to make a call to https://api.github.com, which will accept an Authorization header.

The specific endpoint we want is https://api.github.com/repos/$ORG/$REPO/releases/assets/$ASSET_ID.

While $ORG and $REPO are static, $ASSET_ID will change every time a new nightly release is created and a new artifact is attached to the release. But we can deal with that later.

We can get something hardcoded working if we target a specific asset:

class KomorebiForMacNightly < Formula desc "Tiling window manager for macOS (nightly build)" homepage "https://github.com/KomoCorp/komorebi-for-mac" url "https://api.github.com/repos/KomoCorp/komorebi-for-mac/releases/assets/308949887", headers: [ "Accept: application/octet-stream", "X-GitHub-Api-Version: 2022-11-28", "Authorization: bearer #‌{ENV.fetch("HOMEBREW_GITHUB_API_TOKEN")}", ] version "nightly" sha256 "d91583e5479ed0a23bfb9c7253e8f1f32dcedc0e0d1b886ff54d43894f670724" license "Komorebi License 2.0.0" def install bin.install Dir["*"] end def caveats <<~EOS This formula requires access to a private GitHub repository. Make sure you have set your GitHub token: export HOMEBREW_GITHUB_API_TOKEN=your_token_here EOS end test do system "‌#‌{bin}/komorebi", "--help" end end

The well-documented HOMEBREW_GITHUB_API_TOKEN from the user’s environment is used in the Authorization header to allow sponsors with access to the private repository to download the release artifact, with a useful caveat message if the user doesn’t have one set.

The next problem to solve: the $ASSET_ID and sha256 hash need to be updated whenever there is a new nightly release.

To handle this, I currently keep a template file in the project repo into which I inject updated values before committing it to my Homebrew Tap:

class KomorebiForMacNightly < Formula desc "Tiling window manager for macOS (nightly build)" homepage "https://github.com/KomoCorp/komorebi-for-mac" url "https://api.github.com/repos/KomoCorp/komorebi-for-mac/releases/assets/REPLACE_ASSET_ID", # <-- Update this headers: [ "Accept: application/octet-stream", "X-GitHub-Api-Version: 2022-11-28", "Authorization: bearer #‌{ENV.fetch("HOMEBREW_GITHUB_API_TOKEN")}", ] version "nightly" sha256 "REPLACE_SHA_256" # <-- Update this license "Komorebi License 2.0.0" def install bin.install Dir["*"] end def caveats <<~EOS This formula requires access to a private GitHub repository. Make sure you have set your GitHub token: export HOMEBREW_GITHUB_API_TOKEN=your_token_here EOS end test do system "‌‌#‌{bin}/komorebi", "--help" end end

In the final step of my GitHub Actions workflow, I create a new commit on my Homebrew Tap using this file, and then push the changeset:

jobs: build: steps: # previous build steps - shell: bash run: | ASSET_ID=$(gh api repos/${{ github.repository }}/releases/tags/nightly \ --jq '.assets[] | select(.name=="komorebi-nightly-aarch64-apple-darwin.zip") | .id') SHA256=$(cat checksums.txt | cut -d' ' -f1) echo "ASSET_ID=$ASSET_ID" >> $GITHUB_ENV echo "SHA256=$SHA256" >> $GITHUB_ENV - shell: bash run: | sed "s/REPLACE_ASSET_ID/$ASSET_ID/g; s/REPLACE_SHA_256/$SHA256/g" formula.template.rb > komorebi-for-mac-nightly.rb cat komorebi-for-mac-nightly.rb git clone https://x-access-token:${{ secrets.GH_USER_API_TOKEN }}@github.com/LGUG2Z/homebrew-tap cd homebrew-tap cp ../komorebi-for-mac-nightly.rb ./Formula git add . git commit -m "Update komorebi-for-mac-nightly to $ASSET_ID ($SHA256)" git push

In my opinion, there is a lot more friction in this process than there should be. The systems we use do not encourage the use case of independent developers trying to sustain themselves financially.

In any case, my GitHub Sponsors (thank you, all 52 of you!) who use Homebrew can now simplify their updates by updating through Homebrew instead of pulling the latest git changes and building from source.

brew tap lgug2z/tap brew install lgug2z/tap/komorebi-for-mac-nightly

Who knows, maybe you’ll want to try developing something in private with early access for sponsors in the future - hopefully this post will help future you.


If you have any questions or comments you can reach out to me on Bluesky and Mastodon.

If you’re interested in what I read to come up with software like komorebi, you can subscribe to my Software Development RSS feed.

If you’d like to watch me writing code while explaining what I’m doing, you can also subscribe to my YouTube channel.

If you would like early access to komorebi for Mac, you can sponsor me on GitHub.

Read Entire Article