Files
transcriber/docs/conventions/config.md
T
av 4d1c2bf44c Канон документов, каталог задач и OpenSpec
docs/ по канону 12: паспорт с целью проекта, архитектура сегодняшнего
устройства, схема хранилища, модель угроз, конвенции кода, журнал ревью.
Конвенции перенесены из jellybit; места, где код им не следует, помечены
строкой «Расхождение» как объявленный долг.

tasks/ с роадмапом: две достигнутые цели, две запланированные (веб и
многопользовательский режим), два направления (все форматы, долгие
записи) и пять задач в беклоге.

openspec/config.yaml — маршрутизатор с адресами документов, спек пока нет.

CLAUDE.md переписан по форме канона: инварианты с severity, семантика
гейта, запреты с путями. Taskfile получил task gate.
2026-08-10 21:19:07 +03:00

125 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Конфигурация
Конвенция: *как* устроена и грузится конфигурация 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`.
```toml
[server]
port = <N> # порт HTTP-сервера
shutdown_timeout = <N> # ждать мягкой остановки сервера, секунды
force_shutdown_timeout = <N> # ждать остановки воркеров, секунды
users_while_list = ["<@name>"] # кому отвечает бот; строка автора Telegram
```
Значения намеренно заменены плейсхолдерами: предмет конвенции — форма
комментария, а числа, совпадающие с фактом до цифры, от факта неотличимы и
начинают врать молча при смене умолчания. Действующие умолчания и их смысл живут
одним домом — таблица «Настройки с числовым значением» в
[../database.md](../database.md); `config.dist.toml` — источник истины по составу
полей.
Секретные поля оставляем пустыми — значение приходит из выкладки (см. «Секреты»).
*Расхождение:* секции `[server]` в `config.dist.toml` не хватает поля
`users_while_list`, из-за чего бот на свежем конфиге отвечает отказом всем.
## Поля по дискриминатору `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`.
- Отрендеренный `config.toml` (с секретами) **не коммитится**; права `0600`,
владелец — пользователь процесса (`1000:1000`).
- В `config.dist.toml` секретные поля — пустые строки.
*Расхождение:* сейчас там стоят подсказки вида `your_..._here`, а не пустые
строки, и загрузчик их не отличает от настоящего значения.
- Загрузчик на старте проверяет, что обязательные секреты не пусты (ловит криво
отрендеренный файл) — см. «Проверка и остановка на старте».
- В логи секреты не попадают — см. [logging.md](logging.md), «Безопасность».
## Проверка и остановка на старте
Конфиг проверяем **на старте, до приёма трафика**. Негодный конфиг — лог `ERROR`
и выход с ненулевым кодом, не стартуем наполовину.
Что проверяем:
- обязательные поля заданы;
- каталоги хранилища существуют и доступны на запись;
- границы числовых полей соблюдены;
- ключи внешних сервисов не пусты.
*Расхождение:* `LoadConfig` проверяет только существование файла и разбирает
TOML. Пустой токен бота ловится в `NewTelegramController` уже после старта, и
приложение продолжает работу без бота; пустые ключи Yandex ловятся в
конструкторе распознавателя, и вот там процесс уже выходит с кодом 1. Единого
места проверки нет.
## Структура в коде
- Весь разбор и проверка — в `internal/config`; наружу отдаётся готовая `Config`.
- Одна корневая структура `Config` с под-структурами по секциям (`Server`,
`Database`, `Storage`, `Yandex`, `Telegram`).
- Умолчания задаются в `defaultConfig()`, файл их перекрывает. Новое поле
требует правки обоих мест.