はじめに
写真を良い感じに整理するCLIツールを作り、GoReleaserでクロスコンパイルした成果物を自動的に配布できるようになったのはいいが、アップデートしたときなどいちいちバイナリを置き換えるのはダルい。
そこで、Homebrewで配布できるようにすれば楽なのでは無いか、と気軽に始めたらどハマりしたので、正攻法をまとめておく。この手の記事はその辺に転がっているのだが、Github Actionsの仕様変更、GoReleaserの仕様変更に追いついてなくて動かなかったりする。
当該CLIツール↓
Tap用リポジトリをつくる
brew tap
したときに参照するリポジトリをつくる。
このとき、リポジトリ名はhomebrew-${APP_NAME}
とする。中身に何か入れる必要はなく、空っぽでよい。後々、GoReleaserが自動的に構成ファイルをPushしてくれる。
今回はexiforge
という名前なので、homebrew-exiforge
とした。
Github Appsを設定する
前述したとおり、アプリのリポジトリで走るGithub Actionsから、Tap用のリポジトリに対して構成ファイルをPushできるようにする必要がある。従来はPATというトークンを使う手法があったが、今から作るならGithub Appsを使った方が便利だしセキュアだろう。
参考
つくりかた
まずはGithub Appsをつくる。
Account Settings → Developer settings → New GitHub App で作成画面までたどり着ける。
入力すべき項目は以下の2つだ。名前はなんでもいいし、Homepage URLもなんでもいい。
- GitHub App Name
- Homepage URL
また、Appが使われたときにWebhookでイベントの詳細を送ってくれる機能がデフォルトで有効になっているが、いらないので切っておく。
最後に権限の設定を行う。Repository permissionsを展開し、ContentsをRead and writeにする。
ここまできたら、一番下のCreate GitHub Appをクリックすれば、Github Appsが作成される。
Github Appsに対する設定
作ったAppをActionsで使えるようにする。
まずは、App IDを控える。これは、作成後表示された画面に表示されている。Aboutの中、Owned byの下にあるものだ。
また、private keyを作成する。このまま一番下まで行くと、Generate a private keyというボタンがあるので、クリックして作成する。そうすると鍵がダウンロードされる。
次に、アカウントに対して、Github Appをインストールする。
左側のメニューからInstall Appを選択し、Installボタンをクリックする。
このとき、このAppが使えるリポジトリを自分が所有するすべてにするか、絞ることができる。ここはお好みで。
少なくとも、homebrew-${APP_NAME}リポジトリで使えれば問題ない。
これでGithub Appsに対する設定は終わりだ。
リポジトリに対する設定
アプリケーションのリポジトリに対する設定を行う。
リポジトリのSettingsを開き、Security → Secrets and variables → Actions を開く。
New repository secretから、APP_ID
という名前で、先ほど控えたGithub AppのID、PRIVATE_KEY
という名前で、ダウンロードした秘密鍵の内容を貼り付ける。
これで、アプリケーションのリポジトリ内で走るGithub Actionsから、homebrew-${APP_NAME}リポジトリへのPushができるようになった。(正確にはもうワンステップをGithub Actions Workflow内にて踏む必要があるが。)
Github Actions Workflowをつくる
以下のようなWorkflowを作った。
バージョンのタグがPushされたタイミングで実行されるようにし、トークンの取得、Goのセットアップ、GoReleaserの実行というワークフローだ。
name: goreleaser
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate GitHub Apps token
id: generate
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23.6'
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
# 'latest', 'nightly', or a semver
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APP_TOKEN: ${{ steps.generate.outputs.token }}
重要なのは、Generate Github Apps tokenというjobだ。
ここで、先ほど設定したApp IDと秘密鍵を使って、トークンを取得する。このトークンを使うことで、やっとリポジトリへの操作ができるわけだ。
- name: Generate GitHub Apps token
id: generate
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
ここで発行されたトークンはsteps.generate.outputs.token
で参照できるので、これをAPP_TOKEN
としてGoReleaserに与える。homebrew-${APP_NAME}への構成情報ファイルはGoReleaserが生成し、Pushを行うので、こうしなければならない。
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
# 'latest', 'nightly', or a semver
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APP_TOKEN: ${{ steps.generate.outputs.token }}
GoReleaserの設定
最後にGoReleaserの設定だ。以下のような設定をしている。
version: 2
before:
hooks:
- go mod tidy
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
binary: exiforge
archives:
- formats: [ 'tar.gz' ]
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
format_overrides:
- goos: windows
formats: [ 'zip' ]
files:
- nothing*
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
brews:
- repository:
owner: # Githubのユーザー名
name: # homebrew-${APP_NAME}
token: "{{ .Env.APP_TOKEN }}"
Homebrewでパッケージ配布するにあたって重要なのはこの部分である。
brew以下に、オーナー名、Tap用のリポジトリ名、そしてGithub Appのトークンを設定する。
APP_TOKEN
は先ほどWorkflow内にて、設定した環境変数名だ。
brews:
- repository:
owner: # Githubのユーザー名
name: # homebrew-${APP_NAME}
token: "{{ .Env.APP_TOKEN }}"
これですべての設定が終わりだ。
Pushしてタグ付けPushして、お祈りを捧げる。
インストールしてみる
Workflowが無事完了したら、brew install
をしてみる。
$ brew tap ${USER_NAME}/${APP_NAME}
$ brew install ${USER_NAME}/${APP_NAME}/${APP_NAME}
これでインストールできれば、リリースとパッケージ配布の自動化は完了だ。
パッケージを更新したときは、こんな感じで更新できる。まあ、普通のbrewの操作である。
$ brew update
$ brew upgrade