向晚时光

acmeDeliver V3: SSL证书分发工具

笔记 ·

证书的签发管理应该是多台 VPS 玩家的烦恼。当然,像 MJJ 一样买小鸡只挂探针的当我没说。 我手上的小鸡不少,为了省事喜欢签通配符证书,可站点分散在不同机器上,每台都得单独签一遍。后来在网上搜到了宝藏博主七夏浅笑写的 acmeDeliver,正好解决这件事。

当初就是照着原作者的教程《使用 acme.sh 部署通配符证书申请与分发服务》搭起来的。美美哒用了一段时间,还是觉得不够懒,每台机器都要配 crontab 定时去拉。好在这两年 AI 足够发达,我前后换着用了好几个当时最强的模型,按自己的需求 vibe coding 出了 V3,这里记录一下。

项目地址

原版 julydate/acmeDeliver
原作者教程 《使用 acme.sh 部署通配符证书申请与分发服务》
我魔改的 V3 Catker/acmeDeliver

先感谢原作者七夏浅笑,还有给原版贡献了 client.sh 客户端脚本的 Raoby。V3 是在原版的基础上改出来的。

关于原版

原作者的出发点在文章里讲得很清楚。他不想把 DNS 服务商的 API Key 放到每台服务器上,也不想每台机器都配一遍 acme.sh,干脆在一台机器上签发,再起一个轻量服务把证书分发出去。服务端用 Go 写成单个二进制,不用装运行环境,跨平台也方便。

  • 服务端是一个 Go 写的小 Web 服务,把 acme.sh 装好的证书目录通过 HTTP 提供出去,用"密码 + 时间戳"鉴权,可以开 TLS
  • 客户端是一个 Bash 脚本 client.sh,配合 cron 定时请求服务端,比对证书的更新时间戳,有新证书就下载,还能顺手部署到 Nginx 或 Apache

一台机器签发,其余机器定时来拉,DNS API 密钥只放在签发机上,这正是我想要的。

关于 V3

V3 最大的改动是把"客户端定时拉取"换成了"服务端主动推送"。

  • WebSocket 推送。客户端以 daemon 形式常驻,和服务端保持一条 WebSocket 长连接。服务端用 fsnotify 盯着证书目录,证书一更新,就推给订阅了这个域名的客户端
  • 按域名订阅。每个客户端只订阅自己用得到的域名,支持 *.example.com 这样的通配符,写 * 就是全部订阅
  • 断线后补推。客户端重连后会自动同步一次,另外每小时还有一次定时同步,离线期间错过的更新都能补回来
  • 交付回执。客户端收到证书后回一个 ACK,服务端记下每台机器每个域名最近一次的结果,--status 一查就知道谁拿到了
  • 部署前校验。写盘前先检查证书和私钥是否配对,对不上就拒收,免得坏证书部署上去把服务搞挂
  • reload 失败重试。reload 命令失败后会退避重试,30 秒起步,最多 5 次,省得证书换了服务还挂着旧的

原版的 Pull 模式也留着,客户端二进制一次性拉取照样能用,习惯 cron 的可以继续这么玩。

整体结构大概是这样。

签发机(签发 + 服务端)
     acme.sh 走 DNS 验证签发泛域名证书
                      │
     --install-cert 安装到 /home/acme/<域名>/
     --reloadcmd 写入 time.log
                      │
         acmedeliver-server 监控目录变化
                      │  WebSocket 推送(wss)
        ┌─────────────┼─────────────┐
     小鸡 A         小鸡 B         其他小鸡 …
  (daemon)      (daemon)
                  部署后 reload Nginx

1. 证书签发

这部分基本照搬原作者教程。acme.sh 的安装、CA 选择、ECC 证书、续期通知,原文写得很细,这里不重复。我只是包了一个小脚本,输入域名后走 DNS 验证签泛域名证书,再装到服务端的证书目录。

#!/bin/bash
echo -n "Please Enter your Domain:"
read DOMAIN

