feat: Implement full-featured V2Ray deployment script

This commit is contained in:
2025-07-23 14:34:43 +08:00
commit 6281a013f7
6 changed files with 6609 additions and 0 deletions

3
.cursorindexingignore Normal file
View File

@ -0,0 +1,3 @@
# Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references
.specstory/**

2
.specstory/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# SpecStory explanation file
/.what-is-this.md

File diff suppressed because it is too large Load Diff

25
a.md Normal file
View File

@ -0,0 +1,25 @@
脚本执行流程与功能分解
信息收集(交互式)
脚本启动后,会首先请您输入以下信息:
域名 (Domain):您准备用于 V2Ray 服务的域名例如sydney.zyj.best
邮箱 (Email):用于申请 Let's Encrypt SSL 证书,接收续期提醒。
UUIDV2Ray 的用户 ID。脚本可以自动为您生成一个新的 UUID。
WebSocket 路径 (Path):用于伪装的 WebSocket 路径(例如:/my-secret-path脚本可以自动生成一个随机路径。
环境准备与依赖安装
自动更新系统软件包 (apt update && apt upgrade)。
自动安装 V2Ray、Nginx 和 Certbot。
伪装站点与 Nginx HTTP 配置
创建伪装站点的目录 /var/www/your_domain。
生成一个简单的 index.html 首页。
根据您输入的域名,动态生成 Nginx 的 HTTP 配置文件,用于后续的 SSL 证书申请。
申请 SSL 证书
自动调用 certbot使用您提供的域名和邮箱以非交互方式申请 Let's Encrypt 证书,并配置 Nginx 进行 HTTPS 跳转。
生成 V2Ray 服务端配置
您提供的 readme.md 中是 Clash 客户端的配置。脚本将根据您输入的 UUID 和 WebSocket 路径,自动生成一份 V2Ray 服务端所需的 config.json 文件,并放置在 /usr/local/etc/v2ray/config.json。
更新 Nginx 实现 WebSocket 代理
脚本将重写 Nginx 的站点配置文件,加入完整的 HTTPS 支持,并将特定 WebSocket 路径的流量反向代理到本地的 V2Ray 服务。
生成 Clash 客户端订阅文件
部署成功后,脚本会在服务器的 /root/clash_config.yaml 路径下,根据您输入的所有信息,生成一份可以直接使用的 Clash 客户端配置文件。
重启服务并显示结果
完成所有配置后,自动重启 V2Ray 和 Nginx 服务使之生效。
在终端清晰地输出您的 V2Ray 连接信息和 Clash 配置文件的存放路径,方便您直接复制使用

660
deploy_v2ray.sh Normal file
View File

@ -0,0 +1,660 @@
#!/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
# 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 "$@"

396
readme.md Normal file
View File

@ -0,0 +1,396 @@
#我希望创建一个自动化脚本,用于在新的服务器上部署我的 v2ray 网络代理服务,以下是大概步骤,给出你的方案
1. 安装 v2ray通过脚本 bash <(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh) 和
bash <(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-dat-release.sh)
2. 安装 nginx 和 cerbot
sudo apt install -y nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
3. 放一个伪装首页
sudo mkdir -p /var/www/bwh-site
echo '<h1>Hello from sydney.zyj.best</h1>' | sudo tee /var/www/bwh-site/index.html
sudo chown -R www-data:www-data /var/www/bwh-site
4. 写入 HTTP 站点80 —— 仅用于申请证书
sudo tee /etc/nginx/sites-available/sydney.zyj.best <<'EOF'
server {
listen 80;
listen [::]:80;
server_name bwh.zyj.best;
root /var/www/bwh-site;
index index.html;
# 允许 ACME 挑战
location ~ /.well-known/acme-challenge/ {
allow all;
}
# 其他全部跳转到 HTTPS
location / { return 301 https://$host$request_uri; }
}
EOF
sudo ln -s /etc/nginx/sites-available/bwh.zyj.best /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
5. 一键签发 LetsEncrypt 证书443
sudo certbot --nginx -d sydney.zyj.best --agree-tos -m you@example.com --redirect
6. 更新 v2ray 配置
# ================= Clash Meta 配置 =================
port: 7890 # HTTP 代理
socks-port: 7891 # SOCKS5 代理
redir-port: 7892 # 透明代理 (Linux 可注释)
mode: rule # rule / global / direct
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 # 避免占用系统 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:
- name: "Bwh"
type: vmess
server: bwh.zyj.best
port: 443
uuid: 81c5bd30-21c0-ba05-f711-47e11c659598
alterId: 0
cipher: auto
network: ws
tls: true
servername: bwh.zyj.best
skip-cert-verify: false
udp: true
ws-opts:
path: "/mysecretpath-221667"
headers:
Host: bwh.zyj.best
######### 代理组 #########
proxy-groups:
# 主出站
- name: "🚀 PROXY"
type: select
proxies:
- "Bwh"
- DIRECT
# 流媒体分流
- name: "🎥 Streaming"
type: select
proxies:
- "Bwh"
- DIRECT
# 广告拦截后去向
- name: "🆎 AdBlock"
type: select
proxies:
- REJECT
- DIRECT
######### 规则提供器 #########
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:
# 1) 广告拦截
- RULE-SET,ads,🆎 AdBlock
# 2) 内网及局域网
- RULE-SET,cn_ip,DIRECT
# 3) 中国域名直连
- RULE-SET,mainland,DIRECT
# 4) 流媒体示例 (可按需追加 geosite:netflix 等)
# - GEOSITE,netflix,🎥 Streaming
# - GEOSITE,youtube,🎥 Streaming
# 5) GFW / 其他国外域名走代理
- RULE-SET,gfwlist,🚀 PROXY
# 6) 默认
- MATCH,🚀 PROXY
7. 更新 nginx 配置
##
# 站点bwh.zyj.best
# 模式:伪装静态站 + V2Ray WebSocketoverTLS同端口 443
# 文件:/etc/nginx/sites-available/bwh.zyj.best
##
############################
# HTTPS 443 伪装站 + 代理
############################
server {
server_name bwh.zyj.best;
# ----------------- 静态网站 -----------------
root /var/www/bwh-site;
index index.html;
# ----------------- WebSocket 反向代理 -----------------
# 与 V2Ray streamSettings.wsSettings.path 完全一致
location /mysecretpath-221667 {
# 非 WebSocket 请求直接 404可防扫描
if ($http_upgrade != "websocket") { return 404; }
proxy_pass http://127.0.0.1:10086; # V2Ray 本地监听
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;
proxy_set_header Origin "";
}
# ----------------- 其他静态资源 -----------------
# 直接按 root 目录查找文件
location / {
try_files $uri $uri/ =404;
}
# ----------------- ACME HTTP01 回调 -----------------
location ~ /.well-known/acme-challenge/ {
allow all;
}
# ----------------- SSL 设置Certbot 自动管理) -----------------
listen 443 ssl http2;
listen [::]:443 ssl http2 ipv6only=on;
ssl_certificate /etc/letsencrypt/live/bwh.zyj.best/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/bwh.zyj.best/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
############################
# HTTP 80 → HTTPS 重定向
############################
server {
listen 80;
listen [::]:80;
server_name bwh.zyj.best;
# ACME HTTP01 挑战
location ~ /.well-known/acme-challenge/ {
allow all;
root /var/www/bwh-site;
}
# 其余全部 301 到 HTTPS
location / {
return 301 https://$host$request_uri;
}
}
8. 生成 clash 订阅 文件
# ================= Clash Meta 配置 =================
port: 7890 # HTTP 代理
socks-port: 7891 # SOCKS5 代理
redir-port: 7892 # 透明代理 (Linux 可注释)
mode: rule # rule / global / direct
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 # 避免占用系统 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:
- name: "Bwh"
type: vmess
server: bwh.zyj.best
port: 443
uuid: 81c5bd30-21c0-ba05-f711-47e11c659598
alterId: 0
cipher: auto
network: ws
tls: true
servername: bwh.zyj.best
skip-cert-verify: false
udp: true
ws-opts:
path: "/mysecretpath-221667"
headers:
Host: bwh.zyj.best
######### 代理组 #########
proxy-groups:
# 主出站
- name: "🚀 PROXY"
type: select
proxies:
- "Bwh"
- DIRECT
# 流媒体分流
- name: "🎥 Streaming"
type: select
proxies:
- "Bwh"
- DIRECT
# 广告拦截后去向
- name: "🆎 AdBlock"
type: select
proxies:
- REJECT
- DIRECT
######### 规则提供器 #########
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:
# 1) 广告拦截
- RULE-SET,ads,🆎 AdBlock
# 2) 内网及局域网
- RULE-SET,cn_ip,DIRECT
# 3) 中国域名直连
- RULE-SET,mainland,DIRECT
# 4) 流媒体示例 (可按需追加 geosite:netflix 等)
# - GEOSITE,netflix,🎥 Streaming
# - GEOSITE,youtube,🎥 Streaming
# 5) GFW / 其他国外域名走代理
- RULE-SET,gfwlist,🚀 PROXY
# 6) 默认
- MATCH,🚀 PROXY