Let’s Encrypt 90天免费证书自动申请脚本

2025年12月2日

原创内容,转载请注明出处:https://www.myzhenai.com.cn/post/4869.html

我们博客的服务器是阿里云的,所以我以前一直都使用阿里云的个人免费证书,一年有20个证书

原本个人免费证书是一年的,后面改成90天,

但是我今天发现我的证书过期了,并且我一年20个证书的额度也用完了

怎么办?

我知道有acme.sh和certbot可以自动安装证书

在配置脚本之前,我们先要确定自己需要以哪种方式进行验证,

我们知道,在验证证书的时候有三种方式进行验证,分别是dns验证、文件验证和网站代码验证

我们经常使用的是dns验证,就是你要在你的dns记录中添加验证信息,文件验证是打开一个文件来验证你是网站的实际控制者

因为有些朋友不一定懂得怎样设置dns记录,所以我们本文分别是两种验证方式

如果你选择dns记录验证,那么就要做一些设置,例如阿里云,你需要一个可以检测dns的AccessKey IDAccessKey Secret

怎样获得这两个参数呢?

1、登录阿里云官网(https://www.aliyun.com/),使用你域名的管理账号

2、创建一个仅拥有「DNS 管理权限」的子账号,避免泄露主账号全部权限

3、点击右上角用户头像那里,点击“访问控制”

 

4、在RAM访问控制面板左侧,点击用户,创建用户,勾选 “使用永久 AccessKey 访问创建 AccessKey ID 和 AccessKey Secret,支持通过 API 或其他开发工具访问” 选项

 

 

5、获取 AccessKey ID 和 Secret(最重要!)

创建成功后,会弹出「AccessKey 创建成功」提示框,显示:

AccessKey ID:一串字母 + 数字(如 LTAI5txxxxxxxxxxxxxxx

AccessKey Secret:一串随机字符(如 8txxxxxxxxxxxxxxxxxxxxxxxx

务必:

复制这两个值保存到本地(Secret 仅显示这一次,后续无法查看)

不要泄露给他人,泄露后立即在 IAM 控制台禁用该 AccessKey

 

6、权限策略搜索框输入「AliyunDNSFullAccess」,勾选该策略(仅这一个即可,无需其他权限)

 

那么现在我们就可以来实现这两个脚本的安装证书实现方法,两个脚本使用一个就可以

acme.sh

#!/bin/bash
# 功能:申请Let's Encrypt 90天免费证书,支持HTTP/DNS验证,自动更新&复制证书
# 依赖:acme.sh(自动检测安装)、curl/wget(acme.sh安装依赖)

##############################################################################
# 1. 检查并安装acme.sh(若未安装)
##############################################################################
if ! command -v acme.sh &> /dev/null; then
    echo "未检测到acme.sh,开始自动安装..."
    # 官方安装命令(默认安装到 ~/.acme.sh/)
    curl https://get.acme.sh | sh -s email=your-email@example.com
    # 加载acme.sh环境变量(避免重启终端)
    source ~/.bashrc
    echo "acme.sh安装完成!"
fi

##############################################################################
# 2. 读取用户输入(域名、验证方式、网站路径、目标目录)
##############################################################################
# 输入域名
read -p "请输入需要申请证书的域名(例如:example.com 或 www.example.com):" DOMAIN
# 选择验证方式
echo -e "\n请选择证书验证方式:"
echo "1. HTTP检测(需网站可访问,无需修改DNS)"
echo "2. DNS检测(无需网站上线,需手动添加DNS TXT记录)"
read -p "输入选择(1/2):" VALIDATION_METHOD

# 若选择HTTP检测,读取网站根目录
if [ "$VALIDATION_METHOD" = "1" ]; then
    read -p "请输入网站根目录路径(例如:/var/www/html 或 /usr/share/nginx/html):" WEBROOT
    # 验证目录是否存在
    if [ ! -d "$WEBROOT" ]; then
        echo "错误:网站目录 $WEBROOT 不存在!"
        exit 1
    fi
fi

# 输入证书目标目录(复制证书用)
read -p "请输入证书目标存储目录(例如:/etc/nginx/ssl):" TARGET_DIR
# 自动创建目标目录(若不存在)
mkdir -p "$TARGET_DIR" || { echo "错误:无法创建目标目录 $TARGET_DIR!"; exit 1; }

##############################################################################
# 3. 申请Let's Encrypt证书
##############################################################################
echo -e "\n开始申请 $DOMAIN 的证书..."
if [ "$VALIDATION_METHOD" = "1" ]; then
    # HTTP验证方式(--webroot 模式,acme.sh会在网站目录下创建.well-known目录)
    acme.sh --issue -d "$DOMAIN" --webroot -w "$WEBROOT" --server letsencrypt
elif [ "$VALIDATION_METHOD" = "2" ]; then
    # DNS验证方式(--dns 模式,需手动添加TXT记录,按提示操作)
    acme.sh --issue -d "$DOMAIN" --dns --server letsencrypt
else
    echo "错误:无效的验证方式选择!"
    exit 1
fi

# 验证证书申请是否成功
if [ -f "$HOME/.acme.sh/$DOMAIN/$DOMAIN.key" ] && [ -f "$HOME/.acme.sh/$DOMAIN/fullchain.cer" ]; then
    echo "证书申请成功!"
else
    echo "错误:证书申请失败,请检查操作步骤或网络环境!"
    exit 1
fi

##############################################################################
# 4. 复制证书到目标目录(覆盖旧证书)
##############################################################################
echo -e "\n开始复制证书到目标目录 $TARGET_DIR..."
cp -f "$HOME/.acme.sh/$DOMAIN/$DOMAIN.key" "$TARGET_DIR/$DOMAIN.key"
cp -f "$HOME/.acme.sh/$DOMAIN/fullchain.cer" "$TARGET_DIR/$DOMAIN.crt"
echo "证书复制完成!"
echo "私钥路径:$TARGET_DIR/$DOMAIN.key"
echo "证书链路径:$TARGET_DIR/$DOMAIN.crt"

##############################################################################
# 5. 设置自动更新(每周日凌晨3点检测,有效期<10天则更新)
##############################################################################
# 检查是否已存在定时任务
CRON_JOB="0 3 * * 0 $HOME/.acme.sh/acme.sh --cron --home $HOME/.acme.sh/ --renew-hook \"cp -f $HOME/.acme.sh/$DOMAIN/$DOMAIN.key $TARGET_DIR/ && cp -f $HOME/.acme.sh/$DOMAIN/fullchain.cer $TARGET_DIR/ && echo '$(date +%Y-%m-%d_%H:%M:%S) - $DOMAIN 证书更新成功' >> /var/log/acme_cert_renew.log\""
if ! crontab -l 2>/dev/null | grep -q "$DOMAIN"; then
    # 添加定时任务到crontab
    (crontab -l 2>/dev/null; echo "$CRON_JOB") | crontab -
    echo -e "\n自动更新任务已设置:每周日凌晨3点检测证书有效期,少于10天自动更新并同步到目标目录"
    echo "更新日志路径:/var/log/acme_cert_renew.log"
else
    echo -e "\n该域名的自动更新任务已存在,无需重复设置"
fi

##############################################################################
# 6. 额外完善功能:证书备份(备份到目标目录的backup子目录)
##############################################################################
BACKUP_DIR="$TARGET_DIR/backup"
mkdir -p "$BACKUP_DIR"
cp -f "$TARGET_DIR/$DOMAIN.key" "$BACKUP_DIR/$(date +%Y%m%d)_$DOMAIN.key"
cp -f "$TARGET_DIR/$DOMAIN.crt" "$BACKUP_DIR/$(date +%Y%m%d)_$DOMAIN.crt"
echo -e "\n证书备份完成:$BACKUP_DIR/$(date +%Y%m%d)_$DOMAIN.*"
#!/bin/bash

# ============================================
# acme.sh SSL证书管理脚本
# 功能:申请、检测、更新和部署SSL证书
# ============================================

# 配置常量
CERT_CHECK_INTERVAL="7"  # 检查间隔(天)
CERT_RENEW_THRESHOLD="10" # 续期阈值(天)
CRON_SCHEDULE="0 0 * * 0" # 每周日0点执行

# 颜色输出函数
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

print_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查acme.sh是否安装
check_acme_installed() {
    if command -v acme.sh &> /dev/null; then
        print_info "acme.sh 已安装"
        return 0
    else
        print_warning "acme.sh 未安装,尝试自动安装..."
        
        # 尝试安装acme.sh
        if curl https://get.acme.sh | sh; then
            print_success "acme.sh 安装成功"
            # 重新加载bash配置
            source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null
            return 0
        else
            print_error "acme.sh 安装失败,请手动安装"
            print_info "手动安装命令: curl https://get.acme.sh | sh"
            exit 1
        fi
    fi
}

# 获取用户输入
get_user_input() {
    echo "=================================="
    echo "    SSL证书管理工具 (acme.sh)     "
    echo "=================================="
    
    # 输入域名
    while true; do
        read -p "请输入域名 (多个域名用空格分隔): " domain_input
        if [ -z "$domain_input" ]; then
            print_error "域名不能为空"
        else
            DOMAINS=($domain_input)
            
            # 验证域名格式
            invalid_domains=()
            for domain in "${DOMAINS[@]}"; do
                if ! [[ "$domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$ ]]; then
                    invalid_domains+=("$domain")
                fi
            done
            
            if [ ${#invalid_domains[@]} -eq 0 ]; then
                break
            else
                print_error "以下域名格式无效: ${invalid_domains[*]}"
            fi
        fi
    done
    
    # 选择验证方式
    echo ""
    echo "请选择验证方式:"
    echo "1) HTTP验证 (需要网站可访问)"
    echo "2) DNS验证 (需要修改DNS记录)"
    echo "3) DNS API验证 (支持自动DNS验证)"
    
    while true; do
        read -p "请选择 [1-3]: " verify_method
        case $verify_method in
            1)
                VERIFY_METHOD="http"
                read -p "请输入网站根目录路径 [默认: /var/www/html]: " web_root
                WEB_ROOT=${web_root:-/var/www/html}
                
                # 检查目录是否存在
                if [ ! -d "$WEB_ROOT" ]; then
                    print_warning "目录不存在,将尝试创建: $WEB_ROOT"
                    sudo mkdir -p "$WEB_ROOT"
                fi
                break
                ;;
            2)
                VERIFY_METHOD="dns"
                print_info "请稍后在DNS管理界面添加TXT记录"
                break
                ;;
            3)
                VERIFY_METHOD="dns-api"
                echo ""
                echo "支持的DNS提供商:"
                echo "1) Cloudflare"
                echo "2) Aliyun"
                echo "3) DNSPod"
                echo "4) GoDaddy"
                echo "5) CloudXNS"
                read -p "选择DNS提供商 [1-5]: " dns_provider_num
                
                case $dns_provider_num in
                    1) DNS_PROVIDER="cloudflare" ;;
                    2) DNS_PROVIDER="dns_ali" ;;
                    3) DNS_PROVIDER="dns_dp" ;;
                    4) DNS_PROVIDER="dns_gd" ;;
                    5) DNS_PROVIDER="cloudxns" ;;
                    *) DNS_PROVIDER="cloudflare" ;;
                esac
                
                print_info "请设置DNS API凭证:"
                case $DNS_PROVIDER in
                    cloudflare)
                        read -p "Cloudflare Email: " CF_Email
                        read -p "Cloudflare API Key: " CF_Key
                        export CF_Email="$CF_Email"
                        export CF_Key="$CF_Key"
                        ;;
                    dns_ali)
                        read -p "Aliyun AccessKey ID: " Ali_Key
                        read -p "Aliyun AccessKey Secret: " Ali_Secret
                        export Ali_Key="$Ali_Key"
                        export Ali_Secret="$Ali_Secret"
                        ;;
                esac
                break
                ;;
            *)
                print_error "无效选择"
                ;;
        esac
    done
    
    # 询问证书部署路径
    echo ""
    read -p "请输入证书部署目录 [默认: /etc/ssl/certs]: " deploy_dir
    DEPLOY_DIR=${deploy_dir:-/etc/ssl/certs}
    
    # 创建部署目录
    if [ ! -d "$DEPLOY_DIR" ]; then
        print_info "创建部署目录: $DEPLOY_DIR"
        sudo mkdir -p "$DEPLOY_DIR"
    fi
    
    # 询问是否安装到Nginx/Apache
    read -p "是否自动配置Web服务器? [y/N]: " config_web
    if [[ $config_web =~ ^[Yy]$ ]]; then
        echo "选择Web服务器:"
        echo "1) Nginx"
        echo "2) Apache"
        read -p "请选择 [1-2]: " web_server
    fi
}

