Лёгкий шлюз для запуска локального Telegram Bot API сервера (aiogram/telegram-bot-api) в Docker и удобной смены Bot API URL и webhook при миграции между серверами.
- Создайте
.envна основе примера:
cp .env.example .env- Укажите
API_IDиAPI_HASH(их можно получить на my.telegram.org). - Запустите контейнер:
docker compose up -dСервис поднимется на http://localhost:18081 и будет использовать директорию ./data для постоянного хранения состояния.
Этот проект работает как прокси-совместимый Bot API сервер, который полностью заменяет официальное облако https://api.telegram.org для вашего бота.
Важно различать две настройки:
TELEGRAM_BOT_URL— это адрес (endpoint) самого Bot API сервера, к которому вы обращаетесь для вызова методов (getMe,setWebhook,sendMessage).WEB_HOOK— это HTTPS-адрес вашего приложения, на который Telegram будет отправлять входящие апдейты после успешной установки.
ℹ️ Примечание: Для смены webhook на новый не требуется знать его старый адрес — метод
setWebhookпросто перезапишет его. МетодdeleteWebhookаналогично удалит хук без привязки к конкретному URL.
Для надёжной и бесперебойной миграции бота архитектуру обновления необходимо строить вокруг перехода от старого к новому серверу: OLD_BOT_API_URL → NEW_BOT_API_URL.
⚠️ Важно: Если бот уже запущен на локальном Bot API сервере, для корректного переноса необходимо знать адрес старого сервера. Это требуется для вызова методаclose, который корректно завершает сессию. Без вызоваcloseTelegram может заблокировать подключение на новом сервере (ошибка429 Too Many Requests).
Рекомендуемая модель хранения состояния (например, в БД или .env):
LAST_BOT_API_URL
LAST_WEBHOOK_URL
LAST_APPLIED_AT
В зависимости от текущего (старого) сервера и целевого (нового), алгоритм действий отличается. Ниже представлена визуальная схема и сводная таблица.
┌─────────────────────────────────────────────────────────────────────┐
│ АЛГОРИТМ МИГРАЦИИ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ OLD == NEW ? │
└────────┬────────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
ДА НЕТ
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Сценарий А: │ │ OLD это │
│ Только webhook │ │ api.telegram │
│ │ │ .org? │
│ setWebhook │ └────────┬────────┘
└─────────────────┘ │
┌───────┴───────┐
│ │
▼ ▼
ДА НЕТ
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Сценарий Б: │ │ NEW это │
│ Official → │ │ api.telegram │
│ Self-Hosted │ │ .org? │
│ │ └──────┬───────┘
│ 1. logOut │ │
│ 2. setWebhook│ ┌──────┴───────┐
└──────────────┘ │ │
▼ ▼
ДА НЕТ
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Сценарий Г: │ │ Сценарий В: │
│ Self-Hosted │ │ Self-Hosted │
│ → Official │ │ → Self-Hosted│
│ │ │ │
│ 1. close │ │ 1. close │
│ 2. setWebhook│ │ 2. setWebhook│
└──────────────┘ └──────────────┘
| Сценарий | Старый сервер (OLD) | Новый сервер (NEW) | Основные шаги |
|---|---|---|---|
| А | Любой ( OLD == NEW ) |
(Тот же самый) | Просто сделать setWebhook |
| Б | api.telegram.org (Облако) |
self-hosted |
logOut на OLD → setWebhook на NEW |
| В | self-hosted |
self-hosted |
close на OLD → setWebhook на NEW |
| Г | self-hosted |
api.telegram.org (Облако) |
close на OLD → setWebhook на NEW |
OLD_BOT_API_URL == NEW_BOT_API_URL — меняется лишь адрес доставки апдейтов.
NEW_BOT_API_URL/getMeNEW_BOT_API_URL/setWebhook(url=NEW_WEB_HOOK)NEW_BOT_API_URL/getWebhookInfo
OLD = api.telegram.org, NEW = локальный сервер.
api.telegram.org/deleteWebhookapi.telegram.org/logOut(Необходим перед запуском локального сервера. Блокирует возврат в облако на 10 минут).NEW_BOT_API_URL/getMeNEW_BOT_API_URL/setWebhook(url=NEW_WEB_HOOK)
Переезд между двумя приватными локальными серверами.
OLD_BOT_API_URL/deleteWebhookOLD_BOT_API_URL/close(Обязательно закрываем сессию на старом локальном сервере).NEW_BOT_API_URL/getMeNEW_BOT_API_URL/setWebhook(url=NEW_WEB_HOOK)
OLD_BOT_API_URL/deleteWebhookOLD_BOT_API_URL/close(Толькоclose, методlogOutздесь не применяется).api.telegram.org/getMeapi.telegram.org/setWebhook(url=NEW_WEB_HOOK)
Если старый Bot API URL неизвестен (например, при утере базы данных), полностью гарантировать корректный перенос local → local технически невозможно. В этом случае применяется логика best-effort с проверкой статуса бота через официальное облако:
curl "https://api.telegram.org/bot<TOKEN>/getMe"-
Если получен ответ
ok=true: Значит бот работает через облако. Смело применяем Сценарий Б (штатный переезд Official → Self-Hosted с предварительнымlogOutв облаке). -
Если получена ошибка вида
Logged out: Значит бот отвязан от облака и работает на каком-то неизвестномlocal-сервере. Поскольку вызватьcloseна старом сервере нельзя, делаем перенос втупую:NEW_BOT_API_URL/getMeNEW_BOT_API_URL/setWebhook(url=NEW_WEB_HOOK)(Учитывайте, что такой перенос не гарантирует моментальную чистую работу, так как старый сервер не был закрыт должным образом).
- Очередь сообщений (
drop_pending_updates): При миграции накопившиеся за время простоя апдейты будут доставлены на новый сервер. Если они больше не актуальны, используйте флагdrop_pending_updates=trueв запросахsetWebhook/deleteWebhook. - Fix the loop: Обязательно сохраняйте
NEW_BOT_API_URLв качествеLAST_BOT_API_URLпосле каждого успешного старта, чтобы в следующий раз миграция пошла по надежному (универсальному) алгоритму.
В репозитории есть утилита migrate_off_to_self.sh, которая автоматизирует переход:
- Official (
api.telegram.org) → Self-hosted - Polling без webhook (если
WEBHOOK_URLне указан) - Учитывает
drop_pending_updatesдля удаления накопленных апдейтов
Требуется:
jq(скрипт использует его для разбора JSON). Если хотите, можно создать.envрядом со скриптом — тогда переменные будут автоматически загружены.
SELF_BOT_URL="https://tg.ie0.ru" \
./migrate_off_to_self.sh "$BOT_TOKEN"SELF_BOT_URL="https://tg.ie0.ru" \
./migrate_off_to_self.sh "$BOT_TOKEN" "https://mysite/updates"DROP_PENDING_UPDATES=true \
SELF_BOT_URL="https://tg.ie0.ru" \
./migrate_off_to_self.sh "$BOT_TOKEN"BOT_TOKEN— обязательный (аргумент 1)SELF_BOT_URL— адрес self-hosted Bot API (аргумент 2 или env)WEBHOOK_URL— URL webhook (аргумент 3 или env); если не задан, используется pollingDROP_PENDING_UPDATES—true/false(по умолчаниюfalse).env— при наличии в директории загружается автоматически (конфиденциальные переменные можно хранить здесь)