How to Set Up Homebrew Tap for Private CLI Tools: A Complete Guide

如何为私有 CLI 工具提供 Homebrew 一键安装能力(完全指南)

这份指南总结了如何为内部或半公开的 CLI 工具构建稳定、全自动且支持降级的 Homebrew Tap 分发流程。主要经验基于为 mes-cli 开发 brew install 功能的实践。

1. 核心挑战与架构决策

在实现 brew install 时,由于 CLI 源码及 Releases 是私有仓库 (Private Repository),外部用户或内部员工在使用 brew install 下载时,如果在终端没有配置强权限的 GITHUB_TOKEN,会直接报 404 错误。

我们的解决方案:脱离 GitHub Releases,使用公开的 OSS / CDN
1. 源码编译与发布:CLI 仓库依然通过 GitHub Actions 完成编译跨平台包并生成 Releases。
2. 资产分发:流水线将生成的 ZIP 压缩包和 checksums.txt 同步推送到公开的阿里云 OSS(或 CDN)上。
3. Formula 托管:建立一个公开homebrew-tap 仓库。流水线根据 OSS 上的资源,自动拼接出 Ruby 安装脚本(Formula),推送到该 Tap 仓库。
4. 客户端安装:用户的 brew install 会从公开的 Tap 仓库拉取脚本,并从公开的 OSS 高速下载压缩包,全程无权限阻碍。


2. 前期准备工作

  1. 建立公开的 Tap 仓库
  2. 命名规范:在你的组织下新建一个公开仓库,通常命名为 homebrew-taphomebrew-brew
  3. 这样用户可以使用 brew tap org/tap 引入。
  4. 准备具有仓库读写权限的 Token
  5. 在 GitHub 中创建一个具有访问目标 Tap 仓库推拉权限的 PAT (Personal Access Token),或使用细粒度的 Token。
  6. 将该 Token 配置为 CLI 源码仓库的 Actions Secret(例如 SKILLS_REPO_TOKEN)。
  7. 规范化打包产物
  8. 确保你的打包脚本能生成跨平台的压缩包(例如 cli-0.1.0-macOS-arm64.zip)。
  9. 必须生成带有文件 SHA256 校验值的清单文件,如 checksums.txt,供后续提取使用。

3. GitHub Actions 流水线自动化

核心的魔法在于 CLI 源码仓库中的发布流水线(如 .github/workflows/release.yml)。它在每次打标签发布后,需要执行以下脚本去自动化生成 .rb 文件并提交。

