MAATRIX GAMES / Блог / Telegram-бот с уведомлениями о статусе сервера

Telegram-бот с уведомлениями о статусе сервера

MAATRIX GAMES

Сервер лёг в три часа ночи, а вы узнали об этом только утром — от полутора десятков сообщений в стиле «сервер не работает???» и одного злого личного сообщения от игрока, который час пытался законнектиться. Знакомая ситуация для любого, кто держит Minecraft, Rust или CS2 сервер хоть немного дольше недели. Решение простое и не требует стороннего SaaS с подпиской: свой Telegram-бот, который раз в минуту стучится на сервер и пишет в чат ровно тогда, когда статус реально меняется — упал или снова поднялся. Ниже — рабочая схема на Python (python-telegram-bot) с альтернативой на Node.js, без магии и с честными граблями.

Что нужно от бота и чего не нужно

Задача узкая и конкретная: не полноценная RCON-панель с командами, не дашборд с графиками, а именно нотификатор. Он должен:

  • проверять доступность сервера раз в N секунд (пинг порта, systemd-статус или query-запрос — зависит от того, что доступно);
  • запоминать последнее известное состояние (up/down), чтобы не спамить одним и тем же сообщением на каждой проверке;
  • слать сообщение в Telegram-чат/канал только при смене состояния;
  • быть максимально простым — один файл, один systemd-юнит, ноль внешних баз данных.

Осознанно не делаем: обработку входящих команд от игроков (это отдельная задача — RCON-бот с правами; про неё подробно в статье про Discord-бота для управления сервером, логика для Telegram аналогична), веб-интерфейс, хранение истории аптайма. Чем меньше движущихся частей — тем меньше причин, по которым бот сам однажды замолчит.

Регистрация бота через BotFather

Открываете @BotFather в Telegram, отправляете /newbot, задаёте имя (то, что видят пользователи) и username (обязательно заканчивается на bot, например mycraft_status_bot). В ответ приходит токен вида:

123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw

Это единственный секрет, который нужен боту. Храните его не в коде, а в переменной окружения или отдельном файле с правами 600 — токен даёт полный контроль над ботом, и утечка в публичный git-репозиторий рано или поздно случается у всех хотя бы раз.

Дальше нужен chat_id — куда слать уведомления. Проще всего: создайте приватный канал или группу (можно даже только для себя), добавьте туда бота как администратора, напишите любое сообщение и получите обновления через API:

curl -s "https://api.telegram.org/bot<ТОКЕН>/getUpdates" | python3 -m json.tool

В ответе ищите "chat":{"id": -1001234567890, ...} — это и есть chat_id (для групп/каналов он отрицательный).

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

Создать сервер

Вариант на Python: python-telegram-bot

Ставим зависимости на сервере (Ubuntu/Debian):

sudo apt install python3-venv -y
python3 -m venv /opt/status-bot/venv
/opt/status-bot/venv/bin/pip install python-telegram-bot==21.* python-a2s

python-a2s нужен, если сервер поддерживает протокол A2S (Source-движок, многие игры на Steam). Для игр без query-протокола проще проверять открытый порт напрямую или статус systemd-юнита.

Сам скрипт /opt/status-bot/monitor.py:

import asyncio
import os
import socket
import subprocess
from telegram import Bot

TOKEN = os.environ["BOT_TOKEN"]
CHAT_ID = os.environ["CHAT_ID"]
HOST = "127.0.0.1"
PORT = 25565          # порт вашего сервера
CHECK_INTERVAL = 60    # секунд между проверками
SERVICE_NAME = "minecraft-server"  # имя systemd-юнита, если проверяем через него

bot = Bot(token=TOKEN)


def is_port_open(host: str, port: int, timeout: float = 5.0) -> bool:
    try:
        with socket.create_connection((host, port), timeout=timeout):
            return True
    except OSError:
        return False


def is_service_active(name: str) -> bool:
    result = subprocess.run(
        ["systemctl", "is-active", "--quiet", name]
    )
    return result.returncode == 0


async def check_loop():
    last_status = None  # None — ещё не знаем, True — up, False — down
    while True:
        # можно комбинировать: сервис жив И порт слушает
        status = is_service_active(SERVICE_NAME) and is_port_open(HOST, PORT)

        if last_status is not None and status != last_status:
            if status:
                text = "✅ Сервер снова в сети."
            else:
                text = "🔴 Сервер не отвечает. Проверяю дальше."
            await bot.send_message(chat_id=CHAT_ID, text=text)

        last_status = status
        await asyncio.sleep(CHECK_INTERVAL)


if __name__ == "__main__":
    asyncio.run(check_loop())

Ключевой момент — last_status is not None: без этой проверки бот пришлёт сообщение при самом первом запуске (когда переходит из «неизвестно» в «up»), что обычно не нужно и просто шумит при каждом рестарте самого бота.

Вариант на Node.js: node-telegram-bot-api

Тем, кто уже держит инфраструктуру на Node (например, панель на Pterodactyl или свой скрипт деплоя), удобнее не плодить Python-окружение:

mkdir -p /opt/status-bot && cd /opt/status-bot
npm init -y
npm install node-telegram-bot-api

monitor.js:

const TelegramBot = require('node-telegram-bot-api');
const net = require('net');
const { exec } = require('child_process');

const TOKEN = process.env.BOT_TOKEN;
const CHAT_ID = process.env.CHAT_ID;
const HOST = '127.0.0.1';
const PORT = 25565;
const INTERVAL_MS = 60_000;

const bot = new TelegramBot(TOKEN, { polling: false });

function isPortOpen(host, port, timeout = 5000) {
  return new Promise((resolve) => {
    const socket = new net.Socket();
    socket.setTimeout(timeout);
    socket.once('connect', () => { socket.destroy(); resolve(true); });
    socket.once('error', () => resolve(false));
    socket.once('timeout', () => { socket.destroy(); resolve(false); });
    socket.connect(port, host);
  });
}

let lastStatus = null;

async function checkOnce() {
  const status = await isPortOpen(HOST, PORT);

  if (lastStatus !== null && status !== lastStatus) {
    const text = status ? '✅ Сервер снова в сети.' : '🔴 Сервер не отвечает. Проверяю дальше.';
    await bot.sendMessage(CHAT_ID, text);
  }
  lastStatus = status;
}

setInterval(checkOnce, INTERVAL_MS);
checkOnce();

Обратите внимание: тут polling: false — боту не нужно принимать команды, только отправлять сообщения, поэтому нет смысла держать соединение на приём обновлений (это лишний расход и потенциальная точка отказа, если два процесса случайно запустят polling одновременно с одним токеном — Telegram API начнёт конфликтовать между ними и ронять обе сессии).

Автозапуск через systemd

Бот должен переживать перезагрузку сервера и падение процесса — то есть жить под systemd, как и сам игровой сервер. Подробный разбор systemd/screen/tmux для игровых процессов — в отдельной статье про systemd, screen и tmux для игровых процессов, здесь — минимальный юнит под сам бот.

/etc/systemd/system/status-bot.service:

[Unit]
Description=Telegram status notifier
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=botuser
WorkingDirectory=/opt/status-bot
EnvironmentFile=/opt/status-bot/.env
ExecStart=/opt/status-bot/venv/bin/python3 /opt/status-bot/monitor.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Для Node-варианта меняете только ExecStart на /usr/bin/node /opt/status-bot/monitor.js. Файл .env с секретами:

BOT_TOKEN=123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw
CHAT_ID=-1001234567890

Права на файл — chmod 600 /opt/status-bot/.env, владелец — тот же botuser, под которым крутится сервис. Запуск:

sudo systemctl daemon-reload
sudo systemctl enable --now status-bot
sudo systemctl status status-bot

Restart=always здесь критичен вдвойне: если сам бот-нотификатор упадёт и не поднимется, вы останетесь без уведомлений именно тогда, когда они нужнее всего — при падении основного сервера. Полезно завести отдельный внешний контроль за самим ботом (см. следующий раздел) — иначе получается «кто сторожит сторожей».

Как проверять статус: порт, systemd или query-протокол

Три способа проверки, каждый со своими нюансами:

