From 9ececc58433bd3d992eed5d49342d7e936d8b358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Sat, 6 Jun 2026 00:37:24 +0200 Subject: [PATCH] fix install v2.15.X --- npm_install.py | 156 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 130 insertions(+), 26 deletions(-) diff --git a/npm_install.py b/npm_install.py index 810e224..59a55da 100644 --- a/npm_install.py +++ b/npm_install.py @@ -1074,6 +1074,79 @@ def _detect_certbot_version(certbot_path: Path) -> str: return m.group(1) +def certbot_version_for_npm() -> str: + """Return the certbot version that NPM must expose as CERTBOT_VERSION. + + NPM v2.15.x replaces {{certbot-version}} in dns-plugins.json from the + CERTBOT_VERSION environment variable. If it is missing, Node turns it into + "undefined" and pip receives invalid requirements such as acme==undefined. + """ + candidates = [ + Path("/opt/certbot/bin/certbot"), + Path("/usr/local/bin/certbot"), + ] + + for certbot_path in candidates: + if certbot_path.exists(): + try: + return _detect_certbot_version(certbot_path) + except Exception: + pass + + if shutil.which("certbot"): + try: + out = run_out(["certbot", "--version"], check=False).strip() + m = re.search(r"(\d+\.\d+\.\d+)", out) + if m: + return m.group(1) + except Exception: + pass + + return "" + + +def patch_npm_certbot_plugins_config() -> str: + """Patch NPM DNS plugin metadata with a concrete certbot version. + + This is a non-docker install. NPM normally expects CERTBOT_VERSION in the + container environment. We set the env in systemd and also replace the JSON + placeholders after every install/update so startup cannot generate + acme==undefined even if the process environment is incomplete. + """ + certbot_ver = certbot_version_for_npm() + if not certbot_ver: + print("⚠ Could not detect Certbot version for NPM DNS plugin metadata") + return "" + + os.environ["CERTBOT_VERSION"] = certbot_ver + patched = [] + for path in [ + Path("/opt/npm/certbot/dns-plugins.json"), + Path("/opt/npm/backend/certbot/dns-plugins.json"), + ]: + if not path.exists(): + continue + try: + txt = path.read_text(encoding="utf-8") + new = txt.replace("{{certbot-version}}", certbot_ver) + new = new.replace("acme==undefined", f"acme=={certbot_ver}") + if new != txt: + path.write_text(new, encoding="utf-8") + patched.append(str(path)) + except Exception as e: + print(f"⚠ Could not patch {path}: {e}") + + if patched: + print(f"✔ Patched NPM Certbot plugin metadata: Certbot {certbot_ver}") + if DEBUG: + for path in patched: + print(f" - {path}") + else: + print(f"✔ NPM Certbot plugin metadata ready: Certbot {certbot_ver}") + + return certbot_ver + + def run_logged(cmd, log_path: Path, timeout=1200, check=True, env=None): """Run command with stdout/stderr saved to a log file. @@ -3123,6 +3196,59 @@ exec /usr/sbin/logrotate -s {state_file} "$@" print(f"⚠ Warning: could not fix {logrotate_dir} permissions: {e}") + +def write_npm_service_unit(ipv6_enabled: bool, include_certbot_version: bool = True): + """Write npm.service. + + During update this refreshes an older unit so NPM receives + CERTBOT_VERSION and does not expand DNS plugin requirements to + acme==undefined. Fresh installs also use this helper when creating + the service from scratch. + """ + certbot_ver = "" + if include_certbot_version: + certbot_ver = patch_npm_certbot_plugins_config() + + unit_lines = [ + "[Unit]", + "Description=Nginx Proxy Manager (backend)", + "After=network.target angie.service", + "Wants=angie.service", + "", + "[Service]", + "User=npm", + "Group=npm", + "WorkingDirectory=/opt/npm", + "Environment=NODE_ENV=production", + "Environment=PATH=/opt/certbot/bin:/usr/local/bin:/usr/bin:/bin", + ] + if certbot_ver: + unit_lines.append(f"Environment=CERTBOT_VERSION={certbot_ver}") + if not ipv6_enabled: + unit_lines.append("Environment=DISABLE_IPV6=true") + unit_lines += [ + "ExecStart=/usr/bin/node /opt/npm/index.js", + "Restart=on-failure", + "RestartSec=5", + "", + "[Install]", + "WantedBy=multi-user.target", + "", + ] + + write_file(Path("/etc/systemd/system/npm.service"), "\n".join(unit_lines), 0o644) + subprocess.run(["systemctl", "daemon-reload"], check=False) + if certbot_ver: + print(f"✔ npm.service updated with CERTBOT_VERSION={certbot_ver}") + else: + print("✔ npm.service updated") + return certbot_ver + + +def refresh_npm_service_for_update(ipv6_enabled: bool): + with step("Updating npm.service for update (CERTBOT_VERSION)"): + return write_npm_service_unit(ipv6_enabled=ipv6_enabled, include_certbot_version=True) + def create_systemd_units(ipv6_enabled: bool): with step("Creating and starting systemd services (angie, npm)"): # Some configs may already reference the admin/metrics SSL certificate. @@ -3130,32 +3256,7 @@ def create_systemd_units(ipv6_enabled: bool): if NPM_ADMIN_ENABLE_SSL: generate_selfsigned_cert() - unit_lines = [ - "[Unit]", - "Description=Nginx Proxy Manager (backend)", - "After=network.target angie.service", - "Wants=angie.service", - "", - "[Service]", - "User=npm", - "Group=npm", - "WorkingDirectory=/opt/npm", - "Environment=NODE_ENV=production", - "Environment=PATH=/opt/certbot/bin:/usr/local/bin:/usr/bin:/bin", - ] - if not ipv6_enabled: - unit_lines.append("Environment=DISABLE_IPV6=true") - unit_lines += [ - "ExecStart=/usr/bin/node /opt/npm/index.js", - "Restart=on-failure", - "RestartSec=5", - "", - "[Install]", - "WantedBy=multi-user.target", - "", - ] - - write_file(Path("/etc/systemd/system/npm.service"), "\n".join(unit_lines), 0o644) + write_npm_service_unit(ipv6_enabled=ipv6_enabled, include_certbot_version=True) write_file(Path("/etc/systemd/system/angie.service"), ANGIE_UNIT, 0o644) subprocess.run(["systemctl", "daemon-reload"], check=False) ensure_angie_log_include_files() @@ -4098,6 +4199,8 @@ def update_only( patch_npm_backend_commands() ensure_certbot_venv_ready() + patch_npm_certbot_plugins_config() + refresh_npm_service_for_update(ipv6_enabled=ipv6_enabled) configure_letsencrypt() create_systemd_units(ipv6_enabled=ipv6_enabled) @@ -4415,6 +4518,7 @@ def main(): "branch": args.branch if installed_from_branch else None, }) + patch_npm_certbot_plugins_config() create_systemd_units(ipv6_enabled=args.enable_ipv6) ensure_nginx_symlink()