From 1e19399862cbf9ad2f521b0f28cb1e39682152f1 Mon Sep 17 00:00:00 2001 From: gandc Date: Mon, 5 May 2025 00:47:57 +0300 Subject: [PATCH] next release --- .gitignore | 1 + main.py | 82 +++++++++++++++++++-------------------------- modules/checker.py | 18 ++++++++++ modules/config.py | 20 +++++++++++ modules/domains.py | 19 +++++++++++ modules/notifier.py | 19 +++++++++++ requirements.txt | 1 + 7 files changed, 113 insertions(+), 47 deletions(-) create mode 100644 .gitignore create mode 100644 modules/checker.py create mode 100644 modules/config.py create mode 100644 modules/domains.py create mode 100644 modules/notifier.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bcc54c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +conf/*.conf diff --git a/main.py b/main.py index 8993ea7..c2cbdee 100644 --- a/main.py +++ b/main.py @@ -1,56 +1,44 @@ -import ssl -import socket +from pathlib import Path from datetime import datetime from zoneinfo import ZoneInfo -import smtplib -from email.message import EmailMessage -MOSCOW_TZ = ZoneInfo("Europe/Moscow") +from modules.config import load_mail_config +from modules.domains import load_domains +from modules.checker import get_cert_expiry_utc +from modules.notifier import send_email -def get_cert_expiry_utc(host: str, port: int = 443) -> datetime: - ctx = ssl.create_default_context() - with ctx.wrap_socket(socket.socket(), server_hostname=host) as sock: - sock.settimeout(5) - sock.connect((host, port)) - cert = sock.getpeercert() - exp_str = cert['notAfter'] - dt_utc = datetime.strptime(exp_str, '%b %d %H:%M:%S %Y %Z') - return dt_utc.replace(tzinfo=ZoneInfo("UTC")) +# Пути к файлам +BASE_DIR = Path(__file__).parent +CONF_DIR = BASE_DIR / 'conf' +MAIL_CONF = CONF_DIR / 'mail.conf' +DOMAINS_FILE = CONF_DIR / 'domains.txt' -def send_email(subject: str, body: str, to_addr: str) -> None: - msg = EmailMessage() - msg['Subject'] = subject - msg['From'] = 'monitor@example.com' - msg['To'] = to_addr - msg.set_content(body) - with smtplib.SMTP('smtp.example.com') as smtp: - smtp.login('user', 'pass') - smtp.send_message(msg) +# Настройки +MOSCOW_TZ = ZoneInfo('Europe/Moscow') +WARNING_DAYS = 30 -if __name__ == '__main__': - domain = 'example.com' - - # 1. Берём время истечения в UTC - expiry_utc = get_cert_expiry_utc(domain) - - # 2. Конвертируем expiry в московское время для вывода - expiry_moscow = expiry_utc.astimezone(MOSCOW_TZ) - - # 3. Получаем текущее время в Москве +def main(): + # Загружаем настройки и список доменов + mail_cfg = load_mail_config(MAIL_CONF) + domains = load_domains(DOMAINS_FILE) now_moscow = datetime.now(MOSCOW_TZ) - # 4. Считаем дни до окончания - days_left = (expiry_moscow.date() - now_moscow.date()).days + for host, port in domains: + expiry_utc = get_cert_expiry_utc(host, port) + expiry_local = expiry_utc.astimezone(MOSCOW_TZ) + days_left = (expiry_local.date() - now_moscow.date()).days - if days_left < 30: - subject = f'Сертификат {domain} истекает через {days_left} дней' - body = ( - f'Сертификат домена {domain} истечёт {expiry_moscow:%Y-%m-%d %H:%M:%S %Z}.\n' - f'Осталось {days_left} дней (по московскому времени).' - ) - #send_email(subject, body, 'admin@example.com') - body = ( - f'Сертификат домена {domain} истечёт {expiry_moscow:%Y-%m-%d %H:%M:%S %Z}.\n' - f'Осталось {days_left} дней (по московскому времени).' - ) - print(body) \ No newline at end of file + print(f"{host}:{port} — истекает {expiry_local:%Y-%m-%d %H:%M:%S %Z}, осталось {days_left} дн.") + + if days_left < WARNING_DAYS: + subject = f"[WARNING] SSL {host} истекает через {days_left} дн." + body = ( + f"Домен: {host}\n" + f"Порт: {port}\n" + f"Истекает: {expiry_local:%Y-%m-%d %H:%M:%S %Z}\n" + f"Осталось дней: {days_left}\n" + ) + send_email(mail_cfg, subject, body) + +if __name__ == '__main__': + main() diff --git a/modules/checker.py b/modules/checker.py new file mode 100644 index 0000000..61d00c2 --- /dev/null +++ b/modules/checker.py @@ -0,0 +1,18 @@ +import ssl +import socket +from datetime import datetime, timezone +from typing import Tuple + +def get_cert_expiry_utc(host: str, port: int = 443) -> datetime: + """ + Устанавливает TLS-соединение и возвращает дату окончания сертификата + в UTC (timezone-aware). + """ + ctx = ssl.create_default_context() + with ctx.wrap_socket(socket.socket(), server_hostname=host) as sock: + sock.settimeout(5) + sock.connect((host, port)) + cert = sock.getpeercert() + exp_str = cert['notAfter'] # 'Jun 15 12:00:00 2025 GMT' + dt = datetime.strptime(exp_str, '%b %d %H:%M:%S %Y %Z') + return dt.replace(tzinfo=timezone.utc) diff --git a/modules/config.py b/modules/config.py new file mode 100644 index 0000000..d6f96bf --- /dev/null +++ b/modules/config.py @@ -0,0 +1,20 @@ +import configparser +from pathlib import Path +from typing import Dict + +def load_mail_config(path: Path) -> Dict[str, str]: + """ + Читает mail.conf и возвращает словарь с настройками SMTP: + host, port, username, password, from, to + """ + cfg = configparser.ConfigParser() + cfg.read(path, encoding='utf-8') + smtp = cfg['smtp'] + return { + 'host': smtp.get('host'), + 'port': smtp.getint('port', fallback=587), + 'username': smtp.get('username'), + 'password': smtp.get('password'), + 'from': smtp.get('from'), + 'to': smtp.get('to'), + } diff --git a/modules/domains.py b/modules/domains.py new file mode 100644 index 0000000..5087827 --- /dev/null +++ b/modules/domains.py @@ -0,0 +1,19 @@ +from pathlib import Path +from typing import List, Tuple + +def load_domains(path: Path) -> List[Tuple[str, int]]: + """ + Возвращает список кортежей (host, port). + Если порт не указан — 443. + """ + result: List[Tuple[str, int]] = [] + for line in path.read_text(encoding='utf-8').splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + if ':' in line: + host, port = line.split(':', 1) + result.append((host.strip(), int(port))) + else: + result.append((line, 443)) + return result diff --git a/modules/notifier.py b/modules/notifier.py new file mode 100644 index 0000000..0189f4d --- /dev/null +++ b/modules/notifier.py @@ -0,0 +1,19 @@ +import smtplib +from email.message import EmailMessage +from typing import Dict + +def send_email(cfg: Dict[str, str], subject: str, body: str) -> None: + """ + Отправляет письмо через SMTP по настройкам из cfg. + Ожидает ключи: host, port, username, password, from, to. + """ + msg = EmailMessage() + msg['Subject'] = subject + msg['From'] = cfg['from'] + msg['To'] = cfg['to'] + msg.set_content(body) + + with smtplib.SMTP(cfg['host'], cfg['port']) as smtp: + smtp.starttls() + smtp.login(cfg['username'], cfg['password']) + smtp.send_message(msg) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0883ff0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +tzdata