Содержание

Схема и миграции

Схема Postgres — это контракт между двумя сервисами на разных языках. Панель ей владеет и мигрирует, гейт её только читает.

Одно место, где форма определена: migrations/ в репозитории панели. app/models.py — отображение этой схемы, а не её источник; в нём ничего не создаёт таблиц. Go-структуры в internal/store/ — тоже отображение.

Как менять

cd zbgate-admin
# новая ревизия: следующий номер, down_revision — текущая голова
$EDITOR migrations/versions/00NN_что_меняем.py
.venv/bin/alembic upgrade head && .venv/bin/alembic downgrade base

Прогон вниз до нуля — обязательная часть проверки, а не формальность: миграция, которая не откатывается, это миграция, которую нельзя выкатить в пятницу.

Дальше — БД-тесты гейта против новой схемы. Они проверяют расшифровку секретов, авторизацию, лимиты, доступы, запись логов и горячий релоад, то есть ровно те места, где схема и код должны совпадать. См. Разработка и тесты.

Порядок выката

Миграция бывает двух видов, и разница существенна.

Совместимая — добавляет колонку или таблицу. Работающий гейт старой версии её не замечает. Выкатывается когда угодно.

Несовместимая — убирает или переименовывает то, что гейт читает. Гейт старой версии после неё сломается: колонки, которую он выбирает, больше нет. Такие миграции помечены в шапке файла, и панель с гейтом выкатываются вместе.

Пример из истории: миграция, перенёсшая наценку с ключа на учётную запись, удалила user_keys.cost_multiplier. Гейт до этой версии перестал бы находить ключи вовсе — не «терял бы наценку», а отвечал бы 401 на всё.

Уведомления об изменении

Гейт держит конфиг в памяти и узнаёт о правках по LISTEN/NOTIFY на канале zbgate_config. Триггер стоит на каждой таблице конфигурации:

providers, provider_models, provider_keys, user_keys, model_prices, model_price_windows, model_price_tiers, gateway_settings, admin_users.

Новая таблица конфигурации должна получить такой же триггер — иначе правка в ней подействует только после истечения кэша или перезапуска. admin_users в этом списке именно поэтому: наценка переехала туда, и без триггера её изменение ждало бы минуту.

CREATE TRIGGER имя_таблицы_notify
AFTER INSERT OR UPDATE OR DELETE ON имя_таблицы
FOR EACH STATEMENT EXECUTE FUNCTION zbgate_notify_config();

Отдельно есть zbgate_touch_updated_at()updated_at поддерживает база, а не писатель, чтобы гейт, обновляющий last_used_at, или ручная правка SQL не оставляли его устаревшим.

Секреты

ZBGATE_SECRET_KEY — один 32-байтный ключ AES-256-GCM на оба сервиса. Панель шифрует секреты провайдеров, гейт расшифровывает.

Формат хранения nonce(12) || ciphertext || tag(16) — одинаковый в Go и Python без всякого обрамления, потому что и cipher.AEAD.Seal, и AESGCM.encrypt дописывают тег к шифротексту сами.

  • Дамп базы без ключа секретов не выдаёт.
  • Потеря ключа делает все сохранённые секреты нечитаемыми: ротация означает перевод всех ключей провайдеров заново.
  • Расшифровка, упавшая на неверном теге, почти всегда означает подменённый ключ, а не битую строку — так и написано в сообщении, вместо голой ошибки крипто.

Ключи пользователей — SHA-256, только дайджест. Утёкшая база не воспроизводится против гейта. Пароли администраторов — Argon2id.

Расширения Postgres

Никаких. Вся криптография — в коде приложения: AES-256-GCM и SHA-256 в Python, то же самое в Go.

Раньше первая миграция создавала pgcrypto, которым ничего не пользовалось. Это требовало прав суперюзера при установке — чего управляемый Postgres может и не дать — и падало на минимальных сборках. Убрано.

Таблицы

Таблица Что в ней
providers апстримы: тип, адрес, флаги диалекта, нагрузка, повторы
provider_models таблица маршрутов; model уникален глобально
provider_keys секреты провайдеров, вес, ограничение по моделям
user_keys ключи клиентов: хеш, лимиты, бюджет, доступы, владелец
admin_users учётные записи: роль, наценка, потолок ключей, метки
model_prices ставки за токены и за минуту аудио
model_price_tiers ступени по длине контекста
model_price_windows окна по часам
gateway_settings одна строка: логирование, тарификация, самообслуживание
ldap_settings одна строка: каталог
request_logs журнал
request_log_tools вызовы инструментов, по строке на вызов

gateway_settings, ldap_settings — синглтоны, охраняемые CHECK (id = 1).