Files
vidconf/backend/scripts/apply_preset_settings.py

121 lines
5.8 KiB
Python
Raw Permalink 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.
"""Принудительное применение матрицы «пресет → настройки» на живой инсталляции.
Бутстрап (`main.py::lifespan`,
`InstanceSettingsService.ensure_bootstrapped`) применяет overrides пресета
инсталлятора (`BOOTSTRAP_CHAT_ENABLED`/`BOOTSTRAP_TRANSCRIPTION_ENABLED`/
`BOOTSTRAP_AI_LEVEL` в `.env`, см. `core.config.Settings`) только на чистой
БД (`INSERT ... ON CONFLICT DO NOTHING`) — строки уже существующей
инсталляции он не трогает. Этот скрипт запускает install.sh при ПОВТОРНОМ
запуске с другим пресетом на живой инсталляции, после подтверждения в
опроснике («обновить настройки модулей под пресет?») либо флага `--yes`
самого install.sh.
Управляет РОВНО 4 ключами (`chat`, `transcriber`, `summarizer`, `ai_level`,
см. `services.instance_settings.BOOTSTRAP_MANAGED_KEYS`) — остальные
настройки инстанса (таймзона, рассылка саммари, регистрация) этот скрипт не
трогает. Без `--force` пишет только ОТСУТСТВУЮЩИЕ из них — страховка от
молчаливой потери ручных правок администратора; с `--force` перезаписывает
все 4 значениями текущего пресета (`transcriber`/`summarizer` — целиком, тем
же дефолтом `plugins.yaml` + `enabled`, что и обычный бутстрап: подмена
provider/model для уровней `medium`/`max` происходит при чтении конфигурации,
см. `services.instance_settings.load_effective_config`, а не при записи).
`ai_level` пишется напрямую в БД, минуя валидацию доступности
(`services.ai_levels.detect_ai_levels`): модели уровня `medium`/`max`
докачиваются отдельными профилями `docker-compose` уже ПОСЛЕ первого запуска
install.sh, и жёсткая валидация здесь давала бы ложный отказ применения
пресета, пока докачка не завершена — вместо этого пишем предупреждение в лог.
Запустить: `uv run python -m scripts.apply_preset_settings [--force]`
"""
import argparse
import asyncio
import logging
from sqlalchemy import select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from core.config import get_settings
from core.db import async_session_maker, engine
from core.plugins.config import load_plugins_config
from models.instance_setting import InstanceSetting
from services.instance_settings import (
BOOTSTRAP_MANAGED_KEYS,
bootstrap_overrides_from_settings,
build_bootstrap_defaults,
)
logger = logging.getLogger(__name__)
async def apply_preset_settings(*, force: bool) -> list[str]:
"""Применить текущий пресет (`BOOTSTRAP_*`) к управляемым ключам `instance_settings`.
Возвращает список фактически записанных ключей (пустой — писать было
нечего: все 4 ключа уже существуют и `force=False`).
"""
settings = get_settings()
plugins = load_plugins_config(settings.plugins_config_path)
overrides = bootstrap_overrides_from_settings(settings)
defaults = build_bootstrap_defaults(plugins, overrides)
async with async_session_maker() as session:
result = await session.execute(
select(InstanceSetting.key).where(InstanceSetting.key.in_(BOOTSTRAP_MANAGED_KEYS))
)
existing_keys = set(result.scalars().all())
written: list[str] = []
for key in BOOTSTRAP_MANAGED_KEYS:
if key in existing_keys and not force:
continue
value = defaults[key]
stmt = (
pg_insert(InstanceSetting)
.values(key=key, value=value)
.on_conflict_do_update(index_elements=["key"], set_={"value": value})
)
await session.execute(stmt)
written.append(key)
await session.commit()
if "ai_level" in written:
logger.warning(
"apply_preset_settings: ai_level=%r записан напрямую, без проверки доступности "
"(detect_ai_levels) — модели уровня могут ещё докачиваться профилем docker-compose",
defaults["ai_level"]["level"],
)
logger.info(
"apply_preset_settings: обновлены ключи %s (force=%s, пропущены как уже существующие: %s)",
written or "нет",
force,
sorted(existing_keys - set(written)) or "нет",
)
return written
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Применить матрицу «пресет → настройки» инсталлятора к instance_settings."
)
parser.add_argument(
"--force",
action="store_true",
help="перезаписать уже существующие ключи (без флага пишутся только отсутствующие)",
)
return parser.parse_args()
async def _main() -> None:
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s"
)
args = _parse_args()
await apply_preset_settings(force=args.force)
await engine.dispose()
if __name__ == "__main__":
asyncio.run(_main())