#!/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}" <