1627 lines
56 KiB
Bash
Executable File
1627 lines
56 KiB
Bash
Executable File
#!/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
|