飞牛OS通过acme.sh自动部署ssl证书

飞牛OS绑定自有域名后,通过acme.sh工具+CF组合,自动更新ssl证书,自动续签的方案。以下所有命令在root权限下运行

定位到opt目录
cd /opt

国内环境安装acme,注意末尾的邮箱改成自己的
mkdir -p /opt/acme && cd /opt/acme && git clone https://gitee.com/neilpang/acme.sh.git && cd acme.sh && ./acme.sh –install -m yourmail@server.com

创建自动执行脚本:
sudo nano /opt/acme/acmedeploy.sh

给与脚本权限:
chmod 777 /opt/acme/acmedeploy.sh

手工执行脚本创建证书:
bash /opt/acme/acmedeploy.sh

在成功你执行脚本并自动创建证书,网页正常能访问后,添加自动执行任务,输入 crontab -e 打开当前用户的计划任务列表,建议把acme.sh默认的定时任务注释掉,新增一条,每月20号执行一次,证书有效期有90天,所以调整为每个月执行一次。

0 0 20 * * bash /opt/acme/acmedeploy.sh >> /opt/acme/acmedeploy.log 2>&1


以下是代码:
如果证书有效期剩余超过30天,跳过证书申请过程,直接使用现有证书。更新现有证书记录前,取消该域名下所有证书默认状态,在更新现有证书记录时添加了is_default = 1,插入新证书记录时将is_default设置为1,确保新证书设置为默认证书。

#!/bin/bash

# 获取脚本开始运行时间戳
TT=$(date +%s%3N)

# acme.sh 脚本路径
ACME_DIR="/opt/acme/acme.sh"

# SSL证书域名(xxxx.com)
export DOMAIN="your domain"

# DNS类型 CF
export DNS="dns_cf"

# Cloudflare API密钥
export CF_Key="your key"
export CF_Email="your email address"

# DNS API 生效等待时间 值(单位:秒),一般120即可
DNS_SLEEP="120"

# 证书服务商,zerossl 和 letsencrypt,这里用letsencrypt
CERT_SERVER="letsencrypt"

# 飞牛OS SSL证书路径
SSLS_DIR="/usr/trim/var/trim_connect/ssls"

# 飞牛OS重启服务命令行
ReloadCMD="systemctl restart webdav.service smbftpd.service trim_nginx.service"

# 检查现有证书有效期(如果存在)
CURRENT_CERT_DIR=$(psql -t -A -U postgres -d trim_connect -c "SELECT private_key FROM cert WHERE domain = '"${DOMAIN}"' AND is_default = 1;" | sed 's|/'"${DOMAIN}"'.key||g' 2>/dev/null)
if [ -n "${CURRENT_CERT_DIR}" ] && [ -f "${CURRENT_CERT_DIR}/"${DOMAIN}".crt" ]; then
    CURRENT_CERT_EXPIRE=$(date -d "$(openssl x509 -in "${CURRENT_CERT_DIR}/"${DOMAIN}".crt" -noout -enddate | cut -d= -f2)" +%s)
    CURRENT_TIME=$(date +%s)
    DAYS_REMAINING=$(( (CURRENT_CERT_EXPIRE - CURRENT_TIME) / 86400 ))
    
    if [ ${DAYS_REMAINING} -gt 30 ]; then
        echo "当前证书还有 ${DAYS_REMAINING} 天有效期,大于30天,跳过证书申请"
        # 设置变量以便后续步骤使用现有证书目录
        DOMAIN_SSL_DIR=${CURRENT_CERT_DIR}
        # 获取现有证书的时间信息
        CERT_CREATE=$(stat -c %Y "${DOMAIN_SSL_DIR}/"${DOMAIN}".crt")
        CERT_CREATE_TT=${CERT_CREATE}000
        CERT_RENEW=$(date -d @${CERT_CREATE} +%s --date="+90 days")
        CERT_RENEW_TT=${CERT_RENEW}000
        # 跳转到证书安装后步骤
        goto_install="yes"
    fi
fi

