From f49c2b135d9cf65396bc7a6aeba281d064c5e98f Mon Sep 17 00:00:00 2001 From: wdh-home <243823965@qq.com> Date: Mon, 18 May 2026 22:48:12 +0800 Subject: [PATCH] 2 --- conf/conf.d/ai.sggai.site.conf | 21 ---- docker-compose.yml | 15 ++- init-certs.sh | 7 +- scripts/ensure-domain-conf.sh | 17 +--- scripts/ensure-dummy-certs.sh | 26 ++--- scripts/init-certs-core.sh | 170 ++++++++++++++++++++------------ scripts/install-renew-cron.sh | 10 +- scripts/lib-compose.sh | 5 +- scripts/renew-certs.sh | 14 ++- scripts/request-certs.sh | 14 +-- scripts/uninstall-renew-cron.sh | 7 +- 11 files changed, 151 insertions(+), 155 deletions(-) diff --git a/conf/conf.d/ai.sggai.site.conf b/conf/conf.d/ai.sggai.site.conf index 454690f..b44a084 100644 --- a/conf/conf.d/ai.sggai.site.conf +++ b/conf/conf.d/ai.sggai.site.conf @@ -1,6 +1,4 @@ server { - # 文件名前缀是 000-,会比 ai.sggai.site.conf 更早加载。 - # 这样可以在不修改原配置文件的情况下,让这里的 mock 逻辑优先生效。 listen 80; listen 443 ssl; server_name ai.sggai.site; @@ -19,7 +17,6 @@ server { gzip off; gunzip off; - # 公共反代配置放在 server 级别,主后端 proxy_pass location 会继承这些配置。 proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Authorization $http_authorization; @@ -57,59 +54,45 @@ server { proxy_read_timeout 3600s; proxy_send_timeout 3600s; - # 只拦截很小的非流式 Chat Completions 请求。 - # 不满足条件的请求会内部跳转到正常后端,不直接返回 mock。 location = /v1/chat/completions { client_body_buffer_size 16k; content_by_lua_block { - -- 不满足 mock 条件时,统一内部跳转到这个 named location。 local backend = "@ai_sggai_site_backend" - - -- 请求体总大小上限:只处理非常小的探测类请求。 local max_body_bytes = 1024 - -- 只处理 POST;其它方法按普通请求转发到真实后端。 if ngx.req.get_method() ~= "POST" then return ngx.exec(backend) end - -- 先看 Content-Length,超过阈值就不读取 body,直接放行。 local content_length = tonumber(ngx.var.http_content_length) if not content_length then - -- 没有 Content-Length 的请求可能是 chunked,不为了判断 mock 去读取未知大小的 body。 return ngx.exec(backend) end - -- 请求体太大,说明不是目标小请求,直接走真实后端。 if content_length > max_body_bytes then return ngx.exec(backend) end - -- 到这里说明 body 足够小,可以安全读取并解析 JSON。 ngx.req.read_body() local body = ngx.req.get_body_data() if not body or #body > max_body_bytes then - -- body 不在内存里或实际大小仍然超限时,不 mock,转发到真实后端。 return ngx.exec(backend) end - -- JSON 解析失败,或 stream 不是 false,都不 mock。 local cjson = require "cjson.safe" local payload = cjson.decode(body) if type(payload) ~= "table" or payload.stream ~= false then return ngx.exec(backend) end - -- 必须有 messages 数组;没有就不 mock。 if type(payload.messages) ~= "table" then return ngx.exec(backend) end local has_hi_content = false - -- 遍历所有 message,只匹配精确的 "content": "hi"。 for _, message in ipairs(payload.messages) do if type(message) == "table" and message.content == "hi" then has_hi_content = true @@ -117,12 +100,10 @@ server { end end - -- 没有精确命中 "content": "hi",交给真实后端处理。 if not has_hi_content then return ngx.exec(backend) end - -- 命中小请求 + stream=false + "content": "hi" 条件,直接返回 mock 响应。 ngx.status = ngx.HTTP_OK ngx.header["Content-Type"] = "application/json; charset=utf-8" ngx.say([[{ @@ -143,12 +124,10 @@ server { } } - # 仅供上面的 Lua 逻辑通过 ngx.exec 内部跳转使用,外部 URL 不能直接访问这个 location。 location @ai_sggai_site_backend { proxy_pass http://10.1.0.1:3001; } - # 普通流量保持原来的上游转发行为。 location / { proxy_pass http://10.1.0.1:3001; } diff --git a/docker-compose.yml b/docker-compose.yml index 9757146..a90d9a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,21 +5,20 @@ services: network_mode: host restart: unless-stopped volumes: - # 主配置 + # Main config - ./conf/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro - # HTTP 配置 + # HTTP virtual hosts - ./conf/conf.d:/usr/local/openresty/nginx/conf/conf.d:ro - # Stream 配置 + # TCP/stream virtual hosts - ./conf/stream.d:/usr/local/openresty/nginx/conf/stream.d:ro - # 静态网站 + acme challenge + # Static sites and ACME HTTP-01 challenges - ./www:/var/www - # SSL 证书 + # Let's Encrypt certificates - ./certs:/etc/letsencrypt - # 日志 + # OpenResty logs - ./logs:/usr/local/openresty/nginx/logs - # Certbot 是给脚本或手动命令使用的工具容器,不是常驻服务。 - # ./www 用于 ACME HTTP-01 域名验证,./certs 用于持久化保存签发的证书。 + # Tool container used by scripts and manual certbot commands. certbot: image: certbot/certbot container_name: certbot diff --git a/init-certs.sh b/init-certs.sh index b853368..03a7201 100644 --- a/init-certs.sh +++ b/init-certs.sh @@ -3,12 +3,11 @@ set -eu SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" -# 需要签证的域名列表。 -# 注意:这里的域名必须覆盖 nginx 配置里所有 ssl_certificate 使用到的域名。 -# 如果以后新增 HTTPS 域名,也要同步加到这里,否则 OpenResty 可能因为缺证书启动失败。 +# Domains that need certificates. This list must cover every +# /etc/letsencrypt/live// certificate referenced by nginx configs. DOMAINS="ai.sggai.site dms.sggai.site lsbd2.loveteemo.com" -# Let's Encrypt 注册邮箱。 +# Let's Encrypt registration email. CERT_EMAIL="243823965@qq.com" export DOMAINS CERT_EMAIL diff --git a/scripts/ensure-domain-conf.sh b/scripts/ensure-domain-conf.sh index 9df4fa7..37ee4bc 100644 --- a/scripts/ensure-domain-conf.sh +++ b/scripts/ensure-domain-conf.sh @@ -1,21 +1,15 @@ #!/usr/bin/env sh set -eu -# 为传入的域名生成基础 nginx 配置。 -# 如果 conf/conf.d/xxx.conf 已经存在,直接跳过,避免覆盖人工维护的配置。 +# Generate a minimal nginx virtual host for each missing domain config. ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" - -# 默认写入 openresty-gateway/conf/conf.d。 -# 如需生成到其他目录,可以在执行前设置 CONF_DIR,例如: -# CONF_DIR="./conf/test.d" sh scripts/ensure-domain-conf.sh example.com CONF_DIR="${CONF_DIR:-./conf/conf.d}" cd "$ROOT_DIR" -# 必须显式传入域名,避免无参数执行时静默成功但什么都不做。 if [ "$#" -eq 0 ]; then - echo "用法:sh scripts/ensure-domain-conf.sh <域名> [域名...]" >&2 + echo "Usage: sh scripts/ensure-domain-conf.sh [domain...]" >&2 exit 1 fi @@ -23,15 +17,12 @@ ensure_domain_conf() { domain="$1" conf_file="$CONF_DIR/$domain.conf" - # 已存在的域名配置不重新生成,避免覆盖已有转发、限流、路径规则等自定义配置。 if [ -f "$conf_file" ]; then - echo "跳过已存在的 nginx 配置:$conf_file" + echo "Skipping existing nginx config: $conf_file" return fi - # 这里只生成一个能响应 ACME webroot 校验的最小 HTTPS 站点模板。 - # 业务代理规则可以后续在生成的 conf 文件里按需补充。 - echo "创建 nginx 配置模板:$conf_file" + echo "Creating nginx config template: $conf_file" mkdir -p "$CONF_DIR" cat > "$conf_file" <&2 + echo "Error: DOMAINS is required." >&2 exit 1 fi if ! command -v openssl >/dev/null 2>&1; then - echo "错误:缺少 openssl 命令。" >&2 + echo "Error: openssl is required." >&2 exit 1 fi @@ -31,38 +31,30 @@ for domain in $DOMAINS; do if [ -f "$cert_file" ] && [ -f "$key_file" ]; then if [ -f "$marker_file" ]; then - # 上一次脚本可能中途失败,留下了 dummy 证书。 - # 继续把它当作 dummy 处理,后面会删除并重新申请正式证书。 - echo "复用已存在的 dummy 证书:$domain" >&2 + echo "Reusing existing dummy certificate: $domain" >&2 echo "$domain" continue fi - # 已经有正式证书时不覆盖,避免误删线上可用证书。 - echo "跳过已存在的正式证书:$domain" >&2 + echo "Skipping existing real certificate: $domain" >&2 continue fi if [ -f "$cert_file" ] || [ -f "$key_file" ]; then if [ -f "$marker_file" ]; then - # 上次生成 dummy 证书时可能中途退出,留下了不完整文件。 - # 这些文件由本脚本创建,可以安全清理后重建。 - echo "清理不完整的 dummy 证书:$domain" >&2 + echo "Cleaning incomplete dummy certificate: $domain" >&2 rm -f "$cert_file" "$key_file" "$marker_file" "$tmp_cert_file" "$tmp_key_file" else - # 只存在证书或只存在私钥,状态不完整。 - # 自动处理可能误删用户文件,所以直接停止,让用户手工确认。 - echo "错误:$domain 存在不完整的证书文件,请手动检查目录:$cert_dir" >&2 + echo "Error: incomplete certificate files exist for $domain: $cert_dir" >&2 exit 1 fi fi if [ ! -f "$cert_file" ] && [ ! -f "$key_file" ]; then - # marker 先创建,避免 openssl 成功后脚本中断时留下无 marker 的 dummy 文件。 rm -f "$tmp_cert_file" "$tmp_key_file" : > "$marker_file" - echo "创建 dummy 证书:$domain" >&2 + echo "Creating dummy certificate: $domain" >&2 if ! openssl req -x509 -nodes -newkey rsa:2048 -days 1 \ -keyout "$tmp_key_file" \ -out "$tmp_cert_file" \ diff --git a/scripts/init-certs-core.sh b/scripts/init-certs-core.sh index 6ea44b8..3061b0a 100644 --- a/scripts/init-certs-core.sh +++ b/scripts/init-certs-core.sh @@ -1,46 +1,46 @@ #!/usr/bin/env sh set -eu -# 首次部署用: -# 1. 校验每个域名都有 nginx 配置,且配置里放行 ACME HTTP-01 校验路径。 -# 2. 为缺失的证书路径生成 1 天有效期的自签 dummy 证书,避免 OpenResty 因证书文件不存在而启动失败。 -# 3. 启动 OpenResty,让 certbot 的 webroot 校验可以访问 /.well-known/acme-challenge/。 -# 4. 删除本次创建的 dummy 证书文件,避免挡住 certbot 创建正式证书目录。 -# 5. 运行 Certbot,为每个域名单独签发正式证书。 -# 6. 重启 OpenResty,让正式证书生效。 -# 7. 按需安装证书自动续期 cron 任务,后续证书会定期检查续期。 +# First deployment flow: +# 1. Validate nginx configs and ACME HTTP-01 challenge locations. +# 2. Create temporary dummy certificates for missing certificate files. +# 3. Start OpenResty so HTTP-01 challenge files can be served. +# 4. Verify that challenge files are reachable locally and via domain HTTP. +# 5. Remove only the dummy certificates created or reused by this run. +# 6. Request real Let's Encrypt certificates. +# 7. Restart OpenResty so it loads the real certificates. +# 8. Optionally install the renewal cron job. -# nginx 容器里挂载到 /etc/letsencrypt,宿主机这里对应 ./certs。 -# nginx 配置引用的是 /etc/letsencrypt/live/域名/fullchain.pem。 CERT_ROOT="./certs/live" - -# 无论用户从哪个目录执行脚本,都先切到项目根目录,保证相对路径稳定。 ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" CONF_ROOT="${CONF_ROOT:-./conf}" CONF_DIR="${CONF_DIR:-./conf/conf.d}" CREATE_MISSING_CONF="${CREATE_MISSING_CONF:-0}" INSTALL_RENEW_CRON="${INSTALL_RENEW_CRON:-1}" - -# 记录本次脚本创建或复用的 dummy 证书域名。 -# 后面只删除这些 dummy 证书,不动已经存在的正式证书。 +SKIP_PUBLIC_HTTP_CHECK="${SKIP_PUBLIC_HTTP_CHECK:-0}" DUMMY_DOMAINS="" +HTTP_PROBE_DIR="./www/.well-known/acme-challenge" +HTTP_PROBE_TOKEN="init-certs-probe-$$" +HTTP_PROBE_PATH=".well-known/acme-challenge/$HTTP_PROBE_TOKEN" +HTTP_PROBE_VALUE="openresty-gateway-probe-$$" cd "$ROOT_DIR" usage() { cat </dev/null 2>&1; then + echo "Warning: curl is missing; skipping HTTP-01 reachability probe." >&2 + return 0 + fi + + mkdir -p "$HTTP_PROBE_DIR" + printf '%s' "$HTTP_PROBE_VALUE" > "$HTTP_PROBE_DIR/$HTTP_PROBE_TOKEN" + trap cleanup_http_probe EXIT HUP INT TERM + + for domain in $DOMAINS; do + url="http://$domain/$HTTP_PROBE_PATH" + + echo "Checking local HTTP-01 route for $domain..." + if ! body="$(curl -fsS --max-time 5 --resolve "$domain:80:127.0.0.1" "$url" 2>&1)"; then + echo "Error: OpenResty is not serving the challenge path for $domain on local port 80." >&2 + echo "Tried: $url via 127.0.0.1" >&2 + echo "$body" >&2 + return 1 + fi + + if [ "$body" != "$HTTP_PROBE_VALUE" ]; then + echo "Error: local challenge response mismatch for $domain." >&2 + echo "Expected: $HTTP_PROBE_VALUE" >&2 + echo "Got: $body" >&2 + return 1 + fi + + if is_enabled "$SKIP_PUBLIC_HTTP_CHECK"; then + echo "Skipping public HTTP-01 route check for $domain." + continue + fi + + echo "Checking public HTTP-01 route for $domain..." + if ! body="$(curl -fsS --max-time 10 "$url" 2>&1)"; then + echo "Error: $domain is not reachable on public HTTP port 80." >&2 + echo "Tried: $url" >&2 + echo "$body" >&2 + echo "Check DNS, cloud security group, host firewall, and whether OpenResty is listening on 0.0.0.0:80." >&2 + echo "If this host cannot hairpin to its public IP but external clients can, rerun with SKIP_PUBLIC_HTTP_CHECK=1." >&2 + return 1 + fi + + if [ "$body" != "$HTTP_PROBE_VALUE" ]; then + echo "Error: public challenge response mismatch for $domain." >&2 + echo "Expected: $HTTP_PROBE_VALUE" >&2 + echo "Got: $body" >&2 + return 1 + fi + done +} + while [ "$#" -gt 0 ]; do case "$1" in --create-missing-conf) @@ -120,7 +176,7 @@ while [ "$#" -gt 0 ]; do exit 0 ;; *) - echo "错误:未知参数:$1" >&2 + echo "Error: unknown argument: $1" >&2 usage >&2 exit 1 ;; @@ -129,16 +185,15 @@ while [ "$#" -gt 0 ]; do done if [ -z "${DOMAINS:-}" ]; then - echo "错误:必须设置 DOMAINS。请先在 init-certs.sh 中配置,再调用 init-certs-core.sh。" >&2 + echo "Error: DOMAINS is required. Set it in init-certs.sh before calling init-certs-core.sh." >&2 exit 1 fi if [ -z "${CERT_EMAIL:-}" ]; then - echo "错误:必须设置 CERT_EMAIL。请先在 init-certs.sh 中配置,再调用 init-certs-core.sh。" >&2 + echo "Error: CERT_EMAIL is required. Set it in init-certs.sh before calling init-certs-core.sh." >&2 exit 1 fi -# 阶段 1:校验每个域名都有 nginx 配置和 ACME 校验路径。 conf_error=0 for domain in $DOMAINS; do if ! conf_file="$(find_domain_conf "$domain")"; then @@ -146,22 +201,20 @@ for domain in $DOMAINS; do CONF_DIR="$CONF_DIR" sh "$ROOT_DIR/scripts/ensure-domain-conf.sh" "$domain" conf_file="$CONF_DIR/$domain.conf" else - echo "错误:缺少 nginx 配置:$CONF_DIR/$domain.conf" >&2 - echo "提示:确认配置文件存在,或执行 sh init-certs.sh --create-missing-conf 自动生成基础模板。" >&2 + echo "Error: missing nginx config: $CONF_DIR/$domain.conf" >&2 + echo "Hint: create the config or run sh init-certs.sh --create-missing-conf." >&2 conf_error=1 continue fi fi if ! grep -Fq "/.well-known/acme-challenge/" "$conf_file"; then - echo "错误:nginx 配置缺少 ACME 校验路径:$conf_file" >&2 - echo "提示:需要添加 location ^~ /.well-known/acme-challenge/ 并把 root 指向 /var/www。" >&2 + echo "Error: nginx config lacks ACME challenge location: $conf_file" >&2 + echo "Hint: add location ^~ /.well-known/acme-challenge/ with root /var/www." >&2 conf_error=1 fi done -# 反向检查 nginx 配置里引用的证书域名是否都被 DOMAINS 覆盖。 -# 否则 OpenResty 可能因为某个额外 HTTPS 站点缺证书而启动失败。 if [ -d "$CONF_ROOT" ]; then ssl_domains="$( find "$CONF_ROOT" -type f -name '*.conf' | while IFS= read -r conf_file; do @@ -170,15 +223,15 @@ if [ -d "$CONF_ROOT" ]; then done | sort -u )" else - echo "错误:nginx 配置扫描目录不存在:$CONF_ROOT" >&2 + echo "Error: nginx config scan directory does not exist: $CONF_ROOT" >&2 ssl_domains="" conf_error=1 fi for domain in $ssl_domains; do if ! domain_in_list "$domain"; then - echo "错误:nginx 配置引用了未加入 DOMAINS 的证书域名:$domain" >&2 - echo "提示:请把 $domain 加到 init-certs.sh 的 DOMAINS,或移除对应 ssl_certificate 配置。" >&2 + echo "Error: nginx config references a certificate domain not listed in DOMAINS: $domain" >&2 + echo "Hint: add $domain to init-certs.sh DOMAINS or remove the ssl_certificate config." >&2 conf_error=1 fi done @@ -187,25 +240,21 @@ if [ "$conf_error" -ne 0 ]; then exit 1 fi -# 阶段 2:选择可用的 Docker Compose 命令。 . "$ROOT_DIR/scripts/lib-compose.sh" -# 阶段 3:为缺失证书的域名生成 dummy 证书。 -# 目的不是让浏览器信任,而是让 OpenResty 启动时能找到证书文件。 export DOMAINS CERT_ROOT DUMMY_DOMAINS="$(sh "$ROOT_DIR/scripts/ensure-dummy-certs.sh")" -# 阶段 4:启动 OpenResty。 -# 这里能启动,是因为上面已经确保每个 HTTPS 域名都有证书文件。 -# 启动后,Let's Encrypt 才能通过 HTTP 访问 /.well-known/acme-challenge/ 校验文件。 -echo "使用 dummy 证书启动 OpenResty..." +echo "Starting OpenResty with available certificates..." compose up -d openresty +echo "Validating OpenResty configuration inside the container..." +compose exec -T openresty openresty -t + +check_http_challenge + if [ -n "$DUMMY_DOMAINS" ]; then - # 阶段 5:删除本次创建或复用的 dummy 证书。 - # 必须先删掉 dummy 文件,否则 certbot 可能认为 live/域名 目录已经被占用。 - # 已存在的正式证书不会进入 DUMMY_DOMAINS,所以不会被删除。 - echo "申请正式证书前删除 dummy 证书文件..." + echo "Removing dummy certificate files before requesting real certificates..." for domain in $DUMMY_DOMAINS; do cert_dir="$CERT_ROOT/$domain" rm -f "$cert_dir/fullchain.pem" "$cert_dir/privkey.pem" "$cert_dir/.dummy-init-certs" @@ -213,34 +262,27 @@ if [ -n "$DUMMY_DOMAINS" ]; then done fi -# 阶段 6:申请正式 Let's Encrypt 证书。 -# certbot 会把 challenge 文件写到 ./www,然后由 OpenResty 对外提供访问。 if ! sh "$ROOT_DIR/scripts/request-certs.sh"; then - echo "证书申请失败,恢复缺失域名的 dummy 证书,避免 OpenResty 后续因证书文件缺失而无法启动..." >&2 + echo "Certificate request failed; restoring dummy certificates for missing domains." >&2 if ! sh "$ROOT_DIR/scripts/ensure-dummy-certs.sh" >/dev/null; then - echo "警告:dummy 证书恢复失败,请手动检查 $CERT_ROOT。" >&2 + echo "Warning: failed to restore dummy certificates. Check $CERT_ROOT manually." >&2 fi exit 1 fi -# 阶段 7:重启 OpenResty。 -# OpenResty 启动时已经加载过 dummy 证书,正式证书签发后需要重启才能加载新文件。 -echo "重启 OpenResty 以加载正式证书..." +echo "Restarting OpenResty to load real certificates..." compose restart openresty -# 阶段 8:安装证书自动续期任务。 -# scripts/install-renew-cron.sh 是幂等的:重复执行会先删除旧任务块,再写入新任务。 -# 这样首次部署只需要执行 init-certs.sh,就能同时完成启动和自动续期安装。 if is_enabled "$INSTALL_RENEW_CRON"; then if command -v crontab >/dev/null 2>&1; then - echo "安装证书自动续期 cron 任务..." + echo "Installing certificate renewal cron..." sh "$ROOT_DIR/scripts/install-renew-cron.sh" else - echo "警告:缺少 crontab 命令,已跳过证书自动续期任务安装。" >&2 - echo "提示:安装 crontab 后可手动执行 sh scripts/install-renew-cron.sh。" >&2 + echo "Warning: crontab is missing; skipped renewal cron installation." >&2 + echo "Install crontab and run sh scripts/install-renew-cron.sh later if needed." >&2 fi else - echo "跳过证书自动续期 cron 安装。" + echo "Skipped certificate renewal cron installation." fi -echo "完成。" +echo "Done." diff --git a/scripts/install-renew-cron.sh b/scripts/install-renew-cron.sh index 8de68dc..00e532b 100644 --- a/scripts/install-renew-cron.sh +++ b/scripts/install-renew-cron.sh @@ -1,9 +1,7 @@ #!/usr/bin/env sh set -eu -# 安装证书自动续期定时任务。 -# 默认每天 03:00 执行 scripts/renew-certs.sh,并把日志写到 logs/cert-renew.log。 -# 可以重复执行:安装前会先删除旧的 openresty-gateway cert renew 任务块,再写入新的任务。 +# Install an idempotent daily certificate renewal cron block. MARK_BEGIN="# BEGIN openresty-gateway cert renew" MARK_END="# END openresty-gateway cert renew" @@ -13,7 +11,7 @@ LOG_DIR="$ROOT_DIR/logs" CRON_LINE="$SCHEDULE cd '$ROOT_DIR' && sh scripts/renew-certs.sh >> logs/cert-renew.log 2>&1" if ! command -v crontab >/dev/null 2>&1; then - echo "错误:缺少 crontab 命令。" >&2 + echo "Error: crontab is required." >&2 exit 1 fi @@ -22,7 +20,7 @@ mkdir -p "$LOG_DIR" tmp_file="$(mktemp)" trap 'rm -f "$tmp_file"' EXIT -echo "删除已存在的证书续期 cron 块(如果存在)..." +echo "Replacing existing certificate renewal cron block if present..." crontab -l 2>/dev/null | sed "/$MARK_BEGIN/,/$MARK_END/d" > "$tmp_file" { echo "$MARK_BEGIN" @@ -32,5 +30,5 @@ crontab -l 2>/dev/null | sed "/$MARK_BEGIN/,/$MARK_END/d" > "$tmp_file" crontab "$tmp_file" -echo "已安装证书续期 cron:" +echo "Installed certificate renewal cron:" echo "$CRON_LINE" diff --git a/scripts/lib-compose.sh b/scripts/lib-compose.sh index a204ec2..673c91c 100644 --- a/scripts/lib-compose.sh +++ b/scripts/lib-compose.sh @@ -1,7 +1,6 @@ #!/usr/bin/env sh -# 定义 compose(),统一兼容新版 `docker compose` 和旧版 `docker-compose`。 - +# Define compose() for both Docker Compose v2 and legacy docker-compose. if docker compose version >/dev/null 2>&1; then compose() { docker compose "$@" @@ -11,6 +10,6 @@ elif command -v docker-compose >/dev/null 2>&1; then docker-compose "$@" } else - echo "错误:缺少 docker compose 或 docker-compose 命令。" >&2 + echo "Error: docker compose or docker-compose is required." >&2 exit 1 fi diff --git a/scripts/renew-certs.sh b/scripts/renew-certs.sh index f682bfd..8d2f364 100644 --- a/scripts/renew-certs.sh +++ b/scripts/renew-certs.sh @@ -1,9 +1,7 @@ #!/usr/bin/env sh set -eu -# 日常续期用: -# certbot renew 会自己判断证书是否快过期;没到续期时间不会真正重签。 -# renew 成功或无须续期后,重载 OpenResty,让已经更新的证书生效。 +# Renew certificates when needed, then reload OpenResty. ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" @@ -11,16 +9,16 @@ cd "$ROOT_DIR" . "$ROOT_DIR/scripts/lib-compose.sh" -echo "按需续期证书..." +echo "Renewing certificates if needed..." compose run --rm --entrypoint certbot certbot \ renew --webroot -w /var/www -echo "重载 OpenResty 以使用续期后的证书..." +echo "Reloading OpenResty..." if compose exec -T openresty openresty -s reload; then - echo "OpenResty 已重载。" + echo "OpenResty reloaded." else - echo "重载失败,改为重启 OpenResty..." + echo "Reload failed; restarting OpenResty..." compose restart openresty fi -echo "完成。" +echo "Done." diff --git a/scripts/request-certs.sh b/scripts/request-certs.sh index dae8aa1..c415fb0 100644 --- a/scripts/request-certs.sh +++ b/scripts/request-certs.sh @@ -1,31 +1,31 @@ #!/usr/bin/env sh set -eu -# Request Let's Encrypt certificates for DOMAINS. -# Existing valid certificates are kept by certbot because of --keep-until-expiring. +# Request Let's Encrypt certificates for DOMAINS. Existing valid certificates +# are kept by certbot because of --keep-until-expiring. ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" cd "$ROOT_DIR" if [ -z "${DOMAINS:-}" ]; then - echo "错误:必须设置 DOMAINS。" >&2 + echo "Error: DOMAINS is required." >&2 exit 1 fi if [ -z "${CERT_EMAIL:-}" ]; then - echo "错误:必须设置 CERT_EMAIL。" >&2 + echo "Error: CERT_EMAIL is required." >&2 exit 1 fi . "$ROOT_DIR/scripts/lib-compose.sh" -echo "使用 certbot 申请正式证书..." +echo "Requesting Let's Encrypt certificates..." for domain in $DOMAINS; do - echo "申请正式证书:$domain" + echo "Requesting certificate: $domain" compose run --rm --entrypoint certbot certbot \ certonly --webroot -w /var/www -d "$domain" \ --email "$CERT_EMAIL" --agree-tos --non-interactive --keep-until-expiring done -echo "证书申请步骤完成。" +echo "Certificate request step completed." diff --git a/scripts/uninstall-renew-cron.sh b/scripts/uninstall-renew-cron.sh index 2692ef7..db9d2e5 100644 --- a/scripts/uninstall-renew-cron.sh +++ b/scripts/uninstall-renew-cron.sh @@ -1,14 +1,13 @@ #!/usr/bin/env sh set -eu -# 卸载 scripts/install-renew-cron.sh 安装的证书自动续期定时任务。 -# 只删除固定标记之间的内容,不影响其它 crontab 任务。 +# Remove the cron block installed by scripts/install-renew-cron.sh. MARK_BEGIN="# BEGIN openresty-gateway cert renew" MARK_END="# END openresty-gateway cert renew" if ! command -v crontab >/dev/null 2>&1; then - echo "错误:缺少 crontab 命令。" >&2 + echo "Error: crontab is required." >&2 exit 1 fi @@ -18,4 +17,4 @@ trap 'rm -f "$tmp_file"' EXIT crontab -l 2>/dev/null | sed "/$MARK_BEGIN/,/$MARK_END/d" > "$tmp_file" crontab "$tmp_file" -echo "已卸载证书续期 cron。" +echo "Uninstalled certificate renewal cron."