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

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