3 Commits

Author SHA1 Message Date
96e771d546 feat(cert): cert-agent 支持多证书组 + 多目标推送(全量接管)
用户要求「部署时接管全部证书」。原来只签一组(self-only)。

## 多证书组(按证书而非按域名组织)
CERT_GROUPS 每行 '<组名>:<域名1,域名2,...>',例如:
  wild:*.jianfgit.xyz,jianfgit.xyz
  introduce:introduce.homeagent.jianfgit.xyz
  sdk:sdk.homeagent.jianfgit.xyz
每组独立落到 /app/data/certs/<组名>/,签发/续期/推送都以证书为单位
(同一张证书覆盖多个 SAN)。不配置时回落 gateway.yml 的 host(旧行为)。

## 多目标推送
CERT_PUSH_TARGETS 每行 '<host>:<远端cert>:<远端key>:<reload 命令>',
把证书推到其它机器(云主机 SafeLine 等)并触发其 reload。
认证走 CERT_PUSH_SSH_KEY / CERT_PUSH_SSH_PASS,都没有则免密。

## 途中修掉的 bug
① for 循环按**空格**分词,把含空格的 reload 命令("nginx -s reload")
   连同整行目标拆成 host/"-s"/"reload" 三片,产生三条无意义推送。
   改为 IFS=换行 + for,恢复 IFS。
② 凭据检查要求 CERT_DNS_API 与 CERT_DNS_CRED 同时存在,但插件名本来
   就能按凭据键名推断 —— 只需 CERT_DNS_CRED。
③ 改造时误删 LE_RELOAD_CMD / le_reload 定义(set -u 下直接
   "parameter not set" 中断)。已补回。

## 测试
沙箱 stub 覆盖:三组各自签发、通配符 SAN 正确、产出目录结构完整、
推送四步(mkdir→写cert→写key+chmod600→reload)全部执行。
真实签发此前已用 staging CA 验证通过(CN=dashy…, LE_ReloadCmd 已写入)。
2026-10-04 08:52:47 +08:00
12c07034a0 fix(cert): cert-agent 真实签发验证后修的三处问题
在测试容器里用真实凭据跑通了完整签发(staging CA),暴露三个真问题:

## ① CERT_DNS_CRED 没导出成 acme.sh 期望的变量名(致命)
脚本只把凭据解析出插件名 dns_ali,却从没导出 Ali_Key/Ali_Secret。
acme.sh 的 dns_ali.sh 读的是 $Ali_Key/$Ali_Secret,所以 DNS 步骤
报 'Error adding TXT record',而 ACME 其它步骤全部正常 ——
极易误判成"凭据无效/网络问题"。**stub 测试完全没暴露这个**,
只有真跑才看得见。现改为把 CERT_DNS_CRED 按逗号拆开逐个 export。

## ② 镜像内置 acme.sh,不再运行时下载
installer 内部从 raw.githubusercontent.com 取源码,而该域名境内直连超时
(实测直连 8.2s 失败 / 经 .60:7890 代理 0.53s 成功),
首次启动会静默失败、之后永远起不来。Dockerfile 新增 acmebuild stage
内置 acme.sh v3.1.6,运行时安装降级为兜底。

## ③ 下载重试 + 安装后校验
容器出网偶发 Connection reset by peer(同一 URL 隔几秒即 200),
加了 3 次重试与「安装后确认可执行」,避免半截状态。

## 真实测试证据(staging CA,零正式配额消耗)
TXT 写入 → Verifying → Success → Successfully removed → Cert success;
签出 CN=dashy.jianfgit.xyz(Let's Encrypt YE1,90 天);
Le_ReloadCmd 已写入 ⇒ 续期后自动 nginx -t && nginx -s reload。

测试环境已清理(含凭据文件),线上 v106 未受影响。
2026-10-04 08:29:35 +08:00
e7578aa607 feat(cert): cert-agent —— 容器内自签自续 HomeDash 自己的证书
用户决策:scope=self-only,strategy=full-acme(容器内跑完整 acme.sh)。

## 动机
此前 HomeDash 的证书由外部链路提供(.106 acme.sh → .60 → 云主机),
而容器是**只读**挂载 /etc/nginx/certs(RW=false),续期后没人告诉它的
nginx 重新握手。本 agent 让 HomeDash 自己闭环:签发 → 写数据卷 → reload。

## 实现
- deploy/cert-agent.sh:supervisord 新增 [program:cert-agent]
  - 域名从 /app/data/gateway.yml 的 host 提取(或 CERT_DOMAINS 显式指定)
  - acme.sh 装进**数据卷** /app/data/acme(容器重建不丢续期状态)
  - install-cert 到 /app/data/certs/,--reloadcmd 设为 nginx -t && nginx -s reload
  - 续期后比对公钥指纹,变了才 reload(不盲目 reload)
  - 未配置 CERT_DNS_CRED 时安静退出,行为与现在完全一致
- 证书目录挂载去掉 :ro(自签需要写;此前只读是刻意的保护)
- zd-host.sh 的 env 白名单加 CERT_ 前缀,否则升级时凭据传不进新容器
- install-host.sh 同步支持 CERT_DNS_CRED,避免两套脚本行为不一致
- 证书路径改为数据卷 /app/data/certs/(gateway.yml 指向它)

## 测试(沙箱 stub,非真实签发)
- 无凭据 → 安静退出、exit 0、不调 acme、不报错
- 有凭据 → 正确从 gateway.yml 提取 2 个域名、识别 dns_ali、
  签发参数正确、指纹变化后触发 reload

## 途中修掉自己的两个真 bug
① run_acme 里用 `set -- $DOMAINS` 改函数位置参数,dash 下与后续变量
   展开冲突
② LE_RELOAD 是命令串,直接当命令名执行 → "LE_RELOAD: not found",
   容器里 reload 会**静默失效**;改为函数 + eval

## 边界
只管 HomeDash 自己;introduce/sdk.homeagent 等仍走外部链路。
凭据只从环境变量读,不落盘不入库。
2026-10-04 08:02:41 +08:00