下面是一个用于批量部署SSL证书到多台服务器的Shell脚本。它通过SSH/SCP将证书和私钥文件分发到远程服务器,设置正确的权限,并重新加载指定的服务(如Nginx、Apache)。脚本支持从文件读取服务器列表、自定义远程路径、用户名和端口,并包含详细的错误处理和日志输出。

bash

!/bin/bash

Script:  deploy_ssl_cert.sh

描述:  批量将SSL证书部署到多台远程服务器

用法:  ./deploy_ssl_cert.sh  -l  <服务器列表文件>  -c  <本地证书文件>  -k  <本地私钥文件>  -d  <远程目录>  [选项]

示例:  ./deploy_ssl_cert.sh  -l  servers.txt  -c  example.com.crt  -k  example.com.key  -d  /etc/nginx/ssl  -s  nginx

set  -euo  pipefail    #  遇到错误立即退出,禁止未定义变量,管道错误检测

默认值

REMOTE_USER=""

REMOTE_PORT=22

REMOTE_DIR=""

SERVICE_NAME=""

CERT_SRC=""

KEY_SRC=""

CERT_DEST_NAME=""

KEY_DEST_NAME=""

SERVER_LIST=""

LOG_FILE="./deploy_ssl.log"

显示帮助信息

usage()  {

        cat  <<EOF

用法:  $0  -l  <服务器列表文件>  -c  <证书文件>  -k  <私钥文件>  -d  <远程目录>  [选项]

必需参数:

        -l  <文件>        服务器列表文件,每行格式:  [user@]host[:port]  (可省略user/port)

        -c  <文件>        本地证书文件路径(如  .crt  .pem)

        -k  <文件>        本地私钥文件路径(如  .key)

        -d  <目录>        远程服务器上的目标目录(如  /etc/nginx/ssl)


可选参数:

        -u  <用户>        远程服务器SSH用户名(覆盖列表中的用户)

        -p  <端口>        远程服务器SSH端口(默认22,可被列表中的端口覆盖)

        -s  <服务>        部署后重新加载的服务名称(如  nginx,  httpd,  apache2)

        -r  <名称>        远程证书文件名(默认与本地证书文件同名)

        -y  <名称>        远程私钥文件名(默认与本地私钥文件同名)

        -h                      显示此帮助信息


服务器列表文件格式:

        注释行以#开头

        example.com

        admin@192.168.1.10

        web1.example.com:2222

        user@server.example.com:2222


示例:

        $0  -l  servers.txt  -c  cert.crt  -k  priv.key  -d  /etc/ssl/certs  -s  nginx

EOF

        exit  1

}

记录日志

log()  {

        echo  "[$(date  '+%Y-%m-%d  %H:%M:%S')]  $1"  |  tee  -a  "$LOG_FILE"

}

检查必需命令是否存在

check_command()  {

        if  !  command  -v  "$1"  &>/dev/null;  then

                log  "错误:  未找到命令  '$1',请安装后重试。"

                exit  1

        fi

}

解析命令行参数

while  getopts  "l:c:k:d:u:p:s:r:y:h"  opt;  do

        case  "$opt"  in

                l)  SERVER_LIST="$OPTARG"  ;;

                c)  CERT_SRC="$OPTARG"  ;;

                k)  KEY_SRC="$OPTARG"  ;;

                d)  REMOTE_DIR="$OPTARG"  ;;

                u)  REMOTE_USER="$OPTARG"  ;;

                p)  REMOTE_PORT="$OPTARG"  ;;

                s)  SERVICE_NAME="$OPTARG"  ;;

                r)  CERT_DEST_NAME="$OPTARG"  ;;

                y)  KEY_DEST_NAME="$OPTARG"  ;;

                h)  usage  ;;

                *)  usage  ;;

        esac

done

验证必需参数

if  [  -z  "$SERVER_LIST"  ]  ||  [  -z  "$CERT_SRC"  ]  ||  [  -z  "$KEY_SRC"  ]  ||  [  -z  "$REMOTE_DIR"  ];  then

        log  "错误:  缺少必需参数。"

        usage

fi

检查本地文件是否存在

if  [  !  -f  "$CERT_SRC"  ];  then

        log  "错误:  证书文件  '$CERT_SRC'  不存在。"

        exit  1

fi

if  [  !  -f  "$KEY_SRC"  ];  then

        log  "错误:  私钥文件  '$KEY_SRC'  不存在。"

        exit  1

fi

检查服务器列表文件

if  [  !  -f  "$SERVER_LIST"  ];  then

        log  "错误:  服务器列表文件  '$SERVER_LIST'  不存在。"

        exit  1

