next release
This commit is contained in:
parent
488996222b
commit
1e19399862
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
conf/*.conf
|
||||||
82
main.py
82
main.py
@ -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
18
modules/checker.py
Normal 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
20
modules/config.py
Normal 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
19
modules/domains.py
Normal 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
19
modules/notifier.py
Normal 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
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
tzdata
|
||||||
Loading…
x
Reference in New Issue
Block a user