首次提交
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
主服务(CertCenter)
|
||||
|
||||
负责 acme.sh 申请与续签、维护 version 文件、提供 HTTPS 下载接口,以及生成“拉取脚本”。
|
||||
|
||||
拉取脚本(deploy-cert.sh)
|
||||
|
||||
部署到每台服务器,通过 cron/systemd timer 定时执行:检查版本 → 下载 → 比对 → 原子替换 → nginx -t → reload。
|
||||
|
||||
这样每台业务服务器 不需要安装 Python、FastAPI、acme.sh 或任何 Agent,只需要一个 Shell 脚本和 cron。
|
||||
|
||||
### 最终架构
|
||||
|
||||
CertCenter
|
||||
|
||||
acme.sh · AliDNS · version · HTTPS 下载
|
||||
|
||||
HTTPS 拉取
|
||||
|
||||
VPS-A
|
||||
|
||||
blog.example.com
|
||||
|
||||
cron → deploy-cert.sh → reload nginx
|
||||
|
||||
VPS-B
|
||||
|
||||
api.example.com
|
||||
|
||||
cron → deploy-cert.sh → reload nginx
|
||||
|
||||
VPS-C
|
||||
|
||||
git.example.com
|
||||
|
||||
cron → deploy-cert.sh → reload nginx
|
||||
|
||||
### CertCenter 目录
|
||||
|
||||
```
|
||||
certcenter/
|
||||
├── app.py # FastAPI
|
||||
├── config.yaml
|
||||
├── certs/
|
||||
│ └── example.com/
|
||||
│ ├── fullchain.pem
|
||||
│ ├── private.key
|
||||
│ ├── version
|
||||
│ └── metadata.json
|
||||
└── templates/
|
||||
└── deploy-cert.sh.j2 # 脚本模板
|
||||
```
|
||||
|
||||
### 主服务提供的接口
|
||||
|
||||
| 接口 | 用途 |
|
||||
| -------------------------------- | ---------------- |
|
||||
| GET /api/version/{domain} | 返回当前版本号 |
|
||||
| GET /api/cert/{domain}/fullchain | 下载 fullchain.pem |
|
||||
| GET /api/cert/{domain}/private | 下载 private.key |
|
||||
| GET /api/script/{domain} | 动态生成部署脚本 |
|
||||
|
||||
所有接口都使用:
|
||||
|
||||
http
|
||||
|
||||
```
|
||||
Authorization: Bearer <TOKEN>
|
||||
```
|
||||
|
||||
### 版本文件
|
||||
|
||||
`/srv/certs/example.com/version`
|
||||
|
||||
```
|
||||
20260718143000
|
||||
```
|
||||
|
||||
续签成功后,CertCenter 只需要:
|
||||
|
||||
Bash
|
||||
|
||||
```
|
||||
date -u +%Y%m%d%H%M%S > /srv/certs/example.com/version
|
||||
```
|
||||
|
||||
客户端先请求这个小文件,只有版本变化时才下载证书。
|
||||
|
||||
### 生成的拉取脚本(核心)
|
||||
|
||||
下面就是 CertCenter 返回给服务器的脚本内容。你只需要把它保存为 `/usr/local/bin/deploy-cert.sh`。
|
||||
|
||||
Edit
|
||||
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DOMAIN="example.com"
|
||||
BASE_URL="[https://cert.example.com](https://cert.example.com)"
|
||||
TOKEN="REPLACE_WITH_TOKEN"
|
||||
|
||||
CERT_DIR="/etc/nginx/ssl/example.com"
|
||||
TMP_DIR="/tmp/cert-sync-${DOMAIN}"
|
||||
VERSION_FILE="${CERT_DIR}/.version"
|
||||
RELOAD_CMD="systemctl reload nginx"
|
||||
|
||||
mkdir -p "${CERT_DIR}" "${TMP_DIR}"
|
||||
|
||||
auth=(-H "Authorization: Bearer ${TOKEN}")
|
||||
|
||||
# 1. 获取远端版本
|
||||
|
||||
REMOTE_VERSION=$(curl -fsSL "${auth[@]}"
|
||||
"${BASE_URL}/api/version/${DOMAIN}")
|
||||
|
||||
# 2. 获取本地版本
|
||||
|
||||
LOCAL_VERSION=""
|
||||
if [[ -f "${VERSION_FILE}" ]]; then
|
||||
LOCAL_VERSION=$(cat "${VERSION_FILE}")
|
||||
fi
|
||||
|
||||
# 3. 版本一致则退出
|
||||
|
||||
if [[ "${REMOTE_VERSION}" == "${LOCAL_VERSION}" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 4. 下载新证书
|
||||
|
||||
curl -fsSL "${auth[@]}"
|
||||
"${BASE_URL}/api/cert/${DOMAIN}/fullchain"
|
||||
-o "${TMP_DIR}/fullchain.pem"
|
||||
|
||||
curl -fsSL "${auth[@]}"
|
||||
"${BASE_URL}/api/cert/${DOMAIN}/private"
|
||||
-o "${TMP_DIR}/private.key"
|
||||
|
||||
chmod 600 "${TMP_DIR}/private.key"
|
||||
|
||||
# 5. 备份旧证书
|
||||
|
||||
cp -f "${CERT_DIR}/fullchain.pem" "${CERT_DIR}/fullchain.pem.bak" 2>/dev/null || true
|
||||
cp -f "${CERT_DIR}/private.key" "${CERT_DIR}/private.key.bak" 2>/dev/null || true
|
||||
|
||||
# 6. 原子替换
|
||||
|
||||
mv "${TMP_DIR}/fullchain.pem" "${CERT_DIR}/fullchain.pem"
|
||||
mv "${TMP_DIR}/private.key" "${CERT_DIR}/private.key"
|
||||
|
||||
# 7. 校验 Nginx 配置
|
||||
|
||||
if nginx -t; then
|
||||
echo "${REMOTE_VERSION}" > "${VERSION_FILE}"
|
||||
${RELOAD_CMD}
|
||||
echo "[$(date -Is)] certificate updated: ${REMOTE_VERSION}"
|
||||
else
|
||||
echo "Nginx config test failed, rollback" >&2
|
||||
mv -f "${CERT_DIR}/fullchain.pem.bak" "${CERT_DIR}/fullchain.pem" 2>/dev/null || true
|
||||
mv -f "${CERT_DIR}/private.key.bak" "${CERT_DIR}/private.key" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
脚本已经包含:
|
||||
|
||||
* 版本检查(避免重复下载)
|
||||
|
||||
* 下载证书
|
||||
|
||||
* SHA/内容变化隐式通过版本号控制
|
||||
|
||||
* 原子替换(`mv`)
|
||||
|
||||
* `nginx -t` 校验
|
||||
|
||||
* 失败自动回滚
|
||||
|
||||
* 成功后 `systemctl reload nginx`
|
||||
|
||||
### CertCenter 的“生成脚本”接口
|
||||
|
||||
FastAPI 示例(核心逻辑):
|
||||
|
||||
Python
|
||||
|
||||
Run
|
||||
|
||||
```
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import PlainTextResponse
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import secrets
|
||||
|
||||
app = FastAPI()
|
||||
env = Environment(loader=FileSystemLoader("templates"))
|
||||
|
||||
TOKENS = {
|
||||
"server-a": "tok_xxx",
|
||||
"server-b": "tok_yyy",
|
||||
}
|
||||
|
||||
@app.get("/api/script/{domain}", response_class=PlainTextResponse)
|
||||
def generate_script(domain: str, server: str):
|
||||
token = TOKENS.get(server)
|
||||
if not token:
|
||||
raise HTTPException(404, "unknown server")
|
||||
|
||||
tpl = env.get_template("deploy-cert.sh.j2")
|
||||
return tpl.render(
|
||||
domain=domain,
|
||||
base_url="https://cert.example.com",
|
||||
token=token,
|
||||
cert_dir=f"/etc/nginx/ssl/{domain}",
|
||||
)
|
||||
```
|
||||
|
||||
### 业务服务器首次部署
|
||||
|
||||
只需要执行一次:
|
||||
|
||||
Bash
|
||||
|
||||
```
|
||||
curl -fsSL \
|
||||
"https://cert.example.com/api/script/example.com?server=server-a" \
|
||||
-o /usr/local/bin/deploy-cert.sh
|
||||
|
||||
chmod +x /usr/local/bin/deploy-cert.sh
|
||||
```
|
||||
|
||||
然后添加 cron:
|
||||
|
||||
cron
|
||||
|
||||
```
|
||||
*/30 * * * * /usr/local/bin/deploy-cert.sh >> /var/log/deploy-cert.log 2>&1
|
||||
```
|
||||
|
||||
### 主服务续签流程
|
||||
|
||||
Bash
|
||||
|
||||
```
|
||||
# 每天凌晨执行
|
||||
/root/.acme.sh/acme.sh --cron
|
||||
|
||||
# 如果证书更新,安装到统一目录
|
||||
/root/.acme.sh/acme.sh --install-cert \
|
||||
-d example.com \
|
||||
--key-file /srv/certs/example.com/private.key \
|
||||
--fullchain-file /srv/certs/example.com/fullchain.pem
|
||||
|
||||
# 更新版本号
|
||||
date -u +%Y%m%d%H%M%S > /srv/certs/example.com/version
|
||||
```
|
||||
|
||||
### 为什么这个方案最适合你
|
||||
|
||||
极轻量
|
||||
|
||||
业务服务器只需要 Bash + curl + cron,没有常驻进程。
|
||||
|
||||
安全
|
||||
|
||||
AliDNS AccessKey 只保存在 CertCenter,业务服务器只拿下载 Token。
|
||||
|
||||
自动化
|
||||
|
||||
续签后客户端自动感知版本变化并更新证书。
|
||||
|
||||
易扩展
|
||||
|
||||
新增服务器只需执行一次 curl 下载脚本并添加 cron。
|
||||
Reference in New Issue
Block a user