总算抽空把这个烦人的小问题解决了, 用最小的成本解决最大的需求还是值得的.
环境架构
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)
这个麻烦一些, 需要绕点弯路, 借助
1panel的 API , 主动上传更新覆盖已托管的证书, 并重载 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)"