From 668b36d5d8646afabe79df483816f2bdf40d7bf2 Mon Sep 17 00:00:00 2001 From: sindri <147028060+sindricn@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:46:04 +0800 Subject: [PATCH] Update hy2-manager.sh --- hy2-manager.sh | 1038 +++++++++++++++++++++++++++++++----------------- 1 file changed, 663 insertions(+), 375 deletions(-) diff --git a/hy2-manager.sh b/hy2-manager.sh index 59fee08..eae69cc 100644 --- a/hy2-manager.sh +++ b/hy2-manager.sh @@ -1,12 +1,9 @@ #!/bin/bash # Hysteria2 配置管理脚本 -# 版本: 1.0.0 +# 版本: 1.0.1 # 作者: Hysteria2 Manager -# 移除 set -e 以避免脚本意外退出 -# set -e - # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' @@ -16,20 +13,35 @@ PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # No Color -# 脚本目录 - 处理符号链接 -if [[ -L "${BASH_SOURCE[0]}" ]]; then - # 如果是符号链接,获取真实路径 - SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" -else - # 如果不是符号链接,使用当前路径 - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -fi +# 错误处理函数 +error_exit() { + echo -e "${RED}错误: $1${NC}" >&2 + exit "${2:-1}" +} -# 如果脚本在 /usr/local/bin 中运行,假设安装在 /opt/s-hy2 -if [[ "$SCRIPT_DIR" == "/usr/local/bin" ]]; then - SCRIPT_DIR="/opt/s-hy2" -fi +# 脚本目录处理 - 改进符号链接检测 +get_script_dir() { + local source="${BASH_SOURCE[0]}" + local dir + + # 处理符号链接 + while [[ -L "$source" ]]; do + dir="$(cd -P "$(dirname "$source")" && pwd)" + source="$(readlink "$source")" + [[ $source != /* ]] && source="$dir/$source" + done + + dir="$(cd -P "$(dirname "$source")" && pwd)" + + # 如果脚本在 /usr/local/bin 中运行,假设安装在 /opt/s-hy2 + if [[ "$dir" == "/usr/local/bin" ]]; then + dir="/opt/s-hy2" + fi + + echo "$dir" +} +SCRIPT_DIR="$(get_script_dir)" SCRIPTS_DIR="$SCRIPT_DIR/scripts" TEMPLATES_DIR="$SCRIPT_DIR/templates" @@ -38,12 +50,56 @@ CONFIG_PATH="/etc/hysteria/config.yaml" SERVER_DOMAIN_CONFIG="/etc/hysteria/server-domain.conf" SERVICE_NAME="hysteria-server.service" +# 日志记录函数 +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + # 检查是否为 root 用户 check_root() { if [[ $EUID -ne 0 ]]; then - echo -e "${RED}错误: 此脚本需要 root 权限运行${NC}" - echo "请使用 sudo 运行此脚本" - exit 1 + error_exit "此脚本需要 root 权限运行,请使用 sudo 运行此脚本" + fi +} + +# 检查脚本文件完整性 +check_script_integrity() { + local missing_scripts=() + + # 检查必需的脚本文件 + local required_scripts=( + "install.sh" + "config.sh" + "service.sh" + "node-info.sh" + ) + + for script in "${required_scripts[@]}"; do + if [[ ! -f "$SCRIPTS_DIR/$script" ]]; then + missing_scripts+=("$script") + fi + done + + if [[ ${#missing_scripts[@]} -gt 0 ]]; then + log_warn "检测到缺失的脚本文件:" + for script in "${missing_scripts[@]}"; do + echo " - $script" + done + echo "" + echo "这可能影响某些功能的正常使用" + echo "" fi } @@ -51,7 +107,7 @@ check_root() { print_header() { clear echo -e "${CYAN}================================================${NC}" - echo -e "${CYAN} Hysteria2 配置管理脚本${NC}" + echo -e "${CYAN} Hysteria2 配置管理脚本 v1.0.1${NC}" echo -e "${CYAN}================================================${NC}" echo "" } @@ -60,215 +116,300 @@ print_header() { print_menu() { echo -e "${YELLOW}请选择操作:${NC}" echo "" - echo -e "${GREEN}1.${NC} 安装 Hysteria2" - echo -e "${GREEN}2.${NC} 一键快速配置" - echo -e "${GREEN}3.${NC} 手动配置" - echo -e "${GREEN}4.${NC} 管理服务" - echo -e "${GREEN}5.${NC} 查看日志" - echo -e "${GREEN}6.${NC} 测试伪装域名" - echo -e "${GREEN}7.${NC} 服务器域名配置" - echo -e "${GREEN}8.${NC} 进阶配置" - echo -e "${GREEN}9.${NC} 节点信息" - echo -e "${GREEN}10.${NC} 卸载服务" - echo -e "${GREEN}11.${NC} 关于脚本" - echo -e "${RED}0.${NC} 退出" + echo -e "${GREEN} 1.${NC} 安装 Hysteria2" + echo -e "${GREEN} 2.${NC} 一键快速配置" + echo -e "${GREEN} 3.${NC} 手动配置" + echo -e "${GREEN} 4.${NC} 管理服务" + echo -e "${GREEN} 5.${NC} 查看日志" + echo -e "${GREEN} 6.${NC} 测试伪装域名" + echo -e "${GREEN} 7.${NC} 服务器域名配置" + echo -e "${GREEN} 8.${NC} 进阶配置" + echo -e "${GREEN} 9.${NC} 节点信息" + echo -e "${GREEN}10.${NC} 故障排除" + echo -e "${GREEN}11.${NC} 卸载服务" + echo -e "${GREEN}12.${NC} 关于脚本" + echo -e "${RED} 0.${NC} 退出" echo "" - echo -n -e "${BLUE}请输入选项 [0-11]: ${NC}" + echo -n -e "${BLUE}请输入选项 [0-12]: ${NC}" } # 检查 Hysteria2 是否已安装 check_hysteria_installed() { - if command -v hysteria &> /dev/null; then - return 0 - else - return 1 - fi + command -v hysteria &> /dev/null } # 检查服务状态 check_service_status() { - if systemctl is-active --quiet $SERVICE_NAME; then - echo -e "${GREEN}运行中${NC}" - elif systemctl is-enabled --quiet $SERVICE_NAME; then - echo -e "${YELLOW}已启用但未运行${NC}" + if systemctl is-active --quiet "$SERVICE_NAME"; then + echo -e "${GREEN}✅ 运行中${NC}" + return 0 + elif systemctl is-enabled --quiet "$SERVICE_NAME"; then + echo -e "${YELLOW}⏸️ 已启用但未运行${NC}" + return 1 else - echo -e "${RED}未启用${NC}" + echo -e "${RED}❌ 未启用${NC}" + return 2 + fi +} + +# 显示系统信息 +show_system_info() { + local server_ip + server_ip=$(get_server_ip) + local server_domain + server_domain=$(get_server_domain) + + echo -e "${CYAN}系统信息:${NC}" + echo "服务器IP: ${server_ip:-未知}" + if [[ -n "$server_domain" ]]; then + echo "服务器域名: $server_domain" + fi + echo "系统: $(get_system_info)" + echo "" +} + +# 获取系统信息 +get_system_info() { + if [[ -f /etc/os-release ]]; then + source /etc/os-release + echo "${PRETTY_NAME:-$NAME $VERSION_ID}" + else + echo "未知系统" fi } # 显示当前状态 show_status() { - echo "" - echo -e "${CYAN}当前状态:${NC}" + show_system_info + + echo -e "${CYAN}Hysteria2 状态:${NC}" if check_hysteria_installed; then - echo -e "Hysteria2: ${GREEN}已安装${NC}" + echo -e "程序状态: ${GREEN}✅ 已安装${NC}" echo -n "服务状态: " check_service_status if [[ -f "$CONFIG_PATH" ]]; then - echo -e "配置文件: ${GREEN}存在${NC}" + echo -e "配置文件: ${GREEN}✅ 存在${NC}" else - echo -e "配置文件: ${RED}不存在${NC}" + echo -e "配置文件: ${RED}❌ 不存在${NC}" fi else - echo -e "Hysteria2: ${RED}未安装${NC}" + echo -e "程序状态: ${RED}❌ 未安装${NC}" fi echo "" } +# 安全地执行脚本 +safe_source_script() { + local script_path="$1" + local script_name="$2" + + if [[ -f "$script_path" ]]; then + log_info "加载 $script_name..." + # shellcheck source=/dev/null + source "$script_path" || { + log_error "$script_name 加载失败" + return 1 + } + return 0 + else + log_error "$script_name 不存在: $script_path" + echo "" + echo "可能的解决方案:" + echo "1. 重新运行安装脚本" + echo "2. 检查脚本文件是否完整" + echo "" + return 1 + fi +} + +# 等待用户确认 +wait_for_user() { + echo "" + read -p "按回车键继续..." -r +} + # 安装 Hysteria2 install_hysteria() { - echo -e "${BLUE}正在安装 Hysteria2...${NC}" - - # 调试信息 - echo "调试信息:" - echo " 脚本目录: $SCRIPT_DIR" - echo " 功能脚本目录: $SCRIPTS_DIR" - echo " 安装脚本路径: $SCRIPTS_DIR/install.sh" - echo "" - - if [[ -f "$SCRIPTS_DIR/install.sh" ]]; then - echo -e "${GREEN}找到安装脚本,正在执行...${NC}" - source "$SCRIPTS_DIR/install.sh" + log_info "准备安装 Hysteria2..." + + if safe_source_script "$SCRIPTS_DIR/install.sh" "安装脚本"; then install_hysteria2 - else - echo -e "${RED}错误: 安装脚本不存在${NC}" - echo "" - echo "详细信息:" - echo " 期望路径: $SCRIPTS_DIR/install.sh" - echo " 实际情况: $(ls -la "$SCRIPTS_DIR/" 2>/dev/null || echo "目录不存在")" - echo "" - echo "可能的解决方案:" - echo "1. 重新运行安装脚本" - echo "2. 检查安装是否完整" - echo "3. 手动下载缺失的脚本文件" - echo "" - read -p "按回车键继续..." fi + wait_for_user } # 一键快速配置 quick_config() { - echo -e "${BLUE}一键快速配置...${NC}" - if [[ -f "$SCRIPTS_DIR/config.sh" ]]; then - source "$SCRIPTS_DIR/config.sh" + log_info "准备执行一键快速配置..." + + if ! check_hysteria_installed; then + log_error "Hysteria2 未安装,请先安装" + wait_for_user + return + fi + + if safe_source_script "$SCRIPTS_DIR/config.sh" "配置脚本"; then quick_setup_hysteria - else - echo -e "${RED}错误: 配置脚本不存在${NC}" - read -p "按回车键继续..." fi } # 手动配置 manual_config() { - echo -e "${BLUE}手动配置...${NC}" - if [[ -f "$SCRIPTS_DIR/config.sh" ]]; then - source "$SCRIPTS_DIR/config.sh" + log_info "准备执行手动配置..." + + if ! check_hysteria_installed; then + log_error "Hysteria2 未安装,请先安装" + wait_for_user + return + fi + + if safe_source_script "$SCRIPTS_DIR/config.sh" "配置脚本"; then generate_hysteria_config - else - echo -e "${RED}错误: 配置脚本不存在${NC}" - read -p "按回车键继续..." fi } # 管理服务 manage_service() { - echo -e "${BLUE}服务管理...${NC}" - if [[ -f "$SCRIPTS_DIR/service.sh" ]]; then - source "$SCRIPTS_DIR/service.sh" + log_info "准备进入服务管理..." + + if ! check_hysteria_installed; then + log_error "Hysteria2 未安装,请先安装" + wait_for_user + return + fi + + if safe_source_script "$SCRIPTS_DIR/service.sh" "服务管理脚本"; then manage_hysteria_service - else - echo -e "${RED}错误: 服务管理脚本不存在${NC}" fi } -# 查看日志 +# 查看日志 - 改进版本 view_logs() { - echo -e "${BLUE}查看服务日志...${NC}" - echo "" - journalctl --no-pager -e -u $SERVICE_NAME - echo "" - read -p "按回车键继续..." -} - -# 测试伪装域名 -test_domains() { - echo -e "${BLUE}测试伪装域名...${NC}" - if [[ -f "$SCRIPTS_DIR/domain-test.sh" ]]; then - source "$SCRIPTS_DIR/domain-test.sh" - test_masquerade_domains - else - echo -e "${RED}错误: 域名测试脚本不存在${NC}" + log_info "查看 Hysteria2 服务日志..." + + if ! systemctl list-units --type=service | grep -q "$SERVICE_NAME"; then + log_error "Hysteria2 服务不存在" + wait_for_user + return fi -} - -# 服务器域名配置 -server_domain_config() { - echo -e "${BLUE}服务器域名配置${NC}" + echo "" - - # 显示当前配置 - if [[ -f "$SERVER_DOMAIN_CONFIG" ]]; then - local current_domain=$(cat "$SERVER_DOMAIN_CONFIG") - echo -e "${YELLOW}当前配置域名: $current_domain${NC}" - else - echo -e "${YELLOW}当前未配置服务器域名${NC}" - fi - + echo -e "${CYAN}=== 最近 50 行日志 ===${NC}" + journalctl --no-pager -n 50 -u "$SERVICE_NAME" --no-hostname + echo "" - echo -e "${GREEN}1.${NC} 设置服务器域名" - echo -e "${GREEN}2.${NC} 验证域名解析" - echo -e "${GREEN}3.${NC} 删除域名配置" - echo -e "${RED}0.${NC} 返回主菜单" + echo -e "${YELLOW}日志选项:${NC}" + echo "1. 查看实时日志" + echo "2. 查看完整日志" + echo "3. 查看错误日志" + echo "0. 返回" echo "" - echo -n -e "${BLUE}请选择操作 [0-3]: ${NC}" + echo -n -e "${BLUE}请选择 [0-3]: ${NC}" read -r choice - + case $choice in 1) - set_server_domain + echo -e "${BLUE}实时日志 (按 Ctrl+C 退出):${NC}" + journalctl -f -u "$SERVICE_NAME" --no-hostname ;; 2) - verify_domain_resolution + echo -e "${BLUE}完整日志:${NC}" + journalctl --no-pager -u "$SERVICE_NAME" --no-hostname | less ;; 3) - remove_server_domain + echo -e "${BLUE}错误日志:${NC}" + journalctl --no-pager -p err -u "$SERVICE_NAME" --no-hostname + wait_for_user ;; 0) return ;; *) - echo -e "${RED}无效选项${NC}" - sleep 1 - server_domain_config + log_error "无效选择" ;; esac } +# 测试伪装域名 +test_domains() { + log_info "准备测试伪装域名..." + + if safe_source_script "$SCRIPTS_DIR/domain-test.sh" "域名测试脚本"; then + test_masquerade_domains + fi +} + +# 服务器域名配置 - 优化版本 +server_domain_config() { + while true; do + clear + echo -e "${CYAN}=== 服务器域名配置 ===${NC}" + echo "" + + # 显示当前配置 + if [[ -f "$SERVER_DOMAIN_CONFIG" ]]; then + local current_domain + current_domain=$(cat "$SERVER_DOMAIN_CONFIG") + echo -e "${GREEN}当前配置域名: $current_domain${NC}" + else + echo -e "${YELLOW}当前未配置服务器域名${NC}" + fi + + echo "" + echo -e "${YELLOW}域名配置选项:${NC}" + echo -e "${GREEN}1.${NC} 设置服务器域名" + echo -e "${GREEN}2.${NC} 验证域名解析" + echo -e "${GREEN}3.${NC} 删除域名配置" + echo -e "${RED}0.${NC} 返回主菜单" + echo "" + echo -n -e "${BLUE}请选择操作 [0-3]: ${NC}" + read -r choice + + case $choice in + 1) set_server_domain ;; + 2) verify_domain_resolution ;; + 3) remove_server_domain ;; + 0) break ;; + *) + log_error "无效选项" + sleep 1 + ;; + esac + done +} + +# 验证域名格式 +validate_domain() { + local domain="$1" + [[ $domain =~ ^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$ ]] +} + # 设置服务器域名 set_server_domain() { echo "" echo -e "${BLUE}设置服务器域名${NC}" - echo "" echo "请输入解析到此服务器的域名 (例如: example.com):" echo -n -e "${YELLOW}域名: ${NC}" read -r domain if [[ -z "$domain" ]]; then - echo -e "${RED}域名不能为空${NC}" - read -p "按回车键继续..." + log_error "域名不能为空" + wait_for_user return fi - # 简单的域名格式验证 - if [[ ! "$domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$ ]]; then - echo -e "${RED}域名格式不正确${NC}" - read -p "按回车键继续..." + if ! validate_domain "$domain"; then + log_error "域名格式不正确" + wait_for_user return fi + # 创建目录(如果不存在) + mkdir -p "$(dirname "$SERVER_DOMAIN_CONFIG")" + # 保存域名配置 echo "$domain" > "$SERVER_DOMAIN_CONFIG" - echo -e "${GREEN}服务器域名已设置: $domain${NC}" + log_success "服务器域名已设置: $domain" # 询问是否立即验证 echo "" @@ -278,65 +419,73 @@ set_server_domain() { verify_domain_resolution fi - read -p "按回车键继续..." + wait_for_user } -# 验证域名解析 +# 验证域名解析 - 改进版本 verify_domain_resolution() { echo "" echo -e "${BLUE}验证域名解析${NC}" if [[ ! -f "$SERVER_DOMAIN_CONFIG" ]]; then - echo -e "${RED}未配置服务器域名${NC}" - read -p "按回车键继续..." + log_error "未配置服务器域名" + wait_for_user return fi - local domain=$(cat "$SERVER_DOMAIN_CONFIG") - local server_ip=$(get_server_ip) + local domain + domain=$(cat "$SERVER_DOMAIN_CONFIG") + local server_ip + server_ip=$(get_server_ip) echo "正在验证域名: $domain" echo "服务器IP: $server_ip" echo "" # 使用多种方法解析域名 - local resolved_ip="" - - # 方法1: 使用 dig - if command -v dig &> /dev/null; then - resolved_ip=$(dig +short "$domain" A | head -1) - fi - - # 方法2: 使用 nslookup (备选) - if [[ -z "$resolved_ip" ]] && command -v nslookup &> /dev/null; then - resolved_ip=$(nslookup "$domain" | grep -A1 "Name:" | tail -1 | awk '{print $2}') - fi - - # 方法3: 使用 host (备选) - if [[ -z "$resolved_ip" ]] && command -v host &> /dev/null; then - resolved_ip=$(host "$domain" | grep "has address" | awk '{print $4}' | head -1) - fi - - if [[ -n "$resolved_ip" ]]; then - echo "域名解析结果: $resolved_ip" - - if [[ "$resolved_ip" == "$server_ip" ]]; then - echo -e "${GREEN}✅ 域名解析正确,指向当前服务器${NC}" - else - echo -e "${RED}❌ 域名解析错误${NC}" - echo "域名指向: $resolved_ip" - echo "服务器IP: $server_ip" - echo "" - echo "请检查域名DNS设置" + local resolved_ips=() + local dns_tools=("dig" "nslookup" "host") + + for tool in "${dns_tools[@]}"; do + if command -v "$tool" &> /dev/null; then + local result + case $tool in + dig) + result=$(dig +short "$domain" A | head -5) + ;; + nslookup) + result=$(nslookup "$domain" 2>/dev/null | grep "Address:" | tail -n +2 | awk '{print $2}' | head -5) + ;; + host) + result=$(host "$domain" 2>/dev/null | grep "has address" | awk '{print $4}' | head -5) + ;; + esac + + if [[ -n "$result" ]]; then + echo "使用 $tool 解析结果:" + echo "$result" | while read -r ip; do + if [[ -n "$ip" && "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + if [[ "$ip" == "$server_ip" ]]; then + echo -e " ${GREEN}✅ $ip (匹配)${NC}" + else + echo -e " ${YELLOW}⚠️ $ip (不匹配)${NC}" + fi + resolved_ips+=("$ip") + fi + done + break + fi fi - else - echo -e "${YELLOW}⚠️ 无法解析域名,可能原因:${NC}" + done + + if [[ ${#resolved_ips[@]} -eq 0 ]]; then + log_error "无法解析域名,可能原因:" echo "1. 域名DNS设置未生效" echo "2. 网络连接问题" echo "3. DNS服务器问题" fi - read -p "按回车键继续..." + wait_for_user } # 删除服务器域名配置 @@ -345,12 +494,13 @@ remove_server_domain() { echo -e "${YELLOW}删除服务器域名配置${NC}" if [[ ! -f "$SERVER_DOMAIN_CONFIG" ]]; then - echo -e "${YELLOW}未配置服务器域名${NC}" - read -p "按回车键继续..." + log_warn "未配置服务器域名" + wait_for_user return fi - local domain=$(cat "$SERVER_DOMAIN_CONFIG") + local domain + domain=$(cat "$SERVER_DOMAIN_CONFIG") echo "当前配置域名: $domain" echo "" echo -n -e "${RED}确定要删除域名配置吗? [y/N]: ${NC}" @@ -358,146 +508,189 @@ remove_server_domain() { if [[ $confirm =~ ^[Yy]$ ]]; then rm -f "$SERVER_DOMAIN_CONFIG" - echo -e "${GREEN}域名配置已删除${NC}" + log_success "域名配置已删除" else echo -e "${BLUE}取消删除${NC}" fi - read -p "按回车键继续..." + wait_for_user } -# 获取服务器IP +# 获取服务器IP - 改进版本 get_server_ip() { local ip="" + local timeout=5 + + # IP获取服务列表(按可靠性排序) + local ip_services=( + "ipv4.icanhazip.com" + "ifconfig.me/ip" + "ip.sb" + "checkip.amazonaws.com" + "ipinfo.io/ip" + "httpbin.org/ip" + ) - # 尝试多种方法获取公网IP - ip=$(curl -s --connect-timeout 5 ipv4.icanhazip.com 2>/dev/null) || \ - ip=$(curl -s --connect-timeout 5 ifconfig.me 2>/dev/null) || \ - ip=$(curl -s --connect-timeout 5 ip.sb 2>/dev/null) || \ - ip=$(curl -s --connect-timeout 5 checkip.amazonaws.com 2>/dev/null) + for service in "${ip_services[@]}"; do + ip=$(curl -s --connect-timeout "$timeout" --max-time "$timeout" "https://$service" 2>/dev/null) + # 验证IP格式 + if [[ -n "$ip" && "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + # 验证IP有效性 + IFS='.' read -ra ADDR <<< "$ip" + local valid=true + for octet in "${ADDR[@]}"; do + if [[ $octet -gt 255 || $octet -lt 0 ]]; then + valid=false + break + fi + done + if $valid; then + echo "$ip" + return + fi + fi + done - if [[ -n "$ip" && "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then - echo "$ip" - else - # 如果无法获取公网IP,尝试获取本地IP - ip=$(ip route get 8.8.8.8 2>/dev/null | grep -oP 'src \K\S+') - echo "${ip:-127.0.0.1}" - fi + # 如果无法获取公网IP,尝试获取本地IP + ip=$(ip route get 8.8.8.8 2>/dev/null | grep -oP 'src \K\S+' | head -1) + echo "${ip:-127.0.0.1}" } # 获取配置的服务器域名 get_server_domain() { if [[ -f "$SERVER_DOMAIN_CONFIG" ]]; then cat "$SERVER_DOMAIN_CONFIG" - else - echo "" fi } # 进阶配置 advanced_config() { - echo -e "${BLUE}进阶配置...${NC}" - if [[ -f "$SCRIPTS_DIR/advanced.sh" ]]; then - source "$SCRIPTS_DIR/advanced.sh" + log_info "准备进入进阶配置..." + + if ! check_hysteria_installed; then + log_error "Hysteria2 未安装,请先安装" + wait_for_user + return + fi + + if safe_source_script "$SCRIPTS_DIR/advanced.sh" "进阶配置脚本"; then advanced_configuration - else - echo -e "${RED}错误: 进阶配置脚本不存在${NC}" - read -p "按回车键继续..." fi } # 节点信息 show_node_info() { - echo -e "${BLUE}节点信息...${NC}" - if [[ -f "$SCRIPTS_DIR/node-info.sh" ]]; then - source "$SCRIPTS_DIR/node-info.sh" + log_info "准备显示节点信息..." + + if ! check_hysteria_installed; then + log_error "Hysteria2 未安装,请先安装" + wait_for_user + return + fi + + if safe_source_script "$SCRIPTS_DIR/node-info.sh" "节点信息脚本"; then display_node_info - else - echo -e "${RED}错误: 节点信息脚本不存在${NC}" - read -p "按回车键继续..." fi } # 故障排除 troubleshoot() { - echo -e "${BLUE}故障排除...${NC}" - if [[ -f "$SCRIPTS_DIR/troubleshoot.sh" ]]; then - source "$SCRIPTS_DIR/troubleshoot.sh" + log_info "准备进入故障排除..." + + if safe_source_script "$SCRIPTS_DIR/troubleshoot.sh" "故障排除脚本"; then run_diagnostics - else - echo -e "${RED}错误: 故障排除脚本不存在${NC}" - read -p "按回车键继续..." fi } -# 卸载服务 +# 卸载服务 - 改进版本,增加更多选项和安全确认 uninstall_hysteria() { - echo -e "${YELLOW}Hysteria2 卸载选项:${NC}" + clear + echo -e "${CYAN}=== Hysteria2 卸载向导 ===${NC}" echo "" - echo -e "${GREEN}1.${NC} 仅卸载 Hysteria2 服务器 (保留配置文件)" - echo -e "${GREEN}2.${NC} 卸载 Hysteria2 服务器及配置文件" - echo -e "${GREEN}3.${NC} 卸载脚本及 Hysteria2 服务器和所有文件" + + # 显示当前状态 + if check_hysteria_installed; then + echo -e "${YELLOW}当前 Hysteria2 状态:${NC}" + echo -n "服务状态: " + check_service_status + if [[ -f "$CONFIG_PATH" ]]; then + echo "配置文件: 存在" + fi + if [[ -d "/etc/hysteria" ]]; then + echo "配置目录: 存在" + fi + echo "" + else + echo -e "${YELLOW}Hysteria2 未安装,但可以清理残留文件${NC}" + echo "" + fi + + echo -e "${YELLOW}卸载选项:${NC}" + echo -e "${GREEN}1.${NC} 仅卸载 Hysteria2 程序 (保留配置和证书)" + echo -e "${GREEN}2.${NC} 卸载程序和配置文件 (保留管理脚本)" + echo -e "${GREEN}3.${NC} 完全卸载 (包括管理脚本)" echo -e "${RED}0.${NC} 取消" echo "" echo -e "${CYAN}说明:${NC}" - echo "选项1: 只删除程序,保留配置和证书,便于重新安装" - echo "选项2: 删除程序和配置,但保留管理脚本" - echo "选项3: 完全删除所有相关文件,包括管理脚本" + echo "选项1: 保留配置便于重新安装,适合升级或重装" + echo "选项2: 清理配置重新开始,保留管理工具" + echo "选项3: 完全清理系统,删除所有相关文件" echo "" echo -n -e "${BLUE}请选择卸载方式 [0-3]: ${NC}" read -r uninstall_choice case $uninstall_choice in - 1) - uninstall_server_only - ;; - 2) - uninstall_server_and_config - ;; - 3) - uninstall_everything - ;; - 0) + 1) uninstall_server_only ;; + 2) uninstall_server_and_config ;; + 3) uninstall_everything ;; + 0) echo -e "${BLUE}取消卸载${NC}" ;; *) - echo -e "${RED}无效选择${NC}" + log_error "无效选择" ;; esac - echo "" - read -p "按回车键继续..." + wait_for_user } # 方式1: 仅卸载 Hysteria2 服务器 uninstall_server_only() { - echo -e "${BLUE}仅卸载 Hysteria2 服务器 (保留配置文件和证书)${NC}" + echo "" + echo -e "${BLUE}仅卸载 Hysteria2 程序 (保留配置和证书)${NC}" echo "" if ! check_hysteria_installed; then - echo -e "${YELLOW}Hysteria2 未安装${NC}" + log_warn "Hysteria2 未安装" return fi echo -e "${YELLOW}此操作将:${NC}" - echo "✓ 删除 Hysteria2 程序文件" - echo "✓ 停止并删除系统服务" + echo "✓ 停止并卸载 Hysteria2 程序" + echo "✓ 删除系统服务" echo "✗ 保留配置文件和证书" echo "✗ 保留用户账户" echo "✗ 保留管理脚本" echo "" - echo -e "${YELLOW}确定要卸载 Hysteria2 服务器吗? [y/N]: ${NC}" + echo -n -e "${YELLOW}确定要卸载 Hysteria2 程序吗? [y/N]: ${NC}" read -r confirm if [[ ! $confirm =~ ^[Yy]$ ]]; then echo -e "${BLUE}取消卸载${NC}" return fi + log_info "正在卸载 Hysteria2 程序..." + + # 停止服务 + if systemctl is-active --quiet "$SERVICE_NAME"; then + systemctl stop "$SERVICE_NAME" + log_info "已停止服务" + fi + # 使用官方卸载脚本 - echo -e "${BLUE}正在卸载 Hysteria2 服务器...${NC}" - if bash <(curl -fsSL https://get.hy2.sh/) --remove; then + if bash <(curl -fsSL https://get.hy2.sh/) --remove 2>/dev/null; then echo "" - echo -e "${GREEN}Hysteria2 服务器卸载完成!${NC}" + log_success "Hysteria2 程序卸载完成!" echo "" echo -e "${CYAN}已保留内容:${NC}" echo "• 配置文件: /etc/hysteria/" @@ -507,194 +700,270 @@ uninstall_server_only() { echo "" echo -e "${YELLOW}重新安装:${NC}" echo "运行 's-hy2' 选择 '1. 安装 Hysteria2' 即可重新安装" - echo "" else - echo -e "${RED}卸载失败${NC}" + log_error "卸载失败,请检查网络连接或手动卸载" fi } # 方式2: 卸载 Hysteria2 服务器及配置文件 uninstall_server_and_config() { - echo -e "${BLUE}卸载 Hysteria2 服务器及配置文件${NC}" + echo "" + echo -e "${BLUE}卸载 Hysteria2 程序和配置文件${NC}" echo "" - if ! check_hysteria_installed; then - echo -e "${YELLOW}Hysteria2 未安装${NC}" - return - fi - echo -e "${YELLOW}此操作将:${NC}" - echo "✓ 删除 Hysteria2 程序文件" - echo "✓ 停止并删除系统服务" + echo "✓ 停止并卸载 Hysteria2 程序" + echo "✓ 删除系统服务" echo "✓ 删除配置文件和证书" echo "✓ 删除用户账户" echo "✓ 清理端口跳跃规则" echo "✗ 保留管理脚本" echo "" - echo -e "${YELLOW}确定要卸载 Hysteria2 服务器及配置吗? [y/N]: ${NC}" + echo -n -e "${YELLOW}确定要卸载程序和配置吗? [y/N]: ${NC}" read -r confirm if [[ ! $confirm =~ ^[Yy]$ ]]; then echo -e "${BLUE}取消卸载${NC}" return fi - # 1. 卸载程序 - echo -e "${BLUE}步骤 1/5: 卸载 Hysteria2 程序...${NC}" - bash <(curl -fsSL https://get.hy2.sh/) --remove + # 清理端口跳跃配置 (需要在删除配置文件前执行) + log_info "步骤 1/5: 清理端口跳跃配置..." + cleanup_port_hopping - # 2. 删除配置文件和证书 - echo -e "${BLUE}步骤 2/5: 删除配置文件和证书...${NC}" + # 停止服务 + log_info "步骤 2/5: 停止服务..." + if systemctl is-active --quiet "$SERVICE_NAME"; then + systemctl stop "$SERVICE_NAME" + fi + + # 卸载程序 + log_info "步骤 3/5: 卸载 Hysteria2 程序..." + if check_hysteria_installed; then + bash <(curl -fsSL https://get.hy2.sh/) --remove 2>/dev/null || log_warn "程序卸载失败,继续清理" + fi + + # 删除配置文件和证书 + log_info "步骤 4/5: 删除配置文件和证书..." if [[ -d "/etc/hysteria" ]]; then rm -rf /etc/hysteria - echo "已删除 /etc/hysteria" + log_success "已删除 /etc/hysteria" fi - # 3. 删除用户账户 - echo -e "${BLUE}步骤 3/5: 删除用户账户...${NC}" - if id "hysteria" &>/dev/null; then - userdel -r hysteria 2>/dev/null || userdel hysteria 2>/dev/null - echo "已删除 hysteria 用户" - fi - - # 4. 清理 systemd 服务残留 - echo -e "${BLUE}步骤 4/5: 清理 systemd 服务残留...${NC}" - rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server.service - rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server@*.service - systemctl daemon-reload - echo "已清理 systemd 服务残留" - - # 5. 清理端口跳跃配置 - echo -e "${BLUE}步骤 5/5: 清理端口跳跃配置...${NC}" - # 注意:这里配置文件已经被删除,所以无法读取配置 - echo "端口跳跃规则可能需要手动清理" + # 清理用户账户和系统残留 + log_info "步骤 5/5: 清理用户账户和系统残留..." + cleanup_system_remnants echo "" - echo -e "${GREEN}Hysteria2 服务器及配置文件卸载完成!${NC}" + log_success "Hysteria2 程序和配置文件卸载完成!" echo "" echo -e "${CYAN}已保留内容:${NC}" echo "• 管理脚本: s-hy2" echo "" - echo -e "${YELLOW}重新安装:${NC}" - echo "运行 's-hy2' 选择 '1. 安装 Hysteria2' 和 '2. 一键快速配置' 即可重新部署" - echo "" + echo -e "${YELLOW}重新部署:${NC}" + echo "运行 's-hy2' 选择安装和配置选项即可重新部署" } -# 方式3: 卸载脚本及 Hysteria2 服务器和所有文件 +# 方式3: 完全卸载 uninstall_everything() { - echo -e "${RED}卸载脚本及 Hysteria2 服务器和所有文件${NC}" + echo "" + echo -e "${RED}完全卸载 - 删除所有相关文件${NC}" echo "" - echo -e "${RED}警告: 此操作将删除所有相关文件,包括:${NC}" + echo -e "${RED}警告: 此操作将删除:${NC}" echo "• Hysteria2 程序文件" - echo "• 配置文件和证书" + echo "• 所有配置文件和证书" echo "• 用户账户" echo "• 系统服务" echo "• 管理脚本 (s-hy2)" echo "• 端口跳跃规则" + echo "• 所有相关目录和文件" echo "" - echo -e "${YELLOW}确定要完全卸载所有内容吗? 请输入 'YES' 确认: ${NC}" + echo -e "${YELLOW}此操作不可逆!请输入 'YES' 确认完全卸载: ${NC}" read -r confirm if [[ "$confirm" != "YES" ]]; then echo -e "${BLUE}取消卸载${NC}" return fi - # 1. 清理端口跳跃配置 (需要在删除配置文件前执行) - echo -e "${BLUE}步骤 1/8: 清理端口跳跃配置...${NC}" + # 执行完全清理 + perform_complete_uninstall +} + +# 清理端口跳跃配置 +cleanup_port_hopping() { if [[ -f "/etc/hysteria/port-hopping.conf" ]]; then + # shellcheck source=/dev/null source "/etc/hysteria/port-hopping.conf" 2>/dev/null if [[ -n "$INTERFACE" && -n "$START_PORT" && -n "$END_PORT" && -n "$TARGET_PORT" ]]; then iptables -t nat -D PREROUTING -i "$INTERFACE" -p udp --dport "$START_PORT:$END_PORT" -j REDIRECT --to-ports "$TARGET_PORT" 2>/dev/null - echo "已清理端口跳跃 iptables 规则" + log_info "已清理端口跳跃 iptables 规则" fi - else - echo "未找到端口跳跃配置" fi - - # 2. 卸载 Hysteria2 程序 - echo -e "${BLUE}步骤 2/8: 卸载 Hysteria2 程序...${NC}" - if check_hysteria_installed; then - bash <(curl -fsSL https://get.hy2.sh/) --remove - else - echo "Hysteria2 未安装" + + # 清理其他可能的端口跳跃规则 + local rules_cleared=0 + while IFS= read -r line_num; do + if [[ -n "$line_num" ]] && iptables -t nat -D PREROUTING "$line_num" 2>/dev/null; then + ((rules_cleared++)) + fi + done < <(iptables -t nat -L PREROUTING --line-numbers 2>/dev/null | grep "REDIRECT.*--to-ports 443" | awk '{print $1}' | sort -rn) + + if [[ $rules_cleared -gt 0 ]]; then + log_info "已清理 $rules_cleared 条端口跳跃规则" fi +} - # 3. 删除配置文件和证书 - echo -e "${BLUE}步骤 3/8: 删除配置文件和证书...${NC}" - if [[ -d "/etc/hysteria" ]]; then - rm -rf /etc/hysteria - echo "已删除 /etc/hysteria" - fi - - # 4. 删除用户账户 - echo -e "${BLUE}步骤 4/8: 删除用户账户...${NC}" +# 清理系统残留 +cleanup_system_remnants() { + # 删除用户账户 if id "hysteria" &>/dev/null; then userdel -r hysteria 2>/dev/null || userdel hysteria 2>/dev/null - echo "已删除 hysteria 用户" + log_info "已删除 hysteria 用户" fi - # 5. 清理 systemd 服务残留 - echo -e "${BLUE}步骤 5/8: 清理 systemd 服务残留...${NC}" - rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server.service - rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server@*.service + # 清理 systemd 服务残留 + local service_files=( + "/etc/systemd/system/multi-user.target.wants/hysteria-server.service" + "/etc/systemd/system/multi-user.target.wants/hysteria-server@*.service" + "/lib/systemd/system/hysteria-server.service" + "/usr/lib/systemd/system/hysteria-server.service" + ) + + for service_file in "${service_files[@]}"; do + if [[ -f "$service_file" ]]; then + rm -f "$service_file" + fi + done + systemctl daemon-reload - echo "已清理 systemd 服务残留" + log_info "已清理 systemd 服务残留" +} + +# 执行完全卸载 +perform_complete_uninstall() { + echo "" + log_info "开始执行完全卸载..." + + # 1. 清理端口跳跃配置 + log_info "步骤 1/8: 清理端口跳跃配置..." + cleanup_port_hopping + + # 2. 停止并禁用服务 + log_info "步骤 2/8: 停止并禁用服务..." + if systemctl is-active --quiet "$SERVICE_NAME"; then + systemctl stop "$SERVICE_NAME" + fi + if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then + systemctl disable "$SERVICE_NAME" 2>/dev/null + fi + + # 3. 卸载 Hysteria2 程序 + log_info "步骤 3/8: 卸载 Hysteria2 程序..." + if check_hysteria_installed; then + bash <(curl -fsSL https://get.hy2.sh/) --remove 2>/dev/null || log_warn "程序卸载失败,继续清理" + fi + + # 4. 删除配置文件和证书 + log_info "步骤 4/8: 删除配置文件和证书..." + rm -rf /etc/hysteria + + # 5. 清理系统残留 + log_info "步骤 5/8: 清理系统残留..." + cleanup_system_remnants # 6. 清理 iptables 规则残留 - echo -e "${BLUE}步骤 6/8: 清理 iptables 规则残留...${NC}" - iptables -t nat -L PREROUTING --line-numbers | grep "REDIRECT.*443" | awk '{print $1}' | tac | while read line; do - iptables -t nat -D PREROUTING $line 2>/dev/null + log_info "步骤 6/8: 清理 iptables 规则残留..." + iptables -t nat -L PREROUTING --line-numbers 2>/dev/null | grep "REDIRECT.*443" | awk '{print $1}' | tac | while read -r line; do + iptables -t nat -D PREROUTING "$line" 2>/dev/null done - echo "已清理可能的 iptables 规则残留" # 7. 删除管理脚本符号链接 - echo -e "${BLUE}步骤 7/8: 删除管理脚本符号链接...${NC}" + log_info "步骤 7/8: 删除管理脚本符号链接..." rm -f /usr/local/bin/hy2-manager rm -f /usr/local/bin/s-hy2 - echo "已删除命令快捷方式" # 8. 删除管理脚本安装目录 - echo -e "${BLUE}步骤 8/8: 删除管理脚本安装目录...${NC}" + log_info "步骤 8/8: 删除管理脚本安装目录..." if [[ -d "/opt/s-hy2" ]]; then rm -rf /opt/s-hy2 - echo "已删除 /opt/s-hy2" fi # 删除桌面快捷方式 if [[ -n "$SUDO_USER" ]]; then - rm -f "/home/$SUDO_USER/Desktop/S-Hy2-Manager.desktop" - echo "已删除桌面快捷方式" + rm -f "/home/$SUDO_USER/Desktop/S-Hy2-Manager.desktop" 2>/dev/null fi echo "" - echo -e "${GREEN}所有文件卸载完成!${NC}" + log_success "完全卸载完成!" echo -e "${BLUE}系统已完全清理,感谢使用 S-Hy2 管理脚本${NC}" echo "" echo -e "${YELLOW}重新安装:${NC}" echo "curl -fsSL https://raw.githubusercontent.com/sindricn/s-hy2/main/quick-install.sh | sudo bash" echo "" + + # 由于脚本本身已被删除,这里直接退出 + exit 0 } -# 关于脚本 +# 关于脚本 - 增强版本 about_script() { - echo -e "${CYAN}关于 Hysteria2 配置管理脚本${NC}" + clear + echo -e "${CYAN}=== 关于 Hysteria2 配置管理脚本 ===${NC}" echo "" - echo "版本: 1.0.0" + echo -e "${YELLOW}基本信息:${NC}" + echo "脚本名称: S-Hy2 Manager" + echo "版本: 1.0.1" echo "功能: 简化 Hysteria2 的安装、配置和管理" echo "" - echo "支持的功能:" - echo "- 一键安装/卸载" - echo "- 交互式配置生成" - echo "- 智能伪装域名选择" - echo "- 服务管理" - echo "- 进阶配置" + echo -e "${YELLOW}主要功能:${NC}" + echo "✓ 一键安装/卸载 Hysteria2" + echo "✓ 智能配置生成 (ACME/自签名证书)" + echo "✓ 伪装域名自动测试和选择" + echo "✓ 端口跳跃配置" + echo "✓ 混淆和高级安全选项" + echo "✓ 服务管理和监控" + echo "✓ 节点信息和订阅链接生成" + echo "✓ 故障诊断和排除" echo "" - read -p "按回车键继续..." + echo -e "${YELLOW}系统兼容性:${NC}" + echo "• Ubuntu 18.04+ / Debian 9+" + echo "• CentOS 7+ / RHEL 7+ / Fedora" + echo "• 支持 systemd 的 Linux 发行版" + echo "" + echo -e "${YELLOW}脚本信息:${NC}" + echo "安装位置: $SCRIPT_DIR" + echo "配置目录: /etc/hysteria/" + echo "日志查看: journalctl -u hysteria-server" + echo "" + echo -e "${YELLOW}获取支持:${NC}" + echo "• GitHub: https://github.com/sindricn/s-hy2" + echo "• Issues: 在 GitHub 仓库提交问题" + echo "" + wait_for_user +} + +# 输入验证函数 +validate_input() { + local input="$1" + local min="$2" + local max="$3" + + if [[ "$input" =~ ^[0-9]+$ ]] && [[ "$input" -ge "$min" ]] && [[ "$input" -le "$max" ]]; then + return 0 + else + return 1 + fi } # 主循环 main() { + # 检查基本要求 check_root + check_script_integrity + + # 设置错误处理 + trap 'echo -e "\n${RED}脚本被中断${NC}"; exit 130' INT + trap 'echo -e "\n${RED}脚本执行错误${NC}"; exit 1' ERR while true; do print_header @@ -703,51 +972,70 @@ main() { read -r choice + # 输入验证 + if ! validate_input "$choice" 0 12; then + log_error "请输入 0-12 之间的数字" + sleep 2 + continue + fi + case $choice in - 1) - install_hysteria - ;; - 2) - quick_config - ;; - 3) - manual_config - ;; - 4) - manage_service - ;; - 5) - view_logs - ;; - 6) - test_domains - ;; - 7) - server_domain_config - ;; - 8) - advanced_config - ;; - 9) - show_node_info - ;; - 10) - uninstall_hysteria - ;; - 11) - about_script - ;; + 1) install_hysteria ;; + 2) quick_config ;; + 3) manual_config ;; + 4) manage_service ;; + 5) view_logs ;; + 6) test_domains ;; + 7) server_domain_config ;; + 8) advanced_config ;; + 9) show_node_info ;; + 10) troubleshoot ;; + 11) uninstall_hysteria ;; + 12) about_script ;; 0) echo -e "${GREEN}感谢使用 Hysteria2 配置管理脚本!${NC}" exit 0 ;; - *) - echo -e "${RED}无效选项,请重新选择${NC}" - sleep 2 - ;; esac done } +# 检查依赖 +check_dependencies() { + local missing_deps=() + local required_cmds=("curl" "systemctl" "iptables") + + for cmd in "${required_cmds[@]}"; do + if ! command -v "$cmd" &> /dev/null; then + missing_deps+=("$cmd") + fi + done + + if [[ ${#missing_deps[@]} -gt 0 ]]; then + log_warn "缺少必要的依赖:" + printf ' • %s\n' "${missing_deps[@]}" + echo "" + echo "请安装缺少的依赖后重新运行脚本" + exit 1 + fi +} + +# 脚本初始化 +init_script() { + # 设置严格模式(但允许某些命令失败) + set -o pipefail + + # 检查依赖 + check_dependencies + + # 检查脚本目录权限 + if [[ ! -r "$SCRIPT_DIR" ]]; then + error_exit "无法访问脚本目录: $SCRIPT_DIR" + fi +} + # 运行主程序 -main "$@" +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + init_script + main "$@" +fi