Cross-distribution Linux package mirror benchmarking tool
Project description
mirror-bench
A cross-distribution Linux package mirror benchmarking tool.
Ranks mirrors by latency, throughput, and security (HTTPS / TLS 1.3 / cert validity / HTTP/2).
mirror-bench is read-only: it discovers mirrors, probes them, prints a
ranked table, and exits. It never touches /etc/apt/sources.list,
/etc/yum.repos.d/, pacman's mirrorlist, or any other system configuration.
- Cross-distro: Ubuntu, Debian, Fedora, Linux Mint, Arch.
- Modern Python: 3.14, strict typing, async I/O (
httpx+asyncio.TaskGroup). - Shippable everywhere:
pipx,uv tool, PyPI wheels, and a minimal multi-arch container on GHCR.
Table of contents
- Quick start
- Installation
- Usage
- Running in Docker
- Shell completion
- How it works
- Development
- Security
- License
Quick start
Options go after the subcommand (typer / git / kubectl convention).
# Auto-detect distro, benchmark top 15 mirrors
mirror-bench bench
# List discovered mirrors without benchmarking
mirror-bench list
# Benchmark Ubuntu mirrors from the US and Canada
mirror-bench bench --distro ubuntu --country US,CA
# Only consider mirrors that serve HTTPS with TLS 1.3
mirror-bench bench --https-only --tls13-only
# Adjust scoring weights (auto-normalized)
mirror-bench bench --weights lat=0.6,thr=0.3,sec=0.1
# Machine-readable output for scripts
mirror-bench bench --json | jq '.results[0]'
mirror-bench bench --csv > mirrors.csv
Installation
The PyPI distribution is linux-mirror-bench (the short mirror-bench
name was too similar to an existing project). The installed CLI is still
mirror-bench.
With uv (recommended)
uv tool install linux-mirror-bench
mirror-bench --help
With pipx
pipx install linux-mirror-bench
mirror-bench --help
One-shot via uvx
uvx --from linux-mirror-bench mirror-bench bench --distro fedora
From source
git clone https://github.com/MysticRyuujin/mirror-bench
cd mirror-bench
uv sync
uv run mirror-bench bench
Usage
Subcommands
| Command | What it does |
|---|---|
mirror-bench bench |
Discover, probe (two-phase), and print a ranked table. (default) |
mirror-bench list |
Discover only. Print the mirror roster as a table. |
Running mirror-bench with no subcommand is equivalent to mirror-bench bench.
Common flags
| Flag | Description |
|---|---|
--distro, -d NAME |
One of ubuntu, debian, fedora, mint, arch. |
--release, -r VALUE |
Release override: codename for apt (noble, bookworm, wilma) or numeric for Fedora (41, 42). |
--country, -c US,CA,GB |
ISO 3166-1 alpha-2 country codes to filter on. |
--top, -n N |
How many mirrors to include in phase 2 / display (default 15). |
--concurrency N |
Max parallel HTTP probes (default 20). |
--https-only |
Hard-exclude mirrors without HTTPS. |
--tls13-only |
Hard-exclude mirrors that don't negotiate TLS 1.3. |
--weights lat=…,thr=…,sec=… |
Override scoring weights (auto-normalized). |
--no-throughput |
Skip phase 2. Phase-1 latency screen only. |
--json |
Emit JSON to stdout. Suppresses the table + progress. |
--csv |
Emit CSV to stdout. Suppresses the table + progress. |
Running in Docker
A minimal multi-arch (linux/amd64 + linux/arm64) image is published to GHCR:
docker pull ghcr.io/MysticRyuujin/mirror-bench:latest
The image is built on python:3.14-slim-bookworm, runs as a non-root user
(uid 65532), and ships only the wheel + runtime deps (no uv, no sources,
no build chain). ENTRYPOINT is mirror-bench, so you can pass CLI args
directly.
The image renders a colored, properly-widened table even without
-it. If you want rich to use your host terminal's exact width (and react to resizes), add-it; otherwise the image falls back to a sensible 120-col table. SetNO_COLOR=1if you'd rather have plain text — e.g. when redirecting to a file.
Basic invocation
docker run --rm ghcr.io/MysticRyuujin/mirror-bench:latest --help
docker run --rm ghcr.io/MysticRyuujin/mirror-bench:latest bench --distro ubuntu --country US
Per-distribution examples
Pick a --distro — the image is the same for all of them.
# Ubuntu — default per-country sweep if no --country provided
docker run --rm ghcr.io/MysticRyuujin/mirror-bench:latest \
bench --distro ubuntu --country US,CA --top 10
# Debian
docker run --rm ghcr.io/MysticRyuujin/mirror-bench:latest \
bench --distro debian --country DE,FR --https-only
# Fedora — release version inferred from defaults if not passed via --distro value
docker run --rm ghcr.io/MysticRyuujin/mirror-bench:latest \
bench --distro fedora --tls13-only --top 10
# Linux Mint
docker run --rm ghcr.io/MysticRyuujin/mirror-bench:latest \
bench --distro mint --top 15
# Arch Linux
docker run --rm ghcr.io/MysticRyuujin/mirror-bench:latest \
bench --distro arch --country DE,NL,SE
Machine-readable output
docker run --rm ghcr.io/MysticRyuujin/mirror-bench:latest \
bench --distro arch --json | jq '.results[] | {host: .mirror.host, latency_ms, score: .composite}'
Running with auto-detection inside distro base images
If you'd rather let mirror-bench auto-detect the host distribution (useful in
CI containers or inside a running container of a specific distro):
# Inside an Ubuntu container — distro auto-detected from /etc/os-release
docker run --rm --entrypoint bash ubuntu:24.04 -c \
"apt-get update -qq && apt-get install -y -qq python3.14 pipx && pipx install linux-mirror-bench && mirror-bench bench"
# Inside a Fedora container
docker run --rm --entrypoint bash fedora:41 -c \
"dnf install -y python3.14 pipx && pipx install linux-mirror-bench && mirror-bench bench"
# Inside an Arch container
docker run --rm --entrypoint bash archlinux:latest -c \
"pacman -Sy --noconfirm python python-pipx && pipx install linux-mirror-bench && mirror-bench bench"
For most uses the ghcr.io/MysticRyuujin/mirror-bench image is simpler — it already
has the CLI and lets you benchmark any distro via --distro.
Building the image yourself
The Dockerfile is a two-stage build:
- Builder —
python:3.14-slim-bookworm+ the pinneduvfromghcr.io/astral-sh/uv. Generates the wheel, then installs it into a self-contained virtualenv at/opt/venv. - Runtime — fresh
python:3.14-slim-bookworm, copies only/opt/venvfrom the builder, refreshes the CA bundle, creates a non-root user (uid 65532), and setsmirror-benchas theENTRYPOINT.
No dev dependencies, source code, or uv binary lands in the final image.
Standard local build
git clone https://github.com/MysticRyuujin/mirror-bench && cd mirror-bench
# Shortest path — Makefile shortcut tags as `mirror-bench:dev`.
make docker-build
docker run --rm mirror-bench:dev bench --distro arch --top 5
# Or run `docker build` directly for full control over tags and args.
docker build -t mirror-bench:local .
docker run --rm mirror-bench:local --help
Build args
All arguments are optional; defaults match what CI ships.
| Arg | Default | Purpose |
|---|---|---|
PYTHON_VERSION |
3.14 |
Base image tag (python:<PYTHON_VERSION>-slim-bookworm). |
UV_VERSION |
0.11.7 |
uv release pulled from ghcr.io/astral-sh/uv:<UV_VERSION>. |
VERSION |
0.1.0 |
Value embedded in the org.opencontainers.image.version OCI label. |
Example:
docker build \
--build-arg VERSION="$(uv version --short)" \
--build-arg PYTHON_VERSION=3.14 \
--build-arg UV_VERSION=0.11.7 \
-t mirror-bench:"$(uv version --short)" \
-t mirror-bench:latest \
.
Multi-arch (amd64 + arm64) build via buildx
The released image on GHCR is built for both architectures. To produce the same locally (useful for testing on Apple Silicon before publishing):
docker buildx create --name mb --use --bootstrap # one-time
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg VERSION="$(uv version --short)" \
-t mirror-bench:multiarch \
--load . # or --push for a registry
--load only supports a single platform; use --push (against a registry
you control) to actually materialize a multi-arch manifest.
Inspecting the result
docker image inspect mirror-bench:local \
--format '{{json .Config.Labels}}' | jq # OCI labels
docker image ls mirror-bench:local # size (~170 MB)
docker run --rm mirror-bench:local list --distro arch --json | jq '.mirrors | length'
Makefile shortcuts
make docker-build # docker build -t mirror-bench:dev .
make docker-test # build + run --help + `list --distro arch --json` smoke test
Shell completion
mirror-bench ships completion scripts for bash, zsh, and fish.
Two paths, depending on whether shell auto-detection works in your environment:
Recommended: the completion subcommand (no auto-detect)
Always works, including in containers, CI, and sandboxed shells:
# bash
mirror-bench completion bash > ~/.local/share/bash-completion/completions/mirror-bench
# zsh — make sure `~/.zfunc` is on your fpath
mirror-bench completion zsh > ~/.zfunc/_mirror-bench
# fish
mirror-bench completion fish > ~/.config/fish/completions/mirror-bench.fish
Restart your shell (or source the file) and completion is live.
Auto-install via typer (when run in a real terminal)
mirror-bench --install-completion # typer detects the shell and installs
mirror-bench --show-completion # prints the script for the detected shell
This relies on shellingham to
detect your shell from the parent process. It works in normal interactive
shells but can fail inside uv run, nested sandboxes, or minimal containers —
use the completion subcommand in those cases.
After install
Tab-complete subcommands, options, and their values:
$ mirror-bench <TAB>
bench list completion
$ mirror-bench bench --<TAB>
--distro --country --top --concurrency --https-only --tls13-only …
How it works
- Discover — fetch the canonical mirror list for the target distribution:
- Ubuntu:
mirrors.ubuntu.com/<CC>.txtswept across a default country set (mirrors.ubuntu.com/mirrors.txtreturns a geo-selected single mirror, not a full list — so we don't rely on it alone). - Debian:
mirror-master.debian.org/status/Mirrors.masterlist(RFC822 multi-record format). - Fedora:
mirrors.fedoraproject.org/mirrorlist?repo=&arch=&country=. - Linux Mint: scraped from
linuxmint.com/mirrors.php. - Arch:
archlinux.org/mirrors/status/json/(JSON, filtered to active).
- Ubuntu:
- Phase 1 — latency screen. Concurrent ranged GET (
bytes=0-1023) on the distro's canonical small metadata file (InRelease,repomd.xml,lastsync). Three samples per mirror, median TTFB used for ranking. - Phase 2 — throughput test. Full streaming GET of a representative file
(
Contents-<arch>.gzfor apt,core.dbfor Arch, etc.) on the top N from phase 1, capped at 5 MiB.bytes_per_secis measured from first-byte to end-of-stream so steady-state bandwidth isn't polluted by connect/TLS overhead. - Score. Weighted composite of normalized latency (inverse), throughput,
and security (HTTPS + TLS 1.3 + cert-valid + HTTP/2, max 3.5). Weights
default to
lat=0.4, thr=0.4, sec=0.2and are user-overridable. - Render. A
rich.Table, or JSON / CSV for scripting.
Development
See CONTRIBUTING.md for the full dev loop.
make install # uv sync --all-extras
make hooks # install pre-commit + commit-msg hooks
make ci-local # lint + typecheck + tests + security + docker-test
Integration tests (opt-in, real network):
MIRROR_BENCH_INTEGRATION=1 uv run pytest -m integration
Security
Vulnerability reports — see SECURITY.md for the private
disclosure process. Please do not open public issues for security problems.
Supply-chain guarantees (starting at 0.1.0):
- Releases published via GitHub Actions OIDC Trusted Publishing to PyPI — no long-lived tokens.
- Wheels and container images carry SLSA build provenance attestations.
- Container image published to
ghcr.io/MysticRyuujin/mirror-benchwith SBOM generated at build. uv.lockpins dependency graph; Dependabot monitors it weekly.
License
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
The AGPL requires that anyone who runs a modified version of mirror-bench
accessible over a network (for example, as part of a hosted service) must make
the modified source available to the users of that service. If you are
embedding or redistributing mirror-bench, read the license text
for the full terms.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file linux_mirror_bench-0.1.1.tar.gz.
File metadata
- Download URL: linux_mirror_bench-0.1.1.tar.gz
- Upload date:
- Size: 70.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
229c5c8c5bc6ea70b9a4f0443c0d46f89c8bd8d257fc67d025da648bc18292d7
|
|
| MD5 |
5ca17e68c4b41ffed0ad885e4e549273
|
|
| BLAKE2b-256 |
36d7ec72d0167c8090ff76c8557f4c2ef63e4596f548026be292aebfddac2612
|
Provenance
The following attestation bundles were made for linux_mirror_bench-0.1.1.tar.gz:
Publisher:
release.yml on MysticRyuujin/mirror-bench
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
linux_mirror_bench-0.1.1.tar.gz -
Subject digest:
229c5c8c5bc6ea70b9a4f0443c0d46f89c8bd8d257fc67d025da648bc18292d7 - Sigstore transparency entry: 1364883060
- Sigstore integration time:
-
Permalink:
MysticRyuujin/mirror-bench@25d8fa5aac10f45858ca04be43e640031d4e3fb1 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/MysticRyuujin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@25d8fa5aac10f45858ca04be43e640031d4e3fb1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file linux_mirror_bench-0.1.1-py3-none-any.whl.
File metadata
- Download URL: linux_mirror_bench-0.1.1-py3-none-any.whl
- Upload date:
- Size: 43.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f340b08ffb98f5d569538077a4ad2341e3d4b95909bf52469a4ab316c5d30c5
|
|
| MD5 |
29e2e4c68b7be654e478302543b0bf33
|
|
| BLAKE2b-256 |
7a68bf6ca3e031a29788ffd86fe2aef011aaae99ea1344d2c2f381b1d44a52f6
|
Provenance
The following attestation bundles were made for linux_mirror_bench-0.1.1-py3-none-any.whl:
Publisher:
release.yml on MysticRyuujin/mirror-bench
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
linux_mirror_bench-0.1.1-py3-none-any.whl -
Subject digest:
8f340b08ffb98f5d569538077a4ad2341e3d4b95909bf52469a4ab316c5d30c5 - Sigstore transparency entry: 1364883129
- Sigstore integration time:
-
Permalink:
MysticRyuujin/mirror-bench@25d8fa5aac10f45858ca04be43e640031d4e3fb1 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/MysticRyuujin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@25d8fa5aac10f45858ca04be43e640031d4e3fb1 -
Trigger Event:
push
-
Statement type: