next release

This commit is contained in:
gandc 2025-05-05 00:47:57 +03:00
parent 488996222b
commit 1e19399862
Signed by: gandc
GPG Key ID: 9F77B03D43C42CB4
7 changed files with 113 additions and 47 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
conf/*.conf

82
main.py
View File

@ -1,56 +1,44 @@
import ssl from pathlib import Path
import socket
from datetime import datetime from datetime import datetime
from zoneinfo import ZoneInfo 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() BASE_DIR = Path(__file__).parent
with ctx.wrap_socket(socket.socket(), server_hostname=host) as sock: CONF_DIR = BASE_DIR / 'conf'
sock.settimeout(5) MAIL_CONF = CONF_DIR / 'mail.conf'
sock.connect((host, port)) DOMAINS_FILE = CONF_DIR / 'domains.txt'
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"))
def send_email(subject: str, body: str, to_addr: str) -> None: # Настройки
msg = EmailMessage() MOSCOW_TZ = ZoneInfo('Europe/Moscow')
msg['Subject'] = subject WARNING_DAYS = 30
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)
if __name__ == '__main__': def main():
domain = 'example.com' # Загружаем настройки и список доменов
mail_cfg = load_mail_config(MAIL_CONF)
# 1. Берём время истечения в UTC domains = load_domains(DOMAINS_FILE)
expiry_utc = get_cert_expiry_utc(domain)
# 2. Конвертируем expiry в московское время для вывода
expiry_moscow = expiry_utc.astimezone(MOSCOW_TZ)
# 3. Получаем текущее время в Москве
now_moscow = datetime.now(MOSCOW_TZ) now_moscow = datetime.now(MOSCOW_TZ)
# 4. Считаем дни до окончания for host, port in domains:
days_left = (expiry_moscow.date() - now_moscow.date()).days 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: print(f"{host}:{port} — истекает {expiry_local:%Y-%m-%d %H:%M:%S %Z}, осталось {days_left} дн.")
subject = f'Сертификат {domain} истекает через {days_left} дней'
body = ( if days_left < WARNING_DAYS:
f'Сертификат домена {domain} истечёт {expiry_moscow:%Y-%m-%d %H:%M:%S %Z}.\n' subject = f"[WARNING] SSL {host} истекает через {days_left} дн."
f'Осталось {days_left} дней (по московскому времени).' body = (
) f"Домен: {host}\n"
#send_email(subject, body, 'admin@example.com') f"Порт: {port}\n"
body = ( f"Истекает: {expiry_local:%Y-%m-%d %H:%M:%S %Z}\n"
f'Сертификат домена {domain} истечёт {expiry_moscow:%Y-%m-%d %H:%M:%S %Z}.\n' f"Осталось дней: {days_left}\n"
f'Осталось {days_left} дней (по московскому времени).' )
) send_email(mail_cfg, subject, body)
print(body)
if __name__ == '__main__':
main()

18
modules/checker.py Normal file
View File

@ -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)

20
modules/config.py Normal file
View File

@ -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'),
}

19
modules/domains.py Normal file
View File

@ -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

19
modules/notifier.py Normal file
View File

@ -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)

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
tzdata