# 申请证书
issue_certificate() {
    print_info "开始申请SSL证书..."
    
    # 构建域名参数
    domain_args=""
    for domain in "${DOMAINS[@]}"; do
        if [ "$domain" == "${DOMAINS[0]}" ]; then
            domain_args="-d $domain"
        else
            domain_args="$domain_args -d $domain"
        fi
    done
    
    # 根据验证方式执行不同命令
    case $VERIFY_METHOD in
        http)
            print_info "使用HTTP验证方式"
            acme.sh --issue $domain_args --webroot "$WEB_ROOT" --force
            ;;
        dns)
            print_info "使用DNS验证方式"
            print_info "请按照提示添加DNS TXT记录"
            acme.sh --issue $domain_args --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please --force
            ;;
        dns-api)
            print_info "使用DNS API验证方式 (${DNS_PROVIDER})"
            acme.sh --issue $domain_args --dns "$DNS_PROVIDER" --force
            ;;
    esac
    
    if [ $? -eq 0 ]; then
        print_success "证书申请成功"
        return 0
    else
        print_error "证书申请失败"
        return 1
    fi
}

# 部署证书
deploy_certificate() {
    local main_domain="${DOMAINS[0]}"
    print_info "部署证书到: $DEPLOY_DIR"
    
    # 证书源路径
    CERT_SOURCE="$HOME/.acme.sh/$main_domain"
    
    if [ ! -d "$CERT_SOURCE" ]; then
        print_error "证书目录不存在: $CERT_SOURCE"
        return 1
    fi
    
    # 创建域名子目录
    CERT_DEPLOY_DIR="$DEPLOY_DIR/$main_domain"
    sudo mkdir -p "$CERT_DEPLOY_DIR"
    
    # 复制证书文件
    sudo cp "$CERT_SOURCE/fullchain.cer" "$CERT_DEPLOY_DIR/fullchain.pem"
    sudo cp "$CERT_SOURCE/$main_domain.key" "$CERT_DEPLOY_DIR/privkey.pem"
    sudo cp "$CERT_SOURCE/ca.cer" "$CERT_DEPLOY_DIR/chain.pem"
    
    # 设置权限
    sudo chmod 600 "$CERT_DEPLOY_DIR/privkey.pem"
    sudo chmod 644 "$CERT_DEPLOY_DIR/fullchain.pem"
    sudo chmod 644 "$CERT_DEPLOY_DIR/chain.pem"
    
    print_success "证书已部署到: $CERT_DEPLOY_DIR"
    
    # 生成合并证书(部分服务器需要)
    sudo cat "$CERT_DEPLOY_DIR/fullchain.pem" "$CERT_DEPLOY_DIR/privkey.pem" | sudo tee "$CERT_DEPLOY_DIR/combined.pem" > /dev/null
    
    # 配置Web服务器
    if [[ $config_web =~ ^[Yy]$ ]]; then
        configure_webserver "$main_domain" "$CERT_DEPLOY_DIR"
    fi
    
    return 0
}

