relax local install python requirement to 3.11

This commit is contained in:
jxxghp
2026-04-16 23:13:45 +08:00
parent e0e21e39a2
commit 810cb0a203
5 changed files with 167 additions and 81 deletions

View File

@@ -11,7 +11,7 @@ curl -fsSL https://raw.githubusercontent.com/jxxghp/MoviePilot/v2/scripts/bootst
脚本会自动:
- 检测操作系统
- 自动检查并尽量安装 `git``curl``Python 3.12+`
- 自动检查并尽量安装 `git``curl``Python 3.11+`
- 克隆 `MoviePilot`
- 安装后端依赖
- 下载 `MoviePilot-Frontend` 最新 release 的 `dist.zip`
@@ -24,7 +24,8 @@ curl -fsSL https://raw.githubusercontent.com/jxxghp/MoviePilot/v2/scripts/bootst
说明:
- 如果系统里有可用的 `Python 3.12+`,脚本会优先尝试自动补齐运行环境,再继续安装
- 如果系统里已经有可用的 `Python 3.11+`,脚本会优先直接复用本地解释器
- 如果系统里没有可用的 `Python 3.11+`,脚本会再尝试自动补齐运行环境
- Linux 下安装系统依赖时通常需要 `sudo`
- 复用已有仓库时,脚本现在只会因为已跟踪源码改动而阻止自动更新,不会再被 `.DS_Store` 之类未跟踪文件卡住
@@ -141,12 +142,16 @@ moviepilot commands
```shell
moviepilot install deps
moviepilot install deps --python python3.12
moviepilot install deps --python python3.11
moviepilot install deps --venv /path/to/venv
moviepilot install deps --recreate
moviepilot install deps --config-dir /path/to/moviepilot-config
```
说明:
- 默认会自动选择本地已安装的 `Python 3.11+` 解释器
安装前端 release
```shell

View File

@@ -6,7 +6,7 @@
在开始之前,请确保您的系统已安装以下软件:
- **Python 3.12 或更高版本** (暂时兼容 3.11 ,推荐使用 3.12+)
- **Python 3.11 或更高版本**
- **pip** (Python 包管理器)
- **Git** (用于版本控制)
@@ -119,4 +119,4 @@ safety check -r requirements.txt --policy-file=safety.policy.yml > safety_report
### 5. 参考资源
- [pip-tools 官方文档](https://github.com/jazzband/pip-tools)
- [safety 官方文档](https://pyup.io/safety/)
- [safety 官方文档](https://pyup.io/safety/)

View File

@@ -91,7 +91,7 @@ Usage:
Options:
deps:
--python PYTHON 用于创建虚拟环境的 Python 解释器
--python PYTHON 用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.11+ 版本
--venv PATH 虚拟环境目录,默认 ./venv
--recreate 删除并重建虚拟环境
--config-dir PATH 指定配置目录
@@ -128,7 +128,7 @@ show_setup_help() {
Usage: moviepilot setup [OPTIONS]
Options:
--python PYTHON 用于创建虚拟环境的 Python 解释器
--python PYTHON 用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.11+ 版本
--venv PATH 虚拟环境目录,默认 ./venv
--recreate 删除并重建虚拟环境
--frontend-version TAG 前端版本,默认 latest
@@ -152,7 +152,7 @@ Options:
--ref REF 后端 Git 版本,默认 latest
--frontend-version TAG 前端版本,默认 latest
--node-version VER 本地 Node 运行时版本,默认 20.12.1
--python PYTHON 用于安装后端依赖的 Python 解释器
--python PYTHON 用于安装后端依赖的 Python 解释器,默认自动选择本地 3.11+ 版本
--venv PATH 虚拟环境目录,默认 ./venv
--recreate 删除并重建虚拟环境
--skip-resources 更新 all 时跳过资源同步
@@ -179,39 +179,60 @@ python_version_ok() {
local python_bin="$1"
"$python_bin" - <<'PY' >/dev/null 2>&1
import sys
raise SystemExit(0 if sys.version_info >= (3, 12) else 1)
raise SystemExit(0 if sys.version_info >= (3, 11) else 1)
PY
}
try_python_candidate() {
local candidate="$1"
local python_path=""
python_path="$(command -v "$candidate" 2>/dev/null || true)"
if [ -n "$python_path" ] && python_version_ok "$python_path"; then
printf '%s\n' "$python_path"
return 0
fi
return 1
}
find_system_python() {
if command -v python3 >/dev/null 2>&1; then
local python3_bin
python3_bin="$(command -v python3)"
if python_version_ok "$python3_bin"; then
printf '%s\n' "$python3_bin"
return 0
fi
fi
if command -v python >/dev/null 2>&1; then
local python_bin
python_bin="$(command -v python)"
if python_version_ok "$python_bin"; then
printf '%s\n' "$python_bin"
return 0
fi
fi
local minor
local uv_bin
local uv_python
for minor in 20 19 18 17 16 15 14 13 12 11; do
if try_python_candidate "python3.$minor"; then
return 0
fi
done
if try_python_candidate python3; then
return 0
fi
if try_python_candidate python; then
return 0
fi
for uv_bin in "$(command -v uv 2>/dev/null || true)" "$HOME/.local/bin/uv"; do
if [ -n "$uv_bin" ] && [ -x "$uv_bin" ]; then
if "$uv_bin" python find 3.12 >/dev/null 2>&1; then
"$uv_bin" python find 3.12
return 0
fi
for minor in 20 19 18 17 16 15 14 13 12 11; do
uv_python="$("$uv_bin" python find "3.$minor" 2>/dev/null || true)"
if [ -n "$uv_python" ] && python_version_ok "$uv_python"; then
printf '%s\n' "$uv_python"
return 0
fi
done
fi
done
return 1
}
require_bootstrap_python() {
if [ -n "$BOOTSTRAP_PYTHON" ]; then
return 0
fi
echo "未找到可用的 Python 3.11+ 解释器,请先安装 Python 3.11 或更高版本" >&2
exit 1
}
default_config_dir() {
case "$(uname -s)" in
Darwin)
@@ -342,10 +363,7 @@ case "${1:-}" in
;;
install)
shift
if [ -z "$BOOTSTRAP_PYTHON" ]; then
echo "未找到可用的 Python 解释器,请先安装 Python 3" >&2
exit 1
fi
require_bootstrap_python
case "${1:-}" in
deps)
shift
@@ -367,34 +385,22 @@ case "${1:-}" in
;;
init)
shift
if [ -z "$BOOTSTRAP_PYTHON" ]; then
echo "未找到可用的 Python 解释器,请先安装 Python 3" >&2
exit 1
fi
require_bootstrap_python
exec "$BOOTSTRAP_PYTHON" "$SETUP_SCRIPT" init "$@"
;;
setup)
shift
if [ -z "$BOOTSTRAP_PYTHON" ]; then
echo "未找到可用的 Python 解释器,请先安装 Python 3" >&2
exit 1
fi
require_bootstrap_python
exec "$BOOTSTRAP_PYTHON" "$SETUP_SCRIPT" setup "$@"
;;
update)
shift
if [ -z "$BOOTSTRAP_PYTHON" ]; then
echo "未找到可用的 Python 解释器,请先安装 Python 3" >&2
exit 1
fi
require_bootstrap_python
exec "$BOOTSTRAP_PYTHON" "$SETUP_SCRIPT" update "$@"
;;
agent)
shift
if [ -z "$BOOTSTRAP_PYTHON" ]; then
echo "未找到可用的 Python 解释器,请先安装 Python 3" >&2
exit 1
fi
require_bootstrap_python
exec "$BOOTSTRAP_PYTHON" "$SETUP_SCRIPT" agent "$@"
;;
esac

View File

@@ -168,43 +168,74 @@ detect_package_manager() {
esac
}
find_python() {
if command -v python3 >/dev/null 2>&1; then
command -v python3
return 0
fi
if command -v python >/dev/null 2>&1; then
command -v python
python_version_ok() {
local python_bin="$1"
"$python_bin" - <<'PY' >/dev/null 2>&1
import sys
raise SystemExit(0 if sys.version_info >= (3, 11) else 1)
PY
}
try_python_candidate() {
local candidate="$1"
local python_path=""
python_path="$(command -v "$candidate" 2>/dev/null || true)"
if [[ -n "$python_path" ]] && python_version_ok "$python_path"; then
printf '%s\n' "$python_path"
return 0
fi
return 1
}
python_version_ok() {
local python_bin="$1"
"$python_bin" - <<'PY' >/dev/null 2>&1
import sys
raise SystemExit(0 if sys.version_info >= (3, 12) else 1)
PY
find_python() {
local minor=""
for minor in 20 19 18 17 16 15 14 13 12 11; do
if try_python_candidate "python3.$minor"; then
return 0
fi
done
if try_python_candidate python3; then
return 0
fi
if try_python_candidate python; then
return 0
fi
return 1
}
find_uv_python() {
local uv_bin="$1"
local minor=""
local python_path=""
for minor in 20 19 18 17 16 15 14 13 12 11; do
python_path="$("$uv_bin" python find "3.$minor" 2>/dev/null || true)"
if [[ -n "$python_path" ]] && python_version_ok "$python_path"; then
printf '%s\n' "$python_path"
return 0
fi
done
return 1
}
python_install_hint() {
case "$OS_NAME" in
macOS)
echo "脚本已尝试自动安装 Git、curl 和 Python 3.12+。" >&2
echo "如果自动安装失败,请先安装 Homebrew或手动执行brew install git curl python@3.12" >&2
echo "脚本已尝试自动安装 Git、curl 和 Python 3.11+。" >&2
echo "如果自动安装失败,请先安装 Homebrew或手动执行brew install git curl python@3.11" >&2
;;
Linux*)
echo "脚本已尝试自动安装 Git、curl 和 Python 3.12+。" >&2
echo "如果自动安装失败,请先安装 Git、curl 和 Python 3.12,并确保包含 venv 模块。" >&2
echo "例如 Debian/Ubuntu: sudo apt install git curl python3.12 python3.12-venv" >&2
echo "例如 Fedora/RHEL: sudo dnf install git curl python3.12" >&2
echo "脚本已尝试自动安装 Git、curl 和 Python 3.11+。" >&2
echo "如果自动安装失败,请先安装 Git、curl 和 Python 3.11+,并确保包含 venv 模块。" >&2
echo "例如 Debian/Ubuntu: sudo apt install git curl python3.11 python3.11-venv" >&2
echo "例如 Fedora/RHEL: sudo dnf install git curl python3.11" >&2
;;
Windows)
echo "推荐在 WSL、Linux 或 macOS 终端中运行此脚本。" >&2
;;
*)
echo "请先安装 Git、curl 和 Python 3.12。" >&2
echo "请先安装 Git、curl 和 Python 3.11 或更高版本。" >&2
;;
esac
}
@@ -352,7 +383,7 @@ ensure_uv() {
return 0
fi
echo "==> 自动安装 uv用于拉取 Python 3.12+"
echo "==> 自动安装 uv用于补齐 Python 3.11+ 运行时"
env UV_INSTALL_DIR="$HOME/.local/bin" sh -c "$(curl -LsSf https://astral.sh/uv/install.sh)"
export PATH="$HOME/.local/bin:$PATH"
hash -r
@@ -369,12 +400,18 @@ ensure_python() {
return 0
fi
echo "==> 未找到可用的 Python 3.12+,开始自动安装独立 Python 运行时"
ensure_uv
uv python install 3.12
PYTHON_BIN="$(uv python find 3.12 || true)"
PYTHON_BIN="$(find_uv_python "$(command -v uv)" || true)"
if [[ -n "$PYTHON_BIN" ]] && python_version_ok "$PYTHON_BIN"; then
return 0
fi
echo "==> 未找到可用的 Python 3.11+,开始自动安装独立 Python 运行时"
uv python install 3.11
PYTHON_BIN="$(find_uv_python "$(command -v uv)" || true)"
if [[ -z "$PYTHON_BIN" ]] || ! python_version_ok "$PYTHON_BIN"; then
echo "自动安装 Python 3.12+ 失败。" >&2
echo "自动安装 Python 3.11+ 失败。" >&2
return 1
fi
}

View File

@@ -28,6 +28,8 @@ PUBLIC_DIR = ROOT / "public"
RUNTIME_DIR = ROOT / ".runtime"
NODE_DIR = RUNTIME_DIR / "node"
INSTALL_ENV_FILE = ROOT / ".moviepilot.env"
MIN_PYTHON_VERSION = (3, 11)
SUPPORTED_PYTHON_TEXT = f"Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]} 或更高版本"
CONFIG_DIR = LEGACY_CONFIG_DIR
LOG_DIR = CONFIG_DIR / "logs"
@@ -204,6 +206,42 @@ def command_exists(name: str) -> bool:
return shutil.which(name) is not None
def get_python_version(python_bin: str) -> tuple[int, int, int]:
version_json = capture([python_bin, "-c", "import json, sys; print(json.dumps(list(sys.version_info[:3])))"])
version_info = json.loads(version_json)
if not isinstance(version_info, list) or len(version_info) < 3:
raise RuntimeError(f"无法识别 Python 版本信息:{python_bin}")
return int(version_info[0]), int(version_info[1]), int(version_info[2])
def discover_supported_python() -> Optional[str]:
candidates = [f"python3.{minor}" for minor in range(20, MIN_PYTHON_VERSION[1] - 1, -1)]
if sys.executable:
candidates.append(sys.executable)
candidates.extend(["python3", "python"])
seen: set[str] = set()
for candidate in candidates:
if not candidate or candidate in seen:
continue
seen.add(candidate)
python_path = candidate if os.sep in candidate else (shutil.which(candidate) or "")
if not python_path:
continue
try:
version = get_python_version(python_path)
except (OSError, subprocess.CalledProcessError, json.JSONDecodeError):
continue
if version >= MIN_PYTHON_VERSION:
return python_path
return None
DEFAULT_BOOTSTRAP_PYTHON = discover_supported_python() or sys.executable
def get_venv_python(venv_dir: Path) -> Path:
if os.name == "nt":
return venv_dir / "Scripts" / "python.exe"
@@ -211,11 +249,10 @@ def get_venv_python(venv_dir: Path) -> Path:
def ensure_supported_python(python_bin: str) -> None:
version_json = capture([python_bin, "-c", "import json, sys; print(json.dumps(list(sys.version_info[:3])))"])
version = tuple(json.loads(version_json))
if version < (3, 12, 0):
version = get_python_version(python_bin)
if version < MIN_PYTHON_VERSION:
raise RuntimeError(
f"MoviePilot 本地安装需要 Python 3.12 或更高版本,当前解释器为 {python_bin} "
f"MoviePilot 本地安装需要 {SUPPORTED_PYTHON_TEXT},当前解释器为 {python_bin} "
f"({version[0]}.{version[1]}.{version[2]})"
)
@@ -1359,6 +1396,7 @@ def install_deps(*, python_bin: str, venv_dir: Path, recreate: bool) -> Path:
ensure_supported_python(python_bin)
venv_dir = venv_dir.expanduser().resolve()
venv_python = get_venv_python(venv_dir)
print_step(f"使用 Python 解释器:{python_bin}")
if recreate and venv_dir.exists():
print_step(f"删除已有虚拟环境:{venv_dir}")
@@ -1512,7 +1550,7 @@ def build_parser() -> argparse.ArgumentParser:
subparsers = parser.add_subparsers(dest="command", required=True)
install_parser = subparsers.add_parser("install-deps", help="创建虚拟环境并安装后端依赖")
install_parser.add_argument("--python", default=sys.executable, help="用于创建虚拟环境的 Python 解释器")
install_parser.add_argument("--python", default=DEFAULT_BOOTSTRAP_PYTHON, help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.11+ 版本")
install_parser.add_argument("--venv", default=str(ROOT / "venv"), help="虚拟环境目录")
install_parser.add_argument("--recreate", action="store_true", help="删除并重建虚拟环境")
install_parser.add_argument("--config-dir", help="配置目录,默认使用程序目录外的系统配置目录")
@@ -1536,7 +1574,7 @@ def build_parser() -> argparse.ArgumentParser:
init_parser.add_argument("--config-dir", help="配置目录,默认使用程序目录外的系统配置目录")
setup_parser = subparsers.add_parser("setup", help="执行 install-deps、install-frontend、install-resources 和 init")
setup_parser.add_argument("--python", default=sys.executable, help="用于创建虚拟环境的 Python 解释器")
setup_parser.add_argument("--python", default=DEFAULT_BOOTSTRAP_PYTHON, help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.11+ 版本")
setup_parser.add_argument("--venv", default=str(ROOT / "venv"), help="虚拟环境目录")
setup_parser.add_argument("--recreate", action="store_true", help="删除并重建虚拟环境")
setup_parser.add_argument("--frontend-version", default="latest", help="前端版本,默认 latest")
@@ -1560,7 +1598,7 @@ def build_parser() -> argparse.ArgumentParser:
update_parser.add_argument("--ref", default="latest", help="后端 Git 版本,默认 latest")
update_parser.add_argument("--frontend-version", default="latest", help="前端版本,默认 latest")
update_parser.add_argument("--node-version", default=DEFAULT_NODE_VERSION, help="本地 Node 运行时版本")
update_parser.add_argument("--python", default=sys.executable, help="用于安装后端依赖的 Python 解释器")
update_parser.add_argument("--python", default=DEFAULT_BOOTSTRAP_PYTHON, help="用于安装后端依赖的 Python 解释器,默认自动选择本地 3.11+ 版本")
update_parser.add_argument("--venv", default=str(ROOT / "venv"), help="虚拟环境目录")
update_parser.add_argument("--recreate", action="store_true", help="删除并重建虚拟环境")
update_parser.add_argument("--skip-resources", action="store_true", help="更新 all 时跳过资源同步")