diff --git a/xray/setup-xray-vless-ss.sh b/xray/setup-xray-vless-ss.sh new file mode 100755 index 0000000..a786099 --- /dev/null +++ b/xray/setup-xray-vless-ss.sh @@ -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}" </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}" <