СпособПлюсыМинусы
TCP-порт открытРаботает для любой игры, простоНе отличает «завис, но порт слушает» от реально живого сервера
systemd is-activeТочно знает, жив ли процессНе видит зависаний внутри игры (процесс есть, а сервер не отвечает)
Query-протокол (A2S, RCON-пинг)Реальный ответ от игрового движкаНе у всех игр есть, чуть больше кода

Комбинация «systemd жив» + «порт открыт» — разумный баланс сложности и надёжности для большинства случаев. Если нужна более глубокая проверка (например, отличать зависший TPS от реально мёртвого сервера), посмотрите в сторону query-запросов — это описано в статье про Server Query API для мониторинг-ботов: та же логика легко встраивается в цикл проверки вместо простого is_port_open.

Отдельный нюанс для плановых рестартов: если у вас настроен ночной рестарт по cron, бот честно пришлёт «сервер упал» на минуту простоя во время планового рестарта. Это не баг, но раздражает в 4 утра. Решение — либо расширить CHECK_INTERVAL с запасом на длительность рестарта (если он занимает 20-30 секунд, а проверка раз в минуту — обычно не успевает сработать), либо добавить простое окно подавления: не слать уведомление, если время попадает в диапазон планового рестарта.

from datetime import datetime

def in_maintenance_window() -> bool:
    now = datetime.now().time()
    return now.hour == 4 and now.minute < 5  # окно 04:00–04:05

Оборачиваете отправку сообщения проверкой if not in_maintenance_window(): await bot.send_message(...).

Расширения: несколько серверов и разные уровни важности

Если серверов несколько (например, отдельные инстансы под разные модпаки или под разные игры — Rust и ARK: Survival), не плодите по боту на каждый. Один бот, один конфиг-словарь:

SERVERS = {
    "Rust Vanilla": {"host": "127.0.0.1", "port": 28015, "service": "rust-vanilla"},
    "ARK Island": {"host": "127.0.0.1", "port": 7777, "service": "ark-island"},
}

И цикл проверки перебирает словарь, храня last_status для каждого сервера отдельно (обычный dict, ключ — имя сервера). В сообщении указывайте имя, иначе через неделю забудете, какой сервер вообще упал.

Второй полезный уровень — эскалация: если сервер не поднимается больше 15 минут, слать не просто повтор того же сообщения, а отдельное «уже 15 минут не в сети» с упоминанием (@username) конкретного дежурного админа. Реализуется тем же таймером, без сторонних библиотек — считаете, сколько циклов подряд статус down, и на пороговом значении шлёте второе сообщение.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

Создать сервер

Частые вопросы

Бот не отправляет сообщения, хотя токен верный?

Проверьте, что бот добавлен в чат/канал как участник (или админ — для каналов это обязательно), и что chat_id указан с минусом для групп и каналов. Частая ошибка — использовать ID личного чата вместо ID группы.

Можно ли обойтись без своего VPS и слать уведомления прямо с локального ПК?

Технически да, но тогда мониторинг будет работать только пока включён ваш компьютер и есть интернет — то есть теряет смысл. Скрипт должен крутиться там же, откуда виден сервер (или на отдельной внешней машине, если хотите проверять доступность именно снаружи, а не изнутри той же сети).

Как проверять сервер извне, если бот стоит на том же хосте?

Локальная проверка (как в примерах выше) не покажет проблемы с сетью или файрволом хостера. Для внешнего контроля разумно завести второй лёгкий чек с внешней VPS или использовать готовый uptime-сервис в связке — сравнение таких инструментов есть в статье про uptime-мониторинг игрового сервера.

Нужен ли polling или webhook, если бот только отправляет сообщения?

Нет. sendMessage/send_message работает без активного приёма обновлений — polling и webhook нужны только когда бот должен реагировать на сообщения пользователей (команды типа /status, /restart). Для чистого нотификатора это лишняя сложность.

Что делать, если Telegram API временно недоступен (бан региона, сбой)?

Оборачивайте отправку в try/except с логированием в файл — если send_message упадёт с исключением, важно, чтобы это не убило весь цикл мониторинга, а просто пропустило одно уведомление и попробовало на следующей итерации.