Files
vidconf/install.sh
Max Ronzhin 693db6e774 install.sh, docs: pass --env-file explicitly to docker compose
docker compose определяет .env для подстановки ${VAR} по каталогу
compose-файла (deploy/), а не по текущей директории — repo-root .env,
который использует install.sh и вся документация, молча не подхватывался.
Это и была причина "WARN: LIVEKIT_API_KEY not set" на боевом сервере:
секреты были в .env, но compose их не видел и подставлял небезопасные
дефолты (см. таблицу разведки сервера).

Теперь ставшие обязательными ${VAR:?} в docker-compose.yml (см. предыдущий
коммит) без этого немедленно проваливали бы конфиг на любой из
документированных команд. Добавлен `--env-file .env`/"$ENV_FILE" ко всем
вызовам docker compose в install.sh и в командах из README/docs.

install.sh дополнительно: ensure_default (аналог ensure_secret без генерации
секрета) для новых не-секретных параметров nginx/coturn/livekit
(NGINX_SERVER_NAMES, NGINX_CERT_NAME, LIVEKIT_USE_EXTERNAL_IP,
LIVEKIT_NODE_IP, TURN_EXTERNAL_IP) — дефолты только для локальной
разработки, не перезаписывают значения, заданные вручную на боевом
сервере. Плюс вызов deploy/render-templates.sh перед сборкой/подъёмом
стека.
2026-07-25 21:58:03 +03:00

