From ce0edc2e39d67b42d26f427060a92796d72e68de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Sun, 31 May 2026 08:44:22 +0200 Subject: [PATCH] install scrpts --- scripts/INSTALL.md | 42 +++- scripts/install_pytorrent.sh | 2 + scripts/install_pytorrent_only.sh | 37 +++- scripts/install_stack.sh | 14 +- scripts/stack_installers/INSTALL.md | 42 +++- scripts/stack_installers/install_rtorrent.py | 152 ++++++++----- .../stack_installers/install_rtorrent_rhel.py | 101 ++++++--- .../stack_installers/install_stack_arch.sh | 208 ++++++++++++++++++ .../install_stack_debian_ubuntu.sh | 21 +- .../stack_installers/install_stack_rhel.sh | 21 +- 10 files changed, 526 insertions(+), 114 deletions(-) create mode 100755 scripts/stack_installers/install_stack_arch.sh diff --git a/scripts/INSTALL.md b/scripts/INSTALL.md index a5a914e..56118c7 100644 --- a/scripts/INSTALL.md +++ b/scripts/INSTALL.md @@ -19,6 +19,7 @@ The bootstrap script downloads the current pyTorrent repository, detects the ope - Debian / Ubuntu: `scripts/stack_installers/install_stack_debian_ubuntu.sh` - RHEL-compatible systems: `scripts/stack_installers/install_stack_rhel.sh` +- Arch Linux: `scripts/stack_installers/install_stack_arch.sh` Supported RHEL-compatible systems include RHEL, Rocky Linux, AlmaLinux, CentOS Stream, and Fedora-like systems where `dnf` or `yum` is available. @@ -26,9 +27,9 @@ Supported RHEL-compatible systems include RHEL, Rocky Linux, AlmaLinux, CentOS S Default installation includes: -- rTorrent `v0.16.11` -- libtorrent `v0.16.11` -- minimal rTorrent build without c-ares/custom curl +- Debian/Ubuntu/RHEL: rTorrent `v0.16.11` and libtorrent `v0.16.11` built from source with tinyxml2 XML-RPC +- Arch Linux: current `rtorrent` package from the official repositories through `pacman` +- minimal source build without c-ares/custom curl on Debian/Ubuntu/RHEL - rTorrent system user: `rtorrent` - rTorrent SCGI endpoint: `scgi://127.0.0.1:5000` - rTorrent incoming BitTorrent port: `51300` @@ -83,11 +84,14 @@ These variables are used by both stack installers. | --- | --- | --- | | `RTORRENT_USER` | `rtorrent` | System user used to run rTorrent. | | `RTORRENT_HOME` | `/home/${RTORRENT_USER}` | Home directory for the rTorrent user. | -| `RTORRENT_BASE_DIR` | `/opt/rtorrent_build` | Build and install directory for xmlrpc-c, libtorrent and rTorrent. | +| `RTORRENT_BASE_DIR` | `/opt/rtorrent_build` | Build and install directory for libtorrent and rTorrent. On Arch this is used only when source build is requested. | | `RTORRENT_SCGI_PORT` | `5000` | Local SCGI port for rTorrent XMLRPC/SCGI. | | `RTORRENT_TORRENT_PORT` | `51300` | Incoming BitTorrent listen port. | -| `RTORRENT_REF` | `v0.16.11` | rTorrent Git tag, branch, or commit. | -| `LIBTORRENT_REF` | `v0.16.11` | libtorrent Git tag, branch, or commit. | +| `RTORRENT_REF` | `v0.16.11` | rTorrent Git tag, branch, or commit. Ignored by default on Arch unless source build is requested. | +| `LIBTORRENT_REF` | `v0.16.11` | libtorrent Git tag, branch, or commit. Ignored by default on Arch unless source build is requested. | +| `RTORRENT_WITH_XMLRPC_C` | `0` | Set to `1` to compile rTorrent with classic xmlrpc-c instead of the default tinyxml2 XML-RPC backend. On Arch this also switches from repo package to source build. | +| `RTORRENT_BUILD_FROM_SOURCE` | `0` on Arch, source build on Debian/Ubuntu/RHEL | On Arch, set to `1` or pass `--build-rtorrent` to compile instead of using the `pacman` package. | +| `RTORRENT_FORCE_CONFIG` | `1` | On Arch repo-package install, overwrite generated `.rtorrent.rc`; set to `0` to keep an existing config. | Example: @@ -96,6 +100,13 @@ curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/inst | sudo RTORRENT_USER=rtorrent RTORRENT_SCGI_PORT=5001 RTORRENT_TORRENT_PORT=51400 bash ``` +Classic xmlrpc-c backend instead of default tinyxml2. On Arch this forces source build: + +```bash +curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/install_stack.sh \ + | sudo RTORRENT_WITH_XMLRPC_C=1 bash +``` + ## pyTorrent parameters | Variable | Default | Description | @@ -154,6 +165,18 @@ RHEL-compatible systems: sudo bash scripts/stack_installers/install_stack_rhel.sh ``` +Arch Linux, using the repository rTorrent package by default: + +```bash +sudo bash scripts/stack_installers/install_stack_arch.sh +``` + +Arch Linux, forcing source build: + +```bash +sudo bash scripts/stack_installers/install_stack_arch.sh --build-rtorrent +``` + ## Installed service hints Check services: @@ -173,16 +196,17 @@ journalctl -u rtorrent@rtorrent.service -f ## Notes -- The default rTorrent build is intentionally minimal. +- Debian/Ubuntu/RHEL source builds are intentionally minimal by default. +- Arch Linux uses the current repository `rtorrent` package by default and does not compile rTorrent unless `--build-rtorrent`, `RTORRENT_BUILD_FROM_SOURCE=1`, or `--with-xmlrpc-c` is used. - c-ares and custom curl are not enabled by the stack installer defaults. -- The rTorrent installer overwrites the generated `.rtorrent.rc` because the stack installer passes `--force-config`. +- The rTorrent installer overwrites the generated `.rtorrent.rc` by default. - pyTorrent is configured through the HTTP API after the service starts. - If API authentication is enabled before profile configuration, pass `PYTORRENT_API_TOKEN`. ## Build logs and troubleshooting -The stack installer writes quiet build output to `/var/log/pytorrent-installer` by default. +Source-build installers write quiet build output to `/var/log/pytorrent-installer` by default. Override it with: ```bash diff --git a/scripts/install_pytorrent.sh b/scripts/install_pytorrent.sh index 6c1e652..7e79cfa 100755 --- a/scripts/install_pytorrent.sh +++ b/scripts/install_pytorrent.sh @@ -41,6 +41,8 @@ prepare_downloader() { dnf install -y ca-certificates curl tar gzip python3 sudo elif command_exists yum; then yum install -y ca-certificates curl tar gzip python3 sudo + elif command_exists pacman; then + pacman -Sy --noconfirm --needed ca-certificates curl tar gzip python sudo fi if command_exists curl; then DOWNLOADER="curl"; return; fi if command_exists wget; then DOWNLOADER="wget"; return; fi diff --git a/scripts/install_pytorrent_only.sh b/scripts/install_pytorrent_only.sh index b7994c3..39f5645 100755 --- a/scripts/install_pytorrent_only.sh +++ b/scripts/install_pytorrent_only.sh @@ -30,6 +30,7 @@ RT_PROXY_USER="${RTORRENT_SCGI_PROXY_USER:-rtproxy}" RT_PROXY_LISTEN="${RTORRENT_SCGI_PROXY_LISTEN:-127.0.0.1:5050}" RT_PROXY_TOKEN="${RTORRENT_SCGI_PROXY_TOKEN:-}" RT_PROXY_ALLOW_NET="${RTORRENT_SCGI_PROXY_ALLOW_NET:-127.0.0.1}" +RT_PROXY_TARGET_NETWORK_EXPLICIT="${RTORRENT_SCGI_PROXY_TARGET_NETWORK+x}" RT_PROXY_TARGET_NETWORK="${RTORRENT_SCGI_PROXY_TARGET_NETWORK:-tcp}" RT_PROXY_TARGET_ADDRESS="${RTORRENT_SCGI_PROXY_TARGET_ADDRESS:-127.0.0.1:5000}" RT_PROXY_BINARY_URL="${RTORRENT_SCGI_PROXY_BINARY_URL:-https://git.linuxiarz.pl/gru/rtorrent-scgi-proxy/raw/branch/master/dist/rtorrent-scgi-proxy-linux-amd64}" @@ -157,7 +158,7 @@ parse_args() { --proxy-listen) RT_PROXY_LISTEN="$2"; shift 2 ;; --proxy-token) RT_PROXY_TOKEN="$2"; shift 2 ;; --proxy-allow-net) RT_PROXY_ALLOW_NET="$2"; shift 2 ;; - --proxy-target-network) RT_PROXY_TARGET_NETWORK="$2"; shift 2 ;; + --proxy-target-network) RT_PROXY_TARGET_NETWORK="$2"; RT_PROXY_TARGET_NETWORK_EXPLICIT=1; shift 2 ;; --proxy-target-address) RT_PROXY_TARGET_ADDRESS="$2"; shift 2 ;; --skip-profile) SKIP_PROFILE=1; shift ;; -h|--help) usage; exit 0 ;; @@ -174,6 +175,7 @@ detect_os_family() { case "${ID:-} ${ID_LIKE:-}" in *debian*|*ubuntu*) echo "debian" ;; *rhel*|*fedora*|*centos*|*rocky*|*almalinux*) echo "rhel" ;; + *arch*) echo "arch" ;; *) fail "Unsupported OS: ID=${ID:-unknown}, ID_LIKE=${ID_LIKE:-unknown}." ;; esac } @@ -193,6 +195,10 @@ install_prerequisites() { [[ -n "${manager}" ]] || fail "dnf or yum is required." "${manager}" install -y ca-certificates curl git rsync sudo python3 python3-devel python3-pip gcc pkgconf-pkg-config ;; + arch) + command -v pacman >/dev/null 2>&1 || fail "pacman is required on Arch Linux." + pacman -Sy --noconfirm --needed ca-certificates curl git rsync sudo python python-pip gcc pkgconf + ;; esac } @@ -213,7 +219,22 @@ ask_configuration() { INSTALL_SCGI_PROXY="$(normalize_yes_no "${INSTALL_SCGI_PROXY}")" fi if [[ "${INSTALL_SCGI_PROXY}" == "yes" ]]; then - prompt RT_PROXY_LISTEN "SCGI proxy listen address" "127.0.0.1:5050" + if [[ -n "${RTORRENT_SOCKET}" ]]; then + RT_PROXY_TARGET_NETWORK="unix" + RT_PROXY_TARGET_ADDRESS="${RTORRENT_SOCKET}" + elif [[ -z "${RT_PROXY_TARGET_NETWORK_EXPLICIT}" ]]; then + RT_PROXY_TARGET_NETWORK="unix" + fi + prompt RT_PROXY_TARGET_NETWORK "rTorrent SCGI backend: tcp or unix" "${RT_PROXY_TARGET_NETWORK}" + if [[ "${RT_PROXY_TARGET_NETWORK}" == "unix" ]]; then + prompt RT_PROXY_TARGET_ADDRESS "rTorrent Unix socket path" "${RTORRENT_SOCKET:-/run/rtorrent/rtorrent.sock}" + elif [[ "${RT_PROXY_TARGET_NETWORK}" == "tcp" ]]; then + prompt RT_PROXY_TARGET_ADDRESS "rTorrent SCGI TCP address" "${RT_PROXY_TARGET_ADDRESS}" + else + fail "Invalid SCGI proxy backend network: ${RT_PROXY_TARGET_NETWORK}" + fi + prompt RT_PROXY_ALLOW_NET "SCGI proxy allowed client network/IP/CIDR" "127.0.0.1" + prompt RT_PROXY_LISTEN "SCGI proxy TCP listen address for pyTorrent" "127.0.0.1:5050" if [[ -z "${RT_PROXY_TOKEN}" ]]; then RT_PROXY_TOKEN="$(python3 - <<'PY' import secrets @@ -221,16 +242,10 @@ print(secrets.token_urlsafe(32)) PY )" fi - prompt RT_PROXY_ALLOW_NET "SCGI proxy allowed client network/IP/CIDR" "127.0.0.1" - if [[ -n "${RTORRENT_SOCKET}" ]]; then - RT_PROXY_TARGET_NETWORK="unix" - RT_PROXY_TARGET_ADDRESS="${RTORRENT_SOCKET}" - fi - prompt RT_PROXY_TARGET_NETWORK "SCGI proxy backend network: tcp or unix" "${RT_PROXY_TARGET_NETWORK}" - prompt RT_PROXY_TARGET_ADDRESS "SCGI proxy backend address" "${RT_PROXY_TARGET_ADDRESS}" SCGI_URL="scgi://${RT_PROXY_LISTEN}/proxy/${RT_PROXY_TOKEN}" + else + prompt SCGI_URL "rTorrent SCGI URL for pyTorrent profile" "${SCGI_URL}" fi - prompt SCGI_URL "rTorrent SCGI URL for pyTorrent profile" "${SCGI_URL}" if [[ "${AUTH_MODE}" == "ask" ]]; then prompt AUTH_MODE "Enable pyTorrent authentication? yes/no" "no" @@ -271,6 +286,7 @@ ensure_app_user() { if ! id -u "${APP_USER}" >/dev/null 2>&1; then local shell_path="/usr/sbin/nologin" [[ -x "${shell_path}" ]] || shell_path="/sbin/nologin" + [[ -x "${shell_path}" ]] || shell_path="/usr/bin/nologin" useradd --system --create-home --home-dir "/var/lib/${APP_USER}" --shell "${shell_path}" "${APP_USER}" fi } @@ -521,6 +537,7 @@ install_scgi_proxy() { if ! id -u "${RT_PROXY_USER}" >/dev/null 2>&1; then local shell_path="/usr/sbin/nologin" [[ -x "${shell_path}" ]] || shell_path="/sbin/nologin" + [[ -x "${shell_path}" ]] || shell_path="/usr/bin/nologin" useradd --system --no-create-home --shell "${shell_path}" "${RT_PROXY_USER}" fi curl -fL "${RT_PROXY_BINARY_URL}" -o /usr/local/bin/rtorrent-scgi-proxy diff --git a/scripts/install_stack.sh b/scripts/install_stack.sh index 54c649e..a17787d 100755 --- a/scripts/install_stack.sh +++ b/scripts/install_stack.sh @@ -72,6 +72,11 @@ prepare_downloader() { DOWNLOADER="curl" return fi + if command_exists pacman; then + pacman -Sy --noconfirm --needed curl ca-certificates tar gzip python sudo + DOWNLOADER="curl" + return + fi fail "curl or wget is required and no supported package manager was found." } @@ -103,6 +108,9 @@ detect_os_family() { *rhel*|*fedora*|*centos*|*rocky*|*almalinux*) echo "rhel" ;; + *arch*) + echo "arch" + ;; *) fail "Unsupported OS: ID=${ID:-unknown}, ID_LIKE=${ID_LIKE:-unknown}." ;; @@ -129,6 +137,7 @@ if ! download_file "${ARCHIVE_URL}" "${ARCHIVE_PATH}"; then for file in \ install_stack_debian_ubuntu.sh \ install_stack_rhel.sh \ + install_stack_arch.sh \ install_pytorrent_rhel.sh \ install_rtorrent.py \ install_rtorrent_rhel.py \ @@ -153,6 +162,9 @@ case "${OS_FAMILY}" in rhel) INSTALLER="${PROJECT_DIR}/scripts/stack_installers/install_stack_rhel.sh" ;; + arch) + INSTALLER="${PROJECT_DIR}/scripts/stack_installers/install_stack_arch.sh" + ;; *) fail "Unsupported OS family: ${OS_FAMILY}." ;; @@ -160,4 +172,4 @@ esac chmod +x "${PROJECT_DIR}/scripts/stack_installers/"*.sh || true log "Running ${INSTALLER}" -bash "${INSTALLER}" +bash "${INSTALLER}" "$@" diff --git a/scripts/stack_installers/INSTALL.md b/scripts/stack_installers/INSTALL.md index d4ba1c0..5226da6 100644 --- a/scripts/stack_installers/INSTALL.md +++ b/scripts/stack_installers/INSTALL.md @@ -19,6 +19,7 @@ The bootstrap script downloads the current pyTorrent repository, detects the ope - Debian / Ubuntu: `scripts/stack_installers/install_stack_debian_ubuntu.sh` - RHEL-compatible systems: `scripts/stack_installers/install_stack_rhel.sh` +- Arch Linux: `scripts/stack_installers/install_stack_arch.sh` Supported RHEL-compatible systems include RHEL, Rocky Linux, AlmaLinux, CentOS Stream, and Fedora-like systems where `dnf` or `yum` is available. @@ -26,9 +27,9 @@ Supported RHEL-compatible systems include RHEL, Rocky Linux, AlmaLinux, CentOS S Default installation includes: -- rTorrent `v0.16.11` -- libtorrent `v0.16.11` -- minimal rTorrent build without c-ares/custom curl +- Debian/Ubuntu/RHEL: rTorrent `v0.16.11` and libtorrent `v0.16.11` built from source with tinyxml2 XML-RPC +- Arch Linux: current `rtorrent` package from the official repositories through `pacman` +- minimal source build without c-ares/custom curl on Debian/Ubuntu/RHEL - rTorrent system user: `rtorrent` - rTorrent SCGI endpoint: `scgi://127.0.0.1:5000` - rTorrent incoming BitTorrent port: `51300` @@ -83,11 +84,14 @@ These variables are used by both stack installers. | --- | --- | --- | | `RTORRENT_USER` | `rtorrent` | System user used to run rTorrent. | | `RTORRENT_HOME` | `/home/${RTORRENT_USER}` | Home directory for the rTorrent user. | -| `RTORRENT_BASE_DIR` | `/opt/rtorrent_build` | Build and install directory for xmlrpc-c, libtorrent and rTorrent. | +| `RTORRENT_BASE_DIR` | `/opt/rtorrent_build` | Build and install directory for libtorrent and rTorrent. On Arch this is used only when source build is requested. | | `RTORRENT_SCGI_PORT` | `5000` | Local SCGI port for rTorrent XMLRPC/SCGI. | | `RTORRENT_TORRENT_PORT` | `51300` | Incoming BitTorrent listen port. | -| `RTORRENT_REF` | `v0.16.11` | rTorrent Git tag, branch, or commit. | -| `LIBTORRENT_REF` | `v0.16.11` | libtorrent Git tag, branch, or commit. | +| `RTORRENT_REF` | `v0.16.11` | rTorrent Git tag, branch, or commit. Ignored by default on Arch unless source build is requested. | +| `LIBTORRENT_REF` | `v0.16.11` | libtorrent Git tag, branch, or commit. Ignored by default on Arch unless source build is requested. | +| `RTORRENT_WITH_XMLRPC_C` | `0` | Set to `1` to compile rTorrent with classic xmlrpc-c instead of the default tinyxml2 XML-RPC backend. On Arch this also switches from repo package to source build. | +| `RTORRENT_BUILD_FROM_SOURCE` | `0` on Arch, source build on Debian/Ubuntu/RHEL | On Arch, set to `1` or pass `--build-rtorrent` to compile instead of using the `pacman` package. | +| `RTORRENT_FORCE_CONFIG` | `1` | On Arch repo-package install, overwrite generated `.rtorrent.rc`; set to `0` to keep an existing config. | Example: @@ -96,6 +100,13 @@ curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/inst | sudo RTORRENT_USER=rtorrent RTORRENT_SCGI_PORT=5001 RTORRENT_TORRENT_PORT=51400 bash ``` +Classic xmlrpc-c backend instead of default tinyxml2. On Arch this forces source build: + +```bash +curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/install_stack.sh \ + | sudo RTORRENT_WITH_XMLRPC_C=1 bash +``` + ## pyTorrent parameters | Variable | Default | Description | @@ -154,6 +165,18 @@ RHEL-compatible systems: sudo bash scripts/stack_installers/install_stack_rhel.sh ``` +Arch Linux, using the repository rTorrent package by default: + +```bash +sudo bash scripts/stack_installers/install_stack_arch.sh +``` + +Arch Linux, forcing source build: + +```bash +sudo bash scripts/stack_installers/install_stack_arch.sh --build-rtorrent +``` + ## Installed service hints Check services: @@ -173,16 +196,17 @@ journalctl -u rtorrent@rtorrent.service -f ## Notes -- The default rTorrent build is intentionally minimal. +- Debian/Ubuntu/RHEL source builds are intentionally minimal by default. +- Arch Linux uses the current repository `rtorrent` package by default and does not compile rTorrent unless `--build-rtorrent`, `RTORRENT_BUILD_FROM_SOURCE=1`, or `--with-xmlrpc-c` is used. - c-ares and custom curl are not enabled by the stack installer defaults. -- The rTorrent installer overwrites the generated `.rtorrent.rc` because the stack installer passes `--force-config`. +- The rTorrent installer overwrites the generated `.rtorrent.rc` by default. - pyTorrent is configured through the HTTP API after the service starts. - If API authentication is enabled before profile configuration, pass `PYTORRENT_API_TOKEN`. ## Build logs and troubleshooting -The stack installer writes quiet build output to `/var/log/pytorrent-installer` by default. +Source-build installers write quiet build output to `/var/log/pytorrent-installer` by default. Override it with: ```bash diff --git a/scripts/stack_installers/install_rtorrent.py b/scripts/stack_installers/install_rtorrent.py index a88ad47..3b7b5d4 100755 --- a/scripts/stack_installers/install_rtorrent.py +++ b/scripts/stack_installers/install_rtorrent.py @@ -18,6 +18,7 @@ DEFAULT_BASE_DIR = "/opt/rtorrent_build" DEFAULT_LIBTORRENT_REF = "v0.16.11" DEFAULT_RTORRENT_REF = "v0.16.11" DEFAULT_XMLRPC_REF = "latest-stable" +DEFAULT_RPC_BACKEND = "tinyxml2" DEFAULT_CARES_REF = "1.34.6" DEFAULT_CURL_REF = "8.19.0" DEFAULT_SERVICE_PATH = "/etc/systemd/system/rtorrent@.service" @@ -137,18 +138,21 @@ def is_ubuntu_2604(): return data.get("ID", "").lower() == "ubuntu" and data.get("VERSION_ID", "") == "26.04" -def detect_debian(): +def detect_os_family(): data = read_os_release() - distro_id = data.get("ID", "").lower() distro_like = data.get("ID_LIKE", "").lower() - if distro_id != "debian" and "debian" not in distro_like: + if distro_id == "debian" or "debian" in distro_like or distro_id == "ubuntu": + family = "debian" + elif distro_id == "arch" or "arch" in distro_like: + family = "arch" + else: raise InstallError( f"Unsupported distribution: ID={data.get('ID', 'unknown')}, " - f"ID_LIKE={data.get('ID_LIKE', 'unknown')}. This installer currently supports Debian only." + f"ID_LIKE={data.get('ID_LIKE', 'unknown')}. This installer supports Debian/Ubuntu and Arch Linux." ) - - print(f"Detected Debian-compatible system: {data.get('PRETTY_NAME', distro_id)}") + print(f"Detected {family}-compatible system: {data.get('PRETTY_NAME', distro_id)}") + return family def prompt_yes_no(question, default=True, assume_yes=False): @@ -173,7 +177,11 @@ def parse_version(version): return tuple(parts[:3]) if parts else (0,) -def ensure_packages(packages, *, debug=False): +def ensure_packages(packages, *, family="debian", debug=False): + if family == "arch": + print("Installing build and runtime dependencies with pacman...") + run(["pacman", "-Sy", "--noconfirm", "--needed", *packages], debug=debug, log_name="pacman_install_rtorrent_deps") + return print("Updating APT metadata...") run(["apt-get", "update"], debug=debug) print("Installing build and runtime dependencies...") @@ -196,12 +204,13 @@ def create_system_user(user, group, home, assume_yes=False, debug=False): if not prompt_yes_no(f"Create system user '{user}' with home '{home}'?", default=True, assume_yes=assume_yes): raise InstallError("User creation declined.") run(["groupadd", "--system", group], check=False, debug=debug) + shell_path = next((p for p in ["/usr/sbin/nologin", "/sbin/nologin", "/usr/bin/nologin"] if Path(p).exists()), "/usr/bin/false") run([ "useradd", "--system", "--home-dir", home, "--create-home", - "--shell", "/usr/sbin/nologin", + "--shell", shell_path, "--gid", group, user, ], debug=debug) @@ -457,34 +466,40 @@ def build_libtorrent(base_dir, libtorrent_ref, curl_install=None, cares_install= return install_dir, version -def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install, curl_install=None, cares_install=None, *, debug=False): +def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, rpc_backend, xmlrpc_install=None, curl_install=None, cares_install=None, *, debug=False): 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, debug=debug) - 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}.") - 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}") + prefixes = [libtorrent_install] + xmlrpc_config = None + if rpc_backend == "xmlrpc-c": + 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}.") + 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, debug=debug) + prefixes.append(xmlrpc_install) + elif rpc_backend != "tinyxml2": + raise InstallError(f"Unsupported RPC backend: {rpc_backend}") - verify_xmlrpc_environment(xmlrpc_config, debug=debug) - - prefixes = [libtorrent_install, xmlrpc_install] if curl_install: prefixes.append(curl_install) if cares_install: prefixes.append(cares_install) env = build_env(*prefixes) - env["PATH"] = f"{xmlrpc_config.parent}:" + env.get("PATH", "") - env["XMLRPC_C_CONFIG"] = str(xmlrpc_config) + if xmlrpc_config: + env["PATH"] = f"{xmlrpc_config.parent}:" + env.get("PATH", "") + env["XMLRPC_C_CONFIG"] = str(xmlrpc_config) with Spinner("Preparing rTorrent build system", enabled=not debug): run(["autoreconf", "-i"], cwd=str(source_dir), env=env, debug=debug) run(["make", "distclean"], cwd=str(source_dir), env=env, check=False, debug=debug) - configure_cmd = ["./configure", f"--prefix={install_dir}", "--with-xmlrpc-c"] + rpc_flag = "--with-xmlrpc-c" if rpc_backend == "xmlrpc-c" else "--with-xmlrpc-tinyxml2" + configure_cmd = ["./configure", f"--prefix={install_dir}", rpc_flag] with Spinner("Configuring rTorrent", enabled=not debug): run(configure_cmd, cwd=str(source_dir), env=env, debug=debug) with Spinner("Building rTorrent", enabled=not debug): @@ -492,7 +507,9 @@ def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install, c with Spinner("Installing rTorrent", enabled=not debug): run(["make", "install"], cwd=str(source_dir), env=env, debug=debug, log_name=f"make_install_{Path(source_dir).name}") - runtime_prefixes = [libtorrent_install, xmlrpc_install] + runtime_prefixes = [libtorrent_install] + if rpc_backend == "xmlrpc-c" and xmlrpc_install: + runtime_prefixes.append(xmlrpc_install) if curl_install: runtime_prefixes.append(curl_install) if cares_install: @@ -503,7 +520,7 @@ def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install, c return install_dir, version -def install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install, curl_install=None, cares_install=None, *, debug=False): +def install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install=None, curl_install=None, cares_install=None, *, debug=False): rtorrent_bin = Path(rtorrent_install) / "bin" / "rtorrent" if not rtorrent_bin.exists(): raise InstallError(f"Compiled rtorrent binary not found: {rtorrent_bin}") @@ -514,7 +531,9 @@ def install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install, curl_ usr_local_bin.symlink_to(rtorrent_bin) print(f"Symlinked {usr_local_bin} -> {rtorrent_bin}") - lib_dirs = [f"{libtorrent_install}/lib", f"{xmlrpc_install}/lib"] + lib_dirs = [f"{libtorrent_install}/lib"] + if xmlrpc_install: + lib_dirs.append(f"{xmlrpc_install}/lib") if curl_install: lib_dirs.append(f"{curl_install}/lib") if cares_install: @@ -661,7 +680,7 @@ 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.") + print(" - minimal build: builds only libtorrent and rTorrent; it uses the system libraries already available on Debian/RHEL.") def resolve_optional_build_mode(args): @@ -739,26 +758,36 @@ def verify_libtorrent_curl_integration(base_dir, libtorrent_install, curl_instal print("c-ares is not visible in libtorrent ldd; this can still be valid when libcurl is resolved differently.") -def verify_install(base_dir, rtorrent_install, libtorrent_install, xmlrpc_install, curl_install=None, cares_install=None, *, debug=False): +def verify_install(base_dir, rtorrent_install, libtorrent_install, rpc_backend, xmlrpc_install=None, curl_install=None, cares_install=None, *, debug=False): rtorrent_bin = Path(rtorrent_install) / "bin" / "rtorrent" which_rtorrent = capture(["which", "rtorrent"], check=False, debug=debug) or "not found in PATH" print(f"Resolved rtorrent from PATH: {which_rtorrent}") linked = capture(["ldd", str(rtorrent_bin)], check=True, debug=debug) - for libname, expected in [("libtorrent", str(Path(libtorrent_install) / "lib")), ("xmlrpc", str(Path(xmlrpc_install) / "lib"))]: + checks = [("libtorrent", str(Path(libtorrent_install) / "lib"))] + if rpc_backend == "xmlrpc-c": + checks.append(("xmlrpc", str(Path(xmlrpc_install) / "lib"))) + for libname, expected in checks: lines = [line for line in linked.splitlines() if libname in line] print_link_lines(f"Linked {libname} lines:", lines) if not any(expected in line for line in lines): raise InstallError(f"rtorrent does not appear to be linked against the compiled {libname} from {expected}.") + if rpc_backend == "tinyxml2": + tinyxml_lines = [line for line in linked.splitlines() if "tinyxml2" in line.lower()] + print_link_lines("Linked tinyxml2 lines:", tinyxml_lines) + if not tinyxml_lines: + raise InstallError("rTorrent does not appear to be linked against tinyxml2.") if curl_install: verify_libtorrent_curl_integration(base_dir, libtorrent_install, curl_install, cares_install, debug=debug) - env = build_env(libtorrent_install, xmlrpc_install, curl_install, cares_install) + env = build_env(libtorrent_install, xmlrpc_install if rpc_backend == "xmlrpc-c" else None, curl_install, cares_install) env["LANG"] = "C" env["LC_ALL"] = "C" env["TERM"] = env.get("TERM", "xterm") - ld_paths = [str(Path(libtorrent_install) / "lib"), str(Path(xmlrpc_install) / "lib")] + ld_paths = [str(Path(libtorrent_install) / "lib")] + if rpc_backend == "xmlrpc-c" and xmlrpc_install: + ld_paths.append(str(Path(xmlrpc_install) / "lib")) if curl_install: ld_paths.append(str(Path(curl_install) / "lib")) if cares_install: @@ -775,11 +804,12 @@ def verify_install(base_dir, rtorrent_install, libtorrent_install, xmlrpc_instal def build_parser(): - parser = argparse.ArgumentParser(description="Debian installer for xmlrpc-c + libtorrent + rTorrent under /opt with optional c-ares/custom curl support.") + parser = argparse.ArgumentParser(description="Installer for libtorrent + rTorrent under /opt. RPC defaults to tinyxml2; xmlrpc-c is optional.") 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=f"Git branch, tag or commit for libtorrent (default: {DEFAULT_LIBTORRENT_REF})") parser.add_argument("--rtorrent-ref", default=DEFAULT_RTORRENT_REF, help=f"Git branch, tag or commit for rtorrent (default: {DEFAULT_RTORRENT_REF})") - parser.add_argument("--xmlrpc-ref", default=DEFAULT_XMLRPC_REF, help="xmlrpc-c source version or URL (default: latest-stable)") + parser.add_argument("--xmlrpc-ref", default=DEFAULT_XMLRPC_REF, help="xmlrpc-c source version or URL. Used only with --with-xmlrpc-c (default: latest-stable)") + parser.add_argument("--with-xmlrpc-c", action="store_true", help="Build rTorrent with classic xmlrpc-c instead of the default tinyxml2 XML-RPC backend.") parser.add_argument("--cares-ref", default=DEFAULT_CARES_REF, help=f"c-ares release version (default: {DEFAULT_CARES_REF})") parser.add_argument("--curl-ref", default=DEFAULT_CURL_REF, help=f"curl release version (default: {DEFAULT_CURL_REF})") parser.add_argument("--user", default=DEFAULT_USER, help=f"System user for the service (default: {DEFAULT_USER})") @@ -791,7 +821,7 @@ def build_parser(): 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; 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("--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("--minimal", "--core-only", action="store_true", help="Build only 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.") @@ -805,19 +835,34 @@ def main(): args.use_cares = resolve_optional_build_mode(args) require_root() - detect_debian() + os_family = detect_os_family() - packages = [ - "build-essential", "pkg-config", "libtool", "autoconf", "automake", "git", "ca-certificates", - "libssl-dev", "libncurses-dev", "libncurses5-dev", "libncursesw5-dev", "libexpat1-dev", - "libcurl4-openssl-dev", "libxml2-dev", "libreadline-dev", "curl", "tar", "gzip", "xz-utils", - "zlib1g-dev", "bison", "flex", "m4", "gettext", "texinfo", "patch", "diffutils", "file", "procps" - ] - if args.use_cares: - packages.extend(["cmake", "libpsl-dev", "libbrotli-dev", "libzstd-dev"]) + if os_family == "arch": + packages = [ + "base-devel", "pkgconf", "libtool", "autoconf", "automake", "git", "ca-certificates", + "openssl", "ncurses", "expat", "curl", "libxml2", "tinyxml2", "readline", "tar", "gzip", "xz", + "zlib", "bison", "flex", "m4", "gettext", "texinfo", "patch", "diffutils", "file", "procps-ng" + ] + if args.use_cares: + packages.extend(["cmake", "libpsl", "brotli", "zstd"]) + else: + packages = [ + "build-essential", "pkg-config", "libtool", "autoconf", "automake", "git", "ca-certificates", + "libssl-dev", "libncurses-dev", "libncurses5-dev", "libncursesw5-dev", "libexpat1-dev", + "libcurl4-openssl-dev", "libxml2-dev", "libtinyxml2-dev", "libreadline-dev", "curl", "tar", "gzip", "xz-utils", + "zlib1g-dev", "bison", "flex", "m4", "gettext", "texinfo", "patch", "diffutils", "file", "procps" + ] + 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}'") + args.rpc_backend = "xmlrpc-c" if args.with_xmlrpc_c else DEFAULT_RPC_BACKEND + + print(f" - use rTorrent RPC backend: {args.rpc_backend}") + if args.rpc_backend == "xmlrpc-c": + print(f" - build xmlrpc-c from '{args.xmlrpc_ref}'") + else: + print(" - use system tinyxml2 for XML-RPC") print(f" - build libtorrent from '{args.libtorrent_ref}'") print(f" - build rtorrent from '{args.rtorrent_ref}'") if args.use_cares: @@ -826,7 +871,7 @@ def main(): print(" - benefit: async DNS via c-ares and newer curl for HTTP/HTTPS tracker requests") else: print(" - minimal build: skip c-ares/custom curl") - print(" - build only xmlrpc-c, libtorrent and rTorrent; use Debian system libraries") + print(" - build only 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") @@ -838,10 +883,13 @@ def main(): print("Aborted by user.") return 1 - ensure_packages(packages, debug=args.debug) + ensure_packages(packages, family=os_family, debug=args.debug) ensure_dir(args.base_dir) - xmlrpc_install, xmlrpc_version = build_xmlrpc_c(args.base_dir, args.xmlrpc_ref, debug=args.debug) + xmlrpc_install = None + xmlrpc_version = None + if args.rpc_backend == "xmlrpc-c": + xmlrpc_install, xmlrpc_version = build_xmlrpc_c(args.base_dir, args.xmlrpc_ref, debug=args.debug) cares_install = None cares_version = None @@ -856,12 +904,12 @@ def main(): args.base_dir, args.libtorrent_ref, curl_install=curl_install, cares_install=cares_install, debug=args.debug ) rtorrent_install, rtorrent_version = build_rtorrent( - args.base_dir, args.rtorrent_ref, libtorrent_install, xmlrpc_install, curl_install=curl_install, - cares_install=cares_install, debug=args.debug + args.base_dir, args.rtorrent_ref, libtorrent_install, args.rpc_backend, xmlrpc_install=xmlrpc_install, + curl_install=curl_install, cares_install=cares_install, debug=args.debug ) - install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install, curl_install=curl_install, cares_install=cares_install, debug=args.debug) - verify_install(args.base_dir, rtorrent_install, libtorrent_install, xmlrpc_install, curl_install=curl_install, cares_install=cares_install, debug=args.debug) + install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install=xmlrpc_install, curl_install=curl_install, cares_install=cares_install, debug=args.debug) + verify_install(args.base_dir, rtorrent_install, libtorrent_install, args.rpc_backend, xmlrpc_install=xmlrpc_install, curl_install=curl_install, cares_install=cares_install, debug=args.debug) if not args.only_build: create_system_user(args.user, args.group, args.home, assume_yes=args.yes, debug=args.debug) @@ -869,7 +917,9 @@ def main(): 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"] + runtime_lib_dirs = [f"{libtorrent_install}/lib"] + if args.rpc_backend == "xmlrpc-c" and xmlrpc_install: + runtime_lib_dirs.append(f"{xmlrpc_install}/lib") if curl_install: runtime_lib_dirs.append(f"{curl_install}/lib") if cares_install: @@ -880,7 +930,9 @@ def main(): print("\nBuild summary") print("-------------") - print(f"xmlrpc-c: {xmlrpc_version}") + print(f"rpc: {args.rpc_backend}") + if args.rpc_backend == "xmlrpc-c": + print(f"xmlrpc-c: {xmlrpc_version}") print(f"libtorrent: {libtorrent_version}") print(f"rtorrent: {rtorrent_version.splitlines()[0] if rtorrent_version else args.rtorrent_ref}") if args.use_cares: diff --git a/scripts/stack_installers/install_rtorrent_rhel.py b/scripts/stack_installers/install_rtorrent_rhel.py index db10208..70ef726 100755 --- a/scripts/stack_installers/install_rtorrent_rhel.py +++ b/scripts/stack_installers/install_rtorrent_rhel.py @@ -18,6 +18,7 @@ DEFAULT_BASE_DIR = "/opt/rtorrent_build" DEFAULT_LIBTORRENT_REF = "v0.16.11" DEFAULT_RTORRENT_REF = "v0.16.11" DEFAULT_XMLRPC_REF = "latest-stable" +DEFAULT_RPC_BACKEND = "tinyxml2" DEFAULT_CARES_REF = "1.34.6" DEFAULT_CURL_REF = "8.19.0" DEFAULT_SERVICE_PATH = "/etc/systemd/system/rtorrent@.service" @@ -458,34 +459,40 @@ def build_libtorrent(base_dir, libtorrent_ref, curl_install=None, cares_install= return install_dir, version -def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install, curl_install=None, cares_install=None, *, debug=False): +def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, rpc_backend, xmlrpc_install=None, curl_install=None, cares_install=None, *, debug=False): 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, debug=debug) - 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}.") - 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}") + prefixes = [libtorrent_install] + xmlrpc_config = None + if rpc_backend == "xmlrpc-c": + 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}.") + 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, debug=debug) + prefixes.append(xmlrpc_install) + elif rpc_backend != "tinyxml2": + raise InstallError(f"Unsupported RPC backend: {rpc_backend}") - verify_xmlrpc_environment(xmlrpc_config, debug=debug) - - prefixes = [libtorrent_install, xmlrpc_install] if curl_install: prefixes.append(curl_install) if cares_install: prefixes.append(cares_install) env = build_env(*prefixes) - env["PATH"] = f"{xmlrpc_config.parent}:" + env.get("PATH", "") - env["XMLRPC_C_CONFIG"] = str(xmlrpc_config) + if xmlrpc_config: + env["PATH"] = f"{xmlrpc_config.parent}:" + env.get("PATH", "") + env["XMLRPC_C_CONFIG"] = str(xmlrpc_config) with Spinner("Preparing rTorrent build system", enabled=not debug): run(["autoreconf", "-i"], cwd=str(source_dir), env=env, debug=debug) run(["make", "distclean"], cwd=str(source_dir), env=env, check=False, debug=debug) - configure_cmd = ["./configure", f"--prefix={install_dir}", "--with-xmlrpc-c"] + rpc_flag = "--with-xmlrpc-c" if rpc_backend == "xmlrpc-c" else "--with-xmlrpc-tinyxml2" + configure_cmd = ["./configure", f"--prefix={install_dir}", rpc_flag] with Spinner("Configuring rTorrent", enabled=not debug): run(configure_cmd, cwd=str(source_dir), env=env, debug=debug) with Spinner("Building rTorrent", enabled=not debug): @@ -493,7 +500,9 @@ def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install, c with Spinner("Installing rTorrent", enabled=not debug): run(["make", "install"], cwd=str(source_dir), env=env, debug=debug, log_name=f"make_install_{Path(source_dir).name}") - runtime_prefixes = [libtorrent_install, xmlrpc_install] + runtime_prefixes = [libtorrent_install] + if rpc_backend == "xmlrpc-c" and xmlrpc_install: + runtime_prefixes.append(xmlrpc_install) if curl_install: runtime_prefixes.append(curl_install) if cares_install: @@ -504,7 +513,7 @@ def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install, xmlrpc_install, c return install_dir, version -def install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install, curl_install=None, cares_install=None, *, debug=False): +def install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install=None, curl_install=None, cares_install=None, *, debug=False): rtorrent_bin = Path(rtorrent_install) / "bin" / "rtorrent" if not rtorrent_bin.exists(): raise InstallError(f"Compiled rtorrent binary not found: {rtorrent_bin}") @@ -515,7 +524,9 @@ def install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install, curl_ usr_local_bin.symlink_to(rtorrent_bin) print(f"Symlinked {usr_local_bin} -> {rtorrent_bin}") - lib_dirs = [f"{libtorrent_install}/lib", f"{xmlrpc_install}/lib"] + lib_dirs = [f"{libtorrent_install}/lib"] + if xmlrpc_install: + lib_dirs.append(f"{xmlrpc_install}/lib") if curl_install: lib_dirs.append(f"{curl_install}/lib") if cares_install: @@ -663,7 +674,7 @@ 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.") + print(" - minimal build: builds only libtorrent and rTorrent; it uses the system libraries already available on Debian/RHEL.") def resolve_optional_build_mode(args): @@ -741,26 +752,36 @@ def verify_libtorrent_curl_integration(base_dir, libtorrent_install, curl_instal print("c-ares is not visible in libtorrent ldd; this can still be valid when libcurl is resolved differently.") -def verify_install(base_dir, rtorrent_install, libtorrent_install, xmlrpc_install, curl_install=None, cares_install=None, *, debug=False): +def verify_install(base_dir, rtorrent_install, libtorrent_install, rpc_backend, xmlrpc_install=None, curl_install=None, cares_install=None, *, debug=False): rtorrent_bin = Path(rtorrent_install) / "bin" / "rtorrent" which_rtorrent = capture(["which", "rtorrent"], check=False, debug=debug) or "not found in PATH" print(f"Resolved rtorrent from PATH: {which_rtorrent}") linked = capture(["ldd", str(rtorrent_bin)], check=True, debug=debug) - for libname, expected in [("libtorrent", str(Path(libtorrent_install) / "lib")), ("xmlrpc", str(Path(xmlrpc_install) / "lib"))]: + checks = [("libtorrent", str(Path(libtorrent_install) / "lib"))] + if rpc_backend == "xmlrpc-c": + checks.append(("xmlrpc", str(Path(xmlrpc_install) / "lib"))) + for libname, expected in checks: lines = [line for line in linked.splitlines() if libname in line] print_link_lines(f"Linked {libname} lines:", lines) if not any(expected in line for line in lines): raise InstallError(f"rtorrent does not appear to be linked against the compiled {libname} from {expected}.") + if rpc_backend == "tinyxml2": + tinyxml_lines = [line for line in linked.splitlines() if "tinyxml2" in line.lower()] + print_link_lines("Linked tinyxml2 lines:", tinyxml_lines) + if not tinyxml_lines: + raise InstallError("rTorrent does not appear to be linked against tinyxml2.") if curl_install: verify_libtorrent_curl_integration(base_dir, libtorrent_install, curl_install, cares_install, debug=debug) - env = build_env(libtorrent_install, xmlrpc_install, curl_install, cares_install) + env = build_env(libtorrent_install, xmlrpc_install if rpc_backend == "xmlrpc-c" else None, curl_install, cares_install) env["LANG"] = "C" env["LC_ALL"] = "C" env["TERM"] = env.get("TERM", "xterm") - ld_paths = [str(Path(libtorrent_install) / "lib"), str(Path(xmlrpc_install) / "lib")] + ld_paths = [str(Path(libtorrent_install) / "lib")] + if rpc_backend == "xmlrpc-c" and xmlrpc_install: + ld_paths.append(str(Path(xmlrpc_install) / "lib")) if curl_install: ld_paths.append(str(Path(curl_install) / "lib")) if cares_install: @@ -777,11 +798,12 @@ def verify_install(base_dir, rtorrent_install, libtorrent_install, xmlrpc_instal def build_parser(): - parser = argparse.ArgumentParser(description="RHEL-compatible installer for xmlrpc-c + libtorrent + rTorrent under /opt with optional c-ares/custom curl support.") + parser = argparse.ArgumentParser(description="RHEL-compatible installer for libtorrent + rTorrent under /opt. RPC defaults to tinyxml2; xmlrpc-c is optional.") 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=f"Git branch, tag or commit for libtorrent (default: {DEFAULT_LIBTORRENT_REF})") parser.add_argument("--rtorrent-ref", default=DEFAULT_RTORRENT_REF, help=f"Git branch, tag or commit for rtorrent (default: {DEFAULT_RTORRENT_REF})") - parser.add_argument("--xmlrpc-ref", default=DEFAULT_XMLRPC_REF, help="xmlrpc-c source version or URL (default: latest-stable)") + parser.add_argument("--xmlrpc-ref", default=DEFAULT_XMLRPC_REF, help="xmlrpc-c source version or URL. Used only with --with-xmlrpc-c (default: latest-stable)") + parser.add_argument("--with-xmlrpc-c", action="store_true", help="Build rTorrent with classic xmlrpc-c instead of the default tinyxml2 XML-RPC backend.") parser.add_argument("--cares-ref", default=DEFAULT_CARES_REF, help=f"c-ares release version (default: {DEFAULT_CARES_REF})") parser.add_argument("--curl-ref", default=DEFAULT_CURL_REF, help=f"curl release version (default: {DEFAULT_CURL_REF})") parser.add_argument("--user", default=DEFAULT_USER, help=f"System user for the service (default: {DEFAULT_USER})") @@ -793,7 +815,7 @@ def build_parser(): 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; 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("--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("--minimal", "--core-only", action="store_true", help="Build only 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.") @@ -811,14 +833,20 @@ def main(): packages = [ "gcc", "gcc-c++", "make", "pkgconf-pkg-config", "libtool", "autoconf", "automake", "git", "ca-certificates", - "openssl-devel", "ncurses-devel", "expat-devel", "curl", "libcurl-devel", "tar", "gzip", "zlib-devel", "which", + "openssl-devel", "ncurses-devel", "expat-devel", "tinyxml2-devel", "curl", "libcurl-devel", "tar", "gzip", "zlib-devel", "which", "patch", "diffutils", "findutils", "file", "redhat-rpm-config", "libstdc++-devel" ] if args.use_cares: packages.extend(["cmake", "libpsl-devel", "brotli-devel", "libzstd-devel"]) print("This script will:") - print(f" - build xmlrpc-c from '{args.xmlrpc_ref}'") + args.rpc_backend = "xmlrpc-c" if args.with_xmlrpc_c else DEFAULT_RPC_BACKEND + + print(f" - use rTorrent RPC backend: {args.rpc_backend}") + if args.rpc_backend == "xmlrpc-c": + print(f" - build xmlrpc-c from '{args.xmlrpc_ref}'") + else: + print(" - use system tinyxml2 for XML-RPC") print(f" - build libtorrent from '{args.libtorrent_ref}'") print(f" - build rtorrent from '{args.rtorrent_ref}'") if args.use_cares: @@ -827,7 +855,7 @@ def main(): print(" - benefit: async DNS via c-ares and newer curl for HTTP/HTTPS tracker requests") else: print(" - minimal build: skip c-ares/custom curl") - print(" - build only xmlrpc-c, libtorrent and rTorrent; use RHEL system libraries") + print(" - build only libtorrent and rTorrent; use RHEL system libraries") print(f" - install everything under '{args.base_dir}'") if args.only_build: print(" - skip service user, config and systemd setup") @@ -842,7 +870,10 @@ def main(): ensure_packages(packages, debug=args.debug) ensure_dir(args.base_dir) - xmlrpc_install, xmlrpc_version = build_xmlrpc_c(args.base_dir, args.xmlrpc_ref, debug=args.debug) + xmlrpc_install = None + xmlrpc_version = None + if args.rpc_backend == "xmlrpc-c": + xmlrpc_install, xmlrpc_version = build_xmlrpc_c(args.base_dir, args.xmlrpc_ref, debug=args.debug) cares_install = None cares_version = None @@ -857,12 +888,12 @@ def main(): args.base_dir, args.libtorrent_ref, curl_install=curl_install, cares_install=cares_install, debug=args.debug ) rtorrent_install, rtorrent_version = build_rtorrent( - args.base_dir, args.rtorrent_ref, libtorrent_install, xmlrpc_install, curl_install=curl_install, - cares_install=cares_install, debug=args.debug + args.base_dir, args.rtorrent_ref, libtorrent_install, args.rpc_backend, xmlrpc_install=xmlrpc_install, + curl_install=curl_install, cares_install=cares_install, debug=args.debug ) - install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install, curl_install=curl_install, cares_install=cares_install, debug=args.debug) - verify_install(args.base_dir, rtorrent_install, libtorrent_install, xmlrpc_install, curl_install=curl_install, cares_install=cares_install, debug=args.debug) + install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install=xmlrpc_install, curl_install=curl_install, cares_install=cares_install, debug=args.debug) + verify_install(args.base_dir, rtorrent_install, libtorrent_install, args.rpc_backend, xmlrpc_install=xmlrpc_install, curl_install=curl_install, cares_install=cares_install, debug=args.debug) if not args.only_build: create_system_user(args.user, args.group, args.home, assume_yes=args.yes, debug=args.debug) @@ -870,7 +901,9 @@ def main(): 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"] + runtime_lib_dirs = [f"{libtorrent_install}/lib"] + if args.rpc_backend == "xmlrpc-c" and xmlrpc_install: + runtime_lib_dirs.append(f"{xmlrpc_install}/lib") if curl_install: runtime_lib_dirs.append(f"{curl_install}/lib") if cares_install: @@ -881,7 +914,9 @@ def main(): print("\nBuild summary") print("-------------") - print(f"xmlrpc-c: {xmlrpc_version}") + print(f"rpc: {args.rpc_backend}") + if args.rpc_backend == "xmlrpc-c": + print(f"xmlrpc-c: {xmlrpc_version}") print(f"libtorrent: {libtorrent_version}") print(f"rtorrent: {rtorrent_version.splitlines()[0] if rtorrent_version else args.rtorrent_ref}") if args.use_cares: diff --git a/scripts/stack_installers/install_stack_arch.sh b/scripts/stack_installers/install_stack_arch.sh new file mode 100755 index 0000000..8e5dd9e --- /dev/null +++ b/scripts/stack_installers/install_stack_arch.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +set -euo pipefail + +# One-command installer for rTorrent + pyTorrent on Arch Linux. +# Arch uses current repository packages by default. Source build is opt-in. + +if [[ "${EUID}" -ne 0 ]]; then + echo "Run as root: sudo $0" >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +RTORRENT_USER="${RTORRENT_USER:-rtorrent}" +RTORRENT_HOME="${RTORRENT_HOME:-/home/${RTORRENT_USER}}" +RTORRENT_BASE_DIR="${RTORRENT_BASE_DIR:-/opt/rtorrent_build}" +RTORRENT_SCGI_PORT="${RTORRENT_SCGI_PORT:-5000}" +RTORRENT_TORRENT_PORT="${RTORRENT_TORRENT_PORT:-51300}" +RTORRENT_REF="${RTORRENT_REF:-v0.16.11}" +LIBTORRENT_REF="${LIBTORRENT_REF:-v0.16.11}" +PYTORRENT_APP_DIR="${PYTORRENT_APP_DIR:-/opt/pytorrent}" +PYTORRENT_PORT="${PYTORRENT_PORT:-8090}" +PYTORRENT_BASE_URL="${PYTORRENT_BASE_URL:-http://127.0.0.1:${PYTORRENT_PORT}}" +PYTORRENT_PROFILE_NAME="${PYTORRENT_PROFILE_NAME:-Local rTorrent}" +PYTORRENT_API_TOKEN="${PYTORRENT_API_TOKEN:-}" +PYTORRENT_SERVICE_NAME="${PYTORRENT_SERVICE_NAME:-pytorrent}" +PYTORRENT_RTORRENT_SCGI_URL="${PYTORRENT_RTORRENT_SCGI_URL:-scgi://127.0.0.1:${RTORRENT_SCGI_PORT}}" +RTORRENT_BUILD_FROM_SOURCE="${RTORRENT_BUILD_FROM_SOURCE:-0}" +RTORRENT_FORCE_CONFIG="${RTORRENT_FORCE_CONFIG:-1}" + +RTORRENT_EXTRA_ARGS=() +while [[ $# -gt 0 ]]; do + case "$1" in + --build-rtorrent|--build-from-source|--compile-rtorrent) + RTORRENT_BUILD_FROM_SOURCE=1 + shift + ;; + --with-xmlrpc-c) + RTORRENT_BUILD_FROM_SOURCE=1 + RTORRENT_EXTRA_ARGS+=(--with-xmlrpc-c) + shift + ;; + *) + echo "Unknown option: $1" >&2 + echo "Supported options: --build-rtorrent, --with-xmlrpc-c" >&2 + exit 1 + ;; + esac +done +if [[ "${RTORRENT_WITH_XMLRPC_C:-0}" == "1" ]]; then + RTORRENT_BUILD_FROM_SOURCE=1 + RTORRENT_EXTRA_ARGS+=(--with-xmlrpc-c) +fi + +command -v pacman >/dev/null 2>&1 || { echo "pacman is required on Arch Linux." >&2; exit 1; } +pacman -Sy --noconfirm --needed ca-certificates curl tar gzip sudo python python-pip git rsync rtorrent + +ensure_rtorrent_user() { + if ! getent group "${RTORRENT_USER}" >/dev/null; then + groupadd --system "${RTORRENT_USER}" + fi + if ! id "${RTORRENT_USER}" >/dev/null 2>&1; then + local shell="/usr/bin/nologin" + [[ -x "${shell}" ]] || shell="/sbin/nologin" + useradd --system --gid "${RTORRENT_USER}" --home-dir "${RTORRENT_HOME}" --create-home --shell "${shell}" "${RTORRENT_USER}" + fi + mkdir -p "${RTORRENT_HOME}/downloads" "${RTORRENT_HOME}/.session" "${RTORRENT_HOME}/watch" + chown -R "${RTORRENT_USER}:${RTORRENT_USER}" "${RTORRENT_HOME}" +} + +write_rtorrent_config() { + local config="${RTORRENT_HOME}/.rtorrent.rc" + if [[ -f "${config}" && "${RTORRENT_FORCE_CONFIG}" != "1" ]]; then + echo "Keeping existing config: ${config}" + return + fi + cat > "${config}" < /etc/systemd/system/rtorrent@.service <&2 + exit 1 + ;; + esac +done +if [[ "${RTORRENT_WITH_XMLRPC_C:-0}" == "1" ]]; then + RTORRENT_EXTRA_ARGS+=(--with-xmlrpc-c) +fi + export PYTORRENT_APP_DIR PYTORRENT_PORT PYTORRENT_SERVICE_NAME PYTORRENT_API_TOKEN install_debian_stack_prerequisites() { @@ -59,6 +76,7 @@ install_debian_stack_prerequisites() { libexpat1-dev \ libcurl4-openssl-dev \ libxml2-dev \ + libtinyxml2-dev \ libreadline-dev \ zlib1g-dev \ bison \ @@ -78,6 +96,7 @@ install_debian_stack_prerequisites RTORRENT_INSTALL_ARGS=( --yes --minimal + "${RTORRENT_EXTRA_ARGS[@]}" ) if [[ "${PYTORRENT_DEBUG_INSTALL:-0}" == "1" ]]; then RTORRENT_INSTALL_ARGS+=(--debug) diff --git a/scripts/stack_installers/install_stack_rhel.sh b/scripts/stack_installers/install_stack_rhel.sh index b945732..08140fc 100755 --- a/scripts/stack_installers/install_stack_rhel.sh +++ b/scripts/stack_installers/install_stack_rhel.sh @@ -3,7 +3,7 @@ set -euo pipefail # One-command installer for rTorrent + pyTorrent on RHEL-compatible systems. # Notes: -# - rTorrent is built as a minimal v0.16.11 install by default. +# - rTorrent is built as a minimal v0.16.11 install with tinyxml2 XML-RPC by default. # - pyTorrent is configured through its HTTP API after the service starts. if [[ "${EUID}" -ne 0 ]]; then @@ -29,6 +29,23 @@ PYTORRENT_API_TOKEN="${PYTORRENT_API_TOKEN:-}" PYTORRENT_SERVICE_NAME="${PYTORRENT_SERVICE_NAME:-pytorrent}" PYTORRENT_RTORRENT_SCGI_URL="${PYTORRENT_RTORRENT_SCGI_URL:-scgi://127.0.0.1:${RTORRENT_SCGI_PORT}}" +RTORRENT_EXTRA_ARGS=() +while [[ $# -gt 0 ]]; do + case "$1" in + --with-xmlrpc-c) + RTORRENT_EXTRA_ARGS+=(--with-xmlrpc-c) + shift + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done +if [[ "${RTORRENT_WITH_XMLRPC_C:-0}" == "1" ]]; then + RTORRENT_EXTRA_ARGS+=(--with-xmlrpc-c) +fi + export PYTORRENT_APP_DIR PYTORRENT_PORT PYTORRENT_SERVICE_NAME PYTORRENT_API_TOKEN install_rhel_stack_prerequisites() { @@ -65,6 +82,7 @@ install_rhel_stack_prerequisites() { ncurses-devel \ openssl-devel \ expat-devel \ + tinyxml2-devel \ zlib-devel \ libcurl-devel \ redhat-rpm-config \ @@ -81,6 +99,7 @@ install_rhel_stack_prerequisites RTORRENT_INSTALL_ARGS=( --yes --minimal + "${RTORRENT_EXTRA_ARGS[@]}" --force-config ) if [[ "${PYTORRENT_DEBUG_INSTALL:-0}" == "1" ]]; then