linux-mirror-bench 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/workflows/ci.yml +12 -12
  2. linux_mirror_bench-0.2.0/.github/workflows/integration.yml +42 -0
  3. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/workflows/release.yml +4 -4
  4. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.pre-commit-config.yaml +1 -0
  5. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.release-please-config.json +1 -0
  6. linux_mirror_bench-0.2.0/.release-please-manifest.json +3 -0
  7. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/AGENTS.md +13 -4
  8. linux_mirror_bench-0.2.0/CHANGELOG.md +105 -0
  9. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/CONTRIBUTING.md +1 -1
  10. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/Dockerfile +5 -2
  11. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/PKG-INFO +93 -27
  12. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/README.md +89 -25
  13. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/pyproject.toml +6 -1
  14. linux_mirror_bench-0.2.0/src/mirror_bench/__init__.py +5 -0
  15. linux_mirror_bench-0.2.0/src/mirror_bench/benchmark/h3.py +88 -0
  16. linux_mirror_bench-0.2.0/src/mirror_bench/benchmark/h3_quic.py +193 -0
  17. linux_mirror_bench-0.2.0/src/mirror_bench/benchmark/probe.py +283 -0
  18. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/benchmark/scorer.py +57 -29
  19. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/benchmark/transport.py +9 -4
  20. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/cli.py +81 -22
  21. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/__init__.py +2 -1
  22. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/arch.py +12 -8
  23. linux_mirror_bench-0.2.0/src/mirror_bench/discovery/base.py +102 -0
  24. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/debian.py +20 -24
  25. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/fedora.py +62 -26
  26. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/mint.py +9 -6
  27. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/ubuntu.py +32 -37
  28. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/display.py +44 -3
  29. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/distro.py +20 -26
  30. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/models.py +5 -1
  31. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/fixtures/arch_status.json +17 -0
  32. linux_mirror_bench-0.2.0/tests/fixtures/debian_masterlist.txt +31 -0
  33. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/test_cli.py +78 -0
  34. linux_mirror_bench-0.2.0/tests/test_discovery.py +491 -0
  35. linux_mirror_bench-0.2.0/tests/test_display.py +122 -0
  36. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/test_distro.py +67 -1
  37. linux_mirror_bench-0.2.0/tests/test_h3.py +238 -0
  38. linux_mirror_bench-0.2.0/tests/test_integration.py +88 -0
  39. linux_mirror_bench-0.2.0/tests/test_probe.py +401 -0
  40. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/test_scorer.py +74 -3
  41. linux_mirror_bench-0.2.0/uv.lock +658 -0
  42. linux_mirror_bench-0.1.2/.release-please-manifest.json +0 -3
  43. linux_mirror_bench-0.1.2/CHANGELOG.md +0 -61
  44. linux_mirror_bench-0.1.2/src/mirror_bench/__init__.py +0 -3
  45. linux_mirror_bench-0.1.2/src/mirror_bench/benchmark/probe.py +0 -251
  46. linux_mirror_bench-0.1.2/src/mirror_bench/discovery/base.py +0 -43
  47. linux_mirror_bench-0.1.2/tests/fixtures/debian_masterlist.txt +0 -19
  48. linux_mirror_bench-0.1.2/tests/test_discovery.py +0 -272
  49. linux_mirror_bench-0.1.2/tests/test_integration.py +0 -36
  50. linux_mirror_bench-0.1.2/tests/test_probe.py +0 -123
  51. linux_mirror_bench-0.1.2/uv.lock +0 -424
  52. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.dockerignore +0 -0
  53. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.editorconfig +0 -0
  54. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  55. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  56. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  57. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  58. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/dependabot.yml +0 -0
  59. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/workflows/release-please.yml +0 -0
  60. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.gitignore +0 -0
  61. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.markdownlint.yaml +0 -0
  62. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.tool-versions +0 -0
  63. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.yamllint.yaml +0 -0
  64. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/CODEOWNERS +0 -0
  65. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/LICENSE +0 -0
  66. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/Makefile +0 -0
  67. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/SECURITY.md +0 -0
  68. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/__main__.py +0 -0
  69. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/benchmark/__init__.py +0 -0
  70. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/benchmark/tls.py +0 -0
  71. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/__init__.py +0 -0
  72. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/conftest.py +0 -0
  73. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/fixtures/fedora_mirrorlist.txt +0 -0
  74. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/fixtures/mint_mirrors.html +0 -0
  75. {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/fixtures/ubuntu_mirrors.txt +0 -0
@@ -23,8 +23,8 @@ jobs:
23
23
  name: Pre-commit
24
24
  runs-on: ubuntu-latest
25
25
  steps:
26
- - uses: actions/checkout@v6
27
- - uses: actions/setup-python@v6
26
+ - uses: actions/checkout@v7
27
+ - uses: actions/setup-python@v7
28
28
  with:
29
29
  python-version: ${{ env.PYTHON_VERSION }}
30
30
  - uses: astral-sh/setup-uv@v7
@@ -38,13 +38,13 @@ jobs:
38
38
  name: Lint + format
39
39
  runs-on: ubuntu-latest
40
40
  steps:
41
- - uses: actions/checkout@v6
41
+ - uses: actions/checkout@v7
42
42
  - uses: astral-sh/setup-uv@v7
43
43
  with:
44
44
  version: ${{ env.UV_VERSION }}
45
45
  enable-cache: true
46
46
  cache-dependency-glob: pyproject.toml
47
- - run: uv sync --frozen --all-extras || uv sync --all-extras
47
+ - run: uv sync --frozen --all-extras
48
48
  - name: Ruff check
49
49
  run: uv run ruff check --output-format=github .
50
50
  - name: Ruff format check
@@ -54,13 +54,13 @@ jobs:
54
54
  name: Mypy (strict)
55
55
  runs-on: ubuntu-latest
56
56
  steps:
57
- - uses: actions/checkout@v6
57
+ - uses: actions/checkout@v7
58
58
  - uses: astral-sh/setup-uv@v7
59
59
  with:
60
60
  version: ${{ env.UV_VERSION }}
61
61
  enable-cache: true
62
62
  cache-dependency-glob: pyproject.toml
63
- - run: uv sync --frozen --all-extras || uv sync --all-extras
63
+ - run: uv sync --frozen --all-extras
64
64
  - name: mypy
65
65
  run: uv run mypy src tests
66
66
 
@@ -72,8 +72,8 @@ jobs:
72
72
  matrix:
73
73
  python: ["3.14"]
74
74
  steps:
75
- - uses: actions/checkout@v6
76
- - uses: actions/setup-python@v6
75
+ - uses: actions/checkout@v7
76
+ - uses: actions/setup-python@v7
77
77
  with:
78
78
  python-version: ${{ matrix.python }}
79
79
  - uses: astral-sh/setup-uv@v7
@@ -81,7 +81,7 @@ jobs:
81
81
  version: ${{ env.UV_VERSION }}
82
82
  enable-cache: true
83
83
  cache-dependency-glob: pyproject.toml
84
- - run: uv sync --frozen --all-extras || uv sync --all-extras
84
+ - run: uv sync --frozen --all-extras
85
85
  - name: pytest
86
86
  run: uv run pytest -v --tb=short
87
87
  - name: Smoke-test the built CLI
@@ -93,13 +93,13 @@ jobs:
93
93
  name: Security scan (bandit + pip-audit)
94
94
  runs-on: ubuntu-latest
95
95
  steps:
96
- - uses: actions/checkout@v6
96
+ - uses: actions/checkout@v7
97
97
  - uses: astral-sh/setup-uv@v7
98
98
  with:
99
99
  version: ${{ env.UV_VERSION }}
100
100
  enable-cache: true
101
101
  cache-dependency-glob: pyproject.toml
102
- - run: uv sync --frozen --all-extras || uv sync --all-extras
102
+ - run: uv sync --frozen --all-extras
103
103
  - name: Bandit
104
104
  run: uv run --with "bandit[toml]" bandit -c pyproject.toml -r src
105
105
  - name: pip-audit (runtime deps)
@@ -114,7 +114,7 @@ jobs:
114
114
  contents: read
115
115
  security-events: write
116
116
  steps:
117
- - uses: actions/checkout@v6
117
+ - uses: actions/checkout@v7
118
118
  - uses: docker/setup-buildx-action@v4
119
119
  - name: Build image
120
120
  uses: docker/build-push-action@v7
@@ -0,0 +1,42 @@
1
+ ---
2
+ # Runs the opt-in integration tests against real mirror infrastructure.
3
+ # Mirror lists, file paths, and release names change upstream without notice;
4
+ # this is the only job that sees it. It is not a PR gate, because a failure
5
+ # here is usually upstream drift, not a defect in the change under review.
6
+
7
+ name: Integration
8
+
9
+ on:
10
+ schedule:
11
+ - cron: "17 6 * * 1"
12
+ workflow_dispatch:
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ concurrency:
18
+ group: ${{ github.workflow }}
19
+ cancel-in-progress: true
20
+
21
+ env:
22
+ PYTHON_VERSION: "3.14"
23
+ UV_VERSION: "0.11.7"
24
+
25
+ jobs:
26
+ integration:
27
+ name: Live mirrors
28
+ runs-on: ubuntu-latest
29
+ timeout-minutes: 15
30
+ steps:
31
+ - uses: actions/checkout@v6
32
+ - uses: actions/setup-python@v6
33
+ with:
34
+ python-version: ${{ env.PYTHON_VERSION }}
35
+ - uses: astral-sh/setup-uv@v7
36
+ with:
37
+ version: ${{ env.UV_VERSION }}
38
+ enable-cache: true
39
+ cache-dependency-glob: pyproject.toml
40
+ - run: uv sync --frozen --all-extras
41
+ - name: pytest (integration)
42
+ run: make test-integration
@@ -31,8 +31,8 @@ jobs:
31
31
  outputs:
32
32
  version: ${{ steps.version.outputs.value }}
33
33
  steps:
34
- - uses: actions/checkout@v6
35
- - uses: actions/setup-python@v6
34
+ - uses: actions/checkout@v7
35
+ - uses: actions/setup-python@v7
36
36
  with:
37
37
  python-version: ${{ env.PYTHON_VERSION }}
38
38
  - uses: astral-sh/setup-uv@v7
@@ -94,7 +94,7 @@ jobs:
94
94
  id-token: write
95
95
  attestations: write
96
96
  steps:
97
- - uses: actions/checkout@v6
97
+ - uses: actions/checkout@v7
98
98
  - uses: docker/setup-qemu-action@v4
99
99
  - uses: docker/setup-buildx-action@v4
100
100
  - uses: docker/login-action@v4
@@ -143,7 +143,7 @@ jobs:
143
143
  permissions:
144
144
  contents: write
145
145
  steps:
146
- - uses: actions/checkout@v6
146
+ - uses: actions/checkout@v7
147
147
  - uses: actions/download-artifact@v8
148
148
  with:
149
149
  name: dist
@@ -44,6 +44,7 @@ repos:
44
44
  - id: mypy
45
45
  additional_dependencies:
46
46
  - httpx
47
+ - aioquic
47
48
  - typer
48
49
  - rich
49
50
  - pytest
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "release-type": "python",
3
+ "bump-minor-pre-major": true,
3
4
  "packages": {
4
5
  ".": {}
5
6
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.0"
3
+ }
@@ -9,21 +9,26 @@ this repo. Humans should read `README.md` + `CONTRIBUTING.md` instead.
9
9
  src/mirror_bench/
10
10
  cli.py typer app, subcommands `bench`, `list`, `completion`
11
11
  distro.py /etc/os-release parsing + derivative alias table +
12
- structured exceptions (UnsupportedDistroError, etc.)
12
+ default codenames + structured exceptions
13
+ (UnsupportedDistroError, etc.)
13
14
  models.py Mirror, ProbeResult, Score, Weights, BenchConfig, HostInfo
14
15
  discovery/ per-distro mirror fetchers (ABC + 5 impls +
15
- UnknownDiscovererError)
16
+ DiscoveryError, UnknownDiscovererError)
16
17
  benchmark/
