Первоначальная версия VidConf

This commit is contained in:
2026-07-23 01:04:01 +03:00
commit 896455381a
335 changed files with 61527 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
"""Принудительное применение матрицы «пресет → настройки» на живой инсталляции.
Бутстрап (`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())