PVE 下 SSL 证书签发 与 全子系统同步

2026-01-01
2分钟阅读时长
915字
阅读
acme , pve , linux

总算抽空把这个烦人的小问题解决了, 用最小的成本解决最大的需求还是值得的.

环境架构

AIO 物理机 主系统 PVE

子系统部署了 旁路由, 智能家居, Docker, 飞牛, 群晖 等等

其中 Docker 子系统的职责颇为重要, 承载更多细化三级子系统服务:

比如 动态DNS, 反向代理, gitlab, SSL管理, AI服务, MCP, 各种测试 等等

SSL维护

众多内网子系统, 要和谐稳定共存, 避不开个性化访问入口(域名访问)

大部分子系统考虑安全性, 是不做公网解析和内网穿透的, 入口以 .local 即可够用

仅 极少数服务要支持外网穿透, 则 SSL证书自动化维护和同步是必须的

证书签发

Docker 子系统部署 acme.sh 即可,

个人喜欢安全隔离优先, 便捷其次

对安全不敏感的话, 也可以 acme 部署在 PVE 物理机之上 (不推荐)

编排示例:

挂载宿主机的 /data/docker/acme.sh 目录 到 容器 /acme.sh, 方便后续 安装 和 同步 等

# docker-compose.yml
services:
    acme-sh:
        container_name: acme.sh
        image: neilpang/acme.sh:latest
        network_mode: host
        command: daemon
        stdin_open: true
        tty: true
        environments:
          # DNS API
          - DP_Id=00000
          - DP_Key=0000000000000000

          # deploy hook - synology DSM
          - SYNO_USERNAME=xxxxx
          - SYNO_PASSWORD="xxxxx"
          - SYNO_CREATE=1
          - SYNO_CERTIFICATE=xxxxx

          # deploy hook - proxmoxve
          - DEPLOY_PROXMOXVE_USER='xxx'
          - DEPLOY_PROXMOXVE_USER_REALM='pam'
          - DEPLOY_PROXMOXVE_API_TOKEN_NAME='xxxx'
          - DEPLOY_PROXMOXVE_API_TOKEN_KEY='xxxxxxxxx'
        volumes:
            - /data/docker/acme.sh:/acme.sh
            - /etc/localtime:/etc/localtime

配置HOOK

SSH 登录 docker 子系统, 借助 acme.sh 自带指令, 配置部署钩子

同时也配置了每次续签后新证书的 安装路径 (不存在, 手工创建即可) 每次续签后, 会自动执行 同步脚本 (ssl_deploy_zfkun.com.sh)

docker exec acme.sh acme.sh --install-cert \
    -d zfkun.com \
    -d '*.zfkun.com' \
    --key-file /acme.sh/install/zfkun.com/privkey.pem \
    --fullchain-file /acme.sh/install/zfkun.com/fullchain.pem \
    --reloadcmd "sh /acme.sh/ssl_deploy_zfkun.com.sh"

同步脚本

由于 acme.sh 的 deploy hook 仅支持配置1个, 如果希望 deploy 多个, 就只能借助 reloadcmd 触发 自定义脚本 实现

比如, 我的需求:

  • 同步 给 群晖

    acme.sh --deploy -d zfkun.com --deploy-hook synology_dsm

  • 同步 给 PVE

    acme.sh --deploy -d zfkun.com --deploy-hook proxmoxve

  • 同步 给 1Panel (并重载 nginx)

    这个麻烦一些, 需要绕点弯路, 借助 1panelAPI , 主动上传更新覆盖已托管的证书, 并重载 nginx

    其中需要手工确认的几个参数:

    • PANEL_URL: 1Panel 子系统的内网访问地址
    • API_KEY: 在 管理后台 - 面板设置 - 面板 - API接口 设置获取
    • SSL_ID: 在 网站 - 证书 列表里找到需要更新的目标证书, 浏览器打开 开发者工具(F12), 点击 详情 后, 通过查看 XHR 请求 获取ID (请求URL会是 http://xxxxx/api/v1/websites/ssl/1 这种地址, 其中 1 就是ID)
#!/bin/sh
set -e

log() {
  echo "[$(date '+%F %T')] $*"
}

PANEL_URL="http://192.168.1.99:123456"
API_KEY="xxxxx"
SSL_DOMAIN="zfkun.com"
SSL_ID=1

log "Renew detected for domain: $SSL_DOMAIN"

CERT_FILE="/acme.sh/install/${SSL_DOMAIN}/fullchain.pem"
KEY_FILE="/acme.sh/install/${SSL_DOMAIN}/privkey.pem"

if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then
  log "ERROR: cert/key not found for $SSL_DOMAIN"
  exit 1
fi

log "Starting deploy for domain $SSL_DOMAIN (sslID=$SSL_ID)"

# ========= step 1: deploy to nas =========
log "Deploy to synology_dsm"
acme.sh --deploy -d "$SSL_DOMAIN" --deploy-hook synology_dsm

# ========= step 2: deploy to pve =========
log "Deploy to proxmoxve"
acme.sh --deploy -d "$SSL_DOMAIN" --deploy-hook proxmoxve

# ========= step 3: update 1Panel =========
log "Deploy to 1Panel"

# 这个很重要, API执行在宿主机中, 文件路径必须是宿主机下的路径
DOCKER_VOLUME="/data/docker"
TS=$(date +%s)
TOKEN=$(printf "1panel%s%s" "$API_KEY" "$TS" | md5sum | awk '{print $1}')

curl -fsS \
  "$PANEL_URL/api/v1/websites/ssl/upload" \
  -H "1Panel-Token: $TOKEN" \
  -H "1Panel-Timestamp: $TS" \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  --data-binary @- <<EOF
{
  "sslID": $SSL_ID,
  "type": "local",
  "certificate": "",
  "certificatePath": "${DOCKER_VOLUME}${CERT_FILE}",
  "privateKey": "",
  "privateKeyPath": "${DOCKER_VOLUME}${KEY_FILE}",
  "description": "acme.sh auto renew $(date +%F)"
}
EOF
echo ""

log "All deploy steps finished for $SSL_DOMAIN (sslID=$SSL_ID)"
Avatar
zfkun 喜欢游戏、热爱技术、追求艺术、崇尚自由、渴望精彩、最爱唠叨