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:
18
Dockerfile
18
Dockerfile
@ -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
|
||||
|
||||
@ -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=""
|
||||
|
||||
Reference in New Issue
Block a user