How to publish CLI app with Homebrew
Summary
In this post, I will show how to publish any CLI app to Homebrew. The formula will install the binaries from GitHub Releases for a completely free setup.I had quite a lot of problem figuring out how to do this, so I hope this post will help you. We will create a formula for our app and publish it to our own Homebrew Tap.
Setting up a tap
The first thing we need to do is to create a tap. A tap is a repository that contains formula. The formula is the actual CLI program and our tap can contain many Formulas. We can either publish our own tap our get our Formula into the official Homebrew repository which can take some time. In this post we will create our own tap.
The tap will be a GitHub repository that contains our Formula.
To create a tap, run:
brew tap-new <YOUR_GITHUB_USERNAME>/homebrew-<YOUR_TAP_NAME>
After this we will get a message that contains the path to our tap. Like this:
==> Created hugo-persson/dns-cli-tool-test/opt/homebrew/Library/Taps/<YOUR_GITHUB_USERNAME>/homebrew-<YOUR_TAP_NAME>
We can now navigate to this folder and create a new Formula. In your tap folder you should have the following structure:
.├── Formula└── README.md
Creating a Formula
For this step you need to have a compiled binary of your CLI app published on GitHub Releases. For a guide how to do this in Rust, see my post Publish a Rust CLI App with GitHub Actions. A simliar setup can be used for other languages.
We will now create a new Formula in the Formula folder.
cd Formulatouch <YOUR_FORMULA>.rb
The filename should be kebab-case and end with .rb
. For example dns-cli-tools.rb
.
Users will later install your CLI app by running:
brew tap <YOUR_GITHUB_USERNAME>/<YOUR_TAP_NAME>brew install <YOUR_GITHUB_USERNAME>/<YOUR_TAP_NAME>/<YOUR_FORMULA>
We now want to define how to install the CLI app.
For this step it is important that you have the binaries published on GitHub Releases.
Below I will refer to your github url as <YOUR_GITHUB_URL>
.
Replace this with the URL to your github repository. For example https://github.com/Hugo-Persson/dns-cli-tools
Getting the SHA256 hash
We need to get the SHA256 hash for the binaries. This is to ensure that the binaries are not tampered with. We can get the SHA256 hash by running:
VERSION=1.0.0 # Replace with your versionFILE_NAME=dns-cli-macos-arm64.tar.gz # Replace with the name of your binarywget -q "<YOUR_GITHUB_URL>/releases/download/$VERSION/$FILE_NAME"shasum -a 256 "$FILE_NAME" | awk '{ print $1 }
For my CLI app, I automated this with a simple bash script like this:
#!/bin/bashget_latest_release() { curl --silent "https://api.github.com/repos/$1/releases/latest" | # Get latest release from GitHub api grep '"tag_name":' | # Get tag line sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value}
VERSION=$(curl --silent "https://api.github.com/repos/Hugo-Persson/dns-cli-tools/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')echo "Updating to version $VERSION"
# cd to script dir
# Get the directory path of the script being runscript_dir=$(dirname "$(readlink -f "$0")")
# Change the working directory to the script's directorycd "$script_dir"cd ..cd Formula
sed -i "s/version \".*\"/version \"$VERSION\"/" ./dns-cli-tools.rb
BASE_URL="https://github.com/Hugo-Persson/dns-cli-tools/releases/download/$VERSION"
MAC_NAME="dns-cli-macos-arm64.tar.gz"LINUX_NAME="dns-cli-linux-amd64.tar.gz"
wget -q "$BASE_URL/$MAC_NAME"MAC_SHA=$(shasum -a 256 "$MAC_NAME" | awk '{ print $1 }')sed -i "s/mac_sha = ".*"/mac_sha = \"$MAC_SHA\"/" ./dns-cli-tools.rb # Update the mac sharm "$MAC_NAME"
wget -q "$BASE_URL/$LINUX_NAME" >/dev/nullLINUX_SHA=$(shasum -a 256 "$LINUX_NAME" | awk '{ print $1 }')sed -i "s/linux_sha = ".*"/linux_sha = \"$LINUX_SHA\"/" ./dns-cli-tools.rb # Update the linux sharm "$LINUX_NAME"
For the entire project see homebrew-dns-cli-tools.
Creating the Formula file
We will now create the Formula file. Replace the placeholders with your own values.
class YourFormulaName < Formula # Replace YourFormulaName with the name of your formula in CamelCase desc "A description of your formula" homepage "Link to your project" version "1.0.0" @github_base_url = "<YOUR_GITHUB_URL>/releases/download/#{version}" @mac_sha = "<SHA256 for macOS>" # Get this from the script above @linux_sha = "<SHA256 for Linux>" on_macos do url "#{@github_base_url}dns-cli-macos-arm64.tar.gz" # replace the filename with your own sha256 @mac_sha end
# Define the URL and SHA256 for Linux on_linux do url "#{@github_base_url}/dns-cli-linux-amd64.tar.gz" # replace the filename with your own sha256 @linux_sha end
def install bin.install "<BINARY_NAME>" # replace with the name of your binary end
test do system "#{bin}/<BINARY_NAME>" # replace with the name of your binary endend
After this you want to create a GithHub repository and push your tap to it.
After this you can install your CLI app by running:
brew tap <YOUR_GITHUB_USERNAME>/<YOUR_TAP_NAME>brew install <YOUR_GITHUB_USERNAME>/<YOUR_TAP_NAME>/<YOUR_FORMULA>