# 配置Web服务器
configure_webserver() {
    local domain="$1"
    local cert_path="$2"
    
    case $web_server in
        1) # Nginx
            nginx_conf="/etc/nginx/sites-available/$domain"
            if [ -d "/etc/nginx/sites-available" ]; then
                sudo tee "$nginx_conf" > /dev/null <<EOF
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name $domain;
    
    ssl_certificate $cert_path/fullchain.pem;
    ssl_certificate_key $cert_path/privkey.pem;
    ssl_trusted_certificate $cert_path/chain.pem;
    
    # SSL优化配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    
    # 其他配置...
    location / {
        root /var/www/html;
        index index.html;
    }
}
EOF
                if [ -f "$nginx_conf" ]; then
                    sudo ln -sf "$nginx_conf" "/etc/nginx/sites-enabled/"
                    print_info "Nginx配置已创建,请重启Nginx生效"
                fi
            fi
            ;;
        2) # Apache
            apache_conf="/etc/apache2/sites-available/$domain.conf"
            if [ -d "/etc/apache2/sites-available" ]; then
                sudo tee "$apache_conf" > /dev/null <<EOF
<VirtualHost *:443>
    ServerName $domain
    
    SSLEngine on
    SSLCertificateFile $cert_path/fullchain.pem
    SSLCertificateKeyFile $cert_path/privkey.pem
    SSLCertificateChainFile $cert_path/chain.pem
    
    # 其他配置...
    DocumentRoot /var/www/html
</VirtualHost>
EOF
                if [ -f "$apache_conf" ]; then
                    sudo a2ensite "$domain.conf" > /dev/null 2>&1
                    print_info "Apache配置已创建,请重启Apache生效"
                fi
            fi
            ;;
    esac
}

# 检查证书有效期
check_cert_expiry() {
    local domain="${DOMAINS[0]}"
    local cert_file="$HOME/.acme.sh/$domain/fullchain.cer"
    
    if [ ! -f "$cert_file" ]; then
        print_error "证书文件不存在: $cert_file"
        return 1
    fi
    
    # 获取证书过期时间
    expiry_date=$(openssl x509 -enddate -noout -in "$cert_file" | cut -d= -f2)
    expiry_epoch=$(date -d "$expiry_date" +%s)
    current_epoch=$(date +%s)
    
    # 计算剩余天数
    days_remaining=$(( ($expiry_epoch - $current_epoch) / 86400 ))
    
    echo "=================================="
    echo "证书有效期检查:"
    echo "域名: $domain"
    echo "过期时间: $expiry_date"
    echo "剩余天数: $days_remaining 天"
    
    if [ $days_remaining -le $CERT_RENEW_THRESHOLD ]; then
        print_warning "证书即将过期 (少于 ${CERT_RENEW_THRESHOLD} 天)"
        return 1
    else
        print_success "证书有效期正常"
        return 0
    fi
}

# 更新证书
renew_certificate() {
    local main_domain="${DOMAINS[0]}"
    print_info "开始更新证书..."
    
    # 尝试更新
    acme.sh --renew -d "$main_domain" --force
    
    if [ $? -eq 0 ]; then
        print_success "证书更新成功"
        
        # 重新部署证书
        deploy_certificate
        
        # 重启Web服务器
        restart_webserver
        
        return 0
    else
        print_error "证书更新失败"
        return 1
    fi
}

# 重启Web服务器
restart_webserver() {
    if [[ $config_web =~ ^[Yy]$ ]]; then
        case $web_server in
            1) # Nginx
                if systemctl is-active --quiet nginx; then
                    sudo systemctl reload nginx
                    print_info "Nginx已重新加载配置"
                fi
                ;;
            2) # Apache
                if systemctl is-active --quiet apache2; then
                    sudo systemctl reload apache2
                    print_info "Apache已重新加载配置"
                fi
                ;;
        esac
    fi
}

# 设置定时任务
setup_cron_job() {
    local script_path="$(realpath "$0")"
    local cron_cmd="$script_path --cron"
    
    # 添加定时任务
    (crontab -l 2>/dev/null | grep -v "$script_path"; echo "$CRON_SCHEDULE $cron_cmd") | crontab -
    
    if [ $? -eq 0 ]; then
        print_success "定时任务设置成功 (计划: $CRON_SCHEDULE)"
        print_info "将自动检查并更新证书"
    else
        print_error "定时任务设置失败"
    fi
}

# 主函数
main() {
    # 检查参数
    if [ "$1" == "--cron" ]; then
        # 定时任务模式
        check_cert_expiry
        if [ $? -eq 1 ]; then
            renew_certificate
        fi
        exit 0
    fi
    
    # 交互模式
    check_acme_installed
    get_user_input
    
    # 申请证书
    issue_certificate
    if [ $? -eq 0 ]; then
        # 部署证书
        deploy_certificate
        
        # 检查有效期
        check_cert_expiry
        
        # 设置定时任务
        echo ""
        read -p "是否设置自动更新定时任务? [Y/n]: " setup_cron
        if [[ ! $setup_cron =~ ^[Nn]$ ]]; then
            setup_cron_job
        fi
        
        # 生成报告
        generate_report
    fi
}

# 生成报告
generate_report() {
    echo ""
    echo "=================================="
    echo "          SSL证书部署报告         "
    echo "=================================="
    echo "主域名: ${DOMAINS[0]}"
    echo "所有域名: ${DOMAINS[*]}"
    echo "证书位置: $HOME/.acme.sh/${DOMAINS[0]}/"
    echo "部署位置: $DEPLOY_DIR/${DOMAINS[0]}/"
    echo "验证方式: $VERIFY_METHOD"
    if [ "$VERIFY_METHOD" == "http" ]; then
        echo "网站目录: $WEB_ROOT"
    fi
    echo ""
    echo "文件列表:"
    echo "  - fullchain.pem: 完整证书链"
    echo "  - privkey.pem: 私钥文件"
    echo "  - chain.pem: 中间证书"
    echo "  - combined.pem: 合并证书"
    echo ""
    echo "自动更新: 已设置每周检查,少于${CERT_RENEW_THRESHOLD}天自动更新"
    echo "=================================="
}

# 脚本异常处理
trap 'print_error "脚本执行被中断"; exit 1' INT TERM

# 执行主函数
main "$@"

Certbot 

#!/bin/bash
# 功能:申请Let's Encrypt 90天免费证书,支持HTTP/DNS验证(含自动化DNS插件),自动更新&复制证书
# 依赖:certbot + 对应模块(脚本自动检测并提供安装选项)、Python3

##############################################################################
# 1. 检查并安装Certbot及所需模块
##############################################################################
# 检查Certbot主程序
if ! command -v certbot &> /dev/null; then
    echo "未检测到Certbot,开始安装主程序..."
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        case $ID in
            ubuntu|debian)
                sudo apt update && sudo apt install -y certbot python3-certbot-nginx
                ;;
            centos|rhel)
                sudo yum install -y epel-release && sudo yum install -y certbot python3-certbot-nginx
                ;;
            *)
                echo "未支持的系统,请手动安装Certbot:https://certbot.eff.org/instructions"
                exit 1
                ;;
        esac
    fi
    echo "Certbot主程序安装完成!"
fi

# 选择验证方式(关联模块安装)
echo -e "\n请选择证书验证方式:"
echo "1. HTTP检测(http-01,需网站可访问,依赖 python3-certbot-nginx 模块,已默认安装)"
echo "2. 手动DNS检测(dns-01,无需网站上线,手动添加TXT记录,无需额外模块)"
echo "3. 自动化DNS检测(dns-01,自动添加TXT记录,需安装对应云服务商插件)"
read -p "输入选择(1/2/3):" VALIDATION_METHOD

