#!/bin/bash # Script to setup VLESS + WebSocket + TLS (by Caddy) + CDN on a VPS, requires # a domain name. Verified versions: Ubuntu 24.04, Debian 13. # # Prerequisites: # - Essential pacakges are installed via script ./init # - Cloudflare: Proxy status is set to "Proxied" for the domain # # Example usage: curl -L joyus.org/vps/xray | bash -s xx.example.com # Output: a QR code for Shadowrocket to scan and add a server quickly set -o errexit set -o nounset OWNER=${SUDO_USER:-$USER} [[ $OWNER != root ]] || { echo "Please run as a normal user." >&2; exit 1; } DOMAIN="${1?:'Usage: $0 '}" PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin IMAGE="ghcr.io/xtls/xray-core:25.10.15" DIR="/opt/xray" PORT="60066" ROUTE="vless-$PORT" CONFIG="$DIR/config.json" CONTAINER="xray" # NOTE: UID 65532 is hard coded in gcr.io/distroless/static:nonroot GCR_UID=65532 GCR_GID=65532 function main { install_pkgs setup_caddy setup_config setup_start start print_qr } function info { echo -e "\e[32m$*\e[0m" } function install_pkgs { local pkgs=() [[ -x /usr/bin/caddy ]] || pkgs+=(caddy) [[ -x /usr/bin/curl ]] || pkgs+=(curl) [[ -x /usr/bin/docker ]] || pkgs+=(docker.io) [[ -x /usr/bin/qrencode ]] || pkgs+=(qrencode) [[ -z "${pkgs[*]}" ]] || { sudo apt update -y sudo apt install -y "${pkgs[@]}" } } function setup_caddy { local config="/etc/caddy/Caddyfile" local escaped="${DOMAIN//./\\.}" local tmpf=/tmp/Caddyfile.$$ trap "rm -f $tmpf" EXIT sudo mkdir -p $DIR/html sudo chown -R $OWNER:$GCR_GID $DIR/html info "Updating $config for domain $DOMAIN" sed -r "/^[[:blank:]]*${escaped} \{$/,/^[[:blank:]]*\{$/d" $config > $tmpf cat << EOT | tee -a $tmpf > /dev/null $DOMAIN { route /$ROUTE { reverse_proxy 127.0.0.1:$PORT { header_up Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} } } route /* { file_server { root $DIR/html pass_thru # ignores 404 error } respond "Hoi Zäme!" 200 } } EOT caddy fmt --overwrite $tmpf ! diff -u $config $tmpf || return 0 sudo cp $tmpf $config sudo systemctl reload caddy } function _get_uuid { # Read current uuid local uuid=$(grep -Eo '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' $CONFIG 2>/dev/null) if [[ -z $uuid ]]; then uuid=$(cat /proc/sys/kernel/random/uuid) fi echo "$uuid" } function setup_config { local uuid sudo mkdir -p $DIR uuid=$(_get_uuid) info "Writing $CONFIG with uuid '$uuid'" # NOTE: "security" is none, TLS will be handled by caddy cat << EOT | sed -r 's/^ {4}//g' | sudo tee $CONFIG { "log": { "access": "/dev/null", "error": "$DIR/error.log", "loglevel": "warning" }, "inbounds": [ { "port": $PORT, "protocol": "vless", "settings": { "clients": [ { "id": "$uuid" } ], "decryption": "none" }, "streamSettings": { "network": "ws", "wsSettings": { "path": "/$ROUTE" }, "security": "none" } } ], "outbounds": [ { "protocol": "freedom", "settings": {} } ] } EOT sudo chown -R $OWNER:$GCR_GID $DIR sudo chmod 751 $DIR sudo touch $DIR/error.log sudo chmod 660 $CONFIG $DIR/error.log info "Writing $DIR/qr.sh for displaying client config as QR image" local title url title=$(curl -s4 http://ip-api.com/line?fields=city) title="${title// /%20}" # NOTE: Pin the domain to a XIU2/CloudflareSpeedTest picked IP, open # Shadowrocket > Config > Modules > New Module, define a `[Host]` section: # [Host] # = url="vless://${uuid}@${DOMAIN}:443?type=ws&path=/${ROUTE}&host=$DOMAIN&security=tls&sni=${DOMAIN}#${title}" echo -e "#/bin/sh\necho '$url' | qrencode -t utf8" | sudo tee $DIR/qr.sh sudo chown $OWNER:$GCR_GID $DIR/qr.sh sudo chmod 700 $DIR/qr.sh } function setup_start { info "Writing startup script $DIR/start.sh" echo -e "#!/bin/sh\ndocker" \ run --restart=unless-stopped -d \ --name=$CONTAINER \ -v $DIR:$DIR \ -p 127.0.0.1:$PORT:$PORT \ -p "[::1]:$PORT:$PORT" \ $IMAGE \ run -c $CONFIG \ | sudo tee $DIR/start.sh sudo chown $OWNER:$GCR_GID $DIR/start.sh sudo chmod 700 $DIR/start.sh } function start { if docker ps --format='{{.Names}}' | grep -wq $CONTAINER >&/dev/null; then echo "Docker container '$CONTAINER' is already up, removing..." docker stop $CONTAINER docker rm $CONTAINER fi info "Starting container '$CONTAINER'" sudo -H -u $OWNER $DIR/start.sh } function print_qr { info "Open Shadowrocket and scan below QR image to add the server:" sudo -H -u $OWNER $DIR/qr.sh info "\nFor computer use, see example at https://joyus.org/vps/config.json" } main "$@"