Update install_rtorrent.py
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -14,6 +15,7 @@ DEFAULT_HOME = "/home/rtorrent"
|
|||||||
DEFAULT_BASE_DIR = "/opt/rtorrent_build"
|
DEFAULT_BASE_DIR = "/opt/rtorrent_build"
|
||||||
DEFAULT_LIBTORRENT_REF = "master"
|
DEFAULT_LIBTORRENT_REF = "master"
|
||||||
DEFAULT_RTORRENT_REF = "master"
|
DEFAULT_RTORRENT_REF = "master"
|
||||||
|
DEFAULT_XMLRPC_REF = "latest-stable"
|
||||||
DEFAULT_SERVICE_PATH = "/etc/systemd/system/rtorrent@.service"
|
DEFAULT_SERVICE_PATH = "/etc/systemd/system/rtorrent@.service"
|
||||||
DEFAULT_SCGI_PORT = 5000
|
DEFAULT_SCGI_PORT = 5000
|
||||||
DEFAULT_TORRENT_PORT = 51300
|
DEFAULT_TORRENT_PORT = 51300
|
||||||
@@ -92,23 +94,22 @@ def prompt_yes_no(question, default=True, assume_yes=False):
|
|||||||
print("Please answer yes or no.")
|
print("Please answer yes or no.")
|
||||||
|
|
||||||
|
|
||||||
def package_exists(name):
|
def parse_version(version):
|
||||||
return run(["apt-cache", "show", name], check=False) == 0
|
parts = [int(x) for x in re.findall(r"\d+", version)]
|
||||||
|
return tuple(parts[:3]) if parts else (0,)
|
||||||
|
|
||||||
|
|
||||||
def resolve_xmlrpc_packages():
|
def verify_xmlrpc_environment(xmlrpc_config_path):
|
||||||
candidates = [
|
tool = str(xmlrpc_config_path)
|
||||||
"libxmlrpc-c++9-dev",
|
if not Path(tool).exists():
|
||||||
"libxmlrpc-c++8-dev",
|
raise InstallError(f"xmlrpc-c-config was not found: {tool}")
|
||||||
]
|
|
||||||
chosen = None
|
version = capture([tool, "--version"], check=True)
|
||||||
for package in candidates:
|
if parse_version(version) < (1, 11):
|
||||||
if package_exists(package):
|
raise InstallError(
|
||||||
chosen = package
|
f"xmlrpc-c version is too old: {version}. Version 1.11 or newer is required."
|
||||||
break
|
)
|
||||||
if not chosen:
|
print(f"Detected xmlrpc-c version: {version}")
|
||||||
raise InstallError("No supported xmlrpc-c++ development package found in APT repositories.")
|
|
||||||
return [chosen, "libxmlrpc-core-c3-dev"]
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_packages(packages):
|
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)
|
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):
|
def build_libtorrent(base_dir, libtorrent_ref):
|
||||||
source_dir = Path(base_dir) / "libtorrent"
|
source_dir = Path(base_dir) / "libtorrent"
|
||||||
install_dir = Path(base_dir) / "libtorrent_install"
|
install_dir = Path(base_dir) / "libtorrent_install"
|
||||||
@@ -171,17 +207,26 @@ def build_libtorrent(base_dir, libtorrent_ref):
|
|||||||
return install_dir
|
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"
|
source_dir = Path(base_dir) / "rtorrent"
|
||||||
install_dir = Path(base_dir) / "rtorrent_install"
|
install_dir = Path(base_dir) / "rtorrent_install"
|
||||||
|
|
||||||
clone_or_update_repo("https://github.com/rakshasa/rtorrent.git", source_dir, rtorrent_ref)
|
clone_or_update_repo("https://github.com/rakshasa/rtorrent.git", source_dir, rtorrent_ref)
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["CFLAGS"] = f"-I{libtorrent_install}/include"
|
include_flags = f"-I{libtorrent_install}/include -I{xmlrpc_install}/include"
|
||||||
env["LDFLAGS"] = f"-L{libtorrent_install}/lib"
|
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", "")
|
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(["autoreconf", "-i"], cwd=str(source_dir), env=env)
|
||||||
run([
|
run([
|
||||||
@@ -194,7 +239,7 @@ def build_rtorrent(base_dir, rtorrent_ref, libtorrent_install):
|
|||||||
return install_dir
|
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"
|
rtorrent_bin = Path(rtorrent_install) / "bin" / "rtorrent"
|
||||||
if not rtorrent_bin.exists():
|
if not rtorrent_bin.exists():
|
||||||
raise InstallError(f"Compiled rtorrent binary not found: {rtorrent_bin}")
|
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)
|
usr_local_bin.symlink_to(rtorrent_bin)
|
||||||
print(f"Symlinked {usr_local_bin} -> {rtorrent_bin}")
|
print(f"Symlinked {usr_local_bin} -> {rtorrent_bin}")
|
||||||
|
|
||||||
ld_conf = Path("/etc/ld.so.conf.d/rtorrent-libtorrent.conf")
|
ld_conf = Path("/etc/ld.so.conf.d/rtorrent-custom-libs.conf")
|
||||||
ld_conf.write_text(f"{libtorrent_install}/lib\n")
|
ld_conf.write_text(f"{libtorrent_install}/lib\n{xmlrpc_install}/lib\n")
|
||||||
run(["ldconfig"])
|
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]
|
service_content = f"""[Unit]
|
||||||
Description=rTorrent for %I
|
Description=rTorrent for %I
|
||||||
After=network.target
|
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
|
ExecStop=/bin/kill -9 $MAINPID
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=3
|
RestartSec=3
|
||||||
Environment=LD_LIBRARY_PATH={libtorrent_lib_dir}
|
Environment=LD_LIBRARY_PATH={runtime_lib_dirs}
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
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.scgi.open_port = 127.0.0.1:{DEFAULT_SCGI_PORT}
|
||||||
network.port_range.set = {DEFAULT_TORRENT_PORT}-{DEFAULT_TORRENT_PORT}
|
network.port_range.set = {DEFAULT_TORRENT_PORT}-{DEFAULT_TORRENT_PORT}
|
||||||
network.port_random.set = no
|
network.port_random.set = no
|
||||||
|
network.bind_address.set = 0.0.0.0
|
||||||
system.file.allocate.set = 1
|
system.file.allocate.set = 1
|
||||||
dht.mode.set = disable
|
dht.mode.set = disable
|
||||||
protocol.pex.set = no
|
protocol.pex.set = no
|
||||||
@@ -278,7 +324,7 @@ def enable_service(user):
|
|||||||
print(f"Enabled and started {unit_name}")
|
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"
|
rtorrent_bin = Path(rtorrent_install) / "bin" / "rtorrent"
|
||||||
which_rtorrent = capture(["which", "rtorrent"], check=False) or "not found in PATH"
|
which_rtorrent = capture(["which", "rtorrent"], check=False) or "not found in PATH"
|
||||||
print(f"Resolved rtorrent from PATH: {which_rtorrent}")
|
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):
|
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.")
|
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():
|
def build_parser():
|
||||||
parser = argparse.ArgumentParser(
|
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("--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("--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("--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("--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("--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})")
|
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",
|
"libcurl4-openssl-dev",
|
||||||
"libncurses5-dev",
|
"libncurses5-dev",
|
||||||
"libncursesw5-dev",
|
"libncursesw5-dev",
|
||||||
|
"libexpat1-dev",
|
||||||
"curl",
|
"curl",
|
||||||
|
"tar",
|
||||||
]
|
]
|
||||||
|
|
||||||
packages.extend(resolve_xmlrpc_packages())
|
|
||||||
|
|
||||||
print("This script will:")
|
print("This script will:")
|
||||||
|
print(f" - build xmlrpc-c from '{args.xmlrpc_ref}'")
|
||||||
print(f" - build libtorrent from '{args.libtorrent_ref}'")
|
print(f" - build libtorrent from '{args.libtorrent_ref}'")
|
||||||
print(f" - build rtorrent from '{args.rtorrent_ref}'")
|
print(f" - build rtorrent from '{args.rtorrent_ref}'")
|
||||||
print(f" - install everything under '{args.base_dir}'")
|
print(f" - install everything under '{args.base_dir}'")
|
||||||
@@ -347,22 +415,26 @@ def main():
|
|||||||
ensure_packages(packages)
|
ensure_packages(packages)
|
||||||
ensure_dir(args.base_dir)
|
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)
|
libtorrent_install = build_libtorrent(args.base_dir, args.libtorrent_ref)
|
||||||
rtorrent_install = build_rtorrent(args.base_dir, args.rtorrent_ref, libtorrent_install)
|
rtorrent_install = build_rtorrent(args.base_dir, args.rtorrent_ref, libtorrent_install, xmlrpc_install)
|
||||||
install_symlinks(rtorrent_install, libtorrent_install)
|
install_symlinks(rtorrent_install, libtorrent_install, xmlrpc_install)
|
||||||
verify_install(rtorrent_install, libtorrent_install)
|
verify_install(rtorrent_install, libtorrent_install, xmlrpc_install)
|
||||||
|
|
||||||
if not args.only_build:
|
if not args.only_build:
|
||||||
create_system_user(args.user, args.group, args.home, assume_yes=args.yes)
|
create_system_user(args.user, args.group, args.home, assume_yes=args.yes)
|
||||||
prepare_user_dirs(args.home, args.user)
|
prepare_user_dirs(args.home, args.user)
|
||||||
write_rtorrent_config(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)
|
enable_service(args.user)
|
||||||
print(f"\nService status hint: systemctl status rtorrent@{args.user}.service")
|
print(f"\nService status hint: systemctl status rtorrent@{args.user}.service")
|
||||||
|
|
||||||
print("\nDone.")
|
print("\nDone.")
|
||||||
print(f"rtorrent binary: /usr/local/bin/rtorrent")
|
print(f"rtorrent binary: /usr/local/bin/rtorrent")
|
||||||
print(f"libtorrent path: {libtorrent_install / 'lib'}")
|
print(f"libtorrent path: {libtorrent_install / 'lib'}")
|
||||||
|
print(f"xmlrpc-c path: {xmlrpc_install / 'lib'}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user