#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
server_monitor.py — бесплатный мониторинг сервера (ресурсы + веб + docker).
Шлёт сводку в Telegram каждый запуск. Ставится в cron на */30 минут.
Только стандартная библиотека Python 3 — ничего устанавливать не нужно.

УСТАНОВКА:
1) Заполнить BOT_TOKEN (токен бота от @BotFather) и CHAT_ID ниже
   (или создать файл /root/.monitor_creds с двумя строками: токен и chat_id)
2) chmod +x /root/server_monitor.py
3) crontab -e  →  добавить строку:
   */30 * * * * /usr/bin/python3 /root/server_monitor.py >/dev/null 2>&1
"""
import os, subprocess, json, urllib.request, urllib.parse, socket, time

# ============ НАСТРОЙКИ ============
BOT_TOKEN = "ВСТАВЬ_ТОКЕН_БОТА"   # токен Telegram-бота
CHAT_ID   = "8141375991"          # chat_id получателя
WEB_CHECKS = [
    "https://crm.unionkm.org/",
    "https://ops.unionkm.org/",
    "https://app.unionkm.org/",
]
# ===================================

# подхват кредов из файла, если есть (2 строки: токен, chat_id)
try:
    with open("/root/.monitor_creds") as f:
        lines = [l.strip() for l in f if l.strip()]
        if len(lines) >= 2:
            BOT_TOKEN, CHAT_ID = lines[0], lines[1]
except Exception:
    pass

def sh(cmd, timeout=10):
    try:
        return subprocess.run(cmd, shell=True, capture_output=True,
                              text=True, timeout=timeout).stdout.strip()
    except Exception as e:
        return f"ошибка: {e}"

def ram():
    m = {}
    with open("/proc/meminfo") as f:
        for line in f:
            k, v = line.split(":")
            m[k] = int(v.split()[0])  # kB
    total = m.get("MemTotal", 1) / 1024
    avail = m.get("MemAvailable", 0) / 1024
    used = total - avail
    return total, used, used / total * 100

def disk(path="/"):
    st = os.statvfs(path)
    total = st.f_blocks * st.f_frsize / 1e9
    free = st.f_bavail * st.f_frsize / 1e9
    used = total - free
    return total, used, used / total * 100

def web_check(url):
    try:
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 monitor"})
        t0 = time.time()
        with urllib.request.urlopen(req, timeout=12) as r:
            return f"{r.status} ({time.time()-t0:.1f}с)"
    except urllib.error.HTTPError as e:
        return f"{e.code} (HTTP-ошибка)"
    except Exception as e:
        return f"НЕДОСТУПЕН ({type(e).__name__})"

def send_tg(text):
    if BOT_TOKEN.startswith("ВСТАВЬ"):
        print("BOT_TOKEN не заполнен! Отчёт:\n" + text)
        return
    data = urllib.parse.urlencode({
        "chat_id": CHAT_ID, "text": text, "disable_web_page_preview": "true"
    }).encode()
    req = urllib.request.Request(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage", data=data)
    try:
        urllib.request.urlopen(req, timeout=15)
    except Exception as e:
        print(f"Не удалось отправить в TG: {e}")

def main():
    host = socket.gethostname()
    uptime = sh("uptime -p") or "?"
    load1, load5, load15 = os.getloadavg()
    cores = os.cpu_count() or 1
    t_ram, u_ram, p_ram = ram()
    t_dsk, u_dsk, p_dsk = disk("/")

    top = sh("ps aux --sort=-%cpu | head -6 | awk '{printf \"%.0f%% %s\\n\", $3, $11}'")
    docker = sh("docker ps -a --format '{{.Names}}: {{.Status}}' 2>/dev/null") or "docker не найден/нет прав"

    webs = "\n".join(f"  {u} → {web_check(u)}" for u in WEB_CHECKS)

    warn = []
    if load1 > cores * 2: warn.append("⚠️ Высокая нагрузка CPU")
    if p_ram > 90: warn.append("⚠️ RAM почти заполнена")
    if p_dsk > 90: warn.append("⚠️ Диск почти заполнен")
    if "НЕДОСТУПЕН" in webs: warn.append("🔴 Есть недоступные сайты")
    status = " | ".join(warn) if warn else "✅ всё в норме"

    msg = (
        f"🖥 <b>Сервер {host}</b> — {status}\n"
        f"⏱ {uptime}\n"
        f"🧮 Load: {load1:.2f} / {load5:.2f} / {load15:.2f} (ядер: {cores})\n"
        f"💾 RAM: {u_ram:.0f}/{t_ram:.0f} МБ ({p_ram:.0f}%)\n"
        f"💽 Диск: {u_dsk:.1f}/{t_dsk:.1f} ГБ ({p_dsk:.0f}%)\n\n"
        f"🔝 Топ по CPU:\n{top}\n\n"
        f"🐳 Контейнеры:\n{docker}\n\n"
        f"🌐 Сайты:\n{webs}"
    )
    send_tg(msg)

if __name__ == "__main__":
    main()