# 若选择自动化DNS验证,让用户选择云服务商并安装对应模块
DNS_PLUGIN=""
if [ "$VALIDATION_METHOD" = "3" ]; then
    echo -e "\n请选择你的DNS服务商(支持常见云厂商):"
    echo "1. 阿里云(aliyun)"
    echo "2. Cloudflare"
    echo "3. 腾讯云(dnspod)"
    echo "4. AWS Route53"
    read -p "输入选择(1/2/3/4):" DNS_VENDOR

    # 安装对应DNS插件(按系统区分)
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        case $DNS_VENDOR in
            1)
                DNS_PLUGIN="dns-aliyun"
                MODULE_NAME="python3-certbot-dns-aliyun"
                ;;
            2)
                DNS_PLUGIN="dns-cloudflare"
                MODULE_NAME="python3-certbot-dns-cloudflare"
                ;;
            3)
                DNS_PLUGIN="dns-dnspod"
                MODULE_NAME="python3-certbot-dns-dnspod"
                ;;
            4)
                DNS_PLUGIN="dns-route53"
                MODULE_NAME="python3-certbot-dns-route53"
                ;;
            *)
                echo "无效的DNS服务商选择!"
                exit 1
                ;;
        esac

        # 安装插件模块
        if ! dpkg -l | grep -q "$MODULE_NAME" > /dev/null 2>&1; then
            echo "开始安装 $MODULE_NAME 插件..."
            case $ID in
                ubuntu|debian)
                    sudo apt update && sudo apt install -y "$MODULE_NAME"
                    ;;
                centos|rhel)
                    sudo yum install -y "$MODULE_NAME"
                    ;;
            esac
            echo "$MODULE_NAME 插件安装完成!"
        fi
    fi
fi

##############################################################################
# 2. 读取用户输入(域名、网站路径、目标目录、DNS密钥)
##############################################################################
# 输入域名
read -p "请输入需要申请证书的域名(例如:example.com 或 www.example.com):" DOMAIN

# 若选择HTTP检测,读取网站根目录
if [ "$VALIDATION_METHOD" = "1" ]; then
    read -p "请输入网站根目录路径(例如:/var/www/html 或 /usr/share/nginx/html):" WEBROOT
    if [ ! -d "$WEBROOT" ]; then
        echo "错误:网站目录 $WEBROOT 不存在!"
        exit 1
    fi
fi

# 若选择自动化DNS检测,读取云服务商密钥(以阿里云为例,其他厂商类似)
if [ "$VALIDATION_METHOD" = "3" ]; then
    case $DNS_VENDOR in
        1)
            # 阿里云:需要AccessKey ID和Secret(需提前创建,权限:管理DNS解析)
            read -p "请输入阿里云AccessKey ID:" ALIYUN_ACCESS_KEY
            read -p "请输入阿里云AccessKey Secret:" ALIYUN_ACCESS_SECRET
            # 生成阿里云配置文件(Certbot插件需要)
            ALIYUN_CONFIG="$HOME/.certbot-aliyun.ini"
            echo "dns_aliyun_access_key = $ALIYUN_ACCESS_KEY" > "$ALIYUN_CONFIG"
            echo "dns_aliyun_access_secret = $ALIYUN_ACCESS_SECRET" >> "$ALIYUN_CONFIG"
            chmod 600 "$ALIYUN_CONFIG"  # 限制权限,避免密钥泄露
            ;;
        2)
            # Cloudflare:需要API Token(权限:Zone.DNS)
            read -p "请输入Cloudflare API Token:" CF_API_TOKEN
            CF_CONFIG="$HOME/.certbot-cloudflare.ini"
            echo "dns_cloudflare_api_token = $CF_API_TOKEN" > "$CF_CONFIG"
            chmod 600 "$CF_CONFIG"
            ;;
        # 其他厂商密钥配置逻辑类似,已简化示例
    esac
fi

# 输入证书目标目录
read -p "请输入证书目标存储目录(例如:/etc/nginx/ssl):" TARGET_DIR
sudo mkdir -p "$TARGET_DIR" || { echo "错误:无法创建目标目录 $TARGET_DIR!"; exit 1; }
sudo chmod 755 "$TARGET_DIR"

##############################################################################
# 3. 申请Let's Encrypt证书(按验证方式执行)
##############################################################################
echo -e "\n开始申请 $DOMAIN 的证书..."
CERT_PATH="/etc/letsencrypt/live/$DOMAIN"

case $VALIDATION_METHOD in
    1)
        # HTTP验证(--webroot模式)
        sudo certbot certonly --webroot -w "$WEBROOT" -d "$DOMAIN" --agree-tos --email your-email@example.com --quiet
        ;;
    2)
        # 手动DNS验证(--manual模式,需按提示添加TXT记录)
        sudo certbot certonly --manual -d "$DOMAIN" --preferred-challenges dns --agree-tos --email your-email@example.com
        ;;
    3)
        # 自动化DNS验证(使用对应插件,无需手动添加记录)
        case $DNS_VENDOR in
            1)
                sudo certbot certonly --$DNS_PLUGIN --$DNS_PLUGIN-credentials "$ALIYUN_CONFIG" -d "$DOMAIN" --agree-tos --email your-email@example.com --quiet
                ;;
            2)
                sudo certbot certonly --$DNS_PLUGIN --$DNS_PLUGIN-credentials "$CF_CONFIG" -d "$DOMAIN" --agree-tos --email your-email@example.com --quiet
                ;;
        esac
        ;;
    *)
        echo "错误:无效的验证方式选择!"
        exit 1
        ;;
esac

# 验证证书申请结果
if [ -f "$CERT_PATH/privkey.pem" ] && [ -f "$CERT_PATH/fullchain.pem" ]; then
    echo "证书申请成功!"
else
    echo "错误:证书申请失败,请检查密钥配置或网络环境!"
    exit 1
fi

##############################################################################
# 4. 复制证书到目标目录+权限修复(同之前逻辑)
##############################################################################
echo -e "\n开始复制证书到目标目录 $TARGET_DIR..."
sudo cp -f "$CERT_PATH/privkey.pem" "$TARGET_DIR/$DOMAIN.key"
sudo cp -f "$CERT_PATH/fullchain.pem" "$TARGET_DIR/$DOMAIN.crt"
sudo chmod 644 "$TARGET_DIR/$DOMAIN.key" "$TARGET_DIR/$DOMAIN.crt"
echo "证书复制完成!"
echo "私钥路径:$TARGET_DIR/$DOMAIN.key"
echo "证书链路径:$TARGET_DIR/$DOMAIN.crt"

##############################################################################
# 5. 自动更新+日志(同之前逻辑,已兼容自动化DNS验证)
##############################################################################
CRON_JOB="0 4 * * 0 sudo certbot renew --quiet --renew-hook \"cp -f $CERT_PATH/privkey.pem $TARGET_DIR/ && cp -f $CERT_PATH/fullchain.pem $TARGET_DIR/ && echo '$(date +%Y-%m-%d_%H:%M:%S) - $DOMAIN 证书更新成功' >> /var/log/certbot_cert_renew.log\""
if ! crontab -l 2>/dev/null | grep -q "$DOMAIN"; then
    (sudo crontab -l 2>/dev/null; echo "$CRON_JOB") | sudo crontab -
    echo -e "\n自动更新任务已设置:每周日凌晨4点检测证书有效期,少于10天自动更新并同步到目标目录"
fi

