Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-12 00:59:16 -05:00
Commit graph pkg-proxy/internal/handler
Author SHA1 Message Date
Andrew Nesbitt
69a5e15a9b
Merge pull request #671 from milas/milas/s3-fixes
fix(s3): valid direct serve pre-signed URLs for alternate hosts
2026-11-10 16:42:23 +00:00
Andrew Nesbitt
4446438571
Merge pull request #701 from simonchrz/feat/retention-keep-latest
Add storage.retention.keep_latest to keep the newest versions
2026-11-10 16:32:10 +00:00
Andrew Nesbitt
11517de480
Merge pull request #694 from simonchrz/fix/nuget-registration-times
Stop building whole NuGet registrations in memory
2026-11-10 16:27:32 +00:00
Andrew Nesbitt
bb6fb2bb37
Document public-host signing for direct_serve_base_url
Also marks the AWS modules as direct dependencies and drops comments that restate function names.
2026-11-10 16:26:30 +00:00
Andrew Nesbitt
fa38de81de
Merge remote-tracking branch 'origin/main' into pr-671
# Conflicts:
#	internal/storage/blob.go
2026-11-10 16:26:30 +00:00
Andrew Nesbitt
0f85f6daba
Merge pull request #690 from simonchrz/fix/debian-conan-cache-keys
Key Debian and Conan archives by their whole path
2026-11-10 16:24:41 +00:00
Andrew Nesbitt
b43e6a8e12
Merge pull request #691 from simonchrz/fix/cap-detached-metadata
Cap the metadata fetches that outlive their caller
2026-11-10 16:21:55 +00:00
Simon Chrzanowski
b731648123 Add storage.retention.keep_latest to keep the newest versions
Retention evicts every file that has not been served for the configured
time, also the newest version of a package. keep_latest: N holds back
the files of the N highest cached versions of each package from
eviction by age. Versions are ordered by the ecosystem's version rules.

It applies to cargo, golang, npm and pypi, where the cache stores a
release under its version. Other ecosystems ignore it for now.
2026-11-10 16:33:21 +01:00
Simon Chrzanowski
e6abe0e283 Merge remote-tracking branch 'origin/main' into fix/nuget-registration-times
# Conflicts:
#	internal/handler/nuget_cooldown.go
2026-11-10 13:20:13 +01:00
Andrew Nesbitt
560ad5cda2
Merge pull request #645 from simonchrz/feat/mirror-refresh
Refresh the most used packages in the background
2026-11-10 12:11:31 +00:00
Andrew Nesbitt
4fd3294e7c
Merge pull request #693 from simonchrz/fix/pypi-release-times
Read PyPI release times without building the whole document
2026-11-10 12:03:53 +00:00
Simon Chrzanowski
3e50f45b0d Refuse Conan file requests with an encoded slash
A path value is decoded, so a revision of R%2Fpackages%2FP%2FQ gave a
recipe file the cache key of a package file. The recipe and package file
routes now answer 400 when user, channel, a revision, the package
reference or the filename contains a slash.
2026-11-10 13:03:00 +01:00
Andrew Nesbitt
f515df3299
Merge pull request #682 from simonchrz/fix/metadata-cache-consistency
Fix metadata cache responses that don't belong together
2026-11-10 11:58:26 +00:00
Simon Chrzanowski
4f3eb35a9b Merge remote-tracking branch 'origin/main' into fix/cap-detached-metadata
# Conflicts:
#	internal/handler/profile_test.go
2026-11-10 12:44:25 +01:00
simon chrzanowski
2076e65210
Merge pull request #697 from montehurd/npm-profile-packument-format
Keep npm packuments in one format across cooldown profiles
2026-11-10 09:23:44 +01:00
Simon Chrzanowski
16a8ae23fa Read a NuGet registration for the cooldown as it is copied
The cooldown read a registration with encoding/json into a struct and
the copy read it again token by token. The two disagreed on documents
with member names in another case and with repeated members: the struct
matched Items and Published, and kept the first or merged where the copy
took what it saw. Such a document could be served with releases that are
in cooldown, by the registration endpoints, the version list and the
download check, or fail after part of an index was sent.

