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 未受影响。
This commit is contained in:
JianFeeeee
2026-10-04 08:29:35 +08:00
parent e7578aa607
commit 12c07034a0
2 changed files with 72 additions and 10 deletions

View File

@ -54,6 +54,21 @@ RUN mkdir -p /out \
&& cd /src/chrome && zip -qr /out/navi-chrome.zip . \
&& cd /src/firefox && zip -qr /out/navi-firefox.xpi .
# ── Stage 3b: bundled acme.sh (for cert-agent) ──
# 为什么要内置:cert-agent 若在运行时安装 acme.sh,installer 会从
# raw.githubusercontent.com 拉源码,而该域名在境内直连超时
# (实测直连 8.2s 失败 / 经代理 0.53s 成功),导致首次启动静默失败。
# 内置一份让 cert-agent 彻底不依赖运行时下载。
FROM debian:bookworm-slim AS acmebuild
RUN apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
git ca-certificates curl openssl \
&& rm -rf /var/lib/apt/lists/*
RUN git clone --depth 1 https://github.com/acmesh-official/acme.sh /src/acme.sh \
&& chmod +x /src/acme.sh/acme.sh \
&& rm -rf /src/acme.sh/.git
# ── Stage 4: Runtime ──
FROM debian:bookworm-slim
@ -103,6 +118,9 @@ COPY --from=extpack /out/navi-firefox.xpi /app/dashy/extension/navi-firefox.xpi
# 容器内自动续签(cert-agent)。脚本本身不含任何凭据,凭据只从环境变量读。
COPY deploy/cert-agent.sh /usr/local/bin/cert-agent
RUN chmod +x /usr/local/bin/cert-agent
# 内置 acme.sh:cert-agent 优先用它,避免运行时从 raw.githubusercontent.com
# 下载失败(该域名境内直连超时)。
COPY --from=acmebuild /src/acme.sh /usr/local/share/acme.sh
# Inject search script into Dashy
COPY web/navi.js /app/dashy/navi.js

View File

@ -45,19 +45,45 @@ fi
[ -d "$HOME_DIR" ] || mkdir -p "$HOME_DIR"
[ -d "$CERT_DIR" ] || mkdir -p "$CERT_DIR"
# ── 1. 首次运行装 acme.sh(数据卷里已存在就跳过)──
# ── 1. 定位 acme.sh:镜像内置优先,运行时安装是兜底 ─────────────────
# 为什么不运行时安装:installer 内部从 raw.githubusercontent.com 取 acme.sh,
# 而该域名在境内直连会超时(实测直连 8.2s 失败、经 .60:7890 代理 0.53s 成功),
# 于是「首次启动装 acme.sh」这一步会静默失败,之后永远起不来。
# 因此镜像里内置一份(Dockerfile 的 acmebuild stage),运行时只剩兜底路径。
BUNDLED=/usr/local/share/acme.sh
ACME="$HOME_DIR/acme.sh"
if [ -x "$BUNDLED/acme.sh" ]; then
# 拷贝到数据卷:acme.sh 会在自己所在目录写 ca/ dnsapi/ 与证书状态,
# 放在只读的镜像层里会写失败。
mkdir -p "$HOME_DIR"
cp -a "$BUNDLED/." "$HOME_DIR/" 2>/dev/null || true
[ -x "$ACME" ] || say "内置 acme.sh 拷贝失败,落到运行时安装路径"
fi
if [ ! -x "$ACME" ]; then
say "首次运行:安装 acme.sh 到 $HOME_DIR"
curl -fsSL https://get.acme.sh -o "$HOME_DIR/acme.sh.install" 2>>"$LOG" || {
say "下载 acme.sh 失败,跳过本次"
say "镜像内置 acme.sh 不可用,尝试运行时安装(需要能访问 raw.githubusercontent.com)"
# 兜底:需要代理时设 CERT_ACME_PROXY(容器内直连不通的环境)。
PROXY_OPT=""
[ -n "${CERT_ACME_PROXY:-}" ] && PROXY_OPT="-x $CERT_ACME_PROXY"
INSTALLER="$HOME_DIR/acme.sh.install"
OK=0
for i in 1 2 3; do
# shellcheck disable=SC2086 # PROXY_OPT 需按空格拆开
if curl -fsSL -m 120 $PROXY_OPT https://get.acme.sh -o "$INSTALLER" 2>>"$LOG"; then
OK=1; break
fi
say "下载 get.acme.sh 第 $i 次失败,重试"
sleep $((i * 5))
done
if [ "$OK" = "1" ]; then
sh "$INSTALLER" --home "$HOME_DIR" --nocron >>"$LOG" 2>&1 || say "安装 acme.sh 失败"
rm -f "$INSTALLER"
else
say "下载失败,跳过本次(下次循环重试)"
exit 0
}
sh "$HOME_DIR/acme.sh.install" --home "$HOME_DIR" --nocron >>"$LOG" 2>&1 || {
say "安装 acme.sh 失败,跳过本次"
exit 0
}
rm -f "$HOME_DIR/acme.sh.install"
fi
[ -x "$ACME" ] || { say "安装后仍找不到 $ACME,跳过本次"; exit 0; }
fi
# ── 2. 从 conf/gateway.yml 读出要签的域名 ────────────────────────────
@ -99,6 +125,24 @@ esac
CA="letsencrypt"
[ -n "${CERT_ACME_CA:-}" ] && CA="$CERT_ACME_CA"
# ★ 必须把 CERT_DNS_CRED 里的键值**导出成真实环境变量名**,acme.sh 的
# dns_ali.sh 读的是 $Ali_Key / $Ali_Secret(不是我们的 CERT_DNS_CRED)。
# 只挑出插件名而不导出凭据的表现是:DNS 步骤报
# "Error adding TXT record to domain",而 ACME 其它步骤都正常 ——
# 很容易误判成"凭据无效/网络问题"。
# 用 set -a? 不行(要过滤)。这里显式 eval 展开赋值。
save_ifs="$IFS"
IFS=','
for kv in $CERT_DNS_CRED; do
k="${kv%%=*}"
v="${kv#*=}"
case "$k" in
''|*[!A-Za-z0-9_]*) ;; # 跳过空项与非法变量名
*) export "$k=$v" ;;
esac
done
IFS="$save_ifs"
run_acme() {
# 拼出 -d domain 列表。刻意不用 `set -- $DOMAINS`:那会改掉函数的位置参数。
DFLAGS=""