aiogram — одна из самых популярных библиотек для написания Telegram-ботов на Python. Она асинхронная, помогает держать код в порядке с помощью роутеров и фильтров и удобно управляет многошаговыми диалогами через FSM. На ноутбуке бот отлично работает через python bot.py, но стоит выключить компьютер — и бот перестаёт отвечать. В этой статье мы разместим на VPS бота для заказов магазина одежды из Намангана, написанного на aiogram 3, и превратим его в круглосуточный сервис.
Почему VPS, а не обычный хостинг
Бот в режиме polling — это постоянно работающий процесс Python. Обычный shared-хостинг рассчитан в первую очередь на PHP-сайты, и долгоживущие фоновые процессы там, как правило, ограничиваются или останавливаются через какое-то время. Попытки перезапускать бота по cron тоже не дают надёжного результата. Поэтому правильное место для бота — небольшой VPS, где процессом управляет systemd и бот сам поднимается даже после перезагрузки сервера. На sayt.uz VPS заказывается через кабинет и бывает готов за несколько минут.
Подготовка кода бота
Первое правило production: токен не пишется в коде. Он читается из переменной окружения, и тогда даже при загрузке кода на GitHub токен не утечёт. Ниже минимальный, но правильно устроенный пример бота на aiogram 3. Логирование включено, потому что на сервере ошибки видны только в логах.
import asyncio, logging, os
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message
dp = Dispatcher()
@dp.message(CommandStart())
async def start(message: Message):
await message.answer("Здравствуйте! Напишите название нужного товара.")
async def main():
bot = Bot(token=os.environ["BOT_TOKEN"])
await dp.start_polling(bot)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
asyncio.run(main())
Зафиксируйте версии библиотек в файле requirements.txt. Между aiogram 2 и 3 большие различия, поэтому явная граница версии убережёт от неожиданных поломок в будущем: например, aiogram>=3.0,<4.0.
Виртуальное окружение на сервере
Чтобы не вмешиваться в системные пакеты Python, бот должен работать в отдельном виртуальном окружении. Сначала создадим для бота отдельного пользователя, загрузим код через Git и установим зависимости. В Ubuntu модуль venv может ставиться отдельным пакетом.
sudo apt install -y python3-venv git
sudo adduser --disabled-password bot
sudo -iu bot
git clone https://github.com/misol/shop-bot.git
cd shop-bot
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
Для токена и других настроек создайте файл /home/bot/shop-bot/.env со строкой BOT_TOKEN=.... Затем выполните chmod 600 .env, чтобы файл мог читать только владелец. Этот файл обязательно должен быть в .gitignore.
Сервис systemd: бот всегда жив
systemd запускает процесс в фоне, поднимает его через несколько секунд после падения и автоматически стартует при включении сервера. Директива EnvironmentFile передаёт значения из .env как переменные окружения, так что дополнительная библиотека в коде не нужна.
# /etc/systemd/system/shop-bot.service
[Unit]
Description=Shop Telegram bot (aiogram)
After=network-online.target
Wants=network-online.target
[Service]
User=bot
WorkingDirectory=/home/bot/shop-bot
EnvironmentFile=/home/bot/shop-bot/.env
ExecStart=/home/bot/shop-bot/.venv/bin/python bot.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now shop-bot
journalctl -u shop-bot -f
Выкладка обновлений тоже проста: git pull, при необходимости .venv/bin/pip install -r requirements.txt и sudo systemctl restart shop-bot. Следите, чтобы одновременно работал только один экземпляр бота. Если на ноутбуке остался включённым старый процесс, aiogram выдаст ошибку TelegramConflictError из-за конфликта polling.
Чтобы состояния FSM не терялись при перезапуске
Бот для заказов обычно состоит из нескольких шагов: товар, размер, номер телефона, адрес. aiogram хранит эти шаги через FSM и по умолчанию использует MemoryStorage. Проблема в том, что при перезапуске сервиса все состояния в памяти пропадают, и клиент, остановившийся на полпути, вынужден начинать заново. В production правильное решение — хранилище на базе Redis.
from aiogram.fsm.storage.redis import RedisStorage
storage = RedisStorage.from_url("redis://127.0.0.1:6379/0")
dp = Dispatcher(storage=storage)
Сами заказы храните в постоянной базе данных. Для маленького магазина может хватить и SQLite, но если одновременно пишут несколько процессов или админ-панель, надёжнее выбрать PostgreSQL или MySQL.
Переход на webhook
Когда пользователей становится больше или бот живёт на одном сервере с сайтом, удобнее может оказаться webhook. Для этого в aiogram есть готовая интеграция с aiohttp. Бот слушает внутренний порт, а Nginx передаёт ему HTTPS-запросы. secret_token защищает от поддельных запросов.
from aiohttp import web
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
SECRET = os.environ["WEBHOOK_SECRET"]
async def on_startup(bot: Bot):
await bot.set_webhook("https://bot.example.uz/tg/webhook", secret_token=SECRET)
def main():
bot = Bot(token=os.environ["BOT_TOKEN"])
dp.startup.register(on_startup)
app = web.Application()
SimpleRequestHandler(dispatcher=dp, bot=bot, secret_token=SECRET).register(app, path="/tg/webhook")
setup_application(app, dp, bot=bot)
web.run_app(app, host="127.0.0.1", port=8081)
В Nginx блок location /tg/webhook настраивается с proxy_pass http://127.0.0.1:8081;, а для поддомена выпускается сертификат Let's Encrypt. Сервис systemd остаётся прежним — меняется только запускаемый файл на версию с webhook.