Skip to content

Skip duplicate /v2/ when artifact domain already contains it - #23062

Merged
MikeMcQuaid merged 1 commit into
Homebrew:mainfrom
jouve:artifact-domain-oci-path
Aug 17, 2026
Merged

Skip duplicate /v2/ when artifact domain already contains it#23062
MikeMcQuaid merged 1 commit into
Homebrew:mainfrom
jouve:artifact-domain-oci-path

Conversation

@jouve

@jouve jouve commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
  • Have you followed our Contributing guidelines?
  • Have you checked for other open Pull Requests for the same change?
  • Have you explained what your changes do? Performance claims (e.g. "this is faster") must include Hyperfine benchmarks.
  • Have you explained why you'd like these changes included, not just what they do?
  • For bug fixes, have you given step-by-step brew commands to reproduce the bug?
  • Have you written new tests (excluding integration tests)? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) locally?

  • AI was used to generate or assist with generating this PR.

I used Claude Code to help draft the patch, tests and this PR text. I manually reviewed the changes and verified them end-to-end against a real Harbor 2.x proxy cache for ghcr.io (brew fetch and brew reinstall of bottles, plus brew vendor-install ruby for the Bash code path), and ran brew lgtm successfully.


What

As suggested in review: when HOMEBREW_ARTIFACT_DOMAIN already contains the Docker Registry API's /v2 path, skip the v2/ from the original ghcr.io URL instead of duplicating it.

HOMEBREW_ARTIFACT_DOMAIN rewrites ghcr.io bottle URLs by replacing the https://ghcr.io/ prefix while keeping the URL's v2/ path. This makes it impossible to point it at an OCI registry that proxies ghcr.io under a repository prefix (e.g. a Harbor or Nexus proxy cache), because the OCI Distribution Spec requires /v2/ at the root of the host with the prefix inside the repository name:

HOMEBREW_ARTIFACT_DOMAIN=https://mirror.example.com/v2/ghcr-io
before: https://mirror.example.com/v2/ghcr-io/v2/homebrew/core/hello/manifests/2.12.3 (duplicate /v2/, unservable)
after:  https://mirror.example.com/v2/ghcr-io/homebrew/core/hello/manifests/2.12.3

Artifact domains without a /v2 path keep the exact same behaviour as today, as does the fallback to the original ghcr.io URLs (and HOMEBREW_ARTIFACT_DOMAIN_NO_FALLBACK). The same logic is applied to the portable-ruby URL in vendor-install.sh, with cross-referencing comments to keep both implementations in sync.

Why

This makes HOMEBREW_ARTIFACT_DOMAIN usable with standard OCI registry proxy caches (Harbor, Nexus, Artifactory) that expose proxied upstreams under a repository prefix rather than a dedicated (sub)domain — a common corporate setup where provisioning one virtual host per proxied registry isn't practical. Related demand: #22115.

Reproduction

With a Harbor project ghcr-io configured as a proxy cache for https://ghcr.io:

$ HOMEBREW_ARTIFACT_DOMAIN=https://harbor.example.com/v2/ghcr-io HOMEBREW_DOWNLOAD_CONCURRENCY=1 brew fetch --verbose hello
==> Downloading https://harbor.example.com/v2/ghcr-io/v2/homebrew/core/hello/manifests/2.12.3

The registry answers 404 for this path and every download falls back to ghcr.io, defeating the mirror. With this change the duplicate v2 is gone and downloads go through the proxy.

Verified end-to-end against a real Harbor 2.x proxy-cache project (with HOMEBREW_DOCKER_REGISTRY_TOKEN set so the auth header is kept): brew fetch --bottle-tag=x86_64_linux hello and brew vendor-install ruby both download through the proxy, and a fresh Linux bootstrap via install.sh completes with HOMEBREW_ARTIFACT_DOMAIN_NO_FALLBACK=1 (portable-ruby pulled through the proxy). Domains without a /v2 path still produce the unchanged historical URLs, covered by the unmodified pre-existing tests.

🤖 Generated with Claude Code

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't you just set the HOMEBREW_ARTIFACT_DOMAIN to https://mirror.example.com/v2/ghcr-io instead?

