wget https://gist.githubusercontent.com/tokisakiyuu/99b7966b2201a7246c5905f67cc7ae81/raw/a9c92c8d5b4c6f1999d4b77fbf28c6b1f2a6c74b/v2ray.sh -O v2ray.sh && sudo bash v2ray.sh
Last active
February 2, 2026 01:28
-
-
Save tokisakiyuu/99b7966b2201a7246c5905f67cc7ae81 to your computer and use it in GitHub Desktop.
Automatic V2Ray VPN Server Installation on VPS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| # Define colors for output: | |
| GREEN="\033[0;32m" | |
| YELLOW="\033[0;33m" | |
| RED="\033[0;31m" | |
| NC="\033[0m" # Reset color | |
| # Paths to configuration files | |
| CONFIG_PATH="/usr/local/etc/v2ray/config.json" | |
| CLIENTS_DIR="v2ray-profiles" | |
| mkdir -p "$CLIENTS_DIR" | |
| # Function to check and install dependencies | |
| function ensure_installed() { | |
| local cmd="$1" | |
| local package="$1" | |
| if [ "$cmd" == "uuidgen" ]; then | |
| package="uuid-runtime" | |
| fi | |
| if ! command -v "$cmd" >/dev/null 2>&1; then | |
| apt-get update && apt-get install -y "$package" | |
| fi | |
| } | |
| ensure_installed curl | |
| ensure_installed uuidgen | |
| ensure_installed jq | |
| ensure_installed iconv | |
| ensure_installed qrencode | |
| # Function to open port 443 (quiet output) | |
| function open_port443() { | |
| if command -v ufw >/dev/null 2>&1; then | |
| if ! ufw status numbered 2>/dev/null | grep -q "443/tcp"; then | |
| ufw allow 443/tcp >/dev/null 2>&1 | |
| fi | |
| else | |
| if ! iptables -C INPUT -p tcp --dport 443 -j ACCEPT >/dev/null 2>&1; then | |
| iptables -I INPUT -p tcp --dport 443 -j ACCEPT >/dev/null 2>&1 | |
| fi | |
| iptables-save >/dev/null 2>&1 | |
| fi | |
| } | |
| open_port443 | |
| # Step 1. Install V2Ray if it's not installed | |
| if ! systemctl is-active --quiet v2ray; then | |
| bash <(curl -Ls https://github.com/v2fly/fhs-install-v2ray/raw/master/install-release.sh) | |
| systemctl enable v2ray | |
| systemctl start v2ray | |
| if ! systemctl is-active --quiet v2ray; then | |
| echo -e "\n${YELLOW}🚨 Error: V2Ray is not running after installation${NC}" | |
| exit 1 | |
| fi | |
| fi | |
| # Function to convert a string: Cyrillic to Latin and replace non-alphabetical characters with _ | |
| function to_latin_with_underscore() { | |
| echo "$1" | iconv -f utf-8 -t ascii//translit | sed -r 's/[^a-zA-Z0-9]+/_/g' | tr '[:upper:]' '[:lower:]' | |
| } | |
| # Function to save configuration and restart the service | |
| function save_config() { | |
| jq '.' "$CONFIG_PATH" > "$CONFIG_PATH.tmp" && mv "$CONFIG_PATH.tmp" "$CONFIG_PATH" | |
| systemctl restart v2ray | |
| } | |
| # Function to create a new client | |
| function create_client() { | |
| while true; do | |
| read -p "✏️ Enter profile name: " PROFILE_NAME | |
| SLUG=$(to_latin_with_underscore "$PROFILE_NAME") | |
| if [ -z "$SLUG" ]; then | |
| echo -e "\n${YELLOW}⚠️ Profile name cannot be empty. Please try again.${NC}" | |
| else | |
| break | |
| fi | |
| done | |
| CLIENT_UUID=$(uuidgen) | |
| SERVER_IP=$(curl -s ifconfig.me || echo "YOUR_SERVER_IP") | |
| UPDATED_CONFIG=$(jq --arg id "$CLIENT_UUID" --arg profile "$SLUG" ' | |
| .inbounds[0].settings.clients += [{id: $id, alterId: 0, profile: $profile}] | |
| ' "$CONFIG_PATH") | |
| echo "$UPDATED_CONFIG" > "$CONFIG_PATH" | |
| systemctl restart v2ray | |
| CLIENT_JSON=$(jq -n \ | |
| --arg v "2" \ | |
| --arg ps "$PROFILE_NAME" \ | |
| --arg add "$SERVER_IP" \ | |
| --arg port "443" \ | |
| --arg id "$CLIENT_UUID" \ | |
| --arg aid "0" \ | |
| --arg net "ws" \ | |
| --arg type "none" \ | |
| --arg host "" \ | |
| --arg path "$WS_PATH" \ | |
| --arg tls "none" \ | |
| '{ | |
| v: $v, | |
| ps: $ps, | |
| add: $add, | |
| port: $port, | |
| id: $id, | |
| aid: $aid, | |
| net: $net, | |
| type: $type, | |
| host: $host, | |
| path: $path, | |
| tls: $tls | |
| }' | |
| ) | |
| JSON_PATH="$CLIENTS_DIR/${SLUG}.json" | |
| URL_PATH="$CLIENTS_DIR/${SLUG}.url" | |
| echo "$CLIENT_JSON" > "$JSON_PATH" | |
| VMESS_LINK="vmess://$(echo -n "$CLIENT_JSON" | base64 -w 0)" | |
| echo "$VMESS_LINK" > "$URL_PATH" | |
| echo -e "\n✅ Client '$PROFILE_NAME' created" | |
| JSON_FULL_PATH=$(readlink -f "$JSON_PATH") | |
| URL_FULL_PATH=$(readlink -f "$URL_PATH") | |
| echo -e "\n📄 ${JSON_FULL_PATH}" | |
| cat "$JSON_PATH" | |
| echo -e "\n🔗 ${URL_FULL_PATH}" | |
| cat "$URL_PATH" | |
| # Генерация QR-кода | |
| PNG_PATH="$CLIENTS_DIR/${SLUG}.png" | |
| qrencode -o "$PNG_PATH" -s 10 "$VMESS_LINK" | |
| echo -e "\n📸 $(readlink -f "$PNG_PATH")" | |
| qrencode -t ANSI "$VMESS_LINK" | |
| } | |
| # Step 2. Generate basic server config if not exists | |
| SERVER_PORT=443 | |
| if ! jq -e '.inbounds' "$CONFIG_PATH" >/dev/null 2>&1; then | |
| WS_PATH="/$(tr -dc A-Za-z0-9 </dev/urandom | head -c 12)" | |
| cat > "$CONFIG_PATH" <<EOF | |
| { | |
| "inbounds": [ | |
| { | |
| "listen": "0.0.0.0", | |
| "port": $SERVER_PORT, | |
| "protocol": "vmess", | |
| "settings": { | |
| "clients": [] | |
| }, | |
| "streamSettings": { | |
| "network": "ws", | |
| "wsSettings": { | |
| "path": "$WS_PATH" | |
| } | |
| } | |
| } | |
| ], | |
| "outbounds": [ | |
| { | |
| "protocol": "freedom", | |
| "settings": {} | |
| } | |
| ] | |
| } | |
| EOF | |
| systemctl restart v2ray | |
| else | |
| WS_PATH=$(jq -r '.inbounds[0].streamSettings.wsSettings.path' "$CONFIG_PATH") | |
| fi | |
| # Function to list clients from the config | |
| function list_clients() { | |
| jq -r '.inbounds[0].settings.clients[] | "\(.profile // "unnamed")|\(.id)"' "$CONFIG_PATH" | |
| } | |
| # Function to revoke a client | |
| function revoke_client() { | |
| mapfile -t CLIENTS < <(list_clients) | |
| if [ ${#CLIENTS[@]} -eq 0 ]; then | |
| echo -e "\n${YELLOW}⚠️ No clients to revoke.${NC}" | |
| return | |
| fi | |
| echo -e "\n${GREEN}⬅ [0] Back${NC}" | |
| for i in "${!CLIENTS[@]}"; do | |
| NAME=$(cut -d'|' -f1 <<< "${CLIENTS[$i]}") | |
| ID=$(cut -d'|' -f2 <<< "${CLIENTS[$i]}") | |
| echo -e "☠️ ${GREEN}[$((i+1))] ${ID} ${NAME}${NC}" | |
| done | |
| read -p "Choose client number to revoke: " CHOICE | |
| if [[ "$CHOICE" == "0" ]]; then | |
| return | |
| fi | |
| INDEX=$((CHOICE-1)) | |
| if [[ $INDEX -ge 0 && $INDEX -lt ${#CLIENTS[@]} ]]; then | |
| REVOKE_ID=$(cut -d'|' -f2 <<< "${CLIENTS[$INDEX]}") | |
| UPDATED_CONFIG=$(jq --arg id "$REVOKE_ID" '(.inbounds[0].settings.clients) |= map(select(.id != $id))' "$CONFIG_PATH") | |
| echo "$UPDATED_CONFIG" > "$CONFIG_PATH" | |
| systemctl restart v2ray | |
| for jsonfile in "$CLIENTS_DIR"/*.json; do | |
| if grep -q "$REVOKE_ID" "$jsonfile" 2>/dev/null; then | |
| base="${jsonfile%.json}" | |
| rm "$jsonfile" | |
| if [ -f "${base}.url" ]; then | |
| rm "${base}.url" | |
| fi | |
| if [ -f "${base}.png" ]; then | |
| rm "${base}.png" | |
| fi | |
| fi | |
| done | |
| echo -e "\n${GREEN}✅ Client revoked${NC}" | |
| else | |
| echo -e "\n${YELLOW}⚠️ Invalid choice.${NC}" | |
| fi | |
| } | |
| # Function for full removal of V2Ray and client files | |
| function full_removal() { | |
| WS_PATH=$(jq -r '.inbounds[0].streamSettings.wsSettings.path' "$CONFIG_PATH") | |
| WS_NAME="${WS_PATH#/}" | |
| echo -e "\n${RED}🚨 Attention!!! This action is irreversible!${NC}" | |
| echo -e "${GREEN}To cancel, press${NC} ⏎" | |
| echo -e "${RED}To delete, enter${NC} ${YELLOW}${WS_NAME}${NC}" | |
| read -p "Enter the value to confirm deletion: " CONFIRM | |
| if [ "$CONFIRM" = "$WS_NAME" ]; then | |
| systemctl stop v2ray | |
| systemctl disable v2ray | |
| rm -rf "/usr/local/etc/v2ray" | |
| rm -rf "/usr/local/share/v2ray" | |
| rm -f "/usr/local/bin/v2ray" | |
| rm -rf "/etc/systemd/system/v2ray.service.d" | |
| rm -f "/etc/systemd/system/v2ray.service" | |
| rm -f "/etc/systemd/system/v2ray@.service" | |
| rm -f "/etc/systemd/system/multi-user.target.wants/v2ray.service" | |
| rm -rf "$CLIENTS_DIR" | |
| systemctl daemon-reload | |
| echo -e "\n${GREEN}💣 Deletion complete. Don't forget to remove ${YELLOW}v2ray.sh${NC}" | |
| exit 0 | |
| else | |
| echo -e "\n${YELLOW}⚠️ Invalid input. Deletion canceled.${NC}" | |
| fi | |
| } | |
| # Main menu function | |
| function main_menu() { | |
| echo -e "\n${GREEN}🚪 [0] Exit${NC}" | |
| echo -e "${GREEN}👨 [1] Create Client${NC}" | |
| echo -e "${GREEN}☠️ [2] Revoke Client${NC}" | |
| echo -e "${RED}💣 [3] Full Removal${NC}" | |
| read -p "Your choice: " OPTION | |
| case "$OPTION" in | |
| 1) | |
| create_client | |
| ;; | |
| 2) | |
| revoke_client | |
| ;; | |
| 3) | |
| full_removal | |
| ;; | |
| 0) | |
| exit 0 | |
| ;; | |
| *) | |
| echo -e "\n${YELLOW}⚠️ Invalid choice. Try again.${NC}" | |
| ;; | |
| esac | |
| } | |
| while true; do | |
| main_menu | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment