Files
tiny-proxy/config.example.yaml
MRLokopandClaude Opus 5.5 42abe48da1 Add a Go module proxy with per-module routing rules
A go vrepo serves the GOPROXY protocol at /go/<vrepo>/. Its upstreams
are ordered rules matched by module path (GOPRIVATE-style patterns);
each one relays to another module proxy, or builds .info/.mod/.zip,
@v/list and @latest itself from a git repository: through the Gitea
or GitLab REST API, or over git smart-HTTP (go-git) for any other
host, without a git binary. Module layout follows the go command:
<dir>/vX.Y.Z tags, /vN major subdirectories, +incompatible, nested
modules and vendor/ left out of zips. go.mod is served as committed,
so forks keeping their upstream module path work through replace.

Repo templates map captured pattern elements to repository paths
({1}, {2}...). vanity_hosts answers ?go-get=1 with a go-import meta
tag pointing at this proxy or at the repository.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:53:25 +03:00

280 lines
11 KiB
YAML

listen: ":8080"
storage:
# localfs | s3 | none. "none" disables caching entirely: every request is
# proxied straight through to the upstream and nothing is stored.
type: localfs
localfs:
root: ./data
# s3:
# bucket: my-cache
# region: us-east-1
# endpoint: http://localhost:9000 # optional, for MinIO etc.
# access_key: minioadmin
# secret_key: minioadmin
# prefix: cache
# GET /metrics serves Prometheus text exposition: per-vrepo request counts,
# durations and cache hit/miss/error counters; per-upstream attempts, response
# statuses, byte totals (payload and real socket bytes) and timings (connect,
# TLS handshake, time-to-first-byte, body); and Go runtime stats. Storage
# sizes are deliberately absent: listing a large store on every scrape is too
# expensive — use the backend's own accounting (`du`, bucket metrics) instead.
# Empty token (the default) leaves the endpoint open; set one to require
# `Authorization: Bearer <token>` or a `?token=<token>` query param.
# metrics:
# token: ""
# Every request is given a trace id (adopted from the caller's X-Request-Id or
# traceparent when it sent one), echoed back in X-Request-Id, attached to every
# log line the request produces, and passed on to the upstreams it calls — so
# one grep follows a request end to end, across chained proxies included.
# log:
# level: info # debug | info | warn | error
# format: text # text | json
# requests: true # one line when a client request arrives and one when it ends
# upstream: true # one line per outbound attempt against an upstream
vrepos:
# docker pull localhost:8080/dockerhub/library/nginx:1.27
- name: dockerhub
type: docker
upstream:
url: https://registry-1.docker.io
# username: myuser # optional Docker Hub credentials
# password: mytoken
# proxy: socks5://127.0.0.1:1080
auth:
mode: public # public | password | users
cache:
manifest_ttl: 5m
# Docker with multiple upstreams, tried in order: a 404, 401, 403, 429,
# 502, 503, 504 or connection error falls through to the next one, any
# other status is returned as-is. Here a remote tiny-proxy goes first and
# Docker Hub itself second, so pulls come from the remote cache when it is
# up and go direct when it is not. A url whose path starts with /v2/ is a
# repository root — another tiny-proxy's vrepo — and the image path is
# appended to it as-is (no second /v2/, no library/: the remote adds that).
# docker pull localhost:8080/hub-chained/library/nginx:1.27
- name: hub-chained
type: docker
upstreams:
- url: https://proxy.example.com/v2/dockerhub
# A dead remote should cost nothing: skip the connection-failure
# retries and fall through to Docker Hub at once.
retry: { attempts: 0 }
- url: https://registry-1.docker.io
auth:
mode: public
cache:
manifest_ttl: 5m
# docker pull localhost:8080/ghcr/org/image:tag (authenticated repo example)
- name: ghcr
type: docker
upstream:
url: https://ghcr.io
username: github-user
password: ghp_token
auth:
mode: password # any login + this password
password: s3cret
cache:
manifest_ttl: 1m
# https://localhost:8080/maven/central/com/google/guava/guava/33.0.0-jre/guava-33.0.0-jre.jar
- name: central
type: maven
upstream:
url: https://repo1.maven.org/maven2
auth:
mode: users
users:
alice: pw1
bob: pw2
cache:
metadata_ttl: 5m
# Maven with multiple upstreams (also supported for docker, debian and
# pacman, without filters). Upstreams are tried in order: a 404, 401, 403,
# 429, 502, 503, 504 or connection error falls through to the next
# one, any other status is returned as-is. Each upstream may restrict what it
# serves with allowlist filters:
# groups: wildcard group prefixes; "com.google.*" covers the
# whole com/google tree ('*' crosses dots/slashes)
# artifacts: wildcard artifact names, e.g. "my-lib-*"
# snapshots_only: only -SNAPSHOT paths are requested from it
# An upstream with no filters matches everything. Per-upstream
# credentials and egress proxy work the same as the single-upstream form.
# Any upstream (of any type, single or listed) may set `cache: false` to
# be proxied through without being stored here — see the chained example
# below.
- name: maven-all
type: maven
upstreams:
- url: https://corp.example.com/maven-snapshots
username: deploy
password: s3cret
snapshots_only: true
- url: https://maven.google.com
groups: ["com.google.*"]
- url: https://repo1.maven.org/maven2
# `attempts` covers three failures, each waiting the upstream's
# Retry-After or a backoff when it sends none, at most max_wait per
# wait: a 429 (Too Many Requests), a connection that fails before any
# response arrives, and a transfer that dies mid-body (re-requested
# with a byte range and continued where it stopped). Once the attempts
# are spent the request falls through to the next upstream.
# Defaults: attempts 2, max_wait 5s; attempts 0 disables.
retry:
attempts: 3
max_wait: 10s
auth:
mode: public
cache:
metadata_ttl: 5m
# A path every upstream answered 404 for is answered 404 from memory
# for this long instead of walking the list again. Default 10m;
# negative disables.
not_found_ttl: 10m
# Chaining: a vrepo may use another vrepo of this proxy as an upstream.
# `cache: false` keeps the aggregator from storing a second copy of what
# the target vrepo already caches — the bytes live in `central` only, and
# `deps` just routes. Reads still hit `deps`'s own cache if something was
# stored there earlier by a cacheable upstream. Chained upstreams are
# ordinary HTTP requests: add username/password if the target vrepo is
# not `auth.mode: public`, and never let a chain loop back on itself.
- name: deps
type: maven
upstreams:
- url: https://mvn.example.com # cached here as usual
- url: http://localhost:8080/maven/central
cache: false # cached in the central vrepo
auth:
mode: public
cache:
metadata_ttl: 5m
# curl -LO http://localhost:8080/github/gh/golang/go/latest.json
# curl -LO http://localhost:8080/github/gh/golang/go/latest/go1.23.0.linux-amd64.tar.gz
- name: gh
type: github
# upstream.url defaults to https://github.com (API: api.github.com).
# For GitHub Enterprise set your instance URL (API: <url>/api/v3).
upstream:
url: https://github.com
# username: github-user # optional; PAT raises API rate limits
# password: ghp_token # and unlocks private repos
auth:
mode: public
# allow restricts which owner/repo pairs may be proxied (path.Match
# globs). Omit or leave empty to allow everything.
allow:
- "golang/*"
- "myorg/mytool"
# redirect: true (default) sends /latest and /snapshot to the resolved
# tag path with a 302; false serves content under the alias URL.
# redirect: false
cache:
metadata_ttl: 5m # release metadata + alias resolution
# Same, but with per-vhost users and an authenticated upstream (private
# repos), serving aliases in place instead of redirecting.
- name: gh-private
type: github
upstream:
url: https://github.com
username: github-user
password: ghp_token
auth:
mode: users
users:
carol: pw3
redirect: false
cache:
metadata_ttl: 1m
# Gitea releases: same URL grammar as the github type under /gitea/.
# There is no default upstream — point url at your instance (its API is
# served at <url>/api/v1).
# curl -LO http://localhost:8080/gitea/gt/myorg/mytool/latest/mytool_linux-amd64
- name: gt
type: gitea
upstream:
url: https://gitea.example.com
# username: gitea-user # optional; token unlocks private repos
# password: gitea_token
auth:
mode: public
# allow: ["myorg/*"]
# redirect: false
cache:
metadata_ttl: 5m
# Debian / APT repo. apt sources.list: deb http://localhost:8080/debian/deb bookworm main
# Open /ui/debian/deb/ for a generated sources.list line per distro version
# (bookworm, trixie, noble, ...) with each suite's components and
# architectures, probed from the mirror and memoized for metadata_ttl.
# Metadata under dists/** (Release, InRelease, Packages.*, ...) is cached
# for metadata_ttl; pool/** and by-hash/** are content-addressed and
# cached forever. Multiple upstreams act as mirrors: a 404, 401, 403,
# 429, 502, 503, 504 or connection error falls through to the next one
# (they must serve the same archive layout).
- name: deb
type: debian
upstreams:
- url: https://deb.debian.org/debian
- url: http://ftp.us.debian.org/debian
auth:
mode: public
cache:
metadata_ttl: 5m # dists/** freshness; packages immutable
# Pacman (Arch) mirror. pacman.conf: Server = http://localhost:8080/pacman/arch/$repo/os/$arch
# <repo>.db / <repo>.files (any compression, plus .sig) and lastupdate
# are cached for metadata_ttl; *.pkg.tar.* packages are immutable.
- name: arch
type: pacman
upstream:
url: https://geo.mirror.pkgbuild.com
auth:
mode: public # pacman has no native HTTP-auth support
cache:
metadata_ttl: 5m
# Go module proxy: export GOPROXY=http://localhost:8080/go/go
# Upstreams are ordered rules by module path (GOPRIVATE-style patterns).
# Git-backed kinds (gitea, gitlab, git) build .info/.mod/.zip from the
# repository themselves, no git binary needed; goproxy relays to another
# module proxy. Modules built from private repos are unknown to the public
# checksum database: set GONOSUMDB (or GOPRIVATE) for their patterns.
- name: go
type: go
# Answer ?go-get=1 for these hosts with a go-import meta tag.
# vanity_hosts: [go.example.com]
upstreams:
# go.example.com/team/lib[/sub][/vN] -> Team/lib on Gitea.
# - kind: gitea
# modules: ["go.example.com/team/*"]
# url: https://gitea.example.com
# repo: "Team/{1}"
# password: gitea_token # read:repository scope
# Nested groups work: {1}/{2} -> group/project.
# - kind: gitlab
# modules: ["gitlab.example.com/*/*"]
# url: https://gitlab.example.com
# password: glpat-xxxx # sent as Bearer
# Any smart-HTTP git server.
# - kind: git
# modules: ["git.example.com/*"]
# url: https://git.example.com
# repo: "{1}.git"
# go_get: git # vanity answer points at the repo
- url: https://proxy.golang.org
auth:
mode: public
cache:
metadata_ttl: 5m # @v/list, @latest, branch queries