Skip to main content

ISOx

Python PyPI License Release OS CI

A command-line tool that downloads Linux distribution ISOs, races mirrors to find the fastest source, and verifies the ISO against a checksum published by the distro's own host, never the mirror that served it (what that does and doesn't cover). No more hunting down hashes or skipping verification.

Select distro -> Compare mirror speeds -> Download .iso -> Verify checksum
                                          (fastest mirror)  (distro's own host)

Why

I distro-hop a lot across laptops, tablets, Pis, and spare hardware. Manually visiting each project's download page, picking a mirror, and copy-pasting checksums to verify against every time got tedious enough that I started skipping the verification step entirely. This poses an integrity risk (modified ISOs, corruption, etc.), so I built a tool that automates the whole pipeline and makes verification simple.

Furthermore, I simply love Linux. It's been my daily driver for a while now, and I want to see it continue to grow. I hope this tool makes getting started with Linux a little faster, easier, and safer for anyone who wants to use it.

Install

From PyPI:

pip install isox
isox arch

Or straight from a clone:

pip install .
isox arch

Usage

Once installed from PyPI, isox works as a bare command. From a clone, use python isox.py. The two are interchangeable; examples below use the clone form.

List every supported distro:

python isox.py --list

Save somewhere other than ./ISOx_Downloads:

python isox.py arch --output-dir /example/directory

Pick an older release instead of the newest:

python isox.py ubuntu-server --release 24.04
python isox.py proxmox --release 8
python isox.py mint --release 22        # newest 22.x

Download and verify a distro:

python isox.py arch
python isox.py debian
python isox.py kali
python isox.py alpine
python isox.py mint
python isox.py fedora
python isox.py opensuse
python isox.py gentoo
python isox.py void
python isox.py garuda
python isox.py ubuntu
python isox.py rocky
python isox.py alma
python isox.py cachyos
python isox.py mageia
python isox.py openmandriva
python isox.py proxmox
python isox.py ubuntu-server
python isox.py zorin
python isox.py centos-stream

Downloaded ISOs are saved to the created folder ISOx_Downloads/. Output looks like:

https://fastly.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso sampled at 3.33 MB/s
https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso sampled at 1.98 MB/s
https://mirror.rackspace.com/archlinux/iso/latest/archlinux-x86_64.iso sampled at 0.08 MB/s
Downloading archlinux-x86_64.iso from https://fastly.mirror.pkgbuild.com/iso/latest ...
[##############################] 100.0%    3.41 MB/s
Checksum matches, file is good.

Features

  • Config-driven distro support - supported distros are defined in distros.json, meaning adding a new distro is a JSON entry, not a code change.
  • Three ISO-discovery strategies - covers distros that publish their ISOs in very different ways.
  • Version-folder auto-discovery - for distros with no stable "latest" alias, the current version-numbered directory is discovered by scanning a parent directory and sorting versions (folder names).
  • Release selection - --release picks an older release, such as Ubuntu 24.04 or Proxmox 8. A partial version like 22 gets the newest release in that series.
  • Mirror speed checks - samples ~2MB from each candidate mirror via a ranged request to measure real throughput, then downloads from the fastest.
  • Resumable downloads - interrupted transfers are written to a .part file and continued via an HTTP Range request on the next run. (.part files that are stale are also checked before resuming, and are scrapped if they're out of date)
  • Live progress bar - shows percentage and real-time throughput, and degrades to a plain byte counter if the server won't report a total size.
  • Streamed downloads - files are downloaded in large chunks (requests with stream=True), so multi-GB ISOs don't hog RAM.
  • Failure quarantine - an ISO that fails verification is renamed, so it can't be mistaken for a verified file.
  • Path-traversal protection - filenames discovered from remote HTML listings are validated before ever being used in a URL or local file path.

How it works

This section covers what you need to configure and run ISOx.

Config format (distros.json)

Every distro entry needs mirrors, checksum_filename, and hash_algo at minimum. Everything else is optional and only needed if that distro deviates from the simplest cases.

If the included mirrors are not ideal for your location, you can easily update them. Just find a suitable mirror from the distro's official mirror list and replace the URL in distros.json. The tool will then handle the rest. Mirror, checksum and version-discovery URLs must be HTTPS.

{
    "arch": {
        "mirrors": ["https://fastly.mirror.pkgbuild.com/iso/latest/"],
        "checksum_base": "https://geo.mirror.pkgbuild.com/iso/latest/",
        "checksum_filename": "sha256sums.txt",
        "hash_algo": "sha256",
        "iso_filename": "archlinux-x86_64.iso"
    },
    "fedora": {
        "mirrors": [
            "https://dl.fedoraproject.org/pub/fedora/linux/releases/{version}/Workstation/x86_64/iso/",
            "https://mirror.cs.princeton.edu/pub/mirrors/fedora/linux/releases/{version}/Workstation/x86_64/iso/",
            "https://mirror.arizona.edu/fedora/linux/releases/{version}/Workstation/x86_64/iso/"
        ],
        "version_directory" : true,
        "version_discovery_url" : [
            "https://dl.fedoraproject.org/pub/fedora/linux/releases/",
            "https://mirror.arizona.edu/fedora/linux/releases/"
        ],
        "checksum_base": "https://dl.fedoraproject.org/pub/fedora/linux/releases/{version}/Workstation/x86_64/iso/",
        "checksum_filename" : "CHECKSUM",
        "checksum_discovery_method" : "html_scan",
        "checksum_format" : "bsd",
        "discovery_method": "html_scan",
        "hash_algo": "sha256",
        "iso_filename_contains": ["Workstation", "x86_64"]
    }
}
Key Purpose
mirrors Required. Where the ISO may be downloaded from. Raced on every run.
checksum_filename Required. Literal name, or a {iso_filename}.sha256 template.
hash_algo Required. Anything hashlib supports. Validated before any download starts.
checksum_base Host to fetch the checksum from, ideally the distro's own server. Also decides the ISO filename, so name and hash always agree.
iso_filename For distros whose filename never changes.
iso_filename_contains Substrings every candidate filename must contain.
iso_filename_excludes Substrings that disqualify a filename. Use this when a distro publishes images your substrings can't tell apart, like Debian's -edu- and -mac- images.
discovery_method checksum_scan (default) or html_scan.
checksum_discovery_method html_scan when the checksum filename itself has to be scraped.
checksum_format multi (default), bsd, or single.
version_directory true when the current version has to be discovered first.
version_discovery_url One URL or a list of them, tried in order.
version_scheme ubuntu_lts to select only LTS releases.
version_suffix Suffix to strip from version folder names before sorting, such as -stream for CentOS's 10-stream.
rolling true for rolling-release distros. --release is refused with a clear message.
release_filename_prefix For flat directories that keep every release side by side, such as proxmox-ve_. --release filters on the version after this prefix. Requires html_scan.

Where distros.json is loaded from

Searched in this order, first hit wins:

  1. $ISOX_DISTROS
  2. ~/.config/isox/distros.json (%APPDATA%\isox\distros.json on Windows)
  3. Beside isox.py (usually when it's git cloned)
  4. share/isox/distros.json under the install scheme's data directory, the user base, or sys.prefix

If you customize mirrors on a pip install, put your copy at (2), so it survives updates.

mkdir -p ~/.config/isox
isox --list                      # prints the config path currently in use
cp "$(isox --list | sed -n 's/^config: //p')" ~/.config/isox/distros.json

ISO filename discovery

  • "iso_filename" - for distros with one fixed, unchanging filename.
  • "iso_filename_contains" + default discovery - scans a shared checksum file for a filename matching all the given substrings.
  • "iso_filename_contains" + "discovery_method": "html_scan" - scrapes the directory listing HTML for distros with no single shared checksum file.

Version-folder discovery

For distros with no stable "latest" URL alias, "version_directory": true scrapes the parent directory first, sorts version folder names numerically, and splices the newest into every {version} placeholder.

version_discovery_url takes a list as well as a single URL, tried in order. version_scheme: "ubuntu_lts" narrows this to even-year .04 folders, so python isox.py ubuntu resolves to the latest LTS rather than the latest interim.

Selecting a release

--release narrows discovery to a specified version. The listing is still scraped, and only entries whose version starts with the requested numbers are kept. 22 matches 22.1 and 22.3 but not 220. The newest of those wins.

  • Version folders (Ubuntu, Ubuntu Server, Fedora, Rocky, Alma, Mint, Mageia, Zorin, CentOS Stream) filter the folder names. ubuntu still only accepts LTS releases.
  • Flat directories (Proxmox) filter filenames using release_filename_prefix.
  • Rolling distros and distros whose mirrors only serve the current release refuse the flag before any network request.

A release must still be on the configured mirrors. Distros that move old releases to an archive (Rocky & Fedora for example) can't serve them through --release.

Checksum parsing

Three published formats are normalized into the same {filename: hash} lookup:

  • multi (default) - <hash> <filename>, one per line
  • bsd - SHA256 (filename) = <hash>
  • single - the file contains only the hash

Mirror selection

Each mirror is sampled with a ranged GET pulling the first ~2MB of the actual ISO, and real throughput is measured over that sample. Fastest wins. Mirrors that time out or error are skipped.

Resumable downloads

Downloads are written to <filename>.part and only renamed to the final name once the transfer completes, so a partial can't be mistaken for a finished file. On the next run the .part size becomes the offset in a Range: bytes=N- request.

The tool will also handle size mismatches and stale part files.

Checksum verification

The checksum file is fetched new on every run, from checksum_base and compared with hmac.compare_digest. A hash mismatch renames the ISO to <filename>.FAILED; a missing entry renames it to <filename>.UNVERIFIED.

What verification does and doesn't cover

Covered. Corruption in transit, truncated transfers, a bad disk, a botched resume, and a mirror serving a modified ISO. Modified ISOs are handled by grabbing the checksum from the main distro host.

Not covered. If the distro's own server is compromised, both the ISO and its checksum can be replaced together. Only signature verification (GPG) catches that.

GPG is not included as it would require maintaining trusted public keys (or fingerprints) for every supported distribution, along with key management and signature validation logic. That complexity conflicts with ISOx's goal of being a lightweight, easy to use, and config-driven Linux tool.

Requirements

  • Python 3.10+
  • requests
  • beautifulsoup4 - used for HTML directory-listing discovery

Everything else (hashlib, hmac, json, argparse, os, sys, time, re, site, sysconfig) is part of the Python standard library.

Development

pip install -e '.[dev]'
pytest
black --check .
ruff check .
bandit isox.py

The suite is simulated (no network), except tests/test_live_mirrors.py, as it resolves every distro in distros.json against real mirrors and checks if the filename it lands on has a checksum, without downloading any ISO. It's deselected by default and takes about a minute:

pytest -m live       # all distros
pytest -m live -s    # printing each resolved filename and hash
pytest -m live -k rocky

Contributing

Distro requests and additions are welcome. Most new distros are a distros.json entry with no Python at all. See CONTRIBUTING.md.

License

MIT License: see LICENSE for details. Feel free to use, modify, or build on this.

Metadata

Release files for isox 3.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for isox 3.5.0
File Size Uploaded
isox-3.5.0.tar.gz 36.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for isox 3.5.0
File Interpreter ABI Platform
isox-3.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 54.0 kB

Release files / isox-3.5.0.tar.gz

Download URL isox-3.5.0.tar.gz
Size 36.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2b8f875bca11d13066ad5c1707b4246b24d2f08c22b9535b04b8abff38372397
BLAKE2b-256 checksum
How to use checksums
b48f7b418f2b62ba62151da8d6f745c1b3f653dc8905721cdaabd2c49436a635
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / isox-3.5.0-py3-none-any.whl

Download URL isox-3.5.0-py3-none-any.whl
Size 17.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
632968328136dde721450a2658835ae0d4cc7885a8a726dff6eabdce38f91b92
BLAKE2b-256 checksum
How to use checksums
36644b6b670004e78cbc74c019a12a184134b9fe2bd2f0f228693b6d60b0d58c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release history Release notifications | RSS feed

3.7.0

2 release files

3.6.0

2 release files

This release

3.5.0 This release

2 release files

3.4.0

2 release files

3.3.1

2 release files

3.3.0

2 release files

3.2.1

2 release files

3.2.0

2 release files

3.1.0

2 release files

3.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page