2026-01-29 16:06:56 +00:00
|
|
|
# Proxy server configuration
|
|
|
|
|
# Copy to config.yaml and modify as needed
|
|
|
|
|
|
|
|
|
|
# Server listen address
|
|
|
|
|
listen: ":8080"
|
|
|
|
|
|
2026-06-07 16:12:43 +01:00
|
|
|
# Public URL where package endpoints are reachable.
|
|
|
|
|
# Used for rewriting package metadata URLs and shown in install guide snippets
|
|
|
|
|
# so users know what to point their package manager at.
|
2026-01-29 16:06:56 +00:00
|
|
|
base_url: "http://localhost:8080"
|
|
|
|
|
|
2026-07-07 17:48:36 +01:00
|
|
|
# Timeout for individual upstream HTTP requests made by protocol handlers
|
|
|
|
|
# (metadata fetches, pass-through file requests). Uses Go duration syntax.
|
|
|
|
|
# Set to "0" to disable the timeout. Default: "30s".
|
|
|
|
|
# http_timeout: "30s"
|
|
|
|
|
|
Cache rewritten npm and Composer metadata (#402)
The npm and Composer handlers rewrite every metadata document they serve
so that download URLs point at the proxy: decode the whole document into
generic maps, change the URLs, encode it again, and for Composer expand
the minified format first. That ran on every request, cached metadata
included. With metadata caching on and upstream out of the picture, a
cached request still cost 7 ms for @babel/core, 17 ms for
symfony/console and 155 ms and 134 MB of allocations for typescript, and
an 8 vCPU VM serving cached npm packuments to 20 clients ran the proxy
at 450% CPU for about 1,100 requests a second.
Rewritten documents are now kept in memory, keyed by the ecosystem,
proxy URL, package and a SHA-256 of the raw document, so new bytes from
upstream are rewritten again and nothing is served stale. Requests that
arrive while a document is being rewritten wait for that rewrite rather
than running their own; a waiter leaves when its client does, and the
rewrite still completes and is cached. The denylist is fixed at startup,
so it needs no place in the key. Cooldown filtering depends on the
current time, so with cooldown on the cache is bypassed.
metadata_rewrite_cache_size bounds the cache (default "256MB", least
recently used out first, "0" to rewrite on every request). NewProxy
callers keep the old behaviour unless they set it.
Whole cached requests, measured locally:
@babel/core 7.1 ms -> 0.38 ms, 54,489 -> 119 allocations
typescript 155 ms -> 10.5 ms, 1.37M -> 133 allocations
symfony/console 17.4 ms -> 0.48 ms, 165,909 -> 123 allocations
What remains is reading the raw document from storage and hashing it.
2026-10-03 04:09:15 -07:00
|
|
|
# Memory for rewritten npm and Composer metadata, so each upstream document is
|
|
|
|
|
# rewritten once rather than on every request. Set to "0" to rewrite on every
|
|
|
|
|
# request. Default: "256MB".
|
|
|
|
|
# metadata_rewrite_cache_size: "256MB"
|
|
|
|
|
|
2026-06-07 16:12:43 +01:00
|
|
|
# Public URL where the web UI is reached. Defaults to base_url when unset.
|
|
|
|
|
# Set this separately when the UI is served on a different hostname than the
|
|
|
|
|
# package endpoints — for example, the UI on a public domain behind auth while
|
|
|
|
|
# build machines hit a Docker network alias for the package endpoints.
|
|
|
|
|
# ui_base_url: "https://proxy.example.com/ui"
|
|
|
|
|
|
2026-10-01 14:32:21 +02:00
|
|
|
# Attribute requests to the leftmost X-Forwarded-For entry rather than the TCP
|
|
|
|
|
# peer address, in the structured log, the access log and the request-source
|
|
|
|
|
# table on /ui/analytics.
|
|
|
|
|
#
|
|
|
|
|
# Enable this only when the proxy sits behind a load balancer or ingress that
|
|
|
|
|
# sets the header. Any client can send it: behind one it is the only way to see
|
|
|
|
|
# past the hop, in front of one it lets a caller forge its own address.
|
|
|
|
|
# trust_forwarded_for: false
|
|
|
|
|
|
|
|
|
|
# Show the request-source table on /ui/analytics: caller addresses, the tool
|
|
|
|
|
# each ran and how much each pulled.
|
|
|
|
|
#
|
|
|
|
|
# Off by default. The proxy has no authentication of its own, so leave this off
|
|
|
|
|
# unless /ui is gated by a reverse proxy; anyone who can reach the page can
|
|
|
|
|
# otherwise read the addresses of your build fleet.
|
|
|
|
|
# ui_request_sources: false
|
|
|
|
|
|
2026-01-29 16:06:56 +00:00
|
|
|
# Artifact storage configuration
|
|
|
|
|
storage:
|
2026-01-29 16:13:16 +00:00
|
|
|
# Storage backend URL
|
|
|
|
|
# Supported schemes:
|
|
|
|
|
# - file:///path/to/dir - Local filesystem (default)
|
|
|
|
|
# - s3://bucket-name - Amazon S3
|
|
|
|
|
# - s3://bucket?endpoint=http://localhost:9000 - S3-compatible (MinIO)
|
2026-08-29 11:23:07 +01:00
|
|
|
# - gs://bucket-name - Google Cloud Storage
|
|
|
|
|
# - azblob://container-name - Azure Blob Storage
|
2026-01-29 16:13:16 +00:00
|
|
|
#
|
|
|
|
|
# For S3, configure credentials via environment variables:
|
|
|
|
|
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
|
2026-08-29 11:23:07 +01:00
|
|
|
#
|
|
|
|
|
# For GCS, authentication uses Application Default Credentials. On GKE with
|
|
|
|
|
# Workload Identity, bind the Kubernetes service account to a Google service
|
|
|
|
|
# account that has roles/storage.objectAdmin on the bucket. No extra config
|
|
|
|
|
# is needed in this file. For local development, run:
|
|
|
|
|
# gcloud auth application-default login
|
|
|
|
|
# If direct_serve is enabled, the service account also needs
|
|
|
|
|
# roles/iam.serviceAccountTokenCreator on itself so the IAM Credentials
|
|
|
|
|
# signBlob API can sign URLs without a private key.
|
2026-01-29 16:13:16 +00:00
|
|
|
url: ""
|
|
|
|
|
|
|
|
|
|
# Local filesystem path (used when url is empty)
|
|
|
|
|
# Deprecated: Use url with file:// scheme instead
|
2026-01-29 16:06:56 +00:00
|
|
|
path: "./cache/artifacts"
|
|
|
|
|
|
|
|
|
|
# Maximum cache size (e.g., "10GB", "500MB")
|
|
|
|
|
# When exceeded, least recently used artifacts are evicted
|
|
|
|
|
# Empty or "0" means unlimited
|
|
|
|
|
max_size: ""
|
|
|
|
|
|
Add storage.cache_artifacts to serve artifacts without storing them (#387)
* Add storage.passthrough to serve artifacts without storing them
With storage.passthrough enabled the proxy streams every artifact from
upstream to the client and never writes it to storage or the cache
database. Metadata filtering, cooldown and the denylist work as before,
so the proxy can sit behind another cache (e.g. an Artifactory remote)
purely as a policy layer without holding a second copy of every package.
Artifacts whose digest is known up front (OCI, Swift, Helm) are verified
while streaming. Their responses are sent chunked and the connection is
aborted on a mismatch, so a client never receives a tampered artifact as
a complete response.
Passthrough is rejected together with scanning, direct_serve and
mirror_api, and the mirror command refuses to run with it, since all of
them depend on stored artifacts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review: abort truncated streams, guard every cache read
- serveArtifact aborts the response on any body read error, and when fewer
bytes than the declared size were written, not only on a digest mismatch.
A failed or short upstream body in passthrough mode no longer reaches the
client as a complete 200.
- Streamed upstream read failures are logged and counted as
stream_failed upstream errors before the response is aborted.
- checkCache reports a miss when artifact caching is off, so every cache
read path, including Swift archive HEAD requests, ignores entries stored
before the mode was enabled. Denylisted versions are still rejected.
- Rename storage.passthrough to storage.cache_artifacts (default true,
PROXY_STORAGE_CACHE_ARTIFACTS), which names what actually changes.
Tests cover an OCI blob shorter than its Content-Length and an npm tarball
whose chunked response ends without the final chunk, both served through
the HTTP handlers, plus a Swift HEAD request against a cached archive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rename Proxy.Passthrough to StreamArtifacts
Match the storage.cache_artifacts option: the field says what changes
(artifacts are streamed instead of stored) and its zero value keeps the
usual caching behaviour for every Proxy built without the server config,
such as the mirror command and tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:28:20 +07:00
|
|
|
# Store fetched artifacts. Set to false to stream every download from
|
|
|
|
|
# upstream without storing it; metadata filtering, cooldown and the
|
|
|
|
|
# denylist still apply. Useful when another cache sits in front of the
|
|
|
|
|
# proxy. false is incompatible with direct_serve, scanning and mirror_api.
|
|
|
|
|
cache_artifacts: true
|
|
|
|
|
|
2026-04-27 12:04:38 +01:00
|
|
|
# Redirect cached artifact downloads to presigned storage URLs (HTTP 302)
|
2026-08-29 11:23:07 +01:00
|
|
|
# instead of streaming through the proxy. Only effective for S3, GCS, and Azure.
|
2026-04-27 12:04:38 +01:00
|
|
|
# Leave disabled if clients reach the proxy through an authenticating gateway,
|
|
|
|
|
# since presigned URLs bypass it.
|
|
|
|
|
direct_serve: false
|
|
|
|
|
|
|
|
|
|
# How long presigned URLs remain valid (e.g. "5m", "1h"). Default: "15m".
|
|
|
|
|
direct_serve_ttl: "15m"
|
|
|
|
|
|
2026-04-27 12:14:37 +01:00
|
|
|
# Public base URL to substitute into presigned URLs. Set this when the
|
|
|
|
|
# proxy reaches storage at an internal address (127.0.0.1, a Docker
|
|
|
|
|
# service name) but clients must use a public hostname. Only scheme and
|
|
|
|
|
# host are used; the signed path and query are preserved. For S3/MinIO
|
|
|
|
|
# the reverse proxy at this address must forward requests with the
|
|
|
|
|
# internal Host header or the SigV4 signature will not validate.
|
|
|
|
|
# direct_serve_base_url: "https://minio.example.com"
|
|
|
|
|
|
2026-01-29 16:06:56 +00:00
|
|
|
# Database configuration
|
|
|
|
|
database:
|
|
|
|
|
# Database driver: "sqlite" (default) or "postgres"
|
|
|
|
|
driver: "sqlite"
|
|
|
|
|
|
|
|
|
|
# SQLite database file path (used when driver is "sqlite")
|
|
|
|
|
path: "./cache/proxy.db"
|
|
|
|
|
|
|
|
|
|
# PostgreSQL connection URL (used when driver is "postgres")
|
|
|
|
|
# Example: "postgres://user:password@localhost:5432/proxy?sslmode=disable"
|
|
|
|
|
url: ""
|
|
|
|
|
|
2026-09-30 23:09:04 -07:00
|
|
|
# How often cache hit counts and last-access times are written, batched in
|
|
|
|
|
# one transaction. "0" writes each hit as it happens.
|
|
|
|
|
hit_flush_interval: "1s"
|
|
|
|
|
|
2026-01-29 16:06:56 +00:00
|
|
|
# Logging configuration
|
|
|
|
|
log:
|
|
|
|
|
# Minimum log level: "debug", "info", "warn", "error"
|
|
|
|
|
level: "info"
|
|
|
|
|
|
|
|
|
|
# Log format: "text" or "json"
|
|
|
|
|
format: "text"
|
|
|
|
|
|
2026-08-16 18:07:39 +01:00
|
|
|
# JSONL access log. Leave path empty to disable it.
|
|
|
|
|
access_log:
|
|
|
|
|
path: ""
|
|
|
|
|
|
2026-08-29 11:19:54 +01:00
|
|
|
# Upstream URLs for built-in routes and authentication
|
2026-01-29 16:06:56 +00:00
|
|
|
upstream:
|
2026-08-29 11:19:54 +01:00
|
|
|
# Hosts allowed to resolve to private, ULA, or CGNAT addresses
|
|
|
|
|
allow_private_hosts: []
|
|
|
|
|
|
|
|
|
|
# Permit upstream requests and redirects to loopback addresses
|
|
|
|
|
allow_loopback: false
|
|
|
|
|
|
2026-01-29 16:06:56 +00:00
|
|
|
# npm registry URL
|
|
|
|
|
npm: "https://registry.npmjs.org"
|
2026-09-02 07:13:12 -04:00
|
|
|
# Always request full npm packuments so served metadata carries publish
|
|
|
|
|
# times ("time" map) even when cooldown is disabled. Needed by clients that
|
|
|
|
|
# gate on publish age, e.g. Yarn's npmMinimalAgeGate. Default: false.
|
|
|
|
|
# npm_full_metadata: true
|
2026-01-29 16:06:56 +00:00
|
|
|
|
2026-08-29 11:19:54 +01:00
|
|
|
# Cargo sparse index URL
|
|
|
|
|
cargo: "https://index.crates.io"
|
|
|
|
|
|
|
|
|
|
# Cargo crate download URL
|
|
|
|
|
cargo_download: "https://static.crates.io/crates"
|
|
|
|
|
|
|
|
|
|
# RubyGems registry URL
|
|
|
|
|
gem: "https://rubygems.org"
|
|
|
|
|
|
|
|
|
|
# Go module proxy URL
|
|
|
|
|
go: "https://proxy.golang.org"
|
|
|
|
|
|
|
|
|
|
# Hex repository URL
|
|
|
|
|
hex: "https://repo.hex.pm"
|
|
|
|
|
|
|
|
|
|
# Hex API URL used for package timestamps
|
|
|
|
|
hex_api: "https://hex.pm"
|
|
|
|
|
|
|
|
|
|
# pub registry URL
|
|
|
|
|
pub: "https://pub.dev"
|
|
|
|
|
|
|
|
|
|
# PyPI index and API URL
|
|
|
|
|
pypi: "https://pypi.org"
|
|
|
|
|
|
|
|
|
|
# PyPI package download URL
|
|
|
|
|
pypi_download: "https://files.pythonhosted.org"
|
|
|
|
|
|
2026-05-22 18:05:20 +02:00
|
|
|
# Maven repository URL (used by /maven endpoint)
|
|
|
|
|
maven: "https://repo1.maven.org/maven2"
|
|
|
|
|
|
|
|
|
|
# Gradle Plugin Portal Maven URL (fallback for plugin marker artifacts)
|
|
|
|
|
gradle_plugin_portal: "https://plugins.gradle.org/m2"
|
|
|
|
|
|
2026-08-29 11:19:54 +01:00
|
|
|
# NuGet API URL
|
|
|
|
|
nuget: "https://api.nuget.org"
|
2026-01-29 16:06:56 +00:00
|
|
|
|
2026-08-29 11:19:54 +01:00
|
|
|
# NuGet search API URL
|
|
|
|
|
nuget_search: "https://azuresearch-usnc.nuget.org"
|
|
|
|
|
|
|
|
|
|
# Packagist API URL
|
|
|
|
|
composer: "https://packagist.org"
|
|
|
|
|
|
|
|
|
|
# Packagist repository URL
|
|
|
|
|
composer_repository: "https://repo.packagist.org"
|
|
|
|
|
|
|
|
|
|
# Conan registry URL
|
|
|
|
|
conan: "https://center.conan.io"
|
|
|
|
|
|
|
|
|
|
# Conda channel base URL
|
|
|
|
|
conda: "https://conda.anaconda.org"
|
|
|
|
|
|
|
|
|
|
# CRAN mirror URL
|
|
|
|
|
cran: "https://cloud.r-project.org"
|
|
|
|
|
|
|
|
|
|
# Julia package server URL
|
|
|
|
|
julia: "https://pkg.julialang.org"
|
|
|
|
|
|
2026-09-02 13:06:34 +01:00
|
|
|
# Swift Package Registry URL (used by /swift endpoint)
|
|
|
|
|
swift: "https://tuist.dev/api/registry/swift"
|
|
|
|
|
|
2026-08-29 11:19:54 +01:00
|
|
|
# Default OCI registry URL for unprefixed /v2 requests
|
|
|
|
|
oci_default: "https://registry-1.docker.io"
|
2026-01-29 16:33:09 +00:00
|
|
|
|
2026-08-10 09:34:56 +01:00
|
|
|
# Debian/APT repository URL (used by /debian endpoint)
|
|
|
|
|
debian: "http://deb.debian.org/debian"
|
|
|
|
|
|
2026-09-16 10:15:59 -07:00
|
|
|
# Additional Debian/APT archives, served at /debian/{name}/.
|
|
|
|
|
# The names "pool" and "dists" are reserved for the main archive's own paths.
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
debian_repositories:
|
|
|
|
|
security: "https://security.debian.org/debian-security"
|
|
|
|
|
|
2026-08-29 11:19:54 +01:00
|
|
|
# RPM repository URL (used by /rpm endpoint)
|
|
|
|
|
rpm: "https://dl.fedoraproject.org/pub/fedora/linux"
|
|
|
|
|
|
2026-09-03 16:59:12 +01:00
|
|
|
# Homebrew JSON API URL (used by /homebrew endpoint)
|
|
|
|
|
homebrew_api: "https://formulae.brew.sh/api"
|
|
|
|
|
|
|
|
|
|
# Homebrew artifact registry URL (used for /v2/homebrew/core requests)
|
|
|
|
|
homebrew_artifact: "https://ghcr.io"
|
|
|
|
|
|
2026-08-16 22:42:55 +05:30
|
|
|
# Named HTTP Helm chart repositories (used by /helm/{name}/)
|
|
|
|
|
# helm:
|
|
|
|
|
# bitnami: "https://charts.bitnami.com/bitnami"
|
|
|
|
|
|
|
|
|
|
# Named OCI registries. Use the upstream/{name}/ repository prefix, e.g.
|
|
|
|
|
# oci://proxy.example.com/upstream/ghcr/owner/chart.
|
|
|
|
|
# oci:
|
|
|
|
|
# ghcr: "https://ghcr.io"
|
|
|
|
|
|
Add Alpine APK repository proxy support (#293)
* Add Alpine APK repository proxy support
- Serve named APK repositories at /apk/{repository}/ with the official
Alpine mirror as the default repository
- Cache v2 APKINDEX.tar.gz and v3 Packages.adb indexes and detached
signatures via the metadata cache, serving stored bytes unchanged so
apk signature verification keeps working
- Cache .apk packages in the shared artifact cache keyed by the full
repository path, since APK filenames do not include the architecture
- Add configurable upstream repositories via upstream.apk with
validation, plus dashboard registry instructions
- Add tests for index/signature byte fidelity, per-arch caching, cache
hits, offline reads, upstream authentication, and 404 handling
- Document apk usage in README, config example, and configuration docs
* Serve APK package HEAD requests without a body
Use the method-aware serveArtifact helper (as container.go does) so HEAD
responses carry Content-Length but omit the body; add a regression test.
* Drop doubled blank line in docs/configuration.md
---------
Co-authored-by: Andrew Nesbitt <andrewnez@gmail.com>
2026-09-02 13:36:00 +02:00
|
|
|
# Named Alpine APK repositories (used by /apk/{name}/).
|
|
|
|
|
# Defaults to {"alpine": "https://dl-cdn.alpinelinux.org/alpine"} when empty;
|
|
|
|
|
# configuring any entry replaces that default.
|
|
|
|
|
# apk:
|
|
|
|
|
# alpine: "https://dl-cdn.alpinelinux.org/alpine"
|
|
|
|
|
# private: "https://apk.example.com"
|
|
|
|
|
|
Add generic HTTP download proxy for GitHub release assets (mise/aqua) (#302)
* Add generic HTTP download proxy for GitHub release assets
Adds a /generic/{name}/ route backed by a new upstream.generic named-upstream
map, so tools that download from fixed URL shapes (mise's aqua backend
fetching GitHub release assets, and its tag lookups on api.github.com) can be
pointed at the proxy with client-side URL rewriting. Only configured
upstreams are reachable, so this is not an open HTTP proxy.
Paths shaped like {owner}/{repo}/releases/download/{tag}/{asset} are
version-pinned and go through the artifact cache: fetched once, hashed,
served without revalidation, and still served when the upstream is down.
Every other path goes through the metadata cache with the client's Accept
header and query string replayed, so API responses are fresh within
metadata_ttl, revalidated after that, and served stale when the upstream
fails or rate-limits the request.
Tests cover path classification, unknown upstreams and traversal, cache
hits with the upstream down, HEAD, 404 pass-through, Accept and query
forwarding, stale-on-429, cache isolation between upstreams, and that an
upstream token scoped to the release host is not sent to the object store
it redirects to.
Closes #183.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDjDeq27CKzEP2o3GWBY7F
* Use fixed Accept header for generic metadata
---------
Co-authored-by: Giles Westwood <giles@gileswestwood.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 16:44:54 +01:00
|
|
|
# Named generic HTTP upstreams (used by /generic/{name}/). The remaining
|
|
|
|
|
# request path and query are appended to the upstream URL. GitHub release
|
|
|
|
|
# assets ({owner}/{repo}/releases/download/{tag}/{asset}) are cached
|
|
|
|
|
# immutably; other paths use the metadata cache with stale-on-error.
|
|
|
|
|
# generic:
|
|
|
|
|
# github: "https://github.com"
|
|
|
|
|
# github-api: "https://api.github.com"
|
|
|
|
|
|
2026-01-29 16:33:09 +00:00
|
|
|
# Authentication for upstream registries
|
2026-08-13 07:08:23 +01:00
|
|
|
# Keys are absolute URL scopes. Scheme, host, effective port, and path
|
|
|
|
|
# segment boundaries must match; the longest matching scope wins.
|
2026-01-29 16:33:09 +00:00
|
|
|
# Values can reference environment variables using ${VAR_NAME} syntax.
|
|
|
|
|
#
|
|
|
|
|
# Supported auth types:
|
|
|
|
|
# - bearer: Authorization header with Bearer token
|
|
|
|
|
# - basic: Authorization header with Basic auth (username:password)
|
|
|
|
|
# - header: Custom header name and value
|
2026-09-02 12:43:43 +01:00
|
|
|
# - ecr: AWS ECR auto-refreshing token via the AWS SDK credential chain
|
2026-01-29 16:33:09 +00:00
|
|
|
auth:
|
|
|
|
|
# Example: npm with bearer token
|
|
|
|
|
# "https://registry.npmjs.org":
|
|
|
|
|
# type: bearer
|
|
|
|
|
# token: "${NPM_TOKEN}"
|
|
|
|
|
|
|
|
|
|
# Example: GitHub npm registry
|
|
|
|
|
# "https://npm.pkg.github.com":
|
|
|
|
|
# type: bearer
|
|
|
|
|
# token: "${GITHUB_TOKEN}"
|
|
|
|
|
|
|
|
|
|
# Example: PyPI with basic auth
|
|
|
|
|
# "https://pypi.org":
|
|
|
|
|
# type: basic
|
|
|
|
|
# username: "__token__"
|
|
|
|
|
# password: "${PYPI_TOKEN}"
|
|
|
|
|
|
|
|
|
|
# Example: Custom header for private registry
|
|
|
|
|
# "https://maven.mycompany.com":
|
|
|
|
|
# type: header
|
|
|
|
|
# header_name: "X-Auth-Token"
|
|
|
|
|
# header_value: "${MAVEN_TOKEN}"
|
2026-03-04 19:00:31 +00:00
|
|
|
|
2026-09-02 12:43:43 +01:00
|
|
|
# Example: private AWS ECR registry (12h tokens auto-refreshed via
|
|
|
|
|
# ecr:GetAuthorizationToken; credentials come from the AWS SDK default
|
|
|
|
|
# chain, so IRSA / instance profiles / AWS_* env vars all work; the region
|
|
|
|
|
# is inferred from the private ECR hostname)
|
|
|
|
|
# "https://123456789012.dkr.ecr.eu-west-1.amazonaws.com":
|
|
|
|
|
# type: ecr
|
|
|
|
|
|
2026-05-04 12:15:16 +02:00
|
|
|
# Gradle HttpBuildCache configuration
|
|
|
|
|
gradle:
|
|
|
|
|
build_cache:
|
|
|
|
|
# Set to true to disable PUT uploads (read-only cache mode)
|
|
|
|
|
read_only: false
|
|
|
|
|
|
|
|
|
|
# Maximum accepted Gradle cache upload body size
|
|
|
|
|
# Required and must be > 0
|
|
|
|
|
max_upload_size: "100MB"
|
|
|
|
|
|
|
|
|
|
# Evict entries older than this age (set to "0" to disable age-based eviction)
|
|
|
|
|
max_age: "168h"
|
|
|
|
|
|
|
|
|
|
# Cap total Gradle cache size; oldest entries are deleted first
|
|
|
|
|
# ("0" disables size-based eviction)
|
|
|
|
|
# max_size: "20GB"
|
|
|
|
|
|
|
|
|
|
# How often eviction runs when max_age or max_size is set
|
|
|
|
|
sweep_interval: "10m"
|
|
|
|
|
|
Add storage backend probe to /health (closes #73) (#119)
* config: add Health.StorageProbeInterval
* metrics: add proxy_health_probe_failures_total counter
* server: add storageProbe with happy-path test
* server: add storageProbe failure-mode tests
* server: add healthCache with TTL, single-flight, transition logging
* server: wire storage probe into /health
* server: update TestHealthEndpoint for JSON; wire healthCache into newTestServer
Also fix Windows file-locking issue in storageProbe: close the reader
explicitly before Delete so the file handle is released prior to os.Remove.
* server: clean up stale comment in storageProbe
* docs: document storage health probe and new metric
* docs: regenerate Swagger for /health JSON response
* server: simplify rc.Close error handling in storageProbe
* server: defer probe cleanup so size/open/read/verify failures don't leak objects
Previously, storageProbe only called Delete on the success path. Any
failure between Store and the final Delete (size mismatch, Open error,
mid-stream read failure, content mismatch) left the probe object orphaned
in the storage backend. With caching disabled and Kubernetes-rate probing,
the leak could accumulate noticeably on backends like S3.
Use a named return + defer to attempt Delete after every successful Store.
The earlier-step failure remains the primary error; Delete failure only
surfaces as step="delete" when nothing else went wrong. Add a table-driven
test that asserts cleanup runs for each non-delete failure path.
Reported by Copilot on #119.
* config: validate health.storage_probe_interval in Config.Validate
The new duration field was only validated at use time in newHealthCache.
The existing codebase already validates other duration fields
(MetadataTTL, DirectServeTTL, Gradle.MaxAge, Gradle.SweepInterval) in
Config.Validate() so misconfiguration fails fast at startup with a
config-key-specific error.
Match that pattern. The parse-at-use code in newHealthCache stays as
a safety net, mirroring the MetadataTTL precedent.
Reported by Copilot on #119.
* docs: lowercase "counter" in metrics table for consistency
Other rows in the table use lowercase type names (counter/gauge/histogram).
Match that style.
Reported by Copilot on #119.
* docs: include size-check step in /health probe description
The probe is write → size-check → read → verify → delete; the
architecture note was missing the size-check step.
Reported by Copilot on #119.
* server: address andrew's review on #119
- Drop unused callerCtx parameter from healthCache.Check (Check is now
parameter-less; the comment-only "accepted for symmetry" justification
wasn't carrying its weight).
- Emit "storage": {"status": "skipped"} on DB short-circuit instead of
omitting the key, so monitors expecting a fixed key set keep working.
- Reject negative storage_probe_interval at config validation time
(previously parsed and silently behaved like "0").
- Extract HealthConfig.Validate to keep Config.Validate under the
gocognit threshold and match the existing GradleBuildCacheConfig pattern.
- README Health Check section: note that /health is intended as a
readiness probe rather than a liveness probe (Check holds a mutex
for up to the 10s probe timeout).
- cmd/proxy/main.go godoc: column-align the new env var with the
surrounding Gradle entries.
Reported by andrew on #119.
2026-05-22 14:14:01 +03:00
|
|
|
# Health endpoint configuration.
|
|
|
|
|
health:
|
|
|
|
|
# Minimum time between storage backend probes.
|
|
|
|
|
# The /health endpoint runs a write/read/verify/delete round-trip
|
|
|
|
|
# against the configured storage backend and caches the result for
|
|
|
|
|
# this interval. Set to "0" to probe on every request.
|
|
|
|
|
# Default: "30s".
|
|
|
|
|
storage_probe_interval: "30s"
|
|
|
|
|
|
2026-03-04 19:00:31 +00:00
|
|
|
# Version cooldown configuration
|
|
|
|
|
# Hides package versions published too recently, giving the community time
|
|
|
|
|
# to spot malicious releases before they're pulled into projects.
|
|
|
|
|
# Supported durations: "7d" (days), "48h" (hours), "30m" (minutes), "0" (disabled)
|
|
|
|
|
cooldown:
|
|
|
|
|
# Global default cooldown for all ecosystems
|
|
|
|
|
# default: "3d"
|
|
|
|
|
|
|
|
|
|
# Per-ecosystem overrides
|
|
|
|
|
# ecosystems:
|
|
|
|
|
# npm: "7d"
|
|
|
|
|
# cargo: "0"
|
|
|
|
|
|
2026-07-13 17:11:04 -07:00
|
|
|
# Per-package overrides (keyed by PURL). Keys are normalized, so npm scopes
|
|
|
|
|
# may use either @scope or the canonical %40scope form.
|
2026-03-04 19:00:31 +00:00
|
|
|
# packages:
|
|
|
|
|
# "pkg:npm/lodash": "0"
|
|
|
|
|
# "pkg:npm/@babel/core": "14d"
|
2026-09-03 05:06:44 -04:00
|
|
|
|
2026-10-04 20:48:41 +01:00
|
|
|
# Per-package glob overrides, after exact packages and before ecosystems.
|
|
|
|
|
# package_patterns:
|
|
|
|
|
# "pkg:npm/@example/*": "0"
|
|
|
|
|
|
2026-09-20 14:42:30 +05:30
|
|
|
# Exact versions to deny, independently of cooldown and scanning.
|
|
|
|
|
# Metadata filtering: npm, PyPI and Cargo. Shared artifact downloads, including
|
|
|
|
|
# cache hits, are blocked with 403; signed APT metadata is left unchanged.
|
|
|
|
|
# Use versioned PURLs without qualifiers/subpaths. Restart after changes.
|
|
|
|
|
# denylist:
|
|
|
|
|
# packages:
|
|
|
|
|
# - "pkg:pypi/requests@2.31.0"
|
|
|
|
|
# - "pkg:cargo/some-crate@1.2.3"
|
|
|
|
|
# - "pkg:npm/%40scope/example@4.5.6"
|
|
|
|
|
|
2026-09-03 05:06:44 -04:00
|
|
|
# Pre-cache artifact scanning. When enabled, every artifact is staged into
|
|
|
|
|
# storage and scanned by the configured scanners before it is committed to
|
|
|
|
|
# the cache and served to clients. Scanners never receive artifact bytes
|
|
|
|
|
# directly — each notify call includes a short-lived signed URL that the
|
|
|
|
|
# scanner fetches itself, so the proxy stays agnostic to trivy/ClamAV/Wiz/
|
|
|
|
|
# any custom service. Scanners run concurrently; the first "block" verdict
|
|
|
|
|
# wins and cancels the rest.
|
|
|
|
|
# scanning:
|
|
|
|
|
# enabled: true
|
|
|
|
|
# fail_open: false
|
|
|
|
|
# timeout: 30s
|
|
|
|
|
#
|
|
|
|
|
# # Authenticates pull requests to the internal scan-fetch route.
|
|
|
|
|
# # Required whenever enabled is true. Supports ${VAR_NAME} expansion.
|
|
|
|
|
# signing_key: ${PROXY_SCANNING_SIGNING_KEY}
|
|
|
|
|
#
|
|
|
|
|
# # Address scanners use to reach this proxy to pull staged artifacts.
|
|
|
|
|
# # Defaults to base_url.
|
|
|
|
|
# # fetch_base_url: http://proxy.internal:8080
|
|
|
|
|
#
|
|
|
|
|
# scanners:
|
|
|
|
|
# - name: clamav
|
|
|
|
|
# url: http://clamav-adapter:8080/scan
|
|
|
|
|
# mode: block
|
|
|
|
|
# - name: trivy
|
|
|
|
|
# url: http://trivy-adapter:8081/scan
|
|
|
|
|
# mode: monitor
|
|
|
|
|
# ecosystems: [npm, pypi]
|