From 02590643b5d5d145080b98887838679b038237fe Mon Sep 17 00:00:00 2001 From: eddy <1036636139@qq.com> Date: Thu, 6 Nov 2025 20:40:12 +0800 Subject: [PATCH] Init --- ddns-go.sh | 781 +++++++++++++++ ss2022-manager.sh | 1399 +++++++++++++++++++++++++++ vps_init.sh | 675 +++++++++++++ xray-manager-v25.8.31.sh | 1964 ++++++++++++++++++++++++++++++++++++++ xray-manager.sh | 1934 +++++++++++++++++++++++++++++++++++++ 5 files changed, 6753 insertions(+) create mode 100644 ddns-go.sh create mode 100644 ss2022-manager.sh create mode 100644 vps_init.sh create mode 100644 xray-manager-v25.8.31.sh create mode 100644 xray-manager.sh diff --git a/ddns-go.sh b/ddns-go.sh new file mode 100644 index 0000000..1ca528f --- /dev/null +++ b/ddns-go.sh @@ -0,0 +1,781 @@ +#!/bin/bash +# ddns-go 自动安装脚本 +# 使用方法: chmod +x ddns-go.sh && ./ddns-go.sh + +# 彩色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# 全局变量 +DDNS_VERSION="" # 当前指定的 ddns-go 版本 +DDNS_PATH="/root/ddns" +SCRIPT_VERSION="1.0.0" + +# 日志函数 +log_info() { + echo -e "${GREEN}[信息]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[警告]${NC} $1" +} + +log_error() { + echo -e "${RED}[错误]${NC} $1" +} + +# 获取最新版本号 +get_latest_version() { + local version="" + + # 方法1:利用GitHub重定向特性获取最新版本 + local redirect_url=$(curl -s -L -o /dev/null -w '%{url_effective}' https://github.com/jeessy2/ddns-go/releases/latest 2>/dev/null) + version=$(echo "$redirect_url" | grep -o 'tag/v[0-9.]*' | cut -d/ -f2 2>/dev/null) + + # 如果获取失败,尝试备用方法 + if [[ -z "$version" ]]; then + # 方法2: 通过API获取 + version=$(curl -s https://api.github.com/repos/jeessy2/ddns-go/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4 2>/dev/null) + fi + + # 如果还是失败,返回默认版本 + if [[ -z "$version" ]]; then + version="v6.9.1" # 默认版本 + fi + + # 直接返回版本号,不打印任何日志 + echo "$version" +} + +# 检测系统架构 +detect_arch() { + # 获取架构 + local arch=$(uname -m) + local arch_type="" + + # 转换架构名称为ddns-go使用的格式 + case "$arch" in + x86_64) + arch_type="linux_x86_64" + ;; + i386|i686) + arch_type="linux_x86" + ;; + aarch64|arm64) + arch_type="linux_arm64" + ;; + armv7*|armv6*) + arch_type="linux_armv7" + ;; + armv8*) + arch_type="linux_arm64" + ;; + *) + log_warn "未知架构: $arch,将尝试使用x86_64版本" + arch_type="linux_x86_64" + ;; + esac + + # 直接返回结果而不是写入临时文件 + echo "$arch_type" +} + +# 获取IP地址信息 +get_ip_info() { + local ipv4=$(curl -s ipv4.ip.sb) + local ipv6=$(curl -s ipv6.ip.sb 2>/dev/null || echo "无") + + echo "$ipv4|$ipv6" +} + +# 配置防火墙 - 仅处理 UFW +configure_firewall() { + local port=$1 + log_info "配置防火墙" + + # 检查是否安装了 ufw + if command -v ufw &>/dev/null; then + # 检查ufw是否启用 + local ufw_status=$(ufw status | grep -o "Status: active" 2>/dev/null) + + if [[ -z "$ufw_status" ]]; then + log_warn "UFW 防火墙未启用,可能需要手动配置防火墙规则" + log_info "您可以运行 'sudo ufw enable' 启用 UFW 防火墙" + return 0 + fi + + # 检查端口是否已经开放 + if ufw status | grep -q "$port/tcp"; then + log_info "端口 $port 已经开放,跳过" + return 0 + fi + + # 开放端口 + echo -n "配置 UFW 防火墙,开放端口 $port... " + if ufw allow "$port/tcp" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + log_info "已在 UFW 防火墙开放端口: $port" + else + echo -e "${RED}失败${NC}" + log_warn "无法开放端口 $port" + fi + else + log_warn "未检测到 UFW 防火墙,跳过防火墙配置" + log_info "如需管理防火墙规则,请安装 UFW: sudo apt install ufw" + fi + + return 0 +} + +# 关闭防火墙端口 - 仅处理 UFW +close_firewall_port() { + local port=$1 + log_info "关闭防火墙端口" + + # 检查是否安装了 ufw + if command -v ufw &>/dev/null; then + # 检查ufw是否启用 + local ufw_status=$(ufw status | grep -o "Status: active" 2>/dev/null) + + if [[ -z "$ufw_status" ]]; then + log_warn "UFW 防火墙未启用,跳过防火墙配置" + return 0 + fi + + # 检查端口是否已开放在UFW中 + if ! ufw status | grep -q "$port/tcp"; then + log_info "端口 $port 未在 UFW 中开放,跳过" + return 0 + fi + + # 关闭端口 + echo -n "关闭 UFW 防火墙端口 $port... " + if ufw delete allow "$port/tcp" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + log_info "已关闭 UFW 防火墙端口: $port" + else + echo -e "${RED}失败${NC}" + log_warn "无法关闭端口 $port" + fi + else + log_warn "未检测到 UFW 防火墙,跳过防火墙配置" + fi + + return 0 +} + +# 安装 ddns-go +install_ddns_go() { + clear + echo "==================================================" + echo -e "${GREEN}开始安装 ddns-go${NC}" + echo "==================================================" + + log_info "开始安装 ddns-go..." + + # 询问用户是否自定义端口 + local web_port="9876" # 默认端口 + read -rp "是否自定义web访问端口? [y/N] " custom_port + if [[ "$custom_port" =~ ^[yY]$ ]]; then + while true; do + read -rp "请输入端口号 (1-65535): " web_port + if [[ "$web_port" =~ ^[0-9]+$ ]] && [ "$web_port" -ge 1 ] && [ "$web_port" -le 65535 ]; then + log_info "将使用端口: $web_port" + break + else + log_error "无效的端口号,请输入1-65535之间的数字" + fi + done + else + log_info "将使用默认端口: $web_port" + fi + + # 更新软件包 + log_info "更新软件包..." + apt update -y && apt upgrade -y + + # 安装必要工具 + log_info "安装必要工具..." + apt install -y wget curl sudo vim git + + # 创建安装目录 + mkdir -p $DDNS_PATH + + # 1. 获取版本 - 先获取所有必要变量,不输出日志 + local version="" + if [[ -n "$DDNS_VERSION" ]]; then + version="$DDNS_VERSION" + else + version=$(get_latest_version) + fi + + # 2. 移除版本号前的 'v' + local version_num=${version#v} + + # 3. 检测系统架构 - 使用改进后的函数,直接返回结果 + local arch_suffix=$(detect_arch) + + # 4. 构建下载URL - 使用纯文本变量 + local download_file="ddns-go_${version_num}_${arch_suffix}.tar.gz" + local download_path="${DDNS_PATH}/${download_file}" + local download_url="https://github.com/jeessy2/ddns-go/releases/download/${version}/${download_file}" + + # 5. 现在安全地输出日志 + log_info "获取到最新版本:$version" + log_info "检测到系统架构: $(uname -m) (使用: $arch_suffix)" + log_info "下载链接: $download_url" + + # 6. 下载文件 - 统一使用curl下载 + log_info "正在下载 ddns-go..." + + if curl -s -L -o "$download_path" "$download_url"; then + log_info "下载成功" + else + log_error "下载失败,请检查网络连接" + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 7. 解压文件 + log_info "正在解压文件..." + if tar -zxf "$download_path" -C $DDNS_PATH; then + log_info "解压成功" + else + log_error "解压失败" + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 8. 设置权限 + chmod +x $DDNS_PATH/ddns-go + + # 9. 验证可执行文件 + log_info "验证 ddns-go 二进制文件..." + if [ ! -f $DDNS_PATH/ddns-go ]; then + log_error "未找到 ddns-go 可执行文件" + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 10. 测试运行 + if ! $DDNS_PATH/ddns-go -h > /dev/null 2>&1; then + log_error "ddns-go 可执行文件无法运行,可能是架构不匹配" + log_info "尝试检查更多架构版本..." + + # 清理之前的文件 + rm -rf $DDNS_PATH/* + + # 尝试其他架构版本 + local try_arch_list=("linux_arm64" "linux_armv7" "linux_x86" "linux_x86_64") + local success=false + + for try_arch in "${try_arch_list[@]}"; do + if [ "$try_arch" != "$arch_suffix" ]; then + log_info "尝试 $try_arch 架构版本..." + + # 构建下载信息 + local try_file="ddns-go_${version_num}_${try_arch}.tar.gz" + local try_path="${DDNS_PATH}/${try_file}" + local try_url="https://github.com/jeessy2/ddns-go/releases/download/${version}/${try_file}" + + # 下载并解压 - 统一使用curl + if curl -s -L -o "$try_path" "$try_url" && + tar -zxf "$try_path" -C $DDNS_PATH && + chmod +x $DDNS_PATH/ddns-go; then + + # 测试是否可运行 + if $DDNS_PATH/ddns-go -h > /dev/null 2>&1; then + log_info "$try_arch 架构版本可以运行" + success=true + break + else + log_warn "$try_arch 架构版本不兼容" + fi + else + log_warn "$try_arch 架构版本下载或解压失败" + fi + fi + done + + # 如果所有架构都尝试失败 + if [ "$success" = false ]; then + log_error "无法找到合适的版本,安装失败" + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + fi + + # 11. 安装服务 + log_info "安装系统服务..." + cd $DDNS_PATH + ./ddns-go -s install -l 0.0.0.0:$web_port + + # 12. 验证服务 + if systemctl status ddns-go > /dev/null 2>&1; then + log_info "ddns-go 服务已成功安装并运行" + else + log_warn "ddns-go 服务可能未正确启动,请手动检查: systemctl status ddns-go" + fi + + # 13. 获取IP信息 + local ip_info=$(get_ip_info) + local ipv4=$(echo "$ip_info" | cut -d'|' -f1) + + # 14. 配置防火墙 + configure_firewall $web_port + + log_info "ddns-go 安装完成!" + echo "==================================================" + echo -e "${GREEN}安装成功!${NC}" + echo -e "${CYAN}Web管理界面访问地址: http://$ipv4:$web_port${NC}" + echo -e "请在浏览器中打开上述地址进行配置" + echo "==================================================" + + # 清理下载文件 + rm -f "$download_path" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 卸载服务 +uninstall_ddns_go() { + clear + echo "==================================================" + echo -e "${RED}开始卸载 ddns-go${NC}" + echo "==================================================" + + # 确认卸载 + echo -e "${YELLOW}警告: 这将卸载 ddns-go 并删除相关文件${NC}" + read -rp "是否继续? [Y/n] " confirm + if [[ "$confirm" =~ ^[nN]$ ]]; then + log_info "卸载已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + log_info "正在卸载 ddns-go 服务..." + + # 获取端口信息用于关闭防火墙 + local port="" + if [ -d "$DDNS_PATH" ] && [ -f "$DDNS_PATH/config.yaml" ]; then + port=$(grep -o 'listen: 0.0.0.0:[0-9]*' "$DDNS_PATH/config.yaml" 2>/dev/null | grep -o '[0-9]*$' | head -n 1) + fi + + if [[ -z "$port" ]]; then + port=$(systemctl status ddns-go 2>/dev/null | grep -o '\-l 0.0.0.0:[0-9]*' | grep -o '[0-9]*$' | head -n 1) + fi + + if [ -d "$DDNS_PATH" ]; then + cd $DDNS_PATH + if [ -f "./ddns-go" ]; then + ./ddns-go -s uninstall + log_info "服务已卸载" + else + log_error "找不到 ddns-go 可执行文件" + fi + + # 询问是否删除文件 + read -rp "是否删除所有 ddns-go 文件? [Y/n] " delete_confirm + if [[ ! "$delete_confirm" =~ ^[nN]$ ]]; then + rm -rf $DDNS_PATH + log_info "所有文件已删除" + else + log_info "文件已保留" + fi + else + log_error "找不到 ddns-go 安装目录" + fi + + # 关闭防火墙端口 + if [[ -n "$port" ]]; then + close_firewall_port $port + fi + + log_info "ddns-go 卸载完成!" + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 更新 ddns-go +update_ddns_go() { + clear + echo "==================================================" + echo -e "${YELLOW}更新 ddns-go${NC}" + echo "==================================================" + + # 检查是否已安装 + if [ ! -d "$DDNS_PATH" ] || [ ! -f "$DDNS_PATH/ddns-go" ]; then + log_error "ddns-go 未安装,请先安装" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 获取当前版本 + local current_version="" + current_version=$($DDNS_PATH/ddns-go -v 2>&1 | grep -o 'v[0-9.]*' | head -n 1) + + if [[ -z "$current_version" ]]; then + log_warn "无法获取当前版本信息" + current_version="未知" + fi + + log_info "当前版本: $current_version" + + # 获取最新版本 + local latest_version=$(get_latest_version) + log_info "最新版本: $latest_version" + + # 比较版本 + if [[ "$current_version" == "$latest_version" ]]; then + log_info "已经是最新版本" + read -rp "是否强制更新? [y/N] " force_update + if [[ ! "$force_update" =~ ^[yY]$ ]]; then + log_info "更新已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + fi + + # 备份配置 + local config_backup="$DDNS_PATH/config.yaml.bak" + if [ -f "$DDNS_PATH/config.yaml" ]; then + log_info "备份配置文件..." + cp "$DDNS_PATH/config.yaml" "$config_backup" + fi + + # 停止服务 + log_info "停止 ddns-go 服务..." + cd $DDNS_PATH + ./ddns-go -s uninstall + + # 下载新版本 + log_info "下载新版本..." + + # 检测系统架构 - 使用改进后的函数,直接返回结果 + local arch_suffix=$(detect_arch) + local version_num=${latest_version#v} + + # 构建下载URL + local download_file="ddns-go_${version_num}_${arch_suffix}.tar.gz" + local download_path="${DDNS_PATH}/${download_file}" + local download_url="https://github.com/jeessy2/ddns-go/releases/download/${latest_version}/${download_file}" + + log_info "下载链接: $download_url" + + # 删除原来的二进制文件 + rm -f $DDNS_PATH/ddns-go + + # 下载文件 - 统一使用curl下载 + if curl -s -L -o "$download_path" "$download_url"; then + log_info "下载成功" + else + log_error "下载失败,请检查网络连接" + log_warn "将恢复服务" + if [ -f "$config_backup" ]; then + cp "$config_backup" "$DDNS_PATH/config.yaml" + fi + cd $DDNS_PATH + ./ddns-go -s install -l 0.0.0.0:9876 + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 解压文件 + log_info "解压新版本..." + if tar -zxf "$download_path" -C $DDNS_PATH; then + log_info "解压成功" + else + log_error "解压失败" + log_warn "将恢复服务" + if [ -f "$config_backup" ]; then + cp "$config_backup" "$DDNS_PATH/config.yaml" + fi + cd $DDNS_PATH + ./ddns-go -s install -l 0.0.0.0:9876 + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 设置权限 + chmod +x $DDNS_PATH/ddns-go + + # 恢复配置 + if [ -f "$config_backup" ]; then + log_info "恢复配置文件..." + cp "$config_backup" "$DDNS_PATH/config.yaml" + fi + + # 获取当前配置的端口 + local port="9876" + if [ -f "$DDNS_PATH/config.yaml" ]; then + local config_port=$(grep -o 'listen: 0.0.0.0:[0-9]*' "$DDNS_PATH/config.yaml" 2>/dev/null | grep -o '[0-9]*$' | head -n 1) + if [[ -n "$config_port" ]]; then + port="$config_port" + fi + fi + + # 安装服务 + log_info "重新安装服务..." + cd $DDNS_PATH + ./ddns-go -s install -l 0.0.0.0:$port + + # 验证更新 + local new_version=$($DDNS_PATH/ddns-go -v 2>&1 | grep -o 'v[0-9.]*' | head -n 1) + if [[ -z "$new_version" ]]; then + new_version="未知" + fi + log_info "更新完成,当前版本: $new_version" + + # 清理下载文件 + rm -f "$download_path" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 查看状态 +check_status() { + clear + echo "==================================================" + echo -e "${BLUE}ddns-go 状态检查${NC}" + echo "==================================================" + + # 检查是否安装 + if [ ! -d "$DDNS_PATH" ] || [ ! -f "$DDNS_PATH/ddns-go" ]; then + echo -e "${RED}ddns-go 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 检查版本 + local version=$($DDNS_PATH/ddns-go -v 2>&1 | grep -o 'v[0-9.]*' | head -n 1) + if [[ -z "$version" ]]; then + version="未知" + fi + echo -e "ddns-go 版本: ${GREEN}$version${NC}" + + # 检查服务状态 + echo -n "服务状态: " + if systemctl is-active ddns-go &>/dev/null; then + echo -e "${GREEN}运行中${NC}" + else + echo -e "${RED}未运行${NC}" + fi + + echo -n "自启动状态: " + if systemctl is-enabled ddns-go &>/dev/null; then + echo -e "${GREEN}已启用${NC}" + else + echo -e "${RED}未启用${NC}" + fi + + # 检查配置文件 + echo -n "配置文件: " + if [ -f "$DDNS_PATH/config.yaml" ]; then + echo -e "${GREEN}存在${NC}" + else + echo -e "${RED}不存在${NC}" + fi + + # 获取内存和 CPU 使用情况 + echo "资源使用情况:" + ps -aux | grep ddns-go | grep -v grep | awk '{print "内存使用: " $4 "%, CPU使用: " $3 "%"}' + + # 获取端口信息 + echo -n "端口状态: " + local port=$(grep -o 'listen: 0.0.0.0:[0-9]*' "$DDNS_PATH/config.yaml" 2>/dev/null | grep -o '[0-9]*$' | head -n 1) + if [[ -z "$port" ]]; then + port=$(systemctl status ddns-go 2>/dev/null | grep -o '\-l 0.0.0.0:[0-9]*' | grep -o '[0-9]*$' | head -n 1) + fi + + if [[ -n "$port" ]]; then + if command -v ss &>/dev/null; then + if ss -tuln | grep -q ":$port "; then + echo -e "${GREEN}端口 $port 已开放${NC}" + else + echo -e "${RED}端口 $port 未开放${NC}" + fi + elif command -v netstat &>/dev/null; then + if netstat -tuln | grep -q ":$port "; then + echo -e "${GREEN}端口 $port 已开放${NC}" + else + echo -e "${RED}端口 $port 未开放${NC}" + fi + else + echo -e "${YELLOW}无法检查端口状态${NC}" + fi + else + echo -e "${YELLOW}未找到端口信息${NC}" + fi + + # 检查DNS解析记录 + echo -e "\n上次DNS更新信息:" + if [ -f "$DDNS_PATH/config.yaml" ]; then + grep -A 10 'ipv4' "$DDNS_PATH/config.yaml" | head -n 10 + else + echo "未找到配置文件,无法获取DNS更新信息" + fi + + # 显示日志 + echo -e "\n最近日志:" + if command -v journalctl &>/dev/null; then + journalctl -u ddns-go --no-pager -n 10 + else + echo "找不到日志信息" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 重启服务 +restart_service() { + clear + echo "==================================================" + echo -e "${GREEN}重启 ddns-go 服务${NC}" + echo "==================================================" + + # 检查是否已安装 + if [ ! -d "$DDNS_PATH" ] || [ ! -f "$DDNS_PATH/ddns-go" ]; then + log_error "ddns-go 未安装,请先安装" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + log_info "正在重启 ddns-go 服务..." + + # 尝试使用systemctl重启 + if systemctl restart ddns-go; then + log_info "服务已重启" + else + log_warn "systemctl重启失败,尝试手动重启..." + cd $DDNS_PATH + ./ddns-go -s uninstall + sleep 1 + + # 获取当前配置的端口 + local web_port="9876" + if [ -f "$DDNS_PATH/config.yaml" ]; then + local config_port=$(grep -o 'listen: 0.0.0.0:[0-9]*' "$DDNS_PATH/config.yaml" 2>/dev/null | grep -o '[0-9]*$' | head -n 1) + if [[ -n "$config_port" ]]; then + web_port="$config_port" + fi + fi + + ./ddns-go -s install -l 0.0.0.0:$web_port + log_info "服务已手动重启" + fi + + # 获取IP信息 + local ip_info=$(get_ip_info) + local ipv4=$(echo "$ip_info" | cut -d'|' -f1) + + log_info "服务已重启,Web管理界面: http://$ipv4:$web_port" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 显示帮助 +show_help() { + echo "ddns-go 管理脚本 v${SCRIPT_VERSION}" + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " install 直接安装 ddns-go" + echo " uninstall 直接卸载 ddns-go" + echo " restart 重启 ddns-go 服务" + echo " status 查看 ddns-go 状态" + echo " update 更新 ddns-go" + echo " ip 显示当前公网IP地址" + echo " help 显示此帮助信息" + echo "" + echo "无参数运行脚本将显示交互式菜单" +} + +# 菜单函数 +show_menu() { + clear + echo "==================================================" + echo -e "${CYAN}ddns-go 管理脚本 v${SCRIPT_VERSION}${NC}" + echo "==================================================" + echo -e "1) ${GREEN}安装 ddns-go${NC}" + echo -e "2) ${RED}卸载 ddns-go${NC}" + echo -e "3) ${YELLOW}更新 ddns-go${NC}" + echo -e "4) ${BLUE}查看 ddns-go 状态${NC}" + echo -e "5) ${GREEN}重启 ddns-go 服务${NC}" + echo -e "0) ${RED}退出${NC}" + echo "==================================================" + echo "" + read -rp "请输入选项 [0-5]: " choice + + case $choice in + 1) install_ddns_go ;; + 2) uninstall_ddns_go ;; + 3) update_ddns_go ;; + 4) check_status ;; + 5) restart_service ;; + 0) exit 0 ;; + *) log_error "无效选项" && sleep 2 && show_menu ;; + esac +} + +# 主函数 +main() { + # 处理命令行参数 + if [[ $# -gt 0 ]]; then + case "$1" in + -h|--help|help) + show_help + exit 0 + ;; + install) + install_ddns_go + exit 0 + ;; + uninstall) + uninstall_ddns_go + exit 0 + ;; + restart) + restart_service + exit 0 + ;; + status) + check_status + exit 0 + ;; + update) + update_ddns_go + exit 0 + ;; + *) + log_error "未知参数: $1" + show_help + exit 1 + ;; + esac + fi + + # 无参数则显示菜单 + show_menu +} + +# 执行主函数 +main "$@" diff --git a/ss2022-manager.sh b/ss2022-manager.sh new file mode 100644 index 0000000..2f06696 --- /dev/null +++ b/ss2022-manager.sh @@ -0,0 +1,1399 @@ +#!/bin/bash +# Shadowsocks 管理脚本 - 集成安装、卸载和管理功能 +# 专门支持 shadowsocks-libev 协议 +# 使用方法: chmod +x shadowsocks-manager.sh && ./shadowsocks-manager.sh +# 使用方法: chmod +x ss2022-manager.sh && ./ss2022-manager.sh + + +# 全局变量 +SS_PATH="/usr/local/bin" +CONFIG_PATH="/etc/shadowsocks-libev" +SS_PORT="" # Shadowsocks 端口 +LOG_PATH="/var/log/shadowsocks" +LOG_FILE="/var/log/shadowsocks-manager.log" +SCRIPT_VERSION="1.0.0" +SS_PASSWORD="" +SS_METHOD="aes-128-gcm" # 默认使用 AEAD 加密方法 +CONFIG_BACKUP="" +SERVER_IP="" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# 进度条函数 +show_progress() { + local pid=$1 + local delay=0.1 + local spinstr='|/-\' + local temp + local count=0 + local start_time=$(date +%s) + echo -n " " + + while ps -p $pid > /dev/null; do + temp=${spinstr#?} + printf "\r[%c] %s 已进行 %ds" "$spinstr" "$2" "$(($(date +%s) - start_time))" + spinstr=$temp${spinstr%"$temp"} + sleep $delay + count=$((count + 1)) + done + printf "\r\033[K" +} + +# 进度条显示函数 - 适用于apt操作 +apt_progress() { + local cmd="$1" + local msg="$2" + local logfile="$LOG_FILE.tmp" + + echo -e "${CYAN}开始 $msg...${NC}" + touch "$logfile" + ($cmd 2>&1 | tee -a "$LOG_FILE" > "$logfile") & + local pid=$! + + # 显示进度条 + local start_time=$(date +%s) + local dots="" + local status="" + local progress=0 + local last_line="" + local delay=0.2 + + while ps -p $pid > /dev/null; do + # 读取最后一行日志 + if [[ -f "$logfile" ]]; then + last_line=$(tail -n 1 "$logfile") + + # 尝试从输出中提取进度信息 + if [[ $last_line == *%* ]]; then + status="${last_line%%(*}" + progress="${last_line#*(}" + progress="${progress%%)*}" + printf "\r\033[K${CYAN}[$msg]${NC} %s %s " "$status" "$progress" + else + dots="${dots}." + if [[ ${#dots} -gt 5 ]]; then dots="."; fi + elapsed=$(($(date +%s) - start_time)) + printf "\r\033[K${CYAN}[$msg]${NC} 进行中%s (%ds)" "$dots" "$elapsed" + fi + fi + sleep $delay + done + + if wait $pid; then + printf "\r\033[K${GREEN}[$msg]${NC} 完成! 用时: %ds\n" "$(($(date +%s) - start_time))" + rm -f "$logfile" + return 0 + else + printf "\r\033[K${RED}[$msg]${NC} 失败! 查看日志: $LOG_FILE\n" + rm -f "$logfile" + return 1 + fi +} + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" | tee -a "$LOG_FILE" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_FILE" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 | tee -a "$LOG_FILE" +} + +log_debug() { + echo -e "${BLUE}[DEBUG]${NC} $1" | tee -a "$LOG_FILE" +} + +# 检查是否有 root 权限 +check_root() { + if [[ $EUID -ne 0 ]]; then + log_error "此脚本需要 root 权限运行" + exit 1 + fi +} + +# 检查系统环境 +check_system() { + # 显示系统信息 + echo "系统信息:" + echo "------------------------" + if [ -f /etc/os-release ]; then + cat /etc/os-release | grep "PRETTY_NAME" | cut -d= -f2- | tr -d '"' + fi + echo "内核版本: $(uname -r)" + echo "架构: $(uname -m)" + echo "------------------------" + + # 检查是否为 Debian/Ubuntu 系统 + if [[ ! -f /etc/debian_version && ! -f /etc/lsb-release ]]; then + log_warn "未检测到 Debian/Ubuntu 系统,脚本可能无法正常工作" + read -rp "是否继续? [y/N] " response + if [[ ! "$response" =~ ^[yY]$ ]]; then + exit 1 + fi + fi + + # 检查网络连接 + echo -n "检查网络连接... " + if ping -c 1 -W 2 github.com &>/dev/null; then + echo -e "${GREEN}连接正常${NC}" + else + echo -e "${YELLOW}无法连接到 GitHub${NC}" + log_warn "无法连接到 GitHub,请检查网络连接" + read -rp "是否继续? [y/N] " response + if [[ ! "$response" =~ ^[yY]$ ]]; then + exit 1 + fi + fi +} + +# 备份函数 +backup_config() { + if [[ -d "$CONFIG_PATH" ]]; then + CONFIG_BACKUP="${CONFIG_PATH}_backup_$(date +%Y%m%d%H%M%S)" + log_info "备份现有配置到 $CONFIG_BACKUP" + cp -r "$CONFIG_PATH" "$CONFIG_BACKUP" || log_warn "配置备份失败" + + # 备份 Shadowsocks 信息文件 + if [[ -f "/root/shadowsocks_info.txt" ]]; then + cp "/root/shadowsocks_info.txt" "${CONFIG_BACKUP}/shadowsocks_info.txt.bak" || log_warn "Shadowsocks 信息文件备份失败" + fi + else + log_warn "找不到配置目录,跳过备份" + fi +} + +# 显示帮助信息 +show_help() { + echo "Shadowsocks 管理脚本 v${SCRIPT_VERSION}" + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " -h, --help 显示此帮助信息" + echo " -i, --install 直接运行安装" + echo " -u, --uninstall 直接运行卸载" + echo " -s, --status 查看 Shadowsocks 状态" + echo " -up, --update 更新 Shadowsocks" + echo "" + echo "无参数运行脚本将显示交互式菜单" +} + +# 菜单函数 +show_menu() { + clear + echo "==================================================" + echo -e "${CYAN}Shadowsocks 管理脚本 v${SCRIPT_VERSION}${NC}" + echo -e "${CYAN}(shadowsocks-libev)${NC}" + echo "==================================================" + echo -e "1) ${GREEN}安装 Shadowsocks${NC}" + echo -e "2) ${RED}卸载 Shadowsocks${NC}" + echo -e "3) ${YELLOW}更新 Shadowsocks${NC}" + echo -e "4) ${BLUE}查看 Shadowsocks 状态${NC}" + echo -e "5) ${CYAN}查看 Shadowsocks 配置信息${NC}" + echo -e "6) ${GREEN}重启 Shadowsocks 服务${NC}" + echo -e "7) ${YELLOW}修改服务器配置${NC}" + echo -e "0) ${RED}退出${NC}" + echo "==================================================" + echo "" + read -rp "请输入选项 [0-7]: " choice + + case $choice in + 1) install_shadowsocks ;; + 2) uninstall_shadowsocks ;; + 3) update_shadowsocks ;; + 4) check_status ;; + 5) show_config ;; + 6) restart_service ;; + 7) modify_config ;; + 0) exit 0 ;; + *) log_error "无效选项" && sleep 2 && show_menu ;; + esac +} + +# 安装依赖 +install_dependencies() { + log_info "安装必要依赖" + + # 更新软件包列表 + apt_progress "apt-get update" "更新软件包列表" || { + log_error "更新软件包列表失败" + return 1 + } + + # 安装必要工具 + local deps=(curl wget jq) + for dep in "${deps[@]}"; do + if ! command -v "$dep" &>/dev/null; then + apt_progress "apt-get install -y $dep" "安装 $dep" || { + log_error "安装 $dep 失败" + return 1 + } + else + log_info "$dep 已安装,跳过" + fi + done + + # 检查安装结果 + local all_installed=true + for dep in "${deps[@]}"; do + if ! command -v "$dep" &>/dev/null; then + log_error "$dep 安装失败" + all_installed=false + fi + done + + if [ "$all_installed" = true ]; then + log_info "所有依赖安装完成" + return 0 + else + log_error "部分依赖安装失败" + return 1 + fi +} + +# 安装 shadowsocks-libev +install_shadowsocks_libev() { + log_info "开始安装 shadowsocks-libev" + + # 检查是否已安装 + if command -v ss-server &>/dev/null; then + log_info "检测到已安装的 shadowsocks-libev,将进行更新" + + # 停止服务 + if systemctl is-active shadowsocks-libev &>/dev/null; then + echo -n "停止 Shadowsocks 服务... " + if systemctl stop shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Shadowsocks 服务" + fi + fi + fi + + # 添加 shadowsocks-libev 官方仓库(如果是 Ubuntu) + if command -v lsb_release &>/dev/null && [[ $(lsb_release -si) == "Ubuntu" ]]; then + local ubuntu_version=$(lsb_release -sr) + if [[ $(echo "$ubuntu_version >= 16.04" | bc) -eq 1 ]]; then + log_info "添加 shadowsocks-libev 官方 PPA" + apt_progress "add-apt-repository -y ppa:max-c-lv/shadowsocks-libev" "添加 PPA 仓库" + apt_progress "apt-get update" "更新软件包列表" + fi + fi + + # 安装 shadowsocks-libev + apt_progress "apt-get install -y shadowsocks-libev" "安装 shadowsocks-libev" || { + log_error "安装 shadowsocks-libev 失败" + return 1 + } + + # 验证安装 + if command -v ss-server &>/dev/null; then + log_info "shadowsocks-libev 安装成功" + return 0 + else + log_error "shadowsocks-libev 安装失败" + return 1 + fi +} + +# 生成随机配置值 +generate_random_values() { + log_info "生成随机配置值" + + # 询问用户是否指定端口 + if [[ -z "$SS_PORT" ]]; then + read -rp "是否指定端口? [y/N] " specify_port + if [[ "$specify_port" =~ ^[yY]$ ]]; then + # 用户选择指定端口 + while true; do + read -rp "请输入端口号 (1-65535): " SS_PORT + # 验证端口是否为有效数字 + if ! [[ "$SS_PORT" =~ ^[0-9]+$ ]] || [ "$SS_PORT" -lt 1 ] || [ "$SS_PORT" -gt 65535 ]; then + log_error "无效的端口号,请输入1-65535之间的数字" + continue + fi + + # 检查端口是否被占用 + if ss -tuln | grep -q ":$SS_PORT "; then + log_warn "端口 $SS_PORT 已被占用,请选择其他端口" + continue + fi + + log_info "将使用指定端口: $SS_PORT" + break + done + else + # 用户选择随机端口 + local attempts=0 + while [[ "$attempts" -lt 10 ]]; do + SS_PORT=$(shuf -i 10000-60000 -n 1) + # 检查端口是否被占用 + if ! ss -tuln | grep -q ":$SS_PORT "; then + log_info "生成随机端口: $SS_PORT" + break + fi + attempts=$((attempts + 1)) + done + if [[ "$attempts" -eq 10 ]]; then + log_warn "无法找到未占用的端口,使用随机端口: $SS_PORT" + fi + fi + fi + + # 生成密码 + if [[ -z "$SS_PASSWORD" ]]; then + read -rp "是否指定密码? [y/N] " specify_password + if [[ "$specify_password" =~ ^[yY]$ ]]; then + while true; do + read -rp "请输入密码 (至少8位): " SS_PASSWORD + if [[ ${#SS_PASSWORD} -lt 8 ]]; then + log_error "密码至少需要8位" + continue + fi + log_info "将使用指定密码" + break + done + else + # 生成随机密码 + SS_PASSWORD=$(openssl rand -base64 16) + log_info "生成随机密码: $SS_PASSWORD" + fi + fi + + # 选择加密方法 + echo "选择加密方法:" + echo "1) chacha20-ietf-poly1305" + echo "2) aes-256-gcm" + echo "3) aes-192-gcm" + echo "4) aes-128-gcm (默认)" + read -rp "请选择加密方法 [1-4, 默认4]: " method_choice + + case $method_choice in + 1) SS_METHOD="chacha20-ietf-poly1305" ;; + 2) SS_METHOD="aes-256-gcm" ;; + 3) SS_METHOD="aes-192-gcm" ;; + *) SS_METHOD="aes-128-gcm" ;; + esac + + log_info "选择的加密方法: $SS_METHOD" + + # 获取服务器IP + get_server_ip +} + +# 获取服务器IP +get_server_ip() { + log_info "获取服务器IP地址" + + if [[ -n "$SERVER_IP" ]]; then + log_info "使用已设置的IP: $SERVER_IP" + return 0 + fi + + # 尝试首选方法获取公网IP + SERVER_IP=$(curl -s -m 5 https://api.ipify.org 2>/dev/null) + + # 验证获取到的IP是否为有效IPv4地址 + if [[ -n "$SERVER_IP" && "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + log_info "成功获取公网IP: $SERVER_IP" + return 0 + fi + + # 如果第一个方法失败,尝试备用方法 + local backup_services=("https://ifconfig.me" "https://ip.sb" "https://ipinfo.io/ip") + + for service in "${backup_services[@]}"; do + SERVER_IP=$(curl -s -m 3 "$service" 2>/dev/null) + if [[ -n "$SERVER_IP" && "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + log_info "成功从 $service 获取公网IP: $SERVER_IP" + return 0 + fi + done + + # 如果所有公网IP获取方式都失败,则使用本地IP + if command -v hostname &>/dev/null; then + SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}') + fi + + # 如果hostname命令失败,尝试使用ip命令 + if [[ -z "$SERVER_IP" && -x "$(command -v ip)" ]]; then + SERVER_IP=$(ip -4 addr show scope global | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n 1) + fi + + if [[ -n "$SERVER_IP" ]]; then + log_warn "无法获取公网IP,使用本地IP: $SERVER_IP" + return 0 + else + log_error "无法获取任何有效IP地址,将使用127.0.0.1作为占位符" + SERVER_IP="127.0.0.1" + return 1 + fi +} + +# 配置 Shadowsocks +configure_shadowsocks() { + log_info "配置 Shadowsocks" + + # 生成随机值 + generate_random_values + + # 创建配置目录 + mkdir -p "$CONFIG_PATH" + mkdir -p "$LOG_PATH" + + log_info "创建 Shadowsocks 配置文件" + + # 创建主配置文件 + cat > "$CONFIG_PATH/config.json" << EOF +{ + "server": "0.0.0.0", + "server_port": $SS_PORT, + "password": "$SS_PASSWORD", + "method": "$SS_METHOD", + "timeout": 300, + "fast_open": true, + "workers": 1, + "prefer_ipv6": false, + "no_delay": true, + "reuse_port": true +} +EOF + + # 创建客户端配置示例 + cat > "$CONFIG_PATH/client_config.json" << EOF +{ + "server": "$SERVER_IP", + "server_port": $SS_PORT, + "local_address": "127.0.0.1", + "local_port": 1080, + "password": "$SS_PASSWORD", + "method": "$SS_METHOD", + "timeout": 300, + "fast_open": true +} +EOF + + if [[ -f "$CONFIG_PATH/config.json" ]]; then + log_info "配置文件创建成功" + else + log_error "配置文件创建失败" + return 1 + fi + + return 0 +} + +# 配置防火墙 +configure_firewall() { + log_info "配置防火墙" + + # 检测和关闭旧端口 + local old_ports=() + + # 从备份中查找旧端口 + if [[ -n "$CONFIG_BACKUP" && -f "$CONFIG_BACKUP/config.json" ]]; then + log_info "检测旧配置中的端口" + if command -v jq &>/dev/null; then + local detected_port=$(jq '.server_port' "$CONFIG_BACKUP/config.json" 2>/dev/null) + if [[ "$detected_port" != "null" && -n "$detected_port" ]]; then + old_ports+=("$detected_port") + log_info "检测到旧端口: $detected_port" + fi + else + local detected_port=$(grep -o '"server_port": [0-9]*' "$CONFIG_BACKUP/config.json" | awk '{print $2}') + if [[ -n "$detected_port" ]]; then + old_ports+=("$detected_port") + log_info "检测到旧端口: $detected_port" + fi + fi + fi + + # 检查是否安装了 ufw + if command -v ufw &>/dev/null; then + # 检查ufw是否启用 + local ufw_status=$(ufw status | grep -o "Status: active" 2>/dev/null) + + if [[ -z "$ufw_status" ]]; then + log_warn "UFW 防火墙未启用,可能需要手动配置防火墙规则" + log_info "您可以运行 'sudo ufw enable' 启用 UFW 防火墙" + return 0 + fi + + # 关闭旧端口 + if [[ ${#old_ports[@]} -gt 0 ]]; then + log_info "开始关闭旧端口" + for port in "${old_ports[@]}"; do + if [[ "$port" -eq "$SS_PORT" ]]; then + continue # 跳过当前端口 + fi + + echo -n "关闭 UFW 防火墙端口 $port... " + if ufw delete allow "$port" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + else + echo -e "${RED}失败${NC}" + fi + done + fi + + # 开放新端口 + echo -n "配置 UFW 防火墙开放端口 $SS_PORT... " + if ufw allow "$SS_PORT" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + log_info "已在 UFW 防火墙开放端口: $SS_PORT" + else + echo -e "${RED}失败${NC}" + log_warn "无法开放端口 $SS_PORT" + fi + else + log_warn "未检测到 UFW 防火墙,跳过防火墙配置" + fi + + return 0 +} + +# 生成客户端信息 +generate_client_info() { + log_info "生成客户端信息" + + # 确保IP地址已获取 + if [[ -z "$SERVER_IP" ]]; then + get_server_ip + fi + + # 生成 Shadowsocks URI + local userinfo="${SS_METHOD}:${SS_PASSWORD}" + local userinfo_encoded=$(echo -n "$userinfo" | base64 -w 0) + local share_uri="ss://${userinfo_encoded}@${SERVER_IP}:${SS_PORT}#Shadowsocks-Server" + + # 保存客户端信息 + cat > /root/shadowsocks_info.txt << EOF +========================= Shadowsocks 配置信息 ========================= +服务器地址: ${SERVER_IP} +端口: ${SS_PORT} +密码: ${SS_PASSWORD} +加密方法: ${SS_METHOD} +================================================================== + +分享链接 (SIP002 格式): +${share_uri} + +二维码链接: +https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${share_uri} + +================================================================== + +客户端配置文件: ${CONFIG_PATH}/client_config.json + +支持的客户端: +- Android: shadowsocks-android +- Windows: shadowsocks-windows +- macOS: ShadowsocksX-NG +- iOS: Shadowrocket, Quantumult +- Linux: shadowsocks-libev + +================================================================== + +服务控制: +启动: systemctl start shadowsocks-libev +停止: systemctl stop shadowsocks-libev +重启: systemctl restart shadowsocks-libev +状态: systemctl status shadowsocks-libev + +配置文件: ${CONFIG_PATH}/config.json +日志文件: /var/log/shadowsocks/shadowsocks.log +================================================================== +EOF + + log_info "客户端信息已保存到 /root/shadowsocks_info.txt" + + # 打印信息 + cat /root/shadowsocks_info.txt + + return 0 +} + +# 创建系统服务 +create_service() { + log_info "创建 Shadowsocks 系统服务" + + # 检查并清理现有服务 + if [[ -f /etc/systemd/system/shadowsocks-libev.service ]]; then + log_info "检测到现有服务文件,清理现有服务" + systemctl stop shadowsocks-libev &>/dev/null + systemctl disable shadowsocks-libev &>/dev/null + + # 确保进程完全停止 + pkill -f ss-server &>/dev/null + sleep 2 + + # 删除旧服务文件 + rm -f /etc/systemd/system/shadowsocks-libev.service + systemctl daemon-reload + log_info "现有服务已清理" + fi + + # 创建服务文件 + cat > /etc/systemd/system/shadowsocks-libev.service << EOF +[Unit] +Description=Shadowsocks-libev Default Server Service +Documentation=man:shadowsocks-libev(8) +After=network-online.target + +[Service] +Type=simple +User=root +LimitNOFILE=32768 +ExecStart=/usr/bin/ss-server -c $CONFIG_PATH/config.json -u -v +ExecReload=/bin/kill -s HUP \$MAINPID +ExecStop=/bin/kill -s TERM \$MAINPID +TimeoutStopSec=10 +KillMode=mixed +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=multi-user.target +EOF + + # 重新加载 systemd 配置并启用服务 + systemctl daemon-reload + + echo -n "启用 Shadowsocks 服务... " + if systemctl enable shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "无法启用 Shadowsocks 服务" + return 1 + fi + + echo -n "启动 Shadowsocks 服务... " + if systemctl start shadowsocks-libev; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "无法启动 Shadowsocks 服务" + return 1 + fi + + # 等待服务启动并验证配置 + echo -n "验证服务配置... " + sleep 3 + + # 检查服务是否真正运行 + if systemctl is-active shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}服务运行正常${NC}" + + # 验证端口监听 + local config_port="" + if command -v jq &>/dev/null; then + config_port=$(jq -r '.server_port' "$CONFIG_PATH/config.json" 2>/dev/null) + else + config_port=$(grep -o '"server_port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}' 2>/dev/null) + fi + + if [[ -n "$config_port" ]]; then + local retry_count=0 + while [[ $retry_count -lt 10 ]]; do + if command -v netstat &>/dev/null; then + if netstat -tuln | grep -q ":$config_port "; then + log_info "端口 $config_port 监听正常" + break + fi + elif command -v ss &>/dev/null; then + if ss -tuln | grep -q ":$config_port "; then + log_info "端口 $config_port 监听正常" + break + fi + fi + sleep 1 + retry_count=$((retry_count + 1)) + done + + if [[ $retry_count -eq 10 ]]; then + log_warn "警告: 端口 $config_port 可能未正确监听,请检查配置" + fi + fi + else + echo -e "${RED}服务启动异常${NC}" + log_error "服务启动后状态异常,请检查日志" + systemctl status shadowsocks-libev --no-pager -l + return 1 + fi + + return 0 +} + +# 安装完整流程 +install_shadowsocks() { + clear + echo "==================================================" + echo -e "${GREEN}开始安装 Shadowsocks${NC}" + echo "==================================================" + + # 检查是否为 root + check_root + + # 检查系统环境 + check_system + + # 备份现有配置 + backup_config + + # 安装依赖 + install_dependencies || { + log_error "安装依赖失败,退出安装" + return 1 + } + + # 安装 shadowsocks-libev + install_shadowsocks_libev || { + log_error "安装 shadowsocks-libev 失败,退出安装" + return 1 + } + + # 配置 Shadowsocks + configure_shadowsocks || { + log_error "配置 Shadowsocks 失败,退出安装" + return 1 + } + + # 创建系统服务 + create_service || { + log_error "创建系统服务失败,但会继续安装过程" + } + + # 配置防火墙 + configure_firewall + + # 生成客户端信息 + generate_client_info + + echo "" + echo "==================================================" + echo -e "${GREEN}Shadowsocks 安装完成!${NC}" + echo "==================================================" + + # 最终验证 + echo -e "\n${BLUE}最终验证结果:${NC}" + + # 检查服务状态 + if systemctl is-active shadowsocks-libev &>/dev/null; then + echo -e "服务状态: ${GREEN}运行中${NC}" + else + echo -e "服务状态: ${RED}未运行${NC}" + fi + + # 检查端口监听 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + local current_port="" + if command -v jq &>/dev/null; then + current_port=$(jq -r '.server_port' "$CONFIG_PATH/config.json" 2>/dev/null) + else + current_port=$(grep -o '"server_port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}' 2>/dev/null) + fi + + if [[ -n "$current_port" ]]; then + if command -v netstat &>/dev/null; then + if netstat -tuln | grep -q ":$current_port "; then + echo -e "端口状态: ${GREEN}$current_port 正常监听${NC}" + else + echo -e "端口状态: ${RED}$current_port 未监听${NC}" + fi + elif command -v ss &>/dev/null; then + if ss -tuln | grep -q ":$current_port "; then + echo -e "端口状态: ${GREEN}$current_port 正常监听${NC}" + else + echo -e "端口状态: ${RED}$current_port 未监听${NC}" + fi + fi + fi + fi + + echo -e "\n${YELLOW}重要提醒:${NC}" + echo "1. 客户端信息已保存到 /root/shadowsocks_info.txt" + echo "2. 如果使用云服务器,请在控制台开放对应端口" + echo "3. 可以运行脚本选择'4'查看详细状态" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 停止 Shadowsocks 服务 +stop_shadowsocks_service() { + log_info "停止 Shadowsocks 服务" + + if systemctl is-active shadowsocks-libev &>/dev/null; then + echo -n "正在停止 Shadowsocks 服务... " + if systemctl stop shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Shadowsocks 服务,将尝试继续卸载" + fi + else + log_info "Shadowsocks 服务未运行" + fi + + echo -n "禁用 Shadowsocks 服务自启动... " + if systemctl disable shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${YELLOW}失败${NC}" + log_warn "无法禁用 Shadowsocks 服务自启动" + fi +} + +# 关闭防火墙端口 +close_firewall_port() { + log_info "尝试关闭之前开放的防火墙端口" + + # 检查是否安装了 ufw + if ! command -v ufw &>/dev/null; then + log_warn "未检测到 UFW 防火墙,跳过防火墙配置关闭" + return 0 + fi + + # 尝试从配置文件中读取端口 + local ports=() + + if [[ -f "$CONFIG_PATH/config.json" ]]; then + if command -v jq &>/dev/null; then + local port=$(jq '.server_port' "$CONFIG_PATH/config.json" 2>/dev/null) + if [[ "$port" != "null" && -n "$port" ]]; then + ports+=("$port") + fi + else + local port=$(grep -o '"server_port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}') + if [[ -n "$port" ]]; then + ports+=("$port") + fi + fi + fi + + # 如果找到了端口,关闭防火墙规则 + if [[ ${#ports[@]} -gt 0 ]]; then + for port in "${ports[@]}"; do + echo -n "关闭 UFW 防火墙端口 $port... " + if ufw delete allow "$port" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + else + echo -e "${RED}失败${NC}" + fi + done + fi +} + +# 删除 Shadowsocks 文件 +remove_shadowsocks_files() { + log_info "删除 Shadowsocks 文件" + + # 删除配置目录 + echo -n "删除配置目录... " + if [[ -d "$CONFIG_PATH" ]]; then + if rm -rf "$CONFIG_PATH"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + fi + else + echo -e "${YELLOW}配置目录不存在${NC}" + fi + + # 删除日志目录 + echo -n "删除日志目录... " + if [[ -d "$LOG_PATH" ]]; then + if rm -rf "$LOG_PATH"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + fi + else + echo -e "${YELLOW}日志目录不存在${NC}" + fi + + # 删除服务文件 + echo -n "删除服务文件... " + if rm -f /etc/systemd/system/shadowsocks-libev.service; then + echo -e "${GREEN}成功${NC}" + systemctl daemon-reload + else + echo -e "${YELLOW}服务文件不存在${NC}" + fi + + # 卸载 shadowsocks-libev 软件包 + echo -n "卸载 shadowsocks-libev... " + if apt-get remove -y shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${YELLOW}卸载失败或软件包未安装${NC}" + fi + + return 0 +} + +# 完整卸载流程 +uninstall_shadowsocks() { + clear + echo "==================================================" + echo -e "${RED}开始卸载 Shadowsocks${NC}" + echo "==================================================" + + # 确认卸载 + echo -e "${YELLOW}警告: 这将卸载 Shadowsocks 并删除相关文件${NC}" + read -rp "是否继续? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "卸载已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 检查 root 权限 + check_root + + # 备份配置 + backup_config + + # 停止服务 + stop_shadowsocks_service + + # 关闭防火墙端口 + close_firewall_port + + # 删除文件 + remove_shadowsocks_files + + echo "" + log_info "Shadowsocks 卸载完成" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 检查 Shadowsocks 状态 +check_status() { + clear + echo "==================================================" + echo -e "${BLUE}Shadowsocks 状态检查${NC}" + echo "==================================================" + + # 检查是否安装 + if ! command -v ss-server &>/dev/null; then + echo -e "${RED}Shadowsocks 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 检查版本 + echo -n "Shadowsocks 版本: " + ss-server -h 2>&1 | grep -o 'shadowsocks-libev [0-9]\+\.[0-9]\+\.[0-9]\+' || echo "shadowsocks-libev $(dpkg -l shadowsocks-libev 2>/dev/null | grep '^ii' | awk '{print $3}' || echo '未知版本')" + + # 检查服务状态 + echo -n "服务状态: " + if systemctl is-active shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}运行中${NC}" + else + echo -e "${RED}未运行${NC}" + fi + + echo -n "自启动状态: " + if systemctl is-enabled shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}已启用${NC}" + else + echo -e "${RED}未启用${NC}" + fi + + # 检查端口 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + local current_port="" + if command -v jq &>/dev/null; then + current_port=$(jq '.server_port' "$CONFIG_PATH/config.json" 2>/dev/null) + else + current_port=$(grep -o '"server_port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}') + fi + + if [[ -n "$current_port" && "$current_port" != "null" ]]; then + echo -n "端口 $current_port 状态: " + if command -v netstat &>/dev/null; then + if netstat -tuln | grep -q ":$current_port "; then + echo -e "${GREEN}已开放${NC}" + else + echo -e "${RED}未开放${NC}" + fi + elif command -v ss &>/dev/null; then + if ss -tuln | grep -q ":$current_port "; then + echo -e "${GREEN}已开放${NC}" + else + echo -e "${RED}未开放${NC}" + fi + else + echo -e "${YELLOW}无法检查${NC}" + fi + fi + fi + + echo -e "\n最近的日志:" + if systemctl status shadowsocks-libev &>/dev/null; then + systemctl status shadowsocks-libev --no-pager -l + else + echo "无法获取服务日志" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 显示配置信息 +show_config() { + clear + echo "==================================================" + echo -e "${CYAN}Shadowsocks 配置信息${NC}" + echo "==================================================" + + # 检查是否已安装 + if ! command -v ss-server &>/dev/null; then + echo -e "${RED}Shadowsocks 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 显示配置文件内容 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + echo "服务器配置:" + if command -v jq &>/dev/null; then + jq . "$CONFIG_PATH/config.json" + else + cat "$CONFIG_PATH/config.json" + fi + else + echo -e "${RED}找不到服务器配置文件${NC}" + fi + + echo -e "\n" + + # 显示客户端信息 + if [[ -f "/root/shadowsocks_info.txt" ]]; then + echo "客户端信息:" + cat /root/shadowsocks_info.txt + else + echo -e "${RED}找不到客户端信息文件${NC}" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 重启 Shadowsocks 服务 +restart_service() { + clear + echo "==================================================" + echo -e "${GREEN}重启 Shadowsocks 服务${NC}" + echo "==================================================" + + # 检查是否已安装 + if ! command -v ss-server &>/dev/null; then + echo -e "${RED}Shadowsocks 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + echo -n "重启 Shadowsocks 服务... " + if systemctl restart shadowsocks-libev; then + echo -e "${GREEN}成功${NC}" + log_info "Shadowsocks 服务已重启" + else + echo -e "${RED}失败${NC}" + log_error "无法重启 Shadowsocks 服务" + fi + + # 等待服务启动 + echo -n "等待服务启动... " + sleep 3 + echo -e "${GREEN}完成${NC}" + + # 检查服务状态 + echo -n "Shadowsocks 服务状态: " + if systemctl is-active shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}运行中${NC}" + else + echo -e "${RED}未运行${NC}" + fi + + # 验证端口监听 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + local current_port="" + if command -v jq &>/dev/null; then + current_port=$(jq -r '.server_port' "$CONFIG_PATH/config.json" 2>/dev/null) + else + current_port=$(grep -o '"server_port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}' 2>/dev/null) + fi + + if [[ -n "$current_port" ]]; then + echo -n "端口 $current_port 监听状态: " + if command -v netstat &>/dev/null; then + if netstat -tuln | grep -q ":$current_port "; then + echo -e "${GREEN}正常${NC}" + else + echo -e "${RED}未监听${NC}" + fi + elif command -v ss &>/dev/null; then + if ss -tuln | grep -q ":$current_port "; then + echo -e "${GREEN}正常${NC}" + else + echo -e "${RED}未监听${NC}" + fi + fi + fi + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 修改配置 +modify_config() { + clear + echo "==================================================" + echo -e "${YELLOW}修改 Shadowsocks 配置${NC}" + echo "==================================================" + + # 检查是否已安装 + if ! command -v ss-server &>/dev/null; then + echo -e "${RED}Shadowsocks 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 读取当前配置 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + if command -v jq &>/dev/null; then + SS_PORT=$(jq -r '.server_port' "$CONFIG_PATH/config.json") + SS_PASSWORD=$(jq -r '.password' "$CONFIG_PATH/config.json") + SS_METHOD=$(jq -r '.method' "$CONFIG_PATH/config.json") + else + SS_PORT=$(grep -o '"server_port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}') + SS_PASSWORD=$(grep -o '"password": "[^"]*"' "$CONFIG_PATH/config.json" | cut -d'"' -f4) + SS_METHOD=$(grep -o '"method": "[^"]*"' "$CONFIG_PATH/config.json" | cut -d'"' -f4) + fi + fi + + echo "当前配置:" + echo "端口: $SS_PORT" + echo "密码: $SS_PASSWORD" + echo "加密方法: $SS_METHOD" + echo "" + + echo "1) 修改端口" + echo "2) 修改密码" + echo "3) 修改加密方法" + echo "4) 重新生成所有配置" + echo "0) 返回主菜单" + echo "" + + read -rp "请选择 [0-4]: " modify_choice + + local config_changed=false + + case $modify_choice in + 1) + read -rp "请输入新端口 (当前: $SS_PORT): " new_port + if [[ "$new_port" =~ ^[0-9]+$ ]] && [[ "$new_port" -ge 1 ]] && [[ "$new_port" -le 65535 ]]; then + SS_PORT="$new_port" + config_changed=true + else + log_error "无效端口" + fi + ;; + 2) + read -rp "请输入新密码 (当前: $SS_PASSWORD): " new_password + if [[ ${#new_password} -ge 8 ]]; then + SS_PASSWORD="$new_password" + config_changed=true + else + log_error "密码至少需要8位" + fi + ;; + 3) + echo "选择新的加密方法:" + echo "1) chacha20-ietf-poly1305" + echo "2) aes-256-gcm" + echo "3) aes-192-gcm" + echo "4) aes-128-gcm (默认)" + read -rp "请选择 [1-4, 默认4]: " method_choice + + case $method_choice in + 1) SS_METHOD="chacha20-ietf-poly1305"; config_changed=true ;; + 2) SS_METHOD="aes-256-gcm"; config_changed=true ;; + 3) SS_METHOD="aes-192-gcm"; config_changed=true ;; + 4) SS_METHOD="aes-128-gcm"; config_changed=true ;; + "") SS_METHOD="aes-128-gcm"; config_changed=true ;; # 默认选择 + *) log_error "无效选择" ;; + esac + ;; + 4) + SS_PORT="" + SS_PASSWORD="" + configure_shadowsocks + create_service + configure_firewall + generate_client_info + log_info "配置已重新生成" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + ;; + 0) + show_menu + return 0 + ;; + *) + log_error "无效选择" + read -rp "按回车键重试..." temp + modify_config + return 0 + ;; + esac + + if [[ "$config_changed" == true ]]; then + # 更新配置文件 + cat > "$CONFIG_PATH/config.json" << EOF +{ + "server": "0.0.0.0", + "server_port": $SS_PORT, + "password": "$SS_PASSWORD", + "method": "$SS_METHOD", + "timeout": 300, + "fast_open": true, + "workers": 1, + "prefer_ipv6": false, + "no_delay": true, + "reuse_port": true +} +EOF + + # 重启服务 + systemctl restart shadowsocks-libev + + # 重新配置防火墙 + configure_firewall + + # 重新生成客户端信息 + generate_client_info + + log_info "配置已更新并重启服务" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + + +# 更新 Shadowsocks +update_shadowsocks() { + clear + echo "==================================================" + echo -e "${YELLOW}更新 Shadowsocks${NC}" + echo "==================================================" + + # 检查是否已安装 + if ! command -v ss-server &>/dev/null; then + echo -e "${RED}Shadowsocks 未安装,请先安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 备份配置 + backup_config + + # 停止服务 + echo -n "停止 Shadowsocks 服务... " + if systemctl stop shadowsocks-libev &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Shadowsocks 服务" + fi + + # 更新软件包 + apt_progress "apt-get update" "更新软件包列表" + apt_progress "apt-get upgrade -y shadowsocks-libev" "更新 shadowsocks-libev" + + # 启动服务 + echo -n "启动 Shadowsocks 服务... " + if systemctl start shadowsocks-libev; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "启动 Shadowsocks 服务失败" + fi + + log_info "Shadowsocks 更新完成" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 主函数 +main() { + # 创建日志目录 + mkdir -p "$(dirname "$LOG_FILE")" + + # 处理命令行参数 + if [[ $# -gt 0 ]]; then + case "$1" in + -h|--help) + show_help + exit 0 + ;; + -i|--install) + check_root + install_shadowsocks + exit 0 + ;; + -u|--uninstall) + check_root + uninstall_shadowsocks + exit 0 + ;; + -s|--status) + check_root + check_status + exit 0 + ;; + -up|--update) + check_root + update_shadowsocks + exit 0 + ;; + *) + log_error "未知参数: $1" + show_help + exit 1 + ;; + esac + fi + + # 无参数则显示菜单 + check_root + show_menu +} + +# 执行主函数 +main "$@" diff --git a/vps_init.sh b/vps_init.sh new file mode 100644 index 0000000..7c6e494 --- /dev/null +++ b/vps_init.sh @@ -0,0 +1,675 @@ +#!/bin/bash +# +# VPS初始化一键脚本 +# 整合了系统更新、登录安全设置、系统清理、Docker安装、防火墙设置、时区设置、 +# 内存优化、Fail2ban安装和BBR加速 +# 使用方法: chmod +x vps_init.sh && ./vps_init.sh +# + +# =========================================== +# 用户设置区域 - 根据需要修改 +# =========================================== +NEW_PASSWORD="d!Fssw97SoALHa" # root用户新密码 +NEW_SSH_PORT="4399" # SSH新端口号 +TIMEZONE="Asia/Shanghai" # 时区设置 +SWAP_SIZE=1024 # 交换分区大小(MB) +# =========================================== + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # 恢复默认颜色 + +# 确保脚本以root权限运行 +if [ "$(id -u)" -ne 0 ]; then + echo -e "${RED}错误: 必须以root用户运行此脚本!${NC}" + exit 1 +fi + +# 显示欢迎信息 +echo -e "${GREEN}=============================================${NC}" +echo -e "${BLUE} VPS初始化一键脚本开始执行 ${NC}" +echo -e "${GREEN}=============================================${NC}" +echo "" + +# =========================================== +# 交互式设置选项 +# =========================================== +echo -e "${BLUE}进行交互式设置...${NC}" + +# 询问是否修改SSH端口 +echo -e "${YELLOW}1. 是否修改SSH端口? 当前设置为: ${NEW_SSH_PORT}${NC}" +while true; do + read -p "修改SSH端口? (y/n, 默认n): " CHANGE_SSH_PORT + # 设置默认值为否,用户按Enter就不修改 + CHANGE_SSH_PORT=${CHANGE_SSH_PORT:-n} + if [[ "$CHANGE_SSH_PORT" =~ ^[Yy]$ ]] || [[ "$CHANGE_SSH_PORT" =~ ^[Nn]$ ]]; then + break + else + echo -e "${RED}无效的输入,请输入 y 或 n${NC}" + fi +done + +if [[ "$CHANGE_SSH_PORT" =~ ^[Yy]$ ]]; then + echo -e "${GREEN}SSH端口将被修改为: ${NEW_SSH_PORT}${NC}" +else + echo -e "${GREEN}保持SSH端口不变: $NEW_SSH_PORT${NC}" +fi + +# 询问是否修改密码 +echo -e "${YELLOW}2. 是否修改root密码?${NC}" +while true; do + read -p "修改root密码? (y/n, 默认n): " CHANGE_PASSWORD + # 设置默认值为否,用户按Enter就不修改 + CHANGE_PASSWORD=${CHANGE_PASSWORD:-n} + if [[ "$CHANGE_PASSWORD" =~ ^[Yy]$ ]] || [[ "$CHANGE_PASSWORD" =~ ^[Nn]$ ]]; then + break + else + echo -e "${RED}无效的输入,请输入 y 或 n${NC}" + fi +done + +if [[ "$CHANGE_PASSWORD" =~ ^[Yy]$ ]]; then + echo -e "${GREEN}root密码将被修改为系统预设值${NC}" +else + echo -e "${GREEN}保持root密码不变${NC}" +fi + +# 询问是否修改主机名 +echo -e "${YELLOW}3. 是否修改主机名?${NC}" +while true; do + read -p "修改主机名? (y/n, 默认n): " CHANGE_HOSTNAME + # 设置默认值为否,用户按Enter就不修改 + CHANGE_HOSTNAME=${CHANGE_HOSTNAME:-n} + if [[ "$CHANGE_HOSTNAME" =~ ^[Yy]$ ]] || [[ "$CHANGE_HOSTNAME" =~ ^[Nn]$ ]]; then + break + else + echo -e "${RED}无效的输入,请输入 y 或 n${NC}" + fi +done + +if [[ "$CHANGE_HOSTNAME" =~ ^[Yy]$ ]]; then + # 显示当前主机名 + CURRENT_HOSTNAME=$(hostname) + echo -e "${YELLOW}当前主机名: ${CURRENT_HOSTNAME}${NC}" + + # 让用户输入新主机名 + read -p "请输入新的主机名: " NEW_HOSTNAME + if [ -n "$NEW_HOSTNAME" ]; then + CHANGE_HOSTNAME_FLAG=true + echo -e "${GREEN}主机名将被修改为: ${NEW_HOSTNAME}${NC}" + else + CHANGE_HOSTNAME_FLAG=false + echo -e "${RED}主机名不能为空,将保持不变${NC}" + fi +else + CHANGE_HOSTNAME_FLAG=false + echo -e "${GREEN}保持主机名不变${NC}" +fi + +echo -e "${BLUE}交互式设置完成${NC}" +echo "" + +# 记录开始时间 +START_TIME=$(date +%s) + +# 检查系统类型 +if [ -f /etc/debian_version ]; then + OS_TYPE="debian" + echo -e "${GREEN}检测到Debian/Ubuntu系统${NC}" +else + echo -e "${YELLOW}警告: 此脚本主要为Debian/Ubuntu系统设计${NC}" + echo -e "${YELLOW}部分功能可能在其他系统上不正常工作${NC}" + OS_TYPE="other" +fi + +# 创建日志文件 +LOG_FILE="/var/log/vps_init_$(date +%Y%m%d_%H%M%S).log" +touch $LOG_FILE +echo "VPS初始化脚本开始执行: $(date)" > $LOG_FILE + +# 定义日志函数 +log() { + echo -e "$1" | tee -a $LOG_FILE +} + +# 定义错误处理函数 +handle_error() { + local exit_code=$? + local line_no=$1 + if [ $exit_code -ne 0 ]; then + log "${RED}错误: 在第 $line_no 行发生错误,退出代码: $exit_code${NC}" + log "${RED}请检查日志文件: $LOG_FILE${NC}" + fi +} + +# 设置错误跟踪 +trap 'handle_error $LINENO' ERR + +# =========================================== +# 1. 系统更新 +# =========================================== +log "${BLUE}[1/10] 系统更新开始...${NC}" + +# 保存当前的sources.list作为备份 +if [ -f "/etc/apt/sources.list" ]; then + cp /etc/apt/sources.list /etc/apt/sources.list.bak + log "${GREEN}备份了软件源配置文件${NC}" +fi + +# 更新系统包 +if [ "$OS_TYPE" = "debian" ]; then + apt update -y || log "${RED}更新软件源失败${NC}" + DEBIAN_FRONTEND=noninteractive apt full-upgrade -y || log "${RED}系统升级失败${NC}" + apt install -y wget curl sudo vim git ufw net-tools htop iftop || log "${RED}安装基础软件包失败${NC}" + log "${GREEN}系统更新完成,安装了常用工具${NC}" +else + log "${YELLOW}非Debian系统,跳过标准更新流程${NC}" +fi + +# =========================================== +# 2. 主机名设置(如果用户选择了修改) +# =========================================== +if [ "$CHANGE_HOSTNAME_FLAG" = true ]; then + log "${BLUE}[2/10] 设置主机名...${NC}" + + # 备份当前主机名配置 + cp /etc/hostname /etc/hostname.bak + cp /etc/hosts /etc/hosts.bak + + # 修改主机名 + echo "$NEW_HOSTNAME" > /etc/hostname + hostname "$NEW_HOSTNAME" + + # 更新hosts文件 + sed -i "s/127.0.1.1.*/127.0.1.1\t$NEW_HOSTNAME/g" /etc/hosts + + # 检查是否修改成功 + CURRENT_HOSTNAME=$(hostname) + if [ "$CURRENT_HOSTNAME" = "$NEW_HOSTNAME" ]; then + log "${GREEN}主机名已成功修改为: $NEW_HOSTNAME${NC}" + else + log "${RED}主机名修改失败,当前名称: $CURRENT_HOSTNAME${NC}" + fi + + log "${GREEN}主机名设置完成${NC}" +else + log "${YELLOW}[2/10] 跳过主机名设置...${NC}" +fi + +# =========================================== +# 3. 登录安全设置 +# =========================================== +log "${BLUE}[3/10] 设置登录安全...${NC}" + +# 根据用户选择修改root密码 +if [[ "$CHANGE_PASSWORD" =~ ^[Yy]$ ]]; then + echo "root:$NEW_PASSWORD" | chpasswd + if [ $? -eq 0 ]; then + log "${GREEN}Root密码修改成功${NC}" + else + log "${RED}Root密码修改失败${NC}" + fi +else + log "${YELLOW}根据用户选择,保持root密码不变${NC}" +fi + +# 根据用户选择修改SSH端口 +if [[ "$CHANGE_SSH_PORT" =~ ^[Yy]$ ]]; then + # 备份SSH配置文件 + cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak + log "${GREEN}SSH配置已备份${NC}" + + # 修改SSH配置 + sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config + sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config + sed -i 's/#Port/Port/' /etc/ssh/sshd_config + sed -i "s/Port [0-9]\+/Port $NEW_SSH_PORT/g" /etc/ssh/sshd_config + + # 读取修改后的SSH端口以确认更改 + NEW_PORT_CONFIGURED=$(grep -P "^Port\s+\d+" /etc/ssh/sshd_config | awk '{print $2}') + if [ "$NEW_PORT_CONFIGURED" = "$NEW_SSH_PORT" ]; then + log "${GREEN}SSH端口已修改为: $NEW_SSH_PORT${NC}" + else + log "${RED}SSH端口修改失败,当前设置: $NEW_PORT_CONFIGURED${NC}" + # 尝试使用另一种方法修改 + echo "Port $NEW_SSH_PORT" >> /etc/ssh/sshd_config + log "${YELLOW}尝试使用备选方法添加端口设置${NC}" + fi + + # 重启SSH服务 + systemctl restart sshd + if [ $? -eq 0 ]; then + log "${GREEN}SSH服务重启成功${NC}" + else + log "${RED}SSH服务重启失败${NC}" + # 尝试使用service命令 + service sshd restart || service ssh restart + fi + + # 检查SSH服务状态 + systemctl status sshd --no-pager || service sshd status || service ssh status + log "${GREEN}SSH配置更改完成${NC}" + log "${YELLOW}注意:新的SSH连接端口为 $NEW_SSH_PORT${NC}" +else + log "${YELLOW}根据用户选择,保持SSH端口不变${NC}" + + # 即使不修改端口,仍然应该确保其他SSH安全设置 + sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config + sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config + + log "${GREEN}SSH基本安全设置完成${NC}" +fi + +# =========================================== +# 4. 系统清理 +# =========================================== +log "${BLUE}[4/10] 系统清理开始...${NC}" + +# 清理不需要的软件包 +if [ "$OS_TYPE" = "debian" ]; then + apt autoremove --purge -y + apt clean -y + apt autoclean -y + apt remove --purge $(dpkg -l | awk '/^rc/ {print $2}') -y 2>/dev/null || log "${YELLOW}没有需要清理的软件包配置${NC}" + + # 清理日志 + journalctl --rotate + journalctl --vacuum-time=1d + journalctl --vacuum-size=50M + log "${GREEN}系统日志已清理${NC}" + + # 清理旧内核(保留当前运行的内核) + apt remove --purge $(dpkg -l | awk '/^ii linux-(image|headers)-[^ ]+/{print $2}' | grep -v $(uname -r | sed 's/-.*//') | xargs) -y 2>/dev/null || log "${YELLOW}没有可清理的旧内核${NC}" + + log "${GREEN}系统清理完成${NC}" +else + log "${YELLOW}非Debian系统,跳过系统清理流程${NC}" +fi + +# =========================================== +# 5. Docker安装 +# =========================================== +log "${BLUE}[5/10] Docker安装开始...${NC}" + +# 检查Docker是否已安装 +if command -v docker &> /dev/null; then + log "${GREEN}Docker已经安装,版本信息:${NC}" + docker --version +else + # 安装Docker + if [ "$OS_TYPE" = "debian" ]; then + # 使用官方安装脚本 + curl -fsSL https://get.docker.com -o get-docker.sh + sh get-docker.sh + rm get-docker.sh + systemctl enable docker + + # 安装Docker Compose + if ! command -v docker-compose &> /dev/null; then + curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + log "${GREEN}Docker Compose 安装完成${NC}" + fi + + log "${GREEN}Docker安装完成,版本信息:${NC}" + docker --version + docker-compose --version + else + log "${YELLOW}非Debian系统,请手动安装Docker${NC}" + fi +fi + +# =========================================== +# 6. 防火墙设置 +# =========================================== +log "${BLUE}[6/10] 防火墙设置开始...${NC}" + +# 安装UFW +if [ "$OS_TYPE" = "debian" ]; then + apt update -y && apt install -y ufw net-tools lsof + + # 确保防火墙默认策略 + ufw default deny incoming + ufw default allow outgoing + + # 获取当前SSH端口(如果有多个SSH端口,获取所有) + CURRENT_SSH_PORT=$(grep -P "^Port\s+\d+" /etc/ssh/sshd_config | awk '{print $2}') + if [ -z "$CURRENT_SSH_PORT" ]; then + # 如果没找到,使用默认端口22 + CURRENT_SSH_PORT="22" + fi + + # 总是添加新配置的SSH端口(防止被锁在系统之外) + log "${GREEN}允许SSH端口 $NEW_SSH_PORT${NC}" + ufw allow $NEW_SSH_PORT/tcp comment 'New SSH Port' + + # 如果当前SSH端口与新端口不同,添加当前SSH端口作为备份 + if [ "$CURRENT_SSH_PORT" != "$NEW_SSH_PORT" ]; then + log "${GREEN}允许当前SSH端口 $CURRENT_SSH_PORT (备份)${NC}" + ufw allow $CURRENT_SSH_PORT/tcp comment 'Current SSH Port (Backup)' + fi + + # 添加基本Web服务端口 + log "${GREEN}允许HTTP/HTTPS端口${NC}" + ufw allow 80/tcp comment 'HTTP' + ufw allow 443/tcp comment 'HTTPS' + + # 检测活跃的网络连接和正在监听的端口 + log "${YELLOW}检测当前活跃的服务端口...${NC}" + + # 使用netstat查找监听的TCP端口 + LISTENING_PORTS=$(netstat -tlnp 2>/dev/null | grep "LISTEN" | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq) + + # 使用lsof作为备选方法 + if [ -z "$LISTENING_PORTS" ]; then + LISTENING_PORTS=$(lsof -i -P -n | grep LISTEN | awk '{print $9}' | awk -F: '{print $NF}' | sort -n | uniq) + fi + + # 如果仍然为空,提示手动检查 + if [ -z "$LISTENING_PORTS" ]; then + log "${YELLOW}未检测到活跃端口,只开放SSH、HTTP和HTTPS端口${NC}" + else + log "${GREEN}检测到以下活跃端口:${NC}" + for PORT in $LISTENING_PORTS; do + # 跳过SSH端口(已经添加过),以及常见的本地服务端口 + if [[ "$PORT" != "$NEW_SSH_PORT" && "$PORT" != "$CURRENT_SSH_PORT" && + "$PORT" != "80" && "$PORT" != "443" && + "$PORT" -lt "65535" && "$PORT" -gt "1024" ]]; then + + # 尝试找出服务名称 + SERVICE=$(lsof -i:$PORT -sTCP:LISTEN | grep -v "COMMAND" | awk '{print $1}' | head -1) + if [ -z "$SERVICE" ]; then + SERVICE=$(netstat -tlnp 2>/dev/null | grep ":$PORT" | awk '{print $7}' | cut -d"/" -f2 | head -1) + fi + + if [ -n "$SERVICE" ]; then + COMMENT="Service: $SERVICE" + else + COMMENT="Unknown Service" + fi + + log "${GREEN}允许端口 $PORT/tcp ($COMMENT)${NC}" + ufw allow $PORT/tcp comment "$COMMENT" + fi + done + fi + + # 删除询问用户是否手动开放端口的部分 + log "${GREEN}已自动开放SSH、HTTP、HTTPS端口和检测到的活跃服务端口${NC}" + + # 启用防火墙 + if ! ufw status | grep -q "Status: active"; then + log "${YELLOW}启用UFW防火墙...${NC}" + echo "y" | ufw enable || log "${RED}UFW启用失败${NC}" + else + log "${GREEN}UFW防火墙已启用${NC}" + fi + + # 显示防火墙状态 + ufw status numbered | tee -a $LOG_FILE + log "${GREEN}防火墙设置完成,已使用最小化原则开放端口${NC}" +else + log "${YELLOW}非Debian系统,请手动配置防火墙${NC}" +fi + +# =========================================== +# 7. 时区设置 +# =========================================== +log "${BLUE}[7/10] 时区设置开始...${NC}" + +# 设置时区 +timedatectl set-timezone $TIMEZONE +if [ $? -eq 0 ]; then + CURRENT_TZ=$(timedatectl show --property=Timezone --value) + log "${GREEN}时区设置为: $CURRENT_TZ${NC}" +else + log "${RED}时区设置失败${NC}" +fi + +# =========================================== +# 8. 内存优化 - 添加交换空间 +# =========================================== +log "${BLUE}[8/10] 内存优化开始...${NC}" + +# 获取当前所有交换空间信息 +CURRENT_SWAP_TOTAL=$(free -m | grep "Swap:" | awk '{print $2}') +log "${YELLOW}当前系统交换空间总大小: ${CURRENT_SWAP_TOTAL}MB${NC}" + +# 检查是否存在交换空间且大小与设定值相同 +if [ "$CURRENT_SWAP_TOTAL" -eq "$SWAP_SIZE" ]; then + log "${GREEN}当前交换空间大小(${CURRENT_SWAP_TOTAL}MB)与设定值一致,无需修改${NC}" + # 显示交换空间信息 + free -h | tee -a $LOG_FILE +else + # 如果不存在交换空间或大小不同,则进行处理 + if [ "$CURRENT_SWAP_TOTAL" -gt "0" ]; then + log "${YELLOW}系统已有交换空间但大小不符(${CURRENT_SWAP_TOTAL}MB),准备清理现有交换空间...${NC}" + + # 获取所有交换设备 + SWAP_DEVICES=$(swapon --show=NAME --noheadings) + + # 清理所有活跃的交换空间 + for DEVICE in $SWAP_DEVICES; do + log "${YELLOW}关闭交换空间: $DEVICE${NC}" + swapoff "$DEVICE" + done + + # 从fstab中移除所有交换空间条目(保留备份) + cp /etc/fstab /etc/fstab.bak + log "${GREEN}备份了/etc/fstab文件${NC}" + sed -i '/swap/d' /etc/fstab + + # 删除交换文件 + if [ -f /swapfile ]; then + log "${YELLOW}删除现有交换文件...${NC}" + rm -f /swapfile + fi + + log "${GREEN}所有现有交换空间已清理${NC}" + else + log "${YELLOW}系统未配置交换空间,准备创建...${NC}" + fi + + # 创建新的交换文件 + log "${GREEN}创建${SWAP_SIZE}MB大小的交换文件...${NC}" + dd if=/dev/zero of=/swapfile bs=1M count=$SWAP_SIZE status=progress + chmod 600 /swapfile + mkswap /swapfile + swapon /swapfile + + # 设置开机自动挂载 + if ! grep -q "/swapfile" /etc/fstab; then + echo "/swapfile swap swap defaults 0 0" >> /etc/fstab + log "${GREEN}已添加到fstab,开机将自动挂载${NC}" + fi + + # 调整swappiness参数(控制系统对交换空间的使用倾向) + echo "vm.swappiness=10" > /etc/sysctl.d/99-swappiness.conf + sysctl -p /etc/sysctl.d/99-swappiness.conf + + # 显示交换空间信息 + log "${GREEN}交换分区配置完成,当前内存和交换空间状态:${NC}" + free -h | tee -a $LOG_FILE +fi + +# =========================================== +# 9. Fail2ban安装和配置 +# =========================================== +log "${BLUE}[9/10] Fail2ban安装开始...${NC}" + +if [ "$OS_TYPE" = "debian" ]; then + # 安装Fail2ban + apt update -y && apt install -y fail2ban + systemctl start fail2ban + systemctl enable fail2ban + + # 配置Fail2ban + cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local + + # 清理任何现有配置 + rm -rf /etc/fail2ban/jail.d/* 2>/dev/null || true + + # 创建SSH防护配置 + cat < /etc/fail2ban/jail.d/sshd.local +[sshd] +enabled = true +mode = normal +port = $NEW_SSH_PORT +logpath = %(sshd_log)s +backend = systemd +maxretry = 5 +bantime = 1h +findtime = 10m +EOF + + # 重启Fail2ban + log "${YELLOW}重启Fail2ban服务...${NC}" + systemctl restart fail2ban + + # 等待服务启动完成 + log "${YELLOW}等待Fail2ban服务完全启动...${NC}" + sleep 5 + + # 检查服务状态 + if systemctl is-active --quiet fail2ban; then + log "${GREEN}Fail2ban服务已成功启动${NC}" + + # 显示Fail2ban状态(使用错误处理避免脚本终止) + log "${YELLOW}获取Fail2ban状态信息...${NC}" + + # 尝试获取fail2ban状态,忽略可能的错误 + fail2ban-client status >/dev/null 2>&1 || log "${YELLOW}无法获取fail2ban综合状态,但这不影响功能${NC}" + + # 尝试获取sshd监狱状态 + if fail2ban-client status sshd >/dev/null 2>&1; then + log "${GREEN}SSH防护已成功配置${NC}" + # 只有在前面成功的情况下才显示详细信息 + fail2ban-client status sshd + else + log "${YELLOW}无法获取SSH监狱状态,这可能是因为服务刚刚启动或配置需要更多时间生效${NC}" + log "${YELLOW}如果在重启后仍有问题,请检查 /var/log/fail2ban.log${NC}" + fi + + # 显示服务状态 + systemctl status fail2ban --no-pager || true + else + log "${RED}Fail2ban服务启动失败,请检查错误日志${NC}" + log "${YELLOW}尝试查看Fail2ban日志获取错误详情:${NC}" + tail -n 20 /var/log/fail2ban.log 2>/dev/null || log "${RED}无法读取Fail2ban日志${NC}" + fi + + log "${GREEN}Fail2ban安装和配置完成${NC}" + log "${YELLOW}如果出现临时错误,服务器重启后通常会正常工作${NC}" +else + log "${YELLOW}非Debian系统,请手动安装Fail2ban${NC}" +fi + +# =========================================== +# 10. BBR加速配置 +# =========================================== +log "${BLUE}[10/10] BBR配置开始...${NC}" + +# 检查BBR是否已启用 +if sysctl net.ipv4.tcp_congestion_control | grep -q "bbr"; then + log "${GREEN}BBR已经启用${NC}" +else + log "${YELLOW}配置BBR...${NC}" + # 添加BBR配置 + echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf + echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf + + # 应用设置 + sysctl -p + + # 验证设置 + if sysctl net.ipv4.tcp_congestion_control | grep -q "bbr"; then + log "${GREEN}BBR启用成功${NC}" + else + log "${RED}BBR启用失败${NC}" + fi +fi + +# 显示可用的拥塞控制算法 +log "${GREEN}当前系统支持的TCP拥塞控制算法:${NC}" +sysctl net.ipv4.tcp_available_congestion_control + +# 验证模块是否加载 +lsmod | grep bbr + +# =========================================== +# 完成处理 +# =========================================== +END_TIME=$(date +%s) +DURATION=$((END_TIME - START_TIME)) +MINUTES=$((DURATION / 60)) +SECONDS=$((DURATION % 60)) + +log "${GREEN}=======================================================${NC}" +log "${GREEN}VPS初始化完成!用时: ${MINUTES}分${SECONDS}秒${NC}" +log "${GREEN}=======================================================${NC}" +log "${YELLOW}重要提示:${NC}" + +# 根据用户选择显示相应的提示信息 +TIP_COUNT=1 + +# 如果用户选择了修改SSH端口,显示端口信息 +if [[ "$CHANGE_SSH_PORT" =~ ^[Yy]$ ]]; then + log "${YELLOW}$TIP_COUNT. SSH端口已更改为: ${NEW_SSH_PORT}${NC}" + TIP_COUNT=$((TIP_COUNT + 1)) +fi + +# 如果用户选择了修改root密码,显示密码信息 +if [[ "$CHANGE_PASSWORD" =~ ^[Yy]$ ]]; then + log "${YELLOW}$TIP_COUNT. root密码已更改为: ${NEW_PASSWORD}${NC}" + TIP_COUNT=$((TIP_COUNT + 1)) +fi + +# 如果用户选择了修改主机名,显示主机名信息 +if [ "$CHANGE_HOSTNAME_FLAG" = true ]; then + log "${YELLOW}$TIP_COUNT. 主机名已更改为: ${NEW_HOSTNAME}${NC}" + TIP_COUNT=$((TIP_COUNT + 1)) +fi + +# 始终显示防火墙和日志文件信息 +log "${YELLOW}$TIP_COUNT. 防火墙已启用,只开放了必要端口${NC}" +TIP_COUNT=$((TIP_COUNT + 1)) +log "${YELLOW}$TIP_COUNT. 日志文件保存在: ${LOG_FILE}${NC}" + +# 如果已启用BBR,显示BBR信息 +if sysctl net.ipv4.tcp_congestion_control 2>/dev/null | grep -q "bbr"; then + TIP_COUNT=$((TIP_COUNT + 1)) + log "${YELLOW}$TIP_COUNT. BBR加速已成功启用${NC}" +fi + +# 如果配置了交换空间,显示交换空间信息 +if [ "$CURRENT_SWAP_TOTAL" -gt "0" ]; then + TIP_COUNT=$((TIP_COUNT + 1)) + log "${YELLOW}$TIP_COUNT. 交换空间大小: $(free -m | grep "Swap:" | awk '{print $2}')MB${NC}" +fi + +log "${GREEN}=======================================================${NC}" +log "${BLUE}建议您现在重启服务器以应用所有更改${NC}" +log "${GREEN}=======================================================${NC}" + +# 提示用户是否立即重启 +while true; do + read -p "是否立即重启服务器?(y/n, 默认n): " REBOOT_NOW + # 设置默认值为否 + REBOOT_NOW=${REBOOT_NOW:-n} + if [[ "$REBOOT_NOW" =~ ^[Yy]$ ]] || [[ "$REBOOT_NOW" =~ ^[Nn]$ ]]; then + break + else + echo -e "${RED}无效的输入,请输入 y 或 n${NC}" + fi +done + +if [[ "$REBOOT_NOW" =~ ^[Yy]$ ]]; then + log "${GREEN}服务器将在5秒后重启...${NC}" + sleep 5 + reboot +else + log "${YELLOW}请稍后手动重启服务器以应用所有更改${NC}" +fi \ No newline at end of file diff --git a/xray-manager-v25.8.31.sh b/xray-manager-v25.8.31.sh new file mode 100644 index 0000000..731cc03 --- /dev/null +++ b/xray-manager-v25.8.31.sh @@ -0,0 +1,1964 @@ +#!/bin/bash +# Xray 管理脚本 - 集成安装、卸载和管理功能 +# 专门支持 VLESS+REALITY 协议 +# 使用方法: chmod +x xray-manager.sh && ./xray-manager.sh + +# 全局变量 +XRAY_PATH="/usr/local/bin" +CONFIG_PATH="/usr/local/etc/xray" +REALITY_PORT="" # VLESS+REALITY 端口 +LOG_PATH="/var/log/xray" +LOG_FILE="/var/log/xray-manager.log" +SCRIPT_VERSION="1.0.0" +XRAY_VERSION="v25.3.6" # 当前固定的Xray版本 +UUID="" +PRIVATE_KEY="" +PUBLIC_KEY="" +CONFIG_BACKUP="" +SERVER_IP="" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# 进度条函数 +show_progress() { + local pid=$1 + local delay=0.1 + local spinstr='|/-\' + local temp + local count=0 + local start_time=$(date +%s) + echo -n " " + + while ps -p $pid > /dev/null; do + temp=${spinstr#?} + printf "\r[%c] %s 已进行 %ds" "$spinstr" "$2" "$(($(date +%s) - start_time))" + spinstr=$temp${spinstr%"$temp"} + sleep $delay + count=$((count + 1)) + done + printf "\r\033[K" +} + +# 进度条显示函数 - 适用于apt操作 +apt_progress() { + local cmd="$1" + local msg="$2" + local logfile="$LOG_FILE.tmp" + + echo -e "${CYAN}开始 $msg...${NC}" + touch "$logfile" + ($cmd 2>&1 | tee -a "$LOG_FILE" > "$logfile") & + local pid=$! + + # 显示进度条 + local start_time=$(date +%s) + local dots="" + local status="" + local progress=0 + local last_line="" + local delay=0.2 + + while ps -p $pid > /dev/null; do + # 读取最后一行日志 + if [[ -f "$logfile" ]]; then + last_line=$(tail -n 1 "$logfile") + + # 尝试从输出中提取进度信息 + if [[ $last_line == *%* ]]; then + status="${last_line%%(*}" + progress="${last_line#*(}" + progress="${progress%%)*}" + printf "\r\033[K${CYAN}[$msg]${NC} %s %s " "$status" "$progress" + else + dots="${dots}." + if [[ ${#dots} -gt 5 ]]; then dots="."; fi + elapsed=$(($(date +%s) - start_time)) + printf "\r\033[K${CYAN}[$msg]${NC} 进行中%s (%ds)" "$dots" "$elapsed" + fi + fi + sleep $delay + done + + if wait $pid; then + printf "\r\033[K${GREEN}[$msg]${NC} 完成! 用时: %ds\n" "$(($(date +%s) - start_time))" + rm -f "$logfile" + return 0 + else + printf "\r\033[K${RED}[$msg]${NC} 失败! 查看日志: $LOG_FILE\n" + rm -f "$logfile" + return 1 + fi +} + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" | tee -a "$LOG_FILE" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_FILE" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 | tee -a "$LOG_FILE" +} + +log_debug() { + echo -e "${BLUE}[DEBUG]${NC} $1" | tee -a "$LOG_FILE" +} + +# 检查是否有 root 权限 +check_root() { + if [[ $EUID -ne 0 ]]; then + log_error "此脚本需要 root 权限运行" + exit 1 + fi +} + +# 检查系统环境 +check_system() { + # 显示系统信息 + echo "系统信息:" + echo "------------------------" + if [ -f /etc/os-release ]; then + cat /etc/os-release | grep "PRETTY_NAME" | cut -d= -f2- | tr -d '"' + fi + echo "内核版本: $(uname -r)" + echo "架构: $(uname -m)" + echo "------------------------" + + # 检查是否为 Debian/Ubuntu 系统 + if [[ ! -f /etc/debian_version && ! -f /etc/lsb-release ]]; then + log_warn "未检测到 Debian/Ubuntu 系统,脚本可能无法正常工作" + read -rp "是否继续? [y/N] " response + if [[ ! "$response" =~ ^[yY]$ ]]; then + exit 1 + fi + fi + + # 检查网络连接 + echo -n "检查网络连接... " + if ping -c 1 -W 2 github.com &>/dev/null; then + echo -e "${GREEN}连接正常${NC}" + else + echo -e "${YELLOW}无法连接到 GitHub${NC}" + log_warn "无法连接到 GitHub,请检查网络连接" + read -rp "是否继续? [y/N] " response + if [[ ! "$response" =~ ^[yY]$ ]]; then + exit 1 + fi + fi +} + +# 获取最新版本号 +get_latest_version() { + # 利用GitHub重定向特性获取最新版本 + local redirect_url=$(curl -s -L -o /dev/null -w '%{url_effective}' https://github.com/XTLS/Xray-core/releases/latest 2>/dev/null) + local version=$(echo "$redirect_url" | grep -o 'tag/v[0-9.]*' | cut -d/ -f2 2>/dev/null) + + # 如果获取失败,尝试备用方法 + if [[ -z "$version" ]]; then + # 方法2: 通过API获取 + version=$(curl -s https://api.github.com/repos/XTLS/Xray-core/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4 2>/dev/null) + fi + + # 如果还是失败,返回当前全局版本 + if [[ -z "$version" ]]; then + version="$XRAY_VERSION" + else + # 更新全局变量 + XRAY_VERSION="$version" + fi + + echo "$version" +} + +# 备份函数 +backup_config() { + if [[ -d "$CONFIG_PATH" ]]; then + CONFIG_BACKUP="${CONFIG_PATH}_backup_$(date +%Y%m%d%H%M%S)" + log_info "备份现有配置到 $CONFIG_BACKUP" + cp -r "$CONFIG_PATH" "$CONFIG_BACKUP" || log_warn "配置备份失败" + + # 备份 Xray 信息文件 + if [[ -f "/root/xray_info.txt" ]]; then + cp "/root/xray_info.txt" "${CONFIG_BACKUP}/xray_info.txt.bak" || log_warn "Xray 信息文件备份失败" + fi + else + log_warn "找不到配置目录,跳过备份" + fi +} + +# 显示帮助信息 +show_help() { + echo "Xray 管理脚本 v${SCRIPT_VERSION}" + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " -h, --help 显示此帮助信息" + echo " -i, --install 直接运行安装" + echo " -u, --uninstall 直接运行卸载" + echo " -s, --status 查看 Xray 状态" + echo " -up, --update 更新 Xray" + echo "" + echo "无参数运行脚本将显示交互式菜单" +} + +# 菜单函数 +show_menu() { + clear + echo "==================================================" + echo -e "${CYAN}Xray REALITY管理脚本 v${SCRIPT_VERSION}${NC}" + echo -e "${CYAN}(VLESS+REALITY 协议 - 稳定版本)${NC}" + echo "==================================================" + echo -e "1) ${GREEN}安装 Xray (自动配置所有稳定功能)${NC}" + echo -e "2) ${RED}卸载 Xray${NC}" + echo -e "3) ${YELLOW}更新 Xray${NC}" + echo -e "4) ${BLUE}查看 Xray 状态${NC}" + echo -e "5) ${CYAN}查看配置信息${NC}" + echo -e "6) ${GREEN}重启服务${NC}" + echo -e "0) ${RED}退出${NC}" + echo "==================================================" + echo "" + read -rp "请输入选项 [0-6]: " choice + + case $choice in + 1) install_xray ;; + 2) uninstall_xray ;; + 3) update_xray ;; + 4) check_status ;; + 5) show_config ;; + 6) restart_service ;; + 0) exit 0 ;; + *) log_error "无效选项" && sleep 2 && show_menu ;; + esac +} + + + + + +# 安装依赖 +install_dependencies() { + log_info "安装必要依赖" + + # 更新软件包列表 + apt_progress "apt-get update" "更新软件包列表" || { + log_error "更新软件包列表失败" + return 1 + } + + # 安装必要工具(包含 jq 以支持后量子密码学配置) + local deps=(curl wget jq unzip) + for dep in "${deps[@]}"; do + if ! command -v "$dep" &>/dev/null; then + apt_progress "apt-get install -y $dep" "安装 $dep" || { + log_error "安装 $dep 失败" + return 1 + } + else + log_info "$dep 已安装,跳过" + fi + done + + # 检查安装结果 + local all_installed=true + for dep in "${deps[@]}"; do + if ! command -v "$dep" &>/dev/null; then + log_error "$dep 安装失败" + all_installed=false + fi + done + + if [ "$all_installed" = true ]; then + log_info "所有依赖安装完成" + return 0 + else + log_error "部分依赖安装失败" + return 1 + fi +} + +# 下载 Xray +download_xray() { + log_info "开始下载 Xray" + + # 检查Xray服务是否正在运行,如果是则停止 + if systemctl is-active xray &>/dev/null; then + log_info "检测到Xray服务正在运行,先停止服务" + echo -n "停止 Xray 服务... " + if systemctl stop xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Xray 服务,可能会影响安装" + fi + + # 等待进程完全停止 + echo -n "等待进程释放资源... " + sleep 2 + if pgrep -x "xray" > /dev/null; then + # 如果进程仍在运行,尝试强制终止 + pkill -9 -x "xray" &>/dev/null + sleep 1 + fi + echo -e "${GREEN}完成${NC}" + fi + + # 创建临时目录 + local tmp_dir="/tmp/xray_install" + mkdir -p "$tmp_dir" + + # 获取最新版本 + echo -n "获取 Xray 最新版本... " + local latest_version=$(get_latest_version) + echo -e "${GREEN}$latest_version${NC}" + + # 确定系统架构 + local arch + case $(uname -m) in + x86_64|amd64) arch="64" ;; + armv7l|armv8l) arch="arm32-v7a" ;; + aarch64) arch="arm64-v8a" ;; + *) arch="64" ;; # 默认使用64位版本 + esac + + # 构建下载URL + local download_url="https://github.com/XTLS/Xray-core/releases/download/$latest_version/Xray-linux-$arch.zip" + log_info "下载链接: $download_url" + + # 下载Xray + echo -n "下载 Xray... " + if wget -q --show-progress -O "$tmp_dir/xray.zip" "$download_url"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "下载 Xray 失败" + return 1 + fi + + # 解压文件 + echo -n "解压 Xray... " + if unzip -q -o "$tmp_dir/xray.zip" -d "$tmp_dir"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "解压 Xray 失败" + return 1 + fi + + # 创建目录 + mkdir -p "$XRAY_PATH" "$CONFIG_PATH" "$LOG_PATH" + + # 复制文件前确保目标文件不被占用 + if [[ -f "$XRAY_PATH/xray" ]]; then + # 如果文件存在,先尝试重命名它 + mv "$XRAY_PATH/xray" "$XRAY_PATH/xray.old" 2>/dev/null + fi + + # 复制文件 + echo -n "安装 Xray 核心文件... " + if cp "$tmp_dir/xray" "$XRAY_PATH/xray" && chmod +x "$XRAY_PATH/xray"; then + echo -e "${GREEN}成功${NC}" + # 删除旧文件 + rm -f "$XRAY_PATH/xray.old" 2>/dev/null + else + echo -e "${RED}失败${NC}" + log_error "安装 Xray 核心文件失败" + # 恢复旧文件 + if [[ -f "$XRAY_PATH/xray.old" ]]; then + mv "$XRAY_PATH/xray.old" "$XRAY_PATH/xray" 2>/dev/null + fi + return 1 + fi + + # 复制 geoip.dat 和 geosite.dat + echo -n "安装 GeoIP 和 GeoSite 数据... " + if cp "$tmp_dir/geoip.dat" "$XRAY_PATH/geoip.dat" && \ + cp "$tmp_dir/geosite.dat" "$XRAY_PATH/geosite.dat"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "安装 GeoIP 和 GeoSite 数据失败,将在配置时下载" + fi + + # 清理临时文件 + rm -rf "$tmp_dir" + + log_info "Xray $latest_version 安装完成" + return 0 +} + +# 生成随机 PORT 和 UUID +generate_random_values() { + log_info "生成随机配置值" + + # 询问用户是否指定端口 + if [[ -z "$REALITY_PORT" ]]; then + read -rp "是否指定端口? [y/N] " specify_port + if [[ "$specify_port" =~ ^[yY]$ ]]; then + # 用户选择指定端口 + while true; do + read -rp "请输入端口号 (1-65535): " REALITY_PORT + # 验证端口是否为有效数字 + if ! [[ "$REALITY_PORT" =~ ^[0-9]+$ ]] || [ "$REALITY_PORT" -lt 1 ] || [ "$REALITY_PORT" -gt 65535 ]; then + log_error "无效的端口号,请输入1-65535之间的数字" + continue + fi + + # 检查端口是否被占用 + if ss -tuln | grep -q ":$REALITY_PORT "; then + log_warn "端口 $REALITY_PORT 已被占用,请选择其他端口" + continue + fi + + log_info "将使用指定端口: $REALITY_PORT" + break + done + else + # 用户选择随机端口,继续原来的逻辑 + # 尝试找一个未被占用的端口 + local attempts=0 + while [[ "$attempts" -lt 10 ]]; do + REALITY_PORT=$(shuf -i 10000-60000 -n 1) + # 检查端口是否被占用 + if ! ss -tuln | grep -q ":$REALITY_PORT "; then + log_info "生成随机端口: $REALITY_PORT" + break + fi + attempts=$((attempts + 1)) + done + if [[ "$attempts" -eq 10 ]]; then + log_warn "无法找到未占用的端口,使用随机端口: $REALITY_PORT" + fi + fi + else + # 验证端口是否为有效数字 + if ! [[ "$REALITY_PORT" =~ ^[0-9]+$ ]]; then + log_warn "无效的端口: $REALITY_PORT,生成新的随机端口" + REALITY_PORT=$(shuf -i 10000-60000 -n 1) + fi + fi + + # 生成 UUID + if [[ -z "$UUID" ]]; then + # 检查xray命令是否可用 + if [[ -f "$XRAY_PATH/xray" && -x "$XRAY_PATH/xray" ]]; then + UUID=$($XRAY_PATH/xray uuid) + log_info "生成 UUID: $UUID" + else + # 如果xray不可用,使用uuidgen或者随机生成 + if command -v uuidgen &>/dev/null; then + UUID=$(uuidgen) + else + # 简单的UUID生成方法(不完全符合标准但足够使用) + UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || + (date +%s%N | sha256sum | head -c 32 | + sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-/')) + fi + log_info "生成 UUID: $UUID" + fi + else + # 验证UUID格式 + if ! [[ "$UUID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then + log_warn "无效的 UUID 格式: $UUID,生成新的 UUID" + if [[ -f "$XRAY_PATH/xray" && -x "$XRAY_PATH/xray" ]]; then + UUID=$($XRAY_PATH/xray uuid) + else + UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || + (date +%s%N | sha256sum | head -c 32 | + sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-/')) + fi + log_info "生成新的 UUID: $UUID" + fi + fi + + # 生成 REALITY 密钥对 + log_info "生成 REALITY 密钥对" + if [[ -z "$PRIVATE_KEY" || -z "$PUBLIC_KEY" ]]; then + local key_pair + if [[ -f "$XRAY_PATH/xray" && -x "$XRAY_PATH/xray" ]]; then + key_pair=$($XRAY_PATH/xray x25519) + PRIVATE_KEY=$(echo "$key_pair" | grep -E "(Private|PrivateKey)" | awk '{print $NF}') + PUBLIC_KEY=$(echo "$key_pair" | grep -E "(Public|Password)" | awk '{print $NF}') + + # 验证密钥是否正确生成 + if [[ -z "$PRIVATE_KEY" || -z "$PUBLIC_KEY" ]]; then + log_error "密钥生成失败,请检查 Xray 版本" + return 1 + fi + else + log_warn "无法使用 xray 生成密钥对,将跳过密钥生成" + log_info "安装完成后,将自动生成密钥对" + return 1 + fi + fi + + # 获取服务器IP + get_server_ip + + log_debug "私钥: $PRIVATE_KEY" + log_debug "公钥: $PUBLIC_KEY" + + # 验证密钥是否成功生成 + if [[ -z "$PRIVATE_KEY" || -z "$PUBLIC_KEY" ]]; then + log_error "REALITY 密钥生成失败" + return 1 + fi + + return 0 +} + + + + + +# 获取服务器IP +get_server_ip() { + log_info "获取服务器IP地址" + + if [[ -n "$SERVER_IP" ]]; then + log_info "使用已设置的IP: $SERVER_IP" + return 0 + fi + + # 尝试首选方法获取公网IP + SERVER_IP=$(curl -s -m 5 https://api.ipify.org 2>/dev/null) + + # 验证获取到的IP是否为有效IPv4地址 + if [[ -n "$SERVER_IP" && "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + log_info "成功获取公网IP: $SERVER_IP" + return 0 + fi + + # 如果第一个方法失败,尝试备用方法 + local backup_services=("https://ifconfig.me" "https://ip.sb" "https://ipinfo.io/ip") + + for service in "${backup_services[@]}"; do + SERVER_IP=$(curl -s -m 3 "$service" 2>/dev/null) + if [[ -n "$SERVER_IP" && "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + log_info "成功从 $service 获取公网IP: $SERVER_IP" + return 0 + fi + done + + # 如果所有公网IP获取方式都失败,则使用本地IP + if command -v hostname &>/dev/null; then + SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}') + fi + + # 如果hostname命令失败,尝试使用ip命令 + if [[ -z "$SERVER_IP" && -x "$(command -v ip)" ]]; then + SERVER_IP=$(ip -4 addr show scope global | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n 1) + fi + + # 如果还是失败,尝试使用ifconfig命令 + if [[ -z "$SERVER_IP" && -x "$(command -v ifconfig)" ]]; then + SERVER_IP=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -n 1) + fi + + if [[ -n "$SERVER_IP" ]]; then + log_warn "无法获取公网IP,使用本地IP: $SERVER_IP" + return 0 + else + log_error "无法获取任何有效IP地址,将使用127.0.0.1作为占位符" + SERVER_IP="127.0.0.1" + return 1 + fi +} + +# 配置 Xray - 修改为 VLESS+REALITY 并支持 TUN 模式 +configure_xray() { + log_info "配置 Xray" + + # 生成随机值 + generate_random_values || { + log_error "生成随机值失败" + return 1 + } + + # 验证必要的变量是否已设置 + if [[ -z "$PRIVATE_KEY" || -z "$PUBLIC_KEY" || -z "$UUID" || -z "$REALITY_PORT" ]]; then + log_error "必要的配置参数缺失:" + [[ -z "$PRIVATE_KEY" ]] && log_error "- PRIVATE_KEY 未设置" + [[ -z "$PUBLIC_KEY" ]] && log_error "- PUBLIC_KEY 未设置" + [[ -z "$UUID" ]] && log_error "- UUID 未设置" + [[ -z "$REALITY_PORT" ]] && log_error "- REALITY_PORT 未设置" + return 1 + fi + + # 创建配置目录 + mkdir -p "$CONFIG_PATH" + + # 创建示例目录 + mkdir -p "$CONFIG_PATH/examples" + + log_info "创建 VLESS+REALITY 配置文件(最新版本,稳定功能)" + + # 注意:后量子密码学功能需要更新版本的 Xray,暂时禁用以确保兼容性 + log_info "配置 REALITY 协议基础功能(稳定版本)" + + # VLESS + REALITY 配置 (最新版本,包含所有功能) + cat > "$CONFIG_PATH/config.json" << EOF +{ + "log": { + "loglevel": "warning", + "access": "$LOG_PATH/access.log", + "error": "$LOG_PATH/error.log" + }, + "inbounds": [ + { + "port": $REALITY_PORT, + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "$UUID", + "flow": "xtls-rprx-vision" + } + ], + "decryption": "none" + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "target": "www.shopify.com:443", + "xver": 0, + "serverNames": [ + "shopify.com", + "www.shopify.com" + ], + "privateKey": "$PRIVATE_KEY", + "minClientVer": "", + "maxClientVer": "", + "maxTimeDiff": 0, + "shortIds": [ + "", + "6ba85179e30d" + ], + "limitFallbackUpload": { + "afterBytes": 1048576, + "bytesPerSec": 1048576, + "burstBytesPerSec": 2097152 + }, + "limitFallbackDownload": { + "afterBytes": 1048576, + "bytesPerSec": 1048576, + "burstBytesPerSec": 2097152 + } + } + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls", + "quic" + ], + "routeOnly": false + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "tag": "direct" + }, + { + "protocol": "blackhole", + "tag": "block" + } + ], + "dns": { + "hosts": { + "dns.google": "8.8.8.8", + "proxy.example.com": "127.0.0.1" + }, + "servers": [ + { + "address": "https://1.1.1.1/dns-query", + "domains": [ + "geosite:geolocation-!cn" + ], + "expectedIPs": [ + "geoip:!cn" + ], + "skipFallback": true, + "queryStrategy": "UseIPv4" + }, + { + "address": "https://223.5.5.5/dns-query", + "domains": [ + "geosite:cn" + ], + "expectedIPs": [ + "geoip:cn" + ], + "skipFallback": false, + "queryStrategy": "UseIP" + }, + "8.8.8.8", + "https://dns.google/dns-query" + ], + "queryStrategy": "UseIP", + "disableCache": false, + "disableFallback": false + }, + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "ip": [ + "geoip:private" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "domain": [ + "geosite:private" + ] + }, + { + "type": "field", + "port": "443", + "network": "udp", + "outboundTag": "block" + }, + { + "type": "field", + "outboundTag": "direct", + "ip": [ + "geoip:cn" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "domain": [ + "geosite:cn" + ] + } + ] + } +} +EOF + + if [[ -f "$CONFIG_PATH/config.json" ]]; then + log_info "配置文件创建成功" + else + log_error "配置文件创建失败" + return 1 + fi + + return 0 +} + +# 配置防火墙 - 简化只处理 REALITY 端口 +configure_firewall() { + log_info "配置防火墙" + + # 检测和关闭旧端口 + local old_ports=() + + # 从备份中查找旧端口 + if [[ -n "$CONFIG_BACKUP" && -f "$CONFIG_BACKUP/config.json" ]]; then + log_info "检测旧配置中的端口" + if command -v jq &>/dev/null; then + # 使用jq查找所有inbounds的端口 + local detected_ports=$(jq '.inbounds[].port' "$CONFIG_BACKUP/config.json" 2>/dev/null) + for port in $detected_ports; do + if [[ "$port" != "null" && -n "$port" ]]; then + old_ports+=("$port") + log_info "检测到旧端口: $port" + fi + done + else + # 使用grep查找端口 + local detected_ports=$(grep -o '"port": [0-9]*' "$CONFIG_BACKUP/config.json" | awk '{print $2}') + for port in $detected_ports; do + if [[ -n "$port" ]]; then + old_ports+=("$port") + log_info "检测到旧端口: $port" + fi + done + fi + fi + + # 如果找不到旧端口,也查找默认位置 + if [[ ${#old_ports[@]} -eq 0 && -f "$CONFIG_PATH/config.json" && "$CONFIG_PATH/config.json" != "$(readlink -f "$CONFIG_BACKUP/config.json")" ]]; then + if command -v jq &>/dev/null; then + # 使用jq查找所有inbounds的端口 + local detected_ports=$(jq '.inbounds[].port' "$CONFIG_PATH/config.json" 2>/dev/null) + for port in $detected_ports; do + if [[ "$port" != "null" && -n "$port" ]]; then + old_ports+=("$port") + log_info "检测到当前配置的端口: $port" + fi + done + else + # 使用grep查找端口 + local detected_ports=$(grep -o '"port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}') + for port in $detected_ports; do + if [[ -n "$port" ]]; then + old_ports+=("$port") + log_info "检测到当前配置的端口: $port" + fi + done + fi + fi + + # 从旧端口列表中过滤掉当前端口 + local filtered_ports=() + for port in "${old_ports[@]}"; do + # 检查端口是否为有效数字 + if ! [[ "$port" =~ ^[0-9]+$ ]]; then + log_warn "无效的端口号: $port,已跳过" + continue + fi + + # 检查是否与当前端口相同 + if [[ "$port" -eq "$REALITY_PORT" ]]; then + log_info "端口 $port 与当前端口相同,已跳过" + continue + fi + + filtered_ports+=("$port") + done + old_ports=("${filtered_ports[@]}") + + # 检查是否安装了 ufw + if command -v ufw &>/dev/null; then + # 检查ufw是否启用 + local ufw_status=$(ufw status | grep -o "Status: active" 2>/dev/null) + + if [[ -z "$ufw_status" ]]; then + log_warn "UFW 防火墙未启用,可能需要手动配置防火墙规则" + log_info "您可以运行 'sudo ufw enable' 启用 UFW 防火墙" + return 0 + fi + + # 关闭旧端口 + if [[ ${#old_ports[@]} -gt 0 ]]; then + log_info "开始关闭旧端口" + local closed_ports=() + + for port in "${old_ports[@]}"; do + # 检查端口是否已开放在UFW中 + if ! ufw status | grep -q "$port/tcp"; then + log_info "端口 $port 未在 UFW 中开放,跳过" + continue + fi + + echo -n "关闭 UFW 防火墙端口 $port... " + if ufw delete allow "$port/tcp" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + closed_ports+=("$port") + else + echo -e "${RED}失败${NC}" + log_warn "无法关闭端口 $port" + fi + done + + # 打印关闭的端口 + if [[ ${#closed_ports[@]} -gt 0 ]]; then + local closed_list=$(printf ", %s" "${closed_ports[@]}") + closed_list=${closed_list:2} # 移除开头的逗号和空格 + log_info "已关闭 UFW 防火墙中的旧端口: $closed_list" + else + log_info "没有需要关闭的旧端口" + fi + else + log_info "未检测到旧端口,跳过关闭端口步骤" + fi + + # 确定需要开放的端口 + local ports=("$REALITY_PORT") + + echo -n "配置 UFW 防火墙... " + local opened_ports=() + for port in "${ports[@]}"; do + # 检查端口是否为有效数字 + if ! [[ "$port" =~ ^[0-9]+$ ]]; then + log_warn "端口 '$port' 不是有效数字,已跳过" + continue + fi + + # 检查端口是否已经开放 + if ufw status | grep -q "$port/tcp"; then + log_info "端口 $port 已经开放,跳过" + opened_ports+=("$port") + continue + fi + + # 开放端口 + if ufw allow "$port/tcp" &>/dev/null; then + opened_ports+=("$port") + else + log_warn "无法开放端口 $port" + fi + done + echo -e "${GREEN}完成${NC}" + + # 打印开放的端口 + if [[ ${#opened_ports[@]} -gt 0 ]]; then + local port_list=$(printf ", %s" "${opened_ports[@]}") + port_list=${port_list:2} # 移除开头的逗号和空格 + log_info "已在 UFW 防火墙开放端口: $port_list" + else + log_warn "没有成功开放任何端口" + fi + else + log_warn "未检测到 UFW 防火墙,跳过防火墙配置" + log_info "如需管理防火墙规则,请安装 UFW: sudo apt install ufw" + fi + + return 0 +} + +# 生成客户端配置 - 更新为只提供 VLESS+REALITY +generate_client_info() { + log_info "生成客户端信息" + + # 确保IP地址已获取 + if [[ -z "$SERVER_IP" ]]; then + get_server_ip + fi + + # 生成 VLESS + REALITY 分享链接 + local share_link="vless://${UUID}@${SERVER_IP}:${REALITY_PORT}?security=reality&encryption=none&pbk=${PUBLIC_KEY}&fp=chrome&type=tcp&flow=xtls-rprx-vision&sni=www.shopify.com&sid=6ba85179e30d&spx=%2Fsearch%3Fq%3Dcdn#Xray-Reality" + + # 保存客户端信息 + cat > /root/xray_info.txt << EOF +========================= Xray Reality 配置信息 ========================= +服务器地址: ${SERVER_IP} +端口: ${REALITY_PORT} +UUID: ${UUID} +协议: vless +传输协议: tcp +加密方式: none +流控: xtls-rprx-vision +安全: reality +公钥: ${PUBLIC_KEY} +私钥: ${PRIVATE_KEY} +SNI: www.shopify.com +指纹: chrome +短 ID: 6ba85179e30d +爬虫路径: /search?q=cdn +目标站点: www.shopify.com:443 +速率限制: 已启用回退连接限制 +TUN模式支持: 已启用 +DNS优化: 已启用(DoH) +兼容性: 当前版本完全兼容 +================================================================== + +分享链接: +${share_link} + +二维码链接: +https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${share_link} +================================================================== + +TUN模式支持: +REALITY 协议已配置支持 TUN 模式,客户端配置示例文件已生成: +1. REALITY-TUN配置: ${CONFIG_PATH}/examples/reality_tun_example.json + +将配置文件导入到支持TUN模式的客户端后: +1. 切换到"TUN模式"选项卡 +2. 点击"启用TUN模式" +3. 选择"系统代理"或"全局代理" +4. 重启客户端并允许管理员权限 + +================================================================== + +配置文件路径: ${CONFIG_PATH}/config.json +服务控制: +启动: systemctl start xray +停止: systemctl stop xray +重启: systemctl restart xray +状态: systemctl status xray +================================================================== +EOF + + # 生成 REALITY TUN 模式配置示例 + generate_reality_tun_config + + log_info "客户端信息已保存到 /root/xray_info.txt" + + # 打印信息 + cat /root/xray_info.txt + + log_info "安装完成!Xray REALITY 已成功部署" + return 0 +} + +# 新增:生成 REALITY TUN 模式配置 +generate_reality_tun_config() { + log_info "生成 REALITY TUN 模式配置示例" + + # 确保IP地址已设置 + if [[ -z "$SERVER_IP" ]]; then + get_server_ip + fi + + # 创建 TUN 配置示例目录 + local example_dir="$CONFIG_PATH/examples" + mkdir -p "$example_dir" + + # 生成客户端配置示例,用于TUN模式 + cat > "$example_dir/reality_tun_example.json" << EOF +{ + "log": { + "loglevel": "warning" + }, + "inbounds": [ + { + "tag": "socks", + "port": 10808, + "listen": "127.0.0.1", + "protocol": "socks", + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ], + "routeOnly": false + }, + "settings": { + "auth": "noauth", + "udp": true, + "allowTransparent": false + } + }, + { + "tag": "tun", + "protocol": "tun", + "settings": { + "network": "all" + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ], + "routeOnly": false + } + } + ], + "tun": { + "enable": true, + "stack": "gvisor", + "mtu": 9000, + "strict_route": true + }, + "outbounds": [ + { + "tag": "proxy", + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "${SERVER_IP}", + "port": ${REALITY_PORT}, + "users": [ + { + "id": "${UUID}", + "flow": "xtls-rprx-vision", + "encryption": "none" + } + ] + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "fingerprint": "chrome", + "serverName": "www.shopify.com", + "publicKey": "${PUBLIC_KEY}", + "shortId": "6ba85179e30d", + "spiderX": "/search?q=cdn" + } + } + }, + { + "tag": "direct", + "protocol": "freedom" + }, + { + "tag": "block", + "protocol": "blackhole" + } + ], + "dns": { + "hosts": { + "dns.google": "8.8.8.8", + "proxy.example.com": "127.0.0.1" + }, + "servers": [ + { + "address": "1.1.1.1", + "domains": [ + "geosite:geolocation-!cn" + ], + "expectIPs": [ + "geoip:!cn" + ] + }, + { + "address": "223.5.5.5", + "domains": [ + "geosite:cn" + ], + "expectIPs": [ + "geoip:cn" + ] + }, + "8.8.8.8", + "https://dns.google/dns-query" + ], + "queryStrategy": "UseIP", + "disableCache": false, + "disableFallback": false + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "outboundTag": "proxy", + "domain": [ + "domain:google.com", + "domain:googleapis.cn", + "domain:gstatic.com" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "domain": [ + "geosite:cn" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "ip": [ + "geoip:private", + "geoip:cn" + ] + } + ] + } +} +EOF + + log_info "REALITY TUN 模式配置示例已保存到 $example_dir/reality_tun_example.json" + log_info "使用方法: 将配置导入到支持TUN模式的客户端,然后开启TUN模式" + + return 0 +} + +# 创建系统服务 +create_service() { + log_info "创建 Xray 系统服务" + + # 创建服务文件 + cat > /etc/systemd/system/xray.service << EOF +[Unit] +Description=Xray Service +Documentation=https://github.com/XTLS/Xray-core +After=network.target nss-lookup.target + +[Service] +User=root +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +NoNewPrivileges=true +ExecStart=$XRAY_PATH/xray run -config $CONFIG_PATH/config.json +Restart=on-failure +RestartPreventExitStatus=23 +LimitNPROC=10000 +LimitNOFILE=1000000 + +[Install] +WantedBy=multi-user.target +EOF + + # 重新加载 systemd 配置并启用服务 + systemctl daemon-reload + + echo -n "启用 Xray 服务... " + if systemctl enable xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "无法启用 Xray 服务" + return 1 + fi + + echo -n "启动 Xray 服务... " + if systemctl start xray; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "无法启动 Xray 服务" + return 1 + fi + + return 0 +} + +# 安装完整流程 - 更新为直接安装 VLESS+REALITY +install_xray() { + clear + echo "==================================================" + echo -e "${GREEN}开始安装 Xray REALITY 协议(稳定版本)${NC}" + echo "==================================================" + echo -e "${CYAN}自动配置功能列表:${NC}" + echo -e "✓ VLESS+REALITY 协议" + echo -e "✓ 速率限制保护" + echo -e "✓ TUN模式支持" + echo -e "✓ DNS优化 (DoH)" + echo -e "✓ 智能路由规则" + echo -e "✓ 自动防火墙配置" + echo -e "✓ 完全兼容当前版本" + echo "==================================================" + + # 检查是否为 root + check_root + + # 检查系统环境 + check_system + + # 备份现有配置 + backup_config + + # 安装依赖 + install_dependencies || { + log_error "安装依赖失败,退出安装" + return 1 + } + + # 下载和安装 Xray + download_xray || { + log_error "下载 Xray 失败,退出安装" + return 1 + } + + # 配置 Xray + configure_xray || { + log_error "配置 Xray 失败,退出安装" + return 1 + } + + # 创建系统服务 + create_service || { + log_error "创建系统服务失败,但会继续安装过程" + } + + # 配置防火墙 + configure_firewall + + # 生成客户端信息 + generate_client_info + + echo "" + echo "==================================================" + echo -e "${GREEN}🎉 安装完成!${NC}" + echo "==================================================" + echo -e "${CYAN}已自动启用的功能:${NC}" + echo -e "✅ VLESS+REALITY 协议" + echo -e "✅ 速率限制保护" + echo -e "✅ TUN模式支持" + echo -e "✅ DNS优化 (DoH)" + echo -e "✅ 智能路由规则" + echo -e "✅ 自动防火墙配置" + echo -e "✅ 完全兼容当前版本" + echo "==================================================" + log_info "Xray REALITY 协议(稳定版本)安装成功!" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 停止 Xray 服务 +stop_xray_service() { + log_info "停止 Xray 服务" + + if systemctl is-active xray &>/dev/null; then + echo -n "正在停止 Xray 服务... " + if systemctl stop xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Xray 服务,将尝试继续卸载" + fi + else + log_info "Xray 服务未运行" + fi + + echo -n "禁用 Xray 服务自启动... " + if systemctl disable xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${YELLOW}失败${NC}" + log_warn "无法禁用 Xray 服务自启动,服务可能不存在" + fi +} + +# 读取并关闭防火墙端口 +close_firewall_port() { + log_info "尝试关闭之前开放的防火墙端口" + + # 检查是否安装了 ufw + if ! command -v ufw &>/dev/null; then + log_warn "未检测到 UFW 防火墙,跳过防火墙配置关闭" + return 0 + fi + + # 检查ufw是否启用 + local ufw_status=$(ufw status | grep -o "Status: active" 2>/dev/null) + if [[ -z "$ufw_status" ]]; then + log_warn "UFW 防火墙未启用,跳过防火墙规则关闭" + return 0 + fi + + # 尝试从配置或备份文件中读取端口 + local ports=() + + # 从当前配置中查找端口 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + if command -v jq &>/dev/null; then + # 使用jq查找所有inbounds的端口 + local all_ports=$(jq '.inbounds[].port' "$CONFIG_PATH/config.json" 2>/dev/null) + for port in $all_ports; do + if [[ "$port" != "null" && -n "$port" ]]; then + ports+=("$port") + fi + done + else + # 使用grep查找端口 + local all_ports=$(grep -o '"port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}') + for port in $all_ports; do + if [[ -n "$port" ]]; then + ports+=("$port") + fi + done + fi + fi + + # 如果找不到端口,从备份中查找 + if [[ ${#ports[@]} -eq 0 && -n "$CONFIG_BACKUP" && -f "$CONFIG_BACKUP/config.json" ]]; then + if command -v jq &>/dev/null; then + local all_ports=$(jq '.inbounds[].port' "$CONFIG_BACKUP/config.json" 2>/dev/null) + for port in $all_ports; do + if [[ "$port" != "null" && -n "$port" ]]; then + ports+=("$port") + fi + done + else + local all_ports=$(grep -o '"port": [0-9]*' "$CONFIG_BACKUP/config.json" | awk '{print $2}') + for port in $all_ports; do + if [[ -n "$port" ]]; then + ports+=("$port") + fi + done + fi + fi + + # 如果还是找不到端口,尝试使用全局变量 + if [[ ${#ports[@]} -eq 0 ]]; then + if [[ -n "$REALITY_PORT" && "$REALITY_PORT" =~ ^[0-9]+$ ]]; then + ports+=("$REALITY_PORT") + fi + fi + + # 去除重复的端口 + if [[ ${#ports[@]} -gt 0 ]]; then + local unique_ports=() + local port_list="" + for port in "${ports[@]}"; do + # 检查端口是否为有效数字 + if ! [[ "$port" =~ ^[0-9]+$ ]]; then + log_warn "端口 '$port' 不是有效数字,已跳过" + continue + fi + + # 检查端口是否已在列表中 + if [[ "$port_list" != *",$port,"* ]]; then + unique_ports+=("$port") + port_list="$port_list,$port," + fi + done + ports=("${unique_ports[@]}") + fi + + # 如果找到了端口,关闭防火墙规则 + if [[ ${#ports[@]} -gt 0 ]]; then + local port_list=$(printf ", %s" "${ports[@]}") + port_list=${port_list:2} # 移除开头的逗号和空格 + log_info "找到端口: $port_list,尝试关闭 UFW 防火墙规则" + + local closed_ports=() + for port in "${ports[@]}"; do + # 检查端口是否已开放在UFW中 + if ! ufw status | grep -q "$port/tcp"; then + log_info "端口 $port 未在 UFW 中开放,跳过" + continue + fi + + echo -n "关闭 UFW 防火墙端口 $port... " + if ufw delete allow "$port/tcp" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + closed_ports+=("$port") + else + echo -e "${RED}失败${NC}" + log_warn "无法关闭端口 $port" + fi + done + + # 打印关闭的端口 + if [[ ${#closed_ports[@]} -gt 0 ]]; then + local closed_list=$(printf ", %s" "${closed_ports[@]}") + closed_list=${closed_list:2} # 移除开头的逗号和空格 + log_info "已关闭 UFW 防火墙中的端口: $closed_list" + else + log_warn "没有关闭任何端口" + fi + else + log_warn "无法确定之前使用的端口,跳过防火墙规则关闭" + fi +} + +# 删除 Xray 文件 +remove_xray_files() { + log_info "删除 Xray 文件" + + # 删除二进制文件 + echo -n "删除 Xray 核心文件... " + if rm -f "$XRAY_PATH/xray" "$XRAY_PATH/geoip.dat" "$XRAY_PATH/geosite.dat"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${YELLOW}部分文件未能删除${NC}" + log_warn "部分 Xray 核心文件可能未能完全删除" + fi + + # 删除配置目录(不删除备份) + echo -n "删除 Xray 配置目录... " + if [[ -d "$CONFIG_PATH" ]]; then + if rm -rf "$CONFIG_PATH"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法删除配置目录 $CONFIG_PATH" + fi + else + echo -e "${YELLOW}配置目录不存在${NC}" + fi + + # 删除日志目录 + echo -n "删除 Xray 日志目录... " + if [[ -d "$LOG_PATH" ]]; then + if rm -rf "$LOG_PATH"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法删除日志目录 $LOG_PATH" + fi + else + echo -e "${YELLOW}日志目录不存在${NC}" + fi + + # 删除服务文件 + echo -n "删除 Xray 服务文件... " + if rm -f /etc/systemd/system/xray.service; then + echo -e "${GREEN}成功${NC}" + systemctl daemon-reload + else + echo -e "${YELLOW}服务文件不存在或无法删除${NC}" + fi + + return 0 +} + +# 完整卸载流程 +uninstall_xray() { + clear + echo "==================================================" + echo -e "${RED}开始卸载 Xray${NC}" + echo "==================================================" + + # 确认卸载 + echo -e "${YELLOW}警告: 这将卸载 Xray 并删除相关文件${NC}" + read -rp "是否继续? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "卸载已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 检查 root 权限 + check_root + + # 备份配置 + backup_config + + # 停止服务 + stop_xray_service + + # 关闭防火墙端口 + close_firewall_port + + # 删除文件 + remove_xray_files + + echo "" + log_info "Xray 卸载完成" + + # 直接删除备份文件,不询问用户 + if [[ -n "$CONFIG_BACKUP" && -d "$CONFIG_BACKUP" ]]; then + rm -rf "$CONFIG_BACKUP" + log_info "备份文件已删除" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 检查 Xray 状态 +check_status() { + clear + echo "==================================================" + echo -e "${BLUE}Xray 状态检查${NC}" + echo "==================================================" + + # 检查是否安装 + if [[ ! -f "$XRAY_PATH/xray" ]]; then + echo -e "${RED}Xray 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 检查版本 + echo -n "Xray 版本: " + $XRAY_PATH/xray version | head -n 1 + + # 检查服务状态 + echo -n "服务状态: " + if systemctl is-active xray &>/dev/null; then + echo -e "${GREEN}运行中${NC}" + else + echo -e "${RED}未运行${NC}" + fi + + echo -n "自启动状态: " + if systemctl is-enabled xray &>/dev/null; then + echo -e "${GREEN}已启用${NC}" + else + echo -e "${RED}未启用${NC}" + fi + + # 获取内存和 CPU 使用情况 + echo "资源使用情况:" + ps -aux | grep xray | grep -v grep | awk '{print "内存使用: " $4 "%, CPU使用: " $3 "%"}' + + # 检查端口 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + local current_port="" + if command -v jq &>/dev/null; then + current_port=$(jq '.inbounds[0].port' "$CONFIG_PATH/config.json" 2>/dev/null) + else + current_port=$(grep -o '"port": [0-9]*' "$CONFIG_PATH/config.json" | head -1 | awk '{print $2}') + fi + + if [[ -n "$current_port" && "$current_port" != "null" ]]; then + echo -n "端口 $current_port 状态: " + if command -v ss &>/dev/null; then + if ss -tuln | grep -q ":$current_port "; then + echo -e "${GREEN}已开放${NC}" + else + echo -e "${RED}未开放${NC}" + fi + elif command -v netstat &>/dev/null; then + if netstat -tuln | grep -q ":$current_port "; then + echo -e "${GREEN}已开放${NC}" + else + echo -e "${RED}未开放${NC}" + fi + else + echo -e "${YELLOW}无法检查${NC}" + fi + + # 显示链接数 + echo "当前连接:" + if command -v ss &>/dev/null; then + ss -tn | grep ":$current_port" | wc -l | awk '{print "活跃连接数: " $1}' + elif command -v netstat &>/dev/null; then + netstat -tn | grep ":$current_port" | wc -l | awk '{print "活跃连接数: " $1}' + else + echo "无法获取连接信息" + fi + fi + fi + + echo -e "\n最近的日志:" + if [[ -f "$LOG_PATH/error.log" ]]; then + tail -n 10 "$LOG_PATH/error.log" + else + echo "找不到错误日志文件" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 显示配置信息 +show_config() { + clear + echo "==================================================" + echo -e "${CYAN}Xray 配置信息${NC}" + echo "==================================================" + + # 检查是否已安装 + if [[ ! -f "$XRAY_PATH/xray" ]]; then + echo -e "${RED}Xray 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 显示配置文件内容 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + if command -v jq &>/dev/null; then + echo "配置信息 (美化格式):" + jq . "$CONFIG_PATH/config.json" + else + echo "配置文件内容:" + cat "$CONFIG_PATH/config.json" + fi + else + echo -e "${RED}找不到配置文件${NC}" + fi + + # 显示客户端信息 + if [[ -f "/root/xray_info.txt" ]]; then + echo -e "\n客户端信息:" + cat /root/xray_info.txt + else + echo -e "\n${RED}找不到客户端信息文件${NC}" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 重启 Xray 服务 +restart_service() { + clear + echo "==================================================" + echo -e "${GREEN}重启 Xray 服务${NC}" + echo "==================================================" + + # 检查是否已安装 + if [[ ! -f "$XRAY_PATH/xray" ]]; then + echo -e "${RED}Xray 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + echo -n "重启 Xray 服务... " + if systemctl restart xray; then + echo -e "${GREEN}成功${NC}" + log_info "Xray 服务已重启" + else + echo -e "${RED}失败${NC}" + log_error "无法重启 Xray 服务" + fi + + # 检查服务状态 + echo -n "Xray 服务状态: " + if systemctl is-active xray &>/dev/null; then + echo -e "${GREEN}运行中${NC}" + else + echo -e "${RED}未运行${NC}" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 更新 Xray +update_xray() { + clear + echo "==================================================" + echo -e "${YELLOW}更新 Xray${NC}" + echo "==================================================" + + # 检查是否已安装 + if [[ ! -f "$XRAY_PATH/xray" ]]; then + echo -e "${RED}Xray 未安装,请先安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 获取当前版本 + local current_version + current_version=$($XRAY_PATH/xray version | head -n 1 | cut -d ' ' -f 2) + echo "当前版本: $current_version" + + # 获取最新版本 + echo -n "获取最新版本... " + local latest_version=$(get_latest_version) + echo -e "${GREEN}$latest_version${NC}" + + # 比较版本 + if [[ "$current_version" == "$latest_version" ]]; then + echo -e "${GREEN}已经是最新版本${NC}" + read -rp "是否强制更新? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "更新已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + fi + + # 备份配置 + backup_config + + # 停止服务 + echo -n "停止 Xray 服务... " + if systemctl stop xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Xray 服务,将尝试继续更新" + fi + + # 下载新版本 + log_info "开始下载新版本" + + # 创建临时目录 + local tmp_dir="/tmp/xray_update" + mkdir -p "$tmp_dir" + + # 确定系统架构 + local arch + case $(uname -m) in + x86_64|amd64) arch="64" ;; + armv7l|armv8l) arch="arm32-v7a" ;; + aarch64) arch="arm64-v8a" ;; + *) arch="64" ;; # 默认使用64位版本 + esac + + # 构建下载URL + local download_url="https://github.com/XTLS/Xray-core/releases/download/$latest_version/Xray-linux-$arch.zip" + log_info "下载链接: $download_url" + + # 下载Xray + echo -n "下载 Xray... " + if wget -q --show-progress -O "$tmp_dir/xray.zip" "$download_url"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "下载 Xray 失败" + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 解压文件 + echo -n "解压 Xray... " + if unzip -q -o "$tmp_dir/xray.zip" -d "$tmp_dir"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "解压 Xray 失败" + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 备份旧的二进制文件 + mv "$XRAY_PATH/xray" "$XRAY_PATH/xray.old" 2>/dev/null + + # 复制新文件 + echo -n "更新 Xray 核心文件... " + if cp "$tmp_dir/xray" "$XRAY_PATH/xray" && chmod +x "$XRAY_PATH/xray"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "更新 Xray 核心文件失败" + + # 恢复旧文件 + if [[ -f "$XRAY_PATH/xray.old" ]]; then + mv "$XRAY_PATH/xray.old" "$XRAY_PATH/xray" + log_warn "已恢复为旧版本" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 更新 geoip.dat 和 geosite.dat + echo -n "更新 GeoIP 和 GeoSite 数据... " + if cp "$tmp_dir/geoip.dat" "$XRAY_PATH/geoip.dat" && \ + cp "$tmp_dir/geosite.dat" "$XRAY_PATH/geosite.dat"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${YELLOW}失败${NC}" + log_warn "更新 GeoIP 和 GeoSite 数据失败,但不影响核心功能" + fi + + # 删除旧备份和临时文件 + rm -f "$XRAY_PATH/xray.old" + rm -rf "$tmp_dir" + + # 启动服务 + echo -n "启动 Xray 服务... " + if systemctl start xray; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "启动 Xray 服务失败,请检查配置" + systemctl status xray + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 检查更新后的版本 + local new_version + new_version=$($XRAY_PATH/xray version | head -n 1 | cut -d ' ' -f 2) + + log_info "Xray 更新完成,版本: $new_version" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 更新脚本版本信息 +update_script_version() { + clear + echo "==================================================" + echo -e "${GREEN}更新脚本版本信息${NC}" + echo "==================================================" + + # 显示当前脚本版本 + echo "当前脚本版本: $SCRIPT_VERSION" + + # 获取最新脚本版本 + echo -n "获取最新脚本版本... " + local latest_version + latest_version=$(curl -s https://raw.githubusercontent.com/XTLS/Xray-core/main/xray-manager.sh | grep -o 'SCRIPT_VERSION="[^"]*"' | cut -d'"' -f2) + + if [[ -z "$latest_version" ]]; then + echo -e "${RED}失败${NC}" + log_error "无法获取最新脚本版本信息" + read -rp "是否继续更新? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "更新已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + latest_version="1.0.0" # 设置一个固定版本作为备用 + log_info "将使用固定版本: $latest_version" + else + echo -e "${GREEN}$latest_version${NC}" + + # 比较版本 + if [[ "$SCRIPT_VERSION" == "$latest_version" ]]; then + echo -e "${GREEN}已经是最新版本${NC}" + read -rp "是否强制更新? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "更新已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + fi + fi + + # 更新脚本版本 + echo -n "更新脚本版本... " + sed -i "s/SCRIPT_VERSION=\"[^\"]*\"/SCRIPT_VERSION=\"$latest_version\"/" "$0" + echo -e "${GREEN}成功${NC}" + + log_info "脚本版本已更新为 $latest_version" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 主函数 +main() { + # 处理命令行参数 + if [[ $# -gt 0 ]]; then + case "$1" in + -h|--help) + show_help + exit 0 + ;; + -i|--install) + check_root + install_xray + exit 0 + ;; + -u|--uninstall) + check_root + uninstall_xray + exit 0 + ;; + -s|--status) + check_root + check_status + exit 0 + ;; + -up|--update) + check_root + update_xray + exit 0 + ;; + *) + log_error "未知参数: $1" + show_help + exit 1 + ;; + esac + fi + + # 无参数则显示菜单 + check_root + show_menu +} + +# 执行主函数 +main "$@" \ No newline at end of file diff --git a/xray-manager.sh b/xray-manager.sh new file mode 100644 index 0000000..40eb98b --- /dev/null +++ b/xray-manager.sh @@ -0,0 +1,1934 @@ +#!/bin/bash +# Xray 管理脚本 - 集成安装、卸载和管理功能 +# 专门支持 VLESS+REALITY 协议 +# 使用方法: chmod +x xray-manager.sh && ./xray-manager.sh + +# 全局变量 +XRAY_PATH="/usr/local/bin" +CONFIG_PATH="/usr/local/etc/xray" +REALITY_PORT="" # VLESS+REALITY 端口 +LOG_PATH="/var/log/xray" +LOG_FILE="/var/log/xray-manager.log" +SCRIPT_VERSION="1.0.0" +XRAY_VERSION="v25.3.6" # 当前固定的Xray版本 +UUID="" +PRIVATE_KEY="" +PUBLIC_KEY="" +CONFIG_BACKUP="" +SERVER_IP="" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# 进度条函数 +show_progress() { + local pid=$1 + local delay=0.1 + local spinstr='|/-\' + local temp + local count=0 + local start_time=$(date +%s) + echo -n " " + + while ps -p $pid > /dev/null; do + temp=${spinstr#?} + printf "\r[%c] %s 已进行 %ds" "$spinstr" "$2" "$(($(date +%s) - start_time))" + spinstr=$temp${spinstr%"$temp"} + sleep $delay + count=$((count + 1)) + done + printf "\r\033[K" +} + +# 进度条显示函数 - 适用于apt操作 +apt_progress() { + local cmd="$1" + local msg="$2" + local logfile="$LOG_FILE.tmp" + + echo -e "${CYAN}开始 $msg...${NC}" + touch "$logfile" + ($cmd 2>&1 | tee -a "$LOG_FILE" > "$logfile") & + local pid=$! + + # 显示进度条 + local start_time=$(date +%s) + local dots="" + local status="" + local progress=0 + local last_line="" + local delay=0.2 + + while ps -p $pid > /dev/null; do + # 读取最后一行日志 + if [[ -f "$logfile" ]]; then + last_line=$(tail -n 1 "$logfile") + + # 尝试从输出中提取进度信息 + if [[ $last_line == *%* ]]; then + status="${last_line%%(*}" + progress="${last_line#*(}" + progress="${progress%%)*}" + printf "\r\033[K${CYAN}[$msg]${NC} %s %s " "$status" "$progress" + else + dots="${dots}." + if [[ ${#dots} -gt 5 ]]; then dots="."; fi + elapsed=$(($(date +%s) - start_time)) + printf "\r\033[K${CYAN}[$msg]${NC} 进行中%s (%ds)" "$dots" "$elapsed" + fi + fi + sleep $delay + done + + if wait $pid; then + printf "\r\033[K${GREEN}[$msg]${NC} 完成! 用时: %ds\n" "$(($(date +%s) - start_time))" + rm -f "$logfile" + return 0 + else + printf "\r\033[K${RED}[$msg]${NC} 失败! 查看日志: $LOG_FILE\n" + rm -f "$logfile" + return 1 + fi +} + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" | tee -a "$LOG_FILE" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_FILE" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 | tee -a "$LOG_FILE" +} + +log_debug() { + echo -e "${BLUE}[DEBUG]${NC} $1" | tee -a "$LOG_FILE" +} + +# 检查是否有 root 权限 +check_root() { + if [[ $EUID -ne 0 ]]; then + log_error "此脚本需要 root 权限运行" + exit 1 + fi +} + +# 检查系统环境 +check_system() { + # 显示系统信息 + echo "系统信息:" + echo "------------------------" + if [ -f /etc/os-release ]; then + cat /etc/os-release | grep "PRETTY_NAME" | cut -d= -f2- | tr -d '"' + fi + echo "内核版本: $(uname -r)" + echo "架构: $(uname -m)" + echo "------------------------" + + # 检查是否为 Debian/Ubuntu 系统 + if [[ ! -f /etc/debian_version && ! -f /etc/lsb-release ]]; then + log_warn "未检测到 Debian/Ubuntu 系统,脚本可能无法正常工作" + read -rp "是否继续? [y/N] " response + if [[ ! "$response" =~ ^[yY]$ ]]; then + exit 1 + fi + fi + + # 检查网络连接 + echo -n "检查网络连接... " + if ping -c 1 -W 2 github.com &>/dev/null; then + echo -e "${GREEN}连接正常${NC}" + else + echo -e "${YELLOW}无法连接到 GitHub${NC}" + log_warn "无法连接到 GitHub,请检查网络连接" + read -rp "是否继续? [y/N] " response + if [[ ! "$response" =~ ^[yY]$ ]]; then + exit 1 + fi + fi +} + +# 获取最新版本号 +get_latest_version() { + # 利用GitHub重定向特性获取最新版本 + local redirect_url=$(curl -s -L -o /dev/null -w '%{url_effective}' https://github.com/XTLS/Xray-core/releases/latest 2>/dev/null) + local version=$(echo "$redirect_url" | grep -o 'tag/v[0-9.]*' | cut -d/ -f2 2>/dev/null) + + # 如果获取失败,尝试备用方法 + if [[ -z "$version" ]]; then + # 方法2: 通过API获取 + version=$(curl -s https://api.github.com/repos/XTLS/Xray-core/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4 2>/dev/null) + fi + + # 如果还是失败,返回当前全局版本 + if [[ -z "$version" ]]; then + version="$XRAY_VERSION" + else + # 更新全局变量 + XRAY_VERSION="$version" + fi + + echo "$version" +} + +# 备份函数 +backup_config() { + if [[ -d "$CONFIG_PATH" ]]; then + CONFIG_BACKUP="${CONFIG_PATH}_backup_$(date +%Y%m%d%H%M%S)" + log_info "备份现有配置到 $CONFIG_BACKUP" + cp -r "$CONFIG_PATH" "$CONFIG_BACKUP" || log_warn "配置备份失败" + + # 备份 Xray 信息文件 + if [[ -f "/root/xray_info.txt" ]]; then + cp "/root/xray_info.txt" "${CONFIG_BACKUP}/xray_info.txt.bak" || log_warn "Xray 信息文件备份失败" + fi + else + log_warn "找不到配置目录,跳过备份" + fi +} + +# 显示帮助信息 +show_help() { + echo "Xray 管理脚本 v${SCRIPT_VERSION}" + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " -h, --help 显示此帮助信息" + echo " -i, --install 直接运行安装" + echo " -u, --uninstall 直接运行卸载" + echo " -s, --status 查看 Xray 状态" + echo " -up, --update 更新 Xray" + echo "" + echo "无参数运行脚本将显示交互式菜单" +} + +# 菜单函数 +show_menu() { + clear + echo "==================================================" + echo -e "${CYAN}Xray REALITY管理脚本 v${SCRIPT_VERSION}${NC}" + echo -e "${CYAN}(VLESS+REALITY 协议)${NC}" + echo "==================================================" + echo -e "1) ${GREEN}安装 Xray${NC}" + echo -e "2) ${RED}卸载 Xray${NC}" + echo -e "3) ${YELLOW}更新 Xray${NC}" + echo -e "4) ${BLUE}查看 Xray 状态${NC}" + echo -e "5) ${CYAN}查看 Xray 配置信息${NC}" + echo -e "6) ${GREEN}重启 Xray 服务${NC}" + echo -e "7) ${YELLOW}手动设置 Xray 版本号${NC}" + echo -e "0) ${RED}退出${NC}" + echo "==================================================" + echo "" + read -rp "请输入选项 [0-7]: " choice + + case $choice in + 1) install_xray ;; + 2) uninstall_xray ;; + 3) update_xray ;; + 4) check_status ;; + 5) show_config ;; + 6) restart_service ;; + 7) set_xray_version ;; + 0) exit 0 ;; + *) log_error "无效选项" && sleep 2 && show_menu ;; + esac +} + +# 设置Xray版本号 +set_xray_version() { + clear + echo "==================================================" + echo -e "${YELLOW}手动设置 Xray 版本号${NC}" + echo "==================================================" + + echo "当前Xray版本号: $XRAY_VERSION" + echo "" + echo "1) 自动获取最新版本" + echo "2) 手动输入版本号" + echo "0) 返回主菜单" + echo "" + + read -rp "请选择操作 [0-2]: " version_choice + + case $version_choice in + 1) + echo -n "正在获取最新版本... " + local latest_version=$(get_latest_version) + + if [[ "$latest_version" == "$XRAY_VERSION" && ! -z "$latest_version" ]]; then + echo -e "${GREEN}成功${NC}" + log_info "当前已是最新版本: $XRAY_VERSION" + elif [[ -z "$latest_version" || "$latest_version" == "v1.8.4" ]]; then + echo -e "${RED}失败${NC}" + log_error "无法自动获取最新版本" + else + echo -e "${GREEN}成功${NC}" + XRAY_VERSION="$latest_version" + log_info "Xray版本已自动更新为: $XRAY_VERSION" + fi + ;; + 2) + echo "请访问 https://github.com/XTLS/Xray-core/releases 查看可用版本" + read -rp "请输入新的版本号(例如 v25.3.6): " new_version + + if [[ -z "$new_version" ]]; then + log_error "版本号不能为空" + elif [[ ! "$new_version" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + log_error "版本号格式不正确,请使用类似 v25.3.6 的格式" + else + XRAY_VERSION="$new_version" + log_info "Xray版本已更新为: $XRAY_VERSION" + fi + ;; + 0) + # 直接返回 + ;; + *) + log_error "无效选项" + ;; + esac + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 安装依赖 +install_dependencies() { + log_info "安装必要依赖" + + # 更新软件包列表 + apt_progress "apt-get update" "更新软件包列表" || { + log_error "更新软件包列表失败" + return 1 + } + + # 安装必要工具 + local deps=(curl wget jq unzip) + for dep in "${deps[@]}"; do + if ! command -v "$dep" &>/dev/null; then + apt_progress "apt-get install -y $dep" "安装 $dep" || { + log_error "安装 $dep 失败" + return 1 + } + else + log_info "$dep 已安装,跳过" + fi + done + + # 检查安装结果 + local all_installed=true + for dep in "${deps[@]}"; do + if ! command -v "$dep" &>/dev/null; then + log_error "$dep 安装失败" + all_installed=false + fi + done + + if [ "$all_installed" = true ]; then + log_info "所有依赖安装完成" + return 0 + else + log_error "部分依赖安装失败" + return 1 + fi +} + +# 下载 Xray +download_xray() { + log_info "开始下载 Xray" + + # 检查Xray服务是否正在运行,如果是则停止 + if systemctl is-active xray &>/dev/null; then + log_info "检测到Xray服务正在运行,先停止服务" + echo -n "停止 Xray 服务... " + if systemctl stop xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Xray 服务,可能会影响安装" + fi + + # 等待进程完全停止 + echo -n "等待进程释放资源... " + sleep 2 + if pgrep -x "xray" > /dev/null; then + # 如果进程仍在运行,尝试强制终止 + pkill -9 -x "xray" &>/dev/null + sleep 1 + fi + echo -e "${GREEN}完成${NC}" + fi + + # 创建临时目录 + local tmp_dir="/tmp/xray_install" + mkdir -p "$tmp_dir" + + # 获取最新版本 + echo -n "获取 Xray 最新版本... " + local latest_version=$(get_latest_version) + echo -e "${GREEN}$latest_version${NC}" + + # 确定系统架构 + local arch + case $(uname -m) in + x86_64|amd64) arch="64" ;; + armv7l|armv8l) arch="arm32-v7a" ;; + aarch64) arch="arm64-v8a" ;; + *) arch="64" ;; # 默认使用64位版本 + esac + + # 构建下载URL + local download_url="https://github.com/XTLS/Xray-core/releases/download/$latest_version/Xray-linux-$arch.zip" + log_info "下载链接: $download_url" + + # 下载Xray + echo -n "下载 Xray... " + if wget -q --show-progress -O "$tmp_dir/xray.zip" "$download_url"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "下载 Xray 失败" + return 1 + fi + + # 解压文件 + echo -n "解压 Xray... " + if unzip -q -o "$tmp_dir/xray.zip" -d "$tmp_dir"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "解压 Xray 失败" + return 1 + fi + + # 创建目录 + mkdir -p "$XRAY_PATH" "$CONFIG_PATH" "$LOG_PATH" + + # 复制文件前确保目标文件不被占用 + if [[ -f "$XRAY_PATH/xray" ]]; then + # 如果文件存在,先尝试重命名它 + mv "$XRAY_PATH/xray" "$XRAY_PATH/xray.old" 2>/dev/null + fi + + # 复制文件 + echo -n "安装 Xray 核心文件... " + if cp "$tmp_dir/xray" "$XRAY_PATH/xray" && chmod +x "$XRAY_PATH/xray"; then + echo -e "${GREEN}成功${NC}" + # 删除旧文件 + rm -f "$XRAY_PATH/xray.old" 2>/dev/null + else + echo -e "${RED}失败${NC}" + log_error "安装 Xray 核心文件失败" + # 恢复旧文件 + if [[ -f "$XRAY_PATH/xray.old" ]]; then + mv "$XRAY_PATH/xray.old" "$XRAY_PATH/xray" 2>/dev/null + fi + return 1 + fi + + # 复制 geoip.dat 和 geosite.dat + echo -n "安装 GeoIP 和 GeoSite 数据... " + if cp "$tmp_dir/geoip.dat" "$XRAY_PATH/geoip.dat" && \ + cp "$tmp_dir/geosite.dat" "$XRAY_PATH/geosite.dat"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "安装 GeoIP 和 GeoSite 数据失败,将在配置时下载" + fi + + # 清理临时文件 + rm -rf "$tmp_dir" + + log_info "Xray $latest_version 安装完成" + return 0 +} + +# 生成随机 PORT 和 UUID +generate_random_values() { + log_info "生成随机配置值" + + # 询问用户是否指定端口 + if [[ -z "$REALITY_PORT" ]]; then + read -rp "是否指定端口? [y/N] " specify_port + if [[ "$specify_port" =~ ^[yY]$ ]]; then + # 用户选择指定端口 + while true; do + read -rp "请输入端口号 (1-65535): " REALITY_PORT + # 验证端口是否为有效数字 + if ! [[ "$REALITY_PORT" =~ ^[0-9]+$ ]] || [ "$REALITY_PORT" -lt 1 ] || [ "$REALITY_PORT" -gt 65535 ]; then + log_error "无效的端口号,请输入1-65535之间的数字" + continue + fi + + # 检查端口是否被占用 + if ss -tuln | grep -q ":$REALITY_PORT "; then + log_warn "端口 $REALITY_PORT 已被占用,请选择其他端口" + continue + fi + + log_info "将使用指定端口: $REALITY_PORT" + break + done + else + # 用户选择随机端口,继续原来的逻辑 + # 尝试找一个未被占用的端口 + local attempts=0 + while [[ "$attempts" -lt 10 ]]; do + REALITY_PORT=$(shuf -i 10000-60000 -n 1) + # 检查端口是否被占用 + if ! ss -tuln | grep -q ":$REALITY_PORT "; then + log_info "生成随机端口: $REALITY_PORT" + break + fi + attempts=$((attempts + 1)) + done + if [[ "$attempts" -eq 10 ]]; then + log_warn "无法找到未占用的端口,使用随机端口: $REALITY_PORT" + fi + fi + else + # 验证端口是否为有效数字 + if ! [[ "$REALITY_PORT" =~ ^[0-9]+$ ]]; then + log_warn "无效的端口: $REALITY_PORT,生成新的随机端口" + REALITY_PORT=$(shuf -i 10000-60000 -n 1) + fi + fi + + # 生成 UUID + if [[ -z "$UUID" ]]; then + # 检查xray命令是否可用 + if [[ -f "$XRAY_PATH/xray" && -x "$XRAY_PATH/xray" ]]; then + UUID=$($XRAY_PATH/xray uuid) + log_info "生成 UUID: $UUID" + else + # 如果xray不可用,使用uuidgen或者随机生成 + if command -v uuidgen &>/dev/null; then + UUID=$(uuidgen) + else + # 简单的UUID生成方法(不完全符合标准但足够使用) + UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || + (date +%s%N | sha256sum | head -c 32 | + sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-/')) + fi + log_info "生成 UUID: $UUID" + fi + else + # 验证UUID格式 + if ! [[ "$UUID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then + log_warn "无效的 UUID 格式: $UUID,生成新的 UUID" + if [[ -f "$XRAY_PATH/xray" && -x "$XRAY_PATH/xray" ]]; then + UUID=$($XRAY_PATH/xray uuid) + else + UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || + (date +%s%N | sha256sum | head -c 32 | + sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-/')) + fi + log_info "生成新的 UUID: $UUID" + fi + fi + + # 生成 REALITY 密钥对 + log_info "生成 REALITY 密钥对" + if [[ -z "$PRIVATE_KEY" || -z "$PUBLIC_KEY" ]]; then + local key_pair + if [[ -f "$XRAY_PATH/xray" && -x "$XRAY_PATH/xray" ]]; then + key_pair=$($XRAY_PATH/xray x25519) + PRIVATE_KEY=$(echo "$key_pair" | grep "Private" | awk '{print $3}') + PUBLIC_KEY=$(echo "$key_pair" | grep "Public" | awk '{print $3}') + else + log_warn "无法使用 xray 生成密钥对,将跳过密钥生成" + log_info "安装完成后,将自动生成密钥对" + fi + fi + + # 获取服务器IP + get_server_ip + + log_debug "私钥: $PRIVATE_KEY" + log_debug "公钥: $PUBLIC_KEY" +} + +# 获取服务器IP +get_server_ip() { + log_info "获取服务器IP地址" + + if [[ -n "$SERVER_IP" ]]; then + log_info "使用已设置的IP: $SERVER_IP" + return 0 + fi + + # 尝试首选方法获取公网IP + SERVER_IP=$(curl -s -m 5 https://api.ipify.org 2>/dev/null) + + # 验证获取到的IP是否为有效IPv4地址 + if [[ -n "$SERVER_IP" && "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + log_info "成功获取公网IP: $SERVER_IP" + return 0 + fi + + # 如果第一个方法失败,尝试备用方法 + local backup_services=("https://ifconfig.me" "https://ip.sb" "https://ipinfo.io/ip") + + for service in "${backup_services[@]}"; do + SERVER_IP=$(curl -s -m 3 "$service" 2>/dev/null) + if [[ -n "$SERVER_IP" && "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + log_info "成功从 $service 获取公网IP: $SERVER_IP" + return 0 + fi + done + + # 如果所有公网IP获取方式都失败,则使用本地IP + if command -v hostname &>/dev/null; then + SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}') + fi + + # 如果hostname命令失败,尝试使用ip命令 + if [[ -z "$SERVER_IP" && -x "$(command -v ip)" ]]; then + SERVER_IP=$(ip -4 addr show scope global | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n 1) + fi + + # 如果还是失败,尝试使用ifconfig命令 + if [[ -z "$SERVER_IP" && -x "$(command -v ifconfig)" ]]; then + SERVER_IP=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -n 1) + fi + + if [[ -n "$SERVER_IP" ]]; then + log_warn "无法获取公网IP,使用本地IP: $SERVER_IP" + return 0 + else + log_error "无法获取任何有效IP地址,将使用127.0.0.1作为占位符" + SERVER_IP="127.0.0.1" + return 1 + fi +} + +# 配置 Xray - 修改为 VLESS+REALITY 并支持 TUN 模式 +configure_xray() { + log_info "配置 Xray" + + # 生成随机值 + generate_random_values + + # 创建配置目录 + mkdir -p "$CONFIG_PATH" + + # 创建示例目录 + mkdir -p "$CONFIG_PATH/examples" + + log_info "创建 VLESS+REALITY 配置文件(支持TUN模式)" + + # VLESS + REALITY 配置 (添加支持TUN模式的DNS和路由配置) + cat > "$CONFIG_PATH/config.json" << EOF +{ + "log": { + "loglevel": "warning", + "access": "$LOG_PATH/access.log", + "error": "$LOG_PATH/error.log" + }, + "inbounds": [ + { + "port": $REALITY_PORT, + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "$UUID", + "flow": "xtls-rprx-vision" + } + ], + "decryption": "none" + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "dest": "www.shopify.com:443", + "xver": 0, + "serverNames": [ + "shopify.com", + "www.shopify.com" + ], + "privateKey": "$PRIVATE_KEY", + "shortIds": [ + "", + "6ba85179e30d" + ] + } + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls", + "quic" + ], + "routeOnly": false + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "tag": "direct" + }, + { + "protocol": "blackhole", + "tag": "block" + } + ], + "dns": { + "hosts": { + "dns.google": "8.8.8.8", + "proxy.example.com": "127.0.0.1" + }, + "servers": [ + { + "address": "1.1.1.1", + "domains": [ + "geosite:geolocation-!cn" + ], + "expectIPs": [ + "geoip:!cn" + ] + }, + { + "address": "223.5.5.5", + "domains": [ + "geosite:cn" + ], + "expectIPs": [ + "geoip:cn" + ] + }, + "8.8.8.8", + "https://dns.google/dns-query" + ] + }, + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "ip": [ + "geoip:private" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "domain": [ + "geosite:private" + ] + }, + { + "type": "field", + "port": "443", + "network": "udp", + "outboundTag": "block" + }, + { + "type": "field", + "outboundTag": "direct", + "ip": [ + "geoip:cn" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "domain": [ + "geosite:cn" + ] + } + ] + } +} +EOF + + if [[ -f "$CONFIG_PATH/config.json" ]]; then + log_info "配置文件创建成功" + else + log_error "配置文件创建失败" + return 1 + fi + + return 0 +} + +# 配置防火墙 - 简化只处理 REALITY 端口 +configure_firewall() { + log_info "配置防火墙" + + # 检测和关闭旧端口 + local old_ports=() + + # 从备份中查找旧端口 + if [[ -n "$CONFIG_BACKUP" && -f "$CONFIG_BACKUP/config.json" ]]; then + log_info "检测旧配置中的端口" + if command -v jq &>/dev/null; then + # 使用jq查找所有inbounds的端口 + local detected_ports=$(jq '.inbounds[].port' "$CONFIG_BACKUP/config.json" 2>/dev/null) + for port in $detected_ports; do + if [[ "$port" != "null" && -n "$port" ]]; then + old_ports+=("$port") + log_info "检测到旧端口: $port" + fi + done + else + # 使用grep查找端口 + local detected_ports=$(grep -o '"port": [0-9]*' "$CONFIG_BACKUP/config.json" | awk '{print $2}') + for port in $detected_ports; do + if [[ -n "$port" ]]; then + old_ports+=("$port") + log_info "检测到旧端口: $port" + fi + done + fi + fi + + # 如果找不到旧端口,也查找默认位置 + if [[ ${#old_ports[@]} -eq 0 && -f "$CONFIG_PATH/config.json" && "$CONFIG_PATH/config.json" != "$(readlink -f "$CONFIG_BACKUP/config.json")" ]]; then + if command -v jq &>/dev/null; then + # 使用jq查找所有inbounds的端口 + local detected_ports=$(jq '.inbounds[].port' "$CONFIG_PATH/config.json" 2>/dev/null) + for port in $detected_ports; do + if [[ "$port" != "null" && -n "$port" ]]; then + old_ports+=("$port") + log_info "检测到当前配置的端口: $port" + fi + done + else + # 使用grep查找端口 + local detected_ports=$(grep -o '"port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}') + for port in $detected_ports; do + if [[ -n "$port" ]]; then + old_ports+=("$port") + log_info "检测到当前配置的端口: $port" + fi + done + fi + fi + + # 从旧端口列表中过滤掉当前端口 + local filtered_ports=() + for port in "${old_ports[@]}"; do + # 检查端口是否为有效数字 + if ! [[ "$port" =~ ^[0-9]+$ ]]; then + log_warn "无效的端口号: $port,已跳过" + continue + fi + + # 检查是否与当前端口相同 + if [[ "$port" -eq "$REALITY_PORT" ]]; then + log_info "端口 $port 与当前端口相同,已跳过" + continue + fi + + filtered_ports+=("$port") + done + old_ports=("${filtered_ports[@]}") + + # 检查是否安装了 ufw + if command -v ufw &>/dev/null; then + # 检查ufw是否启用 + local ufw_status=$(ufw status | grep -o "Status: active" 2>/dev/null) + + if [[ -z "$ufw_status" ]]; then + log_warn "UFW 防火墙未启用,可能需要手动配置防火墙规则" + log_info "您可以运行 'sudo ufw enable' 启用 UFW 防火墙" + return 0 + fi + + # 关闭旧端口 + if [[ ${#old_ports[@]} -gt 0 ]]; then + log_info "开始关闭旧端口" + local closed_ports=() + + for port in "${old_ports[@]}"; do + # 检查端口是否已开放在UFW中 + if ! ufw status | grep -q "$port/tcp"; then + log_info "端口 $port 未在 UFW 中开放,跳过" + continue + fi + + echo -n "关闭 UFW 防火墙端口 $port... " + if ufw delete allow "$port/tcp" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + closed_ports+=("$port") + else + echo -e "${RED}失败${NC}" + log_warn "无法关闭端口 $port" + fi + done + + # 打印关闭的端口 + if [[ ${#closed_ports[@]} -gt 0 ]]; then + local closed_list=$(printf ", %s" "${closed_ports[@]}") + closed_list=${closed_list:2} # 移除开头的逗号和空格 + log_info "已关闭 UFW 防火墙中的旧端口: $closed_list" + else + log_info "没有需要关闭的旧端口" + fi + else + log_info "未检测到旧端口,跳过关闭端口步骤" + fi + + # 确定需要开放的端口 + local ports=("$REALITY_PORT") + + echo -n "配置 UFW 防火墙... " + local opened_ports=() + for port in "${ports[@]}"; do + # 检查端口是否为有效数字 + if ! [[ "$port" =~ ^[0-9]+$ ]]; then + log_warn "端口 '$port' 不是有效数字,已跳过" + continue + fi + + # 检查端口是否已经开放 + if ufw status | grep -q "$port/tcp"; then + log_info "端口 $port 已经开放,跳过" + opened_ports+=("$port") + continue + fi + + # 开放端口 + if ufw allow "$port/tcp" &>/dev/null; then + opened_ports+=("$port") + else + log_warn "无法开放端口 $port" + fi + done + echo -e "${GREEN}完成${NC}" + + # 打印开放的端口 + if [[ ${#opened_ports[@]} -gt 0 ]]; then + local port_list=$(printf ", %s" "${opened_ports[@]}") + port_list=${port_list:2} # 移除开头的逗号和空格 + log_info "已在 UFW 防火墙开放端口: $port_list" + else + log_warn "没有成功开放任何端口" + fi + else + log_warn "未检测到 UFW 防火墙,跳过防火墙配置" + log_info "如需管理防火墙规则,请安装 UFW: sudo apt install ufw" + fi + + return 0 +} + +# 生成客户端配置 - 更新为只提供 VLESS+REALITY +generate_client_info() { + log_info "生成客户端信息" + + # 确保IP地址已获取 + if [[ -z "$SERVER_IP" ]]; then + get_server_ip + fi + + # 生成 VLESS + REALITY 分享链接 + local share_link="vless://${UUID}@${SERVER_IP}:${REALITY_PORT}?security=reality&encryption=none&pbk=${PUBLIC_KEY}&fp=chrome&type=tcp&flow=xtls-rprx-vision&sni=www.shopify.com&sid=6ba85179e30d#Xray-Reality" + + # 保存客户端信息 + cat > /root/xray_info.txt << EOF +========================= Xray Reality 配置信息 ========================= +服务器地址: ${SERVER_IP} +端口: ${REALITY_PORT} +UUID: ${UUID} +协议: vless +传输协议: tcp +加密方式: none +流控: xtls-rprx-vision +安全: reality +公钥: ${PUBLIC_KEY} +私钥: ${PRIVATE_KEY} +SNI: www.shopify.com +指纹: chrome +短 ID: 6ba85179e30d +================================================================== + +分享链接: +${share_link} + +二维码链接: +https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${share_link} +================================================================== + +TUN模式支持: +REALITY 协议已配置支持 TUN 模式,客户端配置示例文件已生成: +1. REALITY-TUN配置: ${CONFIG_PATH}/examples/reality_tun_example.json + +将配置文件导入到支持TUN模式的客户端后: +1. 切换到"TUN模式"选项卡 +2. 点击"启用TUN模式" +3. 选择"系统代理"或"全局代理" +4. 重启客户端并允许管理员权限 + +================================================================== + +配置文件路径: ${CONFIG_PATH}/config.json +服务控制: +启动: systemctl start xray +停止: systemctl stop xray +重启: systemctl restart xray +状态: systemctl status xray +================================================================== +EOF + + # 生成 REALITY TUN 模式配置示例 + generate_reality_tun_config + + log_info "客户端信息已保存到 /root/xray_info.txt" + + # 打印信息 + cat /root/xray_info.txt + + log_info "安装完成!Xray REALITY 已成功部署" + return 0 +} + +# 新增:生成 REALITY TUN 模式配置 +generate_reality_tun_config() { + log_info "生成 REALITY TUN 模式配置示例" + + # 确保IP地址已设置 + if [[ -z "$SERVER_IP" ]]; then + get_server_ip + fi + + # 创建 TUN 配置示例目录 + local example_dir="$CONFIG_PATH/examples" + mkdir -p "$example_dir" + + # 生成客户端配置示例,用于TUN模式 + cat > "$example_dir/reality_tun_example.json" << EOF +{ + "log": { + "loglevel": "warning" + }, + "inbounds": [ + { + "tag": "socks", + "port": 10808, + "listen": "127.0.0.1", + "protocol": "socks", + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ], + "routeOnly": false + }, + "settings": { + "auth": "noauth", + "udp": true, + "allowTransparent": false + } + }, + { + "tag": "tun", + "protocol": "tun", + "settings": { + "network": "all" + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ], + "routeOnly": false + } + } + ], + "tun": { + "enable": true, + "stack": "gvisor", + "mtu": 9000, + "strict_route": true + }, + "outbounds": [ + { + "tag": "proxy", + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "${SERVER_IP}", + "port": ${REALITY_PORT}, + "users": [ + { + "id": "${UUID}", + "flow": "xtls-rprx-vision", + "encryption": "none" + } + ] + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "fingerprint": "chrome", + "serverName": "www.shopify.com", + "publicKey": "${PUBLIC_KEY}", + "shortId": "6ba85179e30d" + } + } + }, + { + "tag": "direct", + "protocol": "freedom" + }, + { + "tag": "block", + "protocol": "blackhole" + } + ], + "dns": { + "hosts": { + "dns.google": "8.8.8.8", + "proxy.example.com": "127.0.0.1" + }, + "servers": [ + { + "address": "1.1.1.1", + "domains": [ + "geosite:geolocation-!cn" + ], + "expectIPs": [ + "geoip:!cn" + ] + }, + { + "address": "223.5.5.5", + "domains": [ + "geosite:cn" + ], + "expectIPs": [ + "geoip:cn" + ] + }, + "8.8.8.8", + "https://dns.google/dns-query" + ] + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "outboundTag": "proxy", + "domain": [ + "domain:google.com", + "domain:googleapis.cn", + "domain:gstatic.com" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "domain": [ + "geosite:cn" + ] + }, + { + "type": "field", + "outboundTag": "direct", + "ip": [ + "geoip:private", + "geoip:cn" + ] + } + ] + } +} +EOF + + log_info "REALITY TUN 模式配置示例已保存到 $example_dir/reality_tun_example.json" + log_info "使用方法: 将配置导入到支持TUN模式的客户端,然后开启TUN模式" + + return 0 +} + +# 创建系统服务 +create_service() { + log_info "创建 Xray 系统服务" + + # 创建服务文件 + cat > /etc/systemd/system/xray.service << EOF +[Unit] +Description=Xray Service +Documentation=https://github.com/XTLS/Xray-core +After=network.target nss-lookup.target + +[Service] +User=root +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +NoNewPrivileges=true +ExecStart=$XRAY_PATH/xray run -config $CONFIG_PATH/config.json +Restart=on-failure +RestartPreventExitStatus=23 +LimitNPROC=10000 +LimitNOFILE=1000000 + +[Install] +WantedBy=multi-user.target +EOF + + # 重新加载 systemd 配置并启用服务 + systemctl daemon-reload + + echo -n "启用 Xray 服务... " + if systemctl enable xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "无法启用 Xray 服务" + return 1 + fi + + echo -n "启动 Xray 服务... " + if systemctl start xray; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "无法启动 Xray 服务" + return 1 + fi + + return 0 +} + +# 安装完整流程 - 更新为直接安装 VLESS+REALITY +install_xray() { + clear + echo "==================================================" + echo -e "${GREEN}开始安装 Xray REALITY 协议${NC}" + echo "==================================================" + + # 检查是否为 root + check_root + + # 检查系统环境 + check_system + + # 备份现有配置 + backup_config + + # 安装依赖 + install_dependencies || { + log_error "安装依赖失败,退出安装" + return 1 + } + + # 下载和安装 Xray + download_xray || { + log_error "下载 Xray 失败,退出安装" + return 1 + } + + # 配置 Xray + configure_xray || { + log_error "配置 Xray 失败,退出安装" + return 1 + } + + # 创建系统服务 + create_service || { + log_error "创建系统服务失败,但会继续安装过程" + } + + # 配置防火墙 + configure_firewall + + # 生成客户端信息 + generate_client_info + + echo "" + log_info "Xray REALITY 协议安装成功!" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 停止 Xray 服务 +stop_xray_service() { + log_info "停止 Xray 服务" + + if systemctl is-active xray &>/dev/null; then + echo -n "正在停止 Xray 服务... " + if systemctl stop xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Xray 服务,将尝试继续卸载" + fi + else + log_info "Xray 服务未运行" + fi + + echo -n "禁用 Xray 服务自启动... " + if systemctl disable xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${YELLOW}失败${NC}" + log_warn "无法禁用 Xray 服务自启动,服务可能不存在" + fi +} + +# 读取并关闭防火墙端口 +close_firewall_port() { + log_info "尝试关闭之前开放的防火墙端口" + + # 检查是否安装了 ufw + if ! command -v ufw &>/dev/null; then + log_warn "未检测到 UFW 防火墙,跳过防火墙配置关闭" + return 0 + fi + + # 检查ufw是否启用 + local ufw_status=$(ufw status | grep -o "Status: active" 2>/dev/null) + if [[ -z "$ufw_status" ]]; then + log_warn "UFW 防火墙未启用,跳过防火墙规则关闭" + return 0 + fi + + # 尝试从配置或备份文件中读取端口 + local ports=() + + # 从当前配置中查找端口 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + if command -v jq &>/dev/null; then + # 使用jq查找所有inbounds的端口 + local all_ports=$(jq '.inbounds[].port' "$CONFIG_PATH/config.json" 2>/dev/null) + for port in $all_ports; do + if [[ "$port" != "null" && -n "$port" ]]; then + ports+=("$port") + fi + done + else + # 使用grep查找端口 + local all_ports=$(grep -o '"port": [0-9]*' "$CONFIG_PATH/config.json" | awk '{print $2}') + for port in $all_ports; do + if [[ -n "$port" ]]; then + ports+=("$port") + fi + done + fi + fi + + # 如果找不到端口,从备份中查找 + if [[ ${#ports[@]} -eq 0 && -n "$CONFIG_BACKUP" && -f "$CONFIG_BACKUP/config.json" ]]; then + if command -v jq &>/dev/null; then + local all_ports=$(jq '.inbounds[].port' "$CONFIG_BACKUP/config.json" 2>/dev/null) + for port in $all_ports; do + if [[ "$port" != "null" && -n "$port" ]]; then + ports+=("$port") + fi + done + else + local all_ports=$(grep -o '"port": [0-9]*' "$CONFIG_BACKUP/config.json" | awk '{print $2}') + for port in $all_ports; do + if [[ -n "$port" ]]; then + ports+=("$port") + fi + done + fi + fi + + # 如果还是找不到端口,尝试使用全局变量 + if [[ ${#ports[@]} -eq 0 ]]; then + if [[ -n "$REALITY_PORT" && "$REALITY_PORT" =~ ^[0-9]+$ ]]; then + ports+=("$REALITY_PORT") + fi + fi + + # 去除重复的端口 + if [[ ${#ports[@]} -gt 0 ]]; then + local unique_ports=() + local port_list="" + for port in "${ports[@]}"; do + # 检查端口是否为有效数字 + if ! [[ "$port" =~ ^[0-9]+$ ]]; then + log_warn "端口 '$port' 不是有效数字,已跳过" + continue + fi + + # 检查端口是否已在列表中 + if [[ "$port_list" != *",$port,"* ]]; then + unique_ports+=("$port") + port_list="$port_list,$port," + fi + done + ports=("${unique_ports[@]}") + fi + + # 如果找到了端口,关闭防火墙规则 + if [[ ${#ports[@]} -gt 0 ]]; then + local port_list=$(printf ", %s" "${ports[@]}") + port_list=${port_list:2} # 移除开头的逗号和空格 + log_info "找到端口: $port_list,尝试关闭 UFW 防火墙规则" + + local closed_ports=() + for port in "${ports[@]}"; do + # 检查端口是否已开放在UFW中 + if ! ufw status | grep -q "$port/tcp"; then + log_info "端口 $port 未在 UFW 中开放,跳过" + continue + fi + + echo -n "关闭 UFW 防火墙端口 $port... " + if ufw delete allow "$port/tcp" &>/dev/null; then + echo -e "${GREEN}完成${NC}" + closed_ports+=("$port") + else + echo -e "${RED}失败${NC}" + log_warn "无法关闭端口 $port" + fi + done + + # 打印关闭的端口 + if [[ ${#closed_ports[@]} -gt 0 ]]; then + local closed_list=$(printf ", %s" "${closed_ports[@]}") + closed_list=${closed_list:2} # 移除开头的逗号和空格 + log_info "已关闭 UFW 防火墙中的端口: $closed_list" + else + log_warn "没有关闭任何端口" + fi + else + log_warn "无法确定之前使用的端口,跳过防火墙规则关闭" + fi +} + +# 删除 Xray 文件 +remove_xray_files() { + log_info "删除 Xray 文件" + + # 删除二进制文件 + echo -n "删除 Xray 核心文件... " + if rm -f "$XRAY_PATH/xray" "$XRAY_PATH/geoip.dat" "$XRAY_PATH/geosite.dat"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${YELLOW}部分文件未能删除${NC}" + log_warn "部分 Xray 核心文件可能未能完全删除" + fi + + # 删除配置目录(不删除备份) + echo -n "删除 Xray 配置目录... " + if [[ -d "$CONFIG_PATH" ]]; then + if rm -rf "$CONFIG_PATH"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法删除配置目录 $CONFIG_PATH" + fi + else + echo -e "${YELLOW}配置目录不存在${NC}" + fi + + # 删除日志目录 + echo -n "删除 Xray 日志目录... " + if [[ -d "$LOG_PATH" ]]; then + if rm -rf "$LOG_PATH"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法删除日志目录 $LOG_PATH" + fi + else + echo -e "${YELLOW}日志目录不存在${NC}" + fi + + # 删除服务文件 + echo -n "删除 Xray 服务文件... " + if rm -f /etc/systemd/system/xray.service; then + echo -e "${GREEN}成功${NC}" + systemctl daemon-reload + else + echo -e "${YELLOW}服务文件不存在或无法删除${NC}" + fi + + return 0 +} + +# 完整卸载流程 +uninstall_xray() { + clear + echo "==================================================" + echo -e "${RED}开始卸载 Xray${NC}" + echo "==================================================" + + # 确认卸载 + echo -e "${YELLOW}警告: 这将卸载 Xray 并删除相关文件${NC}" + read -rp "是否继续? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "卸载已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 检查 root 权限 + check_root + + # 备份配置 + backup_config + + # 停止服务 + stop_xray_service + + # 关闭防火墙端口 + close_firewall_port + + # 删除文件 + remove_xray_files + + echo "" + log_info "Xray 卸载完成" + + # 直接删除备份文件,不询问用户 + if [[ -n "$CONFIG_BACKUP" && -d "$CONFIG_BACKUP" ]]; then + rm -rf "$CONFIG_BACKUP" + log_info "备份文件已删除" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 检查 Xray 状态 +check_status() { + clear + echo "==================================================" + echo -e "${BLUE}Xray 状态检查${NC}" + echo "==================================================" + + # 检查是否安装 + if [[ ! -f "$XRAY_PATH/xray" ]]; then + echo -e "${RED}Xray 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 检查版本 + echo -n "Xray 版本: " + $XRAY_PATH/xray version | head -n 1 + + # 检查服务状态 + echo -n "服务状态: " + if systemctl is-active xray &>/dev/null; then + echo -e "${GREEN}运行中${NC}" + else + echo -e "${RED}未运行${NC}" + fi + + echo -n "自启动状态: " + if systemctl is-enabled xray &>/dev/null; then + echo -e "${GREEN}已启用${NC}" + else + echo -e "${RED}未启用${NC}" + fi + + # 获取内存和 CPU 使用情况 + echo "资源使用情况:" + ps -aux | grep xray | grep -v grep | awk '{print "内存使用: " $4 "%, CPU使用: " $3 "%"}' + + # 检查端口 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + local current_port="" + if command -v jq &>/dev/null; then + current_port=$(jq '.inbounds[0].port' "$CONFIG_PATH/config.json" 2>/dev/null) + else + current_port=$(grep -o '"port": [0-9]*' "$CONFIG_PATH/config.json" | head -1 | awk '{print $2}') + fi + + if [[ -n "$current_port" && "$current_port" != "null" ]]; then + echo -n "端口 $current_port 状态: " + if command -v ss &>/dev/null; then + if ss -tuln | grep -q ":$current_port "; then + echo -e "${GREEN}已开放${NC}" + else + echo -e "${RED}未开放${NC}" + fi + elif command -v netstat &>/dev/null; then + if netstat -tuln | grep -q ":$current_port "; then + echo -e "${GREEN}已开放${NC}" + else + echo -e "${RED}未开放${NC}" + fi + else + echo -e "${YELLOW}无法检查${NC}" + fi + + # 显示链接数 + echo "当前连接:" + if command -v ss &>/dev/null; then + ss -tn | grep ":$current_port" | wc -l | awk '{print "活跃连接数: " $1}' + elif command -v netstat &>/dev/null; then + netstat -tn | grep ":$current_port" | wc -l | awk '{print "活跃连接数: " $1}' + else + echo "无法获取连接信息" + fi + fi + fi + + echo -e "\n最近的日志:" + if [[ -f "$LOG_PATH/error.log" ]]; then + tail -n 10 "$LOG_PATH/error.log" + else + echo "找不到错误日志文件" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 显示配置信息 +show_config() { + clear + echo "==================================================" + echo -e "${CYAN}Xray 配置信息${NC}" + echo "==================================================" + + # 检查是否已安装 + if [[ ! -f "$XRAY_PATH/xray" ]]; then + echo -e "${RED}Xray 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 显示配置文件内容 + if [[ -f "$CONFIG_PATH/config.json" ]]; then + if command -v jq &>/dev/null; then + echo "配置信息 (美化格式):" + jq . "$CONFIG_PATH/config.json" + else + echo "配置文件内容:" + cat "$CONFIG_PATH/config.json" + fi + else + echo -e "${RED}找不到配置文件${NC}" + fi + + # 显示客户端信息 + if [[ -f "/root/xray_info.txt" ]]; then + echo -e "\n客户端信息:" + cat /root/xray_info.txt + else + echo -e "\n${RED}找不到客户端信息文件${NC}" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 重启 Xray 服务 +restart_service() { + clear + echo "==================================================" + echo -e "${GREEN}重启 Xray 服务${NC}" + echo "==================================================" + + # 检查是否已安装 + if [[ ! -f "$XRAY_PATH/xray" ]]; then + echo -e "${RED}Xray 未安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + echo -n "重启 Xray 服务... " + if systemctl restart xray; then + echo -e "${GREEN}成功${NC}" + log_info "Xray 服务已重启" + else + echo -e "${RED}失败${NC}" + log_error "无法重启 Xray 服务" + fi + + # 检查服务状态 + echo -n "Xray 服务状态: " + if systemctl is-active xray &>/dev/null; then + echo -e "${GREEN}运行中${NC}" + else + echo -e "${RED}未运行${NC}" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 更新 Xray +update_xray() { + clear + echo "==================================================" + echo -e "${YELLOW}更新 Xray${NC}" + echo "==================================================" + + # 检查是否已安装 + if [[ ! -f "$XRAY_PATH/xray" ]]; then + echo -e "${RED}Xray 未安装,请先安装${NC}" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + + # 获取当前版本 + local current_version + current_version=$($XRAY_PATH/xray version | head -n 1 | cut -d ' ' -f 2) + echo "当前版本: $current_version" + + # 获取最新版本 + echo -n "获取最新版本... " + local latest_version=$(get_latest_version) + echo -e "${GREEN}$latest_version${NC}" + + # 比较版本 + if [[ "$current_version" == "$latest_version" ]]; then + echo -e "${GREEN}已经是最新版本${NC}" + read -rp "是否强制更新? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "更新已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + fi + + # 备份配置 + backup_config + + # 停止服务 + echo -n "停止 Xray 服务... " + if systemctl stop xray &>/dev/null; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_warn "无法停止 Xray 服务,将尝试继续更新" + fi + + # 下载新版本 + log_info "开始下载新版本" + + # 创建临时目录 + local tmp_dir="/tmp/xray_update" + mkdir -p "$tmp_dir" + + # 确定系统架构 + local arch + case $(uname -m) in + x86_64|amd64) arch="64" ;; + armv7l|armv8l) arch="arm32-v7a" ;; + aarch64) arch="arm64-v8a" ;; + *) arch="64" ;; # 默认使用64位版本 + esac + + # 构建下载URL + local download_url="https://github.com/XTLS/Xray-core/releases/download/$latest_version/Xray-linux-$arch.zip" + log_info "下载链接: $download_url" + + # 下载Xray + echo -n "下载 Xray... " + if wget -q --show-progress -O "$tmp_dir/xray.zip" "$download_url"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "下载 Xray 失败" + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 解压文件 + echo -n "解压 Xray... " + if unzip -q -o "$tmp_dir/xray.zip" -d "$tmp_dir"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "解压 Xray 失败" + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 备份旧的二进制文件 + mv "$XRAY_PATH/xray" "$XRAY_PATH/xray.old" 2>/dev/null + + # 复制新文件 + echo -n "更新 Xray 核心文件... " + if cp "$tmp_dir/xray" "$XRAY_PATH/xray" && chmod +x "$XRAY_PATH/xray"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "更新 Xray 核心文件失败" + + # 恢复旧文件 + if [[ -f "$XRAY_PATH/xray.old" ]]; then + mv "$XRAY_PATH/xray.old" "$XRAY_PATH/xray" + log_warn "已恢复为旧版本" + fi + + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 更新 geoip.dat 和 geosite.dat + echo -n "更新 GeoIP 和 GeoSite 数据... " + if cp "$tmp_dir/geoip.dat" "$XRAY_PATH/geoip.dat" && \ + cp "$tmp_dir/geosite.dat" "$XRAY_PATH/geosite.dat"; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${YELLOW}失败${NC}" + log_warn "更新 GeoIP 和 GeoSite 数据失败,但不影响核心功能" + fi + + # 删除旧备份和临时文件 + rm -f "$XRAY_PATH/xray.old" + rm -rf "$tmp_dir" + + # 启动服务 + echo -n "启动 Xray 服务... " + if systemctl start xray; then + echo -e "${GREEN}成功${NC}" + else + echo -e "${RED}失败${NC}" + log_error "启动 Xray 服务失败,请检查配置" + systemctl status xray + read -rp "按回车键返回主菜单..." temp + show_menu + return 1 + fi + + # 检查更新后的版本 + local new_version + new_version=$($XRAY_PATH/xray version | head -n 1 | cut -d ' ' -f 2) + + log_info "Xray 更新完成,版本: $new_version" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 更新脚本版本信息 +update_script_version() { + clear + echo "==================================================" + echo -e "${GREEN}更新脚本版本信息${NC}" + echo "==================================================" + + # 显示当前脚本版本 + echo "当前脚本版本: $SCRIPT_VERSION" + + # 获取最新脚本版本 + echo -n "获取最新脚本版本... " + local latest_version + latest_version=$(curl -s https://raw.githubusercontent.com/XTLS/Xray-core/main/xray-manager.sh | grep -o 'SCRIPT_VERSION="[^"]*"' | cut -d'"' -f2) + + if [[ -z "$latest_version" ]]; then + echo -e "${RED}失败${NC}" + log_error "无法获取最新脚本版本信息" + read -rp "是否继续更新? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "更新已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + latest_version="1.0.0" # 设置一个固定版本作为备用 + log_info "将使用固定版本: $latest_version" + else + echo -e "${GREEN}$latest_version${NC}" + + # 比较版本 + if [[ "$SCRIPT_VERSION" == "$latest_version" ]]; then + echo -e "${GREEN}已经是最新版本${NC}" + read -rp "是否强制更新? [y/N] " confirm + if [[ ! "$confirm" =~ ^[yY]$ ]]; then + log_info "更新已取消" + read -rp "按回车键返回主菜单..." temp + show_menu + return 0 + fi + fi + fi + + # 更新脚本版本 + echo -n "更新脚本版本... " + sed -i "s/SCRIPT_VERSION=\"[^\"]*\"/SCRIPT_VERSION=\"$latest_version\"/" "$0" + echo -e "${GREEN}成功${NC}" + + log_info "脚本版本已更新为 $latest_version" + + read -rp "按回车键返回主菜单..." temp + show_menu +} + +# 主函数 +main() { + # 处理命令行参数 + if [[ $# -gt 0 ]]; then + case "$1" in + -h|--help) + show_help + exit 0 + ;; + -i|--install) + check_root + install_xray + exit 0 + ;; + -u|--uninstall) + check_root + uninstall_xray + exit 0 + ;; + -s|--status) + check_root + check_status + exit 0 + ;; + -up|--update) + check_root + update_xray + exit 0 + ;; + *) + log_error "未知参数: $1" + show_help + exit 1 + ;; + esac + fi + + # 无参数则显示菜单 + check_root + show_menu +} + +# 执行主函数 +main "$@" \ No newline at end of file