Homebrew 및 개인 GitHub 리포지토리
34319 단어 gohomebrewgoreleasergithub
HOMEBREW_GITHUB_API_TOKEN
환경 변수를 설정하여 private repo를 탭할 수 있었지만 설치할 때 curl
에서 404 오류가 발생했습니다. 검색을 통해 찾을 수 있는 문서와 정보는 존재하지 않거나 오래되었으므로 다른 사람들이 사용할 수 있도록 나에게 맞는 것을 얻을 수 있을 것이라고 생각했습니다.배경
내가 작성한 도구는 go 으로 작성된 작은 CLI 도구입니다. 버전semver으로 main에 대한 커밋에 태그를 지정하면 CI 도구는 GoReleaser을 사용하여 다양한 아키텍처용 바이너리를 빌드하고 GitHub 릴리스를 만들고 Homebrew 수식을 업데이트합니다. GoReleaser는 확실히 필요하지 않지만 작업을 매우 쉽게 만듭니다. 참고로 제
.goreleaser.yaml
는 다음과 같습니다.before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
ignore:
- goos: windows
goarch: arm64
archives:
-
replacements:
amd64: x86_64
darwin: Darwin
linux: Linux
format_overrides:
- goos: windows
format: zip
brews:
-
tap:
owner: myorg
name: myrepo
download_strategy: GitHubPrivateRepositoryReleaseDownloadStrategy
custom_require: "lib/custom_download_strategy"
commit_author:
name: My Name
email: [email protected]
folder: HomebrewFormula
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
저는 Homebrew 수식에 대해 별도의 저장소를 만들지 않고 대신
HomebrewFormula
라는 디렉토리의 도구 저장소에 수식을 넣었습니다. 이렇게 하면 탭 명령이 조금 더 길어지지만 저는 괜찮습니다. 따라서 Homebrew와 함께 배포하려는 각 도구에 대해 추가 저장소가 필요하지 않습니다.GoReleaser는
${toolname}_${semver_without_leading_v}_${platform}_${arch}.tar.gz
와 같은 이름의 각 플랫폼/아키(Windows용 zip)에 대해 gzip을 생성합니다. 이것은 Homebrew 다운로드 전략에 중요합니다.홈브류 소스
어떤 이유로든 유효한 액세스 토큰이 있어도 비공개 저장소에서 자산을 릴리스할 수 없습니다
curl
. 따라서 GitHub API를 사용하여 자산의 API URL을 가져오려면 몇 가지 코드가 필요합니다. 이것은 사용자 정의 다운로드 전략의 형태로 제공됩니다.필요한 작업을 수행했지만 이동하거나 변경된 일부 Homebrew 기능을 사용하는 오래된 코드를 찾았습니다. 그래서 더 파고들고 시행착오를 겪은 후 현재 버전의 Homebrew(이 글을 쓰는 시점 기준 3.3.12)에서 작동하도록 할 수 있었습니다.
HomebrewFormula/lib/custom_download_strategy.rb
require "download_strategy"
# S3DownloadStrategy downloads tarballs from AWS S3.
# To use it, add `:using => :s3` to the URL section of your
# formula. This download strategy uses AWS access tokens (in the
# environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`)
# to sign the request. This strategy is good in a corporate setting,
# because it lets you use a private S3 bucket as a repo for internal
# distribution. (It will work for public buckets as well.)
class S3DownloadStrategy < CurlDownloadStrategy
def initialize(url, name, version, **meta)
super
end
def _fetch(url:, resolved_url:, timeout:)
if url !~ %r{^https?://([^.].*)\.s3\.amazonaws\.com/(.+)$} &&
url !~ %r{^s3://([^.].*?)/(.+)$}
raise "Bad S3 URL: " + url
end
bucket = Regexp.last_match(1)
key = Regexp.last_match(2)
ENV["AWS_ACCESS_KEY_ID"] = ENV["HOMEBREW_AWS_ACCESS_KEY_ID"]
ENV["AWS_SECRET_ACCESS_KEY"] = ENV["HOMEBREW_AWS_SECRET_ACCESS_KEY"]
begin
signer = Aws::S3::Presigner.new
s3url = signer.presigned_url :get_object, bucket: bucket, key: key
rescue Aws::Sigv4::Errors::MissingCredentialsError
ohai "AWS credentials missing, trying public URL instead."
s3url = url
end
curl_download s3url, to: temporary_path
end
end
# GitHubPrivateRepositoryDownloadStrategy downloads contents from GitHub
# Private Repository. To use it, add
# `:using => :github_private_repo` 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::HTTPNotFoundError
# We only handle HTTPNotFoundError here,
# becase AuthenticationFailedError is handled within util/github.
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 => :github_private_release` 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://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", "--header", "Authorization: token #{@github_token}", 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)
end
end
# ScpDownloadStrategy downloads files using ssh via scp. To use it, add
# `:using => :scp` to the URL section of your formula or
# provide a URL starting with scp://. This strategy uses ssh credentials for
# authentication. If a public/private keypair is configured, it will not
# prompt for a password.
#
# @example
# class Abc < Formula
# url "scp://example.com/src/abc.1.0.tar.gz"
# ...
class ScpDownloadStrategy < AbstractFileDownloadStrategy
def initialize(url, name, version, **meta)
super
parse_url_pattern
end
def parse_url_pattern
url_pattern = %r{scp://([^@]+@)?([^@:/]+)(:\d+)?/(\S+)}
if @url !~ url_pattern
raise ScpDownloadStrategyError, "Invalid URL for scp: #{@url}"
end
_, @user, @host, @port, @path = *@url.match(url_pattern)
end
def fetch
ohai "Downloading #{@url}"
if cached_location.exist?
puts "Already downloaded: #{cached_location}"
else
system_command! "scp", args: [scp_source, temporary_path.to_s]
ignore_interrupts { temporary_path.rename(cached_location) }
end
end
def clear_cache
super
rm_rf(temporary_path)
end
private
def scp_source
path_prefix = "/" unless @path.start_with?("~")
port_arg = "-P #{@port[1..-1]} " if @port
"#{port_arg}#{@user}#{@host}:#{path_prefix}#{@path}"
end
end
class DownloadStrategyDetector
class << self
module Compat
def detect(url, using = nil)
strategy = super
require_aws_sdk if strategy == S3DownloadStrategy
strategy
end
def detect_from_url(url)
case url
when %r{^s3://}
S3DownloadStrategy
when %r{^scp://}
ScpDownloadStrategy
else
super(url)
end
end
def detect_from_symbol(symbol)
case symbol
when :github_private_repo
GitHubPrivateRepositoryDownloadStrategy
when :github_private_release
GitHubPrivateRepositoryReleaseDownloadStrategy
when :s3
S3DownloadStrategy
when :scp
ScpDownloadStrategy
else
super(symbol)
end
end
end
prepend Compat
end
end
테스트하지 않았지만 나중에 필요할 경우를 대비하여 파일에 남겨둔 몇 가지 추가 다운로드 전략이 있습니다. 그런 다음 Homebrew 공식에서 이 파일을 참조하고 Homebrew에 GitHubPrivateRepositoryReleaseDownloadStrategy를 사용하도록 지시할 수 있습니다.
HomebrewFormula/mytool.rb
# typed: false
# frozen_string_literal: true
# This file was generated by GoReleaser. DO NOT EDIT.
require_relative "lib/custom_download_strategy"
class Mytool < Formula
desc ""
homepage ""
version "1.1.5"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/myorg/mytool/releases/download/v1.1.5/mytool_1.1.5_Darwin_arm64.tar.gz", :using => GitHubPrivateRepositoryReleaseDownloadStrategy
sha256 "abc123..."
def install
bin.install "mytool"
end
end
if Hardware::CPU.intel?
url "https://github.com/myorg/mytool/releases/download/v1.1.5/mytool_1.1.5_Darwin_x86_64.tar.gz", :using => GitHubPrivateRepositoryReleaseDownloadStrategy
sha256 "qwerty987..."
def install
bin.install "mytool"
end
end
end
on_linux do
if Hardware::CPU.arm? && Hardware::CPU.is_64_bit?
url "https://github.com/myorg/mytool/releases/download/v1.1.5/mytool_1.1.5_Linux_arm64.tar.gz", :using => GitHubPrivateRepositoryReleaseDownloadStrategy
sha256 "f00bar..."
def install
bin.install "mytool"
end
end
if Hardware::CPU.intel?
url "https://github.com/myorg/mytool/releases/download/v1.1.5/mytool_1.1.5_Linux_x86_64.tar.gz", :using => GitHubPrivateRepositoryReleaseDownloadStrategy
sha256 "xyz543..."
def install
bin.install "mytool"
end
end
end
end
그런 다음 다음 명령을 실행하여 설치하기만 하면 됩니다.
HOMEBREW_GITHUB_API_TOKEN=ghp_abc123...
brew tap myorg/mytool https://github.com/myorg/mytool
brew install mytool
Reference
이 문제에 관하여(Homebrew 및 개인 GitHub 리포지토리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/jhot/homebrew-and-private-github-repositories-1dfh텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)