##############################################################################
# 6. 证书备份(同之前逻辑)
##############################################################################
BACKUP_DIR="$TARGET_DIR/backup"
sudo mkdir -p "$BACKUP_DIR"
sudo cp -f "$TARGET_DIR/$DOMAIN.key" "$BACKUP_DIR/$(date +%Y%m%d)_$DOMAIN.key"
sudo cp -f "$TARGET_DIR/$DOMAIN.crt" "$BACKUP_DIR/$(date +%Y%m%d)_$DOMAIN.crt"
echo -e "\n证书备份完成:$BACKUP_DIR/$(date +%Y%m%d)_$DOMAIN.*"
#!/bin/bash

# ============================================
# certbot SSL证书管理脚本
# 功能:申请、检测、更新和部署SSL证书
# 自动安装所需模块和依赖
# ============================================

# 配置常量
CERT_CHECK_INTERVAL="7"  # 检查间隔(天)
CERT_RENEW_THRESHOLD="10" # 续期阈值(天)
CRON_SCHEDULE="0 0 * * 0" # 每周日0点执行

# 颜色输出函数
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

print_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查是否以root运行
check_root() {
    if [ "$EUID" -ne 0 ]; then 
        print_error "请使用sudo或以root用户运行此脚本"
        exit 1
    fi
}

# 检测系统类型
detect_os() {
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        OS=$ID
        VERSION=$VERSION_ID
    elif [ -f /etc/redhat-release ]; then
        OS="rhel"
    elif [ -f /etc/debian_version ]; then
        OS="debian"
    else
        OS=$(uname -s | tr '[:upper:]' '[:lower:]')
    fi
    echo $OS
}

# 安装certbot基础包
install_certbot_base() {
    local os=$(detect_os)
    
    print_info "检测到系统: $os"
    print_info "正在安装certbot基础包..."
    
    case $os in
        ubuntu|debian)
            apt-get update
            apt-get install -y certbot
            ;;
        centos|rhel|fedora)
            if command -v dnf &> /dev/null; then
                dnf install -y certbot
            else
                yum install -y epel-release
                yum install -y certbot
            fi
            ;;
        arch)
            pacman -Syu --noconfirm certbot
            ;;
        *)
            print_error "不支持的操作系统,请手动安装certbot"
            print_info "参考: https://certbot.eff.org/instructions"
            exit 1
            ;;
    esac
    
    if command -v certbot &> /dev/null; then
        print_success "certbot基础包安装成功"
        return 0
    else
        print_error "certbot安装失败"
        return 1
    fi
}

# 安装Web服务器插件
install_web_server_plugin() {
    local web_server=$1
    local os=$(detect_os)
    
    case $web_server in
        nginx)
            print_info "安装certbot-nginx插件..."
            case $os in
                ubuntu|debian)
                    apt-get install -y python3-certbot-nginx
                    ;;
                centos|rhel|fedora)
                    if command -v dnf &> /dev/null; then
                        dnf install -y python3-certbot-nginx
                    else
                        yum install -y python3-certbot-nginx
                    fi
                    ;;
                arch)
                    pacman -Syu --noconfirm certbot-nginx
                    ;;
            esac
            ;;
        apache)
            print_info "安装certbot-apache插件..."
            case $os in
                ubuntu|debian)
                    apt-get install -y python3-certbot-apache
                    ;;
                centos|rhel|fedora)
                    if command -v dnf &> /dev/null; then
                        dnf install -y python3-certbot-apache
                    else
                        yum install -y python3-certbot-apache
                    fi
                    ;;
                arch)
                    pacman -Syu --noconfirm certbot-apache
                    ;;
            esac
            ;;
    esac
}

# 安装DNS插件
install_dns_plugin() {
    local dns_provider=$1
    local os=$(detect_os)
    
    print_info "安装certbot DNS插件: $dns_provider"
    
    case $dns_provider in
        cloudflare)
            case $os in
                ubuntu|debian)
                    apt-get install -y python3-certbot-dns-cloudflare
                    ;;
                centos|rhel|fedora)
                    if command -v dnf &> /dev/null; then
                        dnf install -y python3-certbot-dns-cloudflare
                    else
                        yum install -y python3-certbot-dns-cloudflare
                    fi
                    ;;
                arch)
                    pacman -Syu --noconfirm certbot-dns-cloudflare
                    ;;
            esac
            
            # 配置Cloudflare API凭证
            configure_cloudflare_credentials
            ;;
        
        aliyun|alidns)
            case $os in
                ubuntu|debian)
                    apt-get install -y python3-certbot-dns-aliyun
                    ;;
                centos|rhel|fedora)
                    if command -v dnf &> /dev/null; then
                        dnf install -y python3-certbot-dns-aliyun
                    else
                        # 可能需要从pip安装
                        pip3 install certbot-dns-aliyun
                    fi
                    ;;
                arch)
                    pacman -Syu --noconfirm certbot-dns-aliyun || pip3 install certbot-dns-aliyun
                    ;;
            esac
            
            # 配置阿里云API凭证
            configure_aliyun_credentials
            ;;
        
        dnspod)
            # DNSPod插件通常需要从pip安装
            print_info "通过pip安装certbot-dns-dnspod"
            pip3 install certbot-dns-dnspod
            configure_dnspod_credentials
            ;;
        
        route53)
            case $os in
                ubuntu|debian)
                    apt-get install -y python3-certbot-dns-route53
                    ;;
                centos|rhel|fedora)
                    if command -v dnf &> /dev/null; then
                        dnf install -y python3-certbot-dns-route53
                    else
                        yum install -y python3-certbot-dns-route53
                    fi
                    ;;
                arch)
                    pacman -Syu --noconfirm certbot-dns-route53
                    ;;
            esac
            configure_route53_credentials
            ;;
        
        godaddy)
            # GoDaddy插件通常需要从pip安装
            pip3 install certbot-dns-godaddy
            configure_godaddy_credentials
            ;;
    esac
}

# 配置Cloudflare API凭证
configure_cloudflare_credentials() {
    print_info "配置Cloudflare API凭证"
    
    # 创建凭证目录
    mkdir -p /etc/letsencrypt
    mkdir -p ~/.secrets/certbot
    
    # 询问Cloudflare API凭证
    read -p "请输入Cloudflare邮箱: " cf_email
    read -p "请输入Cloudflare Global API Key: " cf_api_key
    
    # 创建凭证文件
    cat > ~/.secrets/certbot/cloudflare.ini <<EOF
# Cloudflare API credentials used by Certbot
dns_cloudflare_email = ${cf_email}
dns_cloudflare_api_key = ${cf_api_key}
EOF
    
    # 设置权限
    chmod 600 ~/.secrets/certbot/cloudflare.ini
    
    print_success "Cloudflare凭证已保存到 ~/.secrets/certbot/cloudflare.ini"
}

# 配置阿里云API凭证
configure_aliyun_credentials() {
    print_info "配置阿里云API凭证"
    
    mkdir -p ~/.secrets/certbot
    
    read -p "请输入阿里云AccessKey ID: " ali_key
    read -p "请输入阿里云AccessKey Secret: " ali_secret
    
    cat > ~/.secrets/certbot/aliyun.ini <<EOF
# Aliyun DNS API credentials
dns_aliyun_access_key = ${ali_key}
dns_aliyun_access_key_secret = ${ali_secret}
EOF
    
    chmod 600 ~/.secrets/certbot/aliyun.ini
    print_success "阿里云凭证已保存"
}

# 配置DNSPod API凭证
configure_dnspod_credentials() {
    print_info "配置DNSPod API凭证"
    
    mkdir -p ~/.secrets/certbot
    
    read -p "请输入DNSPod API ID: " dp_id
    read -p "请输入DNSPod API Token: " dp_token
    
    cat > ~/.secrets/certbot/dnspod.ini <<EOF
# DNSPod API credentials
dns_dnspod_api_id = ${dp_id}
dns_dnspod_api_token = ${dp_token}
EOF
    
    chmod 600 ~/.secrets/certbot/dnspod.ini
    print_success "DNSPod凭证已保存"
}

# 检查并安装所需模块
install_required_modules() {
    local verify_method=$1
    local dns_provider=$2
    local web_server=$3
    
    # 检查certbot是否已安装
    if ! command -v certbot &> /dev/null; then
        install_certbot_base
        if [ $? -ne 0 ]; then
            return 1
        fi
    fi
    
    # 安装Web服务器插件(如果选择了自动配置)
    if [ -n "$web_server" ]; then
        install_web_server_plugin "$web_server"
    fi
    
    # 安装DNS插件(如果选择了DNS验证)
    if [ "$verify_method" == "dns" ] && [ -n "$dns_provider" ]; then
        install_dns_plugin "$dns_provider"
    fi
    
    return 0
}

# 获取用户输入
get_user_input() {
    echo "=================================="
    echo "    SSL证书管理工具 (certbot)     "
    echo "=================================="
    
    # 输入域名
    while true; do
        read -p "请输入主域名: " main_domain
        if [ -z "$main_domain" ]; then
            print_error "域名不能为空"
        elif ! [[ "$main_domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$ ]]; then
            print_error "域名格式无效"
        else
            break
        fi
    done
    
    read -p "请输入其他域名 (用空格分隔,直接回车跳过): " other_domains_input
    
    # 构建域名列表
    DOMAINS=("$main_domain")
    if [ -n "$other_domains_input" ]; then
        DOMAINS+=($other_domains_input)
    fi
    
    # 选择验证方式
    echo ""
    echo "请选择验证方式:"
    echo "1) Webroot验证 (需要网站可访问)"
    echo "2) Standalone验证 (临时占用80/443端口)"
    echo "3) DNS验证 (自动API验证,推荐)"
    
    while true; do
        read -p "请选择 [1-3]: " verify_method
        case $verify_method in
            1)
                VERIFY_METHOD="webroot"
                read -p "请输入网站根目录路径 [默认: /var/www/html]: " web_root
                WEB_ROOT=${web_root:-/var/www/html}
                
                # 检查目录是否存在
                if [ ! -d "$WEB_ROOT" ]; then
                    print_warning "目录不存在,将尝试创建: $WEB_ROOT"
                    mkdir -p "$WEB_ROOT"
                fi
                break
                ;;
            2)
                VERIFY_METHOD="standalone"
                print_info "将使用standalone模式验证,请确保80/443端口空闲"
                break
                ;;
            3)
                VERIFY_METHOD="dns"
                echo ""
                echo "支持的DNS提供商:"
                echo "1) Cloudflare (推荐)"
                echo "2) Aliyun (阿里云)"
                echo "3) DNSPod"
                echo "4) Route53 (AWS)"
                echo "5) GoDaddy"
                read -p "选择DNS提供商 [1-5]: " dns_provider_num
                
                case $dns_provider_num in
                    1) DNS_PROVIDER="cloudflare" ;;
                    2) DNS_PROVIDER="aliyun" ;;
                    3) DNS_PROVIDER="dnspod" ;;
                    4) DNS_PROVIDER="route53" ;;
                    5) DNS_PROVIDER="godaddy" ;;
                    *) DNS_PROVIDER="cloudflare" ;;
                esac
                print_info "将安装 ${DNS_PROVIDER} DNS插件..."
                break
                ;;
            *)
                print_error "无效选择"
                ;;
        esac
    done
    
    # 询问证书部署路径
    echo ""
    read -p "请输入证书部署目录 [默认: /etc/ssl/certs]: " deploy_dir
    DEPLOY_DIR=${deploy_dir:-/etc/ssl/certs}
    
    # 创建部署目录
    if [ ! -d "$DEPLOY_DIR" ]; then
        print_info "创建部署目录: $DEPLOY_DIR"
        mkdir -p "$DEPLOY_DIR"
    fi
    
    # 询问邮箱(用于通知)
    read -p "请输入邮箱地址 (用于证书到期提醒): " email
    EMAIL=${email:-admin@$main_domain}
    
    # 询问是否安装到Nginx/Apache
    read -p "是否自动配置Web服务器? [y/N]: " config_web
    if [[ $config_web =~ ^[Yy]$ ]]; then
        # 检测已安装的Web服务器
        if command -v nginx &> /dev/null; then
            WEB_SERVER="nginx"
            print_info "检测到Nginx,将自动配置"
        elif command -v apache2 &> /dev/null; then
            WEB_SERVER="apache"
            print_info "检测到Apache,将自动配置"
        else
            echo "请选择Web服务器:"
            echo "1) Nginx"
            echo "2) Apache"
            read -p "请选择 [1-2]: " web_server_choice
            case $web_server_choice in
                1) WEB_SERVER="nginx" ;;
                2) WEB_SERVER="apache" ;;
                *) WEB_SERVER="nginx" ;;
            esac
        fi
    fi
    
    # 询问是否测试证书申请
    read -p "是否使用测试环境(避免达到Let's Encrypt限制)? [Y/n]: " test_cert
    if [[ $test_cert =~ ^[Nn]$ ]]; then
        STAGING=""
    else
        STAGING="--test-cert"
        print_info "将使用Let's Encrypt测试环境"
    fi
}

# 申请证书
issue_certificate() {
    print_info "开始申请SSL证书..."
    
    # 构建域名参数
    domain_args="-d ${DOMAINS[0]}"
    for (( i=1; i<${#DOMAINS[@]}; i++ )); do
        domain_args="$domain_args -d ${DOMAINS[$i]}"
    done
    
    # 根据验证方式执行不同命令
    case $VERIFY_METHOD in
        webroot)
            print_info "使用Webroot验证方式"
            certbot certonly --webroot -w "$WEB_ROOT" \
                $domain_args \
                --email "$EMAIL" \
                --agree-tos \
                --non-interactive \
                --force-renewal \
                $STAGING
            ;;
            
        standalone)
            print_info "使用Standalone验证方式"
            # 检查端口占用
            if netstat -tuln | grep -q ":80 "; then
                print_warning "80端口已被占用,尝试停止相关服务"
                # 尝试停止Nginx或Apache
                if systemctl is-active --quiet nginx; then
                    systemctl stop nginx
                    NGINX_WAS_RUNNING=true
                fi
                if systemctl is-active --quiet apache2; then
                    systemctl stop apache2
                    APACHE_WAS_RUNNING=true
                fi
            fi
            
            certbot certonly --standalone \
                $domain_args \
                --email "$EMAIL" \
                --agree-tos \
                --non-interactive \
                --force-renewal \
                --preferred-challenges http \
                $STAGING
            
            # 恢复服务
            if [ "$NGINX_WAS_RUNNING" = true ]; then
                systemctl start nginx
            fi
            if [ "$APACHE_WAS_RUNNING" = true ]; then
                systemctl start apache2
            fi
            ;;
            
        dns)
            print_info "使用DNS验证方式 (${DNS_PROVIDER})"
            
            # 构建DNS插件参数
            dns_plugin_args=""
            case $DNS_PROVIDER in
                cloudflare)
                    dns_plugin_args="--dns-cloudflare --dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini"
                    ;;
                aliyun)
                    dns_plugin_args="--dns-aliyun --dns-aliyun-credentials ~/.secrets/certbot/aliyun.ini"
                    ;;
                dnspod)
                    dns_plugin_args="--dns-dnspod --dns-dnspod-credentials ~/.secrets/certbot/dnspod.ini"
                    ;;
                route53)
                    dns_plugin_args="--dns-route53"
                    ;;
                godaddy)
                    dns_plugin_args="--dns-godaddy --dns-godaddy-credentials ~/.secrets/certbot/godaddy.ini"
                    ;;
            esac
            
            certbot certonly $dns_plugin_args \
                $domain_args \
                --email "$EMAIL" \
                --agree-tos \
                --non-interactive \
                --force-renewal \
                $STAGING
            ;;
    esac
    
    local exit_code=$?
    
    if [ $exit_code -eq 0 ]; then
        print_success "证书申请成功"
        return 0
    elif [ $exit_code -eq 1 ]; then
        print_error "证书申请失败: 输入参数错误"
        return 1
    elif [ $exit_code -eq 2 ]; then
        print_error "证书申请失败: 网络问题"
        return 1
    else
        print_error "证书申请失败 (错误码: $exit_code)"
        return 1
    fi
}

