From b39f1e9f0b34201584c6064c1dbc0ee8e15a698e Mon Sep 17 00:00:00 2001 From: mike Date: Fri, 7 Aug 2026 09:45:08 +0800 Subject: [PATCH] Upload files to "/" --- fix-existing-cert-renewal.sh | 114 ++++++ vless-ws-tls-setup.sh | 655 +++++++++++++++++++++++++++++++++++ 2 files changed, 769 insertions(+) create mode 100644 fix-existing-cert-renewal.sh create mode 100644 vless-ws-tls-setup.sh diff --git a/fix-existing-cert-renewal.sh b/fix-existing-cert-renewal.sh new file mode 100644 index 0000000..f916029 --- /dev/null +++ b/fix-existing-cert-renewal.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# ============================================================ +# 现有 VPS 证书过期 + 续期修复脚本 +# 适用场景:用旧版 vless-ws-tls-setup.sh 部署的机器, +# 证书用 --standalone 申请,nginx 起来后一直占 80 端口, +# 导致 certbot renew 从第一次续期起就必定失败。 +# +# 本脚本做的事: +# 1. 找到已部署的域名(从 /etc/letsencrypt/live 里找) +# 2. 用 certbot 的 nginx 插件重新签发证书(nginx 不需要停) +# 3. 把 renewal 配置里的 authenticator 改成 nginx 插件 +# 4. 重写 cron 续期任务,加日志、用 deploy-hook +# 5. 跑一次 --dry-run 验证以后能不能正常自动续期 +# ============================================================ + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +info() { echo -e "${CYAN}[INFO]${NC} $1"; } +success() { echo -e "${GREEN}[OK]${NC} $1"; } +warning() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } + +[ "$EUID" -ne 0 ] && error "请使用 root 用户运行" + +# 1. 找域名 +if [ -n "$1" ]; then + DOMAIN="$1" +else + DOMAIN=$(ls /etc/letsencrypt/live 2>/dev/null | grep -v README | head -1) +fi +[ -z "$DOMAIN" ] && error "找不到域名,请手动指定:bash $0 你的域名" +info "目标域名:$DOMAIN" + +# 2. 确保 nginx 插件已安装 +if ! dpkg -l | grep -q python3-certbot-nginx; then + info "安装 python3-certbot-nginx..." + apt-get update -qq && apt-get install -y -qq python3-certbot-nginx +fi + +# 3. 检查 nginx 是否在跑(插件依赖 nginx 正常运行) +if ! systemctl is-active --quiet nginx; then + warning "nginx 当前未运行,先启动它" + systemctl start nginx || error "nginx 启动失败,请先手动修复 nginx 再运行本脚本" +fi + +# 4. 用 nginx 插件强制重签证书(不停 nginx,不改端口占用) +info "用 nginx 插件重新签发证书(过程中不会中断现有连接)..." +EMAIL_GUESS=$(grep -oP '(?<=--email )\S+' /var/log/letsencrypt/letsencrypt.log 2>/dev/null | tail -1) +EMAIL_GUESS=${EMAIL_GUESS:-admin@${DOMAIN}} + +certbot certonly --nginx \ + --cert-name "$DOMAIN" \ + -d "$DOMAIN" \ + --non-interactive \ + --agree-tos \ + --email "$EMAIL_GUESS" \ + --force-renewal \ + || error "证书重签失败,请检查:1. 域名解析是否指向本机 2. Cloudflare 是否切成灰色云朵(仅DNS)后再试" + +success "证书重签成功" + +# 5. 确认/修正 renewal 配置里的 authenticator +RENEWAL_CONF="/etc/letsencrypt/renewal/${DOMAIN}.conf" +if [ -f "$RENEWAL_CONF" ]; then + sed -i 's/^authenticator = .*/authenticator = nginx/' "$RENEWAL_CONF" + if grep -q '^installer = ' "$RENEWAL_CONF"; then + sed -i 's/^installer = .*/installer = nginx/' "$RENEWAL_CONF" + else + sed -i '/^\[renewalparams\]/a installer = nginx' "$RENEWAL_CONF" + fi + success "已确认 $RENEWAL_CONF 中 authenticator = nginx" +else + warning "未找到 $RENEWAL_CONF,续期方式可能没有正确记录,请手动检查" +fi + +# 6. 重写 cron 续期任务 +mkdir -p /var/log +touch /var/log/certbot-renew.log + +cat > /etc/cron.d/certbot-renew << CRONEOF +# Let's Encrypt 证书自动续期 - 每周一凌晨3点执行 +0 3 * * 1 root certbot renew --deploy-hook "systemctl reload nginx" >> /var/log/certbot-renew.log 2>&1 +CRONEOF +chmod 644 /etc/cron.d/certbot-renew +systemctl restart cron +success "已重写 cron 续期任务,日志输出到 /var/log/certbot-renew.log" + +# 7. 让 nginx 加载新证书 +systemctl reload nginx +success "nginx 已重新加载新证书" + +# 8. 验证以后能不能正常自动续期(这次 nginx 是开着的,应该会通过) +info "验证自动续期是否正常(dry-run,nginx 全程不会停)..." +if certbot renew --dry-run >> /var/log/certbot-renew.log 2>&1; then + success "续期验证通过!以后不会再出现到期后失效的问题" +else + warning "验证仍未通过,详情见 /var/log/certbot-renew.log,请贴出来我再帮你看" +fi + +CERT_PATH="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" +if [ -f "$CERT_PATH" ]; then + CERT_EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_PATH" | cut -d= -f2) + echo "" + success "当前证书有效期至:$CERT_EXPIRY" +fi + +echo "" +info "以后排查续期问题直接看日志:cat /var/log/certbot-renew.log" diff --git a/vless-ws-tls-setup.sh b/vless-ws-tls-setup.sh new file mode 100644 index 0000000..ec409be --- /dev/null +++ b/vless-ws-tls-setup.sh @@ -0,0 +1,655 @@ +#!/bin/bash +# ============================================================ +# VLESS + WebSocket + TLS + Cloudflare CDN 一键部署脚本 +# 专为 Debian 系统优化 +# ============================================================ + +set -e + +# 自检:去除 Windows CRLF 换行符 +if grep -qP '\r' "$0" 2>/dev/null; then + sed -i 's/\r//' "$0" + exec bash "$0" "$@" +fi + +# 预设标准化配置(直接回车使用以下默认值) +PRESET_UUID="0ad7d003-8e79-4529-8b55-09610947c09b" +PRESET_WS_PATH="/stickrouter" +PRESET_SING_PORT="10000" +PRESET_NGINX_PORT="2053" +PRESET_EMAIL="a79376464@gmail.com" +BASE_DOMAIN="stickrouter.com" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +info() { echo -e "${CYAN}[INFO]${NC} $1"; } +success() { echo -e "${GREEN}[OK]${NC} $1"; } +warning() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } + +ask() { + echo -e "${BLUE}[?]${NC} $1" + read INPUT_VAR + eval "$2=\$INPUT_VAR" +} + +ask_enter() { + echo -e "${BLUE}[?]${NC} $1" + read DUMMY +} + +# ============================================================ +# 检查 root 权限 +# ============================================================ +check_root() { + if [ "$EUID" -ne 0 ]; then + error "请使用 root 用户运行此脚本" + fi +} + +# ============================================================ +# 检查 Debian 系统 +# ============================================================ +check_debian() { + if [ ! -f /etc/debian_version ]; then + error "此脚本仅支持 Debian 系统" + fi + DEBIAN_VER=$(cat /etc/debian_version | cut -d. -f1) + info "检测到 Debian 版本:$(cat /etc/debian_version)" + if [ "$DEBIAN_VER" -lt 10 ] 2>/dev/null; then + warning "建议使用 Debian 10 或更高版本,当前版本可能存在兼容性问题" + fi +} + +# ============================================================ +# 欢迎信息 +# ============================================================ +print_banner() { + clear + echo -e "${CYAN}" + echo "======================================================" + echo " VLESS + WebSocket + TLS + Cloudflare CDN" + echo " 一键部署脚本 (Debian 专版)" + echo "======================================================" + echo -e "${NC}" + echo "" + warning "部署前请确认:" + echo " 1. 子域名已在 Cloudflare 添加 A 记录指向本机 IP" + echo " 2. Cloudflare 代理已关闭(灰色云朵)用于申请证书" + echo " 3. Cloudflare SSL/TLS 模式已设为 完全(Full) 或 完全严格" + echo "" + ask_enter "确认以上条件已满足?按 Enter 继续,Ctrl+C 退出..." +} + +# ============================================================ +# 收集用户输入 +# ============================================================ +collect_info() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "开始收集配置信息..." + echo "" + + # 子域名输入(只需输入前缀) + while true; do + ask "请输入子域名前缀(例如输入 hk01,域名将为 hk01.${BASE_DOMAIN}):" SUBDOMAIN + if echo "$SUBDOMAIN" | grep -qE '^[a-zA-Z0-9-]+$'; then + DOMAIN="${SUBDOMAIN}.${BASE_DOMAIN}" + info "完整域名:$DOMAIN" + break + else + warning "子域名只能包含字母、数字和连字符,请重新输入" + fi + done + + # 邮箱(回车使用默认值) + echo "" + ask "请输入邮箱地址(直接回车使用预设值:${PRESET_EMAIL}):" INPUT_EMAIL + EMAIL=${INPUT_EMAIL:-$PRESET_EMAIL} + info "使用邮箱:$EMAIL" + + # UUID + echo "" + ask "请输入 UUID(直接回车使用预设值:${PRESET_UUID}):" INPUT_UUID + UUID=${INPUT_UUID:-$PRESET_UUID} + + # WS 路径 + ask "请输入 WebSocket 路径(直接回车使用预设值:${PRESET_WS_PATH}):" INPUT_WS_PATH + if [ -z "$INPUT_WS_PATH" ]; then + WS_PATH="$PRESET_WS_PATH" + else + WS_PATH="$INPUT_WS_PATH" + case "$WS_PATH" in + /*) ;; + *) WS_PATH="/$WS_PATH" ;; + esac + fi + + # sing-box 内部端口 + ask "请输入 sing-box 本地监听端口(直接回车使用预设值:${PRESET_SING_PORT}):" INPUT_PORT + SING_PORT=${INPUT_PORT:-$PRESET_SING_PORT} + + # 对外端口 + ask "请输入对外监听端口(直接回车使用预设值:${PRESET_NGINX_PORT}):" INPUT_NGINX_PORT + NGINX_PORT=${INPUT_NGINX_PORT:-$PRESET_NGINX_PORT} + + # 确认信息 + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "配置信息确认:" + echo "" + echo " 完整域名: $DOMAIN" + echo " 邮箱: $EMAIL" + echo " UUID: $UUID" + echo " WS 路径: $WS_PATH" + echo " sing-box 端口: $SING_PORT (本地内部)" + echo " 对外端口: $NGINX_PORT (客户端连接)" + echo "" + ask "确认以上信息正确?直接回车继续,输入 n 重新填写:" CONFIRM + if [ "$CONFIRM" = "n" ] || [ "$CONFIRM" = "N" ]; then + collect_info + fi +} + +# ============================================================ +# 修复 APT 源(兼容 Debian 11/12) +# ============================================================ +fix_apt_sources() { + echo "" + info "检查并修复 APT 源..." + + DEBIAN_VER=$(cat /etc/debian_version | cut -d. -f1) + + if [ "$DEBIAN_VER" = "11" ]; then + warning "检测到 Debian 11,修复 backports 源..." + # 修复 sources.list 里的 backports + sed -i 's|deb.debian.org/debian bullseye-backports|archive.debian.org/debian bullseye-backports|g' \ + /etc/apt/sources.list 2>/dev/null || true + # 注释掉 sources.list.d 里失效的 backports + if ls /etc/apt/sources.list.d/*.list >/dev/null 2>&1; then + sed -i '/bullseye-backports/s/^/#/' /etc/apt/sources.list.d/*.list 2>/dev/null || true + fi + success "Debian 11 源修复完成" + elif [ "$DEBIAN_VER" = "12" ]; then + success "Debian 12 源正常,无需修复" + else + info "Debian 版本:$DEBIAN_VER,跳过源修复" + fi +} + +# ============================================================ +# 安装依赖(Debian 专用) +# ============================================================ +install_deps() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "更新系统并安装依赖..." + + rm -f /var/lib/dpkg/lock-frontend + rm -f /var/lib/apt/lists/lock + + apt-get update -qq + apt-get install -y -qq \ + nginx \ + certbot \ + python3-certbot-nginx \ + curl \ + wget \ + openssl \ + cron + + systemctl enable cron + systemctl start cron + + success "依赖安装完成" +} + +# ============================================================ +# 安装 sing-box +# ============================================================ +install_singbox() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "安装 sing-box..." + + if command -v sing-box >/dev/null 2>&1; then + CURRENT_VER=$(sing-box version | head -1) + warning "sing-box 已安装:$CURRENT_VER" + ask "是否重新安装最新版?(y/N):" REINSTALL + if [ "$REINSTALL" != "y" ] && [ "$REINSTALL" != "Y" ]; then + success "跳过安装,使用现有版本" + return + fi + fi + + ARCH=$(uname -m) + case "$ARCH" in + x86_64) SB_ARCH="amd64" ;; + aarch64) SB_ARCH="arm64" ;; + armv7l) SB_ARCH="armv7" ;; + *) SB_ARCH="amd64" ;; + esac + + SB_VER=$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \ + | grep '"tag_name"' | cut -d'"' -f4 | sed 's/^v//') + + if [ -z "$SB_VER" ]; then + error "无法获取 sing-box 最新版本,请检查网络连接" + fi + + info "下载 sing-box v${SB_VER} (${SB_ARCH})..." + DEB_URL="https://github.com/SagerNet/sing-box/releases/download/v${SB_VER}/sing-box_${SB_VER}_linux_${SB_ARCH}.deb" + TMP_DEB="/tmp/sing-box.deb" + + if ! curl -fsSL -o "$TMP_DEB" "$DEB_URL"; then + error "sing-box 下载失败,请检查网络或手动安装" + fi + + dpkg -i "$TMP_DEB" + rm -f "$TMP_DEB" + + if command -v sing-box >/dev/null 2>&1; then + success "sing-box 安装成功:$(sing-box version | head -1)" + else + error "sing-box 安装失败,请检查网络连接" + fi +} + +# ============================================================ +# 配置 sing-box +# ============================================================ +configure_singbox() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "配置 sing-box..." + + mkdir -p /etc/sing-box + + cat > /etc/sing-box/config.json << EOF +{ + "log": { + "level": "error", + "timestamp": true + }, + "inbounds": [ + { + "type": "vless", + "listen": "127.0.0.1", + "listen_port": ${SING_PORT}, + "users": [ + { + "uuid": "${UUID}" + } + ], + "transport": { + "type": "ws", + "path": "${WS_PATH}" + } + } + ], + "outbounds": [ + { + "type": "direct" + } + ] +} +EOF + + if sing-box check -c /etc/sing-box/config.json; then + success "sing-box 配置验证通过" + else + error "sing-box 配置有误,请检查" + fi + + systemctl enable sing-box + systemctl restart sing-box + sleep 2 + + if systemctl is-active --quiet sing-box; then + success "sing-box 启动成功,监听 127.0.0.1:${SING_PORT}" + else + error "sing-box 启动失败:$(systemctl status sing-box | tail -3)" + fi +} + +# ============================================================ +# 申请 SSL 证书 +# ============================================================ +request_cert() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "申请 SSL 证书..." + + SERVER_IP=$(curl -4 -s --connect-timeout 5 ifconfig.me 2>/dev/null) + DOMAIN_IP=$(getent hosts "$DOMAIN" 2>/dev/null | awk '{print $1}' | head -1) + + info "本机 IP:${SERVER_IP:-获取失败}" + info "域名解析 IP:${DOMAIN_IP:-解析失败}" + + if [ -z "$SERVER_IP" ] || [ -z "$DOMAIN_IP" ]; then + warning "IP 获取失败,请检查网络连接" + elif [ "$SERVER_IP" != "$DOMAIN_IP" ]; then + warning "IP 不一致!请确认:" + warning " 1. Cloudflare 中 $DOMAIN 的 A 记录已指向 ${SERVER_IP}" + warning " 2. Cloudflare 已切换为灰色云朵(仅DNS)" + ask "是否仍然继续申请?(y/N):" FORCE + if [ "$FORCE" != "y" ] && [ "$FORCE" != "Y" ]; then + error "请修复域名解析后重新运行脚本" + fi + else + success "域名解析验证通过:$DOMAIN → $SERVER_IP" + fi + + systemctl stop nginx 2>/dev/null || true + sleep 1 + + certbot certonly \ + --standalone \ + --non-interactive \ + --agree-tos \ + --email "$EMAIL" \ + -d "$DOMAIN" + + # 证书申请完成后重启 nginx(后续 configure_nginx 会再次配置) + systemctl start nginx 2>/dev/null || true + + CERT_PATH="/etc/letsencrypt/live/$DOMAIN" + if [ -f "$CERT_PATH/fullchain.pem" ]; then + CERT_EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_PATH/fullchain.pem" | cut -d= -f2) + success "SSL 证书申请成功!有效期至:$CERT_EXPIRY" + else + error "证书申请失败,请检查:1.域名解析 2.80端口是否开放 3.Cloudflare是否灰色云朵" + fi +} + +# ============================================================ +# 配置 Nginx +# ============================================================ +configure_nginx() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "配置 Nginx 反向代理..." + + cat > /var/www/html/index.html << 'HTMLEOF' + + + + Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and working.

