成功后效果
安装acme.sh
安装 acme.sh 并设置邮箱用来接受重要通知,如证书快过期未更新通知
curl https://get.acme.sh | sh -s email=my@example.com
执行命令后几秒就安装好了,如果半天没有反应请 Ctrl+C 后重新执行命令。acme.sh 安装在 ~/.acme.sh 目录下,并自动创建了一个 cronjob,每天 0:00 点自动检测所有的证书,如果快过期了, 则会自动更新。安装后,理论上会自动添加一个 acme.sh 全局应用别名,但有时候会 command not found,需要手动执行以下命令:source ~/.bashrc 或 source ~/.bash_profile,或关掉终端重新打开,然后再继续下一步。也可以在 ~/.acme.sh 目录下使用 sh acme.sh 执行命令(例如:sh acme.sh --list)
编写脚本
申请域名脚本(acme_apply.sh)
#!/bin/bash
###################### 写入日志 ######################
log() {
LOG_PATH="acme_apply.sh.log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG_PATH
}
###################### 证书申请 ######################
log "开始接收参数"
# 域名
read -p "请输入需申请证书的域名(多个域名使用空格符隔开): " DOMAIN
if [ -z "$DOMAIN" ]; then
log "输入不能为空,请重新运行脚本并输入有效字符。"
echo "输入不能为空,请重新运行脚本并输入有效字符。"
exit 1
fi
read -p "请输入更新证书的天数(默认 60 天后自动更新): " UPDATE_DAY
if [ -z "$UPDATE_DAY" ]; then
UPDATE_DAY="60"
fi
# 证书目录,最后面不需要"/"符号
read -p "请输入证书安装路径(默认为/etc/nginx/ssl): " CERTIFICATE_PATH
if [ -z "$CERTIFICATE_PATH" ]; then
CERTIFICATE_PATH="/etc/nginx/ssl"
fi
# nginx 安装环境
read -p "请输入 nginx 安装环境(0:Docker; 1:Linix; 默认为 0): " NGINX_ENV
if [ -z "$NGINX_ENV" ]; then
NGINX_ENV="0"
elif [ $NGINX_ENV != "0" ] && [ $NGINX_ENV != "1" ]; then
log "输入的参数错误,请重新运行脚本并输入正确字符。"
echo "输入的参数错误,请重新运行脚本并输入正确字符。"
exit 1
fi
# 部署环境
read -p "请输入部署环境(dev|test|prod 默认为 dev): " DEPLOY_ENV
if [ -z "$DEPLOY_ENV" ]; then
DEPLOY_ENV="dev"
elif [ $DEPLOY_ENV != "dev" ] && [ $DEPLOY_ENV != "test" ] && [ $DEPLOY_ENV != "prod" ]; then
log "输入的参数错误,请重新运行脚本并输入正确字符。"
echo "输入的参数错误,请重新运行脚本并输入正确字符。"
exit 1
fi
log "接收的参数信息: {证书域名: $DOMAIN, 更新证书的天数: $UPDATE_DAY, 安装路径=$CERTIFICATE_PATH, nginx 安装环境: ${NGINX_ENV}, 部署环境: ${DEPLOY_ENV}}"
# 处理域名参数
DOMAIN_ARRAY=(${DOMAIN// / })
FIRST_DOMIAN=${DOMAIN_ARRAY[0]}
NEW_DOMAIN=""
for var in ${DOMAIN_ARRAY[@]}
do
NEW_DOMAIN="${NEW_DOMAIN} -d $var"
done
log "处理后的域名参数: {FIRST_DOMIAN: $FIRST_DOMIAN, NEW_DOMAIN: $NEW_DOMAIN}"
log "$DOMAIN 证书开始申请"
cd /root/.acme.sh
log "sh acme.sh --issue --dns dns_dp$NEW_DOMAIN --days $UPDATE_DAY"
# --days 2 表示更新证书的天数,默认值为 60 天
sh acme.sh --issue --dns dns_dp$NEW_DOMAIN --days $UPDATE_DAY
# 检查退出状态码并输出结果
if [ $? -eq 0 ]; then
# 申请证书成功后安装证书
log "sh acme.sh --install-cert -d $FIRST_DOMIAN \
--key-file $CERTIFICATE_PATH/$FIRST_DOMIAN.key \
--fullchain-file $CERTIFICATE_PATH/$FIRST_DOMIAN.cer \
--reloadcmd \"sh /root/.acme.sh/install_after.sh $FIRST_DOMIAN $NGINX_ENV $DEPLOY_ENV\""
sh acme.sh --install-cert -d $FIRST_DOMIAN \
--key-file $CERTIFICATE_PATH/$FIRST_DOMIAN.key \
--fullchain-file $CERTIFICATE_PATH/$FIRST_DOMIAN.cer \
--reloadcmd "sh /root/.acme.sh/install_after.sh $FIRST_DOMIAN $NGINX_ENV $DEPLOY_ENV"
log "申请证书成功"
else
log "申请证书失败"
fi
log "$DOMAIN 证书申请结束"
更新成功/失败通知(install_after.sh)
本文通过URL发起通知
#!/bin/bash
###################### 写入日志 ######################
log() {
LOG_PATH="install_after.sh.log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG_PATH
}
###################### 验证证书是否更新成功和生效(废弃) ######################
verify() {
# 设置要查询的域名和 HTTPS 端口
PORT="443"
# 使用 OpenSSL 获取证书信息并提取到期时间
END_DATE=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:$PORT 2>/dev/null | openssl x509 -noout -enddate | sed 's/.*=//')
log "使用 OpenSSL 获取证书信息并提取到期时间: $END_DATE"
# 将到期时间转换成 Unix 时间戳
END_TIMESTAMP=$(date +%s -d "$END_DATE")
log "将到期时间转换成 Unix 时间戳: $END_TIMESTAMP"
# 获取当前时间的 Unix 时间戳
NOW_TIMESTAMP=$(date +%s)
log "当前时间的 Unix 时间戳: $NOW_TIMESTAMP"
# 计算两个时间戳之间的间隔(单位:秒)
INTERVAL=$((END_TIMESTAMP - NOW_TIMESTAMP))
log "两个时间戳之间的间隔: $INTERVAL"
# 判断间隔是否小于 30 天(单位:秒) 7776000
WARNING_THRESHOLD=$((30 * 24 * 60 * 60)) # 30 天的秒数
if [ $INTERVAL -lt $WARNING_THRESHOLD ]; then
log "SSL/TLS 证书更新失败: $DOMAIN"
early_warning_push "{\"environment\": \"$DEPLOY_ENV\", \"logLevel\":\"warn\", \"projectName\":\"域名证书\",\"moduleName\":\"证书更新\",\"detailContent\":\"证书更新失败\"}"
else
log "SSL/TLS 证书更新成功: $DOMAIN"
early_warning_push "{\"environment\": \"$DEPLOY_ENV\", \"logLevel\":\"info\", \"projectName\":\"域名证书\",\"moduleName\":\"证书更新\",\"detailContent\":\"证书更新成功\"}"
fi
}
###################### 发送通知 ######################
early_warning_push() {
log "请求信息: curl -H 'Content-Type: application/json' -X POST -d '$1' $EARLY_WARNING_PUSH_URL"
curl -H 'Content-Type: application/json' -X POST -d '$1' $EARLY_WARNING_PUSH_URL
}
###################### 配置更新证书成功后执行的命令 ######################
# 域名
DOMAIN=$1
# nginx 安装环境(0:Docker; 1:Linix);
NGINX_ENV=$2
# 部署环境(dev|test|prod)
DEPLOY_ENV=$3
log "接收的参数信息: {域名: $DOMAIN, nginx 安装环境: $NGINX_ENV, 部署环境: $DEPLOY_ENV}"
# 设置通知URL
if [ $DEPLOY_ENV = "prod" ]; then
EARLY_WARNING_PUSH_URL="http://127.0.0.1:8000/push"
elif [ $DEPLOY_ENV = "test" ]; then
EARLY_WARNING_PUSH_URL="http://127.0.0.1:8000/push"
else
EARLY_WARNING_PUSH_URL="http://127.0.0.1:8000/push"
fi
log "开始验证 $DOMAIN 证书更新"
# 更新 nginx 配置信息
if [ $NGINX_ENV = "0" ]; then
log "docker exec -it nginx nginx -s reload"
docker exec -it nginx nginx -s reload
elif [ $NGINX_ENV = "1" ]; then
log "nginx -s reload"
nginx -s reload
fi
# 检查退出状态码并输出结果
if [ $? -eq 0 ]; then
# 输出日志
log "重新加载 Nginx 配置文件成功"
early_warning_push "{\"environment\": \"$DEPLOY_ENV\", \"logLevel\":\"info\", \"projectName\":\"域名证书\",\"moduleName\":\"证书更新\",\"detailContent\":\"证书更新成功\"}"
# verify
else
log "重新加载 Nginx 配置文件失败"
early_warning_push "{\"environment\": \"$DEPLOY_ENV\", \"logLevel\":\"error\", \"projectName\":\"域名证书\",\"moduleName\":\"证书更新\",\"detailContent\":\"重新加载 Nginx 配置文件失败\"}"
fi
log "验证 $DOMAIN 证书更新结束"
注意
- 两个脚本推荐存放在 /root/.acme.sh 目录下,并通过 chmod +x 授权。自定义存放目录,需要调整 acme_apply.sh 脚本。
- 执行脚本之后,会生成对应的日志文件(acme_apply.sh -> acme_apply.sh.log , install_after.sh -> install_after.sh.log)
使用
泛域名申请(如果单独使用 *.example.com 申请会出现问题,在安装证书时如果同目录下存在泛域名所包含的域名时证书会安装失败):
非泛域名申请