17
18
  transport.py shared httpx.AsyncClient factory
18
19
  probe.py two-phase async orchestration (TaskGroup + semaphore)
19
20
  tls.py TLS version / cert inspection via response.extensions
21
+ h3.py HTTP/3 discovery from the Alt-Svc header + optional
22
+ QUIC probe orchestration (--http3)
23
+ h3_quic.py aioquic client; imported only through h3.py (optional
24
+ `http3` extra, not a runtime dependency)
20
25
  scorer.py weighted composite ranking
21
26
  display.py rich.Table + JSON/CSV emitters
22
27
 
23
28
  tests/
24
29
  fixtures/ captured mirror-list bodies per distro
25
30
  test_*.py respx-mocked units + @pytest.mark.integration opt-ins
26
- (45 tests, 2 integration-gated)
31
+ (integration.yml runs the opt-ins each week)
27
32
  ```
28
33
 
29
34
  ## Commands (use these, not ad-hoc ones)
@@ -100,7 +105,8 @@ Run a single test: `uv run pytest tests/test_scorer.py::test_weights_shift_ranki
100
105
  - Don't skip hooks (`--no-verify`), nor add `# type: ignore` / `# noqa` /
101
106
  `# nosec` without a one-line reason at the call site.
102
107
  - Don't introduce new runtime dependencies without a strong reason — three
