ESPN DeportesRays saluda al abridor Sandoval con jugoso rally de cuatro carrerasRTP DesportoManchester United empata e iguala registo negativo de Louis van GaalESPNSources: York won't attend Niners' home opener as NFL discipline loomsDaily MaverickLONG ARM OF THE ’LORD’: How ‘SA’s top gang boss’ represents a caving criminal justice systemTagesschau++ Liveticker zur Berlin-Wahl: Klingbeil kündigt Kurskorrektur an ++Straits Times SportJuventus beat Atalanta as Frosinone fairytale run continuesWirtualna PolskaStrzelanina w Bukownie. Trwa policyjna obławaEngadgetRetroid Pocket unexpectedly expands its Duo lineup with a Lite Plus version7sur7La Russie va “intensifier” ses attaques sur Kiev en réponse aux attaques de dronesRMF24Są wyniki exit poll w Rosji. Zaskoczenia nie maOnetWybory parlamentarne w Rosji. Znamy wyniki exit pollRadio-CanadaAllemagne : des élections régionales à haut risque pour le chancelier Merz
The Daily Newsstand · Free, Always
Sunday, September 20, 2026

Как я сделал Telegram‑бота для управления Linux‑серверами через SSH

Translate

Мне довольно часто нужно было зайти на свой сервер: посмотреть какие‑то данные, проверить его состояние или обновить один из сервисов. Каждый раз для этого приходилось брать ноутбук, открывать SSH‑клиент, подключаться к серверу и только потом выполнять нужные команды. Ради небольшой операции это занимало больше времени, чем хотелось.

Поэтому я решил сделать Telegram‑бота, который сможет подключаться к нужному мне серверу по SSH и выполнять команды, которые я использую чаще всего. Идея была простой: открыть Telegram, выбрать сервер, выполнить нужное действие и сразу получить результат.

Сначала мне казалось, что вся схема будет выглядеть примерно так:

Telegram → SSH → Linux

Но когда я начал делать бота, оказалось, что само SSH‑подключение — далеко не самая сложная часть. Нужно было решить, как безопасно хранить данные для подключения, проверять пользователя, работать с ключами серверов и не позволить боту подключаться куда угодно.

Задача: быстрые операции, а не замена SSH

Бот не должен превращать Telegram в универсальный терминал. Для долгой диагностики, интерактивных команд и редактирования большого конфига обычный SSH удобнее и прозрачнее.

Сценарий бота сейчас следующий: открыть телефон, выбрать сервер, выполнить действие и получить результат. Такой процесс полезен, когда нужно быстро проверить состояние сервиса или выполнить короткую операцию без ноутбука.

Что умеет бот

  • показывать uptime, load average, загрузку CPU, RAM и диска;

  • выполнять команды через SSH и присылать длинный вывод файлом;

  • загружать и скачивать файлы через SFTP;

  • редактировать небольшие UTF-8-файлы с созданием резервной копии;

  • перезапускать разрешённые systemd‑сервисы после подтверждения;

  • устанавливать пакеты через APT или DNF/YUM;

  • вести список серверов и показывать быстрые действия для выбранного сервера.

Меню действий для выбранного сервера. Лимиты видны до запуска операции.

Меню действий для выбранного сервера. Лимиты видны до запуска операции.

Состояние диалога хранит только идентификаторы и несекретные параметры. Пароль или приватный ключ используются для проверки подключения, шифруются перед сохранением, а сообщение пользователя с секретом удаляется после обработки.

Как команда доходит до сервера

Когда я только начинал делать бота, хотелось построить всё максимально просто: нажал кнопку в Telegram — бот подключился по SSH — выполнил команду.

Но довольно быстро стало понятно, что между нажатием кнопки и самим SSH нужно сделать ещё несколько проверок.

Архитектура запроса: интерфейс сообщает о намерении, backend принимает решение.

Архитектура запроса: интерфейс сообщает о намерении, backend принимает решение.

Когда приходит команда или callback, я сначала определяю пользователя и сервер. Затем backend ещё раз проверяет, принадлежит ли этот сервер пользователю и доступна ли ему выбранная операция. Только после этого бот подключается по SSH или SFTP.

Данные о серверах, пользователях, тарифах и лимитах я храню в PostgreSQL. Там же находятся зашифрованные данные для подключения.

Отдельно пришлось разобраться с лимитами. Попытку я учитываю перед выполнением операции, но транзакцию с базой не держу открытой всё время, пока выполняется SSH‑команда. Иначе какая‑нибудь долгая команда могла бы зря держать соединение с PostgreSQL.

После выполнения результат возвращается в Telegram. Если вывод небольшой — отправляю обычным сообщением. Если текста получилось много — бот присылает его файлом.

Почему я не доверяю кнопкам Telegram

Сначала может показаться, что если пользователь видит кнопку, значит ему уже можно выполнять это действие. Но я решил на это не рассчитывать.

Например, старый callback можно отправить повторно или попробовать передать другой server_id. Поэтому перед выполнением команды backend всё равно проверяет владельца сервера.

У меня это примерно выглядит так:

server = await servers.get_owned(
    server_id=server_id,
    telegram_user_id=current_user.id,
)

if server is None:
    raise AccessDenied

await permissions.require(operation, subscription)
await limits.reserve(operation)
await execute(server, operation)

То есть одного server_id мне недостаточно. Я сразу ищу сервер с учётом Telegram ID текущего пользователя. Если такой записи нет, операция дальше вообще не выполняется.

