AlexRomeo
发布于 2024-06-20 / 266 阅读
0
0

自动同步证书脚本

背景

ssl证书通过acme自动申请,约3个月自动续签一次证书,将申请下来的证书自动同步给其他需要使用的主机。

环境介绍

系统:CentOS 7.8

要求证书所在的主机与其他主机之间可以免密访问,否则脚本无法自动进行同步。

正文

脚本内容

#!/bin/bash
set -eo pipefail  # 遇到错误立即退出

# 目标目录和文件配置
target_dir="/data/etc/nginx-proxy-manager/letsencrypt/archive/npm-4"
state_file="/root/scripts/file_state.txt"
log_file="/root/scripts/cert_sync.log"

# 初始化变量
old_pri_key=''
old_pub_crt=''
pri_key=''
pub_crt=''

# 日志函数 - 同时输出到控制台和日志文件
log() {
    local timestamp=$(date +'%Y-%m-%d %H:%M:%S')
    local message="[$timestamp] $1"
    echo "$message"  # 控制台输出
    echo "$message" >> "$log_file"  # 日志文件输出
}

# 从状态文件读取历史记录
loadState() {
    if [ -f "$state_file" ]; then
        old_pri_key=$(sed -n '1p' "$state_file" 2>/dev/null || true)
        old_pub_crt=$(sed -n '2p' "$state_file" 2>/dev/null || true)
        log "加载历史状态: 私钥=$old_pri_key, 证书=$old_pub_crt"
    else
        log "状态文件不存在,将创建新文件: $state_file"
    fi
}

# 保存当前状态到文件
saveState() {
    mkdir -p "$(dirname "$state_file")"
    echo "$pri_key" > "$state_file"
    echo "$pub_crt" >> "$state_file"
    log "已保存当前状态到文件: 私钥=$pri_key, 证书=$pub_crt"
}

# 同步单个文件到目标主机
sync_file() {
    local source_path=$1
    local dest_host=$2
    local dest_port=$3
    local dest_path=$4
    local file_type=$5

    log "正在同步$file_type到$dest_host"
    if scp -P "$dest_port" "$source_path" "root@$dest_host:$dest_path"; then
        log "$file_type同步到$dest_host成功"
        return 0
    else
        log "ERROR: $file_type同步到$dest_host失败"
        return 1
    fi
}

# 同步证书到所有目标主机
sync_to_all_hosts() {
    local pri_key_path=$1
    local pub_crt_path=$2
    
    # 目标主机配置数组: 主机名 端口 私钥目标路径及文件名称 证书目标路径及文件名称 描述
    local hosts=(
        "192.168.215.247 2122 /home/dev.alexromeo.net/dev.alexromeo.net.key /home/dev.alexromeo.net/dev.alexromeo.net.crt 'hs中继服务器'"
        "172.22.5.178 2318 /data/nginx-proxy-manager/data/custom_ssl/npm-2/privkey.pem /data/nginx-proxy-manager/data/custom_ssl/npm-2/fullchain.pem '阿里服务器'"
    )
    
    log "开始执行证书同步操作"
    
    # 遍历所有主机并同步文件
    local success=1
    for host in "${hosts[@]}"; do
        # 解析主机配置
        local host_info=($host)
        local dest_host=${host_info[0]}
        local dest_port=${host_info[1]}
        local priv_key_dest=${host_info[2]}
        local cert_dest=${host_info[3]}
        local desc=${host_info[4]}
        
        # 同步私钥和证书
        if ! sync_file "$pri_key_path" "$dest_host" "$dest_port" "$priv_key_dest" "私钥到$desc"; then
            success=0
        fi
        
        if ! sync_file "$pub_crt_path" "$dest_host" "$dest_port" "$cert_dest" "证书到$desc"; then
            success=0
        fi
    done
    
    return $success
}

# 检查证书状态并同步
checkSSL() {
    log "开始执行证书检测流程"
    
    # 文件前缀定义
    local pri_key_prefix='privkey'
    local pub_crt_prefix='fullchain'

    # 检查目标目录
    if [ ! -d "$target_dir" ]; then
        log "ERROR: 目标证书目录不存在 - $target_dir"
        return 1
    fi
    log "证书目录检查通过: $target_dir"

    # 获取最新证书文件
    log "正在查找最新的证书文件..."
    local new_pri_key=$(ls -tr "$target_dir"/${pri_key_prefix}* 2>/dev/null | tail -n 1)
    local new_pub_crt=$(ls -tr "$target_dir"/${pub_crt_prefix}* 2>/dev/null | tail -n 1)

    # 验证文件存在性
    if [ -z "$new_pri_key" ] || [ ! -f "$new_pri_key" ]; then
        log "ERROR: 未找到私钥文件 (前缀: $pri_key_prefix) 在 $target_dir"
        return 1
    fi
    log "找到最新私钥文件: $new_pri_key"
    
    if [ -z "$new_pub_crt" ] || [ ! -f "$new_pub_crt" ]; then
        log "ERROR: 未找到证书文件 (前缀: $pub_crt_prefix) 在 $target_dir"
        return 1
    fi
    log "找到最新证书文件: $new_pub_crt"

    # 检查文件可读性
    if [ ! -r "$new_pri_key" ] || [ ! -r "$new_pub_crt" ]; then
        log "ERROR: 没有证书文件的读取权限"
        return 1
    fi

    # 比较证书是否更新
    log "开始比较证书是否有更新..."
    log "历史私钥: $old_pri_key"
    log "当前私钥: $new_pri_key"
    log "历史证书: $old_pub_crt"
    log "当前证书: $new_pub_crt"
    
    if [ "$old_pri_key" != "$new_pri_key" ] && [ "$old_pub_crt" != "$new_pub_crt" ]; then
        log "检测到证书有更新!"
        # 更新变量
        pri_key="$new_pri_key"
        pub_crt="$new_pub_crt"
        
        # 保存状态
        saveState
        
        # 同步文件
        if sync_to_all_hosts "$pri_key" "$pub_crt"; then
            log "所有主机证书同步操作已完成"
        else
            log "部分主机证书同步过程中出现错误"
        fi
    else
        log "证书未发生变化,无需同步"
    fi
    
    log "证书检测流程执行完毕"
    log "======================================"
    return 0
}

# 初始化
log "===== SSL证书自动同步脚本启动 ====="
log "=====       Version 1.2       ====="
log "目标证书目录: $target_dir"

# 加载历史状态
loadState

# 主循环
while true; do
    current_time=$(date +'%Y-%m-%d %H:%M:%S')
    log "===== 开始证书检测 (${current_time}) ====="
    checkSSL
    log "本轮检测结束,将在1小时后进行下一次检测"
    log "======================================"
    sleep 3600
    echo -e "\n"
done


评论