Skip to main content
Omniflow настраивается целиком через переменные окружения. Конфигурационного файла нет, флагов командной строки нет — кроме --decrypt-backup у воркера, — и горячей перезагрузки тоже нет. internal/config предоставляет три загрузчика: один для API, один для бота, один для воркера, и каждый процесс выполняет свой загрузчик один раз, при старте. Значение, не прошедшее валидацию, приводит к отказу сразу, а не остаётся скрытой проблемой: процесс пишет invalid configuration и завершается с кодом 1, поэтому установка никогда не работает наполовину настроенной — с shop ID YooKassa и без секрета или с включёнными резервными копиями и без ключа. Следствие однократной загрузки в том, что изменение переменной требует перезапуска процесса, который её читает. Docker Compose передаёт один и тот же файл .env сервисам api, bot и worker, поэтому на практике один файл настраивает все три, а изменение означает повторный docker compose up -d. Чтобы начать, скопируйте .env.example в .env; о первом запуске см. Быстрый старт, о топологии compose — Развёртывание. В таблицах ниже перечислены все переменные, которые читает хоть один процесс Omniflow, включая несколько отсутствующих в .env.example, и одну, которую .env.example задаёт, но не читает никто. Кто читает — это процессы, чей загрузчик читает переменную; о переменной, которую читает процесс, никогда не использующий её значение, отдельно сказано в тексте под её таблицей.

Как разбираются значения

Каждая переменная проходит через один из небольшого набора считывателей, и у каждого свой режим отказа, который стоит знать до того, как вы начнёте отлаживать настройку, которая словно бы игнорируется.
Опечатка в булевом значении незаметна. APP_METRICS_ENABLED=yes и APP_TELEMETRY_ENABLED=nope оба отвергаются парсером и оба откатываются на значение по умолчанию, а оно в обоих случаях равно true. Используйте true и false.

Что нужно каждому процессу для запуска

Бот отказывается стартовать без Valkey, вместо того чтобы работать незащищённым: на Valkey держатся его ограничения частоты запросов по пользователю и его защита от повторного проигрывания callback, и обе стоят между двойным нажатием и двойным списанием. Ошибка говорит об этом прямо — APP_VALKEY_URL is required for bot rate limits and callback replay protection.

Основное

APP_DATA_ENCRYPTION_KEY — это ключ, которым запечатываются контактные данные клиентов, TOTP-секреты операторов, сохранённые в панели настройки и секреты OIDC, cookie процесса входа клиента, учётные данные провайдеров цифровых товаров и отпечатки контактов реферальной программы. Сгенерируйте его один раз:
Сохраняйте резервную копию ключа вместе с базой данных и никогда не меняйте его без веской причины. Смена делает нечитаемым каждое значение, уже запечатанное старым ключом, и ни один процесс не предложит их перешифровать.
Бот и воркер каждый запускается без него и отключает одну возможность, вместо того чтобы отказаться работать. Воркер без ключа пишет digital goods delivery disabled с reason: APP_DATA_ENCRYPTION_KEY is not set, а кроме того работает без обновления блок-листа и оценки аномалий в свипере и без исполнителя массовых действий, потому что все три распечатывают учётные данные, записанные панелью. Задайте ключ на воркере, как только начнёте продавать цифровые товары, подпишетесь на блок-лист или воспользуетесь массовыми действиями.

База данных и кеш

Служба резервного копирования разбирает APP_DATABASE_URL второй раз — в PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD и PGSSLMODE, — чтобы пароль попадал в pg_dump через окружение, а не через argv дочернего процесса, где его мог бы прочитать любой локальный пользователь. Загрузчик воркера читает APP_VALKEY_URL, а сам воркер ничего из этого не использует: ни один путь исполнения в его коде не открывает клиент Valkey. Оставить переменную заданной безвредно, и это позволяет держать один .env пригодным для всех трёх процессов.
Compose-сервис migrate жёстко зашивает postgres://omniflow:omniflow@postgres:5432/omniflow?sslmode=disable и не читает .env. Смена учётных данных базы данных означает правку и compose.yaml.

Remnawave

Проба готовности API обращается к Remnawave аутентифицированным запросом, который перечисляет одного пользователя, поэтому отозванный или истёкший токен проявляется как нездоровая зависимость remnawave в /readyz, а не как сорвавшаяся покупка позже. Три подряд нездоровых замера по умолчанию также открывают режим обслуживания. См. Интеграция с Remnawave.

Telegram

Длинные опросы используются по умолчанию и остаются поддерживаемым режимом для разработки и как запасной вариант. Установка webhook-URL заставляет бота при старте вызвать setWebhook с allowed_updates, ограниченным значениями message, edited_message, callback_query, pre_checkout_query, my_chat_member и chat_member. Сгенерируйте секрет командой openssl rand -hex 32. Полная настройка, включая BotFather и домен Mini App, описана в Интеграции с Telegram.

URL и валюта

У APP_UPDATE_FEED_URL нет значения по умолчанию, и это сделано намеренно. Чтение ленты сообщает тому, кто её отдаёт, что эта установка существует, примерно где она находится и какую версию запускает, — поэтому проверку владелец включает сам, а не обнаруживает, что она уже идёт. Omniflow не поставляет и адреса для сравнения: документация не называет репозитория, а URL, зашитый в бинарник, был бы фактом, который в установке нечем подтвердить. Укажите любой JSON-документ, называющий версию: эндпоинт релизов GitHub отвечает полем tag_name, а владелец, публикующий собственный файл, может отвечать полем version. Читаются обе формы. Результат появляется в диагностическом пакете под ключом update, и лента читается не чаще раза в шесть часов, сколько бы раз пакет ни собирали. Лента, которая отказала, не ответила вовремя или вернула нечитаемое, сообщает unreachable, а не «всё актуально». Подробность — это категория, и никогда не адрес: в URL ленты может быть токен. | APP_DEFAULT_CURRENCY | RUB | api, bot, worker | нет | нет | Валюта расчётов, в которой каталог сравнивает тарифы. Приводится к верхнему регистру, затем сверяется с ^[A-Z]{3}$; всё остальное валит запуск | От APP_PUBLIC_URL зависят два отказа при запуске, и оба существуют потому, что альтернатива — ссылка, ведущая в никуда: бот отказывается стартовать, когда YooKassa настроена, а публичного URL нет (APP_PUBLIC_URL is required for YooKassa hosted checkout return links), и бот, и API отказываются, когда APP_CUSTOMER_MAGIC_LINK_ENABLED=true, а публичный URL не задан. Passkey операторов — третье, что от него зависит, и единственное, что при запуске не приводит к отказу. Passkey привязан к origin, поэтому без публичного URL привязывать не к чему и функция просто выключена: экран входа не показывает кнопку, вместо того чтобы показать кнопку, которая ничего не открывает. Планировать нужно обратное: смена этого значения после того, как операторы зарегистрировали ключи, делает недействительными все ключи — браузер не предложит учётные данные сайту, для которого они не создавались. См. Безопасность. Используйте XTR для установки, продающей только через Telegram Stars. Тариф можно купить лишь способом оплаты, который рассчитывается в одной из валют, в которых у тарифа есть цена, поэтому назначайте тарифу цену в XTR, чтобы продавать его через Stars, и в фиатной валюте, чтобы продавать через CryptoBot или YooKassa. См. Каталог и промоакции.

Платёжные провайдеры

Адаптер существует только тогда, когда настроены его учётные данные, поэтому отдельного переключателя включения нет. Пару значений YooKassa нужно задавать вместе: бот отказывается стартовать с одним без другого (APP_YOOKASSA_SHOP_ID and APP_YOOKASSA_SECRET must be configured together), потому что shop ID без секрета — это установка, предлагающая способ оплаты, который она не может довести до конца. Воркер держит те же учётные данные, что и API, потому что именно он списывает деньги за автоматические продления: воркер без адаптера мог бы разве что записать, что продление не удалось. Настройка каждого провайдера — в Платёжных провайдерах, а сценарий регулярных списаний — в Регулярных платежах.

Пополнение кошелька и лимиты подписок

Суммы указываются в минорных единицах APP_DEFAULT_CURRENCY. Пресет, который лимиты отвергли бы, вообще не отрисовывается, поэтому клиент не может нажать кнопку, которую бэкенд затем отклонит.
Эти переменные — то, что применяют работающие процессы. При первом запуске против базы данных, где нет строки commerce_settings, API дополнительно заполняет эту строку из этих же значений, чтобы установка, обновляющаяся с более старой версии, сохранила лимиты, настроенные её оператором, а не молча взяла значения по умолчанию из схемы. Дальше строку пишет только панель (PUT /v1/panel/settings/commerce/topup и .../subscriptions), и именно сохранённая строка показывается в панели и в обзоре клиентского веб-кабинета — но заказы и кошелёк по-прежнему проверяются по значениям окружения, загруженным при старте. Держите переменные заданными и согласованными с панелью и перезапускайте процессы после изменения любой из сторон.
Заполнение никогда не блокирует запуск: сбой пишется в лог как commerce settings could not be seeded; the panel will show none yet, и API продолжает работу. См. Пополнение кошелька и корзины и Подписки.

Коммуникация с клиентами

Окно восстановления и контроль возраста аккаунта для пробного периода читает не только бот, но и API, и это сделано намеренно: клиентский веб-кабинет оформляет заказ по тем же правилам, что и бот, а пробный период, в котором чат отказывает, а браузер выдаёт, был бы средством контроля с дырой. Задавайте их одинаково для обоих процессов — один общий .env делает это сам. Маркетинговые ограничения и время жизни корзины читает только бот, потому что именно он отправляет сообщения и именно он держит сохранённые корзины. Поведение описано в Уведомлениях и согласиях.

Панель оператора, клиентская панель и сессии

Оба флага cookie по умолчанию равны true, поэтому развёртывание, которое о них забыло, всё равно получает cookie, которую браузер отправит только по TLS. Установка любого из них в false пишет в лог предупреждение, называющее риск, — admin session cookie is not marked Secure; use this only for local development.
APP_ADMIN_COOKIE_SECURE=false и APP_CUSTOMER_COOKIE_SECURE=false предназначены только для локального стека по обычному HTTP. Без Secure cookie сессии можно перехватить из любого незашифрованного запроса к тому же хосту.
Ссылка для входа выключена по умолчанию, потому что это вторые предъявительские учётные данные, путешествующие через чат. Включайте её там, где нельзя воспользоваться login-виджетом Telegram — обычно из-за того, что домен не удаётся привязать в BotFather, — и команда бота /login начинает работать. Самому входу через Telegram отдельная переменная не нужна: он проверяет полезную нагрузку виджета с помощью APP_TELEGRAM_TOKEN. APP_TRUSTED_PROXIES разбирается один раз при старте и используется и операторской, и клиентской поверхностью. Некорректный элемент — это ошибка запуска (trusted proxy entry must be an IP address or CIDR block); голый IP превращается в префикс /32 или /128. Заголовок X-Forwarded-For подделывается тривиально, поэтому он учитывается только тогда, когда непосредственный узел-отправитель — один из настроенных прокси, а если не настроен ни один, заголовок игнорируется полностью и используется адрес сокета: правильное поведение по умолчанию для развёртывания, выставленного напрямую. Полученный адрес питает ограничение частоты запросов, журнал событий безопасности клиента и журнал аудита, поэтому важны обе стороны ошибки: не доверять никому — и все запросы за прокси делят один адрес; доверять слишком многим — и атакующий сам выбирает себе адрес. Для мостовой сети Docker за обратным прокси обычное значение — 172.16.0.0/12. См. Обратный прокси, Панель администратора и Клиентский веб-кабинет.

Уведомления операторам и поверхность /ops

Если APP_OPERATOR_CHAT_ID остаётся равным 0, нотификатор пишет operator notifications are disabled because no group is configured, а постановка в очередь становится пустой операцией, так что уведомления даже не сохраняются. Команда /ops регистрируется безусловно и просто ничего не отвечает Telegram-аккаунту, которого нет в списке, поэтому существование команды никогда не подтверждается клиенту; кроме того, она отвечает только в личном чате. См. Уведомления операторам.

Резервные копии

Образы бота и воркера собираются с включёнными клиентскими утилитами PostgreSQL, и оба монтируют один и тот же том backup-data в /var/lib/omniflow/backups, поэтому под compose значения по умолчанию работают без изменений. Тот же ключ читается и напрямую — до всякой загрузки конфигурации — командой worker --decrypt-backup, которая превращает зашифрованный дамп обратно во входные данные для pg_restore, не используя ничего, кроме бинарника, ключа и файла.
Храните APP_BACKUP_ENCRYPTION_KEY там, куда не достаёт сама резервная копия базы данных. Копию, зашифрованную ключом, который существует только внутри этой копии, восстановить невозможно.
См. Резервное копирование и восстановление.

Обнаружение режима обслуживания

Действует по ним только API. Это процесс, который и без того опрашивает каждую зависимость для готовности, поэтому именно он ведёт контроллер; загрузчик воркера разбирает те же четыре значения и не использует ни одного. Серии существуют, чтобы один таймаут не мог закрыть установку, а окно, открытое оператором вручную, никогда не снимается автоматически. Автоматическое обнаружение следит только за postgres и remnawave — отказ Valkey виден в /readyz и сам по себе режим обслуживания никогда не открывает. См. Режим обслуживания.

Сроки хранения и вложения поддержки

Хранение убирает только расходуемые операционные данные. История финансов, аудита, согласий, идентичности и прав на услуги пишется только на добавление и живёт столько же, сколько установка; подчистка её никогда не удаляет. Хранение выполняет только воркер, поэтому установка, где запущены лишь API и бот, никогда не убирает за собой. Окно outbox сейчас не удаляет ничего. Omniflow пишет строки outbox_events для order.paid, wallet.credited и addon.purchased, но ни один процесс не помечает их опубликованными, а запрос на удаление совпадает только с опубликованными строками. Ожидайте, что outbox будет расти, а GET /v1/admin/outbox — показывать растущее число ожидающих; см. Задания и очереди.
APP_SUPPORT_ATTACHMENT_DIR отсутствует в .env.example, и compose.yaml не монтирует под неё том ни на сервисе api, ни на worker. Поэтому вложение к обращению, загруженное из браузера, живёт в записываемом слое контейнера API и теряется при пересоздании контейнера, а воркер — нацеленный на собственную пустую копию того же пути — удаляет строки по сроку хранения, пока уцелевшие байты остаются там, где были. Если вы принимаете загрузки из браузера, задайте переменную с одним и тем же путём на обоих процессах и смонтируйте туда один общий том.
Вложений, загруженных через Telegram, это не касается: их байты остаются у Telegram, а Omniflow хранит только ссылку. См. Служба поддержки.

Анонимная телеметрия

Телеметрия включена по умолчанию, и отправляет её только API — ни бот, ни воркер клиента не создают. APP_TELEMETRY_ENABLED=false предотвращает все сетевые запросы телеметрии, а при старте API пишет либо anonymous telemetry enabled с disable_with=APP_TELEMETRY_ENABLED=false, либо anonymous telemetry disabled.
Предпросмотр телеметрии в панели, в разделе Настройки, читает сохранённый документ настроек, а не работающую конфигурацию, поэтому установка по умолчанию показывает предпросмотр как отключённый, хотя API на самом деле отправляет heartbeat. Считайте APP_TELEMETRY_ENABLED ответом на вопрос, включена ли телеметрия.
Точный состав отправляемых данных — на странице Телеметрия.

Операционные эндпоинты

