663 lines
23 KiB
Bash
663 lines
23 KiB
Bash
#!/bin/bash
|
|
|
|
#================================================================
|
|
# Project: V2Ray All-in-One Deployment Script
|
|
# Author: Gemini & User
|
|
# Version: 3.0 (Fully Non-Interactive & Documented)
|
|
# Description: Automates the deployment of V2Ray with WebSocket,
|
|
# TLS, Nginx, and generates a Clash configuration.
|
|
# This script is designed for Debian/Ubuntu systems.
|
|
#================================================================
|
|
#
|
|
# --- User Guide ---
|
|
#
|
|
# This script supports two modes of operation:
|
|
#
|
|
# 1. Interactive Mode (Default):
|
|
# Simply run the script with sudo, and it will prompt you for all
|
|
# necessary information.
|
|
# $ sudo ./deploy_v2ray.sh
|
|
#
|
|
# 2. Non-Interactive Mode (for Automation):
|
|
# Set the required configuration as environment variables before running
|
|
# the script. This is ideal for use with secrets management tools like
|
|
# 1Password CLI (op), Doppler, or in CI/CD pipelines.
|
|
#
|
|
# Example with 1Password CLI:
|
|
# $ op run --env-file=.env -- sudo ./deploy_v2ray.sh
|
|
#
|
|
# --- Environment Variables ---
|
|
#
|
|
# To run in non-interactive mode, set the following variables.
|
|
# For yes/no questions, 'y' means yes, anything else means no.
|
|
#
|
|
# [Core Configuration]
|
|
# V2RAY_DOMAIN # Your domain name (e.g., v2.example.com). Required.
|
|
# V2RAY_EMAIL # Your email for SSL certificates. Required.
|
|
# V2RAY_UUID # Your V2Ray UUID. Optional, will be generated if not set.
|
|
#
|
|
# [Cloudflare DNS Automation]
|
|
# V2RAY_USE_CF_DNS # Set to 'y' to enable. If not set, will ask interactively.
|
|
# CF_API_TOKEN # Your Cloudflare API Token. Required if V2RAY_USE_CF_DNS=y.
|
|
# CF_ZONE_ID # Your Cloudflare Zone ID. Required if V2RAY_USE_CF_DNS=y.
|
|
#
|
|
# [GitHub Gist Subscription]
|
|
# V2RAY_USE_GIST # Set to 'y' to enable. If not set, will ask interactively.
|
|
# GITHUB_USER # Your GitHub username. Required if V2RAY_USE_GIST=y.
|
|
# GITHUB_TOKEN # Your GitHub Personal Access Token (with 'gist' scope).
|
|
# # Required if V2RAY_USE_GIST=y.
|
|
#
|
|
#================================================================
|
|
|
|
|
|
# --- Color Codes ---
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# --- State File ---
|
|
STATE_FILE="/root/.v2ray_deployment_state"
|
|
|
|
# --- Script functions ---
|
|
|
|
# Function to print error messages and exit
|
|
error_exit() {
|
|
echo -e "${RED}Error: $1${NC}" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Function to check if running as root
|
|
check_root() {
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
error_exit "This script must be run as root. Please use sudo."
|
|
fi
|
|
}
|
|
|
|
# Function to run pre-flight checks and install essential tools
|
|
pre_flight_checks() {
|
|
echo -e "${BLUE}Running pre-flight checks and installing essential tools...${NC}"
|
|
apt-get update
|
|
apt-get install -y curl wget jq socat unzip || error_exit "Failed to install essential tools. Please check your network and apt sources."
|
|
}
|
|
|
|
# Function to get user input
|
|
get_user_input() {
|
|
# Load existing state if available
|
|
if [ -f "${STATE_FILE}" ]; then
|
|
source "${STATE_FILE}"
|
|
fi
|
|
|
|
echo -e "${BLUE}--- V2Ray Deployment Setup ---${NC}"
|
|
|
|
# Check for Domain from env
|
|
if [ -n "$V2RAY_DOMAIN" ]; then
|
|
echo -e "${GREEN}Domain found in environment variables: ${V2RAY_DOMAIN}${NC}"
|
|
DOMAIN="$V2RAY_DOMAIN"
|
|
else
|
|
read -p "Enter your domain name (e.g., v2.example.com): " DOMAIN
|
|
fi
|
|
if [ -z "${DOMAIN}" ]; then
|
|
error_exit "Domain name cannot be empty."
|
|
fi
|
|
|
|
# Check for Email from env
|
|
if [ -n "$V2RAY_EMAIL" ]; then
|
|
echo -e "${GREEN}Email found in environment variables: ${V2RAY_EMAIL}${NC}"
|
|
EMAIL="$V2RAY_EMAIL"
|
|
else
|
|
read -p "Enter your email for SSL certificate (e.g., admin@example.com): " EMAIL
|
|
fi
|
|
if [ -z "${EMAIL}" ]; then
|
|
error_exit "Email cannot be empty."
|
|
fi
|
|
|
|
echo ""
|
|
if [ -z "$V2RAY_USE_CF_DNS" ]; then
|
|
read -p "Do you want to automatically configure Cloudflare DNS? (y/n): " USE_CF_DNS
|
|
else
|
|
echo -e "${GREEN}Cloudflare DNS configuration is set by V2RAY_USE_CF_DNS environment variable.${NC}"
|
|
USE_CF_DNS=$V2RAY_USE_CF_DNS
|
|
fi
|
|
|
|
if [[ "$USE_CF_DNS" =~ ^[Yy]$ ]]; then
|
|
if [ -n "$CF_API_TOKEN" ]; then
|
|
echo -e "${GREEN}Cloudflare API Token found in environment variables.${NC}"
|
|
else
|
|
read -p "Enter your Cloudflare API Token: " CF_API_TOKEN
|
|
fi
|
|
|
|
if [ -n "$CF_ZONE_ID" ]; then
|
|
echo -e "${GREEN}Cloudflare Zone ID found in environment variables.${NC}"
|
|
else
|
|
read -p "Enter your Cloudflare Zone ID: " CF_ZONE_ID
|
|
fi
|
|
|
|
if [ -z "${CF_API_TOKEN}" ] || [ -z "${CF_ZONE_ID}" ]; then
|
|
error_exit "Cloudflare API Token and Zone ID are required for this feature."
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
if [ -z "$V2RAY_USE_GIST" ]; then
|
|
read -p "Do you want to create a GitHub Gist subscription link? (y/n): " USE_GIST
|
|
else
|
|
echo -e "${GREEN}GitHub Gist creation is set by V2RAY_USE_GIST environment variable.${NC}"
|
|
USE_GIST=$V2RAY_USE_GIST
|
|
fi
|
|
|
|
if [[ "$USE_GIST" =~ ^[Yy]$ ]]; then
|
|
if [ -n "$GITHUB_USER" ]; then
|
|
echo -e "${GREEN}GitHub Username found in environment variables: ${GITHUB_USER}${NC}"
|
|
else
|
|
read -p "Enter your GitHub Username: " GITHUB_USER
|
|
fi
|
|
|
|
if [ -n "$GITHUB_TOKEN" ]; then
|
|
echo -e "${GREEN}GitHub Personal Access Token found in environment variables.${NC}"
|
|
else
|
|
read -s -p "Enter your GitHub Personal Access Token (with 'gist' scope): " GITHUB_TOKEN
|
|
echo
|
|
fi
|
|
|
|
if [ -z "${GITHUB_USER}" ] || [ -z "${GITHUB_TOKEN}" ]; then
|
|
error_exit "GitHub Username and Token are required for this feature."
|
|
fi
|
|
fi
|
|
|
|
# Check for UUID from env
|
|
if [ -n "$V2RAY_UUID" ]; then
|
|
echo -e "${GREEN}UUID found in environment variables.${NC}"
|
|
UUID="$V2RAY_UUID"
|
|
else
|
|
read -p "Enter your V2Ray UUID (or press Enter to generate one): " UUID
|
|
fi
|
|
|
|
if [ -z "${UUID}" ]; then
|
|
UUID=$(cat /proc/sys/kernel/random/uuid)
|
|
echo -e "${YELLOW}Generated UUID: ${UUID}${NC}"
|
|
fi
|
|
|
|
# Generate a random path for WebSocket
|
|
WS_PATH="/$(head -n 10 /dev/urandom | md5sum | head -c 8)-ws"
|
|
echo -e "${YELLOW}Generated WebSocket Path: ${WS_PATH}${NC}"
|
|
|
|
echo -e "${BLUE}--- Configuration Summary ---"
|
|
echo -e "Domain: ${GREEN}${DOMAIN}${NC}"
|
|
echo -e "Email: ${GREEN}${EMAIL}${NC}"
|
|
echo -e "UUID: ${GREEN}${UUID}${NC}"
|
|
echo -e "WS Path: ${GREEN}${WS_PATH}${NC}"
|
|
echo -e "Auto DNS: " $([[ "$USE_CF_DNS" =~ ^[Yy]$ ]] && echo -e "${GREEN}Enabled${NC}" || echo -e "${YELLOW}Disabled${NC}")
|
|
echo -e "Gist Link: " $([[ "$USE_GIST" =~ ^[Yy]$ ]] && echo -e "${GREEN}Enabled${NC}" || echo -e "${YELLOW}Disabled${NC}")
|
|
echo -e "----------------------------${NC}"
|
|
read -p "Press Enter to continue, or Ctrl+C to cancel..."
|
|
}
|
|
|
|
# Function to update system and install dependencies
|
|
install_dependencies() {
|
|
echo -e "${BLUE}Updating system and installing dependencies...${NC}"
|
|
|
|
# System update and upgrade, with options to handle config file conflicts automatically
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade || error_exit "System upgrade failed."
|
|
|
|
# Install main applications
|
|
apt-get install -y nginx certbot python3-certbot-nginx || error_exit "Failed to install Nginx or Certbot."
|
|
|
|
# Install V2Ray
|
|
echo -e "${BLUE}Installing V2Ray...${NC}"
|
|
bash <(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh) || error_exit "V2Ray core installation failed."
|
|
bash <(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-dat-release.sh) || error_exit "V2Ray dat files installation failed."
|
|
|
|
systemctl enable --now v2ray || error_exit "Failed to enable V2Ray service."
|
|
systemctl enable --now nginx || error_exit "Failed to enable Nginx service."
|
|
|
|
echo -e "${GREEN}Dependencies installed successfully.${NC}"
|
|
}
|
|
|
|
# Function to configure Cloudflare DNS
|
|
setup_cloudflare_dns() {
|
|
if [[ ! "$USE_CF_DNS" =~ ^[Yy]$ ]]; then
|
|
return
|
|
fi
|
|
|
|
echo -e "${BLUE}Verifying domain with Cloudflare Zone...${NC}"
|
|
|
|
# Get the zone name from the provided Zone ID to verify domain ownership
|
|
ZONE_DETAILS_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}" \
|
|
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
|
-H "Content-Type: application/json")
|
|
|
|
SUCCESS=$(echo "${ZONE_DETAILS_RESPONSE}" | jq -r '.success')
|
|
if [ "${SUCCESS}" != "true" ]; then
|
|
ERRORS=$(echo "${ZONE_DETAILS_RESPONSE}" | jq -r '.errors[0].message')
|
|
error_exit "Cloudflare API call to get zone details failed: ${ERRORS}. Please check your Zone ID and API Token."
|
|
fi
|
|
|
|
ZONE_NAME=$(echo "${ZONE_DETAILS_RESPONSE}" | jq -r '.result.name')
|
|
|
|
# Check if the user-provided domain is part of the fetched zone
|
|
if ! [[ "${DOMAIN}" == "${ZONE_NAME}" || "${DOMAIN}" == *".${ZONE_NAME}" ]]; then
|
|
error_exit "Domain mismatch: The domain '${DOMAIN}' does not belong to the Cloudflare zone '${ZONE_NAME}' associated with your Zone ID."
|
|
fi
|
|
|
|
echo -e "${GREEN}Domain '${DOMAIN}' successfully verified against Zone '${ZONE_NAME}'.${NC}"
|
|
|
|
echo -e "${BLUE}Configuring Cloudflare DNS record for ${DOMAIN}...${NC}"
|
|
|
|
PUBLIC_IP=$(curl -s https://api.ipify.org)
|
|
if [ -z "${PUBLIC_IP}" ]; then
|
|
error_exit "Failed to get public IP address."
|
|
fi
|
|
echo "Public IP detected: ${PUBLIC_IP}"
|
|
|
|
# Check if DNS record already exists
|
|
DNS_RECORD_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records?type=A&name=${DOMAIN}" \
|
|
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
|
-H "Content-Type: application/json" | jq -r '.result[0].id')
|
|
|
|
if [ "$DNS_RECORD_ID" != "null" ] && [ ! -z "$DNS_RECORD_ID" ]; then
|
|
echo -e "${YELLOW}DNS record for ${DOMAIN} already exists. Updating it...${NC}"
|
|
CF_API_RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${DNS_RECORD_ID}" \
|
|
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "{\"type\":\"A\",\"name\":\"${DOMAIN}\",\"content\":\"${PUBLIC_IP}\",\"ttl\":120,\"proxied\":false}")
|
|
else
|
|
echo -e "${BLUE}Creating new DNS A record for ${DOMAIN}...${NC}"
|
|
CF_API_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records" \
|
|
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "{\"type\":\"A\",\"name\":\"${DOMAIN}\",\"content\":\"${PUBLIC_IP}\",\"ttl\":120,\"proxied\":false}")
|
|
fi
|
|
|
|
SUCCESS=$(echo "${CF_API_RESPONSE}" | jq -r '.success')
|
|
if [ "${SUCCESS}" != "true" ]; then
|
|
ERRORS=$(echo "${CF_API_RESPONSE}" | jq -r '.errors[0].message')
|
|
error_exit "Cloudflare API call failed: ${ERRORS}"
|
|
fi
|
|
|
|
echo -e "${GREEN}Cloudflare DNS record configured successfully.${NC}"
|
|
echo -e "${YELLOW}Waiting 30 seconds for DNS to propagate...${NC}"
|
|
sleep 30
|
|
}
|
|
|
|
# Function to configure Nginx and get SSL certificate
|
|
configure_nginx_and_ssl() {
|
|
echo -e "${BLUE}Configuring Nginx and obtaining SSL certificate...${NC}"
|
|
|
|
# --- Pre-emptive Cleanup ---
|
|
# Remove any stray .bak files from previous failed runs in sites-enabled
|
|
rm -f /etc/nginx/sites-enabled/*.bak*
|
|
# Remove any broken symlinks in sites-enabled
|
|
find /etc/nginx/sites-enabled/ -xtype l -delete
|
|
|
|
# Create a directory for the fake site
|
|
mkdir -p /var/www/${DOMAIN} || error_exit "Failed to create web directory."
|
|
echo "<h1>Welcome to ${DOMAIN}</h1>" > /var/www/${DOMAIN}/index.html
|
|
chown -R www-data:www-data /var/www/${DOMAIN} || error_exit "Failed to set web directory permissions."
|
|
|
|
# Backup the SOURCE config file in sites-available if it exists, then remove it
|
|
if [ -f "/etc/nginx/sites-available/${DOMAIN}" ]; then
|
|
echo -e "${YELLOW}Backing up and removing old config in sites-available...${NC}"
|
|
mv "/etc/nginx/sites-available/${DOMAIN}" "/etc/nginx/sites-available/${DOMAIN}.bak_$(date +%s)"
|
|
fi
|
|
|
|
# Remove any existing symlink in sites-enabled to avoid conflicts
|
|
if [ -L "/etc/nginx/sites-enabled/${DOMAIN}" ]; then
|
|
rm -f "/etc/nginx/sites-enabled/${DOMAIN}"
|
|
fi
|
|
|
|
# Create a minimal Nginx config for certbot to find and modify
|
|
cat > /etc/nginx/sites-available/${DOMAIN} <<EOF
|
|
server {
|
|
listen 80;
|
|
server_name ${DOMAIN};
|
|
root /var/www/${DOMAIN};
|
|
}
|
|
EOF
|
|
|
|
# Enable the site by creating a clean symlink
|
|
ln -s "/etc/nginx/sites-available/${DOMAIN}" "/etc/nginx/sites-enabled/${DOMAIN}" || error_exit "Failed to enable Nginx site."
|
|
|
|
# Test and reload Nginx
|
|
nginx -t && systemctl reload nginx || error_exit "Nginx configuration test failed."
|
|
|
|
# Obtain SSL certificate and let certbot configure Nginx
|
|
echo -e "${BLUE}Requesting SSL Certificate for ${DOMAIN} and configuring Nginx...${NC}"
|
|
certbot --nginx -d "${DOMAIN}" --agree-tos -m "${EMAIL}" --no-eff-email --redirect --non-interactive || error_exit "Certbot failed to obtain SSL certificate and configure Nginx."
|
|
|
|
echo -e "${GREEN}Nginx and SSL configured successfully by Certbot.${NC}"
|
|
}
|
|
|
|
# Function to configure V2Ray server and update Nginx for WebSocket
|
|
configure_v2ray_and_final_nginx() {
|
|
echo -e "${BLUE}Configuring V2Ray and overwriting Nginx config for WebSocket...${NC}"
|
|
|
|
# V2Ray local port
|
|
V2RAY_PORT=10086
|
|
|
|
# Backup V2Ray config before overwriting
|
|
if [ -f /usr/local/etc/v2ray/config.json ]; then
|
|
mv /usr/local/etc/v2ray/config.json /usr/local/etc/v2ray/config.json.bak_$(date +%s)
|
|
fi
|
|
|
|
# Create V2Ray server config
|
|
cat > /usr/local/etc/v2ray/config.json <<EOF
|
|
{
|
|
"log": {
|
|
"loglevel": "warning"
|
|
},
|
|
"inbounds": [
|
|
{
|
|
"port": 10086,
|
|
"listen": "127.0.0.1",
|
|
"protocol": "vmess",
|
|
"settings": {
|
|
"clients": [
|
|
{
|
|
"id": "${UUID}",
|
|
"alterId": 0
|
|
}
|
|
]
|
|
},
|
|
"streamSettings": {
|
|
"network": "ws",
|
|
"wsSettings": {
|
|
"path": "${WS_PATH}"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"outbounds": [
|
|
{
|
|
"protocol": "freedom",
|
|
"settings": {}
|
|
}
|
|
]
|
|
}
|
|
EOF
|
|
|
|
# Backup Nginx config before overwriting
|
|
if [ -f /etc/nginx/sites-available/${DOMAIN} ]; then
|
|
mv /etc/nginx/sites-available/${DOMAIN} /etc/nginx/sites-available/${DOMAIN}.bak_$(date +%s)
|
|
fi
|
|
|
|
# Create final Nginx config for WebSocket proxy, building upon Certbot's work
|
|
cat > /etc/nginx/sites-available/${DOMAIN} <<EOF
|
|
server {
|
|
server_name ${DOMAIN};
|
|
|
|
root /var/www/${DOMAIN};
|
|
index index.html;
|
|
|
|
# WebSocket proxy to V2Ray
|
|
location ${WS_PATH} {
|
|
if (\$http_upgrade != "websocket") {
|
|
return 404;
|
|
}
|
|
proxy_pass http://127.0.0.1:${V2RAY_PORT};
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
}
|
|
|
|
# SSL Configuration managed by Certbot
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name ${DOMAIN};
|
|
if (\$request_uri !~ "^/\\.well-known/acme-challenge/") {
|
|
return 301 https://\$host\$request_uri;
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Test Nginx config and restart services
|
|
nginx -t && systemctl restart nginx || error_exit "Nginx final configuration failed."
|
|
systemctl restart v2ray || error_exit "V2Ray service restart failed."
|
|
|
|
echo -e "${GREEN}V2Ray and Nginx re-configured for WebSocket successfully.${NC}"
|
|
}
|
|
|
|
# Function to create or update a GitHub Gist for the Clash config
|
|
create_github_gist() {
|
|
if [[ ! "$USE_GIST" =~ ^[Yy]$ ]]; then
|
|
return
|
|
fi
|
|
|
|
echo -e "${BLUE}Creating or updating GitHub Gist for subscription...${NC}"
|
|
|
|
CLASH_CONFIG_CONTENT=$(cat ${CLASH_CONFIG_PATH})
|
|
GIST_FILENAME="${DOMAIN}.yaml"
|
|
|
|
GIST_PAYLOAD=$(jq -n \
|
|
--arg description "V2Ray Clash subscription for ${DOMAIN}" \
|
|
--arg filename "${GIST_FILENAME}" \
|
|
--arg content "${CLASH_CONFIG_CONTENT}" \
|
|
'{
|
|
"description": $description,
|
|
"public": false,
|
|
"files": {
|
|
($filename): {
|
|
"content": $content
|
|
}
|
|
}
|
|
}')
|
|
|
|
if [ -z "${GIST_ID}" ]; then
|
|
echo "Creating new Gist..."
|
|
API_METHOD="POST"
|
|
API_URL="https://api.github.com/gists"
|
|
else
|
|
echo "Updating existing Gist (ID: ${GIST_ID})..."
|
|
API_METHOD="PATCH"
|
|
API_URL="https://api.github.com/gists/${GIST_ID}"
|
|
fi
|
|
|
|
GIST_RESPONSE=$(curl -s -X ${API_METHOD} ${API_URL} \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
-H "Accept: application/vnd.github.v3+json" \
|
|
-d "${GIST_PAYLOAD}")
|
|
|
|
# Try to get URL and ID from response
|
|
GIST_RAW_URL=$(echo "${GIST_RESPONSE}" | jq -r --arg filename "${GIST_FILENAME}" '.files[$filename].raw_url')
|
|
NEW_GIST_ID=$(echo "${GIST_RESPONSE}" | jq -r '.id')
|
|
|
|
if [ "${GIST_RAW_URL}" == "null" ] || [ -z "${GIST_RAW_URL}" ]; then
|
|
ERROR_MSG=$(echo "${GIST_RESPONSE}" | jq -r '.message')
|
|
echo -e "${YELLOW}Warning: Failed to create or update GitHub Gist. Error: ${ERROR_MSG}${NC}"
|
|
GIST_RAW_URL=""
|
|
else
|
|
# If Gist ID is new, save it to the state file
|
|
if [ "${GIST_ID}" != "${NEW_GIST_ID}" ]; then
|
|
echo "GIST_ID=\"${NEW_GIST_ID}\"" > "${STATE_FILE}"
|
|
echo -e "${GREEN}New GitHub Gist created and state saved.${NC}"
|
|
else
|
|
echo -e "${GREEN}GitHub Gist updated successfully.${NC}"
|
|
fi
|
|
GIST_ID=${NEW_GIST_ID} # Ensure GIST_ID is set for the current run
|
|
fi
|
|
}
|
|
|
|
# Function to generate Clash config and show summary
|
|
generate_clash_config() {
|
|
echo -e "${BLUE}Generating Clash client configuration...${NC}"
|
|
|
|
CLASH_CONFIG_PATH="/root/clash_config.yaml"
|
|
|
|
cat > ${CLASH_CONFIG_PATH} <<EOF
|
|
# ================== Clash Meta Configuration ==================
|
|
port: 7890
|
|
socks-port: 7891
|
|
redir-port: 7892
|
|
mode: rule
|
|
allow-lan: false
|
|
bind-address: 127.0.0.1
|
|
log-level: info
|
|
|
|
external-controller: 127.0.0.1:9090
|
|
external-ui: dashboard
|
|
|
|
######### DNS #########
|
|
dns:
|
|
enable: true
|
|
listen: 127.0.0.1:53
|
|
ipv6: false
|
|
enhanced-mode: redir-host
|
|
nameserver:
|
|
- https://dns.cloudflare.com/dns-query
|
|
- https://dns.google/dns-query
|
|
- 223.5.5.5
|
|
fallback:
|
|
- https://1.0.0.1/dns-query
|
|
- tls://8.8.4.4:853
|
|
- 8.8.4.4
|
|
fallback-filter:
|
|
geoip: true
|
|
geoip-code: CN
|
|
|
|
######### Proxies #########
|
|
proxies:
|
|
- name: "${DOMAIN}"
|
|
type: vmess
|
|
server: ${DOMAIN}
|
|
port: 443
|
|
uuid: ${UUID}
|
|
alterId: 0
|
|
cipher: auto
|
|
network: ws
|
|
tls: true
|
|
servername: ${DOMAIN}
|
|
skip-cert-verify: false
|
|
udp: true
|
|
ws-opts:
|
|
path: "${WS_PATH}"
|
|
headers:
|
|
Host: ${DOMAIN}
|
|
|
|
######### Proxy Groups #########
|
|
proxy-groups:
|
|
- name: "🚀 PROXY"
|
|
type: select
|
|
proxies:
|
|
- "${DOMAIN}"
|
|
- DIRECT
|
|
|
|
- name: "🎥 Streaming"
|
|
type: select
|
|
proxies:
|
|
- "${DOMAIN}"
|
|
- DIRECT
|
|
|
|
- name: "🆎 AdBlock"
|
|
type: select
|
|
proxies:
|
|
- REJECT
|
|
- DIRECT
|
|
|
|
######### Rule Providers #########
|
|
rule-providers:
|
|
ads:
|
|
type: http
|
|
behavior: domain
|
|
url: "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/reject.txt"
|
|
path: ./ruleset/ads.list
|
|
interval: 86400
|
|
|
|
mainland:
|
|
type: http
|
|
behavior: domain
|
|
url: "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/direct.txt"
|
|
path: ./ruleset/mainland.list
|
|
interval: 86400
|
|
|
|
gfwlist:
|
|
type: http
|
|
behavior: domain
|
|
url: "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/proxy.txt"
|
|
path: ./ruleset/gfwlist.list
|
|
interval: 86400
|
|
|
|
cn_ip:
|
|
type: http
|
|
behavior: ipcidr
|
|
url: "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/cncidr.txt"
|
|
path: ./ruleset/cn_ip.list
|
|
interval: 86400
|
|
|
|
######### Rules #########
|
|
rules:
|
|
- RULE-SET,ads,🆎 AdBlock
|
|
- RULE-SET,cn_ip,DIRECT
|
|
- RULE-SET,mainland,DIRECT
|
|
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
|
|
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
|
|
- IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
|
|
# Optional streaming media rules
|
|
# - GEOSITE,netflix,🎥 Streaming
|
|
# - GEOSITE,youtube,🎥 Streaming
|
|
- RULE-SET,gfwlist,🚀 PROXY
|
|
- MATCH,🚀 PROXY
|
|
EOF
|
|
|
|
echo -e "${GREEN}Clash configuration file generated at: ${CLASH_CONFIG_PATH}${NC}"
|
|
}
|
|
|
|
# Function to show a summary of the deployment
|
|
show_summary() {
|
|
echo -e "${GREEN}===================================================================${NC}"
|
|
echo -e "${GREEN} V2Ray Deployment Completed Successfully! ${NC}"
|
|
echo -e "${GREEN}===================================================================${NC}"
|
|
echo -e "${YELLOW}Your V2Ray Configuration:${NC}"
|
|
echo -e "-------------------------------------------------------------------"
|
|
echo -e " Address (地址): ${GREEN}${DOMAIN}${NC}"
|
|
echo -e " Port (端口): ${GREEN}443${NC}"
|
|
echo -e " UUID (用户ID): ${GREEN}${UUID}${NC}"
|
|
echo -e " AlterId: ${GREEN}0${NC}"
|
|
echo -e " Security (加密): ${GREEN}auto${NC}"
|
|
echo -e " Network (网络): ${GREEN}ws${NC}"
|
|
echo -e " Host (主机名): ${GREEN}${DOMAIN}${NC}"
|
|
echo -e " Path (路径): ${GREEN}${WS_PATH}${NC}"
|
|
echo -e " TLS: ${GREEN}tls${NC}"
|
|
echo -e "-------------------------------------------------------------------"
|
|
echo -e "${YELLOW}Clash Configuration File:${NC}"
|
|
echo -e " The Clash config file is located at: ${GREEN}/root/clash_config.yaml${NC}"
|
|
if [[ ! -z "${GIST_RAW_URL}" ]]; then
|
|
echo -e "${YELLOW}Clash Subscription Link:${NC}"
|
|
echo -e " ${GREEN}${GIST_RAW_URL}${NC}"
|
|
fi
|
|
echo -e " You can also download the local file using scp or sftp."
|
|
echo -e "${GREEN}===================================================================${NC}"
|
|
}
|
|
|
|
|
|
# --- Main execution ---
|
|
main() {
|
|
pre_flight_checks
|
|
check_root
|
|
get_user_input
|
|
|
|
setup_cloudflare_dns
|
|
install_dependencies
|
|
configure_nginx_and_ssl
|
|
configure_v2ray_and_final_nginx
|
|
generate_clash_config
|
|
create_github_gist
|
|
|
|
show_summary
|
|
}
|
|
|
|
# Run the main function
|
|
main "$@" |