mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-07-09 22:43:44 +08:00
fix(docker): reduce env logs and harden ssl cert startup
This commit is contained in:
@@ -30,6 +30,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
git \
|
git \
|
||||||
busybox \
|
busybox \
|
||||||
tini \
|
tini \
|
||||||
|
cron \
|
||||||
jq \
|
jq \
|
||||||
ripgrep \
|
ripgrep \
|
||||||
less \
|
less \
|
||||||
|
|||||||
185
docker/cert.sh
185
docker/cert.sh
@@ -1,5 +1,14 @@
|
|||||||
#!/bin/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 -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
Green="\033[32m"
|
Green="\033[32m"
|
||||||
Red="\033[31m"
|
Red="\033[31m"
|
||||||
@@ -18,110 +27,206 @@ function WARN() {
|
|||||||
echo -e "${WARN} ${1}"
|
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" ] && \
|
if [ "${ENABLE_SSL}" = "true" ] && \
|
||||||
[ "${AUTO_ISSUE_CERT}" = "true" ] && \
|
[ "${AUTO_ISSUE_CERT}" = "true" ] && \
|
||||||
[ -n "${SSL_DOMAIN}" ]; then
|
[ -n "${SSL_DOMAIN}" ]; then
|
||||||
|
|
||||||
# 创建证书目录
|
# 创建证书目录
|
||||||
mkdir -p /config/certs/"${SSL_DOMAIN}"
|
mkdir -p "${ACME_CERT_DIR}"
|
||||||
chown moviepilot:moviepilot /config/certs -R
|
if id moviepilot >/dev/null 2>&1; then
|
||||||
|
chown moviepilot:moviepilot "${CERT_DIR}" -R
|
||||||
|
fi
|
||||||
|
|
||||||
# 安装acme.sh(使用官方安装脚本)
|
# 安装acme.sh(使用官方安装脚本)
|
||||||
if [ ! -d "/config/acme.sh" ]; then
|
if [ ! -f "${ACME_HOME}/acme.sh" ]; then
|
||||||
INFO "→ 安装acme.sh..."
|
INFO "→ 安装acme.sh..."
|
||||||
|
|
||||||
# 设置安装环境变量
|
|
||||||
export LE_WORKING_DIR="/config/acme.sh"
|
|
||||||
export LE_CONFIG_HOME="/config/acme.sh/data"
|
|
||||||
export LE_CERT_HOME="/config/certs"
|
|
||||||
|
|
||||||
# 执行官方安装命令(添加错误处理)
|
# 执行官方安装命令(添加错误处理)
|
||||||
INFO "正在下载并安装 acme.sh..."
|
INFO "正在下载并安装 acme.sh..."
|
||||||
|
|
||||||
# 构建安装命令
|
install_args=("--install-online")
|
||||||
INSTALL_CMD="curl -sSL https://get.acme.sh | sh -s -- --install-online"
|
|
||||||
if [ -n "${SSL_EMAIL}" ]; then
|
if [ -n "${SSL_EMAIL}" ]; then
|
||||||
INSTALL_CMD="${INSTALL_CMD} --accountemail ${SSL_EMAIL}"
|
install_args+=("--accountemail" "${SSL_EMAIL}")
|
||||||
else
|
else
|
||||||
WARN "未设置SSL_EMAIL,建议配置邮箱用于证书过期提醒"
|
WARN "未设置SSL_EMAIL,建议配置邮箱用于证书过期提醒"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! eval "${INSTALL_CMD}"; then
|
if ! curl -sSL https://get.acme.sh | \
|
||||||
ERROR "acme.sh 安装失败"
|
LE_WORKING_DIR="${ACME_HOME}" \
|
||||||
exit 1
|
LE_CONFIG_HOME="${ACME_DATA_DIR}" \
|
||||||
|
LE_CERT_HOME="${CERT_DIR}" \
|
||||||
|
sh -s -- "${install_args[@]}"; then
|
||||||
|
exit_with_error "acme.sh 安装失败"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 验证安装是否成功
|
# 验证安装是否成功
|
||||||
if [ ! -f "/config/acme.sh/acme.sh" ]; then
|
if [ ! -f "${ACME_HOME}/acme.sh" ]; then
|
||||||
ERROR "acme.sh 安装后文件不存在,安装可能失败"
|
exit_with_error "acme.sh 安装后文件不存在,安装可能失败"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INFO "acme.sh 安装成功"
|
INFO "acme.sh 安装成功"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 签发证书(仅当证书不存在时)
|
# 签发证书(仅当证书不存在时)
|
||||||
if [ ! -f "/config/certs/${SSL_DOMAIN}/fullchain.pem" ]; then
|
if [ ! -f "${ACME_CERT_DIR}/fullchain.pem" ] || [ ! -f "${ACME_CERT_DIR}/privkey.pem" ]; then
|
||||||
# 必要参数检查
|
# 必要参数检查
|
||||||
REQUIRED_VARS=("DNS_PROVIDER")
|
REQUIRED_VARS=("DNS_PROVIDER")
|
||||||
for var in "${REQUIRED_VARS[@]}"; do
|
for var in "${REQUIRED_VARS[@]}"; do
|
||||||
eval "value=\${${var}}"
|
eval "value=\${${var}}"
|
||||||
[ -z "$value" ] && { ERROR "必须设置环境变量: ${var}"; exit 1; }
|
[ -z "$value" ] && exit_with_error "必须设置环境变量: ${var}"
|
||||||
done
|
done
|
||||||
|
|
||||||
INFO "→ 签发证书: ${SSL_DOMAIN} (DNS验证方式: ${DNS_PROVIDER})"
|
INFO "→ 签发证书: ${SSL_DOMAIN} (DNS验证方式: ${DNS_PROVIDER})"
|
||||||
|
|
||||||
# 加载ACME环境变量(带安全过滤)
|
# 加载ACME环境变量(带安全过滤)
|
||||||
INFO "正在加载ACME环境变量..."
|
acme_exported_vars=()
|
||||||
env | grep '^ACME_ENV_' | while read -r line; do
|
acme_original_keys=()
|
||||||
key="${line#ACME_ENV_}"
|
acme_original_values=()
|
||||||
key="${key%%=*}"
|
acme_had_original_values=()
|
||||||
value="${line#ACME_ENV_${key}=}"
|
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
|
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"
|
export "$key"="$value"
|
||||||
INFO "已加载环境变量: ${key}=******"
|
acme_exported_vars+=("${key}")
|
||||||
else
|
else
|
||||||
WARN "跳过无效变量名: ${key}"
|
WARN "跳过无效变量名: ${key}"
|
||||||
fi
|
fi
|
||||||
done
|
done < <(compgen -A variable ACME_ENV_ || true)
|
||||||
|
|
||||||
# 签发证书(添加错误处理)
|
# 签发证书(添加错误处理)
|
||||||
INFO "正在签发证书..."
|
INFO "正在签发证书..."
|
||||||
if ! /config/acme.sh/acme.sh --issue \
|
if ! run_acme --issue \
|
||||||
--dns "${DNS_PROVIDER}" \
|
--dns "${DNS_PROVIDER}" \
|
||||||
--domain "${SSL_DOMAIN}" \
|
--domain "${SSL_DOMAIN}" \
|
||||||
--key-file /config/certs/"${SSL_DOMAIN}"/privkey.pem \
|
|
||||||
--fullchain-file /config/certs/"${SSL_DOMAIN}"/fullchain.pem \
|
|
||||||
--reloadcmd "nginx -s reload" \
|
|
||||||
--force; then
|
--force; then
|
||||||
ERROR "证书签发失败"
|
exit_with_error "证书签发失败"
|
||||||
exit 1
|
|
||||||
fi
|
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
|
||||||
|
|
||||||
# 创建稳定符号链接
|
# 创建稳定符号链接
|
||||||
ln -sf /config/certs/"${SSL_DOMAIN}" /config/certs/latest
|
link_latest_cert_dir
|
||||||
INFO "证书签发成功"
|
INFO "证书签发成功"
|
||||||
else
|
else
|
||||||
|
link_latest_cert_dir
|
||||||
INFO "证书已存在,跳过签发步骤"
|
INFO "证书已存在,跳过签发步骤"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 配置自动更新任务
|
# 配置自动更新任务
|
||||||
INFO "→ 配置cron自动更新..."
|
INFO "→ 配置cron自动更新..."
|
||||||
echo "0 3 * * * /config/acme.sh/acme.sh --cron --home /config/acme.sh && nginx -s reload" > /etc/cron.d/acme
|
configure_cert_renewal
|
||||||
chmod 644 /etc/cron.d/acme
|
|
||||||
service cron start
|
|
||||||
|
|
||||||
elif [ "${ENABLE_SSL}" = "true" ] && [ "${AUTO_ISSUE_CERT}" = "true" ] && [ -z "${SSL_DOMAIN}" ]; then
|
elif [ "${ENABLE_SSL}" = "true" ] && [ "${AUTO_ISSUE_CERT}" = "true" ] && [ -z "${SSL_DOMAIN}" ]; then
|
||||||
WARN "已启用自动签发证书但未设置SSL_DOMAIN,跳过证书管理"
|
exit_with_error "已启用自动签发证书但未设置 SSL_DOMAIN,无法生成 HTTPS 证书"
|
||||||
elif [ "${ENABLE_SSL}" = "true" ] && [ "${AUTO_ISSUE_CERT}" = "false" ]; then
|
elif [ "${ENABLE_SSL}" = "true" ] && [ "${AUTO_ISSUE_CERT}" = "false" ]; then
|
||||||
INFO "SSL已启用但自动签发证书已禁用,将使用手动配置的证书"
|
INFO "SSL已启用但自动签发证书已禁用,将使用手动配置的证书"
|
||||||
# 检查证书文件是否存在
|
# 检查证书文件是否存在
|
||||||
if [ -f "/config/certs/latest/fullchain.pem" ] && [ -f "/config/certs/latest/privkey.pem" ]; then
|
if [ -f "${ACME_LATEST_CERT_DIR}/fullchain.pem" ] && [ -f "${ACME_LATEST_CERT_DIR}/privkey.pem" ]; then
|
||||||
INFO "检测到证书文件,SSL配置正常"
|
INFO "检测到证书文件,SSL配置正常"
|
||||||
else
|
else
|
||||||
WARN "未检测到证书文件,请确保手动配置了正确的证书路径"
|
exit_with_error "未检测到证书文件,请将 fullchain.pem 和 privkey.pem 放入 ${ACME_LATEST_CERT_DIR}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
restore_shell_options
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ export PATH="${VENV_PATH}/bin:$PATH"
|
|||||||
# 校正设置目录
|
# 校正设置目录
|
||||||
CONFIG_DIR="${CONFIG_DIR:-/config}"
|
CONFIG_DIR="${CONFIG_DIR:-/config}"
|
||||||
|
|
||||||
# 记录非系统环境(docker容器表)提供的变量
|
|
||||||
declare -ga VARS_SET_BY_SCRIPT=()
|
|
||||||
|
|
||||||
# 环境变量补全
|
# 环境变量补全
|
||||||
# 优先级: 系统环境变量 -> .env 文件 (即使为空字符串) -> 预设默认值
|
# 优先级: 系统环境变量 -> .env 文件 (即使为空字符串) -> 预设默认值
|
||||||
# 精准适配 Python 端 set_key (quote_mode="always", 单引号包裹, \' 转义)
|
# 精准适配 Python 端 set_key (quote_mode="always", 单引号包裹, \' 转义)
|
||||||
@@ -38,7 +35,7 @@ function load_config_from_app_env() {
|
|||||||
local env_file="${CONFIG_DIR}/app.env"
|
local env_file="${CONFIG_DIR}/app.env"
|
||||||
|
|
||||||
# 定义 ["变量名"]="预设默认值"
|
# 定义 ["变量名"]="预设默认值"
|
||||||
# 禁止填入 CONFIG_DIR 变量,ACME_ENV_ 开头的变量暂时不处理,还是交由 cert.sh 处理
|
# 禁止填入 CONFIG_DIR 变量,ACME_ENV_ 开头的变量不设默认值,仅透传 app.env 中已有配置。
|
||||||
declare -A vars_and_default_values=(
|
declare -A vars_and_default_values=(
|
||||||
# update.sh
|
# update.sh
|
||||||
["PIP_PROXY"]=""
|
["PIP_PROXY"]=""
|
||||||
@@ -48,19 +45,13 @@ function load_config_from_app_env() {
|
|||||||
["MOVIEPILOT_AUTO_UPDATE"]="release"
|
["MOVIEPILOT_AUTO_UPDATE"]="release"
|
||||||
["BROWSER_EMULATION"]="cloakbrowser"
|
["BROWSER_EMULATION"]="cloakbrowser"
|
||||||
|
|
||||||
# database
|
|
||||||
["DB_TYPE"]="sqlite"
|
|
||||||
["DB_POSTGRESQL_HOST"]="localhost"
|
|
||||||
["DB_POSTGRESQL_PORT"]="5432"
|
|
||||||
["DB_POSTGRESQL_DATABASE"]="moviepilot"
|
|
||||||
["DB_POSTGRESQL_USERNAME"]="moviepilot"
|
|
||||||
["DB_POSTGRESQL_PASSWORD"]="moviepilot"
|
|
||||||
["DB_POSTGRESQL_POOL_SIZE"]="20"
|
|
||||||
["DB_POSTGRESQL_MAX_OVERFLOW"]="30"
|
|
||||||
|
|
||||||
# cert
|
# cert
|
||||||
["ENABLE_SSL"]="false"
|
["ENABLE_SSL"]="false"
|
||||||
|
["AUTO_ISSUE_CERT"]="false"
|
||||||
["SSL_DOMAIN"]=""
|
["SSL_DOMAIN"]=""
|
||||||
|
["SSL_EMAIL"]=""
|
||||||
|
["DNS_PROVIDER"]=""
|
||||||
|
["SSL_NGINX_PORT"]="443"
|
||||||
["NGINX_PORT"]="3000"
|
["NGINX_PORT"]="3000"
|
||||||
["PORT"]="3001"
|
["PORT"]="3001"
|
||||||
["NGINX_CLIENT_MAX_BODY_SIZE"]="50m"
|
["NGINX_CLIENT_MAX_BODY_SIZE"]="50m"
|
||||||
@@ -83,7 +74,7 @@ function load_config_from_app_env() {
|
|||||||
key_in_file="${BASH_REMATCH[1]}"
|
key_in_file="${BASH_REMATCH[1]}"
|
||||||
value_raw_in_file="${BASH_REMATCH[2]}"
|
value_raw_in_file="${BASH_REMATCH[2]}"
|
||||||
|
|
||||||
if [[ -n "${vars_and_default_values[$key_in_file]+_}" ]]; then
|
if [[ -n "${vars_and_default_values[$key_in_file]+_}" || "${key_in_file}" == ACME_ENV_* ]]; then
|
||||||
local temp_val_after_initial_trim
|
local temp_val_after_initial_trim
|
||||||
temp_val_after_initial_trim="${value_raw_in_file#"${value_raw_in_file%%[![:space:]]*}"}"
|
temp_val_after_initial_trim="${value_raw_in_file#"${value_raw_in_file%%[![:space:]]*}"}"
|
||||||
temp_val_after_initial_trim="${temp_val_after_initial_trim%"${temp_val_after_initial_trim##*[![:space:]]}"}"
|
temp_val_after_initial_trim="${temp_val_after_initial_trim%"${temp_val_after_initial_trim##*[![:space:]]}"}"
|
||||||
@@ -120,20 +111,16 @@ function load_config_from_app_env() {
|
|||||||
INFO "${env_file} 文件不存在,跳过文件加载。"
|
INFO "${env_file} 文件不存在,跳过文件加载。"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INFO "正在根据优先级确定并导出配置值..."
|
|
||||||
for var_name in "${!vars_and_default_values[@]}"; do
|
for var_name in "${!vars_and_default_values[@]}"; do
|
||||||
local fallback_value="${vars_and_default_values[$var_name]}"
|
local fallback_value="${vars_and_default_values[$var_name]}"
|
||||||
local final_value
|
local final_value
|
||||||
local value_source="未设置"
|
local value_source="未设置"
|
||||||
# 标志变量是否来自初始环境
|
|
||||||
local set_by_initial_env=false
|
|
||||||
|
|
||||||
# 检查变量是否在环境中已设置(可能为空)
|
# 检查变量是否在环境中已设置(可能为空)
|
||||||
if eval "[ -n \"\${${var_name}+x}\" ]"; then
|
if eval "[ -n \"\${${var_name}+x}\" ]"; then
|
||||||
# 获取其值
|
# 获取其值
|
||||||
final_value="$(eval echo \"\$"${var_name}"\")"
|
final_value="$(eval echo \"\$"${var_name}"\")"
|
||||||
value_source="系统环境变量"
|
value_source="系统环境变量"
|
||||||
set_by_initial_env=true
|
|
||||||
elif [[ -n "${values_from_env_file["${var_name}"]+_}" ]]; then
|
elif [[ -n "${values_from_env_file["${var_name}"]+_}" ]]; then
|
||||||
final_value="${values_from_env_file["${var_name}"]}"
|
final_value="${values_from_env_file["${var_name}"]}"
|
||||||
value_source=".env 文件"
|
value_source=".env 文件"
|
||||||
@@ -142,31 +129,20 @@ function load_config_from_app_env() {
|
|||||||
value_source="内置默认值"
|
value_source="内置默认值"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 不论来源如何,都导出变量,以便脚本的其余部分和子进程使用
|
if ! declare -g "${var_name}=${final_value}"; then
|
||||||
# (例如 envsubst, mp_update.sh, cert.sh)
|
ERROR "设置变量 ${var_name}, 值: '${final_value}'失败 (来源: ${value_source}) "
|
||||||
if declare -gx "${var_name}=${final_value}"; then
|
fi
|
||||||
if [ -z "${final_value}" ]; then
|
done
|
||||||
INFO "变量 ${var_name}, 值为空 (来源: ${value_source})。"
|
|
||||||
else
|
|
||||||
INFO "变量 ${var_name}, 值: ${final_value} (来源: ${value_source})。"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 如果变量不是来自初始环境变量,则记录下来以便稍后 unset
|
for var_name in "${!values_from_env_file[@]}"; do
|
||||||
if ! ${set_by_initial_env}; then
|
if [[ "${var_name}" != ACME_ENV_* ]]; then
|
||||||
# 检查是否已在数组中,避免重复添加
|
continue
|
||||||
local found_in_script_vars=false
|
fi
|
||||||
for item in "${VARS_SET_BY_SCRIPT[@]}"; do
|
if eval "[ -n \"\${${var_name}+x}\" ]"; then
|
||||||
if [[ "$item" == "$var_name" ]]; then
|
continue
|
||||||
found_in_script_vars=true
|
fi
|
||||||
break
|
if ! declare -g "${var_name}=${values_from_env_file["${var_name}"]}"; then
|
||||||
fi
|
ERROR "设置变量 ${var_name} 失败 (来源: .env 文件) "
|
||||||
done
|
|
||||||
if ! ${found_in_script_vars}; then
|
|
||||||
VARS_SET_BY_SCRIPT+=("${var_name}")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
ERROR "导出变量 ${var_name}, 值: '${final_value}'失败 (来源: ${value_source}) "
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -174,6 +150,46 @@ function load_config_from_app_env() {
|
|||||||
INFO "配置加载流程执行完毕。"
|
INFO "配置加载流程执行完毕。"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 生成 nginx 配置,仅为 envsubst 单次调用传入模板变量。
|
||||||
|
function render_nginx_config() {
|
||||||
|
local https_server_conf
|
||||||
|
if [ "${ENABLE_SSL}" = "true" ]; then
|
||||||
|
https_server_conf=$(cat <<EOF
|
||||||
|
server {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
listen ${SSL_NGINX_PORT:-443} ssl;
|
||||||
|
listen [::]:${SSL_NGINX_PORT:-443} ssl;
|
||||||
|
server_name ${SSL_DOMAIN:-moviepilot};
|
||||||
|
|
||||||
|
# SSL证书路径
|
||||||
|
ssl_certificate ${CONFIG_DIR}/certs/latest/fullchain.pem;
|
||||||
|
ssl_certificate_key ${CONFIG_DIR}/certs/latest/privkey.pem;
|
||||||
|
|
||||||
|
# SSL安全配置
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
|
||||||
|
# 公共配置
|
||||||
|
include common.conf;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
else
|
||||||
|
https_server_conf="# HTTPS未启用"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NGINX_PORT="${NGINX_PORT}" \
|
||||||
|
PORT="${PORT}" \
|
||||||
|
NGINX_CLIENT_MAX_BODY_SIZE="${NGINX_CLIENT_MAX_BODY_SIZE}" \
|
||||||
|
HTTPS_SERVER_CONF="${https_server_conf}" \
|
||||||
|
envsubst '${NGINX_PORT}${PORT}${NGINX_CLIENT_MAX_BODY_SIZE}${HTTPS_SERVER_CONF}' < /etc/nginx/nginx.template.conf > /etc/nginx/nginx.conf
|
||||||
|
}
|
||||||
|
|
||||||
# 优雅退出
|
# 优雅退出
|
||||||
function graceful_exit() {
|
function graceful_exit() {
|
||||||
local exit_code=${1:-0}
|
local exit_code=${1:-0}
|
||||||
@@ -265,52 +281,21 @@ if [ -f "${ONE_SHOT_UPDATE_FLAG}" ]; then
|
|||||||
fi
|
fi
|
||||||
if [ "${ONE_SHOT_UPDATE_MODE}" = "release" ] || [ "${ONE_SHOT_UPDATE_MODE}" = "dev" ]; then
|
if [ "${ONE_SHOT_UPDATE_MODE}" = "release" ] || [ "${ONE_SHOT_UPDATE_MODE}" = "dev" ]; then
|
||||||
INFO "检测到一次性升级标记,本次启动将执行 ${ONE_SHOT_UPDATE_MODE} 升级..."
|
INFO "检测到一次性升级标记,本次启动将执行 ${ONE_SHOT_UPDATE_MODE} 升级..."
|
||||||
export MOVIEPILOT_AUTO_UPDATE="${ONE_SHOT_UPDATE_MODE}"
|
MOVIEPILOT_AUTO_UPDATE="${ONE_SHOT_UPDATE_MODE}"
|
||||||
ONE_SHOT_UPDATE_APPLIED="true"
|
ONE_SHOT_UPDATE_APPLIED="true"
|
||||||
elif [ -n "${ONE_SHOT_UPDATE_MODE}" ]; then
|
elif [ -n "${ONE_SHOT_UPDATE_MODE}" ]; then
|
||||||
WARN "检测到无效的一次性升级模式:${ONE_SHOT_UPDATE_MODE},已忽略"
|
WARN "检测到无效的一次性升级模式:${ONE_SHOT_UPDATE_MODE},已忽略"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 生成HTTPS配置块
|
# 使用env配置渲染 nginx 配置
|
||||||
if [ "${ENABLE_SSL}" = "true" ]; then
|
render_nginx_config
|
||||||
export HTTPS_SERVER_CONF=$(cat <<EOF
|
|
||||||
server {
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
listen ${SSL_NGINX_PORT:-443} ssl;
|
|
||||||
listen [::]:${SSL_NGINX_PORT:-443} ssl;
|
|
||||||
server_name ${SSL_DOMAIN:-moviepilot};
|
|
||||||
|
|
||||||
# SSL证书路径
|
|
||||||
ssl_certificate ${CONFIG_DIR}/certs/latest/fullchain.pem;
|
|
||||||
ssl_certificate_key ${CONFIG_DIR}/certs/latest/privkey.pem;
|
|
||||||
|
|
||||||
# SSL安全配置
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
ssl_session_cache shared:SSL:10m;
|
|
||||||
ssl_session_timeout 10m;
|
|
||||||
|
|
||||||
# 公共配置
|
|
||||||
include common.conf;
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
else
|
|
||||||
export HTTPS_SERVER_CONF="# HTTPS未启用"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 使用 `envsubst` 将模板文件中的 ${NGINX_PORT} 替换为实际的环境变量值
|
|
||||||
envsubst '${NGINX_PORT}${PORT}${NGINX_CLIENT_MAX_BODY_SIZE}${ENABLE_SSL}${HTTPS_SERVER_CONF}' < /etc/nginx/nginx.template.conf > /etc/nginx/nginx.conf
|
|
||||||
|
|
||||||
# 自动更新
|
# 自动更新
|
||||||
cd /
|
cd /
|
||||||
source /usr/local/bin/mp_update.sh
|
source /usr/local/bin/mp_update.sh
|
||||||
if [ "${ONE_SHOT_UPDATE_APPLIED}" = "true" ]; then
|
if [ "${ONE_SHOT_UPDATE_APPLIED}" = "true" ]; then
|
||||||
export MOVIEPILOT_AUTO_UPDATE="${MOVIEPILOT_AUTO_UPDATE_ORIGINAL}"
|
MOVIEPILOT_AUTO_UPDATE="${MOVIEPILOT_AUTO_UPDATE_ORIGINAL}"
|
||||||
fi
|
fi
|
||||||
cd /app || exit
|
cd /app || exit
|
||||||
|
|
||||||
@@ -375,22 +360,6 @@ fi
|
|||||||
# 设置后端服务权限掩码
|
# 设置后端服务权限掩码
|
||||||
umask "${UMASK}"
|
umask "${UMASK}"
|
||||||
|
|
||||||
# 清除非系统环境导入的变量,保证转移到 dumb-init 的时候,不会带入不必要的环境变量
|
|
||||||
INFO "准备为 Python 应用清理的非系统环境导入的变量..."
|
|
||||||
if [ ${#VARS_SET_BY_SCRIPT[@]} -gt 0 ]; then
|
|
||||||
for var_to_unset in "${VARS_SET_BY_SCRIPT[@]}"; do
|
|
||||||
# 再次确认变量确实存在于当前环境中(虽然理论上应该存在)
|
|
||||||
if eval "[ -n \"\${${var_to_unset}+x}\" ]"; then
|
|
||||||
INFO "取消设置环境变量: ${var_to_unset}"
|
|
||||||
unset "${var_to_unset}"
|
|
||||||
else
|
|
||||||
WARN "变量 ${var_to_unset} 已不存在,无需取消设置。"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
INFO "没有由非系统环境导入的变量需要清理。"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 启动后端服务
|
# 启动后端服务
|
||||||
INFO "→ 启动后端服务..."
|
INFO "→ 启动后端服务..."
|
||||||
if [ "${START_NOGOSU:-false}" = "true" ]; then
|
if [ "${START_NOGOSU:-false}" = "true" ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user