Files
transcriber/docs/conventions/config.md
T
av b46be019fc docs: канон приведён к сегодняшнему состоянию после сверки
- периметр: passport.md и security.md больше не утверждают, что HTTP API
  открыт без аутентификации, а review.md не числит эту находку типовой
  ложноположительной — приём, опрос и файл закрыты сессией с 2026-08-12;
- logging.md писал, что расширение попадает в журнал полем пути: описано
  изъятие инварианта приватности — собственное поле, имени и пути нет;
- узел ревью переименован в repo/pocketbase, поведение конвейера из обзора
  уехало ссылкой в спеку pipeline, Purpose спеки storage написан вместо
  заглушки, в ADR о переезде дописано уточнение о действующей раскладке.
2026-08-12 21:13:06 +03:00

10 KiB
Raw Blame History

Конфигурация

Конвенция: как устроена и грузится конфигурация transcriber (TOML). Правила оформления кода (How), не спецификация поведения.

Взято из проекта jellybit. Расхождения с сегодняшним кодом названы по месту. Главные: образец называется config.dist.toml, а не config.example.toml; комментариями снабжена половина полей; валидации на старте нет вовсе, кроме проверки пустых ключей внутри адаптеров.

Механизировано: ничего. Запрет os.Getenv для конфигурации правилом линтера не выражен, и godotenv в main.go загружает .env — то есть окружение сейчас участвует.

Принципы

  • Конфигурация — только TOML. Переменные окружения для конфигурации не используем: окружение наследуется дочерними процессами и видно через /proc/<pid>/environ — для секретов это слабее файла под 0600. Расхождение: main.go зовёт godotenv.Load() и молча продолжает без файла.
  • Грузим один раз при старте в одну типизированную структуру Config (под-структуры по секциям). Дальше по коду читаем только её — чтения файла в прикладном коде нет, только загрузчик internal/config.
  • Конфиг неизменяем после старта; смена параметров — перезапуск процесса.

Файл и поиск

  • Имя конфига по умолчанию — config.toml, ищется в рабочем каталоге процесса.
  • Путь переопределяется опцией -c path или --config=path.
  • Образец в репозитории — config.dist.toml (см. ниже); реальный config.toml не коммитится.

config.dist.toml — самодокументируемый образец

config.dist.toml коммитим как единый справочник по конфигу: все секции и все поля. Каждое поле снабжаем комментарием, из которого ясно:

  • зачем поле — что оно меняет в поведении;
  • допустимые значения — перечисление или границы;
  • единицы измерения, если применимы — секунды, байты, доля 01.
[server]
port                   = <N>          # порт HTTP-сервера
shutdown_timeout       = <N>          # ждать мягкой остановки сервера, секунды
force_shutdown_timeout = <N>          # ждать остановки воркеров, секунды
users_while_list       = ["<@name>"]  # кому отвечает бот; строка автора Telegram

Значения намеренно заменены плейсхолдерами: предмет конвенции — форма комментария, а числа, совпадающие с фактом до цифры, от факта неотличимы и начинают врать молча при смене умолчания. Действующие умолчания и их смысл живут одним домом — таблица «Настройки с числовым значением» в ../database.md; config.dist.toml — источник истины по составу полей.

Секретные поля оставляем пустыми — значение приходит из выкладки (см. «Секреты»).

Расхождение: секции [server] в config.dist.toml не хватает поля users_while_list, из-за чего бот на свежем конфиге отвечает отказом всем.

Расхождение: адреса провайдера в секции [auth] образца заполнены примерами вида https://auth.example.com/..., а не оставлены пустыми: пустой адрес не говорит, какой формы значение здесь ждут. Пустым оставлен только client_secret — он и есть секрет.

Поля по дискриминатору type

Когда набор полей секции зависит от поля-дискриминатора type (выбор одного из бекендов или внешних сервисов), обязательность и опциональность полей определяет значение type, а не фиксированный список секции.

  • Проверка — по type. Для каждого поддерживаемого значения свой набор обязательных полей; поля других значений не требуются. Неизвестное значение — ошибка на старте с перечислением поддерживаемых.
  • Образец — по type. В config.dist.toml:
    • основное (умолчательное) значение предзаполнено рабочими значениями;
    • альтернативные — блоками-комментариями ниже, каждый со своим описанием полей (зачем, границы, единицы — как у обычных полей);
    • так из примера видны все варианты и поля каждого, не открывая код.

Дискриминатора в transcriber пока нет; правило записано на случай второго распознавателя.

Секреты

Секреты доставляет выкладка, рендеря их прямо в config.toml (transcriber: Ansible из pet-project-server). Приложение просто читает TOML — отдельного слоя секретов в коде нет. Источник истины секрета — внешнее хранилище выкладки, не репозиторий и не окружение.

  • Секретные поля transcriber: telegram.bot_token, yandex.speech_kit_api_key, yandex.object_storage_access_key_id, yandex.object_storage_secret_access_key, auth.client_secret.
  • Отрендеренный config.toml (с секретами) не коммитится; права 0600, владелец — пользователь процесса (1000:1000).
  • В config.dist.toml секретные поля — пустые строки. Расхождение: сейчас там стоят подсказки вида your_..._here, а не пустые строки, и загрузчик их не отличает от настоящего значения.
  • Загрузчик на старте проверяет, что обязательные секреты не пусты (ловит криво отрендеренный файл) — см. «Проверка и остановка на старте».
  • В логи секреты не попадают — см. logging.md, «Безопасность».

Проверка и остановка на старте

Конфиг проверяем на старте, до приёма трафика. Негодный конфиг — лог ERROR и выход с ненулевым кодом, не стартуем наполовину.

Что проверяем:

  • обязательные поля заданы;
  • каталоги хранилища существуют и доступны на запись;
  • границы числовых полей соблюдены;
  • ключи внешних сервисов не пусты.

Расхождение: LoadConfig проверяет только существование файла и разбирает TOML. Пустой токен бота ловится в NewTelegramController уже после старта, и приложение продолжает работу без бота; пустые ключи Yandex ловятся в конструкторе распознавателя, и вот там процесс уже выходит с кодом 1. Единого места проверки нет.

Секция [auth] — первая, у которой проверка своя и стоит на старте: AuthConfig.Validate() зовётся из main.go сразу после загрузки и роняет процесс с перечнем незаполненных ключей. Причина в цене умолчания: поднявшись с молча выключенным входом, сервис остался бы открытым наружу, а узнать об этом было бы неоткуда. Сообщение называет имена ключей, а не значения — значение client_secret в журнал попасть не должно.

Структура в коде

  • Весь разбор и проверка — в internal/config; наружу отдаётся готовая Config.
  • Одна корневая структура Config с под-структурами по секциям. Перечень секций и полей здесь не повторяем: источник истины по составу — config.dist.toml, действующие числа — ../database.md, «Настройки с числовым значением». Каталог данных задаётся одним ключом [storage] data_dir (ADR).
  • Умолчания задаются в defaultConfig(), файл их перекрывает. Новое поле требует правки обоих мест.