The cooldown now reads with the copy's decoder and its rules: exact
names, as main matched them. A node that repeats @id, items,
catalogEntry, version or published is refused.
2026-11-10 07:34:06 +01:00
montehurd
0fd4b5a83f Keep npm packuments in one format across cooldown profiles
Cooldown profiles and the unprefixed URLs share a packument's cache row,
but each chose its format by its own policy: abbreviated without
cooldown, full with it. With cooldown on for only some of them, a request
without cooldown that revalidated the row stored the abbreviated
packument. The next request under cooldown rejected it and fetched the
full one again, even within metadata_ttl, and had no stale copy to serve
when the upstream failed.

Cooldown in the global policy or in any profile now makes all of them
request the full packument, as upstream.npm_full_metadata does. URLs
without cooldown are then served full packuments too.
2026-11-09 18:13:48 -08:00
Milas Bowman
8e6a87a068
fix(oci): serve HEAD requests from cache 2026-11-09 19:39:45 -05:00
Simon Chrzanowski
00ae3d3d15 Give a page whose last leaf has no version a null upper bound
A kept leaf without a version after one with a version left an empty,
non-nil version behind, and writing the page's upper bound from it
panicked.
2026-11-09 23:46:00 +01:00
Simon Chrzanowski
4a69111519 Stream NuGet registration documents instead of building them
The registration endpoints decoded the index and, with cooldown on, all
of its pages into generic maps, filtered and rewrote the tree and
encoded it again. For AWSSDK.Core that allocated about 80 MB per
request for a 6.8 MB response.

The document is now copied token by token. Links are rewritten on the
way, leaves in cooldown and pages left empty are cut out of the output
buffer, and count, lower and upper are written once a page's leaves are
known. The index and its linked pages are still loaded and checked
before the response starts, so a registration that can't be filtered
stays an error response.

The output keeps the upstream's member order and bytes, so it is the
same JSON as before but no longer sorted by key.
2026-11-09 23:33:38 +01:00
Simon Chrzanowski
89fc77ee4b Read NuGet publish times without building the whole registration
With cooldown on, the version list reads the package's registration
index and all of its pages to find the versions in cooldown. Each
document was decoded into a generic map, which for a package like
AWSSDK.Core (6.8 MB of pages) allocated 26 MB per request, also when
everything was cached. The registration is now decoded into a struct
that names only the version and publish time, so the rest is skipped.
The download check reads its leaf the same way.
2026-11-09 23:01:16 +01:00
Simon Chrzanowski
da6a5b4027 Read PyPI release times without building the whole document
With cooldown on, every simple index request and every download reads
the package's JSON API document for its upload times. It was decoded
into a generic map, which for a package like botocore (4.4 MB) allocated
about 17 MB per request, also when everything was cached. The times are
now decoded into a struct that names only that field, so the rest of
the document is skipped.
2026-11-09 22:45:10 +01:00
Simon Chrzanowski
8cf92d1ec1 Cap the metadata fetches that outlive their caller
A metadata fetch always ran on a context detached from its first
caller, so a client could request many uncached documents under
different names and hang up at once, leaving one upstream fetch per
request running to the HTTP client's timeout, each buffering up to
metadata_max_size. Artifact fetches only detach while they hold one of
maxDetachedFetches slots. Metadata fetches now do the same with their
own 64 slots; beyond that, a fetch ends with its first caller.
2026-11-09 17:49:16 +01:00
Simon Chrzanowski
4c3c925cd1 Key Debian and Conan archives by their whole path
A Debian package was cached under its repository and file name, but
fetched from its full path. Docker's apt repository has a pool under each
suite with the same file names, so a bookworm client could get the
bullseye build that was fetched first, and fail its hash check. Packages
are now cached under their path.

Conan joined revision, package reference, package revision and file name
with underscores, which revisions can contain, so a recipe file and a
package file could share a cache entry. The parts are now joined with
slashes.

