Skip to main content

Cross-distribution Linux package mirror benchmarking tool

Project description

mirror-bench

CI PyPI Python License: AGPL v3 Container Ruff Checked with mypy

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

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. Set NO_COLOR=1 if 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 when testing from inside a specific distro's container), use uv — it bootstraps its own Python 3.14 regardless of what the distro ships. Distro-package-manager installs of python3.14 don't work on current stable Ubuntu/Debian/Fedora (they ship older Python).

# 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 curl ca-certificates >/dev/null && \
   curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null && \
   ~/.local/bin/uvx --from linux-mirror-bench mirror-bench bench"

# Inside a Debian container
docker run --rm --entrypoint bash debian:bookworm -c \
  "apt-get update -qq && apt-get install -y -qq curl ca-certificates >/dev/null && \
   curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null && \
   ~/.local/bin/uvx --from linux-mirror-bench mirror-bench bench"

# Inside a Fedora container
docker run --rm --entrypoint bash fedora:41 -c \
  "dnf install -y -q curl ca-certificates >/dev/null && \
   curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null && \
   ~/.local/bin/uvx --from linux-mirror-bench mirror-bench bench"

# Inside an Arch container
docker run --rm --entrypoint bash archlinux:latest -c \
  "pacman -Sy --noconfirm --needed curl ca-certificates >/dev/null && \
   curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null && \
   ~/.local/bin/uvx --from 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:

  1. Builderpython:3.14-slim-bookworm + the pinned uv from ghcr.io/astral-sh/uv. Generates the wheel, then installs it into a self-contained virtualenv at /opt/venv.
  2. Runtime — fresh python:3.14-slim-bookworm, copies only /opt/venv from the builder, refreshes the CA bundle, creates a non-root user (uid 65532), and sets mirror-bench as the ENTRYPOINT.

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

  1. Discover — fetch the canonical mirror list for the target distribution:
    • Ubuntu: mirrors.ubuntu.com/<CC>.txt swept across a default country set (mirrors.ubuntu.com/mirrors.txt returns 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).
  2. 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.
  3. Phase 2 — throughput test. Full streaming GET of a representative file (Contents-<arch>.gz for apt, core.db for Arch, etc.) on the top N from phase 1, capped at 5 MiB. bytes_per_sec is measured from first-byte to end-of-stream so steady-state bandwidth isn't polluted by connect/TLS overhead.
  4. 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.2 and are user-overridable.
  5. 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-bench with SBOM generated at build.
  • uv.lock pins 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

linux_mirror_bench-0.1.2.tar.gz (71.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

linux_mirror_bench-0.1.2-py3-none-any.whl (43.4 kB view details)

Uploaded Python 3

File details

Details for the file linux_mirror_bench-0.1.2.tar.gz.

File metadata

  • Download URL: linux_mirror_bench-0.1.2.tar.gz
  • Upload date:
  • Size: 71.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for linux_mirror_bench-0.1.2.tar.gz
Algorithm Hash digest
SHA256 7fd24df305fe4df969258d58a6aec752b95751ffa17d5d34526d63d5ce52d2b4
MD5 68602a31150e50cb560d0d00bc198475
BLAKE2b-256 d207e0b1fd20d23492614871a67fa199efd3bedbe16a009e249930241ceb31e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for linux_mirror_bench-0.1.2.tar.gz:

Publisher: release.yml on MysticRyuujin/mirror-bench

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file linux_mirror_bench-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for linux_mirror_bench-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 cf86482b5b00f829cf6860bc10f76aec709622637bc349cf8af9167d10358c14
MD5 378676e2fb7e0e36e333476bc3b19801
BLAKE2b-256 2e0131857d135bcd9afa4f3386befc5fd9b58da4fb11f2b0b45834ab9e2d8da9

See more details on using hashes here.

Provenance

The following attestation bundles were made for linux_mirror_bench-0.1.2-py3-none-any.whl:

Publisher: release.yml on MysticRyuujin/mirror-bench

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page