Telegram-бот с уведомлениями о статусе сервера
Сервер лёг в три часа ночи, а вы узнали об этом только утром — от полутора десятков сообщений в стиле «сервер не работает???» и одного злого личного сообщения от игрока, который час пытался законнектиться. Знакомая ситуация для любого, кто держит 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 упадёт с исключением, важно, чтобы это не убило весь цикл мониторинга, а просто пропустило одно уведомление и попробовало на следующей итерации.