Skip to content

1. Debian vless ​

vless.sh ​

bash
#!/usr/bin/env bash
set -Eeuo pipefail

# ==================================================
# Xray VLESS + REALITY + Vision
# AlmaLinux 9.x
# Official Xray installer
# ==================================================

XRAY="/usr/local/bin/xray"
CONFIG="/usr/local/etc/xray/config.json"
INFO="/root/.xray-reality-info"
INSTALLER="https://github.com/XTLS/Xray-install/raw/main/install-release.sh"
DEFAULT_TARGET="www.microsoft.com"

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m'

msg()  { echo -e "${GREEN}[+]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
err()  { echo -e "${RED}[-]${NC} $*" >&2; }

pause() {
    echo
    read -rp "按回车返回菜单..." _
}

check_root() {
    if [[ $EUID -ne 0 ]]; then
        err "请使用 root 用户运行"
        exit 1
    fi
}

check_os() {
    source /etc/os-release

}

installed() {
    [[ -x "$XRAY" && -f "$CONFIG" ]]
}

check_target() {
    [[ "$1" =~ ^[a-zA-Z0-9.-]+$ ]] &&
    [[ "$1" != .* ]] &&
    [[ "$1" != *. ]]
}

get_ip() {
    curl -4 -fsS --max-time 8 https://api.ipify.org 2>/dev/null || true
}

# 兼容不同版本的 xray x25519 输出格式
extract_key() {
    local output="$1"
    local kind="$2"

    printf '%s\n' "$output" | awk -v kind="$kind" '
    {
        line=tolower($0)

        if (kind=="private") {
            match_key=(line ~ /private[ _-]*key/)
        } else {
            match_key=(line ~ /password/ ||
                       line ~ /public[ _-]*key/)
        }

        if (match_key && index($0, ":")) {
            sub(/^[^:]*:[[:space:]]*/, "")
            gsub(/\r/, "")
            gsub(/^[[:space:]]+|[[:space:]]+$/, "")
            if (length($0)>0) {
                print
                exit
            }
        }
    }'
}

generate_keys() {
    msg "生成 REALITY 密钥"

    local output public_output
    output=$("$XRAY" x25519 2>&1) || {
        err "Xray x25519 命令执行失败"
        return 1
    }

    PRIVATE_KEY=$(extract_key "$output" private)

    if [[ -z "$PRIVATE_KEY" ]]; then
        err "无法解析 REALITY 私钥"
        echo "$output" | sed -E \
          's/(PrivateKey|Private key):.*/\1: [已隐藏]/I'
        return 1
    fi

    # 使用私钥单独推导公钥
    public_output=$("$XRAY" x25519 -i "$PRIVATE_KEY" 2>&1) || {
        err "公钥推导失败"
        return 1
    }

    PUBLIC_KEY=$(extract_key "$public_output" public)

    # 兼容部分旧版本
    if [[ -z "$PUBLIC_KEY" ]]; then
        PUBLIC_KEY=$(extract_key "$output" public)
    fi

    if [[ -z "$PUBLIC_KEY" ]]; then
        err "无法解析 REALITY 公钥"
        echo "$public_output" | sed -E \
          's/(Password|PublicKey|Public key):.*/\1: [已隐藏]/I'
        return 1
    fi

    SHORT_ID=$(openssl rand -hex 8)
    UUID=$("$XRAY" uuid)

    if [[ -z "$UUID" || -z "$SHORT_ID" ]]; then
        err "UUID 或 Short ID 生成失败"
        return 1
    fi

    msg "REALITY 公私钥生成成功"
}

save_info() {
    umask 077

    cat > "$INFO" <<EOF
UUID='$UUID'
PUBLIC_KEY='$PUBLIC_KEY'
SHORT_ID='$SHORT_ID'
TARGET='$TARGET'
SERVER_IP='$SERVER_IP'
EOF

    chmod 600 "$INFO"
}

load_info() {
    if [[ ! -f "$INFO" ]]; then
        err "没有找到节点信息,请先安装"
        return 1
    fi

    # 该文件由本脚本 root 用户生成,权限为 600
    source "$INFO"
}

service_group() {
    if getent group xray >/dev/null; then
        echo xray
    else
        err "未找到 xray 用户组"
        return 1
    fi
}

make_config() {
    local group
    group=$(service_group) || return 1

    mkdir -p /usr/local/etc/xray

    cat > "$CONFIG" <<EOF
{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "listen": "0.0.0.0",
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "$UUID",
            "flow": "xtls-rprx-vision"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "raw",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": "$TARGET:443",
          "serverNames": [
            "$TARGET"
          ],
          "privateKey": "$PRIVATE_KEY",
          "shortIds": [
            "$SHORT_ID"
          ]
        }
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "direct"
    },
    {
      "protocol": "blackhole",
      "tag": "block"
    }
  ]
}
EOF

    chown root:"$group" /usr/local/etc/xray
    chmod 750 /usr/local/etc/xray

    chown root:"$group" "$CONFIG"
    chmod 640 "$CONFIG"

    msg "检查 Xray 配置"

    "$XRAY" run -test -config "$CONFIG"
}


