linux-iso-seeder/fetch_torrents.py
2025-07-11 16:19:50 +02:00

223 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import os
import re
import json
import requests
import logging
import time
import shutil
from bs4 import BeautifulSoup
from transmission_rpc import Client
# Configure logging
log_file = "/logs/fetch_torrents.log"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s: %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler(log_file)
]
)
watch_dir = "/watch"
def download_torrent(name, url):
dest = os.path.join(watch_dir, f"{name}.torrent")
added = os.path.join(watch_dir, f"{name}.torrent.added")
# Skip if already processed or queued
if os.path.exists(dest):
logging.info("Skip %s torrent already present.", os.path.basename(dest))
return False
if os.path.exists(added):
logging.info("Skip %s torrent already present.", os.path.basename(added))
return False
try:
logging.info(f"Fetching {url} ...")
r = requests.get(url, timeout=30)
r.raise_for_status()
with open(dest, "wb") as f:
f.write(r.content)
logging.info(f"Saved {dest}")
return True
except Exception as e:
logging.error(f"Failed to download {url}: {e}")
return False
def fetch_ubuntu_lts():
url = "https://releases.ubuntu.com/"
try:
text = requests.get("https://changelogs.ubuntu.com/meta-release-lts", timeout=30).text
blocks = [b for b in text.strip().split("\n\n") if "Supported: 1" in b]
results = {}
# newest first (optional remove reversed() if order is irrelevant)
for block in reversed(blocks):
version = re.search(r"Version:\s*([\d.]+)", block).group(1)
codename = re.search(r"Dist:\s*(\w+)", block).group(1)
results[f"ubuntu-{version}-desktop"] = download_torrent(f"ubuntu-{version}-desktop", f"https://releases.ubuntu.com/{codename}/ubuntu-{version}-desktop-amd64.iso.torrent")
results[f"ubuntu-{version}-live-server"] = download_torrent(f"ubuntu-{version}-live-server", f"https://releases.ubuntu.com/{codename}/ubuntu-{version}-live-server-amd64.iso.torrent")
results[f"lbuntu-{version}-desktop"] = download_torrent(f"lbuntu-{version}-desktop", f"https://cdimage.ubuntu.com/lubuntu/releases/{codename}/release/lubuntu-{version}-desktop-amd64.iso.torrent")
results[f"xbuntu-{version}-desktop"] = download_torrent(f"xbuntu-{version}-desktop", f"https://torrent.ubuntu.com/xubuntu/releases/{codename}/release/desktop/xubuntu-{version}-desktop-amd64.iso.torrent")
results[f"xbuntu-{version}-minimal"] = download_torrent(f"xbuntu-{version}-minimal", f"https://torrent.ubuntu.com/xubuntu/releases/{codename}/release/minimal/xubuntu-{version}-minimal-amd64.iso.torrent")
return results
except Exception as e:
logging.error(f"Ubuntu fetch error: {e}")
return False
def fetch_debian_stable():
urls = [
"https://cdimage.debian.org/debian-cd/current/amd64/bt-dvd/",
"https://cdimage.debian.org/debian-cd/current/arm64/bt-dvd/",
"https://cdimage.debian.org/debian-cd/current/amd64/bt-cd/",
"https://cdimage.debian.org/debian-cd/current/arm64/bt-cd/"
]
results = {}
for url in urls:
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
results[url] = False
for link in soup.find_all('a', href=True):
href = link['href']
if ".iso.torrent" in href:
torrent_url = url + href
name = href.replace(".iso.torrent", "")
results[name] = download_torrent(name, torrent_url)
break
else:
logging.warning("No Debian DVD-1 torrent found.")
except Exception as e:
logging.error(f"Debian fetch error: {e}")
results[url] = False
return results
def fetch_kali_latest():
url = "https://www.kali.org/get-kali/#kali-installer-images"
try:
html = requests.get(url, timeout=30).text
matches = re.findall(r"kali-linux-(\d+\.\d+)-installer-", html)
if not matches:
logging.warning("Could not detect a Kali release number on %s", url)
return False
ver = max(matches, key=lambda v: tuple(map(int, v.split(".")))) # nyeste
base_cd = f"https://cdimage.kali.org/kali-{ver}/kali-linux-{ver}-installer"
base_arm = f"https://kali.download/arm-images/kali-{ver}/kali-linux-{ver}"
base_cloud = f"https://kali.download/cloud-images/kali-{ver}/kali-linux-{ver}-cloud-genericcloud"
torrents = [
f"{base_cd}-amd64.iso.torrent",
f"{base_cd}-netinst-amd64.iso.torrent",
f"{base_cd}-everything-amd64.iso.torrent",
f"{base_cd}-arm64.iso.torrent",
f"{base_cd}-netinst-arm64.iso.torrent",
f"{base_cd}-purple-amd64.iso.torrent",
f"{base_arm}-raspberry-pi-armhf.img.xz.torrent",
f"{base_arm}-raspberry-pi-zero-2-w-armhf.img.xz.torrent",
f"{base_arm}-raspberry-pi-zero-w-armel.img.xz.torrent",
f"{base_cloud}-amd64.tar.xz.torrent",
f"{base_cloud}-arm64.tar.xz.torrent",
]
results = {}
for turl in torrents:
name = os.path.basename(turl).replace(".torrent", "")
results[name] = download_torrent(name, turl)
if not any(results.values()):
logging.warning("No Kali torrents could be downloaded.")
return False
return results
except Exception as exc:
logging.error("Kali fetch error: %s", exc)
return False
def log_seed_ratios_via_http(rpc_url="http://localhost:9091/transmission/rpc", auth: tuple | None = None):
r = requests.post(rpc_url)
headers = {"X-Transmission-Session-Id": r.headers["X-Transmission-Session-Id"]}
payload = {
"method": "torrent-get",
"arguments": {"fields": ["name", "uploadRatio"]}
}
r = requests.post(rpc_url, json=payload, headers=headers, auth=auth, timeout=15)
r.raise_for_status()
torrents = r.json()["arguments"]["torrents"]
# sort by uploadRatio, highest first
torrents_sorted = sorted(
torrents,
key=lambda t: t.get("uploadRatio") or 0,
reverse=True,
)
for t in torrents_sorted:
logging.info("[ratio] %s%.3f", t["name"], t["uploadRatio"])
# Example: find all torrents for a distro, keep only the latest
def cleanup_old_versions():
tc = Client(host='localhost', port=9091)
torrents = tc.get_torrents()
# Collect all torrents matching the distro prefix
matched = []
version_re = re.compile(rf"(\d+\.\d+)\.iso", re.IGNORECASE)
for torrent in torrents:
m = version_re.search(torrent.name)
if m:
matched.append((torrent, m.group(1)))
if not matched:
return
# Sort by version number, keep the latest
def version_key(t):
# Convert version like '1.10' to tuple (1, 10)
return tuple(map(int, t[1].split('.')))
matched.sort(key=version_key, reverse=True)
# Keep the first (latest), remove the rest
for torrent, version in matched[1:]:
logging.info(f"Removing old version: {torrent.name}")
tc.remove_torrent(torrent.id, delete_data=True)
if __name__ == "__main__":
start_time = time.time()
logging.info("Starting torrent fetch run.")
success_count = 0
failure_count = 0
for func in [fetch_ubuntu_lts, fetch_debian_stable, fetch_kali_latest]:
if func():
success_count += 1
else:
failure_count += 1
try:
log_seed_ratios_via_http()
except Exception as exc:
logging.error("Could not query Transmission: %s", exc)
try:
cleanup_old_versions()
except Exception as exc:
logging.error("Could not clean up old versions: %s", exc)
total, used, free = shutil.disk_usage("/downloads")
logging.info(f"Downloads folder usage: {used // (2**30)} GB used / {total // (2**30)} GB total")
elapsed = time.time() - start_time
logging.info(f"Run complete in {elapsed:.2f} seconds. {success_count} successful, {failure_count} failed.")