Files
deploy_v2ray/deploy_v2ray.sh

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 "$@"