port_check() {
    local listeners

    # -H 表示不输出表头
    listeners=$(ss -H -lntp 'sport = :443' 2>/dev/null || true)

    if [[ -z "$listeners" ]]; then
        msg "TCP 443 未被占用,可以继续安装"
        return 0
    fi

    # 如果是 Xray 自己占用,则先停止旧服务
    if grep -q 'xray' <<< "$listeners"; then
        warn "检测到已有 Xray,先停止旧服务"
        systemctl stop xray || true

        listeners=$(ss -H -lntp 'sport = :443' 2>/dev/null || true)
    fi

    if [[ -z "$listeners" ]]; then
        msg "TCP 443 已释放,可以继续安装"
        return 0
    fi

    err "TCP 443 被其他程序占用:"
    echo "$listeners"
    return 1
}

install_xray() {
    echo
    echo "========== 安装 Xray =========="

    if installed; then
        warn "检测到已有 Xray 配置"
        read -rp "重新生成节点并覆盖原配置?(y/N): " answer
        [[ "$answer" =~ ^[Yy]$ ]] || return
    fi

    apt install -y qrencode curl unzip openssl firewalld python3

    port_check || return 1

    msg "安装或更新官方 Xray-core"

    bash -c "$(curl -fsSL "$INSTALLER")" @ install

    [[ -x "$XRAY" ]] || {
        err "Xray 安装失败"
        return 1
    }

    generate_keys || return 1

    echo
    echo "REALITY 目标域名"
    echo "默认值:$DEFAULT_TARGET"
    read -rp "请输入域名,直接回车使用默认值: " input_target

    TARGET="${input_target:-$DEFAULT_TARGET}"

    if ! check_target "$TARGET"; then
        err "域名格式不正确"
        return 1
    fi

    msg "检查目标 HTTPS 连通性"

    if ! curl -sSI --connect-timeout 5 \
        --max-time 10 "https://$TARGET/" >/dev/null; then
        warn "目标 HTTPS 测试失败,请检查域名或网络"
        read -rp "仍然继续安装?(y/N): " confirm
        [[ "$confirm" =~ ^[Yy]$ ]] || return 1
    fi

    SERVER_IP=$(get_ip)

    if [[ -z "$SERVER_IP" ]]; then
        read -rp "请输入 VPS 公网 IPv4: " SERVER_IP
    fi

    if [[ ! "$SERVER_IP" =~ ^[0-9.]+$ ]]; then
        err "公网 IPv4 格式不正确"
        return 1
    fi

    msg "生成服务配置"

    make_config || return 1

    msg "保存客户端信息"
    save_info

    msg "配置防火墙"

    systemctl enable --now firewalld
    firewall-cmd --permanent --add-port=443/tcp
    firewall-cmd --reload

    msg "启动 Xray 服务"

    systemctl daemon-reload
    systemctl enable xray
    systemctl restart xray

    if ! systemctl is-active --quiet xray; then
        err "Xray 启动失败"
        journalctl -u xray -n 50 --no-pager
        return 1
    fi

    msg "Xray 安装成功"

    show_info
}

make_link() {
    load_info || return 1

    local ip="${SERVER_IP:-}"
    [[ -n "$ip" ]] || ip=$(get_ip)

    if [[ -z "$ip" ]]; then
        read -rp "请输入公网 IPv4: " ip
    fi

    printf 'vless://%s@%s:443?encryption=none&security=reality&sni=%s&fp=chrome&pbk=%s&sid=%s&type=tcp&flow=xtls-rprx-vision#AlmaLinux-Reality\n' \
        "$UUID" "$ip" "$TARGET" "$PUBLIC_KEY" "$SHORT_ID"
}

show_info() {
    load_info || return 1

    local ip="${SERVER_IP:-}"
    [[ -n "$ip" ]] || ip=$(get_ip)

    echo
    echo "======================================"
    echo "          VLESS 节点信息"
    echo "======================================"
    echo "服务器 IP:   $ip"
    echo "端口:        443"
    echo "协议:        VLESS"
    echo "传输:        TCP / RAW"
    echo "安全:        REALITY"
    echo "Flow:        xtls-rprx-vision"
    echo "SNI:         $TARGET"
    echo "UUID:        $UUID"
    echo "Public Key:  $PUBLIC_KEY"
    echo "Short ID:    $SHORT_ID"
    echo "Fingerprint: chrome"
    echo
    echo "客户端分享链接:"
    make_link
    echo
    echo "======================================"
}

show_qr() {
    load_info || return 1

    if ! command -v qrencode >/dev/null 2>&1; then
        apt install -y qrencode || {
            warn "qrencode 安装失败"
            return 1
        }
    fi

    echo
    make_link | qrencode -t ANSIUTF8
}

restart_xray() {
    systemctl restart xray

    if systemctl is-active --quiet xray; then
        msg "Xray 重启成功"
    else
        err "重启失败"
        journalctl -u xray -n 40 --no-pager
    fi
}

stop_xray() {
    systemctl stop xray
    msg "Xray 已停止"
}

