#!/bin/sh # ═══════════════════════════════════════════════════════════════════════════════ # NZBHydra2 Controller - Usenet Meta Search # LXC container with Debian rootfs (no Docker/Podman) # ═══════════════════════════════════════════════════════════════════════════════ CONTAINER_NAME="nzbhydra" CONTAINER_DIR="/srv/lxc/$CONTAINER_NAME" DATA_DIR="/srv/nzbhydra" CONFIG="nzbhydra" # Debian LXC rootfs URL (arm64) - Linux Containers official DEBIAN_LXC_MIRROR="https://images.linuxcontainers.org/images/debian/bookworm/arm64/default" # Logging log_info() { logger -t nzbhydra -p user.info "$*"; echo "[INFO] $*"; } log_error() { logger -t nzbhydra -p user.error "$*"; echo "[ERROR] $*" >&2; } log_ok() { echo "[OK] $*"; } # UCI helpers uci_get() { uci -q get "$CONFIG.$1"; } # ───────────────────────────────────────────────────────────────────────────────── # Install container with Debian rootfs # ───────────────────────────────────────────────────────────────────────────────── cmd_install() { log_info "Installing NZBHydra2 container (Debian LXC)..." # Create directories mkdir -p "$CONTAINER_DIR/rootfs" mkdir -p "$DATA_DIR/config" local rootfs="$CONTAINER_DIR/rootfs" # Download Debian LXC rootfs if not exists if [ ! -f "$rootfs/bin/bash" ]; then log_info "Downloading Debian LXC rootfs..." # Get latest build date from LXC mirror local latest=$(curl -sL "$DEBIAN_LXC_MIRROR/" | grep -oE '[0-9]{8}_[0-9]{2}:[0-9]{2}' | sort -r | head -1) [ -z "$latest" ] && latest="20240301_05:24" local rootfs_url="${DEBIAN_LXC_MIRROR}/${latest}/rootfs.tar.xz" local tarball="/tmp/debian-rootfs-nzbhydra.tar.xz" log_info "Fetching: $rootfs_url" curl -L -o "$tarball" "$rootfs_url" || { log_error "Failed to download Debian rootfs" return 1 } log_info "Extracting rootfs..." xz -d -c "$tarball" | tar -xf - -C "$rootfs" || { log_error "Failed to extract rootfs" rm -f "$tarball" return 1 } rm -f "$tarball" fi # Configure DNS echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf" echo "nameserver 1.1.1.1" >> "$rootfs/etc/resolv.conf" # Create startup script cat > "$rootfs/start-nzbhydra.sh" <<'STARTEOF' #!/bin/bash export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" export HOME="/root" exec > /config/startup.log 2>&1 echo "=== Starting NZBHydra2 $(date) ===" # Install NZBHydra2 if not present if [ ! -f "/opt/nzbhydra2/lib/core.jar" ] && ! ls /opt/nzbhydra2/lib/core-*-exec.jar >/dev/null 2>&1; then echo "Installing NZBHydra2..." apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ openjdk-17-jre-headless \ python3 \ curl \ ca-certificates \ unzip || { echo "apt install failed"; exit 1; } # Download NZBHydra2 mkdir -p /opt/nzbhydra2 cd /opt/nzbhydra2 echo "Downloading NZBHydra2..." latest=$(curl -sL "https://api.github.com/repos/theotherp/nzbhydra2/releases/latest" | grep -oP "\"tag_name\": \"v\K[^\"]+") [ -z "$latest" ] && latest="8.5.2" echo "Version: $latest" curl -L -o nzbhydra2.zip "https://github.com/theotherp/nzbhydra2/releases/download/v${latest}/nzbhydra2-${latest}-generic.zip" unzip -o nzbhydra2.zip || { echo "unzip failed"; exit 1; } rm -f nzbhydra2.zip chmod +x nzbhydra2wrapperPy3.py 2>/dev/null || true apt-get clean rm -rf /var/lib/apt/lists/* else echo "NZBHydra2 already installed" # Ensure python3 is installed if ! command -v python3 >/dev/null 2>&1; then apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y python3 apt-get clean fi fi echo "=== Starting NZBHydra2 server ===" mkdir -p /config cd /opt/nzbhydra2 exec python3 nzbhydra2wrapperPy3.py --nobrowser --datafolder /config --host 0.0.0.0 --port 5076 STARTEOF chmod +x "$rootfs/start-nzbhydra.sh" # Create LXC config local memory=$(uci_get main.memory) [ -z "$memory" ] && memory="268435456" # 256MB in bytes cat > "$CONTAINER_DIR/config" </dev/null | grep -q "RUNNING"; then log_info "NZBHydra2 already running" return 0 fi log_info "Starting NZBHydra2..." lxc-start -n "$CONTAINER_NAME" -f "$CONTAINER_DIR/config" # Wait for startup sleep 5 if lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then log_ok "NZBHydra2 started on http://192.168.255.41:5076/" else log_error "Failed to start NZBHydra2" lxc-info -n "$CONTAINER_NAME" 2>&1 return 1 fi } # ───────────────────────────────────────────────────────────────────────────────── # Stop container # ───────────────────────────────────────────────────────────────────────────────── cmd_stop() { if ! lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then log_info "NZBHydra2 not running" return 0 fi log_info "Stopping NZBHydra2..." lxc-stop -n "$CONTAINER_NAME" -t 30 log_ok "NZBHydra2 stopped" } # ───────────────────────────────────────────────────────────────────────────────── # Restart container # ───────────────────────────────────────────────────────────────────────────────── cmd_restart() { cmd_stop sleep 2 cmd_start } # ───────────────────────────────────────────────────────────────────────────────── # Status # ───────────────────────────────────────────────────────────────────────────────── cmd_status() { echo "=== NZBHydra2 Status ===" # Container state if lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then echo "Container: RUNNING" echo "IP: 192.168.255.41" else echo "Container: STOPPED" return 0 fi # Check if API responds if curl -s "http://192.168.255.41:5076/api?t=caps" >/dev/null 2>&1; then echo "API: OK" else echo "API: Not responding (may be starting)" fi echo "Web UI: http://192.168.255.41:5076/" } # ───────────────────────────────────────────────────────────────────────────────── # Logs # ───────────────────────────────────────────────────────────────────────────────── cmd_logs() { local lines="${1:-50}" if [ -f "$DATA_DIR/config/logs/nzbhydra2.log" ]; then tail -n "$lines" "$DATA_DIR/config/logs/nzbhydra2.log" elif [ -f "$DATA_DIR/config/startup.log" ]; then tail -n "$lines" "$DATA_DIR/config/startup.log" else log_info "No logs yet. NZBHydra may not have run." fi } # ───────────────────────────────────────────────────────────────────────────────── # Shell access # ───────────────────────────────────────────────────────────────────────────────── cmd_shell() { if ! lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then log_error "Container not running" return 1 fi lxc-attach -n "$CONTAINER_NAME" -- /bin/bash } # ───────────────────────────────────────────────────────────────────────────────── # Link SABnzbd as downloader # ───────────────────────────────────────────────────────────────────────────────── cmd_link_sabnzbd() { log_info "Linking SABnzbd..." # Get SABnzbd API key local sab_ini="/srv/sabnzbd/config/sabnzbd.ini" if [ ! -f "$sab_ini" ]; then log_error "SABnzbd not configured. Start SABnzbd first." return 1 fi local sab_api=$(grep "^api_key" "$sab_ini" 2>/dev/null | cut -d'=' -f2 | tr -d ' ') if [ -z "$sab_api" ]; then log_error "SABnzbd API key not found. Configure SABnzbd first." return 1 fi # Update UCI uci set "nzbhydra.sabnzbd.host=http://192.168.255.40:8085" uci set "nzbhydra.sabnzbd.api_key=$sab_api" uci commit nzbhydra log_ok "SABnzbd linked: http://192.168.255.40:8085" log_info "Configure in NZBHydra web UI: http://192.168.255.41:5076/" } # ───────────────────────────────────────────────────────────────────────────────── # Search # ───────────────────────────────────────────────────────────────────────────────── cmd_search() { local query="$1" [ -z "$query" ] && { log_error "Usage: nzbhydractl search "; return 1; } log_info "Searching: $query" local result=$(curl -s "http://192.168.255.41:5076/api?t=search&q=$(echo "$query" | sed 's/ /%20/g')" 2>/dev/null) if [ -n "$result" ]; then echo "$result" | python3 -c " import sys, xml.etree.ElementTree as ET xml = sys.stdin.read() try: root = ET.fromstring(xml) items = root.findall('.//item') for i, item in enumerate(items[:10], 1): title = item.find('title').text if item.find('title') is not None else 'N/A' size = item.find('enclosure').get('length', '0') if item.find('enclosure') is not None else '0' size_mb = int(size) // (1024*1024) print(f'{i}. {title} ({size_mb} MB)') except Exception as e: print(f'Error parsing: {e}') " 2>/dev/null else log_error "Search failed or no results" fi } # ───────────────────────────────────────────────────────────────────────────────── # Configure HAProxy exposure # ───────────────────────────────────────────────────────────────────────────────── cmd_configure_haproxy() { local domain=$(uci_get exposure.domain) [ -z "$domain" ] && domain="nzbhydra.gk2.secubox.in" log_info "Configuring HAProxy for $domain" # Create backend uci set haproxy.nzbhydra_web=backend uci set haproxy.nzbhydra_web.name='nzbhydra_web' uci set haproxy.nzbhydra_web.mode='http' uci set haproxy.nzbhydra_web.server="nzbhydra 192.168.255.41:5076 weight 100 check" # Create vhost local vhost_id=$(echo "$domain" | tr '.' '_') uci set "haproxy.$vhost_id=vhost" uci set "haproxy.$vhost_id.domain=$domain" uci set "haproxy.$vhost_id.backend=mitmproxy_inspector" uci set "haproxy.$vhost_id.original_backend=nzbhydra_web" uci set "haproxy.$vhost_id.ssl=1" uci set "haproxy.$vhost_id.ssl_redirect=1" uci set "haproxy.$vhost_id.acme=1" uci commit haproxy # Add mitmproxy route if [ -f /srv/mitmproxy/haproxy-routes.json ]; then python3 -c " import json with open('/srv/mitmproxy/haproxy-routes.json') as f: routes = json.load(f) routes['$domain'] = ['192.168.255.41', 5076] with open('/srv/mitmproxy/haproxy-routes.json', 'w') as f: json.dump(routes, f, indent=2) " 2>/dev/null fi # Reload haproxyctl reload 2>/dev/null || true /etc/init.d/mitmproxy restart 2>/dev/null || true log_ok "HAProxy configured: https://$domain/" } # ───────────────────────────────────────────────────────────────────────────────── # Uninstall # ───────────────────────────────────────────────────────────────────────────────── cmd_uninstall() { log_info "Uninstalling NZBHydra2..." cmd_stop 2>/dev/null rm -rf "$CONTAINER_DIR" log_info "Container removed. Data preserved in $DATA_DIR" log_ok "NZBHydra2 uninstalled" } # ───────────────────────────────────────────────────────────────────────────────── # Main # ───────────────────────────────────────────────────────────────────────────────── case "$1" in install) cmd_install ;; start) cmd_start ;; stop) cmd_stop ;; restart) cmd_restart ;; status) cmd_status ;; logs) shift; cmd_logs "$@" ;; shell) cmd_shell ;; search) shift; cmd_search "$@" ;; link-sabnzbd) cmd_link_sabnzbd ;; configure-haproxy) cmd_configure_haproxy ;; uninstall) cmd_uninstall ;; *) echo "NZBHydra2 Controller - Usenet Meta Search" echo "" echo "Usage: nzbhydractl " echo "" echo "Commands:" echo " install Install Debian LXC container with NZBHydra2" echo " start Start NZBHydra2" echo " stop Stop NZBHydra2" echo " restart Restart NZBHydra2" echo " status Show status" echo " logs [n] Show last n log lines (default 50)" echo " shell Interactive shell in container" echo " search Search indexers" echo " link-sabnzbd Configure SABnzbd as downloader" echo " configure-haproxy Setup HAProxy reverse proxy" echo " uninstall Remove container (keeps data)" ;; esac