配置参考¶
acmecast-server 的配置合并优先级为 内置默认 < config.toml < 环境变量。
文件不存在或为空时回退内置默认;文件解析失败(字段类型不匹配等)启动中止。
[server] 段启用了 deny_unknown_fields:写错键名会直接报错。
配置文件位置¶
解析顺序:显式 --config <path> → 环境变量 ACMECAST_CONFIG →
$ACMECAST_DATA_DIR/config.toml(ACMECAST_DATA_DIR 未设时为 data/config.toml)。
首次启动若配置文件不存在,会自动生成带注释的模板(不覆盖已存在文件);
也可用 acmecast-server init [--force] 手动生成。配置修改需重启生效。
配置项总表¶
| 配置项 | config.toml 键 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|---|
| 监听地址 | server.listen_addr |
ACMECAST_LISTEN |
0.0.0.0:8080 |
host:port |
| 数据目录 | server.data_dir |
ACMECAST_DATA_DIR |
./data |
数据库、证书文件、凭据密文均在此 |
| 数据库连接串 | server.database_url |
ACMECAST_DATABASE_URL |
sqlite://<data_dir>/acmecast.db?mode=rwc |
支持 sqlite/mysql/postgres,见数据库与数据目录 |
| 前端静态目录 | server.static_dir |
ACMECAST_STATIC_DIR |
未设置 | 未设置时不托管前端(仅 API) |
| 关闭鉴权 | server.auth_disabled |
ACMECAST_AUTH_DISABLED |
false |
1/true/yes(大小写不敏感)为真;仅限本地单机 |
| 请求体上限 | server.body_limit_bytes |
ACMECAST_BODY_LIMIT_BYTES |
2097152(2 MiB) |
超出返回 413 payload_too_large;env 须 > 0 |
| 跳过 CA 证书校验 | server.accept_invalid_acme_certs |
ACMECAST_INSECURE_SKIP_VERIFY |
false |
仅供自建测试 CA(pebble 等)使用 |
安全相关环境变量(仅环境变量,不入 config.toml)¶
| 变量 | 必需 | 说明 |
|---|---|---|
ACMECAST_JWT_SECRET |
是 | HS256 JWT 签名密钥;缺失启动失败。建议 openssl rand -base64 48 |
ACMECAST_CREDENTIAL_KEY |
是 | 凭据静态加密密钥(AES-256-GCM,base64 编码的 32 字节)。也接受双下划线别名 ACMECAST__SECURITY__CREDENTIAL_KEY。更换后已存凭据无法解密 |
ACMECAST_ADMIN_PASSWORD_HASH |
否 | 管理员口令的 Argon2 PHC 哈希;未配置则服务可启动但无人能登录(启动日志告警,登录返回 500 configuration_error) |
ACMECAST_ADMIN_PASSWORD_HASH_FILE |
否 | 哈希的文件路径;设置后优先读文件,文件不存在/空白时回退到 ACMECAST_ADMIN_PASSWORD_HASH;读取失败(如指向目录)视为配置错误且不回退 |
ACMECAST_ADMIN_USERNAME |
否 | 管理员用户名,默认 admin |
ACMECAST_TOKEN_TTL_HOURS |
否 | 访问令牌有效期(小时),默认 12;非法值回落 12 |
日志¶
RUST_LOG:tracing 过滤器,缺省info;支持info,acmecast_cert=debug这类按模块调级的写法。- 每个 HTTP 请求自动携带
x-request-id(UUID)并贯穿日志 span。
DNS 解析器扩展¶
config.toml 支持 [[resolvers]] 段追加自定义解析器(模板内附阿里云/Cloudflare
DoH 示例):
[[resolvers]]
type = "doh" # doh / dot / dns
endpoint = "https://dns.alidns.com/dns-query"
name = "alidns" # 可选,缺省取主机名
doh:必须是https://;dot:仅校验并告警跳过(传输层未实现);dns:必须是 IP 地址。
也可用环境变量 ACMECAST_DOH_RESOLVERS(逗号分隔的 名称=端点,或仅端点);
设为 none 表示跳过传播等待(不推荐)。合并顺序:内置 → config 条目 →
环境变量,按归一化端点去重。详见DNS 提供商与传播检查。
webhook 通知¶
config.toml 支持 [[notifications]] 段声明 webhook 通知渠道:cert.apply
(证书申请成功)、cert.deploy(部署成功)后向 IM 群机器人推送文本消息。
内置三种适配器:
| provider | 平台 | 消息格式 | 签名 |
|---|---|---|---|
feishu |
飞书(Lark)自定义机器人 | text | 配置 secret 时启用(timestamp + sign) |
dingtalk |
钉钉自定义机器人 | text | 配置 secret 时启用官方加签(URL 查询参数) |
generic |
通用 webhook(统一请求方案) | 可自定义模板 | 可指定 sign(feishu / dingtalk) |
[[notifications]]
name = "ops-feishu" # 渠道名,须唯一(日志与测试端点用它定位)
provider = "feishu" # feishu / dingtalk / generic
url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx"
secret = "..." # 可选;启用机器人签名校验时填写
events = ["cert.apply", "cert.deploy"] # 订阅事件,不能为空
enabled = true # 可选,缺省 true
generic 统一请求方案¶
generic 渠道把 URL、签名方式、请求方法、请求头与请求体模板全部交给
配置;飞书与钉钉两种签名算法写死在代码中,只需用 sign 指定方式:
[[notifications]]
name = "ops-custom"
provider = "generic"
url = "https://ops.example.com/api/events"
secret = "sk-xxx" # 签名密钥或 API-KEY
sign = "feishu" # feishu / dingtalk,缺省不签名
method = "POST" # POST / PUT / PATCH,缺省 POST
headers = { "X-API-Key" = "sk-xxx" } # 额外静态请求头,可选
body_template = '{"text": "{{title}}|{{pipeline}}|{{domains}}"}'
events = ["cert.apply", "cert.deploy"]
- 签名方式(算法内置,配置只选方式):
feishu以"{秒级时间戳}\n{secret}"为密钥对空串计算 HmacSHA256,签名与时间戳 自动合并进请求体顶层字段;dingtalk以secret为密钥对"{毫秒级时间戳}\n{secret}"计算,经 URL 编码后自动追加到地址查询 参数。两种方案都要求secret必填。
ntfy.sh 推送示例(ntfy 的 JSON 发布格式走根路径,topic 写在请求体里;
若直接 POST 到 /<topic>,JSON 会被当成纯文本消息):
[[notifications]]
name = "ntfy"
provider = "generic"
url = "https://ntfy.sh/" # 自建实例换成你的地址
method = "POST"
body_template = '{"topic": "acmecast-换成随机私有主题", "title": "{{title}}", "message": "{{pipeline}}({{trigger_label}})\n域名:{{domains}}", "tags": ["white_check_mark"], "priority": 4}'
# headers = { Authorization = "Bearer tk_xxx" } # 实例开启访问控制时填写
events = ["cert.apply", "cert.deploy"]
- 公共实例上 topic 名就是订阅凭据(知道即可订阅与推送),请用随机
主题名;部署事件想把目标也带进消息,在
message里追加\n{{target}}。 - ntfy JSON 格式的字段类型比 HTTP header 方式严格:
priority必须是1–5的整数(1=min、3=default、4=high、5=urgent/max),写high之类的字符串会被整体拒绝(报 40024 invalid JSON)。 - 请求体模板:占位符
{{var}}填充事件素材并做 JSON 转义(值放在 引号内使用,如"{{domains}}")。可用变量:event、title、pipeline、trigger(原始标识)、trigger_label(中文)、occurred_at、domains(逗号连接)、target、timestamp(签名方 案对应精度)、sign。模板里显式写了{{sign}}时签名以模板为准, 不再自动合并。 - 缺省消息:未配置模板时按签名方式选格式——
sign = feishu/dingtalk分别用对应平台的 text 格式(与专用适配器一致);无签名时 发统一事件负载 JSON(event/title/pipeline/trigger/occurred_at/domains/target),保持向后兼容。 - 专用适配器的兼容性:
sign、method、headers、body_template仅generic渠道可用——feishu/dingtalk渠道配置这些字段会被 视为非法条目跳过(它们的请求形态写死)。把渠道从专用适配器迁到generic时,复制原有字段并加上sign即可,消息格式不变。 - 事件词汇:
cert.apply(证书申请成功)、cert.deploy(部署成功)。 申请消息含域名列表;部署消息含部署目标(SSH 主机 / 本地路径)与域名。 - 平台接入:在飞书/钉钉群里添加「自定义机器人」(webhook),钉钉建议
选择「加签」安全设置并把密钥填入
secret;飞书同理可开启签名校验。 - 校验语义:非法条目(
provider不在内置之列、events含未知事件、url非 http(s)、重名,以及sign/method/headers/body_template不合法或专用适配器误配扩展字段)启动时跳过并warn,其余渠道照常 加载;enabled = false的渠道保留配置但不投递。 - 投递行为:事件触发后异步投递,10 秒超时,失败按 1s/2s 退避最多 3 次尝试;投递成败不影响流水线运行结果。IM 平台有频控(如钉钉 约 20 条/分钟),大量流水线同时运行时可能触发限流。
- 连通性测试:配置后重启,调用测试端点验证(带鉴权):
curl -X POST http://127.0.0.1:8080/api/notifications/test \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "ops-feishu"}' # 缺省 body 测试全部启用渠道
响应逐渠道给出 ok 与失败原因。
多渠道配置示例¶
一份脱敏自实际运行配置的三渠道示例,覆盖三种接入形态:飞书签名、 钉钉加签、ntfy 自建实例走 generic 统一请求方案:
[[notifications]]
name = "lark"
provider = "feishu" # Lark(open.larksuite.com)与飞书同协议,适配器通用
url = "https://open.larksuite.com/open-apis/bot/v2/hook/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
secret = "机器人签名密钥" # 机器人开启「签名校验」时填写;不填即无签名
events = ["cert.apply", "cert.deploy"]
[[notifications]]
name = "dingtalk"
provider = "dingtalk"
url = "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx"
secret = "SECxxxxxxxxxxxxxxxx" # 钉钉「加签」安全设置给出的密钥(SEC 开头)
events = ["cert.apply", "cert.deploy"]
[[notifications]]
name = "ntfy"
provider = "generic"
url = "https://ntfy.example.com/" # ntfy 自建实例(根路径,topic 在请求体里)
method = "POST"
body_template = '{"topic": "acme", "title": "{{title}}", "message": "{{pipeline}}({{trigger_label}})\n域名:{{domains}}", "tags": ["white_check_mark"], "priority": 4}'
headers = { "Authorization" = "Bearer tk_xxxx" } # 实例开启访问控制时填写
events = ["cert.apply", "cert.deploy"]
三者的签名方式各不相同但都写死在服务端代码中:feishu 把签名放进
请求体、dingtalk 放进地址查询参数、ntfy 凭 Authorization 请求头
鉴权(generic 渠道不配 sign 即不签名)。
secret 明文存于 config.toml(数据目录权限受限 0700),请勿把该文件
提交进版本控制;泄露后果限于向对应群机器人发送消息,风险远低于 DNS /
SSH 凭据。
最小可用配置示例¶
# data/config.toml —— 只写与非默认不同的项
[server]
listen_addr = "0.0.0.0:8080"
static_dir = "/static" # 托管前端时
# database_url = "postgres://acmecast:****@127.0.0.1:5432/acmecast"
配合环境变量: