Files
dock/ssl
2026-01-15 20:48:35 +08:00

203 lines
5.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
cat >/usr/local/bin/issue_cert.sh <<'EOF'
#!/bin/sh
# POSIX sh script: compatible with Debian/Ubuntu/Alpine/busybox
set -eu
INSTALL_BASE="/data"
KEY_NAME="key.pem"
FULLCHAIN_NAME="fullchain.pem"
CA_SERVER="letsencrypt"
DEBUG_LEVEL="${DEBUG_LEVEL:-0}" # 0=off, 1=--debug, 2=--debug 2
ACME_SH="${ACME_SH:-}" # allow override: ACME_SH=/path/to/acme.sh issue_cert.sh xxx
log() { printf '\n[%s] %s\n' "$(date '+%F %T')" "$*"; }
err() { printf '\n[ERROR] %s\n' "$*" >&2; }
usage() {
cat <<USAGE
用法:
issue_cert.sh <domain> [--email you@example.com] [--force] [--wildcard]
说明:
1) 优先 Cloudflare DNS API检测到 CF_Token 时自动启用,支持自动续期)
2) 再尝试 standalone 80需要公网80可达支持自动续期但续期仍需80
3) 最后保底手动 DNS可签发但无法自动续期
输出:
/data/<domain>/key.pem
/data/<domain>/fullchain.pem
可选环境变量:
ACME_SH=/root/.acme.sh/acme.sh # 指定 acme.sh 路径
DEBUG_LEVEL=1 或 2 # 打开调试输出(对应 acme.sh --debug 或 --debug 2
示例:
issue_cert.sh ui.shanghi.net
CF_Token=xxx issue_cert.sh ui.shanghi.net
DEBUG_LEVEL=2 issue_cert.sh ui.shanghi.net --force
USAGE
}
# ---- parse args ----
[ $# -ge 1 ] || { usage; exit 1; }
DOMAIN="$1"; shift
EMAIL=""
FORCE="0"
WILDCARD="0"
while [ $# -gt 0 ]; do
case "$1" in
--email) EMAIL="${2:-}"; [ -n "$EMAIL" ] || { err "--email 缺少参数"; exit 1; }; shift 2;;
--force) FORCE="1"; shift;;
--wildcard) WILDCARD="1"; shift;;
-h|--help) usage; exit 0;;
*) err "未知参数: $1"; usage; exit 1;;
esac
done
# ---- find acme.sh ----
find_acme() {
if [ -n "${ACME_SH}" ] && [ -x "${ACME_SH}" ]; then
echo "${ACME_SH}"; return 0
fi
# Common locations
if [ -x "$HOME/.acme.sh/acme.sh" ]; then echo "$HOME/.acme.sh/acme.sh"; return 0; fi
if [ -x "/root/.acme.sh/acme.sh" ]; then echo "/root/.acme.sh/acme.sh"; return 0; fi
# Try to locate in PATH
if command -v acme.sh >/dev/null 2>&1; then
command -v acme.sh; return 0
fi
return 1
}
ACME="$(find_acme || true)"
# ---- install acme.sh if missing ----
if [ -z "$ACME" ]; then
log "未发现 acme.sh开始安装..."
if ! command -v curl >/dev/null 2>&1; then
err "系统没有 curl请先安装 curl。"; exit 1
fi
if [ -n "$EMAIL" ]; then
curl -fsSL https://get.acme.sh | sh -s email="$EMAIL"
else
curl -fsSL https://get.acme.sh | sh
fi
ACME="$(find_acme || true)"
fi
[ -n "$ACME" ] || { err "仍未找到 acme.sh。请确认安装用户与执行用户一致或用 ACME_SH 指定路径。"; exit 1; }
log "使用 acme.sh: $ACME"
# ---- prepare debug flags ----
DBG=""
if [ "$DEBUG_LEVEL" = "1" ]; then DBG="--debug"; fi
if [ "$DEBUG_LEVEL" = "2" ]; then DBG="--debug 2"; fi
# ---- set CA ----
log "设置默认 CA 为: $CA_SERVER"
# shellcheck disable=SC2086
$ACME --set-default-ca --server "$CA_SERVER" $DBG >/dev/null 2>&1 || true
# ---- prepare install dir ----
INSTALL_DIR="${INSTALL_BASE}/${DOMAIN}"
mkdir -p "$INSTALL_DIR"
KEY_PATH="${INSTALL_DIR}/${KEY_NAME}"
FULLCHAIN_PATH="${INSTALL_DIR}/${FULLCHAIN_NAME}"
# ---- build issue args ----
ISSUE_DOMAINS="-d $DOMAIN"
if [ "$WILDCARD" = "1" ]; then
# 注意:通配符只适用于 DNS 验证。并且对 ui.shanghi.net 这样的子域,
# 通配符 *.ui.shanghi.net 通常没意义。一般通配符用于 shanghi.net -> *.shanghi.net
ISSUE_DOMAINS="-d $DOMAIN -d *.$DOMAIN"
fi
FORCE_FLAG=""
[ "$FORCE" = "1" ] && FORCE_FLAG="--force"
install_cert() {
log "安装证书到: $INSTALL_DIR"
# shellcheck disable=SC2086
$ACME --install-cert -d "$DOMAIN" \
--key-file "$KEY_PATH" \
--fullchain-file "$FULLCHAIN_PATH" \
--reloadcmd "echo cert_installed_for_$DOMAIN" $DBG
log "证书文件已生成:"
ls -l "$KEY_PATH" "$FULLCHAIN_PATH" 2>/dev/null || true
if command -v openssl >/dev/null 2>&1; then
log "证书信息:"
openssl x509 -in "$FULLCHAIN_PATH" -noout -subject -issuer -dates 2>/dev/null || true
fi
}
try_cf_dns() {
if [ -n "${CF_Token:-}" ]; then
log "检测到 CF_Token尝试 Cloudflare DNS APIdns_cf..."
# shellcheck disable=SC2086
$ACME --issue $ISSUE_DOMAINS --dns dns_cf $FORCE_FLAG $DBG && return 0
log "Cloudflare DNS API 失败,继续尝试其他方式。"
fi
return 1
}
try_standalone() {
if [ "$WILDCARD" = "1" ]; then
log "通配符不支持 standalone 80跳过。"
return 1
fi
log "尝试 standalone 80需要公网80可达且本机可绑定80..."
# shellcheck disable=SC2086
$ACME --issue $ISSUE_DOMAINS --standalone $FORCE_FLAG $DBG && return 0
log "standalone 80 失败。"
return 1
}
try_manual_dns() {
log "进入保底:手动 DNS TXT 验证(--dns。"
log "提示:手动 DNS 无法自动续期(续期仍需手动加 TXT。"
# shellcheck disable=SC2086
$ACME --issue $ISSUE_DOMAINS --dns $FORCE_FLAG $DBG || return 1
log "上面已输出 TXT 记录,请去 DNS 面板添加,等待生效后再执行:"
echo " $ACME --renew -d $DOMAIN --force $DBG"
return 0
}
log "开始签发:$DOMAIN"
log "输出目录:$INSTALL_DIR"
if try_cf_dns; then
install_cert
log "完成DNS API 模式支持自动续期acme.sh 自带 cronjob 管理)。"
exit 0
fi
if try_standalone; then
install_cert
log "完成standalone 模式支持自动续期,但续期时仍需要 80 可用且公网可达。"
exit 0
fi
if try_manual_dns; then
log "已进入手动 DNS 模式:按提示添加 TXT 后续期/完成。"
exit 0
fi
err "全部方式失败。建议DEBUG_LEVEL=2 issue_cert.sh $DOMAIN --force 观察详细日志。"
exit 1
EOF
chmod +x /usr/local/bin/issue_cert.sh