# 制作证书(只在需要时执行)
if [ "${goto_install}" != "yes" ]; then
    if [[ "${CERT_SERVER}" == "letsencrypt" ]] ; then
        ${ACME_DIR}/acme.sh --force --log --issue --server ${CERT_SERVER} --dns ${DNS} --dnssleep ${DNS_SLEEP} -d "${DOMAIN}" -d "*.${DOMAIN}"
        if [ $? -ne 0 ] ; then
            echo -e  "制作证书失败,脚本退出. . ."
            exit
        fi
    fi

    if [[ "${CERT_SERVER}" == "zerossl" ]] ; then
        ${ACME_DIR}/acme.sh --register-account -m ${Email} --server ${CERT_SERVER} --eab-kid ${EAB_kid} --eab-hmac-key ${EAB_hmac_key} --issue --dns ${DNS} --dnssleep ${DNS_SLEEP} -d "${DOMAIN}" -d "*.${DOMAIN}"
        if [ $? -ne 0 ] ; then
            echo -e  "制作证书失败,脚本退出. . ."
            exit
        fi
    fi

    # 获取证书详细时间戳并创建目录
    CertCreateTime="$(${ACME_DIR}/acme.sh --info -d "${DOMAIN}" | grep CertCreateTimeStr= | awk -F= '{print $2}' | sed 's|T| |g' | sed 's|Z||g')"
    NextRenewTime="$(${ACME_DIR}/acme.sh --info -d "${DOMAIN}" | grep Le_NextRenewTimeStr= | awk -F= '{print $2}' | sed 's|T| |g' | sed 's|Z||g')"
    CERT_CREATE=$(date -d "${CertCreateTime} 7 hour" +%s)
    CERT_CREATE_TT=$(date -d "${CertCreateTime} 7 hour" +%s%3N)
    CERT_RENEW=$(date -d "${NextRenewTime} 1 month 7 hour" +%s)
    CERT_RENEW_TT=$(date -d "${NextRenewTime} 1 month 7 hour" +%s%3N)
    mkdir -p ${SSLS_DIR}/"${DOMAIN}"/${CERT_CREATE}
    DOMAIN_SSL_DIR=${SSLS_DIR}/"${DOMAIN}"/${CERT_CREATE}

    # 安装证书到域名证书目录
    ${ACME_DIR}/acme.sh --install-cert -d "${DOMAIN}" --cert-file ${DOMAIN_SSL_DIR}/"${DOMAIN}".crt --key-file ${DOMAIN_SSL_DIR}/"${DOMAIN}".key --fullchain-file ${DOMAIN_SSL_DIR}/fullchain.crt --ca-file ${DOMAIN_SSL_DIR}/issuer_certificate.crt --reloadcmd "${ReloadCMD}"

# 配置证书文件权限(公开,但无需执行权限)
chmod 644 "${DOMAIN_SSL_DIR}/${DOMAIN}.crt"
chmod 644 "${DOMAIN_SSL_DIR}/fullchain.crt"
chmod 644 "${DOMAIN_SSL_DIR}/issuer_certificate.crt"
# 私钥文件(严格限制)
chmod 600 "${DOMAIN_SSL_DIR}/${DOMAIN}.key"
# 目录权限(可选)
chmod 750 "${DOMAIN_SSL_DIR}"
fi

# 获取证书颁发者信息
CERT_ISSUED_BY=$(openssl x509 -in ${DOMAIN_SSL_DIR}/"${DOMAIN}".crt -noout -issuer | awk -F' = ' '{print $4}')

# 获取证书加密类型信息
SIG_ALGO=$(openssl x509 -in ${DOMAIN_SSL_DIR}/"${DOMAIN}".crt -noout -text | awk '/Signature Algorithm/ {print $3}' | awk 'END {print}')
shopt -s nocasematch
case $SIG_ALGO in
    *RSA*)
        ALGO_TYPE="RSA"
        ;;
    *ECDSA*)
        ALGO_TYPE="ECDSA"
        ;;
    *ECC*)
        ALGO_TYPE="ECC"
        ;;
    *SM2*)
        ALGO_TYPE="SM2"
        ;;
    *)
        ALGO_TYPE="UNKNOW"
        ;;
esac

