# Override: закрепляет рабочий IP api.telegram.org для tgbot и backend. # # ⚠️ ОБНОВЛЕНО 25.08 (#3059). Файл больше НЕ Selectel-only и подключается # БЕЗУСЛОВНО — его подмешивает deploy-tradein.yml (переменная COMPOSE_FILES). # Имя оставлено прежним, чтобы не ломать ссылки на него в раннбуке и задачах. # # Что было не так: с 23.08 файл лежал в репозитории, но НИ ОДИН вызов compose в # деплое его не подключал — везде было жёстко `-f docker-compose.prod.yml`. # То есть первый же деплой на новом хосте поднял бы МЕРУ БЕЗ закрепления, и # бот вместе с пересылкой алертов умерли бы молча. Написанный, но никем не # подключённый оверрайд выглядит как решённая проблема, оставаясь нерешённой. # # Почему безусловно, а не только на Selectel (замер 25.08 С BEGET): # 149.154.167.220 -> 302 за 0.18 с # реальный Bot API /getMe -> {"ok":false,"error_code":401} # то есть отвечает именно Telegram, а не заглушка # Закрепляемый адрес живой и на старом хосте, поэтому условная логика «оверрайд # только на новом» была бы лишней машинерией ради хоста, который после 30.08 # перестанет быть продовым. Цена решения — на Beget пропадает запасной путь # через резолвер (см. «ОДНА ТОЧКА ОТКАЗА» ниже); принято сознательно. # # ⚠️ Это оверрайд стека МЕРЫ (проект gendesign-tradein). Подмешивать его к стеку # ПТИЦЫ НЕЛЬЗЯ: сервиса tgbot там нет, и compose отвергает весь проект — # service "tgbot" has neither an image nor a build context specified # а сервис backend есть в ОБОИХ стеках, так что закрепление молча легло бы на # бэкенд Птицы. Правильный вызов — внутри проекта МЕРЫ: # # docker compose -p gendesign-tradein \ # -f docker-compose.prod.yml -f docker-compose.selectel.yml up -d # # ── Почему это вообще нужно (замер 2026-08-23, с нового хоста) ───────────── # У api.telegram.org семь публикуемых адресов. С Selectel отвечает РОВНО ОДИН: # # 149.154.167.220 -> 302 за 0.11 с (единственный живой) # 149.154.166.110 -> 000 <- именно его отдаёт резолвер хоста! # 149.154.167.99 -> 000 # 149.154.175.100 -> 000 # 149.154.171.5 -> 000 # 91.108.4.5 -> 000 # 91.108.56.130 -> 000 # # Сканом всей подсети 149.154.167.0/24 с этого же хоста подтверждено, что # 149.154.167.220 — единственный отвечающий адрес Telegram во всём блоке, не # только среди этих семи. Это НЕ блокировка Selectel и не наш файрвол: с # Beget отвечают три адреса из тех же семи (включая тот, что отдаёт DNS там), # т.е. Telegram частично фильтруется у ОБОИХ провайдеров по-разному — Beget # просто попадает на живой адрес через штатный резолвер, а Selectel нет. # # Через прокси (ASocks, все четыре узла) Telegram тоже не проходит — все # запросы 000, узлы российские. Этот путь закрыт, обход только через IP. # # Проверено закрепление (прежде чем закреплять в compose): # - на хосте через /etc/hosts: curl https://api.telegram.org/ -> 302, 0.11 с # - настоящий Bot API (/bot/getMe) -> {"ok":false,"error_code":401} за # 0.11 с (для сравнения — с Beget тот же ответ приходит за 0.25 с, т.е. # закреплённый адрес не просто живой, а ещё и быстрее дефолтного пути) # - В КОНТЕЙНЕРЕ через docker --add-host -> 302 за 0.125 с (подтверждает, # что extra_hosts ниже — не отличается от прямой проверки на хосте) # - стабильность: 5 запросов подряд -> 302 302 302 302 302, без единого сбоя # # ── Что ломается без этого обхода ─────────────────────────────────────────── # tradein-tgbot — long-polling воркер (см. docker-compose.prod.yml, сервис # tgbot): он не слушает входящих соединений, а сам постоянно ходит наружу к # api.telegram.org. Если резолвер отдаёт мёртвый адрес, httpx виснет на # таймауте long-poll'а и процесс тихо умирает — restart: unless-stopped его # поднимает заново, он снова резолвит тот же мёртвый адрес и снова виснет. # Никакого явного краша или строки в логе, по которой это легко поймать — # отсюда и требование закрепить адрес ДО переезда, а не разбираться постфактum. # # Вместе с ботом молча ложатся: # - ops/lib-backup.sh — уведомления об успехе/провале бэкапов в Telegram # - ops/uptime-healthcheck.sh — uptime-нотификации # (оба — ХОСТ-скрипты, не контейнеры; extra_hosts на них не действует, этот # файл их не чинит — см. TODO в конце файла). # # ── TELEGRAM_API_IP: почему переменная, а не жёсткий IP ───────────────────── # Google/Cloudflare-класса анycast у Telegram нет: их адреса — обычные # датацентровые IP, которые Telegram время от времени меняет. Если # 149.154.167.220 однажды тоже станет мёртвым (или Selectel поменяет # маршрутизацию и он перестанет быть единственным живым), правка — это # одна строка в .env.runtime (TELEGRAM_API_IP=<новый адрес>) и # `docker compose ... up -d --force-recreate --no-deps tgbot`, БЕЗ релиза и # без правки этого файла. Дефолт ниже (:-149.154.167.220) — текущий # подтверждённый живой адрес, применяется если переменная не задана. # ── Почему ДВА сервиса, а не только бот ───────────────────────────────────── # В api.telegram.org ходит не только tgbot. Внутри контейнера backend это # делают ещё два пути: # - app/api/v1/glitchtip.py — пересылка алертов GlitchTip в Telegram-тему # - app/api/v1/support.py — support-чат # Оба бьют httpx напрямую в api.telegram.org из того же контейнера, что и # API. Ломаются они ЗАМЕТНЕЕ бота (не long-poll, а per-request: конкретный # запрос отваливается по таймауту), но ломаются — и, что важнее, ломается # пересылка алертов, то есть ещё один канал, которым мы узнали бы о беде. # Закреплять адрес только боту значило бы починить треть и выглядеть готовым. services: tgbot: extra_hosts: - "api.telegram.org:${TELEGRAM_API_IP:-149.154.167.220}" backend: extra_hosts: - "api.telegram.org:${TELEGRAM_API_IP:-149.154.167.220}" # ── Хостовая половина — закрыта в ops/selectel-bootstrap.sh ───────────────── # ops/lib-backup.sh и ops/uptime-healthcheck.sh исполняются НА ХОСТЕ (cron, # не в docker), поэтому extra_hosts им не помогает. Их закрывает шаг 11 # bootstrap-скрипта: он кладёт ту же запись в системный /etc/hosts, идемпотентно # и с той же переменной TELEGRAM_API_IP. Итого закреплены все три потребителя: # tgbot, backend и хостовые скрипты. # # ⚠️ ОДНА ТОЧКА ОТКАЗА, ЗАФИКСИРОВАНА ОСОЗНАННО. 149.154.167.220 — единственный # живой адрес во всём 149.154.167.0/24 с этого хоста. Если он умрёт, бот снова # умрёт молча, а канал оповещения об этом сам идёт через Telegram — поломка # скрывает сама себя. Поэтому закрепление НЕОБХОДИМО, НО НЕ ДОСТАТОЧНО: нужен # независимый канал алертов. Проверено, что с нового хоста доступен # smtp.beget.com:465 (порт 587 закрыт), плюс GlitchTip остаётся на Beget и # доступен по имени. Отдельная задача, см. #3059.