475 lines
25 KiB
Bash
Executable File
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.
#!/usr/bin/env bash
# Инсталлятор VidConf.
#
# Опросник из 5 пресетов поставки:
# 1) MVP-ядро (лобби, конференции, календарь, закреплённые, гости)
# 2) 1 + чат
# 3) 2 + AI-уровень «мин» (CPU, faster-whisper small + Qwen3.5-4B)
# 4) 2 + AI-уровень «средний» (CPU, faster-whisper medium + Qwen3.5-9B)
# 5) 2 + AI-уровень «макс» (GPU ОБЯЗАТЕЛЕН, faster-whisper large-v3 + Qwen3.5-35B-A3B)
# Модели/железо — ADR-004 (docs/architecture/adr/004-ai-tier-matrix.md),
# единственный источник истины. Запись конференций сюда НЕ входит — она
# появится в одной из следующих версий.
#
# Идемпотентность: повторный запуск = апгрейд/даунгрейд пресета на месте.
# Секреты и пользовательские правки .env НЕ теряются — значения пишутся
# точечно (set_env_var ниже), а не перезаписью всего файла. Seed-скрипт
# backend (scripts/seed.py) и бутстрап instance_settings (backend, lifespan)
# идемпотентны сами по себе — существующие настройки не перетираются.
#
# Использование:
# ./install.sh # интерактивный опросник
# ./install.sh --preset 3 # неинтерактивно, пресет 3
# ./install.sh --preset 3 --yes # без подтверждений (скрипты/CI)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
COMPOSE_FILE="deploy/docker-compose.yml"
ENV_FILE="$SCRIPT_DIR/.env"
ENV_EXAMPLE="$SCRIPT_DIR/.env.example"
VERSION_FILE="$SCRIPT_DIR/VERSION"
PRESET=""
ASSUME_YES=0
while [ $# -gt 0 ]; do
case "$1" in
--preset)
PRESET="$2"
shift 2
;;
--yes|-y)
ASSUME_YES=1
shift
;;
-h|--help)
grep -E '^#( |$)' "$0" | sed 's/^#//'
exit 0
;;
*)
echo "Неизвестный аргумент: $1" >&2
exit 1
;;
esac
done
# ---------------------------------------------------------------------------
# Утилиты работы с .env: точечное обновление ключа (не трогает остальные
# строки/комментарии/порядок) — идемпотентность и сохранность пользовательских
# правок при повторном запуске.
# ---------------------------------------------------------------------------
FRESH_ENV=0
# `.env.example` в git хранит ПЛЕЙСХОЛДЕРЫ секретов (`change-me...`,
# `POSTGRES_PASSWORD=vidconf` и т.п.) — не пустые строки. Если после
# `cp .env.example .env` оставить их как есть, `ensure_secret` ниже (генерация
# «только при отсутствии») их не тронет, и на проде осядут секреты из
# публичного репозитория. Поэтому при ПЕРВОМ создании .env явно очищаем
# управляемые секреты перед вызовом ensure_secret — на повторных запусках
# (файл уже существует) эта функция не трогает ничего, ensure_secret
# сохраняет и сгенерированные, и вручную заданные пользователем значения.
ensure_env_file() {
if [ ! -f "$ENV_FILE" ]; then
echo "[install] .env не найден — создаю из .env.example"
cp "$ENV_EXAMPLE" "$ENV_FILE"
FRESH_ENV=1
fi
}
get_env_var() {
key="$1"
[ -f "$ENV_FILE" ] || { echo ""; return 0; }
grep -E "^${key}=" "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2- || true
}
# Установить/заменить KEY=VALUE в .env; добавить строку, если ключа ещё нет.
# `sed -i.bak ... && rm -f *.bak` — переносимо между GNU sed (Linux) и BSD
# sed (macOS): оба принимают суффикс бэкапа сразу после -i, поведение без
# суффикса на BSD sed отличается (требует отдельного аргумента) — этим и
# отличаются друг от друга, суффикс убирает разницу.
set_env_var() {
key="$1"
value="$2"
esc_value=$(printf '%s' "$value" | sed -e 's/[&|\\]/\\&/g')
if grep -qE "^${key}=" "$ENV_FILE"; then
sed -i.bak "s|^${key}=.*|${key}=${esc_value}|" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
else
printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE"
fi
}
# Установить секрет ТОЛЬКО если он ещё не задан (не перезаписывать
# сгенерированные ранее/вручную заданные пользователем значения).
ensure_secret() {
key="$1"
bytes="${2:-32}"
current="$(get_env_var "$key")"
if [ -z "$current" ]; then
generated="$(openssl rand -hex "$bytes" 2>/dev/null || head -c "$bytes" /dev/urandom | od -An -tx1 | tr -d ' \n')"
set_env_var "$key" "$generated"
echo "[install] ${key}: сгенерирован новый секрет"
fi
}
# Установить дефолт для НЕ-секретного ключа только если он ещё не задан —
# аналог ensure_secret, но без генерации случайного значения. Не трогает
# значение, уже заданное вручную (реальный домен/IP боевого сервера).
ensure_default() {
key="$1"
default="$2"
if [ -z "$(get_env_var "$key")" ]; then
set_env_var "$key" "$default"
fi
}
# ---------------------------------------------------------------------------
# Автодетект железа (ADR-004, таблица требований).
# ---------------------------------------------------------------------------
detect_cpus() {
if command -v nproc >/dev/null 2>&1; then
nproc
elif command -v sysctl >/dev/null 2>&1; then
sysctl -n hw.ncpu 2>/dev/null || echo 0
else
echo 0
fi
}
detect_ram_mb() {
if command -v free >/dev/null 2>&1; then
free -m | awk '/^Mem:/{print $2}'
elif command -v sysctl >/dev/null 2>&1; then
bytes=$(sysctl -n hw.memsize 2>/dev/null || echo 0)
echo $((bytes / 1024 / 1024))
else
echo 0
fi
}
# Печатает "имя_gpu|vram_mb" через `|` (имя может содержать пробелы) или
# ничего, если nvidia-smi недоступен/GPU не найден.
detect_gpu() {
if command -v nvidia-smi >/dev/null 2>&1; then
nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits 2>/dev/null \
| head -1 \
| awk -F', *' '{print $1"|"$2}'
fi
}
HW_CPUS="$(detect_cpus)"
HW_RAM_MB="$(detect_ram_mb)"
HW_GPU_LINE="$(detect_gpu || true)"
HW_GPU_NAME="${HW_GPU_LINE%%|*}"
HW_VRAM_MB="${HW_GPU_LINE##*|}"
if [ "$HW_GPU_LINE" = "" ] || [ "$HW_GPU_NAME" = "$HW_VRAM_MB" ]; then
HW_GPU_NAME=""
HW_VRAM_MB=""
fi
echo "[install] Обнаружено железо: CPU=${HW_CPUS} vCPU, RAM=${HW_RAM_MB} МБ, GPU=${HW_GPU_NAME:-нет}${HW_VRAM_MB:+, VRAM=${HW_VRAM_MB} МБ}"
# ---------------------------------------------------------------------------
# Рекомендация пресета по обнаруженному железу (таблица ADR-004).
# ---------------------------------------------------------------------------
recommend_preset() {
if [ -n "$HW_VRAM_MB" ] && [ "$HW_VRAM_MB" -ge 16000 ] 2>/dev/null \
&& [ "$HW_RAM_MB" -ge 65536 ] 2>/dev/null && [ "$HW_CPUS" -ge 16 ] 2>/dev/null; then
echo 5
elif [ "$HW_RAM_MB" -ge 32768 ] 2>/dev/null && [ "$HW_CPUS" -ge 12 ] 2>/dev/null; then
echo 4
elif [ "$HW_RAM_MB" -ge 16384 ] 2>/dev/null && [ "$HW_CPUS" -ge 8 ] 2>/dev/null; then
echo 3
else
echo 2
fi
}
RECOMMENDED_PRESET="$(recommend_preset)"
# ---------------------------------------------------------------------------
# Опросник (интерактивный, если --preset не передан).
# ---------------------------------------------------------------------------
print_menu() {
cat <<EOF
Выберите пресет поставки VidConf:
1) MVP-ядро — лобби, конференции, календарь, закреплённые, гости
2) 1 + чат — то же + групповой чат конференции
3) 2 + AI «мин» — CPU: faster-whisper small + Qwen3.5-4B (8 vCPU / 16 ГБ RAM / 100 ГБ)
4) 2 + AI «средний» — CPU: faster-whisper medium + Qwen3.5-9B (12-16 vCPU / 32 ГБ RAM / 150 ГБ)
5) 2 + AI «макс» — GPU ОБЯЗАТЕЛЕН: faster-whisper large-v3 + Qwen3.5-35B-A3B (16+ vCPU / 64 ГБ RAM / GPU NVIDIA ≥16 ГБ VRAM / 250 ГБ)
Рекомендация по обнаруженному железу: пресет ${RECOMMENDED_PRESET}.
Подробности требований — docs/architecture/adr/004-ai-tier-matrix.md.
EOF
}
if [ -z "$PRESET" ]; then
print_menu
printf "Номер пресета [%s]: " "$RECOMMENDED_PRESET"
read -r answer
PRESET="${answer:-$RECOMMENDED_PRESET}"
fi
case "$PRESET" in
1|2|3|4|5) ;;
*)
echo "Пресет должен быть числом от 1 до 5 (получено: '${PRESET}')" >&2
exit 1
;;
esac
# Пресет 5 (max) требует GPU ОБЯЗАТЕЛЬНО (ADR-004) — если детект его не
# нашёл, явно предупреждаем и просим подтверждение (можно ставить заранее,
# до подключения GPU-хоста, но AI-контейнеры не поднимутся здоровыми).
if [ "$PRESET" = "5" ] && { [ -z "$HW_VRAM_MB" ] || [ "$HW_VRAM_MB" -lt 16000 ]; }; then
echo "[install] ВНИМАНИЕ: пресет 5 требует NVIDIA GPU ≥16 ГБ VRAM — не обнаружен (или недостаточно VRAM)." >&2
if [ "$ASSUME_YES" != "1" ]; then
printf "Продолжить всё равно? [y/N]: "
read -r confirm
case "$confirm" in
y|Y|yes|Yes) ;;
*) echo "Отменено."; exit 1 ;;
esac
fi
fi
# ---------------------------------------------------------------------------
# Заполнение .env: секреты (только при отсутствии), железо, версия, модели,
# профили compose — точечно, см. set_env_var.
# ---------------------------------------------------------------------------
ensure_env_file
if [ "$FRESH_ENV" = "1" ]; then
for key in JWT_SECRET POSTGRES_PASSWORD TURN_STATIC_AUTH_SECRET \
LIVEKIT_API_SECRET GRAFANA_ADMIN_PASSWORD SEED_ADMIN_PASSWORD; do
set_env_var "$key" ""
done
fi
ensure_secret JWT_SECRET 32
ensure_secret POSTGRES_PASSWORD 16
# DATABASE_URL хранит пароль ВНУТРИ строки подключения — пересобираем её из
# POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB на каждом запуске (не только
# при первом), иначе после регенерации POSTGRES_PASSWORD backend/worker
# продолжили бы стучаться со старым паролем из .env.example и не
# подключились бы к БД. Хост — `postgres` (имя сервиса в docker-сети
# compose), не `localhost` (тот годится только для запуска backend на хосте
# вне контейнера, см. docs/deploy/dev-setup.md).
set_env_var DATABASE_URL "postgresql+asyncpg://$(get_env_var POSTGRES_USER):$(get_env_var POSTGRES_PASSWORD)@postgres:5432/$(get_env_var POSTGRES_DB)"
ensure_secret TURN_STATIC_AUTH_SECRET 24
ensure_secret LIVEKIT_API_SECRET 32
ensure_secret GRAFANA_ADMIN_PASSWORD 16
# SEED_ADMIN_PASSWORD — единственный секрет с человекочитаемым дефолтом
# (сообщается пользователю в конце) вместо случайной генерации: это учётные
# данные, которыми оператор будет логиниться вручную сразу после установки.
if [ -z "$(get_env_var SEED_ADMIN_PASSWORD)" ]; then
generated_admin_pw="$(openssl rand -hex 8 2>/dev/null || head -c 8 /dev/urandom | od -An -tx1 | tr -d ' \n')"
set_env_var SEED_ADMIN_PASSWORD "$generated_admin_pw"
fi
set_env_var VIDCONF_VERSION "$(cat "$VERSION_FILE")"
set_env_var HW_CPUS "$HW_CPUS"
set_env_var HW_RAM_MB "$HW_RAM_MB"
set_env_var HW_GPU_NAME "$HW_GPU_NAME"
set_env_var HW_VRAM_MB "$HW_VRAM_MB"
# nginx/coturn/livekit — дефолты для локальной разработки (localhost/127.0.0.1),
# только если ещё не заданы. На боевом сервере ДО первого запуска install.sh
# задайте в .env реальные значения (домены, внешний IP) — ensure_default их
# не перезапишет.
ensure_default NGINX_SERVER_NAMES "localhost"
ensure_default NGINX_CERT_NAME "localhost"
ensure_default LIVEKIT_USE_EXTERNAL_IP "false"
ensure_default LIVEKIT_NODE_IP "127.0.0.1"
ensure_default TURN_EXTERNAL_IP "127.0.0.1"
# Профили compose и модели по пресету. GPU-профили — ТОЛЬКО для пресета 5
# (max): в текущей матрице уровней (backend/services/ai_tiers.py, ADR-004)
# уровень `medium` фиксированно CPU-only (GPU для него — ручная настройка
# администратора вне детекта), поэтому отдельного GPU-варианта для пресета 4
# нет — не путать с прозой ADR-004 про «опциональный GPU для medium».
#
# BOOTSTRAP_CHAT_ENABLED/BOOTSTRAP_TRANSCRIPTION_ENABLED/BOOTSTRAP_AI_LEVEL:
# без них
# бутстрап настроек инстанса (backend, lifespan) брал ВСЁ enabled: true из
# config/plugins.yaml независимо от пресета, и на пресетах 1/2 (без AI)
# сеанс конференции навсегда зависал в очереди transcribe, которую некому
# обслуживать. Значения читает backend при первом бутстрапе И скрипт
# scripts.apply_preset_settings (принудительное применение, см. ниже).
case "$PRESET" in
1)
set_env_var COMPOSE_PROFILES "media"
set_env_var BOOTSTRAP_CHAT_ENABLED "false"
set_env_var BOOTSTRAP_TRANSCRIPTION_ENABLED "false"
set_env_var BOOTSTRAP_AI_LEVEL "min"
;;
2)
set_env_var COMPOSE_PROFILES "media"
set_env_var BOOTSTRAP_CHAT_ENABLED "true"
set_env_var BOOTSTRAP_TRANSCRIPTION_ENABLED "false"
set_env_var BOOTSTRAP_AI_LEVEL "min"
;;
3)
set_env_var COMPOSE_PROFILES "media,transcribe,llm"
set_env_var WHISPER_MODEL "small"
set_env_var LLM_MODEL_FILE "qwen3.5-4b-instruct-q4_k_m.gguf"
set_env_var LLM_MODEL_URL "https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/Qwen3.5-4B-Q4_K_M.gguf"
set_env_var LLM_MODEL_MIN_SIZE "2000000000"
set_env_var LLM_TOKENIZER_FILE "qwen3.5-4b-instruct.tokenizer.json"
set_env_var LLM_TOKENIZER_URL "https://huggingface.co/Qwen/Qwen3.5-4B/resolve/main/tokenizer.json"
set_env_var BOOTSTRAP_CHAT_ENABLED "true"
set_env_var BOOTSTRAP_TRANSCRIPTION_ENABLED "true"
set_env_var BOOTSTRAP_AI_LEVEL "min"
;;
4)
set_env_var COMPOSE_PROFILES "media,transcribe,llm"
set_env_var WHISPER_MODEL "medium"
set_env_var LLM_MODEL_FILE "qwen3.5-9b-instruct-q4_k_m.gguf"
set_env_var LLM_MODEL_URL "https://huggingface.co/unsloth/Qwen3.5-9B-GGUF/resolve/main/Qwen3.5-9B-Q4_K_M.gguf"
set_env_var LLM_MODEL_MIN_SIZE "5000000000"
set_env_var LLM_TOKENIZER_FILE "qwen3.5-9b-instruct.tokenizer.json"
set_env_var LLM_TOKENIZER_URL "https://huggingface.co/Qwen/Qwen3.5-9B/resolve/main/tokenizer.json"
set_env_var BOOTSTRAP_CHAT_ENABLED "true"
set_env_var BOOTSTRAP_TRANSCRIPTION_ENABLED "true"
set_env_var BOOTSTRAP_AI_LEVEL "medium"
;;
5)
set_env_var COMPOSE_PROFILES "media,transcribe-gpu,llm-gpu"
set_env_var WHISPER_MODEL "large-v3"
set_env_var LLM_MODEL_FILE "qwen3.5-35b-a3b-instruct-q4_k_m.gguf"
set_env_var LLM_MODEL_URL "https://huggingface.co/unsloth/Qwen3.5-35B-A3B-GGUF/resolve/main/Qwen3.5-35B-A3B-Q4_K_M.gguf"
set_env_var LLM_MODEL_MIN_SIZE "20000000000"
set_env_var LLM_TOKENIZER_FILE "qwen3.5-35b-a3b-instruct.tokenizer.json"
set_env_var LLM_TOKENIZER_URL "https://huggingface.co/Qwen/Qwen3.5-35B-A3B/resolve/main/tokenizer.json"
set_env_var BOOTSTRAP_CHAT_ENABLED "true"
set_env_var BOOTSTRAP_TRANSCRIPTION_ENABLED "true"
set_env_var BOOTSTRAP_AI_LEVEL "max"
;;
esac
echo "[install] .env обновлён (пресет ${PRESET}, COMPOSE_PROFILES=$(get_env_var COMPOSE_PROFILES))"
# ---------------------------------------------------------------------------
# Подъём стека и миграции/seed.
# ---------------------------------------------------------------------------
# `COMPOSE_PROFILES` в .env НЕ подхватывается автоматически командой
# `docker compose ... up` в этой версии compose (проверено на v5.3.1) —
# нужен явный повторяемый флаг `--profile`, иначе профильные сервисы
# (livekit/coturn для media, транскрибация/LLM для остальных пресетов)
# молча не поднимаются, хотя .env выглядит корректно заполненным.
PROFILE_ARGS=()
IFS=',' read -r -a _profiles <<< "$(get_env_var COMPOSE_PROFILES)"
for _p in "${_profiles[@]}"; do
[ -n "$_p" ] && PROFILE_ARGS+=(--profile "$_p")
done
echo "[install] Рендерю coturn/livekit из шаблонов (deploy/render-templates.sh)"
"$SCRIPT_DIR/deploy/render-templates.sh"
echo "[install] docker compose pull (лучше сразу, но необязательный шаг — образы, которых нет в реестре под текущим тегом, пропускаются)"
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" "${PROFILE_ARGS[@]}" pull --ignore-buildable 2>/dev/null || true
echo "[install] docker compose build"
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" "${PROFILE_ARGS[@]}" build
echo "[install] Поднимаю postgres/redis (--wait) — нужны для миграций до старта backend"
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --wait postgres redis
# backend.lifespan бутстрапит instance_settings при КАЖДОМ старте приложения,
# поэтому миграции обязаны применяться ДО первого запуска backend/worker —
# иначе на чистой БД backend падает на "relation instance_settings does not
# exist" и healthcheck (--wait) никогда не проходит. `compose run` запускает
# одноразовый контейнер с нужной командой, не поднимая uvicorn/lifespan.
echo "[install] Применяю миграции Alembic и seed (админ/справочники) — до старта backend"
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" run --rm backend uv run alembic upgrade head
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" run --rm backend uv run python -m scripts.seed
echo "[install] docker compose up -d --wait (backend/worker/nginx и остальные сервисы профиля)"
# Первый старт backend/worker включает `uv run` (синхронизация окружения +
# компиляция байткода) — на слабой/загруженной машине healthcheck может не
# успеть пройти за отведённые retries, и `--wait` вернёт "container is
# unhealthy", хотя сервис через несколько секунд становится healthy. Команда
# идемпотентна, поэтому повторяем её несколько раз: повтор лишь дожидается
# уже стартующих контейнеров, ничего не пересоздавая.
UP_ATTEMPTS=3
for attempt in $(seq 1 "$UP_ATTEMPTS"); do
if docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" "${PROFILE_ARGS[@]}" up -d --wait; then
break
fi
if [ "$attempt" -eq "$UP_ATTEMPTS" ]; then
echo "[install] ОШИБКА: сервисы не стали healthy за $UP_ATTEMPTS попыток." >&2
echo "[install] Проверьте логи: docker compose -f $COMPOSE_FILE --env-file $ENV_FILE logs backend worker" >&2
exit 1
fi
echo "[install] Не все сервисы healthy (попытка $attempt/$UP_ATTEMPTS) — жду 10с и повторяю..."
sleep 10
done
# ---------------------------------------------------------------------------
# Настройки модулей инстанса (чат/AI-уровень) под пресет — только на живой
# установке (fix/installer-preset-modules): при СВЕЖЕМ .env (FRESH_ENV=1)
# они уже применятся сами при бутстрапе backend (lifespan читает
# BOOTSTRAP_* из .env), повторно накатывать не нужно. При ПОВТОРНОМ запуске
# .env уже существовал ДО этого запуска — настройки уже в БД и могли быть
# вручную изменены администратором, поэтому перезаписываем их только с
# явным подтверждением (или --yes для неинтерактивного режима).
# ---------------------------------------------------------------------------
if [ "$FRESH_ENV" != "1" ]; then
APPLY_PRESET_SETTINGS=0
if [ "$ASSUME_YES" = "1" ]; then
APPLY_PRESET_SETTINGS=1
else
printf "Обновить настройки модулей инстанса (чат, уровень AI) под пресет %s? [Y/n]: " "$PRESET"
read -r update_settings_answer
case "$update_settings_answer" in
n|N|no|No) APPLY_PRESET_SETTINGS=0 ;;
*) APPLY_PRESET_SETTINGS=1 ;;
esac
fi
if [ "$APPLY_PRESET_SETTINGS" = "1" ]; then
echo "[install] Применяю настройки модулей инстанса под пресет ${PRESET}"
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec -T backend uv run python -m scripts.apply_preset_settings --force
else
echo "[install] Настройки модулей инстанса НЕ изменены — сохранены ручные правки администратора"
fi
fi
VERSION_VALUE="$(cat "$VERSION_FILE")"
ADMIN_EMAIL="$(get_env_var SEED_ADMIN_EMAIL)"
ADMIN_PASSWORD="$(get_env_var SEED_ADMIN_PASSWORD)"
GRAFANA_USER="$(get_env_var GRAFANA_ADMIN_USER)"
cat <<EOF
============================================================
VidConf ${VERSION_VALUE} установлен (пресет ${PRESET}).
============================================================
Backend: http://localhost/api/health
Frontend: http://localhost/
Админ: ${ADMIN_EMAIL:-см. .env SEED_ADMIN_EMAIL} / ${ADMIN_PASSWORD:-см. .env SEED_ADMIN_PASSWORD}
(смените пароль сразу после первого входа)
Мониторинг (Grafana/Prometheus) — отдельный профиль compose, не входит
в базовые пресеты:
docker compose -f ${COMPOSE_FILE} --env-file ${ENV_FILE} --profile monitoring up -d
Grafana: http://localhost:3001 (${GRAFANA_USER:-admin} / см. .env GRAFANA_ADMIN_PASSWORD)
Настройки модулей инстанса (чат, уровень AI) синхронизированы под
пресет ${PRESET} (админка → Настройки, при желании переключите вручную).
Повторный запуск ./install.sh с другим --preset — апгрейд/даунгрейд
на месте (модели докачиваются, .env обновляется точечно, секреты и
пользовательские правки сохраняются; настройки модулей обновляются
только с вашего подтверждения или флагом --yes).
============================================================
EOF