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