acme.sh --issue --dns dns_cf -d "${DOMAIN}" -d "*.${DOMAIN}"

mkdir -p /home/acme/${DOMAIN}
acme.sh --install-cert -d "${DOMAIN}" \
  --cert-file      /home/acme/${DOMAIN}/cert.pem \
  --key-file       /home/acme/${DOMAIN}/key.pem \
  --fullchain-file /home/acme/${DOMAIN}/fullchain.pem \
  --reloadcmd      "date +%s > /home/acme/${DOMAIN}/time.log"

最后一行别漏。acme.sh 每次续期装完证书都会执行 --reloadcmd,这里把当前时间戳写进 time.log。这个约定也是从原版沿用下来的。服务端发现证书文件有变化,等 5 秒防抖就推出去,客户端则拿 time.log 里的时间戳判断自己手上那份是不是最新的。

dns_cf 是 Cloudflare 的 DNS 插件,换成自己域名服务商对应的就行,支持列表见 acme.sh 文档。

2. acmeDeliver 服务端

从 Releases 下载对应架构的二进制,先生成一份配置。

./acmedeliver-server --gen-config > config.yaml

我改了这几项。

port: "9090"
base_dir: "/home/acme/"
key: "一个足够长的随机密码"

tls: true
tls_port: "9443"
cert_file: "/home/acme/example.com/fullchain.pem"
key_file: "/home/acme/example.com/key.pem"

ip_whitelist: ""   # 可选

强烈建议开 TLS,毕竟传的是私钥。开了 TLS 以后默认只监听 9443,明文端口就不开了。

TLS 证书直接用 acme.sh 签出来的那张,续期后服务端会自己重新加载,不用重启。

再写个 systemd 服务让它常驻。

[Unit]
Description=acmeDeliver Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/home/acme/acmedeliver-server -c "/home/acme/config.yaml"
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

3. acmeDeliver 客户端

客户端配置的根节点必须是 client:,不然会直接报错。

client:
  server: "wss://your-server:9443"
  password: "和服务端一致的密码"
  workdir: "/var/lib/acmedeliver"   # 证书保存在 <workdir>/<域名>/ 下,默认就是这个

  # 收到证书后执行的命令;域名没有匹配的 sites 时也会执行
  default_reload_cmd: "systemctl reload nginx"

  daemon:
    enabled: true
    reconnect_interval: 30
    reload_debounce: 5

  # 订阅全部域名;也可以只写需要的,如 "example.com"、"*.example.org"
  subscribe:
    - "*"

  # 可选:需要把证书复制到别的路径时再配
  sites:
    - domain: "example.com"
      cert_path: "/etc/realm/ssl/cert.pem"
      key_path: "/etc/realm/ssl/key.pem"
      fullchain_path: "/etc/realm/ssl/fullchain.pem"
      reloadcmd: "systemctl restart realm"

我自己有两种用法。一台小鸡跑着宝塔面板,站点的 nginx 直接引用 /var/lib/acmedeliver/<域名>/ 里的证书,配一行 default_reload_cmd: "systemctl reload nginx" 就够了,证书推过来,客户端顺手 reload Nginx。另一种是 realm 这类要求证书放在固定路径的,就在 sites 里写上目标路径和对应的 reloadcmd,客户端会复制过去再执行。

客户端也用 systemd 常驻,把 ExecStart 换成 acmedeliver-client -c /path/to/config.yaml --daemon。配置里写了 daemon.enabled: true 的话,--daemon 可以省掉。

4. 状态查询

随便找一台客户端执行。

./acmedeliver-client -s wss://your-server:9443 -k 你的密码 --status

在线的客户端、每个域名证书还剩几天、每台机器最近一次有没有收到,都能看到。

ffa896b719296e52289705f988ffff3e

写在最后

我是个菜鸡,整个项目完全是 vibe coding 出来的,肯定还有不少问题和细节没处理好。再次感谢原作者七夏浅笑。

acme.sh acmeDeliver ssl

添加新评论

↑ 回到顶部