103
- (`httpx[http2]`, `typer`, `rich`) is the budget.
108
+ (`httpx[http2]`, `typer`, `rich`) is the budget. `aioquic` is an optional
109
+ extra (`http3`): import it only in `benchmark/h3_quic.py`.
104
110
  - Don't expand `[tool.ruff.lint] select` without a concrete bug class in mind.
105
111
  - Don't emit rich output to stdout when `--json` or `--csv` is set.
106
112
  - Don't rely on network access in unit tests. Network-touching tests must be
@@ -111,6 +117,9 @@ Run a single test: `uv run pytest tests/test_scorer.py::test_weights_shift_ranki
111
117
  - **Mypy error.** Fix the types. `# type: ignore` is not allowed in this
112
118
  codebase — if you think you need one, you're probably modelling something
113
119
  wrong.
120
+ - **Scheduled integration run fails.** First check for upstream drift: a
121
+ moved file, a renamed list field, an end-of-life default release. Fix the
122
+ path or default and refresh the fixture to match the live format.
114
123
  - **Integration test flaky.** Real mirrors come and go. If reproducible,
115
124
  tighten the assertion or bump tolerance. Don't add retries to runtime code
116
125
  just to paper over a test.
@@ -0,0 +1,105 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
+ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0](https://github.com/MysticRyuujin/mirror-bench/compare/v0.1.2...v0.2.0) (2026-11-13)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * follow redirects in probes and require a 2xx response; reject an https mirror that redirects to http
14
+ * report throughput from phase 2 only; throughput_bps is null when phase 2 fails or is skipped
15
+ * remove the certificate point from the security score; the maximum is 2.5
16
+ * change default releases to Debian trixie, Mint zena, and Fedora 45
17
+ * reject --json together with --csv
18
+ * stop mapping distros with their own mirror network (rocky, almalinux, centos, rhel, ol, manjaro, kali, parrot, devuan, raspbian) to another distro
19
+
20
+ ### Features
21
+
22
+ * add --http3 to confirm HTTP/3 with a QUIC probe (optional http3 extra) ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
23
+ * add throughput_error to JSON and CSV output ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
24
+ * report mirrors that advertise HTTP/3 (HTTP/3 column, h3_advertised) ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * add a time limit to both probe phases ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
30
+ * change default releases to Debian trixie, Mint zena, and Fedora 45 ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
31
+ * correct probe and throughput paths for Ubuntu, Mint, Arch, and Fedora ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
32
+ * **docker:** remove pip from the runtime image ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
33
+ * drop rsync entries from Arch discovery ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
34
+ * exit with code 1 and a message when discovery fails ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
35
+ * follow redirects in probes and require a 2xx response; reject an https mirror that redirects to http ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
36
+ * handle Debian records with no Country, and try https and http for each Debian site ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
37
+ * reject --concurrency 0 and --top 0 ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
38
+ * reject --json together with --csv ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
39
+ * remove the certificate point from the security score; the maximum is 2.5 ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
40
+ * report throughput from phase 2 only; throughput_bps is null when phase 2 fails or is skipped ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
41
+ * run bench when no subcommand is given ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
42
+ * stop mapping distros with their own mirror network (rocky, almalinux, centos, rhel, ol, manjaro, kali, parrot, devuan, raspbian) to another distro ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
43
+
44
+
45
+ ### Dependencies
46
+
47
+ * **actions:** bump actions/checkout ([#9](https://github.com/MysticRyuujin/mirror-bench/issues/9)) ([be49743](https://github.com/MysticRyuujin/mirror-bench/commit/be49743112ba6001b022bd3daec9c442a5925a66))
48
+ * **actions:** bump actions/setup-python in the actions group ([#12](https://github.com/MysticRyuujin/mirror-bench/issues/12)) ([930680d](https://github.com/MysticRyuujin/mirror-bench/commit/930680da12de59dc3171385bcb3672399586f66f))
49
+ * bump anyio, certifi, h2, hpack ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
50
+ * bump idna to 3.18 (fixes PYSEC-2026-215) ([#10](https://github.com/MysticRyuujin/mirror-bench/issues/10)) ([690565c](https://github.com/MysticRyuujin/mirror-bench/commit/690565cc1669320eded4faaa175eb8ae85d739e4))
51
+
52
+ ## [0.1.2](https://github.com/MysticRyuujin/mirror-bench/compare/v0.1.1...v0.1.2) (2026-04-23)
53
+
54
+
55
+ ### Bug Fixes
56
+
57
+ * **docs:** lowercase ghcr.io references (docker pull requires lowercase) ([#8](https://github.com/MysticRyuujin/mirror-bench/issues/8)) ([31fcf57](https://github.com/MysticRyuujin/mirror-bench/commit/31fcf57239fc98eecb6df9fe8fed06400cb55dc7))
58
+
59
+
60
+ ### Documentation
61
+
62
+ * add shields.io badges at top of README ([#6](https://github.com/MysticRyuujin/mirror-bench/issues/6)) ([c25c12b](https://github.com/MysticRyuujin/mirror-bench/commit/c25c12bdc36aa6af67cbbb1b8b7ec65c000d7a53))
63
+
64
+ ## [0.1.1](https://github.com/MysticRyuujin/mirror-bench/compare/v0.1.0...v0.1.1) (2026-04-23)
65
+
66
+
67
+ ### Bug Fixes
68
+
69
+ * **ci:** pin aquasecurity/trivy-action to a version that exists ([#2](https://github.com/MysticRyuujin/mirror-bench/issues/2)) ([9984cd0](https://github.com/MysticRyuujin/mirror-bench/commit/9984cd018aaaa6de34b5cdf8261752f563187858))
70
+
71
+
72
+ ### Dependencies
73
+
74
+ * **actions:** bump the actions group across 1 directory with 11 updates ([#4](https://github.com/MysticRyuujin/mirror-bench/issues/4)) ([4aba2a6](https://github.com/MysticRyuujin/mirror-bench/commit/4aba2a68079d2029bf5cc387d9a6eae459d9ff2a))
75
+
76
+ ## [Unreleased]
77
+
78
+ ## [0.1.0] - 2026-04-23
79
+
80
+ ### Added
81
+
82
+ - Cross-distribution mirror benchmarking for **Ubuntu, Debian, Fedora,
83
+ Linux Mint, and Arch**.
84
+ - Two-phase probe: async latency screen (ranged GET on each distro's canonical
85
+ metadata file; median of 3 samples) followed by a throughput test on the
86
+ top-N candidates.
87
+ - Weighted composite scoring across latency, throughput, and security (HTTPS +
88
+ TLS 1.3 + valid cert + HTTP/2). Weights adjustable via
89
+ `--weights lat=…,thr=…,sec=…`.
90
+ - `bench` and `list` subcommands with `--json` / `--csv` / `--https-only` /
91
+ `--tls13-only` / `--no-throughput` / `--country` / `--top` / `--concurrency`.
92
+ - Host auto-detection via `/etc/os-release`, with derivative alias support
93
+ (Pop!\_OS, Kali, Rocky, etc.). `--distro` override for containers and macOS.
94
+ - Shell completion via typer (`--install-completion` / `--show-completion`).
95
+ - Multi-stage Dockerfile (`python:3.14-slim-bookworm`, non-root uid 65532).
96
+ - GitHub Actions CI + release pipelines with OIDC PyPI publishing, multi-arch
97
+ container image, SLSA build provenance, and Trivy image scanning.
98
+ - Pre-commit hooks: ruff, mypy, bandit, gitleaks, hadolint, shellcheck,
99
+ yamllint, markdownlint, commitizen.
100
+ - `SECURITY.md` with responsible-disclosure workflow and supply-chain notes.
101
+ - Licensed under **GNU AGPL-3.0-or-later** — users of hosted/network-facing
102
+ modified versions must receive the modified source.
103
+
104
+ [Unreleased]: https://github.com/MysticRyuujin/mirror-bench/compare/v0.1.0...HEAD
105
+ [0.1.0]: https://github.com/MysticRyuujin/mirror-bench/releases/tag/v0.1.0
@@ -66,7 +66,7 @@ walks you through the format interactively.
66
66
  1. Create `src/mirror_bench/discovery/<name>.py` implementing
67
67
  `MirrorDiscoverer` (`discover`, `probe_path`, `throughput_path`).
68
68
  2. Register it in `src/mirror_bench/discovery/__init__.py`'s `DISCOVERERS` map.
69
- 3. Add the distro's `ID` / `ID_LIKE` aliases to
69
+ 3. Add the distro's `ID` aliases to
70
70
  `src/mirror_bench/distro.py::_ALIASES`.
71
71
  4. Add a respx-mocked test with a realistic fixture under
72
72
  `tests/fixtures/<name>_*`.
@@ -8,7 +8,7 @@ ARG UV_VERSION=0.11.7
8
8
  FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
9
9
 
10
10
  FROM python:${PYTHON_VERSION}-slim-bookworm AS builder
11
- ARG VERSION=0.1.0
11
+ ARG VERSION=dev
12
12
 
13
13
  # Minimal build deps; nothing compiled lands in the final image.
14
14
  ENV PYTHONDONTWRITEBYTECODE=1 \
@@ -33,7 +33,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
33
33
 
34
34
  # ---- Runtime stage -------------------------------------------------------
35
35
  FROM python:${PYTHON_VERSION}-slim-bookworm AS runtime
36
- ARG VERSION=0.1.0
36
+ ARG VERSION=dev
37
37
 
38
38
  LABEL org.opencontainers.image.title="mirror-bench" \
39
39
  org.opencontainers.image.description="Cross-distribution Linux package mirror benchmarking." \
@@ -46,11 +46,14 @@ LABEL org.opencontainers.image.title="mirror-bench" \
46
46
  # We deliberately do not pin ca-certificates: the whole point of this package
47
47
  # is to ship the latest Debian-security-vetted CA list at build time. Pinning
48
48
  # a specific version would freeze trust material and defeat the upgrade path.
49
+ # The runtime never installs anything, so pip goes too: its vendored copies of
50
+ # urllib3, msgpack, and setuptools are what image scanners report.
49
51
  # hadolint ignore=DL3008
50
52
  RUN apt-get update \
51
53
  && apt-get install -y --no-install-recommends ca-certificates \
52
54
  && apt-get clean \
53
55
  && rm -rf /var/lib/apt/lists/* \
56
+ && python -m pip uninstall --yes --no-cache-dir pip \
54
57
  && groupadd --system --gid 65532 app \
55
58
  && useradd --system --uid 65532 --gid app \
56
59
  --home-dir /home/app --create-home --shell /usr/sbin/nologin app
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: linux-mirror-bench
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Cross-distribution Linux package mirror benchmarking tool
5
5
  Project-URL: Homepage, https://github.com/MysticRyuujin/mirror-bench
6
6
  Project-URL: Issues, https://github.com/MysticRyuujin/mirror-bench/issues
@@ -21,6 +21,8 @@ Requires-Python: >=3.14
21
21
  Requires-Dist: httpx[http2]>=0.27
22
22
  Requires-Dist: rich>=13
23
23
  Requires-Dist: typer>=0.12
24
+ Provides-Extra: http3
25
+ Requires-Dist: aioquic>=1.2; extra == 'http3'
24
26
  Description-Content-Type: text/markdown
25
27
 
26
28
  # mirror-bench
@@ -35,7 +37,7 @@ Description-Content-Type: text/markdown
35
37
 
36
38
  A cross-distribution Linux package mirror benchmarking tool.
37
39
 
38
- Ranks mirrors by **latency**, **throughput**, and **security** (HTTPS / TLS 1.3 / cert validity / HTTP/2).
40
+ Ranks mirrors by **latency**, **throughput**, and **security** (HTTPS / TLS 1.3 / HTTP/2).
39
41
 
40
42
  `mirror-bench` is **read-only**: it discovers mirrors, probes them, prints a
41
43
  ranked table, and exits. It never touches `/etc/apt/sources.list`,
@@ -131,19 +133,20 @@ Running `mirror-bench` with no subcommand is equivalent to `mirror-bench bench`.
131
133
 
132
134
  ### Common flags
133
135
 
134
- | Flag | Description |
135
- | ----------------------------- | ------------------------------------------------------------------------------------------- |
136
- | `--distro, -d NAME` | One of `ubuntu`, `debian`, `fedora`, `mint`, `arch`. |
137
- | `--release, -r VALUE` | Release override: codename for apt (noble, bookworm, wilma) or numeric for Fedora (41, 42). |
138
- | `--country, -c US,CA,GB` | ISO 3166-1 alpha-2 country codes to filter on. |
139
- | `--top, -n N` | How many mirrors to include in phase 2 / display (default 15). |
140
- | `--concurrency N` | Max parallel HTTP probes (default 20). |
141
- | `--https-only` | Hard-exclude mirrors without HTTPS. |
142
- | `--tls13-only` | Hard-exclude mirrors that don't negotiate TLS 1.3. |
143
- | `--weights lat=…,thr=…,sec=…` | Override scoring weights (auto-normalized). |
144
- | `--no-throughput` | Skip phase 2. Phase-1 latency screen only. |
145
- | `--json` | Emit JSON to stdout. Suppresses the table + progress. |
146
- | `--csv` | Emit CSV to stdout. Suppresses the table + progress. |
136
+ | Flag | Description |
137
+ | ----------------------------- | ---------------------------------------------------------------------------------------- |
138
+ | `--distro, -d NAME` | One of `ubuntu`, `debian`, `fedora`, `mint`, `arch`. |
139
+ | `--release, -r VALUE` | Release override: codename for apt (noble, trixie, zena) or numeric for Fedora (44, 45). |
140
+ | `--country, -c US,CA,GB` | ISO 3166-1 alpha-2 country codes to filter on. |
141
+ | `--top, -n N` | How many mirrors to include in phase 2 / display (default 15). |
142
+ | `--concurrency N` | Max parallel HTTP probes (default 20). |
143
+ | `--https-only` | Hard-exclude mirrors without HTTPS. |
144
+ | `--tls13-only` | Hard-exclude mirrors that don't negotiate TLS 1.3. |
145
+ | `--weights lat=…,thr=…,sec=…` | Override scoring weights (auto-normalized). |
146
+ | `--no-throughput` | Skip phase 2. Phase-1 latency screen only. |
147
+ | `--http3` | Probe over QUIC the mirrors that advertise HTTP/3. Needs the `http3` extra. |
148
+ | `--json` | Emit JSON to stdout. Suppresses the table + progress. |
149
+ | `--csv` | Emit CSV to stdout. Suppresses the table + progress. |
147
150
 
148
151
  ## Running in Docker
149
152
 
@@ -277,7 +280,7 @@ All arguments are optional; defaults match what CI ships.
277
280
  | ---------------- | -------- | ------------------------------------------------------------------- |
278
281
  | `PYTHON_VERSION` | `3.14` | Base image tag (`python:<PYTHON_VERSION>-slim-bookworm`). |
279
282
  | `UV_VERSION` | `0.11.7` | uv release pulled from `ghcr.io/astral-sh/uv:<UV_VERSION>`. |
280
- | `VERSION` | `0.1.0` | Value embedded in the `org.opencontainers.image.version` OCI label. |
283
+ | `VERSION` | `dev` | Value embedded in the `org.opencontainers.image.version` OCI label. |
281
284
 
282
285
  Example:
283
286
 
@@ -369,6 +372,26 @@ $ mirror-bench bench --<TAB>
369
372
  --distro --country --top --concurrency --https-only --tls13-only …
370
373
  ```
371
374
 
375
+ ## Supported distributions
376
+
377
+ | `--distro` | Auto-detected from `/etc/os-release` `ID` |
378
+ | ---------- | ---------------------------------------------- |
379
+ | `ubuntu` | `ubuntu`, `pop`, `elementary`, `zorin`, `neon` |
380
+ | `debian` | `debian` |
381
+ | `fedora` | `fedora`, `nobara` |
382
+ | `mint` | `linuxmint` |
383
+ | `arch` | `arch`, `archlinux`, `endeavouros` |
384
+
385
+ Another Ubuntu remix is detected when its `os-release` has `ID_LIKE=ubuntu`
386
+ and `UBUNTU_CODENAME`.
387
+
388
+ Distributions with their own mirror network (RHEL family, Kali, Devuan,
389
+ Raspberry Pi OS, Manjaro) are not supported. Ranking the mirrors of a
390
+ different distribution for them gives a wrong answer.
391
+
392
+ The Ubuntu, Mint, and Arch throughput tests always fetch the x86_64 file.
393
+ The mirror lists for those distributions cover x86_64 mirrors only.
394
+
372
395
  ## How it works
373
396
 
374
397
  1. **Discover** — fetch the canonical mirror list for the target distribution:
@@ -376,23 +399,66 @@ $ mirror-bench bench --<TAB>
376
399
  set (`mirrors.ubuntu.com/mirrors.txt` returns a geo-selected _single_
377
400
  mirror, not a full list — so we don't rely on it alone).
378
401
  - Debian: `mirror-master.debian.org/status/Mirrors.masterlist` (RFC822
379
- multi-record format).
402
+ multi-record format). The list does not say which sites serve HTTPS, so
403
+ each site is tried as an `https://` and an `http://` candidate.
380
404
  - Fedora: `mirrors.fedoraproject.org/mirrorlist?repo=&arch=&country=`.
381
405
  - Linux Mint: scraped from `linuxmint.com/mirrors.php`.
382
- - Arch: `archlinux.org/mirrors/status/json/` (JSON, filtered to active).
406
+ - Arch: `archlinux.org/mirrors/status/json/` (JSON, filtered to active
407
+ HTTP and HTTPS entries).
408
+
409
+ If the list cannot be fetched, the command prints the reason and exits
410
+ with code 1.
411
+
383
412
  2. **Phase 1 — latency screen.** Concurrent ranged GET (`bytes=0-1023`) on the
384
- distro's canonical small metadata file (`InRelease`, `repomd.xml`,
385
- `lastsync`). Three samples per mirror, **median TTFB** used for ranking.
386
- 3. **Phase 2 — throughput test.** Full streaming GET of a representative file
387
- (`Contents-<arch>.gz` for apt, `core.db` for Arch, etc.) on the top N from
388
- phase 1, capped at 5 MiB. `bytes_per_sec` is measured from first-byte to
389
- end-of-stream so steady-state bandwidth isn't polluted by connect/TLS
390
- overhead.
413
+ distro's canonical small metadata file (`InRelease` for Ubuntu and Debian,
414
+ `Release` for Mint, `repomd.xml` for Fedora, `lastsync` for Arch). Three
415
+ samples per mirror, **median TTFB** used for ranking.
416
+ 3. **Phase 2 — throughput test.** Streaming GET of a large file
417
+ (`Contents-<arch>.gz` for apt, `images/install.img` for Fedora, `extra.db`
418
+ for Arch) on the top N from phase 1, capped at 5 MiB and 30 seconds.
419
+ `bytes_per_sec` is measured from first-byte to last-byte so steady-state
420
+ bandwidth isn't polluted by connect/TLS overhead.
421
+
422
+ Both phases follow redirects, so the timing includes the extra hop that a
423
+ package manager also pays. Only a 2xx final response counts. An `https://`
424
+ mirror that redirects to `http://` fails with `https_downgrade`. An
425
+ `http://` mirror gets no TLS credit, even when it redirects to HTTPS.
426
+
391
427
  4. **Score.** Weighted composite of normalized latency (inverse), throughput,
392
- and security (HTTPS + TLS 1.3 + cert-valid + HTTP/2, max 3.5). Weights
428
+ and security (HTTPS 1 + TLS 1.3 1 + HTTP/2 0.5, max 2.5). Weights
393
429
  default to `lat=0.4, thr=0.4, sec=0.2` and are user-overridable.
430
+ - Throughput is the phase 2 measurement only. A mirror whose phase 2 probe
431
+ fails shows `-`, scores 0 for throughput, and reports the cause in the
432
+ `throughput_error` field of the JSON and CSV output.
433
+ - With `--no-throughput`, the throughput weight is 0 and the other weights
434
+ are normalized again.
435
+ - When a site answers on `https://` and `http://`, only the `https://` row
436
+ is kept.
394
437
  5. **Render.** A `rich.Table`, or JSON / CSV for scripting.
395
438
 
439
+ ### HTTP/3
440
+
441
+ Phase 1 reads the `Alt-Svc` response header. The `HTTP/3` column shows `adv`
442
+ when a mirror advertises HTTP/3 (`h3`) for its own host; JSON and CSV carry
443
+ the same fact as `h3_advertised`. This has no effect on the score: apt, dnf,
444
+ and pacman do not use HTTP/3 today.
445
+
446
+ To confirm the advertisement with a real QUIC request, install the optional
447
+ extra and add `--http3`:
448
+
449
+ ```sh
450
+ uv tool install "linux-mirror-bench[http3]"
451
+ mirror-bench bench --distro arch --http3
452
+ ```
453
+
454
+ - Each ranked mirror that advertises HTTP/3 gets one ranged GET over QUIC.
455
+ - The `HTTP/3` column shows `yes` when that request succeeds. JSON and CSV
456
+ gain `h3_latency_ms` and `h3_error`.
457
+ - If UDP is blocked on your network, the probes fail after about 8 seconds
458
+ and the column stays at `adv`.
459
+ - Without the extra, `--http3` exits with code 2 and names the extra.
460
+ - The container image does not include the extra.
461
+
396
462
  ## Development
397
463
 
398
464
  See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full dev loop.