Skip to content

Instantly share code, notes, and snippets.

@ZPascal
Forked from minamijoyo/hoge.rb
Last active June 28, 2025 21:50
Show Gist options
  • Save ZPascal/b21c652b811872b3f56db9d54d61d6c6 to your computer and use it in GitHub Desktop.
Save ZPascal/b21c652b811872b3f56db9d54d61d6c6 to your computer and use it in GitHub Desktop.
Using GitHubPrivateRepositoryReleaseDownloadStrategy removed in brew v2
require "formula"
require_relative "lib/private_strategy"
class Hoge < Formula
homepage "https://github.com/yourcompany/hoge"
url "https://github.com/yourcompany/hoge/releases/download/v0.1.0/hoge_v0.1.0_darwin_amd64.tar.gz", :using => GitHubPrivateRepositoryReleaseDownloadStrategy
sha256 "6de411ff3e4b1658a413dd6181fcXXXXXXXXXXXXXXXXXXXX"
head "https://github.com/yourcompany/hoge.git"
version "0.1.0"
def install
bin.install "hoge"
end
end
brew tap yourcompany/tap [email protected]:yourcompany/homebrew-tap.git
export HOMEBREW_GITHUB_API_TOKEN=xxx
brew install hoge
# Save this file as `lib/private_strategy.rb`
# Add `require_relative "lib/private_strategy"` to your formula.
#
# This is based on the following, with minor fixes.
# https://github.com/Homebrew/brew/blob/193af1442f6b9a19fa71325160d0ee2889a1b6c9/Library/Homebrew/compat/download_strategy.rb#L48-L157
# BSD 2-Clause License
#
# Copyright (c) 2009-present, Homebrew contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# GitHubPrivateRepositoryDownloadStrategy downloads contents from GitHub
# Private Repository. To use it, add
# `:using => GitHubPrivateRepositoryDownloadStrategy` to the URL section of
# your formula. This download strategy uses GitHub access tokens (in the
# environment variables `HOMEBREW_GITHUB_API_TOKEN`) to sign the request. This
# strategy is suitable for corporate use just like S3DownloadStrategy, because
# it lets you use a private GitHub repository for internal distribution. It
# works with public one, but in that case simply use CurlDownloadStrategy.
class GitHubPrivateRepositoryDownloadStrategy < CurlDownloadStrategy
require "utils/formatter"
require "utils/github"
def initialize(url, name, version, **meta)
super
parse_url_pattern
set_github_token
end
def parse_url_pattern
unless match = url.match(%r{https://github.com/([^/]+)/([^/]+)/(\S+)})
raise CurlDownloadStrategyError, "Invalid url pattern for GitHub Repository."
end
_, @owner, @repo, @filepath = *match
end
def download_url
"https://#{@github_token}@github.com/#{@owner}/#{@repo}/#{@filepath}"
end
private
def _fetch(url:, resolved_url:, timeout:)
curl_download download_url, to: temporary_path
end
def set_github_token
@github_token = ENV["HOMEBREW_GITHUB_API_TOKEN"]
unless @github_token
raise CurlDownloadStrategyError, "Environmental variable HOMEBREW_GITHUB_API_TOKEN is required."
end
validate_github_repository_access!
end
def validate_github_repository_access!
# Test access to the repository
GitHub.repository(@owner, @repo)
rescue GitHub::API::HTTPNotFoundError
# We switched to GitHub::API::HTTPNotFoundError,
# because we can now handle bad credentials messages
message = <<~EOS
HOMEBREW_GITHUB_API_TOKEN can not access the repository: #{@owner}/#{@repo}
This token may not have permission to access the repository or the url of formula may be incorrect.
EOS
raise CurlDownloadStrategyError, message
end
end
# GitHubPrivateRepositoryReleaseDownloadStrategy downloads tarballs from GitHub
# Release assets. To use it, add
# `:using => GitHubPrivateRepositoryReleaseDownloadStrategy` to the URL section of
# your formula. This download strategy uses GitHub access tokens (in the
# environment variables HOMEBREW_GITHUB_API_TOKEN) to sign the request.
class GitHubPrivateRepositoryReleaseDownloadStrategy < GitHubPrivateRepositoryDownloadStrategy
def initialize(url, name, version, **meta)
super
end
def parse_url_pattern
url_pattern = %r{https://github.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(\S+)}
unless @url =~ url_pattern
raise CurlDownloadStrategyError, "Invalid url pattern for GitHub Release."
end
_, @owner, @repo, @tag, @filename = *@url.match(url_pattern)
end
def download_url
"https://#{@github_token}@api.github.com/repos/#{@owner}/#{@repo}/releases/assets/#{asset_id}"
end
private
def _fetch(url:, resolved_url:, timeout:)
# HTTP request header `Accept: application/octet-stream` is required.
# Without this, the GitHub API will respond with metadata, not binary.
curl_download download_url, "--header", "Accept: application/octet-stream", to: temporary_path
end
def asset_id
@asset_id ||= resolve_asset_id
end
def resolve_asset_id
release_metadata = fetch_release_metadata
assets = release_metadata["assets"].select { |a| a["name"] == @filename }
raise CurlDownloadStrategyError, "Asset file not found." if assets.empty?
assets.first["id"]
end
def fetch_release_metadata
#release_url = "https://api.github.com/repos/#{@owner}/#{@repo}/releases/tags/#{@tag}"
#GitHub::API.open_rest(release_url)
GitHub.get_release(@owner, @repo, @tag)
end
end
@alejakun
Copy link

alejakun commented May 16, 2022

Hi, sorry for the late reply, the curl command with the suggested format is working, however I don't know how to translate that to the URL in my cask definition.

Edit: Finally made it work, it seems I was so into it that I missed some parts of the url. Anyways you nailed it, thanks for the support!

@ZPascal
Copy link
Author

ZPascal commented May 17, 2022

Hi @alejakun, great to hear that now everything works as aspected.

@mtintes
Copy link

mtintes commented Oct 5, 2022

I am not sure this is the exact place for it but has anyone using this seen an error Error: can't modify frozen class while using these scripts? I am running a brew install for a private github repo when I get this error. My google-fu is weak so taking a shot in the dark in case it is more widespread.

@ZPascal
Copy link
Author

ZPascal commented Oct 5, 2022

Hi @mtintes, could you please post the output of the --debug flag of the installation command?

@mtintes
Copy link

mtintes commented Oct 5, 2022

One of coworkers ended up finding an answer (sort of). This was the original error. konductor is a cli written in go in this case.

➜  brew install konductor --debug --verbose             
/opt/homebrew/Library/Homebrew/brew.rb (Formulary::FormulaLoader): loading /opt/homebrew/Library/Taps/<org>/homebrew-konductor/formula/konductor.rb
/opt/homebrew/Library/Homebrew/shims/shared/curl --disable --cookie /dev/null --globoff --show-error --user-agent Homebrew/3.6.4\ \(Macintosh\;\ arm64\ Mac\ OS\ X\ 12.5.1\)\ curl/7.79.1 --header Accept-Language:\ en --retry 3 --location https://api.github.com/repos/<org>/konductor --header Accept:\ application/vnd.github\+json --write-out '
'\%\{http_code\} --header Authorization:\ token\ ****** --dump-header /private/tmp/github_api_headers20221005-47324-1n0405m
/opt/homebrew/Library/Homebrew/shims/shared/curl --disable --cookie /dev/null --globoff --show-error --user-agent Homebrew/3.6.4\ \(Macintosh\;\ arm64\ Mac\ OS\ X\ 12.5.1\)\ curl/7.79.1 --header Accept-Language:\ en --retry 3 --location --silent --head --request GET https://github.com/<org>/konductor/releases/download/v1.17.9/konductor-osx-amd64.tar.gz
==> Downloading https://github.com/<org>/konductor/releases/download/v1.17.9/konductor-osx-amd64.tar.gz
/opt/homebrew/Library/Homebrew/shims/shared/curl --disable --cookie /dev/null --globoff --show-error --user-agent Homebrew/3.6.4\ \(Macintosh\;\ arm64\ Mac\ OS\ X\ 12.5.1\)\ curl/7.79.1 --header Accept-Language:\ en --retry 3 --location https://api.github.com/repos/<org>/konductor/releases/tags/v1.17.9 --header Accept:\ application/vnd.github\+json --write-out '
'\%\{http_code\} --header Authorization:\ token\ ****** --dump-header /private/tmp/github_api_headers20221005-47324-f7o4v1
/opt/homebrew/Library/Homebrew/shims/shared/curl --disable --cookie /dev/null --globoff --show-error --user-agent Homebrew/3.6.4\ \(Macintosh\;\ arm64\ Mac\ OS\ X\ 12.5.1\)\ curl/7.79.1 --header Accept-Language:\ en --fail --retry 3 --location --remote-time --output /Users/miketintes/Library/Caches/Homebrew/downloads/e21e5b9112fcd59c4caec2c40316e0fe0c08cab973ba491d5ccd1460569812b8--konductor-osx-amd64.tar.gz.incomplete https://******@api.github.com/repos/<org>/konductor/releases/assets/79035814 --header Accept:\ application/octet-stream
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 30.6M  100 30.6M    0     0  13.2M      0  0:00:02  0:00:02 --:--:-- 16.4M
==> Verifying checksum for 'e21e5b9112fcd59c4caec2c40316e0fe0c08cab973ba491d5ccd1460569812b8--konductor-osx-amd64.tar.gz'
==> Installing konductor from <org>/konductor
/opt/homebrew/Library/Homebrew/brew.rb (Formulary::TapLoader): loading /opt/homebrew/Library/Taps/<org>/homebrew-konductor/Formula/konductor.rb
Error: can't modify frozen class
/opt/homebrew/Library/Taps/<org>/homebrew-konductor/Formula/lib/private_strategy.rb:98:in `<class:GitHubPrivateRepositoryReleaseDownloadStrategy>'
/opt/homebrew/Library/Taps/<org>/homebrew-konductor/Formula/lib/private_strategy.rb:97:in `<top (required)>'
/opt/homebrew/Library/Taps/<org>/homebrew-konductor/Formula/konductor.rb:2:in `require_relative'
/opt/homebrew/Library/Taps/<org>/homebrew-konductor/Formula/konductor.rb:2:in `block in load_formula'
/opt/homebrew/Library/Homebrew/formulary.rb:90:in `module_eval'
/opt/homebrew/Library/Homebrew/formulary.rb:90:in `block in load_formula'
/opt/homebrew/Library/Homebrew/formulary.rb:102:in `load_formula'
/opt/homebrew/Library/Homebrew/formulary.rb:122:in `load_formula_from_path'
/opt/homebrew/Library/Homebrew/formulary.rb:321:in `load_file'
/opt/homebrew/Library/Homebrew/formulary.rb:478:in `load_file'
/opt/homebrew/Library/Homebrew/formulary.rb:311:in `klass'
/opt/homebrew/Library/Homebrew/formulary.rb:306:in `get_formula'
/opt/homebrew/Library/Homebrew/formulary.rb:468:in `get_formula'
/opt/homebrew/Library/Homebrew/formulary.rb:560:in `factory'
/opt/homebrew/Library/Homebrew/formula_installer.rb:374:in `install'
/opt/homebrew/Library/Homebrew/upgrade.rb:212:in `install_formula'
/opt/homebrew/Library/Homebrew/install.rb:333:in `install_formula'
/opt/homebrew/Library/Homebrew/install.rb:323:in `block in install_formulae'
/opt/homebrew/Library/Homebrew/install.rb:322:in `each'
/opt/homebrew/Library/Homebrew/install.rb:322:in `install_formulae'
/opt/homebrew/Library/Homebrew/cmd/install.rb:226:in `install'
/opt/homebrew/Library/Homebrew/brew.rb:95:in `<main>'

We had to copy a bunch of old code from homebrew that looks like it got deprecated and put that in the private strategy. This is a copy of that. No idea if that is the best path since neither of us does anything with ruby.

@stevenpitts
Copy link

For anyone recently having issues with this, see Homebrew/brew#15169. CurlDownloadStrategy changed a bit.

This fix worked for me.

@roeeyn
Copy link

roeeyn commented Jul 4, 2023

For anyone recently having issues with this, see Homebrew/brew#15169. CurlDownloadStrategy changed a bit.

This fix worked for me.

It worked for me too. Thanks for sharing.

@terrabitz
Copy link

I'm not sure when Homebrew added it, but it looks like they now have a GitHub::API.credentials helper, documented here:https://rubydoc.brew.sh/GitHub/API.html#credentials-class_method. Admittedly this says it's a private API, so YMMV.

This helper looked for a token in 3 separate sources:

  • An env var named HOMEBREW_GITHUB_API_TOKEN
  • The token managed by the gh utility (i.e. what you get when you do gh auth login)
  • A GitHub username/password combo kept in the MacOS keystore

I already use the gh utility, which makes it a lot more seamless than having to provide a PAT every time I wanted to update. I used the above strategy, but with one tweak:

  def set_github_token
    # See https://rubydoc.brew.sh/GitHub/API.html#credentials-class_method
    @github_token = GitHub::API.credentials
    raise CurlDownloadStrategyError, "A GitHub token is required. Either set HOMEBREW_GITHUB_API_TOKEN or login with: gh auth login" if @github_token.nil? || @github_token.empty?

    validate_github_repository_access!
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment