FROM python:3.12-slim

LABEL description="MikroTik IP Monitor with Telegram alerts"

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    tzdata \
    && rm -rf /var/lib/apt/lists/*

# Зависимости
RUN pip install --no-cache-dir \
    routeros-api==0.17.0 \
    httpx==0.27.0 \
    python-dotenv==1.0.1

# Скрипт встроен прямо в Dockerfile — не нужны дополнительные файлы
RUN cat > /app/monitor.py << 'PYEOF'
#!/usr/bin/env python3
"""MikroTik IP Monitor — пингует адреса через MikroTik API и шлёт уведомления в Telegram."""

import os
import time
import logging
import asyncio
from datetime import datetime
from typing import Optional

import routeros_api
import httpx

# ── Настройки ──────────────────────────────────────────────────────────────
MIKROTIK_HOST     = os.environ["MIKROTIK_HOST"]
MIKROTIK_PORT     = int(os.getenv("MIKROTIK_PORT", "8728"))
MIKROTIK_USER     = os.environ["MIKROTIK_USER"]
MIKROTIK_PASSWORD = os.environ["MIKROTIK_PASSWORD"]

TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
TELEGRAM_CHAT_ID   = os.environ["TELEGRAM_CHAT_ID"]

IP_LIST           = [ip.strip() for ip in os.environ["IP_LIST"].split(",") if ip.strip()]
CHECK_INTERVAL    = int(os.getenv("CHECK_INTERVAL", "60"))
PING_COUNT        = int(os.getenv("PING_COUNT", "3"))
PING_TIMEOUT_MS   = int(os.getenv("PING_TIMEOUT_MS", "1000"))
FAILURE_THRESHOLD = int(os.getenv("FAILURE_THRESHOLD", "2"))

# ── Логирование ────────────────────────────────────────────────────────────
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger(__name__)


# ── Telegram ───────────────────────────────────────────────────────────────
async def send_telegram(message: str) -> None:
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
    payload = {"chat_id": TELEGRAM_CHAT_ID, "text": message, "parse_mode": "HTML"}
    async with httpx.AsyncClient(timeout=10) as client:
        try:
            resp = await client.post(url, json=payload)
            resp.raise_for_status()
            log.info("Telegram: сообщение отправлено")
        except Exception as exc:
            log.error("Telegram: ошибка отправки — %s", exc)


def notify(message: str) -> None:
    asyncio.run(send_telegram(message))


# ── MikroTik ping ──────────────────────────────────────────────────────────
def mikrotik_ping(connection, address: str) -> Optional[float]:
    try:
        api = connection.get_api()
        ping_resource = api.get_resource("/ping")
        results = ping_resource.call(
            "",
            {"address": address, "count": str(PING_COUNT), "timeout": str(PING_TIMEOUT_MS)},
        )
        for row in results:
            if "avg-rtt" in row and row.get("avg-rtt"):
                try:
                    return float(row["avg-rtt"].replace("ms", "").strip())
                except ValueError:
                    pass
            if row.get("packet-loss") == "100":
                return None
        return None
    except Exception as exc:
        log.warning("Ошибка пинга %s: %s", address, exc)
        return None


# ── Основной цикл ─────────────────────────────────────────────────────────
def main() -> None:
    log.info("=== MikroTik Monitor запущен ===")
    log.info("Хосты: %s", IP_LIST)
    log.info("Интервал: %d сек", CHECK_INTERVAL)

    failure_count: dict[str, int]  = {ip: 0 for ip in IP_LIST}
    host_down:     dict[str, bool] = {ip: False for ip in IP_LIST}

    notify(
        f"🟢 <b>MikroTik Monitor запущен</b>\n"
        f"Хосты: <code>{', '.join(IP_LIST)}</code>\n"
        f"Интервал: {CHECK_INTERVAL} сек"
    )

    while True:
        connection = None
        try:
            log.info("Подключение к MikroTik %s:%d …", MIKROTIK_HOST, MIKROTIK_PORT)
            connection = routeros_api.RouterOsApiPool(
                MIKROTIK_HOST,
                username=MIKROTIK_USER,
                password=MIKROTIK_PASSWORD,
                port=MIKROTIK_PORT,
                plaintext_login=True,
            )
            connection.get_api()
            log.info("Подключено к MikroTik")

            for ip in IP_LIST:
                now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                rtt = mikrotik_ping(connection, ip)

                if rtt is not None:
                    log.info("✅  %s  avg-rtt=%.1f ms", ip, rtt)
                    if host_down[ip]:
                        host_down[ip] = False
                        failure_count[ip] = 0
                        notify(
                            f"✅ <b>Хост восстановился</b>\n"
                            f"IP: <code>{ip}</code>\n"
                            f"RTT: {rtt:.1f} ms\n"
                            f"Время: {now}"
                        )
                    else:
                        failure_count[ip] = 0
                else:
                    failure_count[ip] += 1
                    log.warning("❌  %s  не отвечает (%d/%d)", ip, failure_count[ip], FAILURE_THRESHOLD)
                    if failure_count[ip] >= FAILURE_THRESHOLD and not host_down[ip]:
                        host_down[ip] = True
                        notify(
                            f"🔴 <b>Хост недоступен!</b>\n"
                            f"IP: <code>{ip}</code>\n"
                            f"Не отвечает {failure_count[ip]} проверки подряд\n"
                            f"Время: {now}"
                        )

        except Exception as exc:
            log.error("Ошибка подключения к MikroTik: %s", exc)
            notify(f"⚠️ <b>Ошибка подключения к MikroTik</b>\n<code>{exc}</code>")
        finally:
            if connection:
                try:
                    connection.disconnect()
                except Exception:
                    pass

        log.info("Следующая проверка через %d сек …\n", CHECK_INTERVAL)
        time.sleep(CHECK_INTERVAL)


if __name__ == "__main__":
    main()
PYEOF

ENV TZ=Europe/Moscow
CMD ["python", "-u", "/app/monitor.py"]
