diff --git a/install_rtorrent.py b/install_rtorrent.py index 883dbe9..6415989 100644 --- a/install_rtorrent.py +++ b/install_rtorrent.py @@ -506,7 +506,23 @@ WantedBy=multi-user.target run(["systemctl", "daemon-reload"]) -def build_rtorrent_config_content(username, scgi_port, torrent_port): +def extract_version_tuple(text): + if not text: + return None + match = re.search(r"(?:^|[^0-9])(\d+)\.(\d+)\.(\d+)(?:[^0-9]|$)", str(text)) + if not match: + return None + return tuple(int(part) for part in match.groups()) + + +def rtorrent_bind_address_directive(rtorrent_ref, rtorrent_version=None): + version = extract_version_tuple(rtorrent_ref) or extract_version_tuple(rtorrent_version) + if version and version < (0, 16, 0): + return "network.bind_address.set" + return "network.bind_address.ipv4.set" + + +def build_rtorrent_config_content(username, scgi_port, torrent_port, bind_address_directive): return f""" ## https://git.linuxiarz.pl/gru/tools_scripts/_edit/master/install_rtorrent.py # Generated by install_rtorrent.py @@ -518,7 +534,7 @@ encoding.add = UTF-8 network.scgi.open_port = 127.0.0.1:{scgi_port} network.port_range.set = {torrent_port}-{torrent_port} network.port_random.set = no -network.bind_address.ipv4.set = 0.0.0.0 +{bind_address_directive} = 0.0.0.0 system.file.allocate.set = 0 system.umask.set = 0022 @@ -543,9 +559,9 @@ network.http.dns_cache_timeout.set = 0 """.lstrip() -def write_rtorrent_config(user_home, username, scgi_port, torrent_port, *, force_config=False): +def write_rtorrent_config(user_home, username, scgi_port, torrent_port, bind_address_directive, *, force_config=False): config_path = Path(user_home) / ".rtorrent.rc" - config_content = build_rtorrent_config_content(username, scgi_port, torrent_port) + config_content = build_rtorrent_config_content(username, scgi_port, torrent_port, bind_address_directive) if config_path.exists() and not force_config: print(f"Config already exists: {config_path}") @@ -580,6 +596,47 @@ def print_link_lines(title, lines): print(line) +def print_optional_libs_explanation(): + print("Optional libraries:") + print(" - c-ares: asynchronous DNS resolver. It helps avoid blocking DNS lookups and can improve tracker/DHT-heavy workloads when curl is built with AsynchDNS support.") + print(" - curl: HTTP/HTTPS transfer library used by libtorrent for tracker/web requests. Building a fresh curl can provide newer TLS/HTTP fixes and c-ares based async DNS.") + print(" - minimal build: builds only xmlrpc-c, libtorrent and rTorrent; it uses the system libraries already available on Debian.") + + +def resolve_optional_build_mode(args): + requested = [name for name, enabled in [ + ("--minimal", args.minimal), + ("--with-cares", args.with_cares), + ("--with-curl", args.with_curl), + ("--no-cares", args.no_cares), + ("--no-curl", args.no_curl), + ] if enabled] + + if args.minimal and (args.with_cares or args.with_curl): + raise InstallError("Conflicting options: --minimal cannot be used with --with-cares or --with-curl.") + if args.no_curl and args.with_curl: + raise InstallError("Conflicting options: --no-curl cannot be used with --with-curl.") + if args.no_cares and (args.with_cares or args.with_curl): + raise InstallError("Conflicting options: --no-cares cannot be used with --with-cares or --with-curl.") + + if args.minimal or args.no_curl: + return False + if args.with_curl or args.with_cares: + return True + if args.no_cares: + return False + + if args.yes: + return False + + print_optional_libs_explanation() + return prompt_yes_no( + "Build additional c-ares and newest custom curl?", + default=False, + assume_yes=False, + ) + + def verify_libtorrent_curl_integration(base_dir, libtorrent_install, curl_install, cares_install, *, debug=False): libtorrent_so = next((p for p in sorted((Path(libtorrent_install) / "lib").glob("libtorrent.so*")) if p.is_file() and not p.is_symlink()), None) if not libtorrent_so: @@ -671,25 +728,31 @@ def build_parser(): parser.add_argument("--torrent-port", type=int, default=DEFAULT_TORRENT_PORT, help=f"Incoming BitTorrent listen port (default: {DEFAULT_TORRENT_PORT})") parser.add_argument("--force-config", action="store_true", help="Overwrite existing ~/.rtorrent.rc. By default, existing config is left unchanged and the proposed changes are printed.") parser.add_argument("--only-build", action="store_true", help="Only build and install libtorrent/rTorrent under /opt. Skip user, config and systemd.") - parser.add_argument("--yes", action="store_true", help="Assume yes for interactive prompts.") + parser.add_argument("--yes", action="store_true", help="Assume yes for interactive prompts; optional c-ares/curl remain disabled unless --with-curl or --with-cares is used.") parser.add_argument("--debug", action="store_true", help="Show full command output during build steps.") - parser.add_argument("--without-cares", dest="use_cares", action="store_false", help="Skip building c-ares and custom curl.") - parser.set_defaults(use_cares=True) + parser.add_argument("--minimal", "--core-only", action="store_true", help="Build only xmlrpc-c, libtorrent and rTorrent. Do not build c-ares or custom curl.") + parser.add_argument("--no-cares", "--without-cares", dest="no_cares", action="store_true", help="Do not build c-ares. This also disables custom curl integration.") + parser.add_argument("--no-curl", "--without-curl", dest="no_curl", action="store_true", help="Do not build custom curl. Implies no c-ares integration for libtorrent.") + parser.add_argument("--with-cares", action="store_true", help="Build c-ares and custom curl with asynchronous DNS support.") + parser.add_argument("--with-curl", action="store_true", help="Build newest custom curl; c-ares is enabled unless --no-cares is used.") return parser def main(): parser = build_parser() args = parser.parse_args() + args.use_cares = resolve_optional_build_mode(args) require_root() detect_debian() packages = [ "build-essential", "pkg-config", "libtool", "autoconf", "automake", "git", "ca-certificates", - "libssl-dev", "libncurses5-dev", "libncursesw5-dev", "libexpat1-dev", "curl", "tar", "cmake", - "libpsl-dev", "zlib1g-dev", "libbrotli-dev", "libzstd-dev" + "libssl-dev", "libncurses5-dev", "libncursesw5-dev", "libexpat1-dev", "curl", "tar", + "zlib1g-dev" ] + if args.use_cares: + packages.extend(["cmake", "libpsl-dev", "libbrotli-dev", "libzstd-dev"]) print("This script will:") print(f" - build xmlrpc-c from '{args.xmlrpc_ref}'") @@ -698,8 +761,10 @@ def main(): if args.use_cares: print(f" - build c-ares from '{args.cares_ref}'") print(f" - build curl from '{args.curl_ref}' with c-ares") + print(" - benefit: async DNS via c-ares and newer curl for HTTP/HTTPS tracker requests") else: - print(" - skip c-ares/custom curl and use system curl") + print(" - minimal build: skip c-ares/custom curl") + print(" - build only xmlrpc-c, libtorrent and rTorrent; use Debian system libraries") print(f" - install everything under '{args.base_dir}'") if args.only_build: print(" - skip service user, config and systemd setup") @@ -739,7 +804,9 @@ def main(): if not args.only_build: create_system_user(args.user, args.group, args.home, assume_yes=args.yes, debug=args.debug) prepare_user_dirs(args.home, args.user) - write_rtorrent_config(args.home, args.user, args.scgi_port, args.torrent_port, force_config=args.force_config) + bind_address_directive = rtorrent_bind_address_directive(args.rtorrent_ref, rtorrent_version) + print(f"Using rTorrent bind address directive: {bind_address_directive}") + write_rtorrent_config(args.home, args.user, args.scgi_port, args.torrent_port, bind_address_directive, force_config=args.force_config) runtime_lib_dirs = [f"{libtorrent_install}/lib", f"{xmlrpc_install}/lib"] if curl_install: runtime_lib_dirs.append(f"{curl_install}/lib")