fix install v2.15.X
This commit is contained in:
+144
-32
@@ -1290,6 +1290,66 @@ def _install_certbot_stack(pip_path: Path, certbot_path: Path, env_build: dict):
|
|||||||
_ensure_certbot_symlink(certbot_path)
|
_ensure_certbot_symlink(certbot_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _install_certbot_stack_with_python(python_path: Path, certbot_path: Path, env_build: dict):
|
||||||
|
"""Repair/install Certbot stack using venv python -m pip.
|
||||||
|
|
||||||
|
This is safer on upgraded Debian 11 systems where /opt/certbot/bin/pip
|
||||||
|
may have a stale shebang, but /opt/certbot/bin/python still works.
|
||||||
|
"""
|
||||||
|
log_path = Path("/tmp/npm-certbot-venv.log")
|
||||||
|
py = str(python_path)
|
||||||
|
run_logged(
|
||||||
|
[py, "-m", "pip", "install", "-U", "pip", "setuptools", "wheel"],
|
||||||
|
log_path,
|
||||||
|
env=env_build,
|
||||||
|
)
|
||||||
|
run_logged(
|
||||||
|
[
|
||||||
|
py,
|
||||||
|
"-m",
|
||||||
|
"pip",
|
||||||
|
"install",
|
||||||
|
"-U",
|
||||||
|
"cryptography",
|
||||||
|
"cffi",
|
||||||
|
"certbot",
|
||||||
|
"tldextract",
|
||||||
|
],
|
||||||
|
log_path,
|
||||||
|
env=env_build,
|
||||||
|
)
|
||||||
|
|
||||||
|
certbot_ver = _detect_certbot_version(certbot_path)
|
||||||
|
run_logged(
|
||||||
|
[
|
||||||
|
py,
|
||||||
|
"-m",
|
||||||
|
"pip",
|
||||||
|
"install",
|
||||||
|
"-U",
|
||||||
|
f"acme=={certbot_ver}",
|
||||||
|
f"certbot-dns-cloudflare=={certbot_ver}",
|
||||||
|
f"certbot-dns-rfc2136=={certbot_ver}",
|
||||||
|
],
|
||||||
|
log_path,
|
||||||
|
env=env_build,
|
||||||
|
)
|
||||||
|
|
||||||
|
missing = []
|
||||||
|
for pkg in CERTBOT_REQUIRED_PACKAGES:
|
||||||
|
result = subprocess.run(
|
||||||
|
[py, "-m", "pip", "show", pkg],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
missing.append(pkg)
|
||||||
|
if missing:
|
||||||
|
raise RuntimeError(f"Certbot venv incomplete, missing: {', '.join(missing)}")
|
||||||
|
|
||||||
|
_ensure_certbot_symlink(certbot_path)
|
||||||
|
|
||||||
|
|
||||||
def _venv_entrypoint_usable(path: Path, args: list[str] | None = None) -> tuple[bool, str]:
|
def _venv_entrypoint_usable(path: Path, args: list[str] | None = None) -> tuple[bool, str]:
|
||||||
"""Return whether a venv script/binary can be executed.
|
"""Return whether a venv script/binary can be executed.
|
||||||
@@ -1330,14 +1390,65 @@ def _venv_entrypoint_usable(path: Path, args: list[str] | None = None) -> tuple[
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, f"cannot execute {path}: {e}"
|
return False, f"cannot execute {path}: {e}"
|
||||||
|
|
||||||
def ensure_certbot_venv_ready(venv_dir: Path = Path("/opt/certbot"), force_rebuild: bool = False):
|
def _venv_python_path(venv_dir: Path) -> Path:
|
||||||
"""Fresh install: build full venv. Update: keep a complete existing venv.
|
return venv_dir / "bin" / "python"
|
||||||
|
|
||||||
Rebuild when forced, when packages are missing, or when the existing venv
|
|
||||||
has stale entrypoints after an OS/Python upgrade.
|
def _venv_package_installed_with_python(python_path: Path, pkg: str) -> bool:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[str(python_path), "-m", "pip", "show", pkg],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
timeout=30,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
return result.returncode == 0
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _try_repair_existing_certbot_venv(venv_dir: Path, reason: str) -> bool:
|
||||||
|
"""Try to repair an existing venv before deleting it.
|
||||||
|
|
||||||
|
Important for Debian 11: a pip wrapper may point to a removed python3.11,
|
||||||
|
while /opt/certbot/bin/python still works. In that case rebuild through
|
||||||
|
pyenv is unnecessary and riskier than repairing with python -m pip.
|
||||||
|
"""
|
||||||
|
python_path = _venv_python_path(venv_dir)
|
||||||
|
certbot_path = venv_dir / "bin" / "certbot"
|
||||||
|
|
||||||
|
python_ok, python_reason = _venv_entrypoint_usable(python_path, ["--version"])
|
||||||
|
if not python_ok:
|
||||||
|
if DEBUG:
|
||||||
|
print(f" Existing certbot venv python unusable: {python_reason}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with step(f"Repairing existing certbot venv ({reason})"):
|
||||||
|
env_build = os.environ.copy()
|
||||||
|
env_build["SETUPTOOLS_USE_DISTUTILS"] = "local"
|
||||||
|
_install_certbot_stack_with_python(python_path, certbot_path, env_build)
|
||||||
|
|
||||||
|
cb_ver = run_out([str(certbot_path), "--version"], check=False).strip()
|
||||||
|
print(f"✓ Existing certbot venv repaired: {cb_ver}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠ Could not repair existing certbot venv: {e}")
|
||||||
|
print(" Falling back to full rebuild.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_certbot_venv_ready(venv_dir: Path = Path("/opt/certbot"), force_rebuild: bool = False):
|
||||||
|
"""Fresh install: build full venv. Update: keep/repair existing venv when possible.
|
||||||
|
|
||||||
|
Debian/Ubuntu upgrades can leave /opt/certbot/bin/pip with a stale shebang.
|
||||||
|
Do not delete the venv immediately: first try /opt/certbot/bin/python -m pip,
|
||||||
|
which is safer on Debian 11 where rebuilding Python 3.11 requires pyenv.
|
||||||
"""
|
"""
|
||||||
certbot_path = venv_dir / "bin" / "certbot"
|
certbot_path = venv_dir / "bin" / "certbot"
|
||||||
pip_path = venv_dir / "bin" / "pip"
|
pip_path = venv_dir / "bin" / "pip"
|
||||||
|
python_path = _venv_python_path(venv_dir)
|
||||||
|
|
||||||
if force_rebuild and venv_dir.exists():
|
if force_rebuild and venv_dir.exists():
|
||||||
with step("Removing certbot venv for forced rebuild"):
|
with step("Removing certbot venv for forced rebuild"):
|
||||||
@@ -1347,46 +1458,47 @@ def ensure_certbot_venv_ready(venv_dir: Path = Path("/opt/certbot"), force_rebui
|
|||||||
rebuild_reason = "forced rebuild" if force_rebuild else ""
|
rebuild_reason = "forced rebuild" if force_rebuild else ""
|
||||||
|
|
||||||
if not needs_rebuild:
|
if not needs_rebuild:
|
||||||
pip_ok, pip_reason = _venv_entrypoint_usable(pip_path, ["--version"])
|
if not venv_dir.exists():
|
||||||
|
needs_rebuild = True
|
||||||
|
rebuild_reason = f"missing: {venv_dir}"
|
||||||
|
else:
|
||||||
|
python_ok, python_reason = _venv_entrypoint_usable(python_path, ["--version"])
|
||||||
certbot_ok, certbot_reason = _venv_entrypoint_usable(certbot_path, ["--version"])
|
certbot_ok, certbot_reason = _venv_entrypoint_usable(certbot_path, ["--version"])
|
||||||
if not pip_ok or not certbot_ok:
|
pip_ok, pip_reason = _venv_entrypoint_usable(pip_path, ["--version"])
|
||||||
needs_rebuild = True
|
|
||||||
rebuild_reason = pip_reason if not pip_ok else certbot_reason
|
|
||||||
|
|
||||||
if not needs_rebuild:
|
packages_ok = python_ok
|
||||||
|
missing_pkg = None
|
||||||
|
if packages_ok:
|
||||||
for pkg in CERTBOT_REQUIRED_PACKAGES:
|
for pkg in CERTBOT_REQUIRED_PACKAGES:
|
||||||
try:
|
if not _venv_package_installed_with_python(python_path, pkg):
|
||||||
result = subprocess.run(
|
packages_ok = False
|
||||||
[str(pip_path), "show", pkg],
|
missing_pkg = pkg
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
needs_rebuild = True
|
|
||||||
rebuild_reason = f"broken pip wrapper: {e}"
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
needs_rebuild = True
|
|
||||||
rebuild_reason = f"cannot verify certbot venv: {e}"
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if result.returncode != 0:
|
if python_ok and certbot_ok and packages_ok:
|
||||||
|
if not pip_ok:
|
||||||
|
# Wrapper is stale, but the venv itself works. Repair scripts in place.
|
||||||
|
_try_repair_existing_certbot_venv(venv_dir, pip_reason)
|
||||||
|
_ensure_certbot_symlink(certbot_path)
|
||||||
|
cb_ver = run_out([str(certbot_path), "--version"], check=False).strip()
|
||||||
|
print(f"✓ Existing certbot venv is complete: {cb_ver}")
|
||||||
|
run(["chown", "-R", "npm:npm", str(venv_dir)], check=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
if python_ok:
|
||||||
|
reason = certbot_reason if not certbot_ok else (f"missing package: {missing_pkg}" if missing_pkg else pip_reason)
|
||||||
|
if _try_repair_existing_certbot_venv(venv_dir, reason):
|
||||||
|
run(["chown", "-R", "npm:npm", str(venv_dir)], check=False)
|
||||||
|
return True
|
||||||
|
|
||||||
needs_rebuild = True
|
needs_rebuild = True
|
||||||
rebuild_reason = f"missing package: {pkg}"
|
rebuild_reason = python_reason if not python_ok else (certbot_reason if not certbot_ok else (f"missing package: {missing_pkg}" if missing_pkg else pip_reason))
|
||||||
if DEBUG:
|
|
||||||
print(f" Certbot venv missing package: {pkg}")
|
|
||||||
break
|
|
||||||
|
|
||||||
if needs_rebuild:
|
if needs_rebuild:
|
||||||
if venv_dir.exists():
|
if venv_dir.exists():
|
||||||
with step(f"Removing broken certbot venv ({rebuild_reason})"):
|
with step(f"Removing broken certbot venv ({rebuild_reason})"):
|
||||||
shutil.rmtree(venv_dir, ignore_errors=True)
|
shutil.rmtree(venv_dir, ignore_errors=True)
|
||||||
setup_certbot_venv(venv_dir)
|
setup_certbot_venv(venv_dir)
|
||||||
else:
|
|
||||||
_ensure_certbot_symlink(certbot_path)
|
|
||||||
cb_ver = run_out([str(certbot_path), "--version"], check=False).strip()
|
|
||||||
print(f"✓ Existing certbot venv is complete: {cb_ver}")
|
|
||||||
|
|
||||||
run(["chown", "-R", "npm:npm", str(venv_dir)], check=False)
|
run(["chown", "-R", "npm:npm", str(venv_dir)], check=False)
|
||||||
return True
|
return True
|
||||||
|
|||||||
Reference in New Issue
Block a user