From 11f3b778ae340cd8f286062e33cab8c5bf353fbf Mon Sep 17 00:00:00 2001 From: gru Date: Sun, 19 Apr 2026 23:19:29 +0200 Subject: [PATCH] Update install_rtorrent.py --- install_rtorrent.py | 136 +++++++++++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 32 deletions(-) diff --git a/install_rtorrent.py b/install_rtorrent.py index 085995b..f15b12a 100644 --- a/install_rtorrent.py +++ b/install_rtorrent.py @@ -2,6 +2,7 @@ import argparse import os import pwd +import re import shutil import subprocess import sys @@ -14,6 +15,7 @@ DEFAULT_HOME = "/home/rtorrent" DEFAULT_BASE_DIR = "/opt/rtorrent_build" DEFAULT_LIBTORRENT_REF = "master" DEFAULT_RTORRENT_REF = "master" +DEFAULT_XMLRPC_REF = "latest-stable" DEFAULT_SERVICE_PATH = "/etc/systemd/system/rtorrent@.service" DEFAULT_SCGI_PORT = 5000 DEFAULT_TORRENT_PORT = 51300 @@ -92,23 +94,22 @@ def prompt_yes_no(question, default=True, assume_yes=False): print("Please answer yes or no.") -def package_exists(name): - return run(["apt-cache", "show", name], check=False) == 0 +def parse_version(version): + parts = [int(x) for x in re.findall(r"\d+", version)] + return tuple(parts[:3]) if parts else (0,) -def resolve_xmlrpc_packages(): - candidates = [ - "libxmlrpc-c++9-dev", - "libxmlrpc-c++8-dev", - ] - chosen = None - for package in candidates: - if package_exists(package): - chosen = package - break - if not chosen: - raise InstallError("No supported xmlrpc-c++ development package found in APT repositories.") - return [chosen, "libxmlrpc-core-c3-dev"] +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): @@ -159,6 +160,41 @@ def clone_or_update_repo(repo_url, repo_dir, ref): run(["git", "pull", "--ff-only"], cwd=str(repo_dir), check=False) +def download_file(url, destination): + run(["curl", "-fL", url, "-o", str(destination)]) + + +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) + ensure_dir(build_root) + if source_root.exists(): + shutil.rmtree(source_root) + source_root.mkdir(parents=True, exist_ok=True) + + if xmlrpc_ref == "latest-stable": + url = "https://sourceforge.net/projects/xmlrpc-c/files/latest/download" + elif re.match(r"^\d+\.\d+\.\d+$", xmlrpc_ref): + url = ( + "https://downloads.sourceforge.net/project/xmlrpc-c/Xmlrpc-c%20Super%20Stable/" + f"{xmlrpc_ref}/xmlrpc-c-{xmlrpc_ref}.tgz" + ) + else: + url = xmlrpc_ref + + print(f"Downloading xmlrpc-c from: {url}") + download_file(url, tarball) + run(["tar", "-xzf", str(tarball), "-C", str(source_root), "--strip-components=1"]) + 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)) + return install_dir + + def build_libtorrent(base_dir, libtorrent_ref): source_dir = Path(base_dir) / "libtorrent" install_dir = Path(base_dir) / "libtorrent_install" @@ -171,17 +207,26 @@ def build_libtorrent(base_dir, libtorrent_ref): return install_dir -def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install): +def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install): source_dir = Path(base_dir) / "rtorrent" install_dir = Path(base_dir) / "rtorrent_install" clone_or_update_repo("https://github.com/rakshasa/rtorrent.git", source_dir, rtorrent_ref) env = os.environ.copy() - env["CFLAGS"] = f"-I{libtorrent_install}/include" - env["LDFLAGS"] = f"-L{libtorrent_install}/lib" + include_flags = f"-I{libtorrent_install}/include -I{xmlrpc_install}/include" + ld_flags = f"-L{libtorrent_install}/lib -L{xmlrpc_install}/lib" + existing_cflags = env.get("CFLAGS", "") + existing_cppflags = env.get("CPPFLAGS", "") + existing_ldflags = env.get("LDFLAGS", "") + env["CFLAGS"] = include_flags + (f" {existing_cflags}" if existing_cflags else "") + env["CPPFLAGS"] = include_flags + (f" {existing_cppflags}" if existing_cppflags else "") + env["LDFLAGS"] = ld_flags + (f" {existing_ldflags}" if existing_ldflags else "") existing_pkg = env.get("PKG_CONFIG_PATH", "") - env["PKG_CONFIG_PATH"] = f"{libtorrent_install}/lib/pkgconfig" + (f":{existing_pkg}" if existing_pkg else "") + 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") run(["autoreconf", "-i"], cwd=str(source_dir), env=env) run([ @@ -194,7 +239,7 @@ def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install): return install_dir -def install_symlinks(rtorrent_install, libtorrent_install): +def install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install): rtorrent_bin = Path(rtorrent_install) / "bin" / "rtorrent" if not rtorrent_bin.exists(): raise InstallError(f"Compiled rtorrent binary not found: {rtorrent_bin}") @@ -205,12 +250,12 @@ def install_symlinks(rtorrent_install, libtorrent_install): usr_local_bin.symlink_to(rtorrent_bin) print(f"Symlinked {usr_local_bin} -> {rtorrent_bin}") - ld_conf = Path("/etc/ld.so.conf.d/rtorrent-libtorrent.conf") - ld_conf.write_text(f"{libtorrent_install}/lib\n") + ld_conf = Path("/etc/ld.so.conf.d/rtorrent-custom-libs.conf") + ld_conf.write_text(f"{libtorrent_install}/lib\n{xmlrpc_install}/lib\n") run(["ldconfig"]) -def write_service(service_path, binary_path, libtorrent_lib_dir): +def write_service(service_path, binary_path, runtime_lib_dirs): service_content = f"""[Unit] Description=rTorrent for %I After=network.target @@ -226,7 +271,7 @@ ExecStart={binary_path} -o system.daemon.set=true -n -o import=/home/%I/.rtorren ExecStop=/bin/kill -9 $MAINPID Restart=always RestartSec=3 -Environment=LD_LIBRARY_PATH={libtorrent_lib_dir} +Environment=LD_LIBRARY_PATH={runtime_lib_dirs} [Install] WantedBy=multi-user.target @@ -248,6 +293,7 @@ execute.nothrow = chmod,777,/home/{username}/downloads network.scgi.open_port = 127.0.0.1:{DEFAULT_SCGI_PORT} network.port_range.set = {DEFAULT_TORRENT_PORT}-{DEFAULT_TORRENT_PORT} network.port_random.set = no +network.bind_address.set = 0.0.0.0 system.file.allocate.set = 1 dht.mode.set = disable protocol.pex.set = no @@ -278,7 +324,7 @@ def enable_service(user): print(f"Enabled and started {unit_name}") -def verify_install(rtorrent_install, libtorrent_install): +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}") @@ -291,14 +337,35 @@ def verify_install(rtorrent_install, libtorrent_install): if not any(expected in line for line in matches): raise InstallError("rtorrent does not appear to be linked against the compiled libtorrent 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"}" + probe = subprocess.run( + [str(rtorrent_bin), "-h"], + env=env, + check=False, + text=True, + 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(): + 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." + ) + def build_parser(): parser = argparse.ArgumentParser( - description="Interactive Debian installer for libtorrent + rTorrent under /opt with optional systemd setup." + description="Interactive Debian installer for xmlrpc-c + libtorrent + rTorrent under /opt with optional systemd setup." ) parser.add_argument("--base-dir", default=DEFAULT_BASE_DIR, help=f"Base build/install directory (default: {DEFAULT_BASE_DIR})") parser.add_argument("--libtorrent-ref", default=DEFAULT_LIBTORRENT_REF, help="Git branch, tag or commit for libtorrent (default: master)") parser.add_argument("--rtorrent-ref", default=DEFAULT_RTORRENT_REF, help="Git branch, tag or commit for rtorrent (default: master)") + parser.add_argument("--xmlrpc-ref", default=DEFAULT_XMLRPC_REF, help="xmlrpc-c source version or URL (default: latest-stable)") parser.add_argument("--user", default=DEFAULT_USER, help=f"System user for the service (default: {DEFAULT_USER})") parser.add_argument("--group", default=DEFAULT_GROUP, help=f"System group for the service (default: {DEFAULT_GROUP})") parser.add_argument("--home", default=DEFAULT_HOME, help=f"Home directory for the service user (default: {DEFAULT_HOME})") @@ -326,12 +393,13 @@ def main(): "libcurl4-openssl-dev", "libncurses5-dev", "libncursesw5-dev", + "libexpat1-dev", "curl", + "tar", ] - packages.extend(resolve_xmlrpc_packages()) - print("This script will:") + print(f" - build xmlrpc-c from '{args.xmlrpc_ref}'") print(f" - build libtorrent from '{args.libtorrent_ref}'") print(f" - build rtorrent from '{args.rtorrent_ref}'") print(f" - install everything under '{args.base_dir}'") @@ -347,22 +415,26 @@ def main(): ensure_packages(packages) 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) - install_symlinks(rtorrent_install, libtorrent_install) - verify_install(rtorrent_install, libtorrent_install) + rtorrent_install = build_rtorrent(args.base_dir, args.rtorrent_ref, libtorrent_install, xmlrpc_install) + install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install) + verify_install(rtorrent_install, libtorrent_install, xmlrpc_install) if not args.only_build: 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) - write_service(DEFAULT_SERVICE_PATH, "/usr/local/bin/rtorrent", str(libtorrent_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(f"libtorrent path: {libtorrent_install / 'lib'}") + print(f"xmlrpc-c path: {xmlrpc_install / 'lib'}") return 0