+ + +HTMLEOF + + rm -f /etc/nginx/sites-enabled/default + + cat > /etc/nginx/sites-available/sing-box << EOF +server { + listen 80; + server_name ${DOMAIN}; + return 301 https://\$host\$request_uri; +} + +server { + listen ${NGINX_PORT} ssl; + server_name ${DOMAIN}; + + ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # 伪装页面 + location / { + root /var/www/html; + index index.html; + } + + # WebSocket 代理 + location ${WS_PATH} { + proxy_pass http://127.0.0.1:${SING_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; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + proxy_buffering off; + } +} +EOF + + ln -sf /etc/nginx/sites-available/sing-box /etc/nginx/sites-enabled/ + + if nginx -t 2>/dev/null; then + success "Nginx 配置验证通过" + else + nginx -t + error "Nginx 配置有误" + fi + + systemctl enable nginx + systemctl start nginx + + if systemctl is-active --quiet nginx; then + success "Nginx 启动成功,监听端口 ${NGINX_PORT}" + else + error "Nginx 启动失败:$(systemctl status nginx | tail -3)" + fi +} + +# ============================================================ +# 修复:将续期认证方式从 standalone 切换为 nginx 插件 +# 原因:request_cert() 用 --standalone 申请证书时会临时停掉 nginx, +# 所以首次申请没问题;但 nginx 部署完成后会常驻占用 80 端口, +# 之后 certbot renew 若仍尝试用 standalone 方式验证,会因为 +# 无法绑定 80 端口而必定失败(此时 nginx 正在跑,不会被停掉)。 +# 这里把 renewal 配置里的 authenticator 改成 nginx 插件, +# 这样续期时不需要抢占 80 端口,nginx 可以一直开着。 +# ============================================================ +fix_renew_authenticator() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "修正证书续期认证方式(standalone → nginx 插件)..." + + RENEWAL_CONF="/etc/letsencrypt/renewal/${DOMAIN}.conf" + + if [ ! -f "$RENEWAL_CONF" ]; then + warning "未找到续期配置文件 $RENEWAL_CONF,跳过修正" + return + fi + + sed -i 's/^authenticator = .*/authenticator = nginx/' "$RENEWAL_CONF" + if grep -q '^installer = ' "$RENEWAL_CONF"; then + sed -i 's/^installer = .*/installer = nginx/' "$RENEWAL_CONF" + else + sed -i '/^\[renewalparams\]/a installer = nginx' "$RENEWAL_CONF" + fi + + success "已将 $DOMAIN 的续期认证方式改为 nginx 插件" +} + +# ============================================================ +# 配置证书自动续期 +# ============================================================ +setup_auto_renew() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "配置证书自动续期..." + + mkdir -p /var/log + touch /var/log/certbot-renew.log + + if certbot renew --dry-run >> /var/log/certbot-renew.log 2>&1; then + success "续期测试通过(nginx 插件方式,nginx 无需停止)" + else + warning "续期测试未通过!详情见 /var/log/certbot-renew.log,请务必手动排查" + warning "常见原因:nginx 配置里找不到对应 server_name,或 80 端口未开放" + fi + + # 用 --deploy-hook 而不是 &&:只有真正发生续期时才 reload nginx, + # 并把输出写入日志文件,方便日后排查是否续期失败 + cat > /etc/cron.d/certbot-renew << CRONEOF +# Let's Encrypt 证书自动续期 - 每周一凌晨3点执行 +0 3 * * 1 root certbot renew --deploy-hook "systemctl reload nginx" >> /var/log/certbot-renew.log 2>&1 +CRONEOF + + chmod 644 /etc/cron.d/certbot-renew + systemctl restart cron + + success "已配置自动续期定时任务(每周一凌晨 3 点,日志见 /var/log/certbot-renew.log)" +} + +# ============================================================ +# 最终验证 +# ============================================================ +verify_services() { + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + info "验证服务状态..." + + if systemctl is-active --quiet sing-box; then + success "sing-box 运行中 ✓" + else + warning "sing-box 未运行,尝试重启..." + systemctl restart sing-box + fi + + if systemctl is-active --quiet nginx; then + success "nginx 运行中 ✓" + else + warning "nginx 未运行,尝试重启..." + systemctl restart nginx + fi + + if ss -tlnp | grep -q ":${NGINX_PORT}"; then + success "端口 ${NGINX_PORT} 监听正常 ✓" + else + warning "端口 ${NGINX_PORT} 未监听,请检查 nginx 配置" + fi + + if ss -tlnp | grep -q "127.0.0.1:${SING_PORT}"; then + success "sing-box 端口 ${SING_PORT} 监听正常 ✓" + else + warning "sing-box 端口 ${SING_PORT} 未监听,请检查配置" + fi +} + +# ============================================================ +# 打印最终结果 +# ============================================================ +print_result() { + WS_PATH_ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${WS_PATH}', safe='/'))" 2>/dev/null || echo "${WS_PATH}") + VLESS_LINK="vless://${UUID}@${DOMAIN}:${NGINX_PORT}?encryption=none&security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=${WS_PATH_ENCODED}#VLESS-WS-TLS" + + echo "" + echo -e "${GREEN}" + echo "======================================================" + echo " 部署完成!" + echo "======================================================" + echo -e "${NC}" + + echo -e "${CYAN}------------------------------------------------------${NC}" + echo -e "${GREEN}客户端配置参数:${NC}" + echo "" + echo " 协议: VLESS" + echo " 地址: $DOMAIN" + echo " 端口: $NGINX_PORT" + echo " UUID: $UUID" + echo " 传输方式: WebSocket (ws)" + echo " WS 路径: $WS_PATH" + echo " WS Host: $DOMAIN" + echo " TLS: 开启" + echo " SNI: $DOMAIN" + echo " 指纹: chrome" + echo " REALITY: 关闭" + echo " 允许不安全: 关闭" + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + echo -e "${YELLOW}VLESS 链接(可直接导入客户端):${NC}" + echo "" + echo "$VLESS_LINK" + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + echo -e "${YELLOW}请在 Cloudflare 确认:${NC}" + echo " 1. 橙色云朵已开启(已代理)" + echo " 2. SSL/TLS 模式:完全(Full) 或 完全严格" + echo "" + echo -e "${CYAN}------------------------------------------------------${NC}" + echo -e "${YELLOW}常用管理命令:${NC}" + echo " 查看 sing-box 状态: systemctl status sing-box" + echo " 查看 nginx 状态: systemctl status nginx" + echo " 查看 sing-box 日志: journalctl -u sing-box -f" + echo " 手动续期证书: certbot renew" + echo " 查看续期日志: cat /var/log/certbot-renew.log" + echo " 配置文件位置:" + echo " sing-box: /etc/sing-box/config.json" + echo " nginx: /etc/nginx/sites-available/sing-box" + echo " 证书: /etc/letsencrypt/live/$DOMAIN/" + echo " 续期任务: /etc/cron.d/certbot-renew" + echo " 续期日志: /var/log/certbot-renew.log" + echo "" + + CONFIG_FILE="/root/vless-config.txt" + cat > "$CONFIG_FILE" << EOF +VLESS + WebSocket + TLS + Cloudflare CDN 配置 +生成时间:$(date) +系统:Debian $(cat /etc/debian_version) + +域名: $DOMAIN +端口: $NGINX_PORT +UUID: $UUID +WS 路径: $WS_PATH +TLS: 开启 +SNI: $DOMAIN +指纹: chrome + +VLESS 链接: +$VLESS_LINK + +文件路径: + sing-box 配置:/etc/sing-box/config.json + nginx 配置: /etc/nginx/sites-available/sing-box + 证书: /etc/letsencrypt/live/$DOMAIN/ + 续期任务: /etc/cron.d/certbot-renew +EOF + success "配置已保存至 $CONFIG_FILE" +} + +# ============================================================ +# 主流程 +# ============================================================ +main() { + check_root + check_debian + print_banner + collect_info + fix_apt_sources + install_deps + install_singbox + configure_singbox + request_cert + configure_nginx + fix_renew_authenticator + setup_auto_renew + verify_services + print_result +} + +main