feat(xray): add vless+ss setup script

Signed-off-by: Ivan Li <ivanli2048@gmail.com>
This commit is contained in:
2025-12-12 03:09:12 +08:00
parent 2f7ce16834
commit 8d3af89592

432
xray/setup-xray-vless-ss.sh Executable file
View File

@@ -0,0 +1,432 @@
#!/bin/sh
set -eu
CONFIG_DIR="/usr/local/etc/xray"
CONFIG_FILE_NAME="vless-ss-reality.yaml"
SERVICE_NAME_DEFAULT="xray-vless-ss"
prompt() {
_prompt_name="$1"
_prompt_text="$2"
_prompt_default="${3-}"
_prompt_value=
if [ -n "${_prompt_default}" ]; then
printf '%s [%s]: ' "${_prompt_text}" "${_prompt_default}"
IFS= read -r _prompt_value || _prompt_value=""
[ -z "${_prompt_value}" ] && _prompt_value="${_prompt_default}"
else
while :; do
printf '%s: ' "${_prompt_text}"
IFS= read -r _prompt_value || _prompt_value=""
[ -n "${_prompt_value}" ] && break
echo "Value is required, please try again."
done
fi
eval "${_prompt_name}=\${_prompt_value}"
}
detect_pkg_manager() {
if command -v apt-get >/dev/null 2>&1; then
echo "apt"
elif command -v dnf >/dev/null 2>&1; then
echo "dnf"
elif command -v yum >/dev/null 2>&1; then
echo "yum"
elif command -v pacman >/dev/null 2>&1; then
echo "pacman"
elif command -v apk >/dev/null 2>&1; then
echo "apk"
elif command -v zypper >/dev/null 2>&1; then
echo "zypper"
else
echo "unknown"
fi
}
install_xray() {
if command -v xray >/dev/null 2>&1; then
echo "xray already installed at $(command -v xray)"
return 0
fi
pm="$(detect_pkg_manager)"
echo "Detected package manager: ${pm}"
case "${pm}" in
pacman)
echo "Installing xray on Arch Linux..."
# First, prefer any xray package already provided by configured repositories (e.g. Arch Linux CN).
if pacman -Si xray >/dev/null 2>&1; then
pacman -S --noconfirm xray
else
# Otherwise, install paru-bin from AUR and use it to get xray-bin/xray.
install_paru
if paru -S --noconfirm xray-bin; then
:
else
paru -S --noconfirm xray
fi
fi
;;
apk)
echo "Installing xray on Alpine Linux via official installer..."
apk update
apk add --no-cache bash curl wget ca-certificates
# Alpine-specific installer (OpenRC-friendly)
if wget -qO- https://github.com/XTLS/alpinelinux-install-xray/raw/main/install-release.sh | bash -s -- install; then
:
else
echo "alpinelinux-install-xray failed; trying generic Xray-install script..."
curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh | bash -s -- install
fi
;;
*)
echo "Unsupported package manager '${pm}'."
echo "This helper currently supports only Arch Linux (pacman) and Alpine (apk)."
exit 1
;;
esac
if ! command -v xray >/dev/null 2>&1; then
echo "xray installation seems to have failed (binary not found in PATH)."
exit 1
fi
}
generate_uuid() {
xray uuid
}
generate_reality_keys() {
# prints: Private key: ... / Public key: ...
xray x25519
}
derive_public_key_from_private() {
_key_priv="$1"
xray x25519 -i "${_key_priv}" | awk '/Public key:/ {print $3}'
}
generate_ss2022_password() {
# Xray does not provide a dedicated ss2022 keygen, use strong random.
if command -v openssl >/dev/null 2>&1; then
openssl rand -base64 16
else
# fallback: 16 random bytes hex-encoded
head -c 16 /dev/urandom | xxd -p
fi
}
ensure_root() {
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root (for installing packages and configuring systemd)."
exit 1
fi
}
create_config_from_template() {
config_dir="$1"
mkdir -p "${config_dir}"
mkdir -p /var/log/xray
echo "=== Basic parameters ==="
domain=
port_vless=
port_ss=
prompt domain "Enter REALITY domain (SNI / dest host)" ""
prompt port_vless "Enter VLESS listen port" "443"
prompt port_ss "Enter Shadowsocks 2022 listen port" "8443"
echo
echo "=== Generating secrets using xray and system RNG ==="
echo "Generating UUID (VLESS client id)..."
uuid="$(generate_uuid)"
echo "Generating Reality x25519 keypair..."
# capture both private and public key output
reality_output="$(generate_reality_keys)"
private_key="$(printf '%s\n' "${reality_output}" | awk '/Private key:/ {print $3}')"
public_key="$(printf '%s\n' "${reality_output}" | awk '/Public key:/ {print $3}')"
if [ -z "${private_key}" ] || [ -z "${public_key}" ]; then
echo "Failed to parse x25519 keys from xray output."
exit 1
fi
echo "Generating Shadowsocks 2022 password..."
ss_password="$(generate_ss2022_password)"
config_path="${config_dir}/${CONFIG_FILE_NAME}"
cat <<'EOF' | sed \
-e "s|\$PORT_VLESS\$|${port_vless}|g" \
-e "s|\$PORT_SS\$|${port_ss}|g" \
-e "s|\$DOMAIN\$|${domain}|g" \
-e "s|\$ID\$|${uuid}|g" \
-e "s|\$PRIVATE_KEY\$|${private_key}|g" \
-e "s|\$PASSWORD\$|${ss_password}|g" \
> "${config_path}"
log:
loglevel: info
# access: /var/log/xray/access.log
error: /var/log/xray/error.log
inbounds:
- tag: vless-vision
listen: 0.0.0.0
port: $PORT_VLESS$
protocol: vless
settings:
clients:
- id: $ID$
flow: xtls-rprx-vision
decryption: none
streamSettings:
network: tcp
security: reality
realitySettings:
show: false
dest: $DOMAIN$:443
serverNames:
- $DOMAIN$
privateKey: $PRIVATE_KEY$
shortIds:
- ""
sniffing:
enabled: true
destOverride:
- http
- tls
- quic
- tag: ss2022-aes128
listen: 0.0.0.0
port: $PORT_SS$
protocol: shadowsocks
settings:
method: 2022-blake3-aes-128-gcm
password: $PASSWORD$
network: tcp,udp
outbounds:
- tag: direct
protocol: freedom
settings: {}
- tag: block
protocol: blackhole
settings: {}
routing:
rules:
- type: field
inboundTag:
- vless-vision
- ss2022-aes128
outboundTag: direct
EOF
echo "Config written to: ${config_path}"
XRAY_CONFIG_PATH="${config_path}"
XRAY_DOMAIN="${domain}"
XRAY_PORT_VLESS="${port_vless}"
XRAY_PORT_SS="${port_ss}"
XRAY_UUID="${uuid}"
XRAY_PRIVATE_KEY="${private_key}"
XRAY_PUBLIC_KEY="${public_key}"
XRAY_SS_PASSWORD="${ss_password}"
}
validate_config() {
config_path="$1"
echo "Validating config with xray..."
if xray run -test -c "${config_path}" -format yaml; then
echo "Config validation succeeded."
else
echo "Config validation failed."
exit 1
fi
}
install_systemd_service() {
service_name="$1"
config_path="$2"
service_file="/etc/systemd/system/${service_name}.service"
cat > "${service_file}" <<EOF
[Unit]
Description=Xray VLESS+Reality & SS2022 service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=$(command -v xray) run -c ${config_path} -format yaml
Restart=on-failure
User=root
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
LimitNPROC=512
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
EOF
echo "systemd service written to: ${service_file}"
systemctl daemon-reload
systemctl enable --now "${service_name}.service"
systemctl --no-pager --full status "${service_name}.service" || true
}
detect_init_system() {
pid1="$(ps -p 1 -o comm= 2>/dev/null || echo "")"
if command -v systemctl >/dev/null 2>&1 && [ "${pid1}" = "systemd" ]; then
echo "systemd"
elif command -v openrc-run >/dev/null 2>&1 || [ -x /sbin/openrc-run ]; then
echo "openrc"
else
echo "unknown"
fi
}
install_paru() {
if command -v paru >/dev/null 2>&1; then
echo "paru already installed at $(command -v paru)"
return 0
fi
echo "Installing paru-bin from AUR (requires network)..."
pacman -Sy --needed --noconfirm base-devel git
tmpdir="$(mktemp -d)"
trap 'rm -rf "${tmpdir}"' EXIT
(
cd "${tmpdir}"
git clone https://aur.archlinux.org/paru-bin.git
cd paru-bin
makepkg -si --noconfirm
)
if ! command -v paru >/dev/null 2>&1; then
echo "paru installation failed (binary not found in PATH)."
exit 1
fi
}
install_openrc_service() {
service_name="$1"
config_path="$2"
if ! command -v rc-update >/dev/null 2>&1 || ! command -v rc-service >/dev/null 2>&1; then
echo "OpenRC tools (rc-update / rc-service) not found; cannot install service automatically."
return 1
fi
service_file="/etc/init.d/${service_name}"
cat > "${service_file}" <<EOF
#!/sbin/openrc-run
name="${service_name}"
description="Xray VLESS+Reality & SS2022 service"
command="$(command -v xray)"
command_args="run -c ${config_path} -format yaml"
command_background="yes"
pidfile="/run/${service_name}.pid"
rc_ulimit="-n 1048576"
depend() {
need net
after net
}
EOF
chmod +x "${service_file}"
rc-update add "${service_name}" default || true
rc-service "${service_name}" restart || rc-service "${service_name}" start
rc-service "${service_name}" status || true
}
setup_service() {
service_name="$1"
config_path="$2"
init="$(detect_init_system)"
case "${init}" in
systemd)
install_systemd_service "${service_name}" "${config_path}"
;;
openrc)
install_openrc_service "${service_name}" "${config_path}"
;;
*)
echo "Could not detect supported init system (systemd or OpenRC)."
echo "You can start Xray manually, for example:"
echo " $(command -v xray) run -c ${config_path} --format yaml"
return 1
;;
esac
}
print_mihomo_snippet() {
name="$1"
domain="$2"
port_vless="$3"
uuid="$4"
public_key="$5"
cat <<EOF
---
# mihomo / Clash.Meta compatible node snippet
proxies:
- name: ${name}
type: vless
server: ${domain}
port: ${port_vless}
uuid: ${uuid}
network: tcp
udp: true
tls: true
flow: xtls-rprx-vision
servername: ${domain}
reality-opts:
public-key: ${public_key}
short-id: ""
client-fingerprint: chrome
EOF
}
main() {
ensure_root
echo "=== Xray VLESS+Reality & SS2022 setup helper ==="
echo
echo "Config will be written to: ${CONFIG_DIR}/${CONFIG_FILE_NAME}"
prompt SERVICE_NAME "Service name (systemd/OpenRC)" "${SERVICE_NAME_DEFAULT}"
install_xray
create_config_from_template "${CONFIG_DIR}"
validate_config "${XRAY_CONFIG_PATH}"
setup_service "${SERVICE_NAME}" "${XRAY_CONFIG_PATH}"
echo
echo "Service '${SERVICE_NAME}' has been configured and started (if supported)."
echo
echo "=== Mihomo / Clash.Meta node snippet ==="
print_mihomo_snippet "xray-vless-reality" "${XRAY_DOMAIN}" "${XRAY_PORT_VLESS}" "${XRAY_UUID}" "${XRAY_PUBLIC_KEY}"
}
main "$@"