If the issue is that https://mirror.example.com/ghcr-io can literally never ever be valid with any host in any circumstance: that should error out. I don't think you can state that, though, so it seems better that that would be error message improvement rather than behaviour change.

@jouve

jouve commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

in the current code URL_PREFIX alway has v2 added : URL_PREFIX=https://ghcr.io/v2,

URL_DOMAIN = "ghcr.io"
URL_PREFIX = T.let("https://#{URL_DOMAIN}/v2/".freeze, String)

then if HOMEBREW_ARTIFACT_DOMAIN is set, it replaces the https://ghcr.io part with HOMEBREW_ARTIFACT_DOMAIN, the final v2 is kept
if (domain = Homebrew::EnvConfig.artifact_domain)
artifact_urls = urls.map do |u|
u.sub(%r{^https?://#{GitHubPackages::URL_DOMAIN}/}o, "#{domain.chomp("/")}/")
end

so if I do HOMEBREW_ARTIFACT_DOMAIN=https://mirror.example.com/v2/ghcr-io , the code above generates HOMEBREW_ARTIFACT_DOMAIN=https://mirror.example.com/v2/ghcr-io/v2 with is not a valid as it has one additionnal v2/ at the end.

Containerd use override_path to make this behaviour explicit.

[host."https://mirror.example.com/v2/ghcr-io"]
  capabilities = ["pull", "resolve"]
  override_path = true

@MikeMcQuaid

Copy link
Copy Markdown
Member

in the current code URL_PREFIX alway has v2 added : URL_PREFIX=https://ghcr.io/v2,

How about we skip doing that if there's already a trailing v2? That seems like it'd be much simpler.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions Bot added the stale No recent activity label Aug 4, 2026
HOMEBREW_ARTIFACT_DOMAIN rewrites ghcr.io bottle URLs by replacing the
domain prefix while keeping the URL's /v2/ path. This makes it
impossible to point it at an OCI registry proxying ghcr.io under a
repository prefix (e.g. a Harbor or Nexus proxy cache): setting it to
https://mirror.example.com/v2/ghcr-io produces
https://mirror.example.com/v2/ghcr-io/v2/... with a duplicate v2.

When the artifact domain already contains the Docker Registry API's /v2
path, skip the v2 from the original URL. Artifact domains without a /v2
path keep the existing behaviour. Apply the same logic to the
portable-ruby URL in vendor-install.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jouve
jouve force-pushed the artifact-domain-oci-path branch from c19e73e to 5983b25 Compare August 10, 2026 17:56
@jouve jouve changed the title Insert artifact domain paths after /v2/ in bottle URLs Skip duplicate /v2/ when artifact domain already contains it Aug 10, 2026
@jouve

jouve commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Done — reworked as suggested: when HOMEBREW_ARTIFACT_DOMAIN already contains the registry API's /v2 path (/v2 or /v2/<prefix>), the v2/ from the original URL is skipped instead of being duplicated. So HOMEBREW_ARTIFACT_DOMAIN=https://mirror.example.com/v2/ghcr-io now produces https://mirror.example.com/v2/ghcr-io/homebrew/core/....

Domains without a /v2 path are untouched — the pre-existing tests pass unmodified, so there's no behaviour change for existing setups. Also rebased on main and re-verified against a real Harbor proxy cache (bottles via the Ruby code path, portable-ruby via vendor-install.sh, and a fresh Linux bootstrap with HOMEBREW_ARTIFACT_DOMAIN_NO_FALLBACK=1).

@github-actions github-actions Bot removed the stale No recent activity label Aug 10, 2026

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense, thanks!

@MikeMcQuaid
MikeMcQuaid enabled auto-merge August 11, 2026 07:11
@MikeMcQuaid
MikeMcQuaid added this pull request to the merge queue Aug 11, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 11, 2026
@MikeMcQuaid
MikeMcQuaid added this pull request to the merge queue Aug 17, 2026
Merged via the queue into Homebrew:main with commit f3fef4f Aug 17, 2026
87 of 90 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants