Конфигурация¶
Всё настраивается через переменные окружения. Они также читаются из .env и
.env.local в рабочем каталоге (более поздние файлы имеют приоритет, а
настоящие переменные окружения важнее обоих). Имена нечувствительны к
регистру; логические значения принимают true/false, 1/0, yes/no,
on/off.
Источник истины —
settings.py.
Пресеты
compose.yaml загружает один из
deploy/presets/*.env
(standard, full, minimal) перед вашим .env. См.
Быстрый старт.
Обзор¶
Сервер¶
HOST¶
Интерфейс, к которому привязывается HTTP-сервер (и слушатель метрик). Внутри
контейнеров оставляйте 0.0.0.0; для экземпляра без контейнера за локальным
обратным прокси используйте 127.0.0.1.
PORT¶
Порт HTTP-сервера. Docker-образ слушает 3000; публикуйте его через
-p 8000:3000.
WEB_CONCURRENCY¶
Число процессов-воркеров. По умолчанию — число CPU, доступных процессу, но не больше 4. У каждого воркера свой цикл событий и, без Redis, свой кэш в памяти, поэтому при нескольких воркерах используйте Redis.
FORWARDED_ALLOW_IPS¶
Адреса прокси через запятую, заголовкам X-Forwarded-For /
X-Forwarded-Proto которых можно доверять, или *, чтобы доверять любым
(только если порт недоступен напрямую). От этого зависит IP клиента, который
использует ограничитель частоты запросов. Пример для nginx на другом хосте:
FORWARDED_ALLOW_IPS=10.0.0.5.
ACCESS_LOG¶
Логировать каждый запрос (true) — по умолчанию выключено, чтобы логи не
шумели при высокой нагрузке.
GRACEFUL_TIMEOUT¶
Сколько секунд воркер может завершать текущие запросы после SIGTERM, прежде
чем его принудительно остановят.
WORKERS_MAX_RSS_MB¶
Перезапускать воркер, резидентная память которого превысила столько МиБ.
Страховка для хостов с ограниченной памятью; по умолчанию не задано. Воркер
занимает ~60 МиБ в простое и ~100 МиБ под нагрузкой, плюс до
MEMORY_CACHE_MAX_MB на свой кэш внутри процесса, так что значение около
MEMORY_CACHE_MAX_MB + 150 срабатывает только при реальных проблемах.
METRICS_PORT¶
Отдавать метрики Prometheus сервера на этом порту по пути /metrics. См.
Служебные эндпоинты.
API¶
DISABLE_DOCS¶
Скрыть стартовую страницу (/), справочник API (/api), /openapi.json,
документацию (/docs/) и /sitemap.xml, а на /graphql — IDE GraphiQL,
GET-запросы и интроспекцию схемы. /robots.txt тогда запрещает всё. См.
Служебные эндпоинты.
CORS_ALLOW_ORIGINS¶
JSON-список разрешённых origin для браузеров, например
CORS_ALLOW_ORIGINS='["https://example.com"]'. Значение по умолчанию ["*"]
разрешает любой origin.
PUBLIC_URL¶
Публичный адрес экземпляра: схема и хост без пути и завершающей косой черты,
например https://tme.example.com. С него начинаются абсолютные URL в
/robots.txt и /sitemap.xml. Если не задан, берётся адрес, на который
пришёл каждый запрос; за прокси он верен, только если прокси передаёт
исходный Host. Workflow развёртывания задаёт его
из PUBLIC_URL или PUBLIC_HOST.
DOCS_SITE_DIR¶
Каталог документации, собранной для /docs/
(scripts/build_docs.py),
которую приложение там отдаёт. Docker-образ содержит сборку и задаёт
/app/docs. Если не задан (установка из исходников), /docs/…
перенаправляет на ту же страницу опубликованной документации. См.
Служебные эндпоинты.
Функции¶
ENABLE_PREVIEWS¶
Включить /v1/preview/{channel} и /v1/previews. Когда они отключены, они
отвечают 503.
ENABLE_FEED¶
Включить POST /v1/feed (и его фоновый рефрешер). По умолчанию выключено,
потому что это самый тяжёлый эндпоинт. Когда он отключён, он отвечает 503.
Защита¶
Обе выключены по умолчанию; как они работают и что отправляют клиенты, описано в разделе Ограничение частоты и proof of work.
ENABLE_RATE_LIMIT¶
Ограничение частоты запросов по IP клиента для всех эндпоинтов (GCRA):
всплеск до RATE_LIMIT_BURST запросов, затем
RATE_LIMIT_RPM в минуту. При превышении API отвечает 429
с Retry-After. За прокси укажите
FORWARDED_ALLOW_IPS, чтобы учитывался настоящий IP клиента.
Запросы самого сервера (через loopback и не проксированные), например
health check и smoke-тест деплоя, не ограничиваются.
RATE_LIMIT_RPM¶
Устойчивый темп: запросов в минуту на IP клиента.
RATE_LIMIT_BURST¶
Сколько запросов клиент может сделать сразу, прежде чем начнёт действовать
поминутный темп. По умолчанию равно RATE_LIMIT_RPM.
RATE_LIMIT_SHARED¶
При заданном REDIS_URL каждый клиент считается по всем
воркерам и серверам: раз в секунду каждый процесс добавляет свои счётчики в
Redis и блокирует клиентов, превысивших лимит там. Запросы никогда не ждут
Redis. При false или без Redis каждый процесс ограничивает сам по себе.
ENABLE_POW¶
Требовать proof of work в каждом запросе
(кроме документации, /healthz и эндпоинта задач). Нужен
POW_SECRET; задачи выдаёт GET /v1/pow/challenge.
POW_SECRET¶
Подписывает задачи (HMAC-SHA256). Не короче 32 символов, одинаковый на
всех серверах и секретный: зная его, можно подделывать задачи. Сгенерировать:
openssl rand -hex 32. Смена секрета делает выданные задачи недействительными.
POW_DIFFICULTY¶
Сколько ведущих нулевых бит должно быть в SHA-256 каждого запроса, от 8 до 32. Каждый бит удваивает работу клиента: 18 — это около 0,2 с на Python и 17 мс в нативном коде на запрос. См. Выбор сложности.
POW_CHALLENGE_TTL¶
Сколько секунд задача остаётся действительной, от 30 до 3600. Всё это время клиент использует одну задачу для всех своих запросов.
POW_BIND_IP¶
Принимать задачу только с того IP, которому она выдана, чтобы решённые задачи нельзя было раздавать. Выключите для клиентов, у которых адрес часто меняется.
GraphQL¶
Как применяются лимиты, описано в разделе GraphQL.
ENABLE_GRAPHQL¶
Обслуживать GraphQL API на /graphql. Когда он отключён,
путь отвечает 404. GraphiQL, GET-запросы и интроспекция дополнительно
зависят от DISABLE_DOCS.
GRAPHQL_MAX_DEPTH¶
Максимально допустимая вложенность выборок ниже корневого поля (у корневого поля глубина 0; поля интроспекции не считаются).
GRAPHQL_MAX_COMPLEXITY¶
Максимально допустимая стоимость операции: 1 за каждое выбранное поле плюс 10 за каждый канал, который корневое поле может загрузить с t.me.
GRAPHQL_MAX_ALIASES¶
Максимальное число алиасов в одном документе.
GRAPHQL_MAX_TOKENS¶
Максимальная длина документа в токенах лексера GraphQL; ограничивает работу разбора.
GRAPHQL_MAX_BATCH_SIZE¶
Максимальное число операций в одном пакетном запросе (JSON-массив). Пакеты
большего размера отклоняются с 400.
Производительность¶
CACHE_MODE¶
| Значение | Кэш страниц | Cache-Control для Cloudflare |
|---|---|---|
off |
нет — каждый запрос идёт на t.me | нет |
normal |
10 с | public, max-age=10, stale-while-revalidate=10 |
aggressive |
120 с | public, max-age=120, stale-while-revalidate=240 |
Действует на /v1/body, /v1/post и /v1/more. У превью и ленты свои
настройки, см. ниже.
PARSER_MODE¶
| Значение | Что извлекается |
|---|---|
full |
всё: текст с сущностями, медиа, опросы, кнопки, ответы, превью ссылок, реакции |
simplified |
текст (без сущностей), медиа и кнопки — заметно дешевле по CPU |
MEMORY_TRIM_INTERVAL¶
Интервал в секундах, с которым освобождённая память кучи возвращается
операционной системе (malloc_trim, только glibc — то есть Docker-образ).
Иначе после всплеска трафика glibc удерживает память больших короткоживущих
объектов для повторного использования: воркер под нагрузкой держал ~80 МиБ
такой свободной кучи. Одна очистка занимает около 1 мс. 0 отключает её.
Источник¶
TELEGRAM_BASE_URL¶
Адрес, с которого загружаются страницы t.me, — схема://хост[:порт] без пути.
Позволяет направить сервис на зеркало или на поддельный t.me: так
интеграционные тесты запускают образ против tests/integration/fake_telegram.py.
TELEGRAM_TIMEOUT¶
Общий тайм-аут одного запроса к t.me в секундах (фаза подключения ограничена 5 с). Неудачный запрос повторяется один раз на новом соединении, если ошибка сетевая.
TELEGRAM_MAX_CONCURRENCY¶
Максимум одновременных запросов к t.me на процесс-воркер. Защищает сервер от ограничения частоты запросов со стороны Telegram при больших запросах ленты или превью. Слоты делятся честно: по кругу между каналами и запросами ленты или превью, запросы пользователей — раньше фоновых обновлений (см. Кэширование).
TELEGRAM_CLIENT¶
HTTP-клиент для запросов к t.me:
browser(по умолчанию): нативный (wreq на BoringSSL). TLS-рукопожатие, настройки HTTP/2 и заголовки запроса берутся из одного профиляTELEGRAM_BROWSER_PROFILE, поэтому описывают один и тот же браузер.reqwest: pyreqwest (rustls и hyper). Отправляет заголовки того же профиля, но его отпечатки TLS и HTTP/2 — это rustls и hyper, каких нет ни у одного браузера, и cookie он не хранит. В продакшене не используется.
Измерения — в разделе HTTP-клиент.
TELEGRAM_BROWSER_PROFILE¶
Браузер, которым представляются запросы. По умолчанию — chrome_156,
собственный профиль проекта для текущего стабильного Chrome, в
rust/telegram-me-native/src/http/chrome.rs. Можно выбрать и
Chromium-профили wreq-util, например
chrome_149, edge_148 или opera_131. С неизвестным именем сервер не
запускается, а в сообщении указан самый новый профиль Chrome.
TELEGRAM_BROWSER_PLATFORM¶
Платформа этого браузера: android (по умолчанию), ios, windows, macos
или linux. Задаёт User-Agent, sec-ch-ua-platform и sec-ch-ua-mobile;
рукопожатие на всех платформах одинаковое. У chrome_156 нет варианта ios:
Chrome на iOS использует TLS-стек Apple, а не BoringSSL; с такой парой сервер
не запускается.
TELEGRAM_SESSION_LIFETIME¶
Сколько секунд живёт одна личность браузера у клиента browser, ±25 %
случайно. Личность — это хранилище cookie (t.me ставит stel_ssid),
соединения и их TLS-тикеты. Когда она заканчивается, клиент начинает с пустым
хранилищем и новыми соединениями, как новый профиль браузера. По умолчанию
14400 (4 часа, то есть от 3 до 5); 0 держит личность, пока не истекут её
cookie (у t.me они живут 24 часа) или t.me не ответит 4xx, кроме 404. И то и
другое завершает личность и раньше срока. У каждого процесса-воркера своя
личность (см. HTTP-клиент).
UPSTREAM_QUEUE_LIMIT¶
Сколько запросов воркер позволяет держать в ожидании свободного слота к
источнику. Следующие запросы, которым нужен t.me, получают 503 с заголовком
Retry-After вместо постановки в очередь. По умолчанию 16 на каждый слот
TELEGRAM_MAX_CONCURRENCY (512).
MAX_WAITING_REQUESTS¶
Сколько запросов воркер позволяет одновременно держать в ожидании работы с
источником (загрузки или рендеринга, начатых кем-то другим); сверх этого —
503 с Retry-After. Ограничивает память, которую может занять лавина
запросов.
SHARED_COALESCING¶
С REDIS_URL каждая страница загружается один раз на все воркеры и серверы:
процесс, который загружает, берёт в Redis короткую аренду (lease), а остальные
ждут его результата, вместо того чтобы самим обращаться к t.me. Выключите,
чтобы объединять запросы только внутри воркера.
EARLY_REFRESH_WINDOW¶
Обновлять в фоне кэшированную страницу, которую всё ещё читают в последней
доле её свежести (0.1 — последняя секунда из 10 с при CACHE_MODE=normal),
чтобы популярные страницы никогда не истекали под нагрузкой. 0 отключает.
Кэш¶
REDIS_URL¶
URL Redis или Valkey, например redis://valkey:6379/0 или
rediss://user:pass@host:6380/0. Если задан, кэш страниц, кэш превью,
популярность для ленты и блокировка лидера рефрешера общие для всех воркеров и
серверов. Повторные запросы каждый воркер всё равно сначала обслуживает из
своего кэша внутри процесса (MEMORY_CACHE_MAX_MB,
MEMORY_CACHE_MAX_TTL), так что обращения к Redis
ждут только его промахи. Если не задан, кэш внутри процесса — единственный.
Сбой Redis надолго не замедляет запросы: команды прерываются по тайм-ауту
через REDIS_TIMEOUT, а после трёх ошибок подряд в течение
10 секунд Redis обходится стороной 30 секунд (circuit breaker), и API тем
временем работает на своём кэше внутри процесса. Одна неудачная команда его не
включает.
Каждый воркер держит до 2 × TELEGRAM_MAX_CONCURRENCY (не меньше 32)
соединений с Redis. Всплеск команд сверх этого, например несколько запросов
ленты по 100 каналов одновременно, ждёт свободного соединения до
REDIS_TIMEOUT; команда, которая так и не получила соединение, считается
промахом кэша и не размыкает circuit breaker.
REDIS_TIMEOUT¶
Тайм-аут выполнения команды в секундах, а также максимальное время ожидания свободного соединения из пула. Команда медленнее этого размыкает circuit breaker.
REDIS_CONNECT_TIMEOUT¶
Сколько секунд даётся на открытие нового соединения с Redis, включая
TLS-рукопожатие. Он больше REDIS_TIMEOUT, потому что подключение к удалённому
TLS-эндпоинту занимает несколько обменов: около 0,3–0,7 с при задержке 90 мс.
С тайм-аутом команд каждое переподключение после ошибки не укладывалось бы во
время и снова размыкало бы circuit breaker, и Redis продолжал бы отваливаться.
Кроме того, каждый воркер открывает первое соединение при старте, чтобы за него
не платил ни один запрос.
REDIS_HEARTBEAT_INTERVAL¶
Раз в столько секунд каждый воркер пишет в Redis свою метку
(tme:v2:node:<host>:<pid>, TTL — три интервала); 0 отключает это.
- Метка держит соединение открытым, поэтому запросы не платят за переподключение к удалённому TLS-эндпоинту.
- Сбой замечается не позже чем через один интервал.
- Метка отправляется даже при разомкнутом circuit breaker: как только Redis снова отвечает, breaker замыкается, не дожидаясь конца 30-секундной паузы.
- Живые метки показывают, какие процессы на каких серверах работают:
redis-cli --scan --pattern 'tme:v2:node:*'.
PREVIEW_CACHE_TTL¶
Сколько секунд превью канала хранится в кэше. Неизвестные каналы в любом случае кэшируются на 60 секунд.
MEMORY_CACHE_MAX_MB¶
Лимит памяти кэша внутри процесса, на воркер, с Redis и без него: записи
хранятся сжатыми, а лимит учитывает то, что они реально занимают (значение,
ключ и служебные данные). Первыми вытесняются давно не использовавшиеся
записи. При значении по умолчанию 64 воркер под высокой нагрузкой держится
около 170 МиБ (весь контейнер — около 230 МиБ). С Redis воркер держит только
то, что было прочитано или записано за последние
MEMORY_CACHE_MAX_TTL секунд, обычно намного меньше
лимита.
MEMORY_CACHE_MAX_TTL¶
С Redis: сколько секунд максимум воркер хранит собственную копию записи кэша. Запись, которую другой сервер (или воркер ленты) записал в Redis, воркер увидит не позже чем через это время после записи; цена — одно чтение из Redis на ключ и воркер с такой периодичностью, для ключей, которые читают. Копии никогда не живут дольше записи в Redis. Без Redis, а также пока Redis недоступен, записи хранятся весь свой TTL. См. Внутреннее устройство → Кэширование.
Узлы¶
Узлы в одной частной сети могут делить работу напрямую: каждый ключ загружает с t.me один узел, его владелец, а остальные спрашивают его. Это работает и без Redis. См. Развёртывание → Слой узлов.
ENABLE_PEERS¶
Включает слой узлов. Каждый процесс-воркер становится узлом. Требует
PEER_SECRET.
PEERS¶
Где искать другие узлы: записи host или host:port через запятую, например
PEERS=10.0.0.108,10.0.0.165. DNS-имя может указывать на несколько адресов;
имена заново разрешаются каждые 30 секунд. Запись без порта пробуется на всех
портах диапазона PEER_PORT. Достаточно одного доступного узла:
об остальных узнают от него. С Redis узлы находят друг друга ещё и по меткам
heartbeat, так что PEERS можно оставить пустым.
PEER_PORT¶
Первый порт слоя узлов, и для UDP (членство), и для TCP (запросы). Каждый
воркер берёт первый свободный порт от PEER_PORT до
PEER_PORT + WEB_CONCURRENCY (один запасной — для воркера, который как раз
заменяется). 0 позволяет ОС выбрать порт; полезно только в тестах.
PEER_ADVERTISE_ADDR¶
IP-адрес, по которому этот узел доступен другим. По умолчанию — адрес источника маршрута по умолчанию. Внутри контейнера это адрес самого контейнера, поэтому там укажите частный адрес хоста (продакшен-деплой так и делает).
PEER_ALLOW_CIDR¶
Сети через запятую, из которых другим узлам можно подключаться. Соединения и
датаграммы откуда-либо ещё отбрасываются до того, как что-то прочитано. По
умолчанию — все частные и loopback-диапазоны: 10.0.0.0/8, 172.16.0.0/12,
192.168.0.0/16, 127.0.0.0/8, fc00::/7 и ::1/128.
PEER_SECRET¶
Общий секрет узлов, не короче 32 символов, например из
openssl rand -hex 32. Каждое сообщение несёт сделанный им тег HMAC-SHA256,
метку времени и nonce; поддельные, повторённые и старше 30 с сообщения
отклоняются.
PEER_ENCRYPTION¶
Ещё и шифровать каждое сообщение (XChaCha20-Poly1305 с ключом, выведенным из
PEER_SECRET) — для сетей, которым вы не доверяете. Настройка должна
совпадать на всех узлах.
PEER_TIMEOUT¶
Сколько секунд у узла есть, чтобы подключиться и ответить на чтение кэша или принять загрузку, прежде чем этот узел сделает работу сам. Узел, не уложившийся в срок, обходят стороной 3 секунды.
Лента¶
Всё это имеет значение только при ENABLE_FEED=true. См.
Внутреннее устройство → Лента.
FEED_FRESH_TTL¶
Закэшированная страница канала моложе этого значения (в секундах) используется как есть.
FEED_STALE_TTL¶
Страница старше FEED_FRESH_TTL, но моложе этого значения отдаётся сразу, а
фоновое обновление её заменяет. Более старые страницы загружаются до ответа.
FEED_REFRESH_MODE¶
| Значение | Поведение |
|---|---|
embedded |
рефрешер запускает каждый процесс API; с Redis работу выполняет только один из них (лидер) |
worker |
API его не запускает; запустите telegram-me worker отдельно (нужен Redis) |
off |
без фонового обновления; страницы обновляются только запросами ленты |
FEED_REFRESH_MIN_INTERVAL¶
Самый короткий интервал обновления (в секундах), для самых популярных каналов.
FEED_REFRESH_MAX_INTERVAL¶
Самый длинный интервал обновления, для каналов почти без спроса. Канал с
оценкой популярности s обновляется каждые
clamp(MAX / (1 + s), MIN, MAX) секунд.
FEED_REFRESH_TOP¶
Сколько самых популярных каналов обслуживает рефрешер.
FEED_POPULARITY_HALF_LIFE¶
Период полураспада популярности канала в секундах: по прошествии этого времени запрос ленты весит вдвое меньше. Каналы с оценкой ниже 0.05 забываются.
FEED_HALF_LIFE_HOURS¶
Через сколько часов свежесть поста в ранжировании уменьшается вдвое. Меньшие значения сильнее продвигают самые новые посты.
FEED_DIVERSITY¶
От 0 (чистый порядок по оценке) до 1 (максимальное перемешивание каналов
и тем). См.
Внутреннее устройство → Лента.
FEED_COLLAPSE_DUPLICATES¶
Сворачивать одну и ту же новость, опубликованную несколькими каналами, в её
лучший пост, перечисляя остальные в _duplicates.
Логи¶
LOG_LEVEL¶
DEBUG, INFO, WARNING или ERROR.
LOG_FORMAT¶
text — для людей, json — для сборщиков логов (по одному JSON-объекту на
строку с time, level, logger, message и любыми структурированными
дополнительными полями).