From 757c1d516efe258caff75f1aa021cd3854f4652e Mon Sep 17 00:00:00 2001 From: gru Date: Sun, 19 Apr 2026 23:43:54 +0200 Subject: [PATCH] Update install_rtorrent.py --- install_rtorrent.py | 129 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 29 deletions(-) diff --git a/install_rtorrent.py b/install_rtorrent.py index f15b12a..30e8022 100644 --- a/install_rtorrent.py +++ b/install_rtorrent.py @@ -70,8 +70,8 @@ def detect_debian(): distro_like = data.get("ID_LIKE", "").lower() if distro_id != "debian" and "debian" not in distro_like: raise InstallError( - f"Unsupported distribution: ID={data.get('ID', 'unknown')}, ID_LIKE={data.get('ID_LIKE', 'unknown')}. " - "This installer currently supports Debian only." + f"Unsupported distribution: ID={data.get('ID', 'unknown')}, " + f"ID_LIKE={data.get('ID_LIKE', 'unknown')}. This installer currently supports Debian only." ) print(f"Detected Debian-compatible system: {data.get('PRETTY_NAME', distro_id)}") @@ -99,19 +99,6 @@ def parse_version(version): return tuple(parts[:3]) if parts else (0,) -def verify_xmlrpc_environment(xmlrpc_config_path): - tool = str(xmlrpc_config_path) - if not Path(tool).exists(): - raise InstallError(f"xmlrpc-c-config was not found: {tool}") - - version = capture([tool, "--version"], check=True) - if parse_version(version) < (1, 11): - raise InstallError( - f"xmlrpc-c version is too old: {version}. Version 1.11 or newer is required." - ) - print(f"Detected xmlrpc-c version: {version}") - - def ensure_packages(packages): print("Updating APT metadata...") run(["apt-get", "update"]) @@ -144,7 +131,6 @@ def create_system_user(user, group, home, assume_yes=False): "--gid", group, user, ]) - print(f"Created user '{user}'.") def clone_or_update_repo(repo_url, repo_dir, ref): @@ -164,13 +150,61 @@ def download_file(url, destination): run(["curl", "-fL", url, "-o", str(destination)]) +def find_xmlrpc_config(base_dir, preferred_install=None): + candidates = [] + + if preferred_install is not None: + preferred = Path(preferred_install) / "bin" / "xmlrpc-c-config" + if preferred.exists(): + candidates.append(preferred.resolve()) + + root = Path(base_dir) + if root.exists(): + for match in root.rglob("xmlrpc-c-config"): + if match.is_file(): + candidates.append(match.resolve()) + + unique = [] + seen = set() + for candidate in candidates: + if candidate not in seen: + seen.add(candidate) + unique.append(candidate) + + if preferred_install is not None: + preferred_prefix = str(Path(preferred_install).resolve()) + for candidate in unique: + if str(candidate).startswith(preferred_prefix): + return candidate + + return unique[0] if unique else None + + +def verify_xmlrpc_environment(xmlrpc_config_path): + tool = Path(xmlrpc_config_path) + if not tool.exists(): + raise InstallError(f"xmlrpc-c-config was not found: {tool}") + + version = capture([str(tool), "--version"], check=True) + if parse_version(version) < (1, 11): + raise InstallError( + f"xmlrpc-c version is too old: {version}. Version 1.11 or newer is required." + ) + print(f"Detected xmlrpc-c version: {version} ({tool})") + + def build_xmlrpc_c(base_dir, xmlrpc_ref): source_root = Path(base_dir) / "xmlrpc-c-src" install_dir = Path(base_dir) / "xmlrpc-c_install" build_root = Path(base_dir) / "_sources" tarball = build_root / "xmlrpc-c.tar.gz" - ensure_dir(source_root) + existing_config = find_xmlrpc_config(base_dir, install_dir) + if existing_config and str(existing_config).startswith(str(install_dir.resolve())): + print(f"Reusing existing xmlrpc-c installation: {existing_config}") + verify_xmlrpc_environment(existing_config) + return install_dir + ensure_dir(build_root) if source_root.exists(): shutil.rmtree(source_root) @@ -192,6 +226,14 @@ def build_xmlrpc_c(base_dir, xmlrpc_ref): run(["./configure", f"--prefix={install_dir}"], cwd=str(source_root)) run(["make", "-j", str(os.cpu_count() or 1)], cwd=str(source_root)) run(["make", "install"], cwd=str(source_root)) + + xmlrpc_config = find_xmlrpc_config(base_dir, install_dir) + if not xmlrpc_config or not str(xmlrpc_config).startswith(str(install_dir.resolve())): + raise InstallError( + f"Custom xmlrpc-c build finished, but xmlrpc-c-config was not found under {install_dir}." + ) + + verify_xmlrpc_environment(xmlrpc_config) return install_dir @@ -213,6 +255,19 @@ def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install): clone_or_update_repo("https://github.com/rakshasa/rtorrent.git", source_dir, rtorrent_ref) + xmlrpc_config = find_xmlrpc_config(base_dir, xmlrpc_install) + if not xmlrpc_config: + raise InstallError( + f"Could not find custom xmlrpc-c-config under {base_dir}. " + "Build xmlrpc-c first or remove the broken installation and retry." + ) + if not str(xmlrpc_config).startswith(str(Path(xmlrpc_install).resolve())): + raise InstallError( + f"Wrong xmlrpc-c-config selected: {xmlrpc_config}. Expected one under: {xmlrpc_install}" + ) + + verify_xmlrpc_environment(xmlrpc_config) + env = os.environ.copy() include_flags = f"-I{libtorrent_install}/include -I{xmlrpc_install}/include" ld_flags = f"-L{libtorrent_install}/lib -L{xmlrpc_install}/lib" @@ -225,10 +280,18 @@ def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install): existing_pkg = env.get("PKG_CONFIG_PATH", "") pkg_paths = [f"{libtorrent_install}/lib/pkgconfig", f"{xmlrpc_install}/lib/pkgconfig"] env["PKG_CONFIG_PATH"] = ":".join(pkg_paths + ([existing_pkg] if existing_pkg else [])) - env["PATH"] = f"{xmlrpc_install}/bin:" + env.get("PATH", "") - env["XMLRPC_C_CONFIG"] = str(Path(xmlrpc_install) / "bin" / "xmlrpc-c-config") + env["PATH"] = f"{xmlrpc_config.parent}:" + env.get("PATH", "") + env["XMLRPC_C_CONFIG"] = str(xmlrpc_config) + + resolved_xmlrpc_config = capture(["sh", "-c", "command -v xmlrpc-c-config"], env=env, check=False) + print(f"Resolved xmlrpc-c-config for build: {resolved_xmlrpc_config or 'not found'}") + if resolved_xmlrpc_config != str(xmlrpc_config): + raise InstallError( + f"Wrong xmlrpc-c-config selected: {resolved_xmlrpc_config or 'not found'}. Expected: {xmlrpc_config}" + ) run(["autoreconf", "-i"], cwd=str(source_dir), env=env) + run(["make", "distclean"], cwd=str(source_dir), env=env, check=False) run([ "./configure", f"--prefix={install_dir}", @@ -328,20 +391,29 @@ def verify_install(rtorrent_install, libtorrent_install, xmlrpc_install): rtorrent_bin = Path(rtorrent_install) / "bin" / "rtorrent" which_rtorrent = capture(["which", "rtorrent"], check=False) or "not found in PATH" print(f"Resolved rtorrent from PATH: {which_rtorrent}") + linked = capture(["ldd", str(rtorrent_bin)], check=True) - matches = [line for line in linked.splitlines() if "libtorrent" in line] + libtorrent_lines = [line for line in linked.splitlines() if "libtorrent" in line] print("Linked libtorrent lines:") - for line in matches: + for line in libtorrent_lines: print(line) - expected = str(Path(libtorrent_install) / "lib") - if not any(expected in line for line in matches): + expected_libtorrent = str(Path(libtorrent_install) / "lib") + if not any(expected_libtorrent in line for line in libtorrent_lines): raise InstallError("rtorrent does not appear to be linked against the compiled libtorrent from /opt.") + xmlrpc_lines = [line for line in linked.splitlines() if "xmlrpc" in line] + print("Linked xmlrpc-c lines:") + for line in xmlrpc_lines: + print(line) + expected_xmlrpc = str(Path(xmlrpc_install) / "lib") + if not any(expected_xmlrpc in line for line in xmlrpc_lines): + raise InstallError("rtorrent does not appear to be linked against the compiled xmlrpc-c from /opt.") + env = os.environ.copy() env["LANG"] = "C" env["LC_ALL"] = "C" env["TERM"] = env.get("TERM", "xterm") - env["LD_LIBRARY_PATH"] = f"{Path(libtorrent_install) / "lib"}:{Path(xmlrpc_install) / "lib"}" + env["LD_LIBRARY_PATH"] = f"{Path(libtorrent_install) / 'lib'}:{Path(xmlrpc_install) / 'lib'}" probe = subprocess.run( [str(rtorrent_bin), "-h"], env=env, @@ -350,8 +422,8 @@ def verify_install(rtorrent_install, libtorrent_install, xmlrpc_install): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) - help_output = (probe.stdout or "") + "\n" + (probe.stderr or "") - if "xmlrpc-c" in help_output and "i8" in help_output.lower(): + help_output = ((probe.stdout or "") + "\n" + (probe.stderr or "")).lower() + if "xmlrpc-c" in help_output and "i8" in help_output: raise InstallError( "rTorrent was built against an xmlrpc-c library without i8 support. " "Make sure the custom xmlrpc-c build is used and that no older local installation shadows it." @@ -416,7 +488,6 @@ def main(): ensure_dir(args.base_dir) xmlrpc_install = build_xmlrpc_c(args.base_dir, args.xmlrpc_ref) - verify_xmlrpc_environment(Path(xmlrpc_install) / "bin" / "xmlrpc-c-config") libtorrent_install = build_libtorrent(args.base_dir, args.libtorrent_ref) rtorrent_install = build_rtorrent(args.base_dir, args.rtorrent_ref, libtorrent_install, xmlrpc_install) install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install) @@ -426,13 +497,13 @@ def main(): create_system_user(args.user, args.group, args.home, assume_yes=args.yes) prepare_user_dirs(args.home, args.user) write_rtorrent_config(args.home, args.user) - runtime_lib_dirs = f"{libtorrent_install / "lib"}:{xmlrpc_install / "lib"}" + runtime_lib_dirs = f"{libtorrent_install / 'lib'}:{xmlrpc_install / 'lib'}" write_service(DEFAULT_SERVICE_PATH, "/usr/local/bin/rtorrent", runtime_lib_dirs) enable_service(args.user) print(f"\nService status hint: systemctl status rtorrent@{args.user}.service") print("\nDone.") - print(f"rtorrent binary: /usr/local/bin/rtorrent") + print("rtorrent binary: /usr/local/bin/rtorrent") print(f"libtorrent path: {libtorrent_install / 'lib'}") print(f"xmlrpc-c path: {xmlrpc_install / 'lib'}") return 0