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)
|
||||
|
||||
|
||||
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]:
|
||||
"""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:
|
||||
return False, f"cannot execute {path}: {e}"
|
||||
|
||||
def ensure_certbot_venv_ready(venv_dir: Path = Path("/opt/certbot"), force_rebuild: bool = False):
|
||||
"""Fresh install: build full venv. Update: keep a complete existing venv.
|
||||
def _venv_python_path(venv_dir: Path) -> Path:
|
||||
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"
|
||||
pip_path = venv_dir / "bin" / "pip"
|
||||
python_path = _venv_python_path(venv_dir)
|
||||
|
||||
if force_rebuild and venv_dir.exists():
|
||||
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 ""
|
||||
|
||||
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"])
|
||||
if not pip_ok or not certbot_ok:
|
||||
needs_rebuild = True
|
||||
rebuild_reason = pip_reason if not pip_ok else certbot_reason
|
||||
pip_ok, pip_reason = _venv_entrypoint_usable(pip_path, ["--version"])
|
||||
|
||||
if not needs_rebuild:
|
||||
packages_ok = python_ok
|
||||
missing_pkg = None
|
||||
if packages_ok:
|
||||
for pkg in CERTBOT_REQUIRED_PACKAGES:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(pip_path), "show", 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}"
|
||||
if not _venv_package_installed_with_python(python_path, pkg):
|
||||
packages_ok = False
|
||||
missing_pkg = pkg
|
||||
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
|
||||
rebuild_reason = f"missing package: {pkg}"
|
||||
if DEBUG:
|
||||
print(f" Certbot venv missing package: {pkg}")
|
||||
break
|
||||
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 needs_rebuild:
|
||||
if venv_dir.exists():
|
||||
with step(f"Removing broken certbot venv ({rebuild_reason})"):
|
||||
shutil.rmtree(venv_dir, ignore_errors=True)
|
||||
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)
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user