• 用法示例

    1. 仅申请证书:
      bash ssl.sh -d demo.com
    2. 配置Nginx(自动检查证书,不存在则申请):
      bash ssl.sh -nginx demo.com
      bash ssl.sh -nginx demo.com 8080
      bash ssl.sh -nginx demo.com -wroot /custom/path
      bash ssl.sh -nginx demo.com 8080 -wroot /custom/path
  • 网站&证书一键脚本

    #!/bin/bash
    
    # SSL证书自动化安装脚本
    # 功能:参数校验 → ACME验证配置 → 证书申请 → Nginx配置生成 → 自动清理
    
    SCRIPT_PATH="$(realpath "$0")"
    
    # 常量定义
    NGINX_CONF="/etc/nginx/sites-available/default"
    CONF_DIR="/etc/nginx/conf.d"
    ACME_DIR="/ssl/acme-challenge"
    SSL_BASE="/ssl"
    
    # 清理函数
    cleanup() {
    echo -e "\033[33m\n[清理] 正在删除脚本文件...\033[0m"
    [ -f "$SCRIPT_PATH" ] && rm -f "$SCRIPT_PATH"
    exit 0
    }
    
    # 帮助信息
    show_help() {
    echo -e "\033[36m▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄\033[0m"
    echo -e "\033[36m█ SSL证书自动化脚本             █\033[0m"
    echo -e "\033[36m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\033[0m"
    echo -e "\033[32m\n用法示例:\033[0m"
    echo "  1. 仅申请证书:"
    echo "    bash $0 -d demo.com"
    echo "  2. 配置Nginx(自动检查证书,不存在则申请):"
    echo "    bash $0 -nginx demo.com"
    echo "    bash $0 -nginx demo.com 8080"
    echo "    bash $0 -nginx demo.com -wroot /custom/path"
    echo "    bash $0 -nginx demo.com 8080 -wroot /custom/path"
    exit 0
    }
    
    # 检查证书是否存在
    check_cert_exists() {
    local domain=$1
    local ssl_dir="${SSL_BASE}/${domain}"
    if [ -f "$ssl_dir/cert.crt" ] && [ -f "$ssl_dir/private.key" ]; then
        return 0
    else
        return 1
    fi
    }
    
    # 自动检测PHP-FPM版本
    detect_php_fpm_sock() {
    local php_versions=$(ls /run/php/php*-fpm.sock 2>/dev/null | grep -oP 'php\d+\.\d+' | sort -r)
    
    if [ -z "$php_versions" ]; then
        echo -e "\033[31m错误:未找到运行的PHP-FPM服务\033[0m" >&2
        return 1
    fi
    
    local latest_php=$(echo "$php_versions" | head -n1)
    echo "/run/php/${latest_php}-fpm.sock"
    }
    
    # 清理ACME验证配置和目录
    cleanup_acme_config() {
    echo -e "\033[34m[清理] 移除ACME验证配置和目录...\033[0m"
    
    # 删除ACME验证配置
    if grep -q "#ACME验证配置开始" "$NGINX_CONF"; then
        sed -i '/#ACME验证配置开始/,/#ACME验证配置结束/d' "$NGINX_CONF"
        echo -e "\033[32m[成功] 已从Nginx配置中移除ACME验证配置\033[0m"
        
        # 测试并重载Nginx
        if nginx -t >/dev/null 2>&1; then
            systemctl reload nginx
            echo -e "\033[32m[成功] Nginx配置已重载\033[0m"
        else
            echo -e "\033[31m错误:Nginx配置测试失败\033[0m" >&2
            nginx -t
            return 1
        fi
    else
        echo -e "\033[33m[跳过] Nginx中未找到ACME验证配置\033[0m"
    fi
    
    # 删除ACME目录
    if [ -d "$ACME_DIR" ]; then
        rm -rf "$ACME_DIR"
        echo -e "\033[32m[成功] 已删除ACME验证目录: $ACME_DIR\033[0m"
    else
        echo -e "\033[33m[跳过] ACME验证目录不存在: $ACME_DIR\033[0m"
    fi
    }
    
    # 参数解析
    DOMAIN=""
    NGINX_MODE=false
    NGINX_PORT=""
    WEB_ROOT="/www/demo.com"  # 默认网站根目录
    while [[ $# -gt 0 ]]; do
    case "$1" in
        -d)
            if [ -z "$2" ] || [[ "$2" == -* ]]; then
                echo -e "\033[31m错误:-d 参数后必须指定域名\033[0m" >&2
                exit 0
            fi
            DOMAIN="$2"
            shift 2
            ;;
        -nginx)
            NGINX_MODE=true
            if [[ "$2" != -* ]] && [[ "$2" != "" ]]; then
                DOMAIN="$2"
                if [[ "$3" =~ ^[0-9]+$ ]]; then
                    NGINX_PORT="$3"
                    shift 3
                elif [[ "$3" == "-wroot" ]]; then
                    if [ -z "$4" ] || [[ "$4" == -* ]]; then
                        echo -e "\033[31m错误:-wroot 参数后必须指定路径\033[0m" >&2
                        exit 0
                    fi
                    WEB_ROOT="$4"
                    shift 4
                else
                    shift 2
                fi
            else
                echo -e "\033[31m错误:-nginx 参数后必须指定域名\033[0m" >&2
                exit 0
            fi
            ;;
        -wroot)
            if [ "$NGINX_MODE" = false ]; then
                echo -e "\033[31m错误:-wroot 参数只能在 -nginx 模式下使用\033[0m" >&2
                exit 0
            fi
            if [ -z "$2" ] || [[ "$2" == -* ]]; then
                echo -e "\033[31m错误:-wroot 参数后必须指定路径\033[0m" >&2
                exit 0
            fi
            WEB_ROOT="$2"
            shift 2
            ;;
        -help|--help|-h|--h)
            show_help
            ;;
        *)
            echo -e "\033[31m错误:未知参数 '$1'\033[0m" >&2
            exit 0
            ;;
    esac
    done
    
    # 验证参数
    if [ -z "$DOMAIN" ]; then
    show_help
    fi
    
    # 替换默认域名路径中的demo.com为实际域名
    if [ "$WEB_ROOT" = "/www/demo.com" ]; then
    WEB_ROOT="/www/${DOMAIN}"
    fi
    
    # 申请证书函数
    request_certificate() {
    local domain=$1
    
    echo -e "\n\033[36m[阶段] 配置ACME验证\033[0m"
    
    # 检查是否已存在配置
    if ! grep -q "acme-challenge" "$NGINX_CONF"; then
        echo -e "\033[34m[1/3] 添加ACME验证配置...\033[0m"
        
        mkdir -p "$ACME_DIR" || {
            echo -e "\033[31m错误:无法创建验证目录 $ACME_DIR\033[0m" >&2
            exit 1
        }
        chown -R www-data:www-data "$ACME_DIR"
    
        TEMP_FILE=$(mktemp) || {
            echo -e "\033[31m错误:无法创建临时文件\033[0m" >&2
            exit 1
        }
    
        cat > "$TEMP_FILE" << EOF
    #ACME验证配置开始
    location ^~ /.well-known/acme-challenge {
        allow all;
        root $ACME_DIR;
    }
    #ACME验证配置结束
    EOF
    
        if ! sed -i '/server_name _;/r '"$TEMP_FILE" "$NGINX_CONF"; then
            echo -e "\033[31m错误:无法修改Nginx配置\033[0m" >&2
            rm -f "$TEMP_FILE"
            exit 1
        fi
        rm -f "$TEMP_FILE"
    
        if nginx -t >/dev/null 2>&1; then
            systemctl reload nginx
            echo -e "\033[32m[成功] ACME验证配置已生效\033[0m"
        else
            echo -e "\033[31m错误:Nginx配置测试失败\033[0m" >&2
            echo "查看错误:"
            nginx -t
            cleanup_acme_config
            exit 1
        fi
    else
        echo -e "\033[33m[跳过] ACME验证配置已存在\033[0m"
    fi
    
    # 申请证书
    echo -e "\n\033[36m[阶段] 申请SSL证书\033[0m"
    SSL_DIR="${SSL_BASE}/${domain}"
    mkdir -p "$SSL_DIR" || {
        echo -e "\033[31m错误:无法创建证书目录 $SSL_DIR\033[0m" >&2
        exit 1
    }
    
    echo -e "\033[34m[2/3] 正在申请证书...\033[0m"
    if ! ~/.acme.sh/acme.sh --force --issue -d "$domain" -w "$ACME_DIR"; then
        echo -e "\033[31m证书申请失败!可能原因:\033[0m" >&2
        echo "1. 域名解析未生效(请检查DNS设置)"
        echo "2. 服务器80端口被占用"
        echo "3. 已有相同域名的证书"
        cleanup_acme_config
        exit 1
    fi
    
    # 安装证书
    echo -e "\033[34m[3/3] 安装证书到指定目录...\033[0m"
    if ! ~/.acme.sh/acme.sh --installcert -d "$domain" \
        --key-file "$SSL_DIR/private.key" \
        --fullchain-file "$SSL_DIR/cert.crt"; then
        echo -e "\033[31m证书安装失败!\033[0m" >&2
        cleanup_acme_config
        exit 1
    fi
    
    chmod 600 "$SSL_DIR/private.key"
    chmod 644 "$SSL_DIR/cert.crt"
    echo -e "\033[32m[成功] 证书已安装到:\033[0m"
    echo -e "私钥:\033[33m$SSL_DIR/private.key\033[0m"
    echo -e "证书:\033[33m$SSL_DIR/cert.crt\033[0m"
    
    # 证书申请成功后清理ACME配置和目录
    cleanup_acme_config
    }
    
    # 配置Nginx函数
    configure_nginx() {
    local domain=$1
    local port=$2
    
    echo -e "\n\033[36m[阶段] 生成Nginx站点配置\033[0m"
    
    SSL_DIR="${SSL_BASE}/${domain}"
    if [ -z "$port" ]; then
        # PHP站点配置
        CONF_FILE="${CONF_DIR}/php-${domain}.conf"
        if [ -f "$CONF_FILE" ]; then
            echo -e "\033[33m[跳过] PHP配置已存在:$CONF_FILE\033[0m"
        else
            echo -e "\033[34m[1/1] 生成PHP站点配置...\033[0m"
            PHP_SOCK=$(detect_php_fpm_sock) || exit 1
            cat > "$CONF_FILE" << EOF
    server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name ${domain};
    root ${WEB_ROOT};
    index index.php index.html index.htm;
    
    # SSL配置
    ssl_certificate $SSL_DIR/cert.crt;
    ssl_certificate_key $SSL_DIR/private.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers on;
    
    # 80重定向到443
    if (\$scheme = http) {
        return 301 https://\$host\$request_uri;
    }
    
    # 如果请求的文件存在,直接返回该文件
    #location / {
    #try_files \$uri \$uri/ /index.php?\$query_string;
    #}
    
    # PHP配置    
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:${PHP_SOCK};
    }
    }
    EOF
            echo -e "\033[32m[成功] 已创建:$CONF_FILE\033[0m"
            echo -e "\033[33m网站根目录设置为:${WEB_ROOT}\033[0m"
        fi
    else
        # 反向代理配置
        CONF_FILE="${CONF_DIR}/proxy-${domain}.conf"
        if [ -f "$CONF_FILE" ]; then
            echo -e "\033[33m[跳过] 代理配置已存在:$CONF_FILE\033[0m"
        else
            echo -e "\033[34m[1/1] 生成反向代理配置...\033[0m"
            cat > "$CONF_FILE" << EOF
    server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name ${domain};
    #root ${WEB_ROOT};
    
    # SSL配置
    ssl_certificate $SSL_DIR/cert.crt;
    ssl_certificate_key $SSL_DIR/private.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers on;
    
    # 80重定向到443
    if (\$scheme = http) {
        return 301 https://\$host\$request_uri;
    }
    
    location / {
        proxy_pass http://127.0.0.1:${port};
        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 REMOTE-HOST \$remote_addr; 
        proxy_set_header Upgrade \$http_upgrade; 
        proxy_set_header Connection \$http_connection; 
        proxy_set_header X-Forwarded-Proto \$scheme; 
        proxy_http_version 1.1; 
        add_header X-Cache \$upstream_cache_status; 
        add_header Strict-Transport-Security "max-age=31536000"; 
    }
    }
    EOF
            echo -e "\033[32m[成功] 已创建:$CONF_FILE (代理到端口 ${port})\033[0m"
        fi
    fi
    
    # 测试并重载Nginx
    if nginx -t >/dev/null 2>&1; then
        systemctl reload nginx
        echo -e "\033[32m[成功] Nginx配置已生效\033[0m"
    else
        echo -e "\033[31m警告:Nginx配置测试失败,请手动检查!\033[0m" >&2
        nginx -t
        rm -rf $CONF_FILE
        echo -e "\033[31m警告:已删除:$CONF_FILE\033[0m"
        cleanup_acme_config
        exit 1
    fi
    }
    
    # 主逻辑
    SSL_DIR="${SSL_BASE}/${DOMAIN}"
    
    # 检查证书是否存在
    if check_cert_exists "$DOMAIN"; then
    echo -e "\033[32m[检测] 发现已有证书\033[0m"
    CERT_EXISTS=true
    else
    echo -e "\033[33m[检测] 未找到现有证书\033[0m"
    CERT_EXISTS=false
    fi
    
    # 功能1:仅申请证书
    if [ -n "$DOMAIN" ] && [ "$NGINX_MODE" = false ]; then
    if $CERT_EXISTS; then
        echo -e "\033[33m[提示] 证书已存在,强制更新\033[0m"
        request_certificate "$DOMAIN"
        systemctl reload nginx
    else
        request_certificate "$DOMAIN"
        systemctl reload nginx
    fi
    fi
    
    # 功能2:配置Nginx(如有需要先申请证书)
    if [ "$NGINX_MODE" = true ]; then
    if ! $CERT_EXISTS; then
        echo -e "\033[33m[流程] 未找到证书,将先申请证书\033[0m"
        request_certificate "$DOMAIN"
    fi
    configure_nginx "$DOMAIN" "$NGINX_PORT"
    fi
    
    # 完成输出
    echo -e "\n\033[36m▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄\033[0m"
    echo -e "\033[36m█ 所有操作已完成!              █\033[0m"
    echo -e "\033[36m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\033[0m"
    
    # 删除脚本
    cleanup