fix install v2.15.X

This commit is contained in:
Mateusz Gruszczyński
2026-06-05 23:53:15 +02:00
parent 3942e8a4f7
commit f9aa0428e0
+141 -90
View File
@@ -972,6 +972,125 @@ def sync_backup_nginx_conf():
print(f"Warning: sync failed for {p} -> {target}: {e}") print(f"Warning: sync failed for {p} -> {target}: {e}")
CERTBOT_REQUIRED_PACKAGES = [
"certbot",
"acme",
"certbot-dns-cloudflare",
"certbot-dns-rfc2136",
]
def _ensure_certbot_symlink(certbot_path: Path):
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)
def _detect_certbot_version(certbot_path: Path) -> str:
cb_ver = run_out([str(certbot_path), "--version"], check=False).strip()
m = re.search(r"(\d+\.\d+\.\d+)", cb_ver)
if not m:
raise RuntimeError(f"Cannot detect certbot version from: {cb_ver!r}")
return m.group(1)
def _install_certbot_stack(pip_path: Path, certbot_path: Path, env_build: dict):
"""Install Certbot and DNS plugins in one venv with matching versions.
NPM installs DNS plugins on startup when they are missing. On non-docker
installs this can fail with 'acme==undefined'. We prevent that by making
the venv complete before npm.service starts.
"""
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,
)
certbot_ver = _detect_certbot_version(certbot_path)
run(
[
str(pip_path),
"install",
"-U",
f"acme=={certbot_ver}",
f"certbot-dns-cloudflare=={certbot_ver}",
f"certbot-dns-rfc2136=={certbot_ver}",
],
env=env_build,
)
missing = []
for pkg in CERTBOT_REQUIRED_PACKAGES:
result = subprocess.run(
[str(pip_path), "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 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.
Rebuild only when forced or when certbot/plugin packages are missing.
"""
certbot_path = venv_dir / "bin" / "certbot"
pip_path = venv_dir / "bin" / "pip"
if force_rebuild and venv_dir.exists():
with step("Removing certbot venv for forced rebuild"):
shutil.rmtree(venv_dir, ignore_errors=True)
needs_rebuild = force_rebuild or not certbot_path.exists() or not pip_path.exists()
if not needs_rebuild:
for pkg in CERTBOT_REQUIRED_PACKAGES:
result = subprocess.run(
[str(pip_path), "show", pkg],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
if result.returncode != 0:
needs_rebuild = True
if DEBUG:
print(f" Certbot venv missing package: {pkg}")
break
if needs_rebuild:
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
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()
@@ -1010,31 +1129,7 @@ def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")):
env_build = os.environ.copy() env_build = os.environ.copy()
env_build["SETUPTOOLS_USE_DISTUTILS"] = "local" env_build["SETUPTOOLS_USE_DISTUTILS"] = "local"
run( _install_certbot_stack(pip_path, certbot_path, env_build)
[str(pip_path), "install", "-U", "pip", "setuptools", "wheel"],
env=env_build,
)
run(
[
str(pip_path),
"install",
"-U",
"cryptography",
"cffi",
"certbot",
"tldextract",
],
env=env_build,
)
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 "" 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 ""
@@ -1073,31 +1168,7 @@ def setup_certbot_venv(venv_dir: Path = Path("/opt/certbot")):
env_build = os.environ.copy() env_build = os.environ.copy()
env_build["SETUPTOOLS_USE_DISTUTILS"] = "local" env_build["SETUPTOOLS_USE_DISTUTILS"] = "local"
run( _install_certbot_stack(pip_path, certbot_path, env_build)
[str(pip_path), "install", "-U", "pip", "setuptools", "wheel"],
env=env_build,
)
run(
[
str(pip_path),
"install",
"-U",
"cryptography",
"cffi",
"certbot",
"tldextract",
],
env=env_build,
)
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 "" 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 ""
@@ -1249,31 +1320,7 @@ fi
env_build = os.environ.copy() env_build = os.environ.copy()
env_build["SETUPTOOLS_USE_DISTUTILS"] = "local" env_build["SETUPTOOLS_USE_DISTUTILS"] = "local"
run( _install_certbot_stack(pip_path, certbot_path, env_build)
[str(pip_path), "install", "-U", "pip", "setuptools", "wheel"],
env=env_build,
)
run(
[
str(pip_path),
"install",
"-U",
"cryptography",
"cffi",
"certbot",
"tldextract",
],
env=env_build,
)
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 "" 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 ""
@@ -1289,10 +1336,7 @@ def configure_letsencrypt():
run(["chown", "-R", "npm:npm", "/opt/certbot"], check=False) run(["chown", "-R", "npm:npm", "/opt/certbot"], check=False)
Path("/etc/letsencrypt").mkdir(parents=True, exist_ok=True) Path("/etc/letsencrypt").mkdir(parents=True, exist_ok=True)
run(["chown", "-R", "npm:npm", "/etc/letsencrypt"], check=False) run(["chown", "-R", "npm:npm", "/etc/letsencrypt"], check=False)
run( # Do not install distro certbot here; use /opt/certbot venv only.
["apt-get", "install", "-y", "--no-install-recommends", "certbot"],
check=False,
)
ini = """text = True ini = """text = True
non-interactive = True non-interactive = True
webroot-path = /data/letsencrypt-acme-challenge webroot-path = /data/letsencrypt-acme-challenge
@@ -1625,6 +1669,9 @@ def write_metrics_files():
"""Create /etc/angie/metrics.conf (port 82/8282 with console & status).""" """Create /etc/angie/metrics.conf (port 82/8282 with console & status)."""
with step("Adding Angie metrics & console on :82 / :8282 (https)"): with step("Adding Angie metrics & console on :82 / :8282 (https)"):
if NPM_ADMIN_ENABLE_SSL:
generate_selfsigned_cert()
metrics = f"""include /etc/angie/prometheus_all.conf; metrics = f"""include /etc/angie/prometheus_all.conf;
server {{ server {{
listen 8282 ssl; listen 8282 ssl;
@@ -2697,12 +2744,12 @@ def deploy_npm_app_from_release(version: str | None) -> str:
version = github_latest_release_tag(repo, override=None) version = github_latest_release_tag(repo, override=None)
print(f"✓ Latest stable version: {version}") print(f"✓ Latest stable version: {version}")
version_parsed = parse_version(version)
if version_parsed < (2, 13, 0): if version_parsed < (2, 13, 0):
error(f"Version {version} is not supported. Minimum version: 2.13.0") error(f"Version {version} is not supported. Minimum version: 2.13.0")
sys.exit(1) sys.exit(1)
# Check if version >= 2.13.0 - if so, use git instead (releases missing /global) # Check if version >= 2.13.0 - if so, use git instead (releases missing /global)
version_parsed = parse_version(version)
if version_parsed >= (2, 13, 0): if version_parsed >= (2, 13, 0):
print( print(
f" Version {version} >= 2.13.0: using git source (release archive incomplete)" f" Version {version} >= 2.13.0: using git source (release archive incomplete)"
@@ -2944,6 +2991,11 @@ exec /usr/sbin/logrotate -s {state_file} "$@"
def create_systemd_units(ipv6_enabled: bool): def create_systemd_units(ipv6_enabled: bool):
with step("Creating and starting systemd services (angie, npm)"): with step("Creating and starting systemd services (angie, npm)"):
# Some configs may already reference the admin/metrics SSL certificate.
# Ensure it exists before the first Angie config test/restart.
if NPM_ADMIN_ENABLE_SSL:
generate_selfsigned_cert()
unit_lines = [ unit_lines = [
"[Unit]", "[Unit]",
"Description=Nginx Proxy Manager (backend)", "Description=Nginx Proxy Manager (backend)",
@@ -2955,6 +3007,7 @@ def create_systemd_units(ipv6_enabled: bool):
"Group=npm", "Group=npm",
"WorkingDirectory=/opt/npm", "WorkingDirectory=/opt/npm",
"Environment=NODE_ENV=production", "Environment=NODE_ENV=production",
"Environment=PATH=/opt/certbot/bin:/usr/local/bin:/usr/bin:/bin",
] ]
if not ipv6_enabled: if not ipv6_enabled:
unit_lines.append("Environment=DISABLE_IPV6=true") unit_lines.append("Environment=DISABLE_IPV6=true")
@@ -2971,9 +3024,12 @@ def create_systemd_units(ipv6_enabled: bool):
write_file(Path("/etc/systemd/system/npm.service"), "\n".join(unit_lines), 0o644) write_file(Path("/etc/systemd/system/npm.service"), "\n".join(unit_lines), 0o644)
write_file(Path("/etc/systemd/system/angie.service"), ANGIE_UNIT, 0o644) write_file(Path("/etc/systemd/system/angie.service"), ANGIE_UNIT, 0o644)
subprocess.run(["systemctl", "daemon-reload"], check=False) subprocess.run(["systemctl", "daemon-reload"], check=False)
# Validate configuration before touching the running service.
run(["/usr/sbin/angie", "-t"], check=True)
subprocess.run(["systemctl", "restart", "angie.service"], check=False) subprocess.run(["systemctl", "restart", "angie.service"], check=False)
subprocess.run(["systemctl", "enable", "angie.service"], check=False) subprocess.run(["systemctl", "enable", "angie.service"], check=False)
run(["/usr/sbin/angie", "-t"], check=False)
subprocess.run(["systemctl", "restart", "npm.service"], check=False) subprocess.run(["systemctl", "restart", "npm.service"], check=False)
subprocess.run(["systemctl", "enable", "npm.service"], check=False) subprocess.run(["systemctl", "enable", "npm.service"], check=False)
@@ -3892,6 +3948,8 @@ def update_only(
run(["yarn", "install"]) run(["yarn", "install"])
patch_npm_backend_commands() patch_npm_backend_commands()
ensure_certbot_venv_ready()
configure_letsencrypt()
create_systemd_units(ipv6_enabled=ipv6_enabled) create_systemd_units(ipv6_enabled=ipv6_enabled)
with step("Setting owners"): with step("Setting owners"):
@@ -3907,14 +3965,6 @@ def update_only(
except Exception as e: except Exception as e:
print(f" ⚠ Warning: Could not remove dev.conf: {e}") print(f" ⚠ Warning: Could not remove dev.conf: {e}")
certbot_venv = Path("/opt/certbot")
if certbot_venv.exists:
print(f"♻ Removing stale certbot venv for rebuild...")
shutil.rmtree(certbot_venv, ignore_errors=True)
setup_certbot_venv()
configure_letsencrypt()
with step("Restarting services after update"): with step("Restarting services after update"):
run(["systemctl", "restart", "angie.service"], check=False) run(["systemctl", "restart", "angie.service"], check=False)
run(["systemctl", "restart", "npm.service"], check=False) run(["systemctl", "restart", "npm.service"], check=False)
@@ -4127,7 +4177,7 @@ def main():
ensure_minimum_nodejs(user_requested_version=args.node_version) ensure_minimum_nodejs(user_requested_version=args.node_version)
ensure_user_and_dirs() ensure_user_and_dirs()
create_sudoers_for_npm() create_sudoers_for_npm()
setup_certbot_venv() ensure_certbot_venv_ready()
configure_letsencrypt() configure_letsencrypt()
# ========== INSTALLATION ========== # ========== INSTALLATION ==========
@@ -4262,3 +4312,4 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
signal.signal(signal.SIGINT, lambda s, f: sys.exit(130)) signal.signal(signal.SIGINT, lambda s, f: sys.exit(130))
main() main()