diff --git a/npm_install.py b/npm_install.py index 68280f6..8fb0782 100644 --- a/npm_install.py +++ b/npm_install.py @@ -270,16 +270,71 @@ def sync_backup_nginx_conf(): def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")): info = os_release() distro_id = (info.get("ID") or "").lower() - - # --- Debian pyenv - PYENV_ROOT = Path("/opt/npm/.pyenv") - PYENV_OWNER = "npm" - PYTHON_VERSION = "3.11.14" - PYENV_BIN_CANDIDATES = ["pyenv", "/usr/bin/pyenv", "/usr/lib/pyenv/bin/pyenv"] - - # --- Ubuntu: PPA deadsnakes + venv --- + + # ============================================================ + # STEP 1: Check if we already have Python 3.11+ available + # ============================================================ + python_candidates = ["python3.13", "python3.12", "python3.11", "python3"] + available_python = None + python_version_str = None + + for py_cmd in python_candidates: + if shutil.which(py_cmd): + try: + # Check version + ver_output = run_out([py_cmd, "--version"], check=False).strip() + # Extract version (e.g., "Python 3.11.2" -> major=3, minor=11) + match = re.search(r'Python (\d+)\.(\d+)', ver_output) + if match: + major, minor = int(match.group(1)), int(match.group(2)) + if major == 3 and minor >= 11: + available_python = py_cmd + python_version_str = ver_output + if DEBUG: + print(f"✔ Found suitable Python: {py_cmd} ({ver_output})") + break + except Exception: + continue + + # ============================================================ + # STEP 2: If Python 3.11+ is available, use it directly + # ============================================================ + if available_python: + with step(f"Using system Python ({available_python}) for certbot venv"): + # Ensure python3-venv is installed + if distro_id in ["debian", "ubuntu"]: + apt_try_install(["python3-venv", "python3-pip"]) + + venv_dir.mkdir(parents=True, exist_ok=True) + run([available_python, "-m", "venv", str(venv_dir)]) + + venv_bin = venv_dir / "bin" + pip_path = venv_bin / "pip" + certbot_path = venv_bin / "certbot" + env_build = os.environ.copy() + env_build["SETUPTOOLS_USE_DISTUTILS"] = "local" + + run([str(pip_path), "install", "-U", "pip", "setuptools", "wheel"], env=env_build) + run([str(pip_path), "install", "-U", "cryptography", "cffi", "certbot", "tldextract"], env=env_build) + + # Create symlink + Path("/usr/local/bin").mkdir(parents=True, exist_ok=True) + target = Path("/usr/local/bin/certbot") + if target.exists() or target.is_symlink(): + try: target.unlink() + except Exception: pass + target.symlink_to(certbot_path) + + cb_ver = run_out([str(certbot_path), "--version"], check=False) or "" + pip_ver = run_out([str(pip_path), "--version"], check=False) or "" + print(f"✔ Certbot: {cb_ver.strip()} | Pip: {pip_ver.strip()} | Python: {python_version_str}") + return + + # ============================================================ + # STEP 3: Ubuntu - install Python 3.11 from deadsnakes PPA + # ============================================================ if distro_id == "ubuntu": - with step(f"Ubuntu detected: {info.get('PRETTY','Ubuntu')}. Install Python 3.11 via deadsnakes"): + with step(f"Ubuntu detected: {info.get('PRETTY','Ubuntu')}. Installing Python 3.11 via deadsnakes PPA"): try: run(["apt-get", "update", "-y"], check=False) apt_try_install(["software-properties-common"]) @@ -290,7 +345,7 @@ def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")): run(["apt-get", "update", "-y"], check=False) run(["apt-get", "install", "-y", "python3.11", "python3.11-venv"]) - with step(f"Create venv at {venv_dir} using python3.11"): + with step(f"Creating venv at {venv_dir} using python3.11"): venv_dir.mkdir(parents=True, exist_ok=True) run(["python3.11", "-m", "venv", str(venv_dir)]) @@ -315,6 +370,16 @@ def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")): print(f"✔ Certbot: {cb_ver.strip()} | Pip: {pip_ver.strip()}") return + # ============================================================ + # STEP 4: Debian (old) - use pyenv to install Python 3.11 + # ============================================================ + print("⚠ Python 3.11+ not found. Installing via pyenv (this may take 3-5 minutes)...") + + PYENV_ROOT = Path("/opt/npm/.pyenv") + PYENV_OWNER = "npm" + PYTHON_VERSION = "3.11.14" + PYENV_BIN_CANDIDATES = ["pyenv", "/usr/bin/pyenv", "/usr/lib/pyenv/bin/pyenv"] + try: apt_try_install([ "pyenv", "build-essential", "gcc", "make", "pkg-config", @@ -337,7 +402,7 @@ def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")): pyenv_bin = next((c for c in PYENV_BIN_CANDIDATES if shutil.which(c)), None) if not pyenv_bin: - raise RuntimeError("No 'pyenv' (try /usr/bin/pyenv or /usr/lib/pyenv/bin/pyenv).") + raise RuntimeError("No 'pyenv' found. Try: apt-get install pyenv") with step(f"Installing Python {PYTHON_VERSION} via pyenv into {PYENV_ROOT}"): run(["mkdir", "-p", str(PYENV_ROOT)]) @@ -364,11 +429,11 @@ def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")): "bash", "-lc", install_cmd ]) - profile_snippet = f"""# Auto-generated by npm-auto-auto-install + profile_snippet = f"""# Auto-generated by npm-angie-auto-install # pyenv for '{PYENV_OWNER}' if [ -d "{PYENV_ROOT}" ]; then export PYENV_ROOT="{PYENV_ROOT}" - case ":$PATH:" in *":$PYENV_ROOT/bin:"*) ;; *) PATH="$PYENV_ROOT/bin:$PATH";; esac + case ":$PATH:" in *":{PYENV_ROOT}/bin:"*) ;; *) PATH="{PYENV_ROOT}/bin:$PATH";; esac case ":$PATH:" in *":/usr/lib/pyenv/bin:"*) ;; *) PATH="/usr/lib/pyenv/bin:$PATH";; esac export PATH case "$-" in *i*) _interactive=1 ;; *) _interactive=0 ;; esac @@ -413,11 +478,10 @@ fi cb_ver = run_out([str(certbot_path), "--version"], check=False) or "" pip_ver = run_out([str(pip_path), "--version"], check=False) or "" - #print(f"Certbot: {cb_ver.strip()} | Pip: {pip_ver.strip()}") + #print(f"✔ Certbot: {cb_ver.strip()} | Pip: {pip_ver.strip()}") run(["chown", "-R", f"{PYENV_OWNER}:{PYENV_OWNER}", str(PYENV_ROOT)], check=False) - def configure_letsencrypt(): with step("configure letsencrypt"): run(["chown", "-R", "npm:npm", "/opt/certbot"], check=False)