Entries cached under the old names are still served, but only for the
URL they were fetched from, so warm caches keep working.
2026-11-09 17:44:19 +01:00
Simon Chrzanowski
b719035424 Write Arch databases with their own validators
Arch wrote databases and signatures with the validators of the cache row
read at write time, which may describe other bytes once another instance
rewrote it. Its results now carry validators: fetchedResult for a fetched
database, describedBy for a stored one, and stale on the fallback after an
upstream failure, which also marks it with metadata_ttl 0. Nothing calls
writeMetadataCachedResponseWithEncoding or lookupCachedMeta any more.
2026-11-09 11:57:39 +01:00
Simon Chrzanowski
f8a24fdeeb Merge remote-tracking branch 'origin/main' into fix/metadata-cache-consistency 2026-11-09 11:52:37 +01:00
Andrew Nesbitt
b6e71f4d7a
Merge pull request #684 from simonchrz/perf/jsonstream-jsontext
Read jsonstream objects with jsontext
2026-11-09 09:32:19 +00:00
Simon Chrzanowski
4ae55605b1 Send Maven coordinate files under odd groups to the 404
A file named {artifact}-{version} under a dotted group directory, such as
org.foo/bar/1.0/bar-1.0-linux.exe, was not recognised as a coordinate file
and so was proxied past the checks. isCoordinateFile no longer checks the
group, so handleDownload answers 404 for it. The docs say which files get
the 404, and the Range test covers a stale If-Range.
2026-11-08 22:18:14 +01:00
Simon Chrzanowski
b61e35205c Answer 404 for Maven files outside the layout
A file whose group directories hold a dot or colon was proxied without
caching, which skipped the denylist, scan and cooldown. Such paths aren't
in the Maven layout, so they now get 404. Directory listings and catalogs
still go through proxyUpstream, which shares its error handling with
Proxy.ProxyUpstream in relayUpstream.
2026-11-08 21:49:48 +01:00
Simon Chrzanowski
2ac80bf76b Merge remote-tracking branch 'origin/main' into fix/cache-key-collisions
# Conflicts:
#	internal/gitcache/cache_test.go
2026-11-08 21:47:56 +01:00
Andrew Nesbitt
1c9509d125
Merge pull request #679 from simonchrz/fix/missing-routes
Fix missing routes and protocol gaps
2026-11-08 19:57:38 +00:00
Simon Chrzanowski
79a0ae2dd0 Read the row again when stale metadata bytes don't match it
The fallback served replaced bytes with the old row's content type and
encoding, so a blob swapped from identity to gzip went out without its
Content-Encoding. It now reads the row once more and serves the blob
under the row that describes it, and treats the copy as unusable if
none does yet.
2026-11-08 19:14:18 +01:00
Simon Chrzanowski
44afe6997a Read jsonstream objects with jsontext
CopyObject takes the reader and decodes it with jsontext, accepting
repeated names and invalid UTF-8 as encoding/json did. Keys are written
with the bytes they were read with instead of being re-encoded. Member
gets Skip, which reads past a value without keeping it, and npm's
RewriteStream skips dropped versions and duplicates with it.

