mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-29 23:16:48 +00:00
233 lines
7.6 KiB
Bash
233 lines
7.6 KiB
Bash
#!/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
|