Find and download OS ISO images for Ubuntu, Fedora, openSUSE, Arch, Linux Mint, Rocky Linux, Windows 11, and more
Project description
os-download is a two-command Python CLI that finds the latest download URL for 15 popular OS ISO families and pulls them to disk — with signature-checked verification, working resume, parallel and multi-connection downloads, and automatic retries.
os-finder # resolve latest ISO URLs for all supported OSes
os-download # download everything that was found, verifying as it goes
Supported operating systems
| OS | Source | Auto-version | Format | Verification |
|---|---|---|---|---|
| Ubuntu | Launchpad API + releases.ubuntu.com | Yes (LTS + latest) | .iso |
Signed (pinned key) |
| Debian | cdimage.debian.org | Yes | .iso |
Signed (pinned key) |
| Linux Mint | kernel.org Linux Mint mirror | Yes (Cinnamon x64) | .iso |
Signed (pinned key) |
| Fedora | fedoraproject.org mirrors | Yes (Workstation + Server) | .iso |
Signed (distro keyring) |
| OPNsense | pkg.opnsense.org | Yes | .iso.bz2 (auto-extracted) |
SHA256 if published |
| pfSense CE | Netgate CDN | Yes | .iso.gz (auto-extracted) |
SHA256 |
| TrueNAS Scale | GitHub Releases API | Yes | .iso |
SHA256 if published |
| Manjaro KDE | manjaro.org/products | Yes | .iso |
SHA256 if published |
| MX Linux | mxlinux.org / SourceForge | Yes (Xfce x64) | .iso |
SHA256 if published |
| Puppy Linux | SourceForge CDN → ibiblio fallback | Yes (fossapup64) | .iso |
SHA256 if published |
| CachyOS | mirror.cachyos.org | Yes | .iso |
SHA256 |
| openSUSE Tumbleweed | download.opensuse.org | Yes (Current) | .iso |
SHA256 |
| Arch Linux | geo.mirror.pkgbuild.com | Yes (latest alias) | .iso |
SHA256 |
| Rocky Linux | download.rockylinux.org | Yes (latest major x86_64) | .iso |
SHA256 |
| Windows 11 | Mido | Yes | .iso |
Handled by Mido |
Signed means the checksum file itself is verified against a signing key that os-download pins — a mirror cannot forge it. SHA256 means the hash is checked, which proves the download is intact but not that the mirror was honest. See Verification.
Windows 11 — Microsoft's ISO download requires a JavaScript session-token flow that cannot be replicated with plain HTTP. os-download delegates this to Mido, which is fetched at a pinned commit on first use.
Requirements
- Python 3.10 or newer (3.9 and earlier are not supported)
- Linux, macOS, or Windows
Two optional binaries unlock more, and os-download works without either:
| Binary | Without it |
|---|---|
gpg |
Signatures cannot be checked; verification falls back to hash-only and says so |
aria2c |
Downloads use a single connection instead of splitting each file across several |
# Debian / Ubuntu
sudo apt install gnupg aria2
# Fedora
sudo dnf install gnupg2 aria2
# Arch
sudo pacman -S gnupg aria2
# macOS
brew install gnupg aria2
Installation
os-download is on PyPI. Either installer puts os-finder and os-download on your PATH in an isolated environment, available from any directory.
With uv (recommended)
# 1. Install uv (skip if you already have it)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. Install os-download
uv tool install os-download
# Upgrade to the latest version at any time
uv tool upgrade os-download
# Uninstall
uv tool uninstall os-download
With pipx
pipx install os-download
Bleeding edge
To install the current main rather than the latest release:
uv tool install git+https://github.com/dragonfoxsl/os-download
Development install
git clone https://github.com/dragonfoxsl/os-download
cd os-download
uv sync
# Run from the project directory
uv run os-finder
uv run os-download
Usage
Step 1: Find ISO URLs
# All supported OSes (runs in parallel)
os-finder
# Specific OSes
os-finder --os ubuntu fedora opensuse arch linuxmint rocky
# Save URLs to a custom path
os-finder --output ~/isos/urls.txt
# Increase timeout for slow connections
os-finder --timeout 30
# Machine-readable JSON output
os-finder --json
# Check every OS still resolves to an ISO (exits non-zero if a mirror moved)
os-finder --check
Mirrors reorganise their directory layouts, which makes a finder quietly stop resolving.
--checkis the same check a scheduled workflow runs weekly against the live mirrors, so a broken finder surfaces as a GitHub issue rather than a surprise at download time.
Step 2: Download
# Download everything found
os-download
# Three simultaneous downloads
os-download --parallel 3
# Skip verification (it is on by default)
os-download --no-verify
# Refuse anything not signed by a pinned distribution key
os-download --require-signature
# Faster: split each file across 16 connections (needs aria2c)
os-download --connections 16
# Force the built-in backend even if aria2c is installed
os-download --backend python
# Give up sooner on a flaky link (each retry resumes)
os-download --retries 1
# Download to a specific directory
os-download --dir /mnt/nas/isos
# Single URL
os-download --url "https://example.com/file.iso"
# Skip automatic decompression of .bz2 / .gz files
os-download --no-decompress
Interrupted downloads resume where they left off — re-run the same command. A dropped connection is retried automatically without failing the file.
Keyboard shortcuts (download dashboard)
| Key | Action |
|---|---|
q |
Quit cleanly — partial files are saved and can be resumed |
Ctrl+C |
Interrupt — same as q, partial files resume automatically |
Download dashboard
When downloading multiple files, os-download shows a live dashboard:
- Header — active / done / failed counts, total data downloaded, speed sparkline, elapsed time
- Files — one progress bar per file with size, speed, and ETA
- Footer — keyboard shortcuts and current session settings
On completion the dashboard transitions to a summary panel showing files downloaded, total data, time taken, and any failures. If downloads fail you are prompted to retry them; the retry runs only the failed files.
At startup, if partial files are detected you are prompted to resume or start from scratch. Files downloaded within the last 24 hours are listed and can be skipped.
Screenshots
Verification
Every download is verified by default. Two things are checked, in this order:
- The signature on the checksum file. A hash fetched from the same mirror as the ISO
only proves the download was not truncated — whoever can serve a bad ISO can serve a
matching hash. Ubuntu, Debian, Linux Mint and Fedora sign their checksum files, and their
signing keys are pinned in
downloader/signatures.py. Keys are only ever imported by pinned fingerprint, or from a keyring published over HTTPS by the distribution itself (Fedora rotates its key every release), so a mirror can never introduce a key of its own. - The SHA256 hash of the file, once the document it came from is known to be authentic.
| Outcome | What it means | Result |
|---|---|---|
| Signed by a pinned key, hash matches | Authentic | Download succeeds |
| Hash matches, but nothing signed it | Intact, unproven | Succeeds with a warning (fails under --require-signature) |
| Bad signature | The signing key was substituted | Fails, file moved to .corrupt |
| Hash mismatch | Corrupt or tampered | Fails, file moved to .corrupt |
| No checksum published | Nothing to check against | Succeeds with a warning (fails under --require-signature) |
A file that fails verification is renamed to <name>.corrupt rather than left in place, so a
later resume cannot append to bad bytes. Verified files record a .verified marker, so a
multi-gigabyte ISO is not re-hashed on every run.
Signature verification needs gpg on PATH; without it, verification falls back to
hash-only and says so.
All flags
os-finder
| Flag | Default | Description |
|---|---|---|
--os |
all |
Space-separated list: ubuntu opnsense pfsense debian truenas windows11 manjaro mxlinux puppy cachyos fedora opensuse arch linuxmint rocky |
--output |
./os-links/all_os.txt |
Output file for resolved ISO URLs |
--timeout |
15 |
HTTP timeout in seconds |
--no-interactive |
off | Skip manual override prompt when a URL cannot be found |
--json |
off | Print JSON to stdout, suppress progress display |
--check |
off | Report which OSes still resolve to an ISO; exits non-zero if any do not |
--log |
./logs/os-finder.log |
Log file path |
--version |
Print the version and exit |
os-download
| Flag | Default | Description |
|---|---|---|
--file / -f |
./os-links/all_os.txt |
URL list file |
--url / -u |
Download a single URL | |
--dir / -d |
~/Downloads/os-isos |
Output directory |
--parallel |
1 |
Simultaneous downloads |
--backend |
auto |
auto / aria2 / python. auto uses aria2c for multi-connection downloads when installed |
--connections |
8 |
Connections per file when using aria2c |
--retries |
3 |
Attempts per file before giving up; a retry resumes |
--no-verify |
off | Skip checksum + signature verification (verification is on by default) |
--require-signature |
off | Fail unless the checksum file is signed by a pinned distribution key |
--no-decompress |
off | Keep .bz2 / .gz files compressed |
--no-resume |
off | Start downloads from the beginning even if partial file exists |
--no-interactive |
off | Fail fast on error without prompting to continue |
--chunk-size |
8192 |
Download chunk size in bytes (built-in backend) |
--log |
./logs/os-download.log |
Log file path |
--version |
Print the version and exit |
How it works
os-finder os-download
───────────────────────────── ─────────────────────────────
MultiOSDownloadFinder DownloadManager
└─ runs all finders in └─ reads ./os-links/all_os.txt
parallel via runs downloads in parallel
ThreadPoolExecutor via ThreadPoolExecutor
Each finder (BaseOSFinder Each download
subclass) is independent: • aria2c, or streams in chunks
• scrapes its source • resumes via Range header
• verifies the URL • retries dropped connections
• returns {variant: url} • checks signature, then hash
• decompresses .bz2/.gz
• mido:// URIs delegated to Mido
Backends: aria2c when installed (several connections per file), the built-in one otherwise, and curl as a fallback for mirrors that reject the library outright with a 403.
Adding a new OS
- Add a finder module under
src/os_download/finders/that subclassesBaseOSFinderfromsrc/os_download/finders/base.py:
class MyOSFinder(BaseOSFinder):
def __init__(self, timeout: int = 15):
super().__init__("My OS", timeout)
def find_download_links(self) -> dict[str, str]:
try:
# fetch, scrape, return {variant: url}
return {"amd64": "https://..."}
except Exception as exc:
# Mirror lookups fail routinely. Log why, or a layout change becomes an
# unexplained "not found".
self.log_failure(exc)
return {}
- Register it in
src/os_download/finders/registry.py:
'myos': MyOSFinder(timeout),
- Add
'myos'toOS_CHOICESsosrc/os_download/cli/finder.pyexposes it through--os.
Credits
| Project | Role |
|---|---|
| Mido by @ElliotKillick | Windows 11 ISO download — Mido replicates Microsoft's JavaScript session-token flow to deliver a direct ISO. os-download clones and invokes it automatically. |
License
MIT
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
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 os_download-0.1.1.tar.gz.
File metadata
- Download URL: os_download-0.1.1.tar.gz
- Upload date:
- Size: 922.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23b55341573486638eeced9eab832c97f4cacaad8a5f648d6067a96de44c6df3
|
|
| MD5 |
083004ed33096a85cb58954a0f984d6f
|
|
| BLAKE2b-256 |
09b8dce01272e47c1a684534cbdab013078078aafc2f452445a9f6d6621c04ae
|
Provenance
The following attestation bundles were made for os_download-0.1.1.tar.gz:
Publisher:
release.yml on dragonfoxsl/os-download
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
os_download-0.1.1.tar.gz -
Subject digest:
23b55341573486638eeced9eab832c97f4cacaad8a5f648d6067a96de44c6df3 - Sigstore transparency entry: 2164454532
- Sigstore integration time:
-
Permalink:
dragonfoxsl/os-download@5b164bd4c5e493c894e21e4f2c706287820fc040 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/dragonfoxsl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5b164bd4c5e493c894e21e4f2c706287820fc040 -
Trigger Event:
push
-
Statement type:
File details
Details for the file os_download-0.1.1-py3-none-any.whl.
File metadata
- Download URL: os_download-0.1.1-py3-none-any.whl
- Upload date:
- Size: 51.5 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 |
2bd433fb0c37bad516fb81b48256006ae4ed9e29bfabd8cb415dafaa785b4ec5
|
|
| MD5 |
61f294e430519e590426fcedcde02bcd
|
|
| BLAKE2b-256 |
5f6b7c3732c23b1b6e63fdbb42ffe13d7731ac8641b94d627763395295565ea0
|
Provenance
The following attestation bundles were made for os_download-0.1.1-py3-none-any.whl:
Publisher:
release.yml on dragonfoxsl/os-download
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
os_download-0.1.1-py3-none-any.whl -
Subject digest:
2bd433fb0c37bad516fb81b48256006ae4ed9e29bfabd8cb415dafaa785b4ec5 - Sigstore transparency entry: 2164454538
- Sigstore integration time:
-
Permalink:
dragonfoxsl/os-download@5b164bd4c5e493c894e21e4f2c706287820fc040 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/dragonfoxsl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5b164bd4c5e493c894e21e4f2c706287820fc040 -
Trigger Event:
push
-
Statement type: