Appearance
2. Centos 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
if [[ "${ID:-}" != "almalinux" || "${VERSION_ID%%.*}" != "9" ]]; then
err "本脚本只支持 AlmaLinux 9.x"
exit 1
fi
}
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
dnf install -y 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
dnf 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 "$@"