conda-forge noarch repodata.json (195 MB): filtering 0.89 to 0.62 s and
379 to 77 MB allocated, the timestamp index 0.84 to 0.61 s. The next
packument (32 MB): RewriteStream 69 to 34 ms and 38 to 3.5 MB.
2026-11-08 19:03:21 +01:00
Simon Chrzanowski
3fdbe51ad5 Merge remote-tracking branch 'origin/main' into feat/mirror-git-tags-submodules 2026-11-08 18:56:36 +01:00
Simon Chrzanowski
8ec60f940d Merge remote-tracking branch 'origin/main' into fix/metadata-cache-consistency
# Conflicts:
#	internal/handler/handler.go
#	internal/handler/npm.go
2026-11-08 18:53:58 +01:00
Simon Chrzanowski
140b70233a Move the PyPI root index replacement loop into its own method 2026-11-08 18:51:46 +01:00
Simon Chrzanowski
2ecdcced4c Search again only for the replacement just made in the PyPI root index
Each match searched the rest of the buffer for every replacement. The
root index has no single-quoted links, so after every double-quoted one
the other pattern scanned to the end again, making each chunk quadratic
in its links. Each replacement's next match is now kept and only
searched for again once a replacement passes it (50,000 links: 18.6 to
818 MB/s).
2026-11-08 18:51:00 +01:00
Andrew Nesbitt
43bd67fc13
Merge pull request #681 from simonchrz/fix/cooldown-denylist-gaps
Fix cooldown and denylist gaps
2026-11-08 17:31:53 +00:00
Andrew Nesbitt
1b22f79be2
Fail the conda timestamp test when the index is empty 2026-11-08 17:23:11 +00:00
Andrew Nesbitt
53c0fd9c51
Merge pull request #676 from simonchrz/perf/npm-jsontext
Skip discarded npm packument values with jsontext
2026-11-08 17:02:31 +00:00
Andrew Nesbitt
9fb3119b87
Merge pull request #672 from milas/milas/deb-caching
fix(debian): route packages by filename rather than `/pool` prefix
2026-11-08 15:55:01 +00:00
Simon Chrzanowski
da090443ec Forward Range to upstream for Maven paths proxied uncached
Paths proxied without caching sent no request headers, so a resumed
download got the whole file back instead of the requested bytes.
2026-11-08 12:19:19 +01:00
Simon Chrzanowski
a5f014d64c Check a cached release file against its row before using it
The release listing read the cached InRelease, Release or repomd.xml
without comparing it with the row's digest, so bytes another instance had
replaced could decide which index is served. Such a file is now ignored,
and the index falls back to its own cache entry.
2026-11-08 10:58:43 +01:00
Simon Chrzanowski
0ff118b642 Try the Plugin Portal for Maven paths proxied uncached
Paths the proxy doesn't cache, such as ones with a dotted group
directory, went to the primary upstream only, so a file available only on
the Gradle Plugin Portal returned 404. They now try the Plugin Portal on a
404, as cached files do.
2026-11-08 10:57:38 +01:00
Simon Chrzanowski
5c42a6dab1 Seed the metadata test row without unused parameters
Every caller seeds the same v1 document, which the linter flags.
2026-11-08 10:12:39 +01:00
Simon Chrzanowski
b1875040de Leave the refresh's own downloads out of the most used packages
A file the refresh mirrored was fetched just now and never accessed,
which counted as recent use, so the refresh kept every package it had
refreshed in the most used list forever. Fetches under the new
handler.MirrorFetch context set mirrored_at, and a file whose last fetch
was a mirror's counts only once a client downloads it.
2026-11-08 10:12:37 +01:00
Simon Chrzanowski
fede2463f7 Describe conda shard timestamps as condaTime reads them 2026-11-08 10:10:43 +01:00
Simon Chrzanowski
7fdfd9908f Refetch npm packuments only when a cached copy lacks the version
Under cooldown, a tarball for a version the packument doesn't list is
decided on a fresh fetch of the packument. That check had gaps:

- A stale copy served because upstream failed counted as fetched, so
  the version went through with no publish time. It now triggers the
  refetch like a copy within its TTL.
- A packument just stored from upstream counted as cached, so every
  request for an unlisted version fetched it twice. Only a stored row
  read within its TTL counts now.
- A stored refetch that joined another caller's fetch could get that
  caller's stale fallback and take it as upstream's answer. The stored
  lookup now reports stale results, and a stale refetch is refused.
- A refused refetch dropped the upstream error, so a rate-limit hold
  answered 502 instead of 503 with Retry-After, and a 404 was lost.
  Stale results now carry the upstream error, and the refusal wraps it.
2026-11-08 10:10:43 +01:00
Simon Chrzanowski
09a6b21fac Pick the APT release file that lists the index
The index lookup always used a cached InRelease over Release, so a
stale InRelease decided which index was served to a client that had
just fetched a fresh Release, and it stopped at the nearest cached
release file even when that file, such as the legacy per-component
Release, listed nothing for the path.

Skip release files that don't list the path, and where both InRelease
and Release do, use the one fetched last: apt reads InRelease and
debootstrap Release, and a client asking for an index has most likely
just fetched one of them.
2026-11-08 10:10:35 +01:00
Simon Chrzanowski
ebc1c4646d Mark the offline checksum database copy stale
Offline mode serves the cached copy without asking the database once
it is known to be unreachable, but derived staleness from the row's
age. With metadata_ttl 0 that copy had no Warning header. It is
always stale, since upstream was not asked.
2026-11-08 10:09:53 +01:00