Files
pyTorrent/scripts/stack_installers/configure_pytorrent_api.py
2026-05-19 13:43:37 +00:00

107 lines
4.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""Configure pyTorrent through its HTTP API after rTorrent is installed."""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
import urllib.error
import urllib.request
def _request(base_url: str, method: str, path: str, payload: dict | None = None, token: str | None = None, timeout: int = 10) -> dict:
url = base_url.rstrip("/") + path
data = None if payload is None else json.dumps(payload).encode("utf-8")
headers = {"Accept": "application/json"}
if payload is not None:
headers["Content-Type"] = "application/json"
if token:
headers["Authorization"] = f"Bearer {token}"
req = urllib.request.Request(url, data=data, method=method.upper(), headers=headers)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8", "replace")
return json.loads(raw or "{}")
except urllib.error.HTTPError as exc:
raw = exc.read().decode("utf-8", "replace")
raise RuntimeError(f"API {method} {path} failed with HTTP {exc.code}: {raw}") from exc
except urllib.error.URLError as exc:
raise RuntimeError(f"API {method} {path} failed: {exc.reason}") from exc
def _wait_for_api(base_url: str, token: str | None, seconds: int) -> None:
deadline = time.time() + seconds
last_error = None
while time.time() < deadline:
try:
_request(base_url, "GET", "/api/profiles", token=token, timeout=5)
return
except Exception as exc: # noqa: BLE001 - installation helper should keep retrying.
last_error = exc
time.sleep(2)
raise RuntimeError(f"pyTorrent API is not ready after {seconds}s at {base_url}: {last_error}. Check PYTORRENT_PORT in .env and systemctl status pytorrent.")
def _find_profile(profiles: list[dict], name: str, scgi_url: str) -> dict | None:
for profile in profiles:
if str(profile.get("name") or "") == name:
return profile
for profile in profiles:
if str(profile.get("scgi_url") or "") == scgi_url:
return profile
return None
def main() -> int:
parser = argparse.ArgumentParser(description="Create/update and activate a pyTorrent rTorrent profile through the HTTP API.")
parser.add_argument("--base-url", default=os.getenv("PYTORRENT_BASE_URL", "http://127.0.0.1:8090"))
parser.add_argument("--api-token", default=os.getenv("PYTORRENT_API_TOKEN", ""), help="Bearer token when pyTorrent auth is enabled.")
parser.add_argument("--profile-name", default=os.getenv("PYTORRENT_RTORRENT_PROFILE_NAME", "Local rTorrent"))
parser.add_argument("--scgi-url", default=os.getenv("PYTORRENT_RTORRENT_SCGI_URL", "scgi://127.0.0.1:5000"))
parser.add_argument("--timeout", type=int, default=int(os.getenv("PYTORRENT_RTORRENT_TIMEOUT", "10")))
parser.add_argument("--wait", type=int, default=int(os.getenv("PYTORRENT_API_WAIT_SECONDS", "90")))
parser.add_argument("--remote", action="store_true", default=os.getenv("PYTORRENT_RTORRENT_REMOTE", "0").lower() in {"1", "true", "yes", "on"})
args = parser.parse_args()
token = args.api_token.strip() or None
_wait_for_api(args.base_url, token, args.wait)
current = _request(args.base_url, "GET", "/api/profiles", token=token)
profiles = current.get("profiles") or []
payload = {
"name": args.profile_name,
"scgi_url": args.scgi_url,
"is_default": True,
"timeout_seconds": args.timeout,
"max_parallel_jobs": 5,
"light_parallel_jobs": 4,
"light_job_timeout_seconds": 300,
"heavy_job_timeout_seconds": 7200,
"pending_job_timeout_seconds": 900,
"is_remote": bool(args.remote),
}
existing = _find_profile(profiles, args.profile_name, args.scgi_url)
if existing:
profile_id = int(existing["id"])
result = _request(args.base_url, "PUT", f"/api/profiles/{profile_id}", payload, token=token)
action = "updated"
else:
result = _request(args.base_url, "POST", "/api/profiles", payload, token=token)
profile_id = int((result.get("profile") or {}).get("id") or 0)
action = "created"
if not profile_id:
raise RuntimeError(f"Profile {action}, but API response did not include an id: {result}")
_request(args.base_url, "POST", f"/api/profiles/{profile_id}/activate", token=token)
test = _request(args.base_url, "GET", f"/api/profiles/{profile_id}/diagnostics", token=token)
print(json.dumps({"ok": True, "action": action, "profile_id": profile_id, "diagnostics": test.get("diagnostics")}, indent=2))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc: # noqa: BLE001 - user-facing installer output.
print(f"ERROR: {exc}", file=sys.stderr)
raise SystemExit(1)