自动化脚本模板

  update-homebrew-tap:
    name: Update Homebrew Tap Formula
    runs-on: ubuntu-latest
    needs: upload-oss # 必须等你的产物上传到公共 OSS 之后执行
    steps:
      - name: Checkout tap repo
        uses: actions/checkout@v4
        with:
          repository: your-org/homebrew-tap
          token: ${{ secrets.SKILLS_REPO_TOKEN }}
          path: homebrew-tap

      - name: Generate Formula and update tap
        env:
          VERSION: ${{ github.ref_name }} # 比如 v0.4.9
          REPO: ${{ github.repository }}
        run: |
          VER_NUM=${VERSION#v}

          # 1. 直接从公共 OSS 下载 checksums.txt (避免 GitHub 私有权限问题)
          wget "https://your-public-oss.com/tools/cli/${VER_NUM}/checksums.txt" -O checksums.txt

          # 2. 从 checksums.txt 中精准提取各平台的 SHA256 
          SHA_MAC_ARM=(grep "cli-{VER_NUM}-macOS-arm64.zip" checksums.txt | awk '{print $1}')
          SHA_MAC_AMD=(grep "cli-{VER_NUM}-macOS-amd64.zip" checksums.txt | awk '{print $1}')
          SHA_LIN_ARM=(grep "cli-{VER_NUM}-linux-arm64.zip" checksums.txt | awk '{print $1}')
          SHA_LIN_AMD=(grep "cli-{VER_NUM}-linux-amd64.zip" checksums.txt | awk '{print $1}')

          OSS_URL="https://your-public-oss.com/tools/cli"

          mkdir -p homebrew-tap/Formula

          # 3. 生成无特殊字符的类名后缀 (解决 Ruby 类名规范:例如 0.4.9 变成 049)
          CLASS_SUFFIX=(echo "VER_NUM" | sed 's/[^a-zA-Z0-9]//g')

          # --------------------------------------------------------------------
          # 生成 1:永远指向最新的主 Formula (cli.rb)
          # --------------------------------------------------------------------
          cat > homebrew-tap/Formula/cli.rb <
          class Cli < Formula
            desc "Your awesome CLI tools"
            homepage "https://github.com/$REPO"
            version "${VER_NUM}"

            if OS.mac? && Hardware::CPU.arm?
              url "{OSS_URL}/{VER_NUM}/cli-${VER_NUM}-macOS-arm64.zip"
              sha256 "${SHA_MAC_ARM}"
            elsif OS.mac? && Hardware::CPU.intel?
              url "{OSS_URL}/{VER_NUM}/cli-${VER_NUM}-macOS-amd64.zip"
              sha256 "${SHA_MAC_AMD}"
            elsif OS.linux? && Hardware::CPU.arm?
              url "{OSS_URL}/{VER_NUM}/cli-${VER_NUM}-linux-arm64.zip"
              sha256 "${SHA_LIN_ARM}"
            elsif OS.linux? && Hardware::CPU.intel?
              url "{OSS_URL}/{VER_NUM}/cli-${VER_NUM}-linux-amd64.zip"
              sha256 "${SHA_LIN_AMD}"
            end

            def install
              # 将二进制安装到系统 PATH
              bin.install "bin/cli"
              # 避坑点:如果有其他额外的目录或文件(如 skills/、assets/),必须显式复制到 prefix 下!
              prefix.install "skills"
            end

            def test
              system "#{bin}/cli", "--version"
            end
          end
          EOF

          # --------------------------------------------------------------------
          # 生成 2:带有版本号的防灾降级 Formula (cli@${VER_NUM}.rb)
          # --------------------------------------------------------------------
          # 代码完全同上,唯一区别是 Ruby 的类名必须加上 AT 版本号后缀
          cat > homebrew-tap/Formula/cli@${VER_NUM}.rb <
          class CliAT${CLASS_SUFFIX} < Formula
            # 内容与上方一致...
          EOF

      - name: Commit and push formula
        run: |
          cd homebrew-tap
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add Formula/
          if git diff --cached --quiet; then
            echo "No changes to commit."
          else
            git commit -m "chore: release formula for cli $VERSION"
            git push origin HEAD
          fi

4. 关键经验与防坑指南

4.1 额外目录被 Homebrew 丢弃的问题

现象:用户执行 brew install cli 后,发现二进制和 README.md 都有了,但压缩包里的其它自定义目录(如 skills/)不见了。
原因:Homebrew 默认只关心你在 def install 里指明的安装内容。
对策:如果 ZIP 包里附带了需要长期存储的文件夹,你必须通过 prefix.install "your-folder" 将其强行放入 Homebrew 的 Cellar 根目录。

4.2 提供“降版本”的后悔药机制

现象:如果最新发布的 CLI 携带了阻断性 Bug,CLI 自身的自动更新程序无法向下降级。而 Homebrew 原生的 brew switch 已经被官方弃用。
对策:如上述自动化脚本中的“生成 2”,除了生成固定的 cli.rb 之外,必须针对每一次发布生成带有版本号的文件,如 cli@0.4.9.rb
Ruby 语法限制:文件名包含 @. 时,Ruby 内部的 Class 名字必须去掉标点并转化为大驼峰。例如 cli@0.4.9.rb 内部类名必须叫 CliAT049,否则脚本报错。所以我们在流水线中加入了 CLASS_SUFFIX=(echo "VER_NUM" | sed 's/[^a-zA-Z0-9]//g') 进行动态替换。


5. 最终暴露给用户的完美用法

你只需要在文档中提供这段精简的指引即可:

安装与更新:

brew tap your-org/tap
brew install cli
# 未来的更新(自动更新本体、附加目录和文档)
brew upgrade cli

回退稳定旧版本:

# 假设最新版有问题,想要退回 0.4.9
brew install cli@0.4.9
brew unlink cli
brew link --overwrite cli@0.4.9

Make a perfect terminal experience from zero in 3 steps

Step1 – 确认已经在使用 zsh

kamus@Kamus-MBP-2015 ~ % echo $SHELL
/bin/zsh

如果不是,安装zsh并设置成默认shell

brew install zsh
chsh

Step2 – 安装必须的字体

因为我们期望在终端中可以显示丰富的图标,因此需要Nerd Font,可以从这个页面中挑选自己喜欢的字体

https://www.nerdfonts.com/

我个人很喜欢Sauce Code Pro这款。

Sauce Code Pro Nerd Font Complete Mono.ttf

Configure your terminal to use this font: Sauce Code Pro Nerd Font

以iTerm2为例,设置界面如下:

当然也可以选择本文要介绍的zsh4humans以及神级zsh样式powerlevel10k的原作者Roman Perepelitsa推荐的MesloLGS NF。

https://github.com/romkatv/powerlevel10k/blob/master/font.md

Step3 – 安装zsh4humans

https://github.com/romkatv/zsh4humans

if command -v curl >/dev/null 2>&1; then
sh -c “$(curl -fsSL https://raw.githubusercontent.com/romkatv/zsh4humans/v5/install)”
else
sh -c “$(wget -O- https://raw.githubusercontent.com/romkatv/zsh4humans/v5/install)”
fi

z4h不但内置了powerlevel10k的配置,而且自身还实现了很多ohmyzsh的插件功能,因此使用了z4h之后就不再需要单独进行onmyzsh的安装和配置,非常方便。

z4h在安装的同时也会拉取一份最新的ohmyzsh,如果确实需要修改z4h的默认配置而要加载更多ohmyzsh的插件,可以在~/.zshrc中进行修改。参看“z4h load ”部分的例子,可以自行加载需要的ohmyzsh包含的插件。

比如如果你更喜欢用autojump,而不是用z4h内置的fzf来进行历史目录的快速跳转;比如你喜欢sudo插件,双击ESC键就可以在上一条命令的前面加上sudo,可以通过在~/.zshrc文件中增加如下行来加载这些插件。

z4h load ohmyzsh/ohmyzsh/plugins/autojump
z4h load ohmyzsh/ohmyzsh/plugins/sudo

注意:以上的autojump仅仅是for zsh的插件,本体程序autojump还是要通过brew install autojump来安装的。

GitHub – wting/autojump: A cd command that learns – easily navigate directories from the command line

实际上根据z4h作者的说法,有很大用处的插件或者功能都基本上已经内置到z4h里了,比如对于上述的autojump功能,z4h内置了fzf,默认的快捷键是Alt(Option)+r,按一下有惊喜。同时还内置了Shift+左箭头快速跳转到之前的目录;Shift+上箭头快速跳转到父目录等快捷键。这些快捷键的设置在.zshrc中可以找到。

# Define key bindings.
z4h bindkey undo Ctrl+/ Shift+Tab # undo the last command line change
z4h bindkey redo Option+/ # redo the last undone command line change

z4h bindkey z4h-cd-back Shift+Left # cd into the previous directory
z4h bindkey z4h-cd-forward Shift+Right # cd into the next directory
z4h bindkey z4h-cd-up Shift+Up # cd into the parent directory
z4h bindkey z4h-cd-down Shift+Down # cd into a child directory

后续如果需要,可以执行一条命令就更新z4h

❯ z4h update
z4h: fetching z4h.zsh
z4h: updating zsh4humans
z4h: updating zsh-history-substring-search
z4h: updating zsh-autosuggestions
z4h: updating zsh-completions
z4h: updating zsh-syntax-highlighting
z4h: updating terminfo
z4h: updating fzf
z4h: updating fzf binary
z4h: updating tmux
z4h: updating ohmyzsh/ohmyzsh
z4h: updating powerlevel10k
z4h: updating gitstatus binary
z4h: initializing zsh
z4h: update successful
z4h: restarting zsh

如何在MarsEdit中使用Markdown编写文章

一直在寻找一个适合编写完毕以后直接发布到Wordpress中,同时又能完美支持Markdown语法的工具,尝试过Ulysess还有Typora,Ulysess本身对于Markdown的支持很奇怪,当粘贴的代码中有一些Markdown语法的关键字时,会出现很难编辑的情况;而Typora本身确实是很好的Markdown编辑器,但是无法直接将本地编写的文章一键发布到Wordpress中是致命弱点。

Setapp中近期引入了MarsEdit,这是一个较为著名的Blog编写工具,一键发布到Wordpress中是这类工具的标准功能,而稍作配置之后,又可以完美支持Markdown语法。

设置MarsEdit

首先需要设置MarsEdit
NewImage

NewImage

设置Wordpress

还需要设置Wordpress。在Wordpress中首先先安装Jetpack插件,实际上Jetpack已经基本上变为Wordpress的标准插件了。
在Jetpack插件的设置界面,将“使用Markdown语法编写纯文本文章”的选项勾上。
NewImage

用MarsEdit编写文章的优点

在MarsEdit中可以混用HTML和Markdown标志来编写文章,因此一些容易记忆的Markdown语法,比如标题,这是我最常用的Markdown语法,没有之一。比如代码块,对于写技术文章的人来说,代码块是非常方便的。

Markdown示例

以下是代码块的例子。

package main

import (
    "fmt"
    "database/sql"
    _ "gopkg.in/goracle.v2"