diff --git a/hysteria2.sh b/hysteria2.sh new file mode 100755 index 0000000..5a68104 --- /dev/null +++ b/hysteria2.sh @@ -0,0 +1,1626 @@ +#!/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 diff --git a/install.sh b/install.sh old mode 100644 new mode 100755