Три детали расходятся с тем, что подсказывают имена переменных, и каждая уже ловила операторов:
  • Бот игнорирует APP_METRICS_ENABLED. Он создаёт реестр метрик безусловно и обслуживает /metrics всякий раз, когда задан APP_BOT_METRICS_ADDR.
  • APP_BOT_METRICS_ADDR игнорируется, когда совпадает с APP_BOT_HTTP_ADDR. Отдавать метрики и готовность на смотрящем в интернет приёмнике webhook значило бы публиковать внутренности установки на хосте, который должен быть доступен откуда угодно, поэтому бот вместо этого отказывается.
  • Очистка APP_WORKER_HTTP_ADDR не отключает приёмник воркера. Пустое значение считается незаданным и откатывается на :8081. Вместо этого держите порт вне публичной сети средствами прокси или файрвола.
У бота из коробки нет эндпоинта проб, потому что APP_BOT_METRICS_ADDR по умолчанию не задан. Задайте его, если хотите /readyz на всех трёх процессах. Оба поставляемых примера обратного прокси возвращают 404 для /metrics и /readyz на публичном хосте API. См. Наблюдаемость.

Трассировка

Omniflow инструментирует входящий HTTP, обновления Telegram, задания исполнения заказов, запросы к PostgreSQL и исходящий HTTP спанами OpenTelemetry, но ни один процесс Omniflow не устанавливает провайдер трассировки, и ни одна переменная OTEL_* нигде в коде не читается. Когда провайдер не установлен, глобальный провайдер является пустышкой, поэтому установка OTEL_EXPORTER_OTLP_ENDPOINT или любой другой стандартной переменной SDK сегодня ничего не меняет. Настройки, которая включает трассировку, не существует. Поддерживаемые операционные сигналы — метрики и структурированные логи.

Веб-приложение

Приложение Next.js в apps/web читает собственные переменные. Они задаются на сервисе web в compose.yaml, а не в .env. Запросы из браузера по умолчанию идут на тот же origin; развёртывание с другим origin задаёт базовый URL клиента явно, а не через переменную окружения.

Настройки, которые панель хранит, но не применяет

Панель оператора пишет документ installation_settings для каждого раздела — бренд, Remnawave, бот, группа операторов, обслуживание, уведомления, телеметрия, резервные копии, политика сессий. Эти документы читаются обратно только собственным экраном настроек панели и предпросмотром телеметрии. Ни один работающий процесс не читает документ раздела или его запечатанные секреты, поэтому сохранение документа не меняет никакого поведения: URL и токен Remnawave, токен бота, группа операторов, политика обслуживания, включённость телеметрии и расписание резервных копий берутся из переменных окружения выше, прочитанных один раз при старте. Считайте .env источником истины, а экран настроек — записью намерений, пока это не изменится. Экран и эту границу описывают Настройки установки.

Секреты

Эти значения являются учётными данными, и обращаться с ними следует соответственно: APP_DATABASE_URL (содержит пароль), APP_REMNAWAVE_TOKEN, APP_OPERATOR_TOKEN, APP_TELEGRAM_TOKEN, APP_TELEGRAM_WEBHOOK_SECRET, APP_CRYPTOBOT_TOKEN, APP_YOOKASSA_SECRET и два AES-ключа APP_DATA_ENCRYPTION_KEY и APP_BACKUP_ENCRYPTION_KEY. Никогда не коммитьте .env или боевые учётные данные; репозиторий гоняет в CI сканер секретов именно ради этого. Механизм вычистки логов Omniflow вырезает токены ботов, bearer- и basic-учётные данные, похожие на карточные последовательности цифр, идентификаторы устройств и любые URL из всего, что может попасть в строку лога, в уведомление оператору или в диагностический пакет, — на заявленном размене: чрезмерно вычищенная строка лога это мелкое неудобство, а утёкшая ссылка на подписку или токен бота это инцидент безопасности. Это защищает от случайностей, а не от конфигурации: токен, вставленный не в ту переменную, остаётся токеном, который оператор может прочитать из окружения процесса. Используйте TLS для внешних подключений к PostgreSQL, Valkey, Remnawave и платёжным провайдерам. Вся картина описана в Безопасности.