diff --git a/Library/Homebrew/env_config.rb b/Library/Homebrew/env_config.rb
index f658da479e..233decbdf9 100644
--- a/Library/Homebrew/env_config.rb
+++ b/Library/Homebrew/env_config.rb
@@ -38,6 +38,18 @@ module Homebrew
"A no-op when using Homebrew's vendored, relocatable Ruby on macOS (as it doesn't work).",
boolean: true,
},
+ HOMEBREW_BOTTLE_DOMAIN: {
+ description: "Use this URL as the download mirror for bottles. " \
+ "If bottles at that URL are temporarily unavailable, " \
+ "the default bottle domain will be used as a fallback mirror. " \
+ "For example, `HOMEBREW_BOTTLE_DOMAIN=http://localhost:8080` will cause all bottles to " \
+ "download from the prefix `http://localhost:8080/`. " \
+ "If bottles are not available at `HOMEBREW_BOTTLE_DOMAIN` " \
+ "they will be downloaded from the default bottle domain.",
+ default_text: "macOS: `https://ghcr.io/v2/homebrew/core`, " \
+ "Linux: `https://ghcr.io/v2/linuxbrew/core`.",
+ default: HOMEBREW_BOTTLE_DEFAULT_DOMAIN,
+ },
HOMEBREW_BREW_GIT_REMOTE: {
description: "Use this URL as the Homebrew/brew `git`(1) remote.",
default: HOMEBREW_BREW_DEFAULT_GIT_REMOTE,
diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb
index d6885aa4b7..0147c2e0d7 100644
--- a/Library/Homebrew/software_spec.rb
+++ b/Library/Homebrew/software_spec.rb
@@ -297,7 +297,7 @@ class Bottle
attr_reader :name, :resource, :prefix, :cellar, :rebuild
def_delegators :resource, :url, :verify_download_integrity
- def_delegators :resource, :cached_download, :fetch
+ def_delegators :resource, :cached_download
def initialize(formula, spec, tag = nil)
@name = formula.name
@@ -324,6 +324,15 @@ class Bottle
root_url(spec.root_url, spec.root_url_specs)
end
+ def fetch(verify_download_integrity: true)
+ @resource.fetch(verify_download_integrity: verify_download_integrity)
+ rescue DownloadError
+ raise unless fallback_on_error
+
+ fetch_tab
+ retry
+ end
+
def clear_cache
@resource.clear_cache
github_packages_manifest_resource&.clear_cache
@@ -427,6 +436,19 @@ class Bottle
specs
end
+ def fallback_on_error
+ # Use the default bottle domain as a fallback mirror
+ if @resource.url.start_with?(Homebrew::EnvConfig.bottle_domain) &&
+ Homebrew::EnvConfig.bottle_domain != HOMEBREW_BOTTLE_DEFAULT_DOMAIN
+ opoo "Bottle missing, falling back to the default domain..."
+ root_url(HOMEBREW_BOTTLE_DEFAULT_DOMAIN)
+ @github_packages_manifest_resource = nil
+ true
+ else
+ false
+ end
+ end
+
def root_url(val = nil, specs = {})
return @root_url if val.nil?
@@ -457,7 +479,11 @@ class BottleSpecification
def root_url(var = nil, specs = {})
if var.nil?
- @root_url ||= HOMEBREW_BOTTLE_DEFAULT_DOMAIN
+ @root_url ||= if (github_packages_url = GitHubPackages.root_url_if_match(Homebrew::EnvConfig.bottle_domain))
+ github_packages_url
+ else
+ Homebrew::EnvConfig.bottle_domain
+ end
else
@root_url = if (github_packages_url = GitHubPackages.root_url_if_match(var))
github_packages_url
diff --git a/Library/Homebrew/sorbet/rbi/hidden-definitions/hidden.rbi b/Library/Homebrew/sorbet/rbi/hidden-definitions/hidden.rbi
index 3002e458ba..0950035dc5 100644
--- a/Library/Homebrew/sorbet/rbi/hidden-definitions/hidden.rbi
+++ b/Library/Homebrew/sorbet/rbi/hidden-definitions/hidden.rbi
@@ -8331,6 +8331,8 @@ module Homebrew::EnvConfig
def self.bootsnap?(); end
+ def self.bottle_domain(); end
+
def self.brew_git_remote(); end
def self.browser(); end
diff --git a/docs/Manpage.md b/docs/Manpage.md
index 89eefb2c66..fdec00d5c3 100644
--- a/docs/Manpage.md
+++ b/docs/Manpage.md
@@ -1878,6 +1878,11 @@ example, run `export HOMEBREW_NO_INSECURE_REDIRECT=1` rather than just
- `HOMEBREW_BOOTSNAP`
If set, use Bootsnap to speed up repeated `brew` calls. A no-op when using Homebrew's vendored, relocatable Ruby on macOS (as it doesn't work).
+- `HOMEBREW_BOTTLE_DOMAIN`
+
Use this URL as the download mirror for bottles. If bottles at that URL are temporarily unavailable, the default bottle domain will be used as a fallback mirror. For example, `HOMEBREW_BOTTLE_DOMAIN=http://localhost:8080` will cause all bottles to download from the prefix `http://localhost:8080/`. If bottles are not available at `HOMEBREW_BOTTLE_DOMAIN` they will be downloaded from the default bottle domain.
+
+ *Default:* macOS: `https://ghcr.io/v2/homebrew/core`, Linux: `https://ghcr.io/v2/linuxbrew/core`.
+
- `HOMEBREW_BREW_GIT_REMOTE`
Use this URL as the Homebrew/brew `git`(1) remote.
diff --git a/manpages/brew.1 b/manpages/brew.1
index 850fbaea4a..f53def03a8 100644
--- a/manpages/brew.1
+++ b/manpages/brew.1
@@ -2644,6 +2644,15 @@ Use this as the \fBbat\fR configuration file\.
If set, use Bootsnap to speed up repeated \fBbrew\fR calls\. A no\-op when using Homebrew\'s vendored, relocatable Ruby on macOS (as it doesn\'t work)\.
.
.TP
+\fBHOMEBREW_BOTTLE_DOMAIN\fR
+.
+.br
+Use this URL as the download mirror for bottles\. If bottles at that URL are temporarily unavailable, the default bottle domain will be used as a fallback mirror\. For example, \fBHOMEBREW_BOTTLE_DOMAIN=http://localhost:8080\fR will cause all bottles to download from the prefix \fBhttp://localhost:8080/\fR\. If bottles are not available at \fBHOMEBREW_BOTTLE_DOMAIN\fR they will be downloaded from the default bottle domain\.
+.
+.IP
+\fIDefault:\fR macOS: \fBhttps://ghcr\.io/v2/homebrew/core\fR, Linux: \fBhttps://ghcr\.io/v2/linuxbrew/core\fR\.
+.
+.TP
\fBHOMEBREW_BREW_GIT_REMOTE\fR
.
.br