架构设计¶
acmecast 是一个 Cargo workspace:10 个内部 crate(均不发布到 crates.io)加一个
React 前端 SPA。可执行二进制只有一个:acmecast-server。
Crate 划分与依赖方向¶
flowchart LR
core[acmecast-core<br/>配置 / 加密 / 错误]
cert[acmecast-cert<br/>证书解析与转换]
acme[acmecast-acme<br/>ACME 客户端防腐层]
dns[acmecast-dns<br/>DNS 挑战与传播]
store[acmecast-store<br/>持久化 SeaORM]
access[acmecast-access<br/>凭据体系]
pipeline[acmecast-pipeline<br/>流水线引擎]
deploy[acmecast-deploy<br/>部署目标]
scheduler[acmecast-scheduler<br/>定时调度]
server[acmecast-server<br/>HTTP / CLI / 装配]
acme --> core
dns --> acme
dns --> core
cert --> core
store --> core
store --> cert
access --> store
pipeline --> access
deploy --> access
scheduler --> store
scheduler --> pipeline
server --> core
server --> store
server --> access
server --> pipeline
server --> deploy
server --> acme
server --> cert
server --> dns
server --> scheduler
依赖单向、职责正交:
| Crate | 职责 | 关键约定 |
|---|---|---|
acmecast-core |
配置装载(YAML + env 覆盖)、AES-256-GCM 凭据加密、统一错误类型 | 不依赖任何其他 acmecast crate,是所有上层的基座 |
acmecast-cert |
证书解析、到期状态判定、PEM/DER/PFX/P7B/JKS 转换、私钥校验 | 只处理内存字节,不落盘(落盘归 store)、不签发(归 acme);纯 Rust 无 OpenSSL |
acmecast-acme |
ACME 账号/订单/挑战/吊销,基于 instant_acme |
防腐层:底层类型不出现在公开签名;testing feature 提供进程内 mock CA |
acmecast-dns |
DNS provider(cloudflare/aliyun)、TXT 记录生命周期、传播等待、权威 NS 直查 | 写入→执行→无论成败都清理;传播判定以权威 NS 一致为准 |
acmecast-store |
SeaORM 三方言(SQLite/MySQL/PostgreSQL)连接、版本化迁移、10 张表实体、仓储层、文件存储(FileStore) | 迁移只增不改;时间一律 UTC |
acmecast-access |
凭据类型注册表、凭据仓库(解密/校验/引用检查)、内建 acme.account 凭据类型 |
凭据字段整体替换;仍被引用的凭据拒绝删除 |
acmecast-pipeline |
PipelineStep 契约、执行器、产物、事件、历史、流水线级 KV 状态、并发闸门 |
无状态单例步骤 + 显式注册;单步失败即中止后续步骤 |
acmecast-deploy |
DeploymentTarget 契约、local/ssh 目标、幂等部署记录、SSH 主机档案 |
同指纹跳过写入但重载照跑;先写临时文件再原子替换 |
acmecast-scheduler |
cron 触发、到期扫描、去重窗口、触发审计 | 每 30 秒 tick 一次;重启时装载并可选补跑 |
acmecast-server |
clap CLI、axum HTTP、JWT 鉴权、OpenAPI、SPA 托管、内置步骤装配、调度器启动 | 唯一的可执行 crate |
内置步骤 / 提供商 / 目标 / 凭据类型¶
server 启动时显式注册(重复注册即报错,不用链接器魔法):
- 步骤:
cert.apply(ACME 申请)、cert.store(证书入库)、cert.deploy(部署) - DNS 提供商:
cloudflare、aliyun - 部署目标:
local(本地文件系统)、ssh(SSH 远程主机) - 凭据类型:
acme.account、cloudflare、aliyun、ssh(SSH 主机档案)
关键横切机制¶
凭据安全¶
凭据字段以 AES-256-GCM 加密后落库(nonce(12B) || ciphertext || tag,base64
存储);加密密钥 ACMECAST_CREDENTIAL_KEY 只存在于环境变量。步骤执行时按
credential_id 现取现解密,不驻留配置、不跨运行缓存。日志不得写入凭据明文,
报错回显前经 redact 脱敏。
幂等¶
- 部署:目标键(
sha256输入摘要)+ 证书指纹一致且未force→ 跳过写入, 重载命令仍执行;部署记录只在成功后写入。 - 入库:同一域名集合再次入库是
Updated而非新增记录。 - 续期触发:运行中检查 + 去重窗口(默认 1 小时)双保险。
一致性¶
- 到期状态判定有唯一实现(
acmecast-cert的ExpiryPolicy),store 实体 委托同一函数,避免两处逻辑漂移。 - 历史记录先插
running行、跑完原地改写终态,崩溃遗留的 running 视为仍在 运行(宁可不自动触发)。 - 所有注册表的错误都列出已注册清单,配置错误能直接看到可选项。
前端¶
frontend/:React 19 + TypeScript + Vite 7 + Ant Design 6 + react-router 7 +
zustand + @tanstack/react-query + openapi-fetch。API 类型由
openapi-typescript 从 /api/openapi.json 生成。生产构建产物由后端同源托管
(SPA fallback 以 200 交出 index.html),无 CORS 层。