From 8a3e3ce04b57db971d1dc724f12841a015fcb699 Mon Sep 17 00:00:00 2001 From: Niranjan Date: Tue, 7 Apr 2026 10:12:30 +0530 Subject: [PATCH] new changes --- .../__pycache__/site_service.cpython-314.pyc | Bin 27672 -> 32648 bytes .../backend/app/services/site_service.py | 69 ++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/YakPanel-server/backend/app/services/__pycache__/site_service.cpython-314.pyc b/YakPanel-server/backend/app/services/__pycache__/site_service.cpython-314.pyc index 1ed45c609421fbea76fdd85608564a068ceaeb64..dbeb6e8b2c9b72e206850cf6642d2bf448258662 100644 GIT binary patch delta 9387 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 A;{X5v delta 4993 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 int: + if not backup_dir or not os.path.isdir(backup_dir): + return 0 + prefix = f"{site_name}_" + n = 0 + try: + for f in os.listdir(backup_dir): + if f.startswith(prefix) and f.endswith(".tar.gz"): + n += 1 + except OSError: + return 0 + return n + + +def _parse_cert_not_after(cert_path: str) -> datetime | None: + if not os.path.isfile(cert_path): + return None + out, _err = exec_shell_sync(f'openssl x509 -in "{cert_path}" -noout -enddate', timeout=5) + if not out or "notAfter=" not in out: + return None + val = out.strip().split("=", 1)[1].strip() + try: + dt = datetime.strptime(val, "%b %d %H:%M:%S %Y GMT") + return dt.replace(tzinfo=timezone.utc) + except ValueError: + return None + + +def _best_ssl_for_hostnames(hostnames: list[str]) -> dict: + """Match LE certs by live//fullchain.pem; pick longest validity.""" + none = {"status": "none", "days_left": None, "cert_name": None} + live_root = LETSENCRYPT_LIVE + try: + if not os.path.isdir(live_root): + return none + best_days: int | None = None + best_name: str | None = None + for host in hostnames: + h = (host or "").split(":")[0].strip().lower() + if not h: + continue + folder = os.path.join(live_root, h) + if not os.path.isdir(folder): + continue + fullchain = os.path.join(folder, "fullchain.pem") + end = _parse_cert_not_after(fullchain) + if end is None: + continue + now = datetime.now(timezone.utc) + days = int((end - now).total_seconds() // 86400) + if best_days is None or days > best_days: + best_days = days + best_name = h + if best_days is None: + return none + if best_days < 0: + status = "expired" + elif best_days <= SSL_EXPIRING_DAYS: + status = "expiring" + else: + status = "active" + return {"status": status, "days_left": best_days, "cert_name": best_name} + except OSError: + return none + def _render_vhost( template: str,