Files
hysteria2/hysteria2.sh
2026-02-23 18:39:31 +01:00

1627 lines
56 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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