mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-04 22:36:34 +08:00
fix(cli): prefer project runtime Python
This commit is contained in:
@@ -402,6 +402,7 @@ moviepilot version
|
||||
说明:
|
||||
|
||||
- `start` 会先启动后端,再启动前端;普通 `start` / `restart` 不检查、不下载、不安装更新,DEV 模式也不例外
|
||||
- 本地 CLI 会优先使用项目根目录 `venv/bin/moviepilot-python`;该入口不存在时回退到同一目录的 `venv/bin/python`,手工启动、更新和开机自启保持一致
|
||||
- `start --safe` 会以安全模式启动后端,本次启动跳过插件、调度器、监控、命令和工作流等后台扩展能力,不修改用户配置
|
||||
- `MOVIEPILOT_AUTO_UPDATE` 为布尔开关,默认 `false`;只有 `true` 启用后台 Release 检查和版本提醒,保存后定时服务热更新。`AUTO_UPDATE_RESOURCE` 独立控制站点资源检查和提醒;任一开关开启即启用检测服务,且只检查对应目标,两者均关闭才移除服务。`MOVIEPILOT_UPDATE_DEV` 为独立布尔开关,默认 `false`;设为 `true` 时手动 `moviepilot update` 默认使用 DEV 更新模式。旧 `dev/release` 值统一转换为 `MOVIEPILOT_AUTO_UPDATE=true`;旧 `dev` 在未显式配置新开关时迁移为 `MOVIEPILOT_UPDATE_DEV=true`
|
||||
- Release 更新由后台每 6 小时检查 GitHub Release;管理员确认后先静默下载安装包并显示进度,下载完成后再次确认重启,通过专门的内部更新入口安装已下载且通过 SHA-256 校验的包后再启动服务
|
||||
|
||||
19
moviepilot
19
moviepilot
@@ -456,11 +456,11 @@ legacy_config_exists() {
|
||||
}
|
||||
|
||||
run_runtime_cli() {
|
||||
if [ ! -x "$VENV_PYTHON" ]; then
|
||||
if [ ! -x "$MOVIEPILOT_PYTHON" ]; then
|
||||
echo "未找到项目虚拟环境,请先执行 moviepilot install deps 或 moviepilot setup" >&2
|
||||
exit 1
|
||||
fi
|
||||
exec "$VENV_PYTHON" -m app.cli "$@"
|
||||
exec "$MOVIEPILOT_PYTHON" -m app.cli "$@"
|
||||
}
|
||||
|
||||
show_command_help() {
|
||||
@@ -541,6 +541,13 @@ done
|
||||
|
||||
ROOT="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
||||
VENV_PYTHON="$ROOT/venv/bin/python"
|
||||
# 优先使用项目环境提供的专用 Python 入口,保留其运行身份和系统权限。
|
||||
PROJECT_RUNTIME_PYTHON="$ROOT/venv/bin/moviepilot-python"
|
||||
if [ -x "$PROJECT_RUNTIME_PYTHON" ]; then
|
||||
MOVIEPILOT_PYTHON="$PROJECT_RUNTIME_PYTHON"
|
||||
else
|
||||
MOVIEPILOT_PYTHON="$VENV_PYTHON"
|
||||
fi
|
||||
SETUP_SCRIPT="$ROOT/scripts/local_setup.py"
|
||||
|
||||
if [ -z "${CONFIG_DIR:-}" ] && [ -f "$ROOT/.moviepilot.env" ]; then
|
||||
@@ -559,8 +566,8 @@ export CONFIG_DIR
|
||||
|
||||
BOOTSTRAP_PYTHON=""
|
||||
EXTERNAL_BOOTSTRAP_PYTHON="$(find_external_python || true)"
|
||||
if [ -x "$VENV_PYTHON" ]; then
|
||||
BOOTSTRAP_PYTHON="$VENV_PYTHON"
|
||||
if [ -x "$MOVIEPILOT_PYTHON" ]; then
|
||||
BOOTSTRAP_PYTHON="$MOVIEPILOT_PYTHON"
|
||||
else
|
||||
BOOTSTRAP_PYTHON="$EXTERNAL_BOOTSTRAP_PYTHON"
|
||||
fi
|
||||
@@ -643,9 +650,9 @@ case "${1:-}" in
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -x "$VENV_PYTHON" ]; then
|
||||
if [ ! -x "$MOVIEPILOT_PYTHON" ]; then
|
||||
echo "未找到项目虚拟环境,请先执行 moviepilot install deps 或 moviepilot setup" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$VENV_PYTHON" -m app.cli "$@"
|
||||
exec "$MOVIEPILOT_PYTHON" -m app.cli "$@"
|
||||
|
||||
@@ -2997,13 +2997,19 @@ def _startup_platform_name() -> str:
|
||||
def _runtime_python_candidates(
|
||||
runtime_python: Optional[Path], venv_dir: Optional[Path]
|
||||
) -> list[Path]:
|
||||
"""按优先级列出启动解释器,并保留 venv 的 Python 启动器软链接。"""
|
||||
"""按优先级列出项目专用入口、venv 解释器和外部启动解释器。"""
|
||||
candidates: list[Path] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
resolved_venv_dir = (venv_dir or (ROOT / "venv")).expanduser().resolve()
|
||||
venv_bin_dir = get_venv_bin_dir(resolved_venv_dir)
|
||||
project_runtime_python = venv_bin_dir / (
|
||||
"moviepilot-python.exe" if os.name == "nt" else "moviepilot-python"
|
||||
)
|
||||
raw_candidates = [
|
||||
project_runtime_python,
|
||||
get_venv_python(resolved_venv_dir),
|
||||
runtime_python,
|
||||
get_venv_python((venv_dir or (ROOT / "venv")).expanduser().resolve()),
|
||||
Path(sys.executable) if sys.executable else None,
|
||||
]
|
||||
for candidate in raw_candidates:
|
||||
|
||||
@@ -67,6 +67,30 @@ def test_startup_python_resolution_preserves_virtualenv_symlink(
|
||||
assert runtime_python.is_symlink()
|
||||
|
||||
|
||||
def test_startup_python_resolution_prefers_project_runtime_launcher(
|
||||
monkeypatch, tmp_path: Path
|
||||
):
|
||||
"""Autostart must prefer the project runtime launcher over an external interpreter."""
|
||||
module = load_local_setup_module()
|
||||
venv_dir = tmp_path / "venv"
|
||||
runtime_launcher = module.get_venv_bin_dir(venv_dir) / "moviepilot-python"
|
||||
runtime_launcher.parent.mkdir(parents=True)
|
||||
runtime_launcher.symlink_to(Path(sys.executable))
|
||||
expected_path = runtime_launcher.absolute()
|
||||
external_python = tmp_path / "external-python"
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"_can_run_moviepilot_cli",
|
||||
lambda candidate: candidate == expected_path,
|
||||
)
|
||||
|
||||
runtime_python = module._resolve_runtime_python_for_startup(
|
||||
external_python, venv_dir
|
||||
)
|
||||
|
||||
assert runtime_python == expected_path
|
||||
|
||||
|
||||
def test_directory_config_keeps_download_path_as_downloader_path(monkeypatch):
|
||||
"""The wizard must preserve downloader paths without resolving them locally."""
|
||||
module = load_local_setup_module()
|
||||
|
||||
@@ -165,3 +165,40 @@ def test_recreate_excludes_custom_venv_from_external_bootstrap(
|
||||
)
|
||||
|
||||
assert record.read_text(encoding="utf-8").startswith(f"{external_python} ")
|
||||
|
||||
|
||||
def test_runtime_commands_prefer_project_runtime_launcher(tmp_path):
|
||||
"""运行命令优先使用项目 venv 下保留专用身份的 Python 入口。"""
|
||||
root = tmp_path / "moviepilot"
|
||||
root.mkdir()
|
||||
launcher = root / "moviepilot"
|
||||
launcher.write_text(LAUNCHER.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
launcher.chmod(launcher.stat().st_mode | stat.S_IXUSR)
|
||||
|
||||
runtime_bin = root / "venv" / "bin"
|
||||
runtime_bin.mkdir(parents=True)
|
||||
record = tmp_path / "record"
|
||||
venv_python = runtime_bin / "python"
|
||||
project_runtime_python = runtime_bin / "moviepilot-python"
|
||||
for python_path in (venv_python, project_runtime_python):
|
||||
python_path.write_text(
|
||||
"#!/bin/sh\n"
|
||||
"printf '%s\\n' \"$0 $*\" > \"$MOVIEPILOT_TEST_RECORD\"\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
python_path.chmod(python_path.stat().st_mode | stat.S_IXUSR)
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = "/usr/bin:/bin"
|
||||
env["MOVIEPILOT_TEST_RECORD"] = str(record)
|
||||
subprocess.run(
|
||||
[str(launcher), "doctor"],
|
||||
cwd=root,
|
||||
env=env,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
invocation = record.read_text(encoding="utf-8")
|
||||
assert invocation.startswith(f"{project_runtime_python} ")
|
||||
|
||||
Reference in New Issue
Block a user