#!/bin/bash export LANG=ru_RU.UTF-8 # Цвета для вывода RED="\033[31m" GREEN="\033[32m" YELLOW="\033[33m" BLUE="\033[34m" PURPLE="\033[35m" CYAN="\033[36m" PLAIN="\033[0m" red(){ echo -e "\033[31m\033[01m$1\033[0m" } green(){ echo -e "\033[32m\033[01m$1\033[0m" } yellow(){ echo -e "\033[33m\033[01m$1\033[0m" } blue(){ echo -e "\033[34m\033[01m$1\033[0m" } purple(){ echo -e "\033[35m\033[01m$1\033[0m" } cyan(){ echo -e "\033[36m\033[01m$1\033[0m" } # Проверка прав root if [[ $EUID -ne 0 ]]; then red "Внимание: Запустите скрипт от имени root пользователя" exit 1 fi # Параметры по умолчанию CUSTOM_SNI="" CONFIG_DIR="/etc/hysteria" USERS_FILE="$CONFIG_DIR/users.txt" HYSTERIA_SERVICE="hysteria-server" # Обработка аргументов командной строки for arg in "$@"; do case $arg in --custom-sni=*) CUSTOM_SNI="${arg#*=}" shift ;; --help) echo "Использование: $0 [--custom-sni=example.com]" echo "" echo "Опции:" echo " --custom-sni=HOSTNAME Указать свой SNI хост (по умолчанию: web.max.ru)" echo " --help Показать эту справку" exit 0 ;; esac done # Определение ОС REGEX=("debian" "ubuntu" "centos|red hat|kernel|oracle linux|alma|rocky" "'amazon linux'" "fedora" "alpine") RELEASE=("Debian" "Ubuntu" "CentOS" "CentOS" "Fedora" "Alpine") PACKAGE_UPDATE=("apt-get update" "apt-get update" "yum -y update" "yum -y update" "yum -y update" "apk update -f") PACKAGE_INSTALL=("apt -y install" "apt -y install" "yum -y install" "yum -y install" "yum -y install" "apk add -f") CMD=("$(grep -i pretty_name /etc/os-release 2>/dev/null | cut -d \" -f2)" "$(hostnamectl 2>/dev/null | grep -i system | cut -d : -f2)" "$(lsb_release -sd 2>/dev/null)" "$(grep -i description /etc/lsb-release 2>/dev/null | cut -d \" -f2)" "$(grep . /etc/redhat-release 2>/dev/null)" "$(grep . /etc/issue 2>/dev/null | cut -d \\ -f1 | sed '/^[ ]*$/d')") for i in "${CMD[@]}"; do SYS="$i" && [[ -n $SYS ]] && break done for ((int = 0; int < ${#REGEX[@]}; int++)); do [[ $(echo "$SYS" | tr '[:upper:]' '[:lower:]') =~ ${REGEX[int]} ]] && SYSTEM="${RELEASE[int]}" && [[ -n $SYSTEM ]] && break done [[ -z $SYSTEM ]] && red "Текущая система VPS не поддерживается, используйте основную операционную систему" && exit 1 # Функция для установки необходимых пакетов install_dependencies() { if [[ -z $(type -P curl) ]]; then if [[ ! $SYSTEM == "CentOS" ]]; then ${PACKAGE_UPDATE[int]} fi ${PACKAGE_INSTALL[int]} curl fi if [[ -z $(type -P openssl) ]]; then ${PACKAGE_INSTALL[int]} openssl fi if [[ -z $(type -P qrencode) ]]; then ${PACKAGE_INSTALL[int]} qrencode fi if [[ -z $(type -P md5sum) ]]; then ${PACKAGE_INSTALL[int]} coreutils fi if [[ -z $(type -P file) ]]; then ${PACKAGE_INSTALL[int]} file fi if [[ -z $(type -P wget) ]]; then ${PACKAGE_INSTALL[int]} wget fi } # Функция для получения IP адреса get_ip() { local ip=$(curl -s4m8 ip.sb -k 2>/dev/null) || ip=$(curl -s6m8 ip.sb -k 2>/dev/null) echo "$ip" } # Функция для генерации пароля generate_password() { date +%s%N | md5sum | cut -c 1-16 } # Функция для получения obfs пароля из конфига get_obfs_password() { if [[ -f "$CONFIG_DIR/config.yaml" ]]; then # Ищем строку с password в секции obfs local obfs_pwd=$(grep -A 5 "obfs:" "$CONFIG_DIR/config.yaml" | grep "password:" | head -1 | awk -F'"' '{print $2}') if [[ -z "$obfs_pwd" ]]; then # Если не нашли в кавычках, пробуем без кавычек obfs_pwd=$(grep -A 5 "obfs:" "$CONFIG_DIR/config.yaml" | grep "password:" | head -1 | awk '{print $2}') fi echo "$obfs_pwd" else echo "" fi } # Функция для получения SNI из конфига get_sni() { local sni="" # Сначала проверяем CUSTOM_SNI из аргументов командной строки if [[ -n "$CUSTOM_SNI" ]]; then sni="$CUSTOM_SNI" # Затем пробуем получить из конфига elif [[ -f "$CONFIG_DIR/config.yaml" ]]; then sni=$(grep "url: https://" "$CONFIG_DIR/config.yaml" | sed 's|.*https://\([^/]*\).*|\1|') # Если не нашли через url, пробуем через CN в сертификате if [[ -z "$sni" ]] && [[ -f "$CONFIG_DIR/cert.crt" ]]; then sni=$(openssl x509 -in "$CONFIG_DIR/cert.crt" -noout -subject 2>/dev/null | grep -o 'CN = [^,]*' | cut -d= -f2 | tr -d ' ') fi fi # Если всё ещё пусто, используем значение по умолчанию if [[ -z "$sni" ]]; then sni="web.max.ru" fi echo "$sni" } # Функция для получения списка пользователей get_user_list() { if [[ ! -f "$USERS_FILE" ]]; then echo "" return fi local users=() while IFS=: read -r username password; do if [[ -n "$username" && "$username" != "_placeholder" ]]; then users+=("$username") fi done < "$USERS_FILE" echo "${users[@]}" } # Функция для выбора пользователя из списка select_user() { local action=$1 local users=($(get_user_list)) if [[ ${#users[@]} -eq 0 ]]; then red "Нет доступных пользователей!" return 1 fi echo cyan "=== Доступные пользователи ===" for i in "${!users[@]}"; do echo "$((i+1)). ${users[$i]}" done echo "0. Отмена" echo read -p "Выберите пользователя для $action (0-${#users[@]}): " choice if [[ "$choice" == "0" ]]; then yellow "Операция отменена" return 1 fi if [[ ! "$choice" =~ ^[0-9]+$ ]] || [[ "$choice" -lt 1 ]] || [[ "$choice" -gt ${#users[@]} ]]; then red "Неверный выбор!" return 1 fi local selected_user="${users[$((choice-1))]}" echo "$selected_user" return 0 } # Функция для получения пароля пользователя get_user_password() { local username=$1 if [[ ! -f "$USERS_FILE" ]]; then echo "" return 1 fi local password=$(grep "^$username:" "$USERS_FILE" | cut -d: -f2) echo "$password" } # Функция для генерации ссылки пользователя с поддержкой keepalive generate_user_link() { local username=$1 local password=$2 # Получаем все необходимые параметры local sni_host=$(get_sni) local server_ip=$(get_ip) local port="443" local obfs_pwd=$(get_obfs_password) # Проверяем, что все параметры не пустые if [[ -z "$password" ]]; then red "ОШИБКА: Пароль пустой!" return 1 fi if [[ -z "$sni_host" ]]; then yellow "Предупреждение: SNI пустой, используем значение по умолчанию" sni_host="web.max.ru" fi if [[ -z "$obfs_pwd" ]]; then yellow "Предупреждение: OBFS пароль пустой, используем значение по умолчанию" obfs_pwd="unknown" fi # Кодируем username для URL (заменяем пробелы и спецсимволы) local encoded_username=$(echo "$username" | sed 's/ /%20/g') # Формируем ссылку с параметрами keepalive # Параметры: # - mport=443 - множественные порты # - security=tls - использование TLS # - sni=$sni_host - SNI хост # - allowInsecure=true - разрешить небезопасные сертификаты (для самоподписанных) # - alpn=h3 - протокол ALPN (h3 для HTTP/3) # - obfs=salamander - тип обфускации # - obfs-password=$obfs_pwd - пароль обфускации # - keepalive=30 - интервал keepalive в секундах # - keepaliveTimeout=10 - таймаут keepalive # - idleTimeout=60 - таймаут бездействия local user_link="hy2://$password@$server_ip:$port?mport=443&security=tls&sni=$sni_host&allowInsecure=true&alpn=h3&obfs=salamander&obfs-password=$obfs_pwd&keepalive=30&keepaliveTimeout=10&idleTimeout=60#$encoded_username" echo "$user_link" } # Функция для сохранения ссылки пользователя в файл save_user_link() { local username=$1 local password=$2 if [[ -z "$username" ]]; then red "ОШИБКА: Имя пользователя не может быть пустым!" return 1 fi local user_link=$(generate_user_link "$username" "$password") # Проверяем, что ссылка сгенерировалась if [[ -z "$user_link" ]]; then red "ОШИБКА: Не удалось сгенерировать ссылку" return 1 fi # Сохраняем в файл local filename="/root/hysteria2_${username}.txt" echo "$user_link" > "$filename" # Проверяем, что файл создан if [[ -f "$filename" ]]; then green "✓ Ссылка сохранена в $filename" else red "✗ Ошибка при сохранении файла" fi echo "$user_link" } # Функция для обновления конфигурации Hysteria с пользователями update_hysteria_config() { if [[ ! -f "$CONFIG_DIR/config.yaml" ]]; then red "Конфигурационный файл не найден!" return 1 fi # Проверяем существование файла пользователей if [[ ! -f "$USERS_FILE" ]]; then yellow "Файл пользователей не найден, создаем пустой" touch "$USERS_FILE" fi # Создаем временный файл с секцией auth local temp_auth=$(mktemp) local temp_config=$(mktemp) # Формируем полную секцию auth с type: userpass и пользователями { echo "auth:" echo " type: userpass" echo " userpass:" # Читаем пользователей из файла local user_found=0 if [[ -s "$USERS_FILE" ]]; then while IFS=: read -r username password; do # Пропускаем пустые строки и заглушки if [[ -n "$username" && "$username" != "_placeholder" && -n "$password" ]]; then echo " $username: \"$password\"" user_found=1 fi done < "$USERS_FILE" fi # Если нет пользователей, добавляем заглушку if [[ $user_found -eq 0 ]]; then echo " _placeholder: \"_placeholder\"" fi } > "$temp_auth" # Создаем резервную копию cp "$CONFIG_DIR/config.yaml" "$CONFIG_DIR/config.yaml.backup" # Обрабатываем конфиг построчно, заменяя секцию auth local in_auth_section=0 local auth_replaced=0 while IFS= read -r line; do if [[ $in_auth_section -eq 0 && "$line" =~ ^[[:space:]]*auth: ]]; then # Нашли старую секцию auth, пропускаем её и все вложенные строки in_auth_section=1 # Вставим новую секцию auth позже elif [[ $in_auth_section -eq 1 ]]; then # Проверяем, не закончилась ли секция auth (новая секция на том же уровне) if [[ "$line" =~ ^[[:space:]]*[a-z] && ! "$line" =~ ^[[:space:]]+ ]]; then # Секция auth закончилась, вставляем новую перед этой строкой cat "$temp_auth" echo "$line" in_auth_section=0 auth_replaced=1 fi # Пропускаем все строки внутри старой секции auth else # Вне секции auth, просто выводим строку echo "$line" fi done < "$CONFIG_DIR/config.yaml" > "$temp_config" # Если мы дошли до конца файла и не вставили новую секцию if [[ $in_auth_section -eq 1 && $auth_replaced -eq 0 ]]; then cat "$temp_auth" >> "$temp_config" fi # Заменяем конфиг mv "$temp_config" "$CONFIG_DIR/config.yaml" rm -f "$temp_auth" # Проверяем валидность конфига yellow "Проверка конфигурации..." if /usr/local/bin/hysteria server --config "$CONFIG_DIR/config.yaml" --disable-update-check &>/dev/null <<< "q"; then green "✓ Конфигурация валидна" else red "✗ Ошибка в конфигурации!" echo yellow "Содержимое конфигурации:" cat "$CONFIG_DIR/config.yaml" echo yellow "Восстанавливаем резервную копию..." cp "$CONFIG_DIR/config.yaml.backup" "$CONFIG_DIR/config.yaml" return 1 fi # Перезапускаем сервис yellow "Перезапуск сервиса Hysteria..." systemctl restart "$HYSTERIA_SERVICE" sleep 3 if systemctl is-active "$HYSTERIA_SERVICE" > /dev/null; then green "✓ Сервис успешно перезапущен" else red "✗ Ошибка при перезапуске сервиса" yellow "Логи ошибки:" journalctl -u "$HYSTERIA_SERVICE" -n 20 --no-pager echo yellow "Восстанавливаем резервную копию..." cp "$CONFIG_DIR/config.yaml.backup" "$CONFIG_DIR/config.yaml" systemctl restart "$HYSTERIA_SERVICE" return 1 fi return 0 } # Функция проверки конфигурации check_config() { if [[ ! -f "$CONFIG_DIR/config.yaml" ]]; then red "Конфигурационный файл не найден!" return 1 fi yellow "Проверка конфигурации..." echo "----------------------------------------" # Проверяем наличие паролей if grep -q "password:" "$CONFIG_DIR/config.yaml"; then green "✓ Конфигурация содержит пароли" else red "✗ В конфигурации отсутствуют пароли" fi # Проверяем наличие сертификатов if [[ -f "$CONFIG_DIR/cert.crt" ]] && [[ -f "$CONFIG_DIR/private.key" ]]; then green "✓ SSL сертификаты найдены" else red "✗ SSL сертификаты не найдены" fi # Проверяем права на сертификаты if [[ -r "$CONFIG_DIR/cert.crt" ]] && [[ -r "$CONFIG_DIR/private.key" ]]; then green "✓ Права на сертификаты корректны" else red "✗ Проблемы с правами на сертификаты" fi # Проверяем синтаксис конфигурации if [[ -f /usr/local/bin/hysteria ]]; then if /usr/local/bin/hysteria server --config "$CONFIG_DIR/config.yaml" --disable-update-check &>/dev/null <<< "q"; then green "✓ Конфигурация валидна" else red "✗ Ошибка в конфигурации" fi fi echo "----------------------------------------" } # Функция установки Hysteria2 install_hysteria() { yellow "Начинаем установку Hysteria2..." # Проверяем, установлен ли уже Hysteria if [[ -f /usr/local/bin/hysteria ]]; then red "Hysteria2 уже установлен!" read -p "Хотите переустановить? (y/n): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then return fi uninstall_hysteria fi install_dependencies # Определяем архитектуру yellow "Определение архитектуры системы..." case "$(uname -m)" in 'i386' | 'i686') ARCH='386' ;; 'amd64' | 'x86_64') ARCH='amd64' ;; 'armv5tel' | 'armv6l' | 'armv7' | 'armv7l') ARCH='arm' ;; 'armv8' | 'aarch64') ARCH='arm64' ;; 'mips' | 'mipsle' | 'mips64' | 'mips64le') ARCH='mipsle' ;; 's390x') ARCH='s390x' ;; *) red "Архитектура не поддерживается!" exit 1 ;; esac green "Архитектура системы: $ARCH" # Скачиваем последнюю версию green "Загружаем последнюю версию Hysteria2..." # Пробуем получить версию через GitHub API LATEST_VERSION=$(curl -s https://api.github.com/repos/apernet/hysteria/releases/latest | grep -o '"tag_name": "v[^"]*"' | cut -d'"' -f4) if [[ -z "$LATEST_VERSION" ]]; then yellow "Не удалось получить версию через API, пробуем альтернативный метод..." # Альтернативный метод - используем фиксированную последнюю версию LATEST_VERSION="v2.4.3" fi green "Последняя версия: $LATEST_VERSION" # Формируем URL для скачивания DOWNLOAD_URL="https://github.com/apernet/hysteria/releases/download/app%2F$LATEST_VERSION/hysteria-linux-$ARCH" yellow "Скачиваем с: $DOWNLOAD_URL" # Скачиваем с проверкой if ! curl -L -o /usr/local/bin/hysteria "$DOWNLOAD_URL"; then red "Ошибка при скачивании! Пробуем альтернативный URL..." # Альтернативный URL DOWNLOAD_URL="https://github.com/apernet/hysteria/releases/download/$LATEST_VERSION/hysteria-linux-$ARCH" if ! curl -L -o /usr/local/bin/hysteria "$DOWNLOAD_URL"; then red "Не удалось скачать Hysteria2. Проверьте соединение." exit 1 fi fi # Устанавливаем правильные права chmod 755 /usr/local/bin/hysteria # Проверяем, что файл действительно исполняемый if [[ ! -x /usr/local/bin/hysteria ]]; then red "Не удалось установить права на выполнение" exit 1 fi # Проверяем, что файл не пустой if [[ -f /usr/local/bin/hysteria ]]; then local file_size=$(stat -c%s /usr/local/bin/hysteria 2>/dev/null || stat -f%z /usr/local/bin/hysteria 2>/dev/null) if [[ -n "$file_size" && "$file_size" -lt 1000000 ]]; then yellow "Предупреждение: Скачанный файл маловат ($(($file_size/1024)) KB). Продолжаем..." else green "Бинарный файл успешно загружен ($(($file_size/1024/1024)) MB)" fi fi # Создаем директорию для конфигурации mkdir -p "$CONFIG_DIR" # Настройка Hysteria configure_hysteria # Создаем systemd сервис cat << EOF > /etc/systemd/system/hysteria-server.service [Unit] Description=Hysteria Server Service After=network.target [Service] Type=simple ExecStart=/usr/local/bin/hysteria server --config /etc/hysteria/config.yaml WorkingDirectory=/etc/hysteria User=root Group=root Environment=HYSTERIA_LOG_LEVEL=info CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW NoNewPrivileges=true Restart=always RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF # Перезагружаем systemd systemctl daemon-reload # Проверяем бинарный файл перед запуском yellow "Проверка бинарного файла..." if /usr/local/bin/hysteria version &>/dev/null; then green "✓ Бинарный файл работает корректно" else red "✗ Бинарный файл не работает. Проверьте архитектуру." file /usr/local/bin/hysteria exit 1 fi # Включаем и запускаем сервис systemctl enable hysteria-server # Пробуем запустить systemctl start hysteria-server # Проверяем статус sleep 3 if systemctl is-active hysteria-server > /dev/null; then green "✓ Hysteria2 успешно установлен и запущен!" # Создаем первого пользователя echo yellow "Создаем первого пользователя..." local admin_user="user1" local admin_pass=$(generate_password) echo "$admin_user:$admin_pass" > "$USERS_FILE" # Обновляем конфиг update_hysteria_config # Сохраняем ссылку local user_link=$(save_user_link "$admin_user" "$admin_pass") green "Первый пользователь создан:" echo "======================================================" echo "Имя пользователя: $admin_user" echo "Пароль: $admin_pass" echo "Ссылка для подключения:" echo "$user_link" echo "======================================================" if command -v qrencode &> /dev/null; then echo "QR код для подключения:" qrencode -t ANSIUTF8 "$user_link" fi echo yellow "Файл со ссылкой сохранен: /root/hysteria2_${admin_user}.txt" else red "✗ Ошибка запуска Hysteria-server" echo yellow "Диагностическая информация:" echo "======================================================" # Проверяем наличие файлов echo "Проверка файлов:" ls -la /usr/local/bin/hysteria 2>/dev/null || echo "Бинарный файл не найден" ls -la /etc/hysteria/config.yaml 2>/dev/null || echo "Конфиг не найден" echo # Проверяем конфигурацию echo "Содержимое конфигурации:" cat /etc/hysteria/config.yaml 2>/dev/null || echo "Не удалось прочитать конфиг" echo # Проверяем журнал echo "Последние строки журнала:" journalctl -u hysteria-server -n 20 --no-pager echo # Проверяем статус сервиса systemctl status hysteria-server --no-pager fi } # Функция настройки Hysteria с правильной структурой auth и keepalive configure_hysteria() { yellow "Настройка сервера Hysteria2..." if [[ -n "$CUSTOM_SNI" ]]; then local sni_host="$CUSTOM_SNI" yellow "Используется кастомный SNI: $sni_host" else local sni_host="web.max.ru" yellow "Используется SNI по умолчанию: $sni_host" fi local masquerade_url="$sni_host" local port="443" local obfs_pwd=$(generate_password) # Проверяем наличие openssl if ! command -v openssl &> /dev/null; then red "openssl не найден, устанавливаем..." ${PACKAGE_INSTALL[int]} openssl fi # Генерируем SSL сертификат yellow "Генерация SSL сертификата..." # Пробуем EC ключ if ! openssl ecparam -genkey -name prime256v1 -out "$CONFIG_DIR/private.key" 2>/dev/null; then yellow "EC ключ не поддерживается, используем RSA..." openssl genrsa -out "$CONFIG_DIR/private.key" 2048 fi if ! openssl req -new -x509 -days 36500 -key "$CONFIG_DIR/private.key" -out "$CONFIG_DIR/cert.crt" -subj "/CN=$sni_host" 2>/dev/null; then red "Ошибка генерации сертификата" exit 1 fi chmod 600 "$CONFIG_DIR/cert.crt" chmod 600 "$CONFIG_DIR/private.key" green "✓ SSL сертификат создан" # Создаем базовую конфигурацию с правильной структурой auth и параметрами keepalive yellow "Создание конфигурации..." cat << EOF > "$CONFIG_DIR/config.yaml" # Hysteria2 конфигурация listen: :$port # TLS конфигурация tls: cert: $CONFIG_DIR/cert.crt key: $CONFIG_DIR/private.key # Обфускация obfs: type: salamander salamander: password: "$obfs_pwd" # Аутентификация auth: type: userpass userpass: _placeholder: "_placeholder" # Маскировка трафика masquerade: type: proxy proxy: url: "https://$masquerade_url" rewriteHost: true # Настройки QUIC quic: initStreamReceiveWindow: 8388608 maxStreamReceiveWindow: 8388608 initConnReceiveWindow: 20971520 maxConnReceiveWindow: 20971520 maxIdleTimeout: 60s keepAlivePeriod: 30s handshakeTimeout: 8s disablePathMTUDiscovery: false # Ограничения пропускной способности bandwidth: up: 100 mbps down: 100 mbps # Настройки UDP udp: idleTimeout: 60s forwarder: worker: 8 bufferSize: 64 EOF # Проверяем, что конфигурация создана if [[ -f "$CONFIG_DIR/config.yaml" ]]; then green "✓ Базовая конфигурация создана" else red "✗ Ошибка создания конфигурации!" exit 1 fi } # Функция добавления пользователя add_user() { if [[ ! -f /usr/local/bin/hysteria ]]; then red "Hysteria2 не установлен! Сначала выполните установку." return fi echo cyan "=== Добавление нового пользователя ===" echo read -p "Введите имя пользователя: " username if [[ -z "$username" ]]; then red "Имя пользователя не может быть пустым!" return fi # Проверяем, существует ли уже пользователь if [[ -f "$USERS_FILE" ]] && grep -q "^$username:" "$USERS_FILE"; then red "Пользователь $username уже существует!" return fi read -p "Введите пароль (оставьте пустым для генерации): " password if [[ -z "$password" ]]; then password=$(generate_password) green "✓ Сгенерирован пароль: $password" fi # Добавляем пользователя в файл echo "$username:$password" >> "$USERS_FILE" green "✓ Пользователь добавлен в файл пользователей" # Обновляем конфиг Hysteria if update_hysteria_config; then green "✓ Конфигурация Hysteria обновлена" # Сохраняем ссылку echo yellow "Генерация ссылки для пользователя $username..." local user_link=$(save_user_link "$username" "$password") if [[ -n "$user_link" ]]; then echo green "✅ Пользователь $username успешно создан!" echo "======================================================" echo "Имя пользователя: $username" echo "Пароль: $password" echo "======================================================" echo "Ссылка для подключения:" echo "$user_link" echo "======================================================" echo "Ссылка также сохранена в /root/hysteria2_${username}.txt" echo if command -v qrencode &> /dev/null; then echo "QR код для подключения:" qrencode -t ANSIUTF8 "$user_link" fi else red "✗ Ошибка при генерации ссылки" fi else red "✗ Ошибка при обновлении конфигурации Hysteria" # Удаляем пользователя из файла в случае ошибки sed -i "/^$username:/d" "$USERS_FILE" fi } # Функция удаления пользователя delete_user() { if [[ ! -f "$USERS_FILE" ]]; then red "Нет пользователей для удаления" return fi echo cyan "=== Удаление пользователя ===" echo # Получаем список пользователей local users=($(get_user_list)) if [[ ${#users[@]} -eq 0 ]]; then red "Нет доступных пользователей для удаления!" return fi # Показываем список пользователей echo "Доступные пользователи:" for i in "${!users[@]}"; do echo "$((i+1)). ${users[$i]}" done echo "0. Отмена" echo read -p "Выберите пользователя для удаления (0-${#users[@]}): " choice if [[ "$choice" == "0" ]]; then yellow "Удаление отменено" return fi if [[ ! "$choice" =~ ^[0-9]+$ ]] || [[ "$choice" -lt 1 ]] || [[ "$choice" -gt ${#users[@]} ]]; then red "Неверный выбор!" return fi local username="${users[$((choice-1))]}" # Подтверждение echo read -p "Вы уверены, что хотите удалить пользователя $username? (y/n): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then yellow "Удаление отменено" return fi # Создаем резервную копию файла пользователей cp "$USERS_FILE" "$USERS_FILE.backup" # Удаляем пользователя sed -i "/^$username:/d" "$USERS_FILE" green "✓ Пользователь удален из файла пользователей" # Удаляем файл со ссылкой если существует if [[ -f "/root/hysteria2_${username}.txt" ]]; then rm "/root/hysteria2_${username}.txt" green "✓ Файл со ссылкой удален" fi # Обновляем конфиг if update_hysteria_config; then green "✅ Пользователь $username успешно удален!" else red "✗ Ошибка при обновлении конфигурации" # Восстанавливаем из резервной копии mv "$USERS_FILE.backup" "$USERS_FILE" yellow "Файл пользователей восстановлен из резервной копии" fi # Удаляем резервную копию если она есть [[ -f "$USERS_FILE.backup" ]] && rm "$USERS_FILE.backup" } # Функция показа списка пользователей list_users() { if [[ ! -f "$USERS_FILE" ]]; then yellow "Нет пользователей" return fi echo cyan "=== Список пользователей Hysteria2 ===" echo "----------------------------------------" printf "%-5s | %-20s | %-30s | %s\n" "№" "Имя пользователя" "Пароль" "Файл ссылки" echo "----------------------------------------" local count=0 while IFS=: read -r username password; do if [[ -n "$username" && "$username" != "_placeholder" ]]; then ((count++)) link_file="/root/hysteria2_${username}.txt" if [[ -f "$link_file" ]]; then file_status="✓ есть" else file_status="✗ нет" fi printf "%-5s | %-20s | %-30s | %s\n" "$count" "$username" "$password" "$file_status" fi done < "$USERS_FILE" echo "----------------------------------------" green "Всего пользователей: $count" echo } # Функция показа ссылки для конкретного пользователя show_user_link() { if [[ ! -f "$USERS_FILE" ]]; then red "Нет пользователей" return fi echo cyan "=== Показать ссылку для пользователя ===" echo local selected_user=$(select_user "показа ссылки") if [[ -z "$selected_user" ]]; then return fi local password=$(get_user_password "$selected_user") if [[ -z "$password" ]]; then red "Не удалось получить пароль для пользователя $selected_user" return fi local user_link=$(generate_user_link "$selected_user" "$password") echo green "Ссылка для пользователя $selected_user:" echo "======================================================" echo "$user_link" echo "======================================================" echo if command -v qrencode &> /dev/null; then echo "QR код для подключения:" qrencode -t ANSIUTF8 "$user_link" fi } # Функция показа QR-кода для конкретного пользователя show_user_qrcode() { if [[ ! -f "$USERS_FILE" ]]; then red "Нет пользователей" return fi if ! command -v qrencode &> /dev/null; then red "qrencode не установлен" ${PACKAGE_INSTALL[int]} qrencode fi echo cyan "=== Показать QR-код для пользователя ===" echo local selected_user=$(select_user "показа QR-кода") if [[ -z "$selected_user" ]]; then return fi local password=$(get_user_password "$selected_user") if [[ -z "$password" ]]; then red "Не удалось получить пароль для пользователя $selected_user" return fi local user_link=$(generate_user_link "$selected_user" "$password") echo green "QR-код для пользователя $selected_user:" echo "======================================================" qrencode -t ANSIUTF8 "$user_link" echo "======================================================" } # Функция показа всех ссылок show_all_links() { if [[ ! -f "$USERS_FILE" ]]; then red "Нет пользователей" return fi echo cyan "=== Все ссылки для подключения ===" echo local count=0 while IFS=: read -r username password; do if [[ -n "$username" && "$username" != "_placeholder" ]]; then echo "----------------------------------------" yellow "Пользователь: $username" local user_link=$(generate_user_link "$username" "$password") echo "$user_link" echo ((count++)) fi done < "$USERS_FILE" if [[ $count -eq 0 ]]; then yellow "Нет пользователей для отображения" else green "Всего пользователей: $count" fi } # Функция показа всех QR-кодов show_all_qrcodes() { if [[ ! -f "$USERS_FILE" ]]; then red "Нет пользователей" return fi if ! command -v qrencode &> /dev/null; then red "qrencode не установлен" ${PACKAGE_INSTALL[int]} qrencode fi echo cyan "=== Все QR-коды для подключения ===" echo local count=0 while IFS=: read -r username password; do if [[ -n "$username" && "$username" != "_placeholder" ]]; then echo "----------------------------------------" yellow "Пользователь: $username" local user_link=$(generate_user_link "$username" "$password") qrencode -t ANSIUTF8 "$user_link" echo ((count++)) fi done < "$USERS_FILE" if [[ $count -eq 0 ]]; then yellow "Нет пользователей для отображения" else green "Всего пользователей: $count" fi } # Функция проверки статуса check_status() { echo cyan "=== Статус Hysteria2 ===" echo if [[ -f /usr/local/bin/hysteria ]]; then green "✓ Hysteria2 установлен" if systemctl is-active hysteria-server > /dev/null; then green "✓ Сервис запущен" else red "✗ Сервис остановлен" fi if systemctl is-enabled hysteria-server > /dev/null 2>&1; then green "✓ Сервис добавлен в автозагрузку" else yellow "✗ Сервис не в автозагрузке" fi # Показываем версию local version=$(/usr/local/bin/hysteria version 2>/dev/null | head -1) if [[ -n "$version" ]]; then green "Версия: $version" fi # Показываем основную информацию echo cyan "Информация о сервере:" echo "----------------------------------------" echo "IP адрес: $(get_ip)" echo "SNI: $(get_sni)" echo "Порт: 443" echo "OBFS пароль: $(get_obfs_password)" echo "Количество пользователей: $(grep -c "^[^_]" "$USERS_FILE" 2>/dev/null || echo 0)" echo "----------------------------------------" # Проверяем конфигурацию check_config else red "✗ Hysteria2 не установлен" fi } # Функция перезапуска сервиса restart_service() { if [[ ! -f /usr/local/bin/hysteria ]]; then red "Hysteria2 не установлен!" return fi yellow "Перезапуск сервиса Hysteria2..." systemctl restart hysteria-server sleep 2 if systemctl is-active hysteria-server > /dev/null; then green "✓ Сервис успешно перезапущен" else red "✗ Ошибка при перезапуске сервиса" systemctl status hysteria-server --no-pager fi } # Функция просмотра логов view_logs() { if [[ ! -f /usr/local/bin/hysteria ]]; then red "Hysteria2 не установлен!" return fi yellow "Просмотр логов Hysteria2 (Ctrl+C для выхода):" journalctl -u hysteria-server -f -n 50 } # Функция полного удаления Hysteria2 uninstall_hysteria() { echo cyan "=== Удаление Hysteria2 ===" echo if [[ ! -f /usr/local/bin/hysteria ]]; then red "Hysteria2 не установлен!" return fi # Подтверждение read -p "Вы уверены, что хотите полностью удалить Hysteria2? (y/n): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then yellow "Удаление отменено" return fi yellow "Останавливаем сервис..." systemctl stop hysteria-server systemctl disable hysteria-server yellow "Удаляем файлы..." rm -f /usr/local/bin/hysteria rm -f /etc/systemd/system/hysteria-server.service systemctl daemon-reload read -p "Удалить конфигурационные файлы и данные пользователей? (y/n): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then rm -rf "$CONFIG_DIR" rm -f /root/hysteria2_*.txt green "Конфигурация и данные пользователей удалены" else green "Конфигурация сохранена в $CONFIG_DIR" fi green "Hysteria2 успешно удален!" } # Функция резервного копирования backup_config() { if [[ ! -f "$CONFIG_DIR/config.yaml" ]]; then red "Конфигурация не найдена!" return fi local backup_dir="/root/hysteria_backup_$(date +%Y%m%d_%H%M%S)" mkdir -p "$backup_dir" cp -r "$CONFIG_DIR" "$backup_dir/" if [[ -f "$USERS_FILE" ]]; then cp "$USERS_FILE" "$backup_dir/" fi # Сохраняем также все файлы со ссылками cp /root/hysteria2_*.txt "$backup_dir/" 2>/dev/null green "Резервная копия создана в: $backup_dir" echo "Размер: $(du -sh $backup_dir | cut -f1)" } # Функция восстановления из резервной копии restore_backup() { echo cyan "=== Восстановление из резервной копии ===" echo local backups=($(ls -d /root/hysteria_backup_* 2>/dev/null)) if [[ ${#backups[@]} -eq 0 ]]; then red "Резервные копии не найдены" return fi echo "Доступные резервные копии:" for i in "${!backups[@]}"; do echo "$((i+1)). ${backups[$i]}" done echo read -p "Выберите номер резервной копии для восстановления: " choice if [[ ! "$choice" =~ ^[0-9]+$ ]] || [[ "$choice" -lt 1 ]] || [[ "$choice" -gt ${#backups[@]} ]]; then red "Неверный выбор" return fi local selected_backup="${backups[$((choice-1))]}" # Проверяем наличие hysteria if [[ ! -f /usr/local/bin/hysteria ]]; then yellow "Hysteria2 не установлен. Сначала установите его." return fi # Останавливаем сервис yellow "Останавливаем сервис..." systemctl stop hysteria-server # Восстанавливаем файлы yellow "Восстанавливаем файлы из $selected_backup..." if [[ -d "$selected_backup/hysteria" ]]; then cp -r "$selected_backup/hysteria/"* "$CONFIG_DIR/" 2>/dev/null fi if [[ -f "$selected_backup/users.txt" ]]; then cp "$selected_backup/users.txt" "$USERS_FILE" fi # Восстанавливаем файлы со ссылками cp "$selected_backup"/hysteria2_*.txt /root/ 2>/dev/null # Запускаем сервис yellow "Запускаем сервис..." systemctl start hysteria-server sleep 2 if systemctl is-active hysteria-server > /dev/null; then green "✓ Восстановление завершено успешно!" else red "✗ Сервис не запустился после восстановления" fi } # Функция изменения SNI change_sni() { if [[ ! -f "$CONFIG_DIR/config.yaml" ]]; then red "Конфигурация не найдена!" return fi echo cyan "=== Изменение SNI ===" echo local current_sni=$(get_sni) yellow "Текущий SNI: $current_sni" read -p "Введите новый SNI (например: web.max.ru): " new_sni if [[ -z "$new_sni" ]]; then red "SNI не может быть пустым" return fi # Останавливаем сервис systemctl stop hysteria-server # Обновляем конфиг sed -i "s|url: https://[^/]*|url: https://$new_sni|" "$CONFIG_DIR/config.yaml" # Обновляем сертификат yellow "Обновление SSL сертификата для нового SNI..." openssl req -new -x509 -days 36500 -key "$CONFIG_DIR/private.key" -out "$CONFIG_DIR/cert.crt" -subj "/CN=$new_sni" # Запускаем сервис systemctl start hysteria-server sleep 2 if systemctl is-active hysteria-server > /dev/null; then green "✓ SNI изменен на: $new_sni" # Обновляем все файлы со ссылками if [[ -f "$USERS_FILE" ]]; then yellow "Обновление файлов со ссылками..." while IFS=: read -r username password; do if [[ -n "$username" && "$username" != "_placeholder" ]]; then save_user_link "$username" "$password" > /dev/null fi done < "$USERS_FILE" green "✓ Файлы со ссылками обновлены" fi else red "✗ Сервис не запустился после изменения SNI" fi } # Функция диагностики конфигурации diagnose_config() { echo cyan "=== Диагностика конфигурации Hysteria ===" echo if [[ ! -f "$CONFIG_DIR/config.yaml" ]]; then red "Конфигурационный файл не найден!" return fi echo "1. Проверка синтаксиса YAML:" if /usr/local/bin/hysteria server --config "$CONFIG_DIR/config.yaml" --disable-update-check &>/dev/null <<< "q"; then green " ✓ YAML синтаксис корректен" else red " ✗ Ошибка в YAML синтаксисе" fi echo echo "2. Содержимое конфигурации:" echo "----------------------------------------" cat "$CONFIG_DIR/config.yaml" echo "----------------------------------------" echo echo "3. Проверка секции auth:" if grep -q "^auth:" "$CONFIG_DIR/config.yaml"; then green " ✓ Секция auth найдена" # Проверяем наличие type: userpass if grep -A 5 "^auth:" "$CONFIG_DIR/config.yaml" | grep -q "type: userpass"; then green " ✓ type: userpass найден" else red " ✗ type: userpass не найден!" fi # Проверяем наличие userpass секции if grep -A 10 "^auth:" "$CONFIG_DIR/config.yaml" | grep -q "userpass:"; then green " ✓ Секция userpass найдена" # Подсчитываем количество пользователей local user_count=$(grep -A 20 "^auth:" "$CONFIG_DIR/config.yaml" | grep -E "^ [a-zA-Z0-9]+:" | wc -l) echo " Количество пользователей в конфиге: $user_count" if [[ $user_count -gt 0 ]]; then echo " Пользователи в конфиге:" grep -A 20 "^auth:" "$CONFIG_DIR/config.yaml" | grep -E "^ [a-zA-Z0-9]+:" | sed 's/^/ /' fi else red " ✗ Секция userpass не найдена" fi else red " ✗ Секция auth не найдена" fi echo echo "4. Проверка параметров keepalive в конфиге:" if grep -q "keepAlivePeriod:" "$CONFIG_DIR/config.yaml"; then local keepalive=$(grep "keepAlivePeriod:" "$CONFIG_DIR/config.yaml" | awk '{print $2}') green " ✓ keepAlivePeriod: $keepalive" else yellow " ⚠ keepAlivePeriod не найден (будет использовано значение по умолчанию)" fi if grep -q "maxIdleTimeout:" "$CONFIG_DIR/config.yaml"; then local idle=$(grep "maxIdleTimeout:" "$CONFIG_DIR/config.yaml" | awk '{print $2}') green " ✓ maxIdleTimeout: $idle" else yellow " ⚠ maxIdleTimeout не найден" fi echo echo "5. Проверка файла пользователей ($USERS_FILE):" if [[ -f "$USERS_FILE" ]]; then green " ✓ Файл пользователей существует" local file_user_count=$(grep -c "^[^_]" "$USERS_FILE" 2>/dev/null || echo 0) echo " Количество пользователей в файле: $file_user_count" if [[ $file_user_count -gt 0 ]]; then echo " Пользователи в файле:" cat "$USERS_FILE" | while IFS=: read -r u p; do if [[ -n "$u" && "$u" != "_placeholder" ]]; then echo " $u" fi done fi else red " ✗ Файл пользователей не найден" fi echo echo "6. Проверка сертификатов:" if [[ -f "$CONFIG_DIR/cert.crt" ]] && [[ -f "$CONFIG_DIR/private.key" ]]; then green " ✓ Сертификаты найдены" # Проверяем срок действия сертификата if command -v openssl &> /dev/null; then local cert_expiry=$(openssl x509 -enddate -noout -in "$CONFIG_DIR/cert.crt" 2>/dev/null | cut -d= -f2) echo " Срок действия до: $cert_expiry" fi else red " ✗ Сертификаты не найдены" fi echo echo "7. Проверка сервиса:" if systemctl is-active "$HYSTERIA_SERVICE" > /dev/null; then green " ✓ Сервис запущен" else red " ✗ Сервис остановлен" echo " Последние логи:" journalctl -u "$HYSTERIA_SERVICE" -n 10 --no-pager | sed 's/^/ /' fi } # Функция показа примера ссылки с keepalive show_link_example() { echo cyan "=== Пример ссылки с параметрами keepalive ===" echo echo "Формат ссылки:" echo "hy2://password@server:port?mport=443&security=tls&sni=domain.com&allowInsecure=true&alpn=h3&obfs=salamander&obfs-password=obfs_pwd&keepalive=30&keepaliveTimeout=10&idleTimeout=60#username" echo echo "Параметры keepalive:" echo " keepalive=30 - интервал отправки keepalive пакетов (секунды)" echo " keepaliveTimeout=10 - таймаут ожидания ответа на keepalive" echo " idleTimeout=60 - таймаут бездействия соединения" echo } # Функция отображения меню show_menu() { clear echo "======================================================" blue " Hysteria2 Management Script" echo "======================================================" echo if [[ -f /usr/local/bin/hysteria ]]; then green "✓ Hysteria2 установлен" if systemctl is-active hysteria-server > /dev/null; then green "✓ Сервис запущен" else red "✗ Сервис остановлен" fi else red "✗ Hysteria2 не установлен" fi # Показываем SNI если установлен if [[ -f "$CONFIG_DIR/config.yaml" ]]; then echo "SNI: $(get_sni)" fi # Показываем количество пользователей if [[ -f "$USERS_FILE" ]]; then local user_count=$(grep -c "^[^_]" "$USERS_FILE" 2>/dev/null || echo 0) echo "Пользователей: $user_count" fi echo cyan "Основные операции:" echo "1) 📦 Установить Hysteria2" echo "2) 🗑️ Полностью удалить Hysteria2" echo cyan "Управление пользователями:" echo "3) ➕ Добавить пользователя" echo "4) ❌ Удалить пользователя (с выбором)" echo "5) 📋 Список всех пользователей" echo "6) 🔗 Показать ссылку (с выбором пользователя)" echo "7) 📱 Показать QR-код (с выбором пользователя)" echo "8) 🔗 Показать ВСЕ ссылки" echo "9) 📱 Показать ВСЕ QR-коды" echo cyan "Управление сервисом:" echo "10) 🔄 Перезапустить сервис" echo "11) 📊 Статус и информация" echo "12) 📜 Просмотр логов" echo cyan "Диагностика и дополнения:" echo "13) 🔍 Диагностика конфигурации" echo "14) 💾 Создать резервную копию" echo "15) 🔄 Восстановить из резервной копии" echo "16) 🌐 Изменить SNI" echo "17) 📚 Показать пример ссылки с keepalive" echo cyan "Прочее:" echo "0) 🚪 Выход" echo echo "======================================================" } # Основной цикл программы main() { while true; do show_menu read -p "Выберите действие (0-17): " choice echo case $choice in 1) install_hysteria ;; 2) uninstall_hysteria ;; 3) add_user ;; 4) delete_user ;; 5) list_users ;; 6) show_user_link ;; 7) show_user_qrcode ;; 8) show_all_links ;; 9) show_all_qrcodes ;; 10) restart_service ;; 11) check_status ;; 12) view_logs ;; 13) diagnose_config ;; 14) backup_config ;; 15) restore_backup ;; 16) change_sni ;; 17) show_link_example ;; 0) green "Выход..." exit 0 ;; *) red "Неверный выбор! Пожалуйста, выберите 0-17" ;; esac echo read -p "Нажмите Enter для продолжения..." done } # Запуск основной программы main