fi

检查必要的远程命令

check_command  "ssh"

check_command  "scp"

设置远程文件名(如果未指定则使用本地文件名)

CERT_DEST_NAME="${CERT_DEST_NAME:-$(basename  "$CERT_SRC")}"

KEY_DEST_NAME="${KEY_DEST_NAME:-$(basename  "$KEY_SRC")}"

log  "开始批量部署  SSL  证书"

log  "证书文件:  $CERT_SRC  ->  $CERT_DEST_NAME"

log  "私钥文件:  $KEY_SRC  ->  $KEY_DEST_NAME"

log  "目标目录:  $REMOTE_DIR"

log  "服务重载:  ${SERVICE_NAME:-无}"

读取服务器列表,逐行处理

deploy_fail=0

deploy_success=0

while  IFS=  read  -r  line  ||  [  -n  "$line"  ];  do

        跳过空行和注释行

        if  [[  -z  "$line"  ]]  ||  [[  "$line"  =~  ^[[:space:]]*#  ]];  then

                continue

        fi

        去除首尾空格

        line="$(echo  "$line"  |  xargs)"

        

        #  解析行中的  user@host:port

        user="$REMOTE_USER"

        host=""

        port="$REMOTE_PORT"

        检查是否包含  '@'

        if  [[  "$line"  =~  @  ]];  then

                user_part="${line%%@*}"

                host_part="${line#*@}"

                if  [  -n  "$user_part"  ];  then

                        user="$user_part"

                fi

        else

                host_part="$line"

        fi

        检查  host_part  是否包含端口  ':'

        if  [[  "$host_part"  =~  :[0-9]+$  ]];  then

                host="${host_part%:*}"

                port="${host_part##*:}"

        else

                host="$host_part"

        fi


        if  [  -z  "$host"  ];  then

                log  "警告:  跳过无效行  '$line'"

                continue

        fi

        构建  SSH  连接字符串

        if  [  -n  "$user"  ];  then

                ssh_target="$user@$host"

        else

                ssh_target="$host"

        fi

        ssh_cmd="ssh  -p  $port  -o  ConnectTimeout=10  $ssh_target"

        scp_cmd="scp  -P  $port  -o  ConnectTimeout=10"

        log  "正在处理服务器:  $ssh_target:$port"

        测试  SSH  连接

        if  !  $ssh_cmd  "echo  OK"  &>/dev/null;  then

                log  "错误:  无法连接到  $ssh_target:$port,跳过。"

                deploy_fail=$((deploy_fail  +  1))

                continue

        fi

        1.  在远程创建临时目录

        temp_dir="/tmp/ssl_deploy_$$"

        if  !  $ssh_cmd  "mkdir  -p  $temp_dir"  &>/dev/null;  then

                log  "错误:  无法在  $ssh_target  上创建临时目录  $temp_dir"

                deploy_fail=$((deploy_fail  +  1))

                continue

        fi

        2.  复制证书和私钥到临时目录

        log  "    复制证书文件..."

        if  !  $scp_cmd  "$CERT_SRC"  "$ssh_target:$temp_dir/$CERT_DEST_NAME"  &>/dev/null;  then

                log  "    错误:  证书文件复制失败"

                $ssh_cmd  "rm  -rf  $temp_dir"  &>/dev/null

                deploy_fail=$((deploy_fail  +  1))

                continue

        fi

        log  "    复制私钥文件..."

        if  !  $scp_cmd  "$KEY_SRC"  "$ssh_target:$temp_dir/$KEY_DEST_NAME"  &>/dev/null;  then

                log  "    错误:  私钥文件复制失败"

                $ssh_cmd  "rm  -rf  $temp_dir"  &>/dev/null

                deploy_fail=$((deploy_fail  +  1))

                continue

        fi

        3.  远程安装文件到目标目录,设置权限

        log  "    安装文件到  $REMOTE_DIR  并设置权限..."

        remote_commands="

                set  -e

                创建目标目录(如果不存在)

                sudo  mkdir  -p  $REMOTE_DIR

                移动文件

                sudo  mv  $temp_dir/$CERT_DEST_NAME  $REMOTE_DIR/

                sudo  mv  $temp_dir/$KEY_DEST_NAME  $REMOTE_DIR/

                设置所有权(通常为  root)

                sudo  chown  root:root  $REMOTE_DIR/$CERT_DEST_NAME  $REMOTE_DIR/$KEY_DEST_NAME

                设置权限:证书可读,私钥仅所有者可读写

                sudo  chmod  644  $REMOTE_DIR/$CERT_DEST_NAME

                sudo  chmod  600  $REMOTE_DIR/$KEY_DEST_NAME

                清理临时目录

                rmdir  $temp_dir  2>/dev/null  ||  true

        "

        if  !  $ssh_cmd  "$remote_commands"  &>/dev/null;  then

                log  "    错误:  安装文件或设置权限失败"

                $ssh_cmd  "rm  -rf  $temp_dir"  &>/dev/null

                deploy_fail=$((deploy_fail  +  1))

                continue

        fi

        4.  如果需要,重新加载服务

        if  [  -n  "$SERVICE_NAME"  ];  then

                log  "    重新加载服务:  $SERVICE_NAME"

                reload_commands="

                        if  command  -v  systemctl  &>/dev/null  &&  systemctl  is-active  $SERVICE_NAME  &>/dev/null;  then

                                sudo  systemctl  reload  $SERVICE_NAME

                        elif  command  -v  service  &>/dev/null  &&  service  $SERVICE_NAME  status  &>/dev/null;  then

                                sudo  service  $SERVICE_NAME  reload

                        elif  [  -f  /etc/init.d/$SERVICE_NAME  ];  then

                                sudo  /etc/init.d/$SERVICE_NAME  reload

                        else

                                echo  '无法确定服务管理方式,请手动重载'  >&2

                                exit  1

                        fi

                "

                if  !  $ssh_cmd  "$reload_commands"  &>/dev/null;  then

                        log  "    警告:  服务重载失败,请手动检查"

                        #  不影响整体成功计数,但记录

                fi

        fi


        log  "    部署成功完成"

        deploy_success=$((deploy_success  +  1))


done  <  "$SERVER_LIST"

汇总结果

log  "========================================="

log  "部署完成:  成功  $deploy_success,  失败  $deploy_fail"

if  [  $deploy_fail  -gt  0  ];  then

        log  "部分服务器部署失败,请检查日志:  $LOG_FILE"

        exit  1

else

        log  "所有服务器部署成功。"

        exit  0

fi

使用说明

1.  保存脚本    

      将上述代码保存为  `deploy_ssl_cert.sh`,并赋予执行权限:

      bash

      chmod  +x  deploy_ssl_cert.sh

2.  准备服务器列表文件    

      创建一个文本文件(如  `servers.txt`),每行一台服务器,支持以下格式:

      注释行

      example.com

      admin@192.168.1.10

      web1.example.com:2222

      user@server.example.com:2222

3.  准备证书文件    

      确保本地已有要分发的证书文件(如  `example.com.crt`)和私钥文件(如  `example.com.key`)。

4.  运行脚本    

      必需参数:`-l`  列表文件,`-c`  证书文件,`-k`  私钥文件,`-d`  远程目标目录。    

      可选参数:`-u`  指定默认用户名,`-p`  默认端口,`-s`  服务名称(如  nginx),`-r`/`-y`  远程文件名。

      示例:

      ```bash

      ./deploy_ssl_cert.sh  -l  servers.txt  -c  example.com.crt  -k  example.com.key  -d  /etc/nginx/ssl  -s  nginx

5.  查看日志  

      脚本执行过程会输出到终端并同时写入  `./deploy_ssl.log`  文件。

注意事项

SSH  免密登录:脚本依赖  `ssh`  和  `scp`,请确保从执行脚本的机器能通过  SSH  密钥登录所有目标服务器(或已配置密码缓存如  `ssh-agent`)。

sudo  权限:远程用户需要拥有  `sudo`  权限,且最好配置为  `NOPASSWD`,否则脚本可能会因等待密码而卡住。如果无法使用无密码  sudo,可以考虑在远程命令中嵌入密码(不推荐)或使用  `expect`  工具,但本脚本未做此处理。

路径和权限:脚本会自动创建目标目录(如果不存在),并将证书权限设为  `644`、私钥设为  `600`,所有者设为  `root:root`。您可根据实际情况修改  `remote_commands`  部分。

服务重载:支持  `systemctl`、`service`  以及  `/etc/init.d/`  三种方式管理服务。如果重载失败,脚本会给出警告但不会中断部署。

错误处理:脚本采用  `set  -e`,但通过子shell或命令组避免了部分错误导致整个脚本退出。部署失败的主机会被跳过并计数。

扩展建议

若需要处理通配符SSL证书或多个证书文件(如中间证书),可修改脚本接受多个  `-c`  参数或使用通配符。

对于更复杂的环境,建议使用  Ansible、Salt  等配置管理工具,本脚本适用于轻量级批量部署场景。