Justfile for helping me with adding distro torrents to my qBittorrent instance
justfile
edited
1# "This will save me effort in the long run!", I tell myself. I have advanced experience in
2# the economics of effort.
3
4# MIT License
5#
6# Copyright (c) 2026 Jeffrey Serio <hyperreal@moonshadow.dev>
7#
8# Permission is hereby granted, free of charge, to any person obtaining a copy
9# of this software and associated documentation files (the "Software"), to deal
10# in the Software without restriction, including without limitation the rights
11# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12# copies of the Software, and to permit persons to whom the Software is
13# furnished to do so, subject to the following conditions:
14#
15# The above copyright notice and this permission notice shall be included in all
16# copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24# SOFTWARE.
25
26
27host := `secret-tool lookup qbt-url admin`
28password := `secret-tool lookup qbt-password admin`
29
30# Add AlmaLinux torrents
31[group('torrent')]
32qbt-alma:
33 #!/usr/bin/env -S uv run --script
34 # /// script
35 # dependencies = [
36 # "beautifulsoup4",
37 # "qbittorrent-api",
38 # "requests",
39 # ]
40 # ///
41
42 import os
43 import qbittorrentapi
44 import re
45 import requests
46 import subprocess
47 from bs4 import BeautifulSoup
48 from packaging.version import Version
49
50 resp = requests.get("https://almalinux-mirror.dal1.hivelocity.net/", timeout=30)
51 soup = BeautifulSoup(resp.text, "html.parser")
52
53 versions = []
54 for link in soup.find_all("a"):
55 link_text = link.get("href")
56 res = bool(re.search(r'^\d.*\/$', link_text))
57 if res:
58 versions.append(link.get("href").strip("/"))
59 versions.sort(key=Version)
60
61 major_versions = []
62 for v in versions:
63 if v.isdigit():
64 major_versions.append(v)
65
66 latest_versions = []
67 for mv in major_versions:
68 full_version = []
69 for v in versions:
70 if v.startswith(mv):
71 full_version.append(v)
72 full_version.sort(key=Version)
73 latest_versions.append(full_version[-1])
74
75 urls = []
76 archs = ["aarch64", "ppc64le", "s390x", "x86_64"]
77 for arch in archs:
78 for ver in latest_versions:
79 urls.append(f"https://almalinux-mirror.dal1.hivelocity.net/{ver}/isos/{arch}/AlmaLinux-{ver}-{arch}.torrent")
80
81 conn_info = dict(
82 host="{{ host }}",
83 username="admin",
84 password="{{ password }}",
85 )
86
87 with qbittorrentapi.Client(**conn_info) as qbt_client:
88 try:
89 qbt_client.auth_log_in()
90 except qbittorrentapi.LoginFailed:
91 exit("Login failed. Please verify credentials are correct.")
92
93 for torrent in qbt_client.torrents_info(category="distro"):
94 if "Alma" in torrent.name:
95 try:
96 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
97 print(f"Deleted torrent {torrent.name}")
98 except Exception as ex:
99 print(f"Failed to delete torrent {torrent.name}")
100 print(ex)
101
102 for url in urls:
103 try:
104 qbt_client.torrents_add(url, category="distro")
105 print(f"Added torrent {os.path.basename(url)}")
106 except Exception as ex:
107 print(f"Failed to add torrent {os.path.basename(url)}")
108 print(ex)
109
110# Add antiX torrents
111[group('torrent')]
112qbt-antix:
113 #!/usr/bin/env -S uv run --script
114 # /// script
115 # dependencies = [
116 # "beautifulsoup4",
117 # "qbittorrent-api",
118 # "requests",
119 # ]
120 # ///
121
122 import os
123 import qbittorrentapi
124 import requests
125 from bs4 import BeautifulSoup
126
127 base_url = "https://l2.mxrepo.com/torrents"
128 resp = requests.get(base_url, timeout=30)
129 soup = BeautifulSoup(resp.text, "html.parser")
130
131 urls = []
132 for link in soup.find_all("a"):
133 if link.get("href").startswith("antiX"):
134 urls.append(f"{base_url}/{link.get('href')}")
135
136 conn_info = dict(
137 host="{{ host }}",
138 username="admin",
139 password="{{ password }}",
140 )
141
142 with qbittorrentapi.Client(**conn_info) as qbt_client:
143 try:
144 qbt_client.auth_log_in()
145 except qbittorrentapi.LoginFailed:
146 exit("Login failed. Please verify credentials are correct.")
147
148 for torrent in qbt_client.torrents_info(category="distro"):
149 if "antiX" in torrent.name:
150 try:
151 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
152 print(f"Deleted torrent {torrent.name}")
153 except Exception as ex:
154 print(f"Failed to delete torrent {torrent.name}")
155 print(ex)
156
157 for url in urls:
158 try:
159 qbt_client.torrents_add(url, category="distro")
160 print(f"Added torrent {os.path.basename(url)}")
161 except Exception as ex:
162 print(f"Failed to add torrent {os.path.basename(url)}")
163 print(ex)
164
165# Add Debian torrents
166[group('torrent')]
167qbt-debian:
168 #!/usr/bin/env -S uv run --script
169 # /// script
170 # dependencies = [
171 # "beautifulsoup4",
172 # "qbittorrent-api",
173 # "requests",
174 # ]
175 # ///
176
177 import os
178 import qbittorrentapi
179 import requests
180 from bs4 import BeautifulSoup
181
182 base_url = "https://cdimage.debian.org/debian-cd/current"
183 archs = ["amd64", "arm64", "armel", "armhf", "mips64el", "mipsel", "ppc64el", "s390x"]
184 urls = set()
185 arch_urls = []
186 for arch in archs:
187 arch_urls.append(f"https://cdimage.debian.org/debian-cd/current/{arch}/bt-dvd")
188 for aurl in arch_urls:
189 resp = requests.get(aurl, timeout=30)
190 soup = BeautifulSoup(resp.text, "html.parser")
191 for link in soup.find_all("a"):
192 if link.get("href").endswith(".torrent"):
193 urls.add(f"{aurl}/{link.get('href')}")
194
195 conn_info = dict(
196 host="{{ host }}",
197 username="admin",
198 password="{{ password }}",
199 )
200
201 with qbittorrentapi.Client(**conn_info) as qbt_client:
202 try:
203 qbt_client.auth_log_in()
204 except qbittorrentapi.LoginFailed:
205 exit("Login failed. Please verify credentials are correct.")
206
207 for torrent in qbt_client.torrents_info(category="distro"):
208 if "debian" in torrent.name:
209 try:
210 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
211 print(f"Deleted torrent {torrent.name}")
212 except Exception as ex:
213 print(f"Failed to delete torrent {torrent.name}")
214 print(ex)
215
216 for url in urls:
217 try:
218 qbt_client.torrents_add(url, category="distro")
219 print(f"Added torrent {os.path.basename(url)}")
220 except Exception as ex:
221 print(f"Failed to add torrent {os.path.basename(url)}")
222 print(ex)
223
224# Add Devuan torrents
225[group('torrent')]
226qbt-devuan:
227 #!/usr/bin/env -S uv run --script
228 # /// script
229 # dependencies = [
230 # "beautifulsoup4",
231 # "qbittorrent-api",
232 # "requests",
233 # ]
234 # ///
235
236 import os
237 import qbittorrentapi
238 import requests
239 from bs4 import BeautifulSoup
240
241 base_url = "https://files.devuan.org"
242 resp = requests.get(base_url, timeout=30)
243 soup = BeautifulSoup(resp.text, "html.parser")
244 torrent_files = []
245 for link in soup.find_all("a"):
246 if link.get("href").endswith(".torrent"):
247 if link.get("href") != "devuan_jessie.torrent":
248 torrent_files.append(link.get("href"))
249 url = f"{base_url}/{torrent_files[-1]}"
250
251 conn_info = dict(
252 host="{{ host }}",
253 username="admin",
254 password="{{ password }}",
255 )
256
257 with qbittorrentapi.Client(**conn_info) as qbt_client:
258 try:
259 qbt_client.auth_log_in()
260 except qbittorrentapi.LoginFailed:
261 exit("Login failed. Please verify credentials are correct.")
262
263 for torrent in qbt_client.torrents_info(category="distro"):
264 if "devuan" in torrent.name:
265 try:
266 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
267 print(F"Deleted torrent {torrent.name}")
268 except Exception as ex:
269 print(f"Failed to delete torrent {torrent.name}")
270 print(ex)
271
272 try:
273 qbt_client.torrents_add(url, category="distro")
274 print(f"Added torrent {os.path.basename(url)}")
275 except Exception as ex:
276 print(f"Failed to add torrent {os.path.basename(url)}")
277 print(ex)
278
279# Add Fedora torrents
280[group('torrent')]
281qbt-fedora:
282 #!/usr/bin/env -S uv run --script
283 # /// script
284 # dependencies = [
285 # "beautifulsoup4",
286 # "qbittorrent-api",
287 # "requests",
288 # ]
289 # ///
290
291 import os
292 import qbittorrentapi
293 import requests
294 from bs4 import BeautifulSoup
295
296 base_url = "https://torrent.fedoraproject.org/torrents"
297 urls = []
298 resp = requests.get(base_url, timeout=30)
299 soup = BeautifulSoup(resp.text, "html.parser")
300 torrents = []
301 for link in soup.find_all("a"):
302 if link.get("href").endswith(".torrent"):
303 torrents.append(link.get("href"))
304
305 relver = torrents[-1][-10:-8]
306 for link in soup.find_all("a"):
307 if link.get("href").endswith(f"{relver}.torrent"):
308 urls.append(f"{base_url}/{link.get('href')}")
309
310 conn_info = dict(
311 host="{{ host }}",
312 username="admin",
313 password="{{ password }}",
314 )
315
316 with qbittorrentapi.Client(**conn_info) as qbt_client:
317 try:
318 qbt_client.auth_log_in()
319 except qbittorrentapi.LoginFailed:
320 exit("Login failed. Please verify credentials are correct.")
321
322 for torrent in qbt_client.torrents_info(category="distro"):
323 if "Fedora" in torrent.name:
324 try:
325 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
326 print(f"Deleted torrent {torrent.name}")
327 except Exception as ex:
328 print(f"Failed to delete torrent {torrent.name}")
329 print(ex)
330
331 for url in urls:
332 try:
333 qbt_client.torrents_add(url, category="distro")
334 print(f"Added torrent {os.path.basename(url)}")
335 except Exception as ex:
336 print(f"Failed to add torrent {os.path.basename(url)}")
337 print(ex)
338
339# Add FreeBSD torrents
340[group('torrent')]
341qbt-freebsd:
342 #!/usr/bin/env -S uv run --script
343 # /// script
344 # dependencies = [
345 # "beautifulsoup4",
346 # "qbittorrent-api",
347 # "requests",
348 # ]
349 # ///
350
351 import os
352 import qbittorrentapi
353 import requests
354 from bs4 import BeautifulSoup
355
356 base_url = "https://people.freebsd.org/~jmg"
357 rel_url = "https://www.freebsd.org/releases"
358 rel_resp = requests.get(rel_url, timeout=30)
359 soup = BeautifulSoup(rel_resp.text, "html.parser")
360 sect2 = soup.find_all("div", class_="sect2")
361 # Lmao. I ought to get a bronze medal in the string gymnastics olympics
362 # for this monkey business. A banana would be nice, too.
363 versions = []
364 for item in sect2:
365 if "Production Release" in item.get_text():
366 versions.append(item.get_text().split(" ")[2])
367 versions.append(item.get_text().split(" ")[17])
368
369 magnet_urls = []
370 for v in versions:
371 url = f"https://people.freebsd.org/~jmg/FreeBSD-{v}-R-magnet.txt"
372 reqs = requests.get(url, timeout=30)
373 data = reqs.text.split("\n")
374
375 for line in data:
376 if line.startswith("magnet:"):
377 magnet_urls.append(line)
378
379 conn_info = dict(
380 host="{{ host }}",
381 username="admin",
382 password="{{ password }}",
383 )
384
385 with qbittorrentapi.Client(**conn_info) as qbt_client:
386 try:
387 qbt_client.auth_log_in()
388 except qbittorrentapi.LoginFailed:
389 exit("Login failed. Please verify credentials are correct.")
390
391 for torrent in qbt_client.torrents_info(category="distro"):
392 if "FreeBSD" in torrent.name:
393 try:
394 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
395 print(f"Deleted torrent {torrent.name}")
396 except Exception as ex:
397 print(f"Failed to delete torrent {torrent.name}")
398 print(ex)
399
400 for murl in magnet_urls:
401 try:
402 qbt_client.torrents_add(murl, category="distro")
403 print(f"Added torrent {murl}")
404 except Exception as ex:
405 print(f"Failed to add torrent {murl}")
406 print(ex)
407
408# Add Kali Linux torrents
409[group('torrent')]
410qbt-kali:
411 #!/usr/bin/env -S uv run --script
412 # /// script
413 # dependencies = [
414 # "beautifulsoup4",
415 # "qbittorrent-api",
416 # "requests",
417 # ]
418 # ///
419
420 import os
421 import qbittorrentapi
422 import requests
423 from bs4 import BeautifulSoup
424
425 base_url = "https://kali.download/base-images/current"
426 resp = requests.get(base_url, timeout=30)
427 soup = BeautifulSoup(resp.text, "html.parser")
428 urls = []
429 for link in soup.find_all("a"):
430 if link.get("href").endswith(".torrent"):
431 urls.append(f"{base_url}/{link.get('href')}")
432
433 conn_info = dict(
434 host="{{ host }}",
435 username="admin",
436 password="{{ password }}",
437 )
438
439 with qbittorrentapi.Client(**conn_info) as qbt_client:
440 try:
441 qbt_client.auth_log_in()
442 except qbittorrentapi.LoginFailed:
443 exit("Login failed. Please verify credentials are correct.")
444
445 for torrent in qbt_client.torrents_info(category="distro"):
446 if "kali" in torrent.name:
447 try:
448 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
449 print(f"Deleted torrent {torrent.name}")
450 except Exception as ex:
451 print(f"Failed to delete torrent {torrent.name}")
452 print(ex)
453
454 for url in urls:
455 try:
456 qbt_client.torrents_add(url, category="distro")
457 print(f"Added torrent {os.path.basename(url)}")
458 except Exception as ex:
459 print(f"Failed to add torrent {os.path.basename(url)}")
460 print(ex)
461
462# Add KDE Neon torrents
463[group('torrent')]
464qbt-kdeneon:
465 #!/usr/bin/env -S uv run --script
466 # /// script
467 # dependencies = [
468 # "beautifulsoup4",
469 # "qbittorrent-api",
470 # "requests",
471 # ]
472 # ///
473
474 import os
475 import qbittorrentapi
476 import requests
477 from bs4 import BeautifulSoup
478
479 user_base_url = "https://files.kde.org/neon/images/user"
480 resp = requests.get(user_base_url, timeout=30)
481 soup = BeautifulSoup(resp.text, "html.parser")
482 user_version = str()
483 for link in soup.find_all("a"):
484 if link.get("href")[0:8].isdigit():
485 user_version = link.text.strip("/")
486
487 dev_base_url = "https://files.kde.org/neon/images/dev"
488 resp = requests.get(dev_base_url, timeout=30)
489 soup = BeautifulSoup(resp.text, "html.parser")
490 dev_version = str()
491 for link in soup.find_all("a"):
492 if link.get("href")[0:8].isdigit():
493 dev_version = link.text.strip("/")
494
495 testing_base_url = "https://files.kde.org/neon/images/testing"
496 resp = requests.get(testing_base_url, timeout=30)
497 soup = BeautifulSoup(resp.text, "html.parser")
498 testing_version = str()
499 for link in soup.find_all("a"):
500 if link.get("href")[0:8].isdigit():
501 testing_version = link.text.strip("/")
502
503 unstable_base_url = "https://files.kde.org/neon/images/unstable"
504 resp = requests.get(unstable_base_url, timeout=30)
505 soup = BeautifulSoup(resp.text, "html.parser")
506 unstable_version = str()
507 for link in soup.find_all("a"):
508 if link.get("href")[0:8].isdigit():
509 unstable_version = link.text.strip("/")
510
511 urls = [
512 f"https://files.kde.org/neon/images/user/{user_version}/neon-user-{user_version}.iso.torrent",
513 f"https://files.kde.org/neon/images/dev/{dev_version}/neon-dev-{dev_version}.iso.torrent",
514 f"https://files.kde.org/neon/images/testing/{testing_version}/neon-testing-{testing_version}.iso.torrent",
515 f"https://files.kde.org/neon/images/unstable/{unstable_version}/neon-unstable-{unstable_version}.iso.torrent",
516 ]
517
518 conn_info = dict(
519 host="{{ host }}",
520 username="admin",
521 password="{{ password }}",
522 )
523
524 with qbittorrentapi.Client(**conn_info) as qbt_client:
525 try:
526 qbt_client.auth_log_in()
527 except qbittorrentapi.LoginFailed:
528 exit("Login failed. Please verify credentials are correct.")
529
530 for torrent in qbt_client.torrents_info(category="distro"):
531 if "neon" in torrent.name:
532 try:
533 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
534 print(f"Deleted torrent {torrent.name}")
535 except Exception as ex:
536 print(f"Failed to delete torrent {torrent.name}")
537 print(ex)
538
539 for url in urls:
540 try:
541 qbt_client.torrents_add(url, category="distro")
542 print(f"Added torrent {os.path.basename(url)}")
543 except Exception as ex:
544 print(f"Failed to add torrent {os.path.basename(url)}")
545 print(ex)
546
547# Add MX Linux torrents
548[group('torrent')]
549qbt-mx:
550 #!/usr/bin/env -S uv run --script
551 # /// script
552 # dependencies = [
553 # "beautifulsoup4",
554 # "qbittorrent-api",
555 # "requests",
556 # ]
557 # ///
558
559 import os
560 import qbittorrentapi
561 import requests
562 from bs4 import BeautifulSoup
563
564 base_url = "https://l2.mxrepo.com/torrents"
565 resp = requests.get(base_url, timeout=30)
566 soup = BeautifulSoup(resp.text, "html.parser")
567 urls = []
568 for link in soup.find_all("a"):
569 if link.get("href").startswith("MX-"):
570 urls.append(f"{base_url}/{link.get('href')}")
571
572 conn_info = dict(
573 host="{{ host }}",
574 username="admin",
575 password="{{ password }}",
576 )
577
578 with qbittorrentapi.Client(**conn_info) as qbt_client:
579 try:
580 qbt_client.auth_log_in()
581 except qbittorrentapi.LoginFailed:
582 exit("Login failed. Please verify credentials are correct.")
583
584 for torrent in qbt_client.torrents_info(category="distro"):
585 if "MX-" in torrent.name:
586 try:
587 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
588 print(f"Deleted torrent {torrent.name}")
589 except Exception as ex:
590 print(f"Failed to delete torrent {torrent.name}")
591 print(ex)
592
593 for url in urls:
594 try:
595 qbt_client.torrents_add(url, category="distro")
596 print(f"Added torrent {os.path.basename(url)}")
597 except Exception as ex:
598 print(f"Failed to add torrent {os.path.basename(url)}")
599 print(ex)
600
601# Add NetBSD torrents
602[group('torrent')]
603qbt-netbsd:
604 #!/usr/bin/env -S uv run --script
605 # /// script
606 # dependencies = [
607 # "beautifulsoup4",
608 # "qbittorrent-api",
609 # "requests",
610 # ]
611 # ///
612
613 import os
614 import qbittorrentapi
615 import re
616 import requests
617 from bs4 import BeautifulSoup
618 from packaging.version import Version
619
620 base_url = "https://cdn.netbsd.org/pub/NetBSD"
621 resp = requests.get(base_url, timeout=30)
622 soup = BeautifulSoup(resp.text, "html.parser")
623 versions = []
624 for link in soup.find_all("a"):
625 link_text = link.get("href")
626 res = bool(re.search(r'^NetBSD-(\d+\.\d+)\/$', link_text))
627 if res:
628 versions.append(link.get("href").strip("/"))
629 just_versions = []
630 for v in versions:
631 just_versions.append(v[7:])
632 just_versions.sort(key=Version)
633 latest_version_url = f"{base_url}/NetBSD-{just_versions[-1]}/images"
634 urls = []
635 resp = requests.get(latest_version_url, timeout=30)
636 soup = BeautifulSoup(resp.text, "html.parser")
637 for link in soup.find_all("a"):
638 if link.get("href").endswith(".torrent"):
639 urls.append(f"{latest_version_url}/{link.get('href')}")
640
641 conn_info = dict(
642 host="{{ host }}",
643 username="admin",
644 password="{{ password }}",
645 )
646
647 with qbittorrentapi.Client(**conn_info) as qbt_client:
648 try:
649 qbt_client.auth_log_in()
650 except qbittorrentapi.LoginFailed:
651 exit("Login failed. Please verify credentials are correct.")
652
653 for torrent in qbt_client.torrents_info(category="distro"):
654 if "NetBSD" in torrent.name:
655 try:
656 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
657 print(f"Deleted torrent {torrent.name}")
658 except Exception as ex:
659 print(f"Failed to delete torrent {torrent.name}")
660 print(ex)
661
662 for url in urls:
663 try:
664 qbt_client.torrents_add(url, category="distro")
665 print(f"Added torrent {os.path.basename(url)}")
666 except Exception as ex:
667 print(f"Failed to add torrent {os.path.basename(url)}")
668 print(ex)
669
670# Add NixOS torrents
671[group('torrent')]
672qbt-nixos:
673 #!/usr/bin/env -S uv run --script
674 # /// script
675 # dependencies = [
676 # "beautifulsoup4",
677 # "qbittorrent-api",
678 # "requests",
679 # ]
680 # ///
681
682 import json
683 import os
684 import qbittorrentapi
685 import requests
686
687 base_url = "https://api.github.com/repos/AnimMouse/NixOS-ISO-Torrents/releases/latest"
688 resp = requests.get(base_url, timeout=30)
689 json_data = json.loads(resp.text)
690
691 urls = []
692 for item in json_data["assets"]:
693 urls.append(item["browser_download_url"])
694
695 conn_info = dict(
696 host="{{ host }}",
697 username="admin",
698 password="{{ password }}",
699 )
700
701 with qbittorrentapi.Client(**conn_info) as qbt_client:
702 try:
703 qbt_client.auth_log_in()
704 except qbittorrentapi.LoginFailed:
705 exit("Login failed. Please verify credentials are correct.")
706
707 for torrent in qbt_client.torrents_info(category="distro"):
708 if "nixos" in torrent.name:
709 try:
710 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
711 print(f"Deleted torrent {torrent.name}")
712 except Exception as ex:
713 print(f"Failed to delete torrent {torrent.name}")
714 print(ex)
715
716 for url in urls:
717 try:
718 qbt_client.torrents_add(url, category="distros")
719 print(f"Added torrent {os.path.basename(url)}")
720 except Exception as ex:
721 print(f"Failed to add torrent {os.path.basename(url)}")
722 print(ex)
723
724# Add Qubes OS torrents
725[group('torrent')]
726qbt-qubes:
727 #!/usr/bin/env -S uv run --script
728 # /// script
729 # dependencies = [
730 # "beautifulsoup4",
731 # "qbittorrent-api",
732 # "requests",
733 # ]
734 # ///
735
736 import os
737 import qbittorrentapi
738 import requests
739 from bs4 import BeautifulSoup
740
741 base_url = "https://mirrors.edge.kernel.org/qubes/iso"
742 resp = requests.get(base_url, timeout=30)
743 soup = BeautifulSoup(resp.text, "html.parser")
744 torrents = []
745 for link in soup.find_all("a"):
746 if link.get("href").endswith(".torrent"):
747 torrents.append(link.get("href"))
748 url = f"{base_url}/{torrents[-1]}"
749
750 conn_info = dict(
751 host="{{ host }}",
752 username="admin",
753 password="{{ password }}",
754 )
755
756 with qbittorrentapi.Client(**conn_info) as qbt_client:
757 try:
758 qbt_client.auth_log_in()
759 except qbittorrentapi.LoginFailed:
760 exit("Login failed. Please verify credentials are correct.")
761
762 for torrent in qbt_client.torrents_info(category="distro"):
763 if "Qubes" in torrent.name:
764 try:
765 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
766 print(f"Deleted torrent {torrent.name}")
767 except Exception as ex:
768 print(f"Failed to delete torrent {torrent.name}")
769 print(ex)
770
771 try:
772 qbt_client.torrents_add(url, category="distro")
773 print(f"Added torrent {os.path.basename(url)}")
774 except Exception as ex:
775 print(f"Failed to add torrent {os.path.basename(url)}")
776 print(ex)
777
778# Add Rocky Linux torrents
779[group('torrent')]
780qbt-rocky:
781 #!/usr/bin/env -S uv run --script
782 # /// script
783 # dependencies = [
784 # "beautifulsoup4",
785 # "qbittorrent-api",
786 # "requests",
787 # ]
788 # ///
789
790 import os
791 import qbittorrentapi
792 import re
793 import requests
794 from bs4 import BeautifulSoup
795 from packaging.version import Version
796
797 base_url = "https://download.rockylinux.org/pub/rocky"
798 resp = requests.get(base_url, timeout=30)
799 soup = BeautifulSoup(resp.text, "html.parser")
800 versions = []
801 for link in soup.find_all("a"):
802 link_text = link.get("href")
803 res = bool(re.search(r'^\d.*\/$', link_text))
804 if res:
805 versions.append(link.get("href").strip("/"))
806 versions.sort(key=Version)
807 major_versions = []
808 for v in versions:
809 if v.isdigit():
810 major_versions.append(v)
811 latest_versions = []
812 for mv in major_versions:
813 full_version = []
814 for v in versions:
815 if v.startswith(mv):
816 full_version.append(v)
817 full_version.sort(key=Version)
818 latest_versions.append(full_version[-1])
819
820 archs = ["aarch64", "ppc64le", "s390x", "x86_64"]
821 urls = []
822 for arch in archs:
823 for lv in latest_versions:
824 urls.append(f"{base_url}/{lv}/isos/{arch}/Rocky-{lv}-{arch}-dvd.torrent")
825
826 conn_info = dict(
827 host="{{ host }}",
828 username="admin",
829 password="{{ password }}",
830 )
831
832 with qbittorrentapi.Client(**conn_info) as qbt_client:
833 try:
834 qbt_client.auth_log_in()
835 except qbittorrentapi.LoginFailed:
836 exit("Login failed. Please verify credentials are correct.")
837
838 for torrent in qbt_client.torrents_info(category="distro"):
839 if "Rocky" in torrent.name:
840 try:
841 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
842 print(f"Deleted torrent {torrent.name}")
843 except Exception as ex:
844 print(f"Failed to delete torrent {torrent.name}")
845 print(ex)
846
847 for url in urls:
848 try:
849 qbt_client.torrents_add(url, category="distro")
850 print(f"Added torrent {os.path.basename(url)}")
851 except Exception as ex:
852 print(f"Failed to add torrent {os.path.basename(url)}")
853 print(ex)
854
855# Add Tails OS torrents
856[group('torrent')]
857qbt-tails:
858 #!/usr/bin/env -S uv run --script
859 # /// script
860 # dependencies = [
861 # "beautifulsoup4",
862 # "qbittorrent-api",
863 # "requests",
864 # ]
865 # ///
866
867 import os
868 import qbittorrentapi
869 import requests
870 from bs4 import BeautifulSoup
871
872 base_url = "https://tails.net/torrents/files"
873 resp = requests.get(base_url, timeout=30)
874 soup = BeautifulSoup(resp.text, "html.parser")
875 urls = []
876 for link in soup.find_all("a"):
877 if link.get("href").endswith(".torrent"):
878 urls.append(f"{base_url}/{link.get('href')}")
879
880 conn_info = dict(
881 host="{{ host }}",
882 username="admin",
883 password="{{ password }}",
884 )
885
886 with qbittorrentapi.Client(**conn_info) as qbt_client:
887 try:
888 qbt_client.auth_log_in()
889 except qbittorrentapi.LoginFailed:
890 exit("Login failed. Please verify credentials are correct.")
891
892 for torrent in qbt_client.torrents_info(category="distro"):
893 if "tails" in torrent.name:
894 try:
895 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
896 print(f"Deleted torrent {torrent.name}")
897 except Exception as ex:
898 print(f"Failed to delete torrent {torrent.name}")
899 print(ex)
900
901 for url in urls:
902 try:
903 qbt_client.torrents_add(url, category="distro")
904 print(f"Added torrent {os.path.basename(url)}")
905 except Exception as ex:
906 print(f"Failed to add torrent {os.path.basename(url)}")
907 print(ex)
908
909# Destroy all the distro torrents!
910[group('torrent')]
911qbt-nuke:
912 #!/usr/bin/env -S uv run --script
913 # /// script
914 # dependencies = [
915 # "beautifulsoup4",
916 # "qbittorrent-api",
917 # "requests",
918 # ]
919 # ///
920
921 import os
922 import qbittorrentapi
923
924 conn_info = dict(
925 host="{{ host }}",
926 username="admin",
927 password="{{ password }}",
928 )
929
930 with qbittorrentapi.Client(**conn_info) as qbt_client:
931 for torrent in qbt_client.torrents_info(category="distro"):
932 try:
933 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True)
934 print(f"Deleted torrent {torrent.name}")
935 except Exception as ex:
936 print(f"Failed to delete torrent {torrent.name}")
937 print(ex)