Подключение и первый ключ хоста

Пользователь добавляет название сервера, публичный IPv4- или IPv6-адрес, SSH‑порт, имя пользователя и способ входа. Поддерживаются пароль и приватный SSH‑ключ. На этом этапе важно проверить не только реквизиты, но и сам сервер.

Список подключённых серверов. Адреса и логины скрыты на демонстрационном скриншоте.

Список подключённых серверов. Адреса и логины скрыты на демонстрационном скриншоте.

Проверка SSH‑ключа

При первом подключении к серверу я не хотел просто молча доверять ему и сохранять соединение. Поэтому бот получает ключ хоста и показывает его SHA256-отпечаток пользователю.

Пользователь подтверждает отпечаток, после чего я сохраняю ключ. При следующем подключении бот сравнивает его с сохранённым. Если ключ внезапно изменился — подключение останавливается.

Здесь используется обычный TOFU (Trust On First Use). Конечно, у него есть минус: при самом первом подключении мы ещё не знаем, правильный ли сервер перед нами. Поэтому отпечаток лучше отдельно сверить, например с тем, который показывает хостинг.

Пароли и приватные SSH‑ключи я тоже не хотел хранить в PostgreSQL в открытом виде, поэтому перед сохранением они шифруются. При этом я понимаю, что одно шифрование базы не решает всё: самому приложению всё равно нужен ключ, чтобы расшифровать данные. Поэтому стараюсь не писать секреты в логи и отдельно ограничивать права SSH‑пользователя.

Зачем я проверяю IP‑адрес

С адресами серверов обнаружилась ещё одна проблема. Пользователь сам указывает IP, а уже мой backend пытается к нему подключиться.

Если вообще никак не проверять этот адрес, можно передать не публичный сервер, а, например, 127.0.0.1 или адрес из внутренней сети. Получается, что бот начнёт делать запросы туда, куда я изначально вообще не планировал давать доступ.

Поэтому перед SSH‑подключением я проверяю IP. Сейчас бот принимает публичные IPv4 и IPv6, а localhost, приватные сети, link‑local, multicast и другие служебные адреса отбрасывает ещё до попытки подключения.

Причём сделать это нужно до SSH, а не после неудачной авторизации. Даже если пароль неправильный, само приложение уже попробует установить соединение с указанным адресом.

Что делать с командами и sudo

С обычными командами возник другой вопрос: сколько вообще свободы давать боту?

Давать SSH‑пользователю полный sudo мне показалось плохой идеей. Например, вот такое правило фактически снимает почти все ограничения:

bot ALL=(ALL) NOPASSWD: ALL

Поэтому я пошёл в другую сторону: если боту нужно перезапускать конкретный сервис, лучше разрешить именно эту команду:

bot ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx

Конечно, это менее универсально, зато мне спокойнее, когда бот не получает root‑доступ ко всему серверу.

Для самих команд я также добавил таймаут и ограничение размера ответа. Иначе достаточно случайно запустить слишком долгую или слишком «разговорчивую» команду — и бот будет ждать её или пытаться отправить огромный ответ в Telegram.

Файлы передаю через SFTP. Редактор специально оставил простым: он рассчитан на небольшие UTF-8-файлы, а перед изменением бот делает резервную копию.

Лимиты и платежи

Когда я добавил тарифы, появилась ещё одна проблема — нельзя полагаться на то, что кнопка уже была показана пользователю. Между открытием меню и нажатием на неё тариф или оставшийся лимит могли измениться.

Поэтому доступ я проверяю ещё раз непосредственно перед выполнением команды.

С платежами похожая история. Telegram может повторно прислать один и тот же update, поэтому один платёж не должен случайно продлить подписку два раза. Для этого ID обработанного платежа сохраняется в базе и повторно уже не принимается.

Что получает пользователь

На телефоне вся цепочка сводится к нескольким действиям: выбрать операцию, выбрать сервер, подтвердить потенциально опасное действие и получить результат. Детали подключения и проверки остаются на backend.

Выбор сервера и результат мониторинга ресурсов в одном сценарии.

Выбор сервера и результат мониторинга ресурсов в одном сценарии.

Это и есть главный компромисс проекта: интерфейс остаётся коротким, но каждая короткая операция проходит полный набор проверок.

Что оказалось сложнее самого SSH

Само подключение по SSH оказалось далеко не самой сложной частью. Больше всего времени у меня ушло на три вещи:

  • Безопасность подключения. Нужно было проверять IP‑адрес до подключения и отдельно разобраться с сохранением и проверкой ключа хоста.

  • Права пользователя. Я не хотел доверять только данным из Telegram‑кнопок, поэтому перед каждой операцией backend ещё раз проверяет, что сервер действительно принадлежит этому пользователю.

  • Работа с командами. Пришлось добавить таймауты и ограничения на размер вывода, потому что команда может выполняться долго или вернуть слишком много данных.

Это мой первый такой проект и первая статья на Хабре, поэтому наверняка какие‑то решения можно было сделать иначе.

Сейчас бот уже работает, но пока я в основном проверял его на своих сценариях. Больше всего мне интересно мнение людей, которые сами работают с VPS: стали бы вы вообще доверять подобному боту доступ к тестовому серверу и каких ограничений по безопасности здесь, на ваш взгляд, ещё не хватает?

Репозиторий: https://github.com/DevoidBark2/telegram‑server‑bot‑showcase

Если кому‑то захочется попробовать самого бота, вот ссылка: https://t.me/programmer_assistant_pro_bot

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.