# Инсталлятор (`install.sh`, релиз v0.0.1)

Один скрипт в корне репозитория — автодетект железа, опросник из 5
пресетов поставки, идемпотентная запись `.env`, сборка образов, миграции и
seed, подъём стека. По завершении `http://localhost/` отдаёт рабочий
фронтенд-SPA (React + Vite; статика вкомпилирована в образ nginx,
`frontend/Dockerfile`) — во ВСЕХ пресетах. Источник истины по
моделям/квантам/требованиям железа — ADR-004
(`docs/architecture/adr/004-ai-tier-matrix.md`); по пресетам поставки —
сам `install.sh` (см. таблицу ниже).

## Пресеты

| # | Состав | Профили compose | Модели |
|---|---|---|---|
| 1 | MVP-ядро (лобби, конференции, календарь, закреплённые, гости) | `media` | — |
| 2 | 1 + чат | `media` | — |
| 3 | 2 + AI «мин» (CPU) | `media,transcribe,llm` | faster-whisper `small` + Qwen3.5-4B |
| 4 | 2 + AI «средний» (CPU) | `media,transcribe,llm` | faster-whisper `medium` + Qwen3.5-9B |
| 5 | 2 + AI «макс» (GPU обязателен) | `media,transcribe-gpu,llm-gpu` | faster-whisper `large-v3` + Qwen3.5-35B-A3B |

Чат (2) и уровень AI (3/4/5) — переключатели плагинов в админке
(`instance_settings`, БД); `install.sh` поднимает НЕОБХОДИМЫЕ для
выбранного пресета контейнеры/модели И синхронизирует настройки инстанса
в соответствии с матрицей пресета (см. раздел «Синхронизация настроек
модулей»). Уровень `medium` в текущей матрице (`backend/services/ai_tiers.py`)
CPU-only — отдельного GPU-варианта профилей для пресета 4 нет (GPU для
`medium` — ручная настройка администратора вне детекта).

Запись конференций НЕ входит в пресеты (появится в v0.1.0) — вопрос
про неё инсталлятор не задаёт.

## Синхронизация настроек модулей (бутстрап)

При **первом старте** backend (lifespan) применяет пресет настроек через
три env-переменные `BOOTSTRAP_*`:

- `BOOTSTRAP_CHAT_ENABLED` (`true` | `false`) — включить чат в конференциях
- `BOOTSTRAP_TRANSCRIPTION_ENABLED` (`true` | `false`) — включить транскрибацию и суммаризацию
- `BOOTSTRAP_AI_LEVEL` (`min` | `medium` | `max`) — уровень AI (игнорируется, если транскрибация выключена)

**Матрица пресет → настройки модулей:**

| Пресет | Чат | AI | Уровень | `BOOTSTRAP_CHAT_ENABLED` | `BOOTSTRAP_TRANSCRIPTION_ENABLED` | `BOOTSTRAP_AI_LEVEL` |
|--------|-----|-----|---------|:---:|:---:|---|
| 1 (MVP-ядро) | — | — | — | `false` | `false` | `min` |
| 2 (+чат) | ✓ | — | — | `true` | `false` | `min` |
| 3 (+AI мин) | ✓ | ✓ | мин | `true` | `true` | `min` |
| 4 (+AI средний) | ✓ | ✓ | средний | `true` | `true` | `medium` |
| 5 (+AI макс) | ✓ | ✓ | макс | `true` | `true` | `max` |

Бутстрап **идемпотентен** при первом старте: дефолты из `config/plugins.yaml`
(всё `enabled: true`) переопределяются переменными `BOOTSTRAP_*` только если
БД ещё не содержит ключи настроек инстанса (проверка `INSERT ... ON CONFLICT DO NOTHING`).
Повторное включение контейнера backend не перетирает существующие настройки.

**При повторном запуске** инсталлятора на живой инсталляции:

```bash
./install.sh --preset 3                  # спросит: обновить настройки? [Y/n]
./install.sh --preset 3 --yes            # без подтверждения, применит пресет 3
```

Поведение:
- **Дефолт (Enter):** применяет матрицу выбранного пресета к настройкам БД
  (defs: `docker compose exec -T backend uv run --no-sync python -m scripts.apply_preset_settings --force`)
- **Отказ (`n`):** сохраняет ручные правки админа; переменные `BOOTSTRAP_*` в `.env`
  обновляются, но скрипт применения настроек НЕ запускается
- **Флаг `--yes`:** автоматически применяет пресет без вопроса (для CI/CD)