# 部署证书
deploy_certificate() {
    local main_domain="${DOMAINS[0]}"
    print_info "部署证书到: $DEPLOY_DIR"
    
    # certbot证书路径
    if [ -n "$STAGING" ]; then
        CERT_SOURCE="/etc/letsencrypt/staging/live/$main_domain"
    else
        CERT_SOURCE="/etc/letsencrypt/live/$main_domain"
    fi
    
    if [ ! -d "$CERT_SOURCE" ]; then
        print_error "证书目录不存在: $CERT_SOURCE"
        print_info "请检查证书申请是否成功"
        return 1
    fi
    
    # 创建部署目录
    CERT_DEPLOY_DIR="$DEPLOY_DIR/$main_domain"
    mkdir -p "$CERT_DEPLOY_DIR"
    
    # 复制证书文件(使用符号链接或实际文件)
    if [ -f "$CERT_SOURCE/fullchain.pem" ]; then
        cp -L "$CERT_SOURCE/fullchain.pem" "$CERT_DEPLOY_DIR/" 2>/dev/null || cp "$CERT_SOURCE/fullchain.pem" "$CERT_DEPLOY_DIR/"
        cp -L "$CERT_SOURCE/privkey.pem" "$CERT_DEPLOY_DIR/" 2>/dev/null || cp "$CERT_SOURCE/privkey.pem" "$CERT_DEPLOY_DIR/"
        cp -L "$CERT_SOURCE/chain.pem" "$CERT_DEPLOY_DIR/" 2>/dev/null || cp "$CERT_SOURCE/chain.pem" "$CERT_DEPLOY_DIR/"
        cp -L "$CERT_SOURCE/cert.pem" "$CERT_DEPLOY_DIR/" 2>/dev/null || cp "$CERT_SOURCE/cert.pem" "$CERT_DEPLOY_DIR/"
    else
        print_error "证书文件不存在"
        return 1
    fi
    
    # 设置权限
    chmod 600 "$CERT_DEPLOY_DIR/privkey.pem"
    chmod 644 "$CERT_DEPLOY_DIR/fullchain.pem"
    chmod 644 "$CERT_DEPLOY_DIR/chain.pem"
    chmod 644 "$CERT_DEPLOY_DIR/cert.pem"
    
    # 创建符号链接(保持最新证书)
    ln -sf "$CERT_SOURCE/fullchain.pem" "$CERT_DEPLOY_DIR/latest-fullchain.pem" 2>/dev/null || true
    ln -sf "$CERT_SOURCE/privkey.pem" "$CERT_DEPLOY_DIR/latest-privkey.pem" 2>/dev/null || true
    
    print_success "证书已部署到: $CERT_DEPLOY_DIR"
    
    # 创建合并证书(某些应用需要)
    cat "$CERT_DEPLOY_DIR/fullchain.pem" "$CERT_DEPLOY_DIR/privkey.pem" > "$CERT_DEPLOY_DIR/combined.pem"
    chmod 600 "$CERT_DEPLOY_DIR/combined.pem"
    
    # 配置Web服务器
    if [[ $config_web =~ ^[Yy]$ ]]; then
        configure_webserver "$main_domain" "$CERT_DEPLOY_DIR"
    fi
    
    return 0
}

# 配置Web服务器
configure_webserver() {
    local domain="$1"
    local cert_path="$2"
    
    case $WEB_SERVER in
        nginx)
            print_info "配置Nginx SSL证书..."
            
            # 使用certbot自动配置Nginx
            if command -v certbot &> /dev/null && [ -z "$STAGING" ]; then
                certbot --nginx -d "$domain" --non-interactive --agree-tos --email "$EMAIL" --redirect
                if [ $? -eq 0 ]; then
                    print_success "Nginx SSL配置完成"
                    return
                fi
            fi
            
            # 手动配置备用
            nginx_conf="/etc/nginx/sites-available/$domain"
            if [ -d "/etc/nginx/sites-available" ]; then
                # 备份原配置
                if [ -f "$nginx_conf" ]; then
                    cp "$nginx_conf" "${nginx_conf}.backup.$(date +%Y%m%d_%H%M%S)"
                fi
                
                tee "$nginx_conf" > /dev/null <<EOF
server {
    listen 80;
    server_name $domain;
    return 301 https://\$server_name\$request_uri;
}

server {
    listen 443 ssl http2;
    server_name $domain;
    
    ssl_certificate $cert_path/fullchain.pem;
    ssl_certificate_key $cert_path/privkey.pem;
    ssl_trusted_certificate $cert_path/chain.pem;
    
    # SSL优化
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    
    # HSTS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    
    location / {
        root /var/www/html;
        index index.html;
    }
}
EOF
                
                if [ -f "$nginx_conf" ]; then
                    ln -sf "$nginx_conf" "/etc/nginx/sites-enabled/" 2>/dev/null || true
                    print_info "Nginx配置已创建,请重启Nginx生效"
                fi
            fi
            ;;
            
        apache)
            print_info "配置Apache SSL证书..."
            
            # 使用certbot自动配置Apache
            if command -v certbot &> /dev/null && [ -z "$STAGING" ]; then
                certbot --apache -d "$domain" --non-interactive --agree-tos --email "$EMAIL" --redirect
                if [ $? -eq 0 ]; then
                    print_success "Apache SSL配置完成"
                    return
                fi
            fi
            
            # 手动配置备用
            apache_conf="/etc/apache2/sites-available/$domain.conf"
            if [ -d "/etc/apache2/sites-available" ]; then
                tee "$apache_conf" > /dev/null <<EOF
<VirtualHost *:80>
    ServerName $domain
    Redirect permanent / https://$domain/
</VirtualHost>

<VirtualHost *:443>
    ServerName $domain
    
    SSLEngine on
    SSLCertificateFile $cert_path/fullchain.pem
    SSLCertificateKeyFile $cert_path/privkey.pem
    SSLCertificateChainFile $cert_path/chain.pem
    
    # SSL优化
    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    
    # HSTS
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    
    DocumentRoot /var/www/html
    
    <Directory /var/www/html>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
EOF
                
                if [ -f "$apache_conf" ]; then
                    a2ensite "$domain.conf" > /dev/null 2>&1
                    a2enmod ssl > /dev/null 2>&1
                    a2enmod headers > /dev/null 2>&1
                    print_info "Apache配置已创建,请重启Apache生效"
                fi
            fi
            ;;
    esac
}