show_status() {
    systemctl status xray --no-pager -l || true

    echo
    echo "443 监听状态:"
    ss -lntp 'sport = :443' || true
}

show_logs() {
    journalctl -u xray -n 80 --no-pager
}

follow_logs() {
    journalctl -u xray -f
}

change_target() {
    load_info || return 1

    echo "当前目标:$TARGET"
    read -rp "请输入新目标域名: " new_target

    if ! check_target "$new_target"; then
        err "域名格式不正确"
        return 1
    fi

    if ! curl -sSI --connect-timeout 5 \
        --max-time 10 "https://$new_target/" >/dev/null; then
        warn "目标 HTTPS 测试失败"
        read -rp "仍然继续?(y/N): " answer
        [[ "$answer" =~ ^[Yy]$ ]] || return 1
    fi

    local backup
    backup="${CONFIG}.bak.$(date +%Y%m%d%H%M%S)"
    cp -a "$CONFIG" "$backup"

    python3 - "$CONFIG" "$new_target" <<'PY'
import json
import sys

path, target = sys.argv[1], sys.argv[2]

with open(path, encoding="utf-8") as f:
    config = json.load(f)

settings = config["inbounds"][0]["streamSettings"]["realitySettings"]
settings["dest"] = target + ":443"
settings["serverNames"] = [target]

with open(path, "w", encoding="utf-8") as f:
    json.dump(config, f, indent=2)
    f.write("\n")
PY

    if "$XRAY" run -test -config "$CONFIG"; then
        TARGET="$new_target"
        save_info
        systemctl restart xray

        if systemctl is-active --quiet xray; then
            msg "目标修改成功"
        else
            err "服务启动失败,恢复旧配置"
            cp -a "$backup" "$CONFIG"
            systemctl restart xray || true
            TARGET=$(python3 -c \
              'import json,sys; print(json.load(open(sys.argv[1]))["inbounds"][0]["streamSettings"]["realitySettings"]["serverNames"][0])' \
              "$CONFIG")
            save_info
        fi
    else
        err "配置检查失败,恢复旧配置"
        cp -a "$backup" "$CONFIG"
        systemctl restart xray || true
    fi
}

update_xray() {
    msg "更新官方 Xray-core"

    bash -c "$(curl -fsSL "$INSTALLER")" @ install

    chown root:"$(service_group)" "$CONFIG"
    chmod 640 "$CONFIG"

    if "$XRAY" run -test -config "$CONFIG"; then
        systemctl restart xray
        systemctl status xray --no-pager -l
    else
        err "更新后配置检查失败,请检查配置"
    fi
}

uninstall_xray() {
    echo
    warn "准备卸载 Xray"
    echo "1. 卸载程序,保留配置"
    echo "2. 完全卸载并删除配置"
    echo "0. 返回"

    read -rp "请选择: " choice

    case "$choice" in
        1)
            bash -c "$(curl -fsSL "$INSTALLER")" @ remove
            ;;
        2)
            read -rp "确认删除所有 Xray 配置?输入 DELETE: " confirm
            [[ "$confirm" == "DELETE" ]] || return

            bash -c "$(curl -fsSL "$INSTALLER")" @ remove --purge
            rm -f "$INFO"

            read -rp "是否删除防火墙 443 规则?(y/N): " fw
            if [[ "$fw" =~ ^[Yy]$ ]]; then
                firewall-cmd --permanent --remove-port=443/tcp
                firewall-cmd --reload
            fi
            ;;
        *)
            return
            ;;
    esac

    msg "卸载操作完成"
}

menu() {
    clear

    echo -e "${CYAN}"
    echo "=========================================="
    echo "       Xray REALITY 管理面板"
    echo "       AlmaLinux 9.8"
    echo "=========================================="
    echo -e "${NC}"

    if installed; then
        echo -e "安装状态:${GREEN}已安装${NC}"
    else
        echo -e "安装状态:${YELLOW}未安装${NC}"
    fi

    echo
    echo "  1. 安装 VLESS + REALITY + Vision"
    echo "  2. 查看节点连接信息"
    echo "  3. 显示客户端二维码"
    echo "  4. 重启 Xray"
    echo "  5. 停止 Xray"
    echo "  6. 查看运行状态"
    echo "  7. 查看最近日志"
    echo "  8. 实时查看日志"
    echo "  9. 修改 REALITY 目标"
    echo " 10. 更新 Xray"
    echo " 11. 卸载 Xray"
    echo
    echo "  0. 退出"
    echo
}

main() {
    check_root
    check_os

    while true; do
        menu

        read -rp "请选择操作: " choice

        case "$choice" in
            1) install_xray; pause ;;
            2) show_info; pause ;;
            3) show_qr; pause ;;
            4) restart_xray; pause ;;
            5) stop_xray; pause ;;
            6) show_status; pause ;;
            7) show_logs; pause ;;
            8) follow_logs ;;
            9) change_target; pause ;;
            10) update_xray; pause ;;
            11) uninstall_xray; pause ;;
            0) exit 0 ;;
            *) warn "无效选项"; pause ;;
        esac
    done
}

main "$@"

基于 Vitepress.