Скрипт обновления: upsert четырёх ключей в `instance_settings` БД:
`chat` (`enabled`), `transcriber` (`enabled`), `summarizer` (`enabled`), `ai_level`.

## Использование

```bash
./install.sh                    # интерактивный опросник + рекомендация по железу
./install.sh --preset 3         # неинтерактивно
./install.sh --preset 3 --yes   # без подтверждений (скрипты/CI, пресет 5 без GPU)
./install.sh --preset 2 --monitoring --yes  # + профиль monitoring (Grafana/Prometheus)
```

Флаг `--monitoring` дописывает профиль `monitoring` к `COMPOSE_PROFILES` (иначе
`set_env_var` при выборе пресета перезаписывает это значение целиком и
Grafana/Prometheus не поднимаются автоматически — см.
`.forcc/deploy/SESSION4-FINDINGS.md`, ГРАБЛИ 2, во внутренних заметках сессии
деплоя). Без флага мониторинг поднимается отдельной командой — см.
[docs/deploy/monitoring.md](monitoring.md).

Повторный запуск с другим `--preset` — апгрейд/даунгрейд на месте: модели
уровня докачиваются (старые с диска не удаляются — занимают место, но не
мешают), `COMPOSE_PROFILES`/`WHISPER_MODEL`/`LLM_MODEL_*` в `.env`
обновляются точечно, секреты (JWT/БД/TURN/LiveKit/Grafana) и любые
пользовательские правки `.env` — сохраняются (генерация только при полном
отсутствии значения, см. `ensure_secret` в `install.sh`).

## Что делает скрипт

1. Автодетект `nproc`/`free -m` (или `sysctl` на macOS)/`nvidia-smi` →
   пишет `HW_CPUS`/`HW_RAM_MB`/`HW_GPU_NAME`/`HW_VRAM_MB` в `.env` — их
   читает `backend/services/ai_levels.py` (детект доступности уровней AI в
   админке, причины недоступности).
2. Точечно обновляет `.env` (создаёт из `.env.example` при первом запуске,
   на первом запуске сразу генерирует РЕАЛЬНЫЕ секреты вместо
   плейсхолдеров `change-me...` из шаблона в git).
3. Собирает флаги `--profile` из `COMPOSE_PROFILES` в `.env` (эта версия
   docker compose НЕ подхватывает `COMPOSE_PROFILES` автоматически при `up`,
   поэтому профили media/transcribe/llm передаются командам явно) и собирает
   образы: `docker compose pull --ignore-buildable` (best-effort) →
   `docker compose build` (backend, worker и nginx — образ nginx multi-stage
   собирает фронтенд-SPA и вкомпилирует статику, `frontend/Dockerfile`).
4. Поднимает `postgres`/`redis` (`up -d --wait`) и применяет **до старта
   backend** миграции и seed одноразовыми контейнерами:
   `docker compose run --rm backend uv run --no-sync alembic upgrade head` +
   `... python -m scripts.seed`. Порядок критичен: `backend.lifespan`
   бутстрапит `instance_settings` при каждом старте приложения, поэтому на
   чистой БД таблицы обязаны существовать до первого запуска backend — иначе
   healthcheck (`--wait`) никогда не проходит. Seed идемпотентен (админ;
   справочник `teams` и ключи `instance_settings`, включая
   `registration_team_choice`/`registration_email_domain`, бутстрапятся
   backend'ом при старте, `INSERT ... ON CONFLICT DO NOTHING` — существующие
   настройки инстанса не перетираются).
5. Поднимает остальной стек: `docker compose <--profile ...> up -d --wait`
   (backend, worker, nginx с фронтом + сервисы активных профилей). Команда
   идемпотентна и обёрнута в ретрай (до 3 попыток): на слабой/загруженной
   машине healthcheck может не успеть за отведённые
   retries — повтор лишь дожидается уже стартующих контейнеров.
6. Печатает сводку: URL фронтенда/бэкенда, учётные данные администратора,
   команда для `--profile monitoring`.

## Проверка

- Чистая установка (пресеты 1 и 3) на пустом `.env`/чистых volume.
- Апгрейд 1 → 3 (повторный запуск с другим `--preset`, секреты сохранены).
- Рекомендация детекта соответствует реальному железу текущей машины.
- `bash -n install.sh`, `docker compose -f deploy/docker-compose.yml --env-file .env config -q`
  для всех сочетаний профилей — минимальная валидация без реального подъёма.

Полную установку на чистой машине/VM гоняют вручную (использование сети
для скачивания GGUF-моделей ~2,6–20,5 ГБ, GPU-хост для пресета 5) — вне
рамок автоматической проверки.