# 检查证书有效期
check_cert_expiry() {
    local main_domain="${DOMAINS[0]}"
    
    # 确定证书路径
    if [ -n "$STAGING" ]; then
        cert_file="/etc/letsencrypt/staging/live/$main_domain/fullchain.pem"
    else
        cert_file="/etc/letsencrypt/live/$main_domain/fullchain.pem"
    fi
    
    if [ ! -f "$cert_file" ]; then
        print_error "证书文件不存在: $cert_file"
        return 1
    fi
    
    # 使用openssl检查证书过期时间
    expiry_date=$(openssl x509 -enddate -noout -in "$cert_file" 2>/dev/null | cut -d= -f2)
    
    if [ -z "$expiry_date" ]; then
        print_error "无法读取证书信息"
        return 1
    fi
    
    # 转换为时间戳(处理不同系统date命令的差异)
    if date --version 2>/dev/null | grep -q GNU; then
        # GNU date (Linux)
        expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null)
    else
        # BSD date (macOS)
        expiry_epoch=$(date -j -f "%b %d %H:%M:%S %Y" "$expiry_date" +%s 2>/dev/null)
    fi
    
    if [ -z "$expiry_epoch" ]; then
        print_error "无法解析证书过期时间"
        return 1
    fi
    
    current_epoch=$(date +%s)
    
    # 计算剩余天数
    days_remaining=$(( ($expiry_epoch - $current_epoch) / 86400 ))
    
    echo "=================================="
    echo "证书有效期检查:"
    echo "域名: $main_domain"
    echo "过期时间: $expiry_date"
    echo "剩余天数: $days_remaining 天"
    
    if [ $days_remaining -le $CERT_RENEW_THRESHOLD ]; then
        print_warning "证书即将过期 (少于 ${CERT_RENEW_THRESHOLD} 天)"
        return 1
    elif [ $days_remaining -le 30 ]; then
        print_warning "证书将在30天内过期"
        return 0
    else
        print_success "证书有效期正常"
        return 0
    fi
}

# 更新证书
renew_certificate() {
    print_info "开始更新证书..."
    
    # 使用certbot renew命令
    certbot renew --quiet --post-hook "systemctl reload nginx apache2 2>/dev/null || true"
    
    if [ $? -eq 0 ]; then
        print_success "证书更新成功"
        
        # 重新部署证书
        deploy_certificate
        
        # 重启Web服务器
        if [ -n "$WEB_SERVER" ]; then
            case $WEB_SERVER in
                nginx)
                    systemctl reload nginx 2>/dev/null || systemctl restart nginx 2>/dev/null || true
                    ;;
                apache)
                    systemctl reload apache2 2>/dev/null || systemctl restart apache2 2>/dev/null || true
                    ;;
            esac
        fi
        
        return 0
    else
        print_error "证书更新失败"
        return 1
    fi
}

# 设置定时任务
setup_cron_job() {
    local script_path="$(realpath "$0")"
    local cron_cmd="$script_path --cron"
    
    # 添加定时任务
    (crontab -l 2>/dev/null | grep -v "$script_path"; echo "$CRON_SCHEDULE $cron_cmd") | crontab -
    
    if [ $? -eq 0 ]; then
        print_success "定时任务设置成功 (计划: $CRON_SCHEDULE)"
        
        # 也添加certbot的自动更新
        (crontab -l 2>/dev/null | grep -v "certbot renew"; echo "0 3 * * * certbot renew --quiet --post-hook 'systemctl reload nginx apache2 2>/dev/null || true' 2>/dev/null") | crontab -
        
        print_info "已设置certbot自动更新 (每天3点执行)"
    else
        print_error "定时任务设置失败"
    fi
}

# 显示证书信息
show_certificate_info() {
    local main_domain="${DOMAINS[0]}"
    
    echo ""
    echo "=================================="
    echo "         证书信息                "
    echo "=================================="
    
    # 使用certbot certificates命令
    if certbot certificates 2>/dev/null | grep -q "$main_domain"; then
        certbot certificates 2>/dev/null | grep -A 10 "$main_domain"
    else
        print_warning "未找到证书信息,尝试直接读取文件"
        
        if [ -n "$STAGING" ]; then
            cert_dir="/etc/letsencrypt/staging/live/$main_domain"
        else
            cert_dir="/etc/letsencrypt/live/$main_domain"
        fi
        
        if [ -d "$cert_dir" ]; then
            echo "证书位置: $cert_dir"
            ls -la "$cert_dir/"
        fi
    fi
}

# 主函数
main() {
    # 检查参数
    if [ "$1" == "--cron" ]; then
        # 定时任务模式
        print_info "执行定时证书检查..."
        check_cert_expiry
        if [ $? -eq 1 ]; then
            renew_certificate
        fi
        exit 0
    elif [ "$1" == "--renew" ]; then
        # 手动更新模式
        renew_certificate
        exit $?
    elif [ "$1" == "--check" ]; then
        # 只检查模式
        check_cert_expiry
        exit $?
    fi
    
    # 交互模式
    check_root
    get_user_input
    
    # 安装所需模块
    print_info "检查并安装所需模块..."
    install_required_modules "$VERIFY_METHOD" "$DNS_PROVIDER" "$WEB_SERVER"
    if [ $? -ne 0 ]; then
        print_error "模块安装失败"
        exit 1
    fi
    
    # 申请证书
    issue_certificate
    if [ $? -eq 0 ]; then
        # 部署证书
        deploy_certificate
        
        # 检查有效期
        check_cert_expiry
        
        # 显示证书信息
        show_certificate_info
        
        # 设置定时任务
        echo ""
        read -p "是否设置自动更新定时任务? [Y/n]: " setup_cron
        if [[ ! $setup_cron =~ ^[Nn]$ ]]; then
            setup_cron_job
        fi
        
        # 生成报告
        generate_report
    else
        print_error "证书申请过程失败"
        exit 1
    fi
}

# 生成报告
generate_report() {
    local main_domain="${DOMAINS[0]}"
    
    echo ""
    echo "=================================="
    echo "          SSL证书部署报告         "
    echo "=================================="
    echo "主域名: $main_domain"
    echo "所有域名: ${DOMAINS[*]}"
    
    if [ -n "$STAGING" ]; then
        echo "证书位置: /etc/letsencrypt/staging/live/$main_domain/"
        echo "模式: 测试环境 (STAGING)"
    else
        echo "证书位置: /etc/letsencrypt/live/$main_domain/"
        echo "模式: 生产环境"
    fi
    
    echo "部署位置: $DEPLOY_DIR/$main_domain/"
    echo "验证方式: $VERIFY_METHOD"
    
    if [ "$VERIFY_METHOD" == "webroot" ]; then
        echo "网站目录: $WEB_ROOT"
    elif [ "$VERIFY_METHOD" == "dns" ]; then
        echo "DNS提供商: $DNS_PROVIDER"
    fi
    
    echo "通知邮箱: $EMAIL"
    
    if [ -n "$WEB_SERVER" ]; then
        echo "Web服务器: $WEB_SERVER"
    fi
    
    echo ""
    echo "管理命令:"
    echo "  - 手动更新: $0 --renew"
    echo "  - 检查证书: $0 --check"
    echo "  - 查看证书: certbot certificates"
    echo "  - 撤销证书: certbot revoke --cert-name $main_domain"
    
    echo ""
    echo "证书文件:"
    echo "  - fullchain.pem: 完整证书链"
    echo "  - privkey.pem: 私钥文件"
    echo "  - chain.pem: 中间证书"
    echo "  - cert.pem: 证书文件"
    echo "  - combined.pem: 合并证书(证书+私钥)"
    
    echo ""
    echo "自动更新:"
    echo "  - 脚本检查: 每周日0点 (少于${CERT_RENEW_THRESHOLD}天自动更新)"
    echo "  - certbot自动更新: 每天3点"
    
    echo "=================================="
}

# 脚本异常处理
trap 'print_error "脚本执行被中断"; exit 1' INT TERM

# 执行主函数
main "$@"

 


sicnature ---------------------------------------------------------------------
I P 地 址: 216.73.216.130
区 域 位 置: 美国加利福尼亚洛杉矶
系 统 信 息: 美国
Original content, please indicate the source:
同福客栈论坛 | 蟒蛇科普海南乡情论坛 | JiaYu Blog
sicnature ---------------------------------------------------------------------
Welcome to reprint. Please indicate the source http://myzhenai.com.cn/post/4869.html

没有评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注