autodetect python version

This commit is contained in:
Mateusz Gruszczyński
2025-10-25 21:48:21 +02:00
parent bac38659e1
commit e4cd853001

View File

@@ -270,16 +270,71 @@ def sync_backup_nginx_conf():
def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")): def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")):
info = os_release() info = os_release()
distro_id = (info.get("ID") or "").lower() distro_id = (info.get("ID") or "").lower()
# --- Debian pyenv # ============================================================
PYENV_ROOT = Path("/opt/npm/.pyenv") # STEP 1: Check if we already have Python 3.11+ available
PYENV_OWNER = "npm" # ============================================================
PYTHON_VERSION = "3.11.14" python_candidates = ["python3.13", "python3.12", "python3.11", "python3"]
PYENV_BIN_CANDIDATES = ["pyenv", "/usr/bin/pyenv", "/usr/lib/pyenv/bin/pyenv"] available_python = None
python_version_str = None
# --- Ubuntu: PPA deadsnakes + venv ---
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": 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: try:
run(["apt-get", "update", "-y"], check=False) run(["apt-get", "update", "-y"], check=False)
apt_try_install(["software-properties-common"]) 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", "update", "-y"], check=False)
run(["apt-get", "install", "-y", "python3.11", "python3.11-venv"]) 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) venv_dir.mkdir(parents=True, exist_ok=True)
run(["python3.11", "-m", "venv", str(venv_dir)]) 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()}") print(f"✔ Certbot: {cb_ver.strip()} | Pip: {pip_ver.strip()}")
return 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: try:
apt_try_install([ apt_try_install([
"pyenv", "build-essential", "gcc", "make", "pkg-config", "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) pyenv_bin = next((c for c in PYENV_BIN_CANDIDATES if shutil.which(c)), None)
if not pyenv_bin: 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}"): with step(f"Installing Python {PYTHON_VERSION} via pyenv into {PYENV_ROOT}"):
run(["mkdir", "-p", str(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 "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}' # pyenv for '{PYENV_OWNER}'
if [ -d "{PYENV_ROOT}" ]; then if [ -d "{PYENV_ROOT}" ]; then
export PYENV_ROOT="{PYENV_ROOT}" 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 case ":$PATH:" in *":/usr/lib/pyenv/bin:"*) ;; *) PATH="/usr/lib/pyenv/bin:$PATH";; esac
export PATH export PATH
case "$-" in *i*) _interactive=1 ;; *) _interactive=0 ;; esac case "$-" in *i*) _interactive=1 ;; *) _interactive=0 ;; esac
@@ -413,11 +478,10 @@ fi
cb_ver = run_out([str(certbot_path), "--version"], check=False) or "" cb_ver = run_out([str(certbot_path), "--version"], check=False) or ""
pip_ver = run_out([str(pip_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) run(["chown", "-R", f"{PYENV_OWNER}:{PYENV_OWNER}", str(PYENV_ROOT)], check=False)
def configure_letsencrypt(): def configure_letsencrypt():
with step("configure letsencrypt"): with step("configure letsencrypt"):
run(["chown", "-R", "npm:npm", "/opt/certbot"], check=False) run(["chown", "-R", "npm:npm", "/opt/certbot"], check=False)