mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 16:36:53 +08:00
优化 Docker 启动完成日志 (#6112)
This commit is contained in:
@@ -20,6 +20,8 @@ function WARN() {
|
|||||||
echo -e "${WARN} ${1}"
|
echo -e "${WARN} ${1}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ENTRYPOINT_START_TIME="$(date +%s)"
|
||||||
|
|
||||||
function normalize_env_value() {
|
function normalize_env_value() {
|
||||||
printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]'
|
printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]'
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,42 @@ function run_package_command() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wait_backend_ready() {
|
||||||
|
local entrypoint_start_time="${1:-$(date +%s)}"
|
||||||
|
local backend_start_time="${2:-$(date +%s)}"
|
||||||
|
local python_pid="${3:-}"
|
||||||
|
local backend_port="${PORT:-3001}"
|
||||||
|
local web_port="${NGINX_PORT:-3000}"
|
||||||
|
local timeout="${MOVIEPILOT_BACKEND_READY_TIMEOUT:-300}"
|
||||||
|
local ready_url="http://127.0.0.1:${backend_port}/api/v1/system/global?token=moviepilot"
|
||||||
|
local deadline
|
||||||
|
if ! [[ "${timeout}" =~ ^[0-9]+$ ]] || [ "$((10#${timeout}))" -le 0 ]; then
|
||||||
|
WARN "→ MOVIEPILOT_BACKEND_READY_TIMEOUT=${timeout} 无效,使用默认 300 秒。"
|
||||||
|
timeout=300
|
||||||
|
else
|
||||||
|
timeout=$((10#${timeout}))
|
||||||
|
fi
|
||||||
|
deadline=$(( $(date +%s) + timeout ))
|
||||||
|
|
||||||
|
while [ "$(date +%s)" -lt "${deadline}" ]; do
|
||||||
|
if [ -n "${python_pid}" ] && ! kill -0 "${python_pid}" >/dev/null 2>&1; then
|
||||||
|
WARN "→ 后端服务启动完成探测已停止:后端进程已退出。"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if curl -fsS --max-time 2 "${ready_url}" >/dev/null 2>&1; then
|
||||||
|
local now
|
||||||
|
now="$(date +%s)"
|
||||||
|
INFO "→ MoviePilot Web 已可访问,启动总耗时 $(( now - entrypoint_start_time )) 秒,后端就绪耗时 $(( now - backend_start_time )) 秒,后端端口 ${backend_port},前端端口 ${web_port}。"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
WARN "→ 后端服务启动完成探测超时,已等待 ${timeout} 秒,后端端口 ${backend_port},继续等待进程日志..."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# 环境变量补全
|
# 环境变量补全
|
||||||
# 优先级: 系统环境变量 -> .env 文件 (即使为空字符串) -> 预设默认值
|
# 优先级: 系统环境变量 -> .env 文件 (即使为空字符串) -> 预设默认值
|
||||||
# 精准适配 Python 端 set_key (quote_mode="always", 单引号包裹, \' 转义)
|
# 精准适配 Python 端 set_key (quote_mode="always", 单引号包裹, \' 转义)
|
||||||
@@ -480,12 +518,14 @@ umask "${UMASK}"
|
|||||||
|
|
||||||
# 启动后端服务
|
# 启动后端服务
|
||||||
INFO "→ 启动后端服务..."
|
INFO "→ 启动后端服务..."
|
||||||
|
BACKEND_START_TIME="$(date +%s)"
|
||||||
if [ "${START_NOGOSU:-false}" = "true" ]; then
|
if [ "${START_NOGOSU:-false}" = "true" ]; then
|
||||||
"${VENV_PATH}/bin/python3" app/main.py > /dev/stdout 2> /dev/stderr &
|
"${VENV_PATH}/bin/python3" app/main.py > /dev/stdout 2> /dev/stderr &
|
||||||
else
|
else
|
||||||
gosu moviepilot:moviepilot "${VENV_PATH}/bin/python3" app/main.py > /dev/stdout 2> /dev/stderr &
|
gosu moviepilot:moviepilot "${VENV_PATH}/bin/python3" app/main.py > /dev/stdout 2> /dev/stderr &
|
||||||
fi
|
fi
|
||||||
PYTHON_PID=$!
|
PYTHON_PID=$!
|
||||||
|
wait_backend_ready "${ENTRYPOINT_START_TIME}" "${BACKEND_START_TIME}" "${PYTHON_PID}" &
|
||||||
|
|
||||||
# 等待 Python 进程退出。
|
# 等待 Python 进程退出。
|
||||||
# 如果收到信号,trap 会中断 wait,并执行 graceful_exit。
|
# 如果收到信号,trap 会中断 wait,并执行 graceful_exit。
|
||||||
|
|||||||
@@ -78,6 +78,26 @@ def _run_permission_case(tmp_path: Path, body: str, env: dict[str, str] | None =
|
|||||||
return chown_log.read_text(encoding="utf-8") if chown_log.exists() else ""
|
return chown_log.read_text(encoding="utf-8") if chown_log.exists() else ""
|
||||||
|
|
||||||
|
|
||||||
|
def _run_entrypoint_case(tmp_path: Path, body: str, env: dict[str, str] | None = None) -> str:
|
||||||
|
functions = _write_entrypoint_functions(tmp_path)
|
||||||
|
case_env = {
|
||||||
|
**os.environ,
|
||||||
|
"ENTRYPOINT_FUNCTIONS": str(functions),
|
||||||
|
}
|
||||||
|
if env:
|
||||||
|
case_env.update(env)
|
||||||
|
|
||||||
|
script = textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
set -euo pipefail
|
||||||
|
source "${{ENTRYPOINT_FUNCTIONS}}"
|
||||||
|
{body}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = subprocess.run(["bash", "-c", script], check=True, env=case_env, text=True, capture_output=True)
|
||||||
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_image_paths_are_not_chowned_by_default_regardless_of_owner(tmp_path: Path) -> None:
|
def test_image_paths_are_not_chowned_by_default_regardless_of_owner(tmp_path: Path) -> None:
|
||||||
log = _run_permission_case(
|
log = _run_permission_case(
|
||||||
tmp_path,
|
tmp_path,
|
||||||
@@ -170,3 +190,56 @@ def test_runtime_writable_paths_are_still_corrected(tmp_path: Path) -> None:
|
|||||||
assert not any(line.startswith("-R ") and ".cloakbrowser" in line for line in lines)
|
assert not any(line.startswith("-R ") and ".cloakbrowser" in line for line in lines)
|
||||||
assert not any(f"{tmp_path}/app " in line for line in lines)
|
assert not any(f"{tmp_path}/app " in line for line in lines)
|
||||||
assert not any(f"{tmp_path}/public" in line for line in lines)
|
assert not any(f"{tmp_path}/public" in line for line in lines)
|
||||||
|
|
||||||
|
|
||||||
|
def test_backend_ready_log_uses_configured_ports(tmp_path: Path) -> None:
|
||||||
|
curl_log = tmp_path / "curl.log"
|
||||||
|
output = _run_entrypoint_case(
|
||||||
|
tmp_path,
|
||||||
|
"""
|
||||||
|
INFO() { printf '[INFO] %s\\n' "$1"; }
|
||||||
|
curl() {
|
||||||
|
printf '%s\\n' "$*" > "${CURL_LOG}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
PORT=4321 NGINX_PORT=8765 wait_backend_ready 1 2 "$$"
|
||||||
|
""",
|
||||||
|
env={"CURL_LOG": str(curl_log)},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert curl_log.read_text(encoding="utf-8") == (
|
||||||
|
"-fsS --max-time 2 http://127.0.0.1:4321/api/v1/system/global?token=moviepilot\n"
|
||||||
|
)
|
||||||
|
assert "MoviePilot Web 已可访问" in output
|
||||||
|
assert "后端就绪耗时" in output
|
||||||
|
assert "后端端口 4321" in output
|
||||||
|
assert "前端端口 8765" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_backend_ready_timeout_falls_back_to_default_for_invalid_value(tmp_path: Path) -> None:
|
||||||
|
output = _run_entrypoint_case(
|
||||||
|
tmp_path,
|
||||||
|
"""
|
||||||
|
WARN() { printf '[WARN] %s\\n' "$1"; }
|
||||||
|
curl() { return 1; }
|
||||||
|
MOVIEPILOT_BACKEND_READY_TIMEOUT=invalid wait_backend_ready 1 2 999999 || true
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "MOVIEPILOT_BACKEND_READY_TIMEOUT=invalid 无效,使用默认 300 秒" in output
|
||||||
|
assert "后端服务启动完成探测已停止:后端进程已退出" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_backend_ready_timeout_accepts_leading_zero_decimal(tmp_path: Path) -> None:
|
||||||
|
output = _run_entrypoint_case(
|
||||||
|
tmp_path,
|
||||||
|
"""
|
||||||
|
INFO() { printf '[INFO] %s\\n' "$1"; }
|
||||||
|
WARN() { printf '[WARN] %s\\n' "$1"; }
|
||||||
|
curl() { return 0; }
|
||||||
|
MOVIEPILOT_BACKEND_READY_TIMEOUT=08 wait_backend_ready 1 2 "$$"
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "MOVIEPILOT_BACKEND_READY_TIMEOUT=08 无效" not in output
|
||||||
|
assert "MoviePilot Web 已可访问" in output
|
||||||
|
|||||||
Reference in New Issue
Block a user