# 新增或更新飞牛OS证书数据库信息
if [ ! -z $(psql -t -A -U postgres -d trim_connect -c "SELECT domain FROM cert WHERE domain = '"${DOMAIN}"';" | sed  '/^\s*$/d') ] ; then
    # 先取消该域名下所有证书的默认状态
    psql -U postgres -d trim_connect -c "UPDATE cert SET is_default = 0 WHERE domain = '"${DOMAIN}"';"
    # 飞牛OS更新数据库证书信息
    psql -U postgres -d trim_connect -c "UPDATE cert SET valid_from = ${CERT_CREATE_TT}, valid_to = ${CERT_RENEW_TT}, encrypt_type = '${ALGO_TYPE}', issued_by = '${CERT_ISSUED_BY}', last_renew_time =  ${TT}, des = '由acme.sh自动生成的证书', private_key = '${DOMAIN_SSL_DIR}/"${DOMAIN}".key', certificate = '${DOMAIN_SSL_DIR}/"${DOMAIN}".crt', issuer_certificate = '${DOMAIN_SSL_DIR}/issuer_certificate.crt', status = 'suc', created_time =  ${TT}, updated_time =  ${TT}, is_default = 1 WHERE domain = '"${DOMAIN}"';"
    if [ $? -ne 0 ] ; then
        echo -e  "更新数据库证书信息失败,脚本退出. . ."
        exit
    fi
else
    DOMAIN_ID=$[$(psql -t -A -U postgres -d trim_connect -c "SELECT id FROM cert ORDER BY id ASC;" | awk 'END {print}')+1]
    psql -U postgres -d trim_connect -c "INSERT INTO cert VALUES ("${DOMAIN_ID}", '"${DOMAIN}"', '*."${DOMAIN}","${DOMAIN}"', ${CERT_CREATE_TT}, ${CERT_RENEW_TT}, '${ALGO_TYPE}', '${CERT_ISSUED_BY}', ${TT}, '由acme.sh自动生成的证书', 1, null, 'upload', null, '${DOMAIN_SSL_DIR}/"${DOMAIN}".key', '${DOMAIN_SSL_DIR}/"${DOMAIN}".crt', '${DOMAIN_SSL_DIR}/issuer_certificate.crt', 'suc', ${TT}, ${TT});"
    if [ $? -ne 0 ] ; then
        echo -e  "更新数据库证书信息失败,脚本退出. . ."
        exit
    fi
fi

# 更新飞牛OS NGINX 配置文件
\cp -rfL /usr/trim/etc/network_gateway_cert.conf /usr/trim/etc/network_gateway_cert.conf.${TT}.bak
NETWORK_GATEWAY_CERT="{\"host\":\""${DOMAIN}"\",\"cert\":\"${DOMAIN_SSL_DIR}/fullchain.crt\",\"key\":\"${DOMAIN_SSL_DIR}/"${DOMAIN}".key\"},"
grep -E ""${DOMAIN}"" /usr/trim/etc/network_gateway_cert.conf >/dev/null 2>&1
if [ $? -eq 0 ] ; then
    sed -i "s|{\"host\":.*\/usr\/.*"${DOMAIN}".*},|${NETWORK_GATEWAY_CERT}|g" /usr/trim/etc/network_gateway_cert.conf
else
    awk '{gsub(/^./,""); print}' /usr/trim/etc/network_gateway_cert.conf > /tmp/fn1
    sed -i "1i[${NETWORK_GATEWAY_CERT}" /tmp/fn1
    sed -i ':a;N;$!ba;s/\n//g' /tmp/fn1
    \cp -rfL /tmp/fn1 /usr/trim/etc/network_gateway_cert.conf
    rm -rf /tmp/fn1
fi

# 飞牛OS删除无效证书(保留当前证书目录,删除其他所有)
if [ -n "${DOMAIN_SSL_DIR}" ]; then
    find ${SSLS_DIR}/"${DOMAIN}"/ -mindepth 1 -maxdepth 1 -type d ! -path "${DOMAIN_SSL_DIR}" -exec rm -rf {} \; > /dev/null 2>&1
fi

# 重启服务并增加日志输出
echo "正在重启服务: ${ReloadCMD}"
if ${ReloadCMD}; then
    echo "服务重启成功"
else
    echo "服务重启失败,请检查"
    exit 1
fi

Language
中文(简体) 中文(繁體) 日本語 한국어 русский English français Deutsch español italiano বাংলা (ভারত) العربية ไทย Tiếng Việt Bahasa Melayu Filipino ελληνικά magyar dansk norsk íslenska Gaeilge