#!/bin/sh username="torrserver" dirInstall="/opt/torrserver" serviceName="torrserver" scriptname=$(basename "$0") NO_COLOR=0 AUTO_MODE=0 LANG_FILE="$dirInstall/lang" # Цвета RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' BLUE='\033[0;34m' NC='\033[0m' colorize() { if [ "$NO_COLOR" -eq 1 ]; then printf "%s" "$2" else case $1 in red) printf "${RED}%s${NC}" "$2" ;; green) printf "${GREEN}%s${NC}" "$2" ;; yellow) printf "${YELLOW}%s${NC}" "$2" ;; cyan) printf "${CYAN}%s${NC}" "$2" ;; blue) printf "${BLUE}%s${NC}" "$2" ;; *) printf "%s" "$2" ;; esac fi } # ============================================================ # ОПРЕДЕЛЕНИЕ ПЛАТФОРМЫ # ============================================================ OS_TYPE="" OS_NAME="" detectOS() { if [ -f /etc/openwrt_release ]; then OS_TYPE="openwrt" OS_NAME="OpenWrt" return fi if [ -f /etc/alpine-release ]; then OS_TYPE="alpine" OS_NAME="Alpine Linux" return fi if [ -f /etc/os-release ]; then . /etc/os-release OS_NAME="${PRETTY_NAME:-$NAME}" case "$ID" in ubuntu|debian|raspbian|linuxmint|pop) OS_TYPE="debian" ;; arch|manjaro|endeavouros|garuda) OS_TYPE="arch" ;; fedora|rhel|centos|rocky|almalinux) OS_TYPE="rhel" ;; opensuse*|sles) OS_TYPE="suse" ;; *) # Проверяем по ID_LIKE case "$ID_LIKE" in *debian*|*ubuntu*) OS_TYPE="debian" ;; *arch*) OS_TYPE="arch" ;; *rhel*|*fedora*) OS_TYPE="rhel" ;; *) OS_TYPE="unknown" ;; esac ;; esac return fi OS_TYPE="unknown" OS_NAME="Unknown Linux" } hasSystemd() { command -v systemctl >/dev/null 2>&1 && systemctl is-system-running >/dev/null 2>&1 } # ============================================================ # ЛОКАЛИЗАЦИЯ # ============================================================ LANG_CODE="ru" loadLang() { if [ -f "$LANG_FILE" ]; then LANG_CODE=$(cat "$LANG_FILE") fi } saveLang() { mkdir -p "$dirInstall" 2>/dev/null printf "%s" "$LANG_CODE" > "$LANG_FILE" } selectLanguage() { printf "\n" printf " Select language / Выберите язык:\n" printf "\n" printf " 1) English\n" printf " 2) Русский\n" printf "\n" printf " Choice / Выбор [1/2]: " read -r lang_choice /dev/null; then t user_exists "$username" # Даже если пользователь уже есть — убеждаемся что директория ему принадлежит chown -R "$username" "$dirInstall" 2>/dev/null || true return 0 fi case "$OS_TYPE" in openwrt|alpine) local group="nogroup" grep -q "^nogroup:" /etc/group 2>/dev/null || group="nobody" adduser -D -H -h "$dirInstall" -s /bin/false -G "$group" "$username" 2>/dev/null ;; debian|arch|rhel|suse|*) useradd -r -s /bin/false -d "$dirInstall" -M "$username" 2>/dev/null ;; esac if grep -q "^$username:" /etc/passwd 2>/dev/null; then # Передаём владение директорией пользователю — он должен иметь право на запись chown -R "$username" "$dirInstall" chmod 750 "$dirInstall" t user_added "$username" else t user_root "$username" username="root" fi } delUser() { [ "$username" = "root" ] && return 0 grep -q "^$username:" /etc/passwd 2>/dev/null || return 0 case "$OS_TYPE" in openwrt|alpine) deluser "$username" 2>/dev/null ;; *) userdel "$username" 2>/dev/null ;; esac t user_deleted "$username" } getIP() { local iface iface=$(ip route show default 2>/dev/null | awk '/default/{print $5; exit}') if [ -n "$iface" ]; then ip addr show dev "$iface" 2>/dev/null | awk '/inet /{print $2; exit}' | cut -d/ -f1 else ip addr 2>/dev/null | awk '/inet /{print $2}' | grep -v '^127\.' | cut -d/ -f1 | head -n1 fi } getLatestRelease() { curl -sf --max-time 15 "https://api.github.com/repos/YouROK/TorrServer/releases/latest" \ | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' } getInstalledVersion() { [ -f "$dirInstall/version" ] && cat "$dirInstall/version" || echo "unknown" } getInstalledArch() { if [ -f "$dirInstall/binary" ]; then local b; b=$(cat "$dirInstall/binary"); echo "${b#TorrServer-}" else detectArch fi } getServicePort() { local f="" if [ "$OS_TYPE" = "openwrt" ] || [ "$OS_TYPE" = "alpine" ]; then f="/etc/init.d/$serviceName" elif hasSystemd; then f="/etc/systemd/system/${serviceName}.service" fi [ -f "$f" ] && grep -o '\-\-port [0-9]*' "$f" 2>/dev/null | awk '{print $2}' | head -1 } getAuthCredentials() { local accsFile="$dirInstall/accs.db" [ ! -f "$accsFile" ] && return local user pass user=$(grep -o '"[^"]*"' "$accsFile" | sed -n '1p' | tr -d '"') pass=$(grep -o '"[^"]*"' "$accsFile" | sed -n '2p' | tr -d '"') [ -n "$user" ] && [ -n "$pass" ] && printf "%s:%s" "$user" "$pass" } isAuthEnabled() { local f="" if [ "$OS_TYPE" = "openwrt" ] || [ "$OS_TYPE" = "alpine" ]; then f="/etc/init.d/$serviceName" elif hasSystemd; then f="/etc/systemd/system/${serviceName}.service" fi [ -f "$f" ] && grep -q '\-\-httpauth' "$f" 2>/dev/null } checkInstalled() { local bin="" if [ -f "$dirInstall/binary" ]; then bin="$dirInstall/$(cat "$dirInstall/binary")" else local arch; arch=$(detectArch) [ -n "$arch" ] && bin="$dirInstall/TorrServer-${arch}" fi [ -n "$bin" ] && [ -f "$bin" ] } checkDiskSpace() { local required=80 available available=$(df "$dirInstall" 2>/dev/null | awk 'NR==2{print int($4/1024)}') if [ -z "$available" ]; then available=$(df /opt 2>/dev/null | awk 'NR==2{print int($4/1024)}') [ -z "$available" ] && available=$(df / | awk 'NR==2{print int($4/1024)}') fi if [ "$available" -lt "$required" ] 2>/dev/null; then t disk_warn "$available" "$required" printf " "; t disk_cont; t yes_no read -r ans /dev/null 2>&1; then t inet_fail; exit 1 fi t inet_ok } initialCheck() { if ! isRoot; then t requires_root "$scriptname"; exit 1; fi checkInternet } isRunning() { local arch; arch=$(getInstalledArch) if [ "$OS_TYPE" = "openwrt" ] || ! hasSystemd; then pidof "TorrServer-${arch}" >/dev/null 2>&1 else systemctl is-active --quiet "$serviceName" 2>/dev/null fi } printLogo() { if [ "$NO_COLOR" -eq 0 ]; then printf "${RED}"; fi printf " ████████╗ ██████╗ ██████╗ ██████╗ \n" printf " ██╔══╝██╔═══██╗██╔══██╗██╔══██╗\n" printf " ██║ ██║ ██║██████╔╝██████╔╝\n" printf " ██║ ██║ ██║██╔══██╗██╔══██╗\n" printf " ██║ ╚██████╔╝██║ ██║██║ ██║\n" printf " ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝\n" if [ "$NO_COLOR" -eq 0 ]; then printf "${BLUE}"; fi printf " ███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗ \n" printf " ██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗\n" printf " ███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝\n" printf " ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗\n" printf " ███████║███████╗██║ ██║ ╚████╔╝ ███████╗██║ ██║\n" printf " ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝\n" if [ "$NO_COLOR" -eq 0 ]; then printf "${NC}"; fi printf " ─────────────────────────────────────────────────────\n" t logo_sub; printf "\n" printf "\n" } # ============================================================ # ОСНОВНЫЕ ФУНКЦИИ # ============================================================ helpUsage() { if [ "$LANG_CODE" = "en" ]; then printf "Usage: %s [command] [flags]\n\nCommands:\n" "$scriptname" printf " %-30s %s\n" "-i | --install | install" "install latest version" printf " %-30s %s\n" "-u | --update | update" "update to latest version" printf " %-30s %s\n" "-s | --status | status" "service status" printf " %-30s %s\n" "-b | --bypass | bypass" "configure proxy bypass" printf " %-30s %s\n" "-r | --remove | remove" "remove TorrServer" printf " %-30s %s\n" "-h | --help | help" "this help" printf "\nFlags:\n" printf " %-30s %s\n" "--no-color" "output without colors (for logs/scripts)" printf " %-30s %s\n" "--auto" "automatic mode (for cron)" else printf "Использование: %s [команда] [флаги]\n\nКоманды:\n" "$scriptname" printf " %-30s %s\n" "-i | --install | install" "установка последней версии" printf " %-30s %s\n" "-u | --update | update" "обновление до последней версии" printf " %-30s %s\n" "-s | --status | status" "статус службы" printf " %-30s %s\n" "-b | --bypass | bypass" "настроить прямое соединение" printf " %-30s %s\n" "-r | --remove | remove" "удаление TorrServer" printf " %-30s %s\n" "-h | --help | help" "эта справка" printf "\nФлаги:\n" printf " %-30s %s\n" "--no-color" "вывод без цветов (для логов/скриптов)" printf " %-30s %s\n" "--auto" "автоматический режим (для cron)" fi } cleanup() { svcRemove rm -rf "$dirInstall" delUser } uninstall() { if ! checkInstalled; then t not_installed_err; return 1; fi printf "\n" printf " Directory: %s\n" "$dirInstall" t s_version "$(getInstalledVersion)" printf "\n" t remove_warn printf "\n" printf " "; t remove_sure; t yes_no read -r ans /dev/null | awk '{print $1}') if [ -n "$pid" ] && [ -f "/proc/$pid/stat" ]; then local ticks uptime_sec hz start_sec running_sec h m s ticks=$(awk '{print $22}' /proc/$pid/stat 2>/dev/null) uptime_sec=$(awk '{print int($1)}' /proc/uptime 2>/dev/null) hz=$(getconf CLK_TCK 2>/dev/null || echo 100) if [ -n "$ticks" ] && [ -n "$uptime_sec" ]; then start_sec=$((ticks / hz)) running_sec=$((uptime_sec - start_sec)) if [ "$running_sec" -gt 0 ] 2>/dev/null; then h=$((running_sec / 3600)) m=$(((running_sec % 3600) / 60)) s=$((running_sec % 60)) t s_uptime "$h" "$m" "$s" fi fi fi else t s_stopped t s_address_off "$ip" "$port" fi if isAuthEnabled; then local creds authUser authPass creds=$(getAuthCredentials) if [ -n "$creds" ]; then authUser="${creds%%:*}"; authPass="${creds#*:}" t s_auth_on t s_login "$authUser" t s_password "$authPass" else t s_auth_warn fi else t s_auth_off fi if crontab -l 2>/dev/null | grep -q "torrserver.*update\|update.*torrserver"; then t s_autoupd_on else t s_autoupd_off fi local latest latest=$(getLatestRelease) if [ -n "$latest" ] && [ "$latest" != "$version" ]; then t s_update_avail "$latest" elif [ -n "$latest" ]; then t s_update_no else t s_update_fail fi t sep; printf "\n" } downloadTorrServer() { local version="$1" arch="$2" local binName="TorrServer-${arch}" local urlBin="https://github.com/YouROK/TorrServer/releases/download/${version}/${binName}" local tmpFile="$dirInstall/${binName}.tmp" # Проверяем свободное место перед скачиванием # При обновлении оба файла существуют одновременно → нужно ~160 МБ local required=160 local existing=0 [ -f "$dirInstall/$binName" ] && existing=$(du -m "$dirInstall/$binName" 2>/dev/null | awk '{print $1}') [ "$existing" -eq 0 ] 2>/dev/null && required=80 local available available=$(df "$dirInstall" 2>/dev/null | awk 'NR==2{print int($4/1024)}') [ -z "$available" ] && available=$(df /opt 2>/dev/null | awk 'NR==2{print int($4/1024)}') [ -z "$available" ] && available=$(df / | awk 'NR==2{print int($4/1024)}') if [ "$available" -lt "$required" ] 2>/dev/null; then printf "\n" t disk_warn "$available" "$required" if [ "$existing" -gt 0 ]; then if [ "$LANG_CODE" = "en" ]; then printf " $(colorize yellow TIP) Remove old binary first to free ~%s MB? " "$existing" else printf " $(colorize yellow СОВЕТ) Удалить старый бинарь чтобы освободить ~%s МБ? " "$existing" fi t yes_no read -r ans "$dirInstall/version.bak" rm -f "$dirInstall/$binName" if [ "$LANG_CODE" = "en" ]; then printf " - Old binary removed, proceeding...\n" else printf " - Старый бинарь удалён, продолжаем...\n" fi available=$(df "$dirInstall" 2>/dev/null | awk 'NR==2{print int($4/1024)}') if [ "$available" -lt 80 ] 2>/dev/null; then t disk_warn "$available" "80" t dl_fail; return 1 fi else t dl_fail; return 1 fi else t dl_fail; return 1 fi fi t downloading "$version" "$arch" if ! curl -L --progress-bar -o "$tmpFile" "$urlBin"; then t dl_fail rm -f "$tmpFile" # Бинарь был удалён для освобождения места — пробуем восстановить _recoverBinary "$arch" return 1 fi local filesize filesize=$(wc -c < "$tmpFile" 2>/dev/null || echo 0) if [ "$filesize" -lt 1000000 ]; then t dl_small "$filesize" rm -f "$tmpFile" _recoverBinary "$arch" return 1 fi chmod +x "$tmpFile" mv -f "$tmpFile" "$dirInstall/$binName" printf "%s" "$version" > "$dirInstall/version" printf "%s" "$binName" > "$dirInstall/binary" rm -f "$dirInstall/version.bak" t dl_done } # Попытка восстановить бинарь после неудачного скачивания _recoverBinary() { local arch="$1" local binName="TorrServer-${arch}" local bakVersion="" # Проверяем есть ли сохранённая версия (значит бинарь был удалён) [ -f "$dirInstall/version.bak" ] || return 0 bakVersion=$(cat "$dirInstall/version.bak") [ -z "$bakVersion" ] && return 0 if [ "$LANG_CODE" = "en" ]; then printf "\n $(colorize red "!") Binary was removed and download failed — system is broken!\n" printf " Attempting to restore version %s...\n" "$bakVersion" else printf "\n $(colorize red "!") Бинарь был удалён, а скачивание прервалось — система не работает!\n" printf " Пытаемся восстановить версию %s...\n" "$bakVersion" fi local urlBin="https://github.com/YouROK/TorrServer/releases/download/${bakVersion}/${binName}" local tmpFile="$dirInstall/${binName}.tmp" if curl -L --progress-bar -o "$tmpFile" "$urlBin" 2>/dev/null; then local filesize filesize=$(wc -c < "$tmpFile" 2>/dev/null || echo 0) if [ "$filesize" -ge 1000000 ]; then chmod +x "$tmpFile" mv -f "$tmpFile" "$dirInstall/$binName" printf "%s" "$bakVersion" > "$dirInstall/version" printf "%s" "$binName" > "$dirInstall/binary" rm -f "$dirInstall/version.bak" svcStart 2>/dev/null if [ "$LANG_CODE" = "en" ]; then printf " ✓ Restored version %s — service started\n" "$bakVersion" else printf " ✓ Восстановлена версия %s — служба запущена\n" "$bakVersion" fi return 0 fi fi rm -f "$tmpFile" rm -f "$dirInstall/version.bak" if [ "$LANG_CODE" = "en" ]; then printf " $(colorize red "✗") Recovery failed. To reinstall run:\n" printf " sh %s -i\n\n" "$scriptname" else printf " $(colorize red "✗") Восстановление не удалось. Для переустановки выполните:\n" printf " sh %s -i\n\n" "$scriptname" fi } writeInitScript() { local binName="$1" authOptions="$2" if [ "$OS_TYPE" = "openwrt" ]; then # procd init script cat > /etc/init.d/$serviceName << EOF #!/bin/sh /etc/rc.common START=99 STOP=10 USE_PROCD=1 PROG="$dirInstall/$binName" start_service() { procd_open_instance procd_set_param command \$PROG $authOptions procd_set_param respawn \${respawn_threshold:-3600} \${respawn_timeout:-5} \${respawn_retry:-5} procd_set_param stdout 1 procd_set_param stderr 1 procd_close_instance } stop_service() { killall "$binName" 2>/dev/null return 0 } reload_service() { stop; sleep 1; start } EOF chmod +x /etc/init.d/$serviceName elif hasSystemd; then # systemd service cat > /etc/systemd/system/${serviceName}.service << EOF [Unit] Description=TorrServer torrent streaming server After=network.target Wants=network-online.target [Service] Type=simple User=$username ExecStart=$dirInstall/$binName $authOptions Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal SyslogIdentifier=torrserver [Install] WantedBy=multi-user.target EOF systemctl daemon-reload elif [ "$OS_TYPE" = "alpine" ]; then # OpenRC init script cat > /etc/init.d/$serviceName << EOF #!/sbin/openrc-run description="TorrServer — torrent streaming server" command="$dirInstall/$binName" command_args="$authOptions" command_user="$username" pidfile="/run/\${RC_SVCNAME}.pid" command_background=true output_log="/var/log/torrserver.log" error_log="/var/log/torrserver.log" depend() { need net } EOF chmod +x /etc/init.d/$serviceName fi } svcEnable() { if [ "$OS_TYPE" = "openwrt" ]; then /etc/init.d/$serviceName enable elif hasSystemd; then systemctl enable "$serviceName" 2>/dev/null elif [ "$OS_TYPE" = "alpine" ]; then rc-update add "$serviceName" default 2>/dev/null fi } svcDisable() { if [ "$OS_TYPE" = "openwrt" ]; then /etc/init.d/$serviceName disable 2>/dev/null elif hasSystemd; then systemctl disable "$serviceName" 2>/dev/null elif [ "$OS_TYPE" = "alpine" ]; then rc-update del "$serviceName" default 2>/dev/null fi } svcStart() { if [ "$OS_TYPE" = "openwrt" ]; then /etc/init.d/$serviceName start elif hasSystemd; then systemctl start "$serviceName" elif [ "$OS_TYPE" = "alpine" ]; then rc-service "$serviceName" start else local arch; arch=$(getInstalledArch) local port; port=$(getServicePort); [ -z "$port" ] && port="8090" "$dirInstall/TorrServer-${arch}" --port "$port" --path "$dirInstall" & fi } svcStop() { if [ "$OS_TYPE" = "openwrt" ]; then /etc/init.d/$serviceName stop 2>/dev/null elif hasSystemd; then systemctl stop "$serviceName" 2>/dev/null elif [ "$OS_TYPE" = "alpine" ]; then rc-service "$serviceName" stop 2>/dev/null else local arch; arch=$(getInstalledArch) killall "TorrServer-${arch}" 2>/dev/null fi } svcRestart() { if [ "$OS_TYPE" = "openwrt" ]; then /etc/init.d/$serviceName restart 2>/dev/null elif hasSystemd; then systemctl restart "$serviceName" elif [ "$OS_TYPE" = "alpine" ]; then rc-service "$serviceName" restart else svcStop; sleep 1; svcStart fi } svcRemove() { svcStop svcDisable if [ "$OS_TYPE" = "openwrt" ]; then rm -f /etc/init.d/$serviceName elif hasSystemd; then rm -f /etc/systemd/system/${serviceName}.service systemctl daemon-reload 2>/dev/null elif [ "$OS_TYPE" = "alpine" ]; then rm -f /etc/init.d/$serviceName fi } logHint() { if [ "$OS_TYPE" = "openwrt" ]; then t log_hint_openwrt else t log_hint_systemd fi } changeAuth() { if ! checkInstalled; then t not_installed_err; return 1; fi local port; port=$(getServicePort); [ -z "$port" ] && port="8090" local creds; creds=$(getAuthCredentials) if [ -n "$creds" ]; then t auth_cur_login "${creds%%:*}"; fi t auth_menu printf " "; t port_enter 2>/dev/null; printf "$(t port_cur 2>/dev/null)" 2>/dev/null if [ "$LANG_CODE" = "en" ]; then printf " Choice: "; else printf " Выбор: "; fi read -r auth_choice "$dirInstall/accs.db" chmod 640 "$dirInstall/accs.db" chown "$username" "$dirInstall/accs.db" 2>/dev/null || true authOptions="--port $port --path $dirInstall --httpauth" t auth_updated ;; 2) rm -f "$dirInstall/accs.db" authOptions="--port $port --path $dirInstall" t auth_disabled ;; 3) t auth_user; read -r newUser "$dirInstall/accs.db" chmod 640 "$dirInstall/accs.db" chown "$username" "$dirInstall/accs.db" 2>/dev/null || true authOptions="--port $port --path $dirInstall --httpauth" t auth_enabled ;; *) t cancelled; return 0 ;; esac local arch binName arch=$(getInstalledArch); binName="TorrServer-${arch}" writeInitScript "$binName" "$authOptions" t restarting svcStop; sleep 1 svcStart; sleep 1 if isRunning; then t settings_applied; else t start_fail; fi } changePort() { if ! checkInstalled; then t not_installed_err; return 1; fi local currentPort; currentPort=$(getServicePort); [ -z "$currentPort" ] && currentPort="8090" t port_cur "$currentPort" t port_new; read -r newPort > /var/log/torrserver-update.log 2>&1" if crontab -l 2>/dev/null | grep -q "torrserver.*update\|update.*torrserver"; then t autoupd_already t autoupd_disable; t yes_no read -r ans /dev/null | grep -v "torrserver.*update\|update.*torrserver" > "$tmp" crontab "$tmp"; rm -f "$tmp" t autoupd_off else t cancelled fi return 0 fi t autoupd_info "$scriptname" t yes_no read -r ans /dev/null > "$tmp" printf "%s\n" "$cronLine" >> "$tmp" crontab "$tmp"; rm -f "$tmp" /etc/init.d/cron enable 2>/dev/null /etc/init.d/cron start 2>/dev/null t autoupd_enabled else t cancelled fi } restartService() { t restart_svc svcRestart sleep 2 if isRunning; then t svc_running; else t start_fail; fi } UpdateVersion() { if ! checkInstalled; then t not_installed_err; return 1; fi t update_checking local latestVersion; latestVersion=$(getLatestRelease) if [ -z "$latestVersion" ]; then t ver_fail; return 1; fi local currentVersion; currentVersion=$(getInstalledVersion) t update_cur_latest "$currentVersion" "$latestVersion" if [ "$currentVersion" = "$latestVersion" ]; then t update_ok; return 0; fi local arch; arch=$(getInstalledArch) t stopping svcStop; sleep 1 killall "TorrServer-${arch}" 2>/dev/null; sleep 1 downloadTorrServer "$latestVersion" "$arch" || { t rollback svcStart 2>/dev/null return 1 } svcStart; sleep 2 if isRunning; then t updated_ok "$latestVersion" else t updated_no_start "$latestVersion"; fi } installTorrServer() { local arch; arch=$(detectArch) if [ -z "$arch" ]; then t arch_unknown "$(uname -m)" t arch_enter; read -r arch /dev/null | grep -q "torrserver.*update\|update.*torrserver"; then t s_autoupd_on else t s_autoupd_off fi t sep; printf "\n" t mgmt_menu while true; do if [ "$LANG_CODE" = "en" ]; then printf " Choice: "; else printf " Выбор: "; fi read -r mgmt "$dirInstall/accs.db" chmod 640 "$dirInstall/accs.db" chown "$username" "$dirInstall/accs.db" 2>/dev/null || true authOptions="--port $servicePort --path $dirInstall --httpauth" t auth_saved fi local binName="TorrServer-${arch}" writeInitScript "$binName" "$authOptions" svcEnable svcStart sleep 2 t autoupd_prompt; t yes_no read -r ans > /var/log/torrserver-update.log 2>&1" mkdir -p /etc/crontabs local tmp="/tmp/cron_ts.tmp" crontab -l 2>/dev/null > "$tmp" printf "%s\n" "$cronLine" >> "$tmp" crontab "$tmp"; rm -f "$tmp" /etc/init.d/cron enable 2>/dev/null /etc/init.d/cron start 2>/dev/null t autoupd_on fi local serverIP; serverIP=$(getIP); [ -z "$serverIP" ] && serverIP="" printf "\n"; t sep t os_detected "$OS_NAME" if isRunning; then t installed_ok "$latestVersion" else t installed_no_start "$latestVersion" # Показываем последние строки лога для диагностики if hasSystemd; then printf "\n" if [ "$LANG_CODE" = "en" ]; then printf " Last log lines:\n" else printf " Последние строки лога:\n" fi printf " ─────────────────────────────────\n" journalctl -u "$serviceName" -n 8 --no-pager 2>/dev/null | tail -8 | sed 's/^/ /' printf " ─────────────────────────────────\n" fi fi t sep t webui "$serverIP" "$servicePort" [ -n "$isAuthUser" ] && t login_pass "$isAuthUser" "$isAuthPass" logHint printf "\n" # Предлагаем bypass только на OpenWrt (где актуальны прокси-инструменты) [ "$OS_TYPE" = "openwrt" ] && applyProxyBypass } # ============================================================ # PROXY BYPASS (OpenWrt only) # ============================================================ NFTFILE="/etc/nftables.d/torrserver-bypass.nft" BYPASS_CGROUP="services/torrserver" # Определяем какие прокси-инструменты активны detectProxyTools() { local found="" uci get nikki.proxy.enabled 2>/dev/null | grep -q "1" && found="$found nikki" uci get podkop.main.enabled 2>/dev/null | grep -q "1" && found="$found podkop" uci get openclash.config.enable 2>/dev/null | grep -q "1" && found="$found openclash" uci get homeproxy.config.enabled 2>/dev/null | grep -q "1" && found="$found homeproxy" /etc/init.d/passwall status 2>/dev/null | grep -q "running" && found="$found passwall" /etc/init.d/passwall2 status 2>/dev/null | grep -q "running" && found="$found passwall2" [ -f "/etc/shellcrash/config.yaml" ] && found="$found shellcrash" pgrep -x "clash" >/dev/null 2>&1 && found="$found clash" printf "%s" "$found" } applyNftablesBypass() { if ! command -v nft >/dev/null 2>&1; then if [ "$LANG_CODE" = "en" ]; then printf " - nftables not found — bypass unavailable on this system\n" else printf " - nftables не найден — bypass недоступен на этой системе\n" fi return 1 fi # Проверяем версию OpenWrt — cgroupv2 socket match требует OpenWrt 22.03+ (ядро 5.10+) local owrt_ver="" if [ -f /etc/openwrt_release ]; then owrt_ver=$(grep "DISTRIB_RELEASE" /etc/openwrt_release | cut -d'"' -f2 | cut -d'.' -f1-2) fi # Сравниваем: нужна версия >= 22.03 local major minor major=$(printf "%s" "$owrt_ver" | cut -d'.' -f1) minor=$(printf "%s" "$owrt_ver" | cut -d'.' -f2) local supported=1 if [ -n "$major" ] && [ -n "$minor" ]; then if [ "$major" -lt 22 ] 2>/dev/null; then supported=0 elif [ "$major" -eq 22 ] && [ "$minor" -lt 3 ] 2>/dev/null; then supported=0 fi fi if [ "$supported" -eq 0 ]; then if [ "$LANG_CODE" = "en" ]; then printf " $(colorize yellow "WARN") OpenWrt %s detected.\n" "$owrt_ver" printf " cgroupv2 bypass requires OpenWrt 22.03+ (kernel 5.10+).\n" printf " Your version may not support it — apply anyway? " else printf " $(colorize yellow "WARN") Обнаружен OpenWrt %s.\n" "$owrt_ver" printf " cgroupv2 bypass требует OpenWrt 22.03+ (ядро 5.10+).\n" printf " Ваша версия может не поддерживать — применить всё равно? " fi t yes_no read -r ans /dev/null; then if [ "$LANG_CODE" = "en" ]; then printf " - nftables bypass already configured\n" else printf " - nftables bypass уже настроен\n" fi return 0 fi mkdir -p "$(dirname "$NFTFILE")" cat > "$NFTFILE" << 'NFT' # TorrServer bypass — direct connection, bypasses any proxy/VPN # Works with: nikki, podkop, sing-box, openclash, passwall, shellcrash, homeproxy # Priority mangle-5 is higher than any tproxy/redirect tool table inet torrserver_bypass { chain prerouting { type filter hook prerouting priority mangle - 5; policy accept; socket cgroupv2 level 2 "services/torrserver" return comment "torrserver direct" } chain output { type route hook output priority mangle - 5; policy accept; socket cgroupv2 level 2 "services/torrserver" return comment "torrserver direct" } } NFT if nft -f "$NFTFILE" 2>/dev/null; then if [ "$LANG_CODE" = "en" ]; then printf " - $(colorize green "nftables rule applied immediately")\n" else printf " - $(colorize green "nftables правило применено немедленно")\n" fi else if [ "$LANG_CODE" = "en" ]; then printf " - $(colorize yellow "nftables rule saved, will apply after reboot")\n" else printf " - $(colorize yellow "nftables правило сохранено, применится после перезагрузки")\n" fi fi } applyUciBypass() { # nikki if uci get nikki.proxy.enabled 2>/dev/null | grep -q "1"; then if ! uci get nikki.@router_access_control[0].cgroup 2>/dev/null | grep -q "$BYPASS_CGROUP"; then uci add_list nikki.@router_access_control[0].cgroup="$BYPASS_CGROUP" uci commit nikki /etc/init.d/nikki restart >/dev/null 2>&1 printf " - $(colorize green "[nikki]") UCI exception added\n" else printf " - $(colorize green "[nikki]") UCI exception already set\n" fi fi # podkop if uci get podkop.main.enabled 2>/dev/null | grep -q "1"; then if ! uci get podkop.main.excluded_processes 2>/dev/null | grep -q "torrserver"; then uci add_list podkop.main.excluded_processes="torrserver" uci commit podkop /etc/init.d/podkop restart >/dev/null 2>&1 printf " - $(colorize green "[podkop]") UCI exception added\n" else printf " - $(colorize green "[podkop]") UCI exception already set\n" fi fi # openclash if uci get openclash.config.enable 2>/dev/null | grep -q "1"; then local ocmixin="/etc/openclash/custom/openclash_custom_firewall_rules.sh" mkdir -p "$(dirname "$ocmixin")" if ! grep -q "torrserver" "$ocmixin" 2>/dev/null; then printf 'nft insert rule inet torrserver_bypass output socket cgroupv2 level 2 "services/torrserver" return 2>/dev/null || true\n' >> "$ocmixin" printf " - $(colorize green "[openclash]") custom firewall rule added\n" else printf " - $(colorize green "[openclash]") already configured\n" fi fi # homeproxy / passwall / shellcrash — nftables covers them for tool in homeproxy passwall passwall2; do if uci get ${tool}.config.enabled 2>/dev/null | grep -q "1" || \ /etc/init.d/$tool status 2>/dev/null | grep -q "running"; then printf " - $(colorize green "[$tool]") detected — covered by nftables rule\n" fi done [ -f "/etc/shellcrash/config.yaml" ] && \ printf " - $(colorize green "[shellcrash]") detected — covered by nftables rule\n" } removeProxyBypass() { if [ -f "$NFTFILE" ]; then nft delete table inet torrserver_bypass 2>/dev/null rm -f "$NFTFILE" printf " - nftables rule removed\n" fi if uci get nikki.@router_access_control[0].cgroup 2>/dev/null | grep -q "$BYPASS_CGROUP"; then uci del_list nikki.@router_access_control[0].cgroup="$BYPASS_CGROUP" 2>/dev/null uci commit nikki; /etc/init.d/nikki restart >/dev/null 2>&1 printf " - nikki UCI exception removed\n" fi if uci get podkop.main.excluded_processes 2>/dev/null | grep -q "torrserver"; then uci del_list podkop.main.excluded_processes="torrserver" 2>/dev/null uci commit podkop; /etc/init.d/podkop restart >/dev/null 2>&1 printf " - podkop UCI exception removed\n" fi } applyProxyBypass() { printf "\n" printf "=============================================================\n" if [ "$LANG_CODE" = "en" ]; then printf " Proxy Bypass Setup\n" else printf " Настройка прямого соединения\n" fi printf "=============================================================\n" printf "\n" # Ищем активные прокси local found found=$(detectProxyTools) if [ -n "$found" ]; then if [ "$LANG_CODE" = "en" ]; then printf " Detected proxy tools:$(colorize yellow "$found")\n" printf "\n" printf " $(colorize yellow "WARNING:") BitTorrent traffic through VPN violates most\n" printf " VPN providers ToS and may get your account banned.\n" printf "\n" printf " Solution: split-tunnel via Linux kernel cgroup.\n" printf " TorrServer traffic goes direct, everything else via VPN.\n" else printf " Обнаружены прокси-инструменты:$(colorize yellow "$found")\n" printf "\n" printf " $(colorize yellow "ВНИМАНИЕ:") BitTorrent через VPN нарушает правила большинства\n" printf " VPN-провайдеров и может привести к блокировке аккаунта.\n" printf "\n" printf " Решение: split-tunnel через cgroup ядра Linux.\n" printf " Трафик TorrServer пойдёт напрямую, остальное — через VPN.\n" fi else if [ "$LANG_CODE" = "en" ]; then printf " No active proxy tools detected.\n" printf "\n" printf " You can still configure bypass now — it will work\n" printf " automatically when any proxy tool is installed later.\n" else printf " Активные прокси-инструменты не обнаружены.\n" printf "\n" printf " Можно настроить bypass заранее — он автоматически\n" printf " сработает при установке любого прокси в будущем.\n" fi fi printf "\n" if [ "$LANG_CODE" = "en" ]; then printf " Configure direct connection for TorrServer? " else printf " Настроить прямое соединение для TorrServer? " fi t yes_no read -r ans /dev/null; then t main_version "$(getInstalledVersion)" if isRunning; then t main_running; else t main_stopped; fi fi printf "\n" t main_menu printf "\n" while true; do if [ "$LANG_CODE" = "en" ]; then printf " Choice: "; else printf " Выбор: "; fi read -r ydn