Files
archived-MoviePilot/docker/cert.sh

233 lines
7.6 KiB
Bash
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.
#!/bin/bash
CERT_ERREXIT_WAS_SET=false
CERT_PIPEFAIL_WAS_SET=false
if [[ "$-" == *e* ]]; then
CERT_ERREXIT_WAS_SET=true
fi
if shopt -qo pipefail; then
CERT_PIPEFAIL_WAS_SET=true
fi
set -e
set -o pipefail
Green="\033[32m"
Red="\033[31m"
Yellow='\033[33m'
Font="\033[0m"
INFO="[${Green}INFO${Font}]"
ERROR="[${Red}ERROR${Font}]"
WARN="[${Yellow}WARN${Font}]"
function INFO() {
echo -e "${INFO} ${1}"
}
function ERROR() {
echo -e "${ERROR} ${1}"
}
function WARN() {
echo -e "${WARN} ${1}"
}
CERT_DIR="${CONFIG_DIR}/certs"
ACME_HOME="${CONFIG_DIR}/acme.sh"
ACME_DATA_DIR="${ACME_HOME}/data"
ACME_CERT_DIR="${CERT_DIR}/${SSL_DOMAIN}"
ACME_LATEST_CERT_DIR="${CERT_DIR}/latest"
NGINX_RELOAD_CMD="nginx -s reload 2>/dev/null || true"
# 恢复调用方原有 shell 选项,避免 source 本脚本后影响 entrypoint 后续流程。
function restore_shell_options() {
if ! ${CERT_PIPEFAIL_WAS_SET}; then
set +o pipefail
fi
if ! ${CERT_ERREXIT_WAS_SET}; then
set +e
fi
}
# 输出错误并恢复调用方 shell 选项,确保 source 失败时不会污染后续流程。
function exit_with_error() {
ERROR "$1"
restore_shell_options
exit 1
}
# 使用固定的 acme.sh 工作目录执行命令,避免签发、安装和续期读取不同配置目录。
function run_acme() {
LE_WORKING_DIR="${ACME_HOME}" \
LE_CONFIG_HOME="${ACME_DATA_DIR}" \
LE_CERT_HOME="${CERT_DIR}" \
"${ACME_HOME}/acme.sh" --home "${ACME_HOME}" "$@"
}
# 维护 nginx 使用的稳定证书目录链接,兼容手动证书和自动签发证书路径。
function link_latest_cert_dir() {
if [ -e "${ACME_LATEST_CERT_DIR}" ] && [ ! -L "${ACME_LATEST_CERT_DIR}" ]; then
rm -rf "${ACME_LATEST_CERT_DIR}"
fi
ln -sfn "${ACME_CERT_DIR}" "${ACME_LATEST_CERT_DIR}"
}
# 配置证书自动续期;续期任务失败不应阻断已有证书启动。
function configure_cert_renewal() {
if ! command -v cron >/dev/null 2>&1; then
WARN "未安装 cron跳过证书自动续期任务配置"
return 0
fi
if ! mkdir -p /etc/cron.d 2>/dev/null; then
WARN "无法创建 /etc/cron.d跳过证书自动续期任务配置"
return 0
fi
if ! printf "0 3 * * * root LE_WORKING_DIR=%q LE_CONFIG_HOME=%q LE_CERT_HOME=%q %q --cron --home %q\n" \
"${ACME_HOME}" \
"${ACME_DATA_DIR}" \
"${CERT_DIR}" \
"${ACME_HOME}/acme.sh" \
"${ACME_HOME}" > /etc/cron.d/acme 2>/dev/null; then
WARN "无法写入 /etc/cron.d/acme跳过证书自动续期任务配置"
return 0
fi
if ! chmod 644 /etc/cron.d/acme 2>/dev/null; then
WARN "无法设置 /etc/cron.d/acme 权限,证书自动续期任务可能不会生效"
return 0
fi
if ! pgrep -x cron >/dev/null 2>&1 && ! cron 2>/dev/null; then
WARN "cron 启动失败,证书自动续期任务可能不会生效"
fi
}
# 核心条件验证
if [ "${ENABLE_SSL}" = "true" ] && \
[ "${AUTO_ISSUE_CERT}" = "true" ] && \
[ -n "${SSL_DOMAIN}" ]; then
# 创建证书目录
mkdir -p "${ACME_CERT_DIR}"
if id moviepilot >/dev/null 2>&1; then
chown moviepilot:moviepilot "${CERT_DIR}" -R
fi
# 安装acme.sh使用官方安装脚本
if [ ! -f "${ACME_HOME}/acme.sh" ]; then
INFO "→ 安装acme.sh..."
# 执行官方安装命令(添加错误处理)
INFO "正在下载并安装 acme.sh..."
install_args=("--install-online")
if [ -n "${SSL_EMAIL}" ]; then
install_args+=("--accountemail" "${SSL_EMAIL}")
else
WARN "未设置SSL_EMAIL建议配置邮箱用于证书过期提醒"
fi
if ! curl -sSL https://get.acme.sh | \
LE_WORKING_DIR="${ACME_HOME}" \
LE_CONFIG_HOME="${ACME_DATA_DIR}" \
LE_CERT_HOME="${CERT_DIR}" \
sh -s -- "${install_args[@]}"; then
exit_with_error "acme.sh 安装失败"
fi
# 验证安装是否成功
if [ ! -f "${ACME_HOME}/acme.sh" ]; then
exit_with_error "acme.sh 安装后文件不存在,安装可能失败"
fi
INFO "acme.sh 安装成功"
fi
# 签发证书(仅当证书不存在时)
if [ ! -f "${ACME_CERT_DIR}/fullchain.pem" ] || [ ! -f "${ACME_CERT_DIR}/privkey.pem" ]; then
# 必要参数检查
REQUIRED_VARS=("DNS_PROVIDER")
for var in "${REQUIRED_VARS[@]}"; do
eval "value=\${${var}}"
[ -z "$value" ] && exit_with_error "必须设置环境变量: ${var}"
done
INFO "→ 签发证书: ${SSL_DOMAIN} (DNS验证方式: ${DNS_PROVIDER})"
# 加载ACME环境变量带安全过滤
acme_exported_vars=()
acme_original_keys=()
acme_original_values=()
acme_had_original_values=()
while IFS= read -r var_name; do
[ -z "${var_name}" ] && continue
key="${var_name#ACME_ENV_}"
value="${!var_name}"
# 过滤非法变量名
if [[ "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
acme_original_keys+=("${key}")
if eval "[ -n \"\${${key}+x}\" ]"; then
acme_had_original_values+=("true")
acme_original_values+=("$(eval "printf '%s' \"\${${key}}\"")")
else
acme_had_original_values+=("false")
acme_original_values+=("")
fi
export "$key"="$value"
acme_exported_vars+=("${key}")
else
WARN "跳过无效变量名: ${key}"
fi
done < <(compgen -A variable ACME_ENV_ || true)
# 签发证书(添加错误处理)
INFO "正在签发证书..."
if ! run_acme --issue \
--dns "${DNS_PROVIDER}" \
--domain "${SSL_DOMAIN}" \
--force; then
exit_with_error "证书签发失败"
fi
INFO "正在安装证书文件..."
if ! run_acme --install-cert \
--domain "${SSL_DOMAIN}" \
--key-file "${ACME_CERT_DIR}/privkey.pem" \
--fullchain-file "${ACME_CERT_DIR}/fullchain.pem" \
--reloadcmd "${NGINX_RELOAD_CMD}"; then
exit_with_error "证书安装失败"
fi
for index in "${!acme_original_keys[@]}"; do
var_name="${acme_original_keys[$index]}"
if [ "${acme_had_original_values[$index]}" = "true" ]; then
export "${var_name}=${acme_original_values[$index]}"
else
unset "${var_name}"
fi
done
# 创建稳定符号链接
link_latest_cert_dir
INFO "证书签发成功"
else
link_latest_cert_dir
INFO "证书已存在,跳过签发步骤"
fi
# 配置自动更新任务
INFO "→ 配置cron自动更新..."
configure_cert_renewal
elif [ "${ENABLE_SSL}" = "true" ] && [ "${AUTO_ISSUE_CERT}" = "true" ] && [ -z "${SSL_DOMAIN}" ]; then
exit_with_error "已启用自动签发证书但未设置 SSL_DOMAIN无法生成 HTTPS 证书"
elif [ "${ENABLE_SSL}" = "true" ] && [ "${AUTO_ISSUE_CERT}" = "false" ]; then
INFO "SSL已启用但自动签发证书已禁用将使用手动配置的证书"
# 检查证书文件是否存在
if [ -f "${ACME_LATEST_CERT_DIR}/fullchain.pem" ] && [ -f "${ACME_LATEST_CERT_DIR}/privkey.pem" ]; then
INFO "检测到证书文件SSL配置正常"
else
exit_with_error "未检测到证书文件,请将 fullchain.pem 和 privkey.pem 放入 ${ACME_LATEST_CERT_DIR}"
fi
fi
restore_shell_options