群晖利用acme.sh自动申请证书并且自动重载证书的问题解决
前言
21年的时候写了一个在群晖(黑群晖)下利用acme.sh自动申请Let‘s Encrypt的脚本工具 群晖使用acme自动申请Let‘s Encrypt证书脚本,自动申请虽然解决了,但是自动重载一直是一个问题,本人也懒,一想到去跟踪重载过程就头大,所以就一直没有更新,每一次证书快过期了,就得手动登陆进去把全部证书的绑定替换两遍,因为在网页端手动更换绑定会触发证书重载的刷新,替换两次是因为第一次换成错的,第二次换成正确的绑定,仅此而已。
虽然拖了几年一直没跟踪这个自动化的实现过程,但是心里始终是有点东西没放下,今天终于解决了!当然解决这个问题的动因还是因为我的公司域名需要加入到以前的脚本当中,本来只是想把脚本做个优化,支持多个账户同时申请,想来想去,一不做二不休,还是一次性把自动重载的过程一起解决了!!!
原理
自动重载的过程分为这么几步:
- 1、重载使用ssl证书的
系统服务
- 2、重载
反向代理
的ssl证书 - 3、重载
WebStation
的ssl证书 - 4、特殊处理的系统服务
- 5、重载web服务
原理介绍
- 1、重载使用ssl证书的系统服务
必要的系统服务包括以下几项:
在INFO文件的绑定中,看到的service_map["DirectoryServer"]="pkgctl-DirectoryServer" service_map["MailServer"]="pkgctl-MailServer" service_map["ReverseProxy"]="nginx" service_map["SynologyDrive"]="pkgctl-SynologyDrive" service_map["WebDAVServer"]="pkgctl-WebDAVServer" service_map["WebStation"]="pkgctl-WebStation" service_map["smbftpd"]="ftpd"
DirectoryServer
服务名,真实调用需要使用的服务名称为pkgctl-DirectoryServer
重载的命令:
其他几项也是同样的,不过多介绍,特殊的服务有1个:/usr/syno/sbin/synoservice --reload "pkgctl-DirectoryServer"
WebDAV
,这个放到步骤四介绍 - 2、重载反向代理的ssl证书
反向代理的重载过程分为两步:-
2.1、拷贝证书到反向代理证书存放的目录
# 复制证书文件 cp -f "/usr/syno/etc/certificate/_archive/xxxxxx/cert.pem" "/usr/syno/etc/certificate/ReverseProxy/$uuid/cert.pem" cp -f "/usr/syno/etc/certificate/_archive/xxxxxx/privkey.pem" "/usr/syno/etc/certificate/ReverseProxy/$uuid/privkey.pem" cp -f "/usr/syno/etc/certificate/_archive/xxxxxx/fullchain.pem" "/usr/syno/etc/certificate/ReverseProxy/$uuid/fullchain.pem"
-
2.2、调用反向代理的重载指令
/usr/libexec/certificate.d/ReverseProxy "$uuid"
ps:
$uuid
进入到/usr/syno/etc/certificate/ReverseProxy/
目录下直接查看文件夹名称就是了,另外INFO的配置信息中也存在有$uuid
-
- 3、重载
WebStation
的ssl证书-
3.1 拷贝证书文件到目录:
/usr/local/etc/certificate/WebStation/$uuid
下cp -f "/usr/syno/etc/certificate/_archive/xxxxxx/cert.pem" "/usr/local/etc/certificate/WebStation/$uuid/cert.pem" cp -f "/usr/syno/etc/certificate/_archive/xxxxxx//privkey.pem" "/usr/local/etc/certificate/WebStation/$uuid/privkey.pem" cp -f "/usr/syno/etc/certificate/_archive/xxxxxx//fullchain.pem" "/usr/local/etc/certificate/WebStation/$uuid/fullchain.pem"
-
3.2 重载
WebStation
证书命令/usr/local/libexec/certificate.d/WebStation "$uuid"
vhost
类型的$uuid
和反向代理类型的$uuid
区别就是以vhost_
开头 -
- 4、特殊处理的系统服务
WebDAV
是一个特殊的服务,和其他系统服务的重载方式不同,如下:/var/packages/WebDAVServer/target/scripts/synowebdavserver_httpd_control.sh stop sleep 2 /var/packages/WebDAVServer/target/scripts/synowebdavserver_httpd_control.sh start
- 5、重载web服务
重启web服务,就看各位使用的是apache还是nginx了,我使用的nginx所以对于apache也没做测试和跟进,有需要的同学抛砖引玉,自行排查一下:/usr/syno/sbin/synoservice --reload nginx
脚本代码片段
大概过程就是如上几个步骤,有补充的同学请留言,下面是我的代码片段。这次我只放了片段出来,是因为我今天发现我的群晖上的wget不支持https了,就连curl也不能正确使用Tslv1.2库,本来想源码编译openssl库升级一下,结果发现我的设备能安装的gcc版本又太低,无奈之下我又拿golang写了个后端,放到另外一台机器上跑,完整的脚本代码需要配合后端运行,大概的逻辑是:
后端定时任务申请证书并保存且提供api给群晖下载证书 -> 群晖定时任务下载证书到本地 -> 自动部署并重载证书
因为临时抓起来的事情,后端功能仅限于自己使用,我担心安全意识薄弱的同学拿到自己的公网服务器上部署,那就完蛋了。我的部署都在局域网内,像这种不需要对公网开放的服务类项目,够用就行,所以暂时就不发全部的代码到帖子上了,下面这段代码,大家拿去和我21年那篇帖子的代码一结合,我相信就能解决单用户申请ssl证书
的需求了。
如果大家强烈要求我发放完整代码的话,可以给我留言。
自动部署并重载ssl证书的代码片段
# 获取脚本所在目录的绝对路径
INFO_DIR="/usr/syno/etc/certificate/_archive"
INFO_FILE="/usr/syno/etc/certificate/_archive/INFO"
# 重新加载证书
ReloadCerts() {
echo "开始重新加载证书..."
# 定义服务名称映射
declare -A service_map
service_map["DirectoryServer"]="pkgctl-DirectoryServer"
service_map["MailServer"]="pkgctl-MailServer"
service_map["ReverseProxy"]="nginx"
service_map["SynologyDrive"]="pkgctl-SynologyDrive"
service_map["WebDAVServer"]="pkgctl-WebDAVServer"
service_map["WebStation"]="pkgctl-WebStation"
service_map["smbftpd"]="ftpd"
# 获取所有需要重载的服务名称
services=$(jq -r '
. as $root |
[
to_entries[] |
select(.value.services != null) |
.value.services[] |
select(.service != null) |
.subscriber
] | unique[]
' "$INFO_FILE")
# 重载每个服务
for service in $services; do
if [ "${service_map[$service]}" != "" ]; then
actual_service="${service_map[$service]}"
echo "重载服务: $service (${actual_service})"
if [ -f "/usr/syno/sbin/synoservice" ]; then
# 尝试重载服务
/usr/syno/sbin/synoservice --reload "$actual_service" || {
echo "尝试重启服务: $actual_service"
/usr/syno/sbin/synoservice --restart "$actual_service"
}
# 如果是 nginx,给它一点时间完成重载
if [ "$actual_service" = "nginx" ]; then
sleep 2
fi
fi
else
echo "跳过未知服务: $service"
fi
done
# 更新并重载反向代理和 vhost 证书
echo "开始处理反向代理和 vhost 证书..."
# 从 INFO 文件中获取证书和服务的映射关系
jq -r '
. as $root |
to_entries[] |
select(.value.services != null) |
.key as $cert_name |
.value.services[] |
select(.subscriber == "ReverseProxy" or .subscriber == "WebStation") |
[$cert_name, .service, .subscriber] |
@tsv
' "$INFO_FILE" | while IFS=$'\t' read -r cert_name service_uuid subscriber; do
# 跳过空值
if [ -z "$cert_name" ] || [ -z "$service_uuid" ]; then
continue
fi
# 源证书目录
cert_dir="$INFO_DIR/$cert_name"
if [[ "$service_uuid" == vhost_* ]] && [ "$subscriber" == "WebStation" ]; then
# 处理 vhost 证书
vhost_dir="/usr/local/etc/certificate/WebStation/$service_uuid"
if [ -d "$cert_dir" ]; then
echo "更新 vhost 证书 $cert_name 到 UUID: $service_uuid"
# 确保目标目录存在
mkdir -p "$vhost_dir"
# 复制证书文件
cp -f "$cert_dir/cert.pem" "$vhost_dir/cert.pem"
cp -f "$cert_dir/privkey.pem" "$vhost_dir/privkey.pem"
cp -f "$cert_dir/fullchain.pem" "$vhost_dir/fullchain.pem"
# 设置正确的权限
chmod 400 "$vhost_dir"/*.pem
# 重载 vhost 证书
echo "重载 vhost 证书 UUID: $service_uuid"
/usr/local/libexec/certificate.d/WebStation "$service_uuid"
sleep 1
fi
elif [ "$subscriber" == "ReverseProxy" ]; then
# 处理反向代理证书
proxy_dir="/usr/syno/etc/certificate/ReverseProxy/$service_uuid"
if [ -d "$cert_dir" ] && [ -d "$proxy_dir" ]; then
echo "更新反向代理证书 $cert_name 到 UUID: $service_uuid"
# 确保目标目录存在
mkdir -p "$proxy_dir"
# 复制证书文件
cp -f "$cert_dir/cert.pem" "$proxy_dir/cert.pem"
cp -f "$cert_dir/privkey.pem" "$proxy_dir/privkey.pem"
cp -f "$cert_dir/fullchain.pem" "$proxy_dir/fullchain.pem"
# 设置正确的权限
chmod 400 "$proxy_dir"/*.pem
# 重载反向代理证书
echo "重载反向代理证书 UUID: $uuid"
/usr/libexec/certificate.d/ReverseProxy "$uuid"
sleep 1
fi
fi
done
# 检查并重启 WebDAV 服务
if [ -f "/var/packages/WebDAVServer/scripts/start-stop-status" ]; then
echo "检查 WebDAV 服务..."
if /var/packages/WebDAVServer/scripts/start-stop-status status >/dev/null 2>&1; then
echo "重启 WebDAV 服务..."
/var/packages/WebDAVServer/target/scripts/synowebdavserver_httpd_control.sh stop
sleep 2
/var/packages/WebDAVServer/target/scripts/synowebdavserver_httpd_control.sh start
sleep 2
if ! /var/packages/WebDAVServer/scripts/start-stop-status status >/dev/null 2>&1; then
echo "警告:WebDAV 服务可能未正确启动"
else
echo "WebDAV 服务已成功重启"
fi
else
echo "WebDAV 服务未运行,跳过重启"
fi
fi
# 最后再次重载 nginx 以确保所有更改生效
if [ -f "/usr/syno/sbin/synoservice" ]; then
echo "最后重载 nginx 服务..."
/usr/syno/sbin/synoservice --reload nginx
fi
echo "证书重新加载完成"
return 0
}
case "$1" in
*)
ReloadCerts
;;
esac