diff --git a/owpengram-server.bat b/owpengram-server.bat index fb2e47d0..6a2e88bf 100644 --- a/owpengram-server.bat +++ b/owpengram-server.bat @@ -55,8 +55,8 @@ if defined PYTHON ( if not defined MISSING (set "MISSING=%%m") else (set "MISSING=!MISSING!, %%m") ) if defined MISSING ( - echo [WARN] Missing Python packages: !MISSING! - echo Install them with: !PYTHON! -m pip install -r tui-panel\requirements-panel.txt + echo [WARN] Missing or outdated Python packages: !MISSING! + echo Install them with: !PYTHON! -m pip install -U -r tui-panel\requirements-panel.txt set "PROBLEMS=1" ) else ( echo [ok] Python dependencies OK ^(textual, psutil, cryptography^) diff --git a/owpengram-server.sh b/owpengram-server.sh index 340e6fc0..78e6fdf3 100644 --- a/owpengram-server.sh +++ b/owpengram-server.sh @@ -30,15 +30,28 @@ fi # of launching real Python. Validate the version output too. PYTHON="" PYTHON_VERSION="" -for cand in python3 python; do - if command -v "$cand" >/dev/null 2>&1; then - if VER_OUT="$("$cand" --version 2>&1)" && [[ "$VER_OUT" == Python\ 3* ]]; then - PYTHON="$cand" - PYTHON_VERSION="$VER_OUT" - break - fi +# A local .venv/ (created per this script's own advice below, to install +# tui-panel/requirements-panel.txt without touching a Debian/Ubuntu +# system Python) takes priority over PATH -- once it exists, every later run +# should keep using it instead of silently falling back to an older/ +# unrelated system interpreter. +if [[ -x ".venv/bin/python" ]]; then + if VER_OUT="$(.venv/bin/python --version 2>&1)" && [[ "$VER_OUT" == Python\ 3* ]]; then + PYTHON=".venv/bin/python" + PYTHON_VERSION="$VER_OUT" fi -done +fi +if [[ -z "$PYTHON" ]]; then + for cand in python3 python; do + if command -v "$cand" >/dev/null 2>&1; then + if VER_OUT="$("$cand" --version 2>&1)" && [[ "$VER_OUT" == Python\ 3* ]]; then + PYTHON="$cand" + PYTHON_VERSION="$VER_OUT" + break + fi + fi + done +fi if [[ -z "$PYTHON" ]]; then warn "Python 3 is not installed (needed to run the server-panel TUI)" @@ -52,8 +65,12 @@ fi if [[ -n "$PYTHON" ]]; then MISSING="$("$PYTHON" tui-panel/check_deps.py)" if [[ -n "$MISSING" ]]; then - warn "Missing Python packages: $(echo "$MISSING" | tr '\n' ' ')" - echo " Install them with: $PYTHON -m pip install -r tui-panel/requirements-panel.txt" + warn "Missing or outdated Python packages: $(echo "$MISSING" | tr '\n' ' ')" + echo " Install them with: $PYTHON -m pip install -U -r tui-panel/requirements-panel.txt" + echo " On Debian/Ubuntu this usually fails with 'externally-managed-environment'" + echo " against the system Python -- use a venv instead, then just re-run this script" + echo " (it prefers .venv/ automatically once one exists):" + echo " python3 -m venv .venv && .venv/bin/pip install -r tui-panel/requirements-panel.txt" PROBLEMS=1 else ok "Python dependencies OK (textual, psutil, cryptography)" diff --git a/tui-panel/check_deps.py b/tui-panel/check_deps.py index 4549a69a..da374eb8 100644 --- a/tui-panel/check_deps.py +++ b/tui-panel/check_deps.py @@ -1,35 +1,73 @@ #!/usr/bin/env python3 -"""Reports which Python packages server-panel.py needs are missing. +"""Reports which Python packages server-panel.py needs are missing or too old. Used by owpengram-server.sh / owpengram-server.bat before launching the panel, so both launchers share one source of truth for the dependency list instead of duplicating it in shell and batch syntax. -Prints one missing package's pip install name per line. Exit code is 0 if -everything is already installed, 1 if anything is missing. +A plain `importlib.import_module` presence check isn't enough: on Debian/ +Ubuntu, `apt`'s python3-textual (and similar system packages) can be old +enough to import fine but lack symbols server-panel.py needs (e.g. the +`work` decorator, added well after the ancient version some distros still +ship) -- the import check passes, then the panel crashes with an +ImportError of its own a few lines into startup. Checking the installed +version against requirements-panel.txt's floor catches that upfront, with +the same actionable message as a genuinely missing package. + +Prints one missing/outdated package's pip install spec per line. Exit code +is 0 if everything installed satisfies its minimum version, 1 otherwise. """ import importlib +import importlib.metadata import sys REQUIRED = [ - ("textual", "textual"), - ("psutil", "psutil"), - ("cryptography", "cryptography"), + ("textual", "textual", "0.60"), + ("psutil", "psutil", "5.9"), + ("cryptography", "cryptography", "41.0"), ] +def _version_tuple(version: str) -> tuple[int, ...]: + """Parses the leading dotted-numeric run of a version string. + + Good enough for the plain "major.minor[.patch]" versions in + requirements-panel.txt and on PyPI -- not a full PEP 440 parser, but this + script only ever compares against floors from that one file. + """ + parts = [] + for chunk in version.split("."): + digits = "" + for ch in chunk: + if not ch.isdigit(): + break + digits += ch + parts.append(int(digits) if digits else 0) + return tuple(parts) + + def main() -> int: - missing = [] - for module_name, pip_name in REQUIRED: + problems = [] + for module_name, pip_name, min_version in REQUIRED: try: importlib.import_module(module_name) except ImportError: - missing.append(pip_name) + problems.append(f"{pip_name}>={min_version}") + continue + try: + installed = importlib.metadata.version(pip_name) + except importlib.metadata.PackageNotFoundError: + # Importable but no dist-info to check (unusual outside a system + # package with a broken/missing manifest) -- can't verify the + # version, so don't block on a guess either way. + continue + if _version_tuple(installed) < _version_tuple(min_version): + problems.append(f"{pip_name}>={min_version} (found {installed})") - for name in missing: + for name in problems: print(name) - return 1 if missing else 0 + return 1 if problems else 0 if __name__ == "__main__":