# FitCRM -- Руководство по развертыванию и DevOps

Полное руководство по развертыванию, настройке инфраструктуры и эксплуатации CRM-системы для фитнес-клубов FitCRM.

---

## Содержание

1. [Требования к окружению](#1-требования-к-окружению)
2. [Быстрый старт](#2-быстрый-старт)
3. [Docker-сервисы](#3-docker-сервисы)
4. [Настройка базы данных](#4-настройка-базы-данных)
5. [Конфигурация окружения (.env)](#5-конфигурация-окружения-env)
6. [Запуск в режиме разработки](#6-запуск-в-режиме-разработки)
7. [Сборка для production](#7-сборка-для-production)
8. [CI/CD пайплайн](#8-cicd-пайплайн)
9. [SSL/TLS и безопасность](#9-ssltls-и-безопасность)
10. [Резервное копирование и восстановление](#10-резервное-копирование-и-восстановление)
11. [Масштабирование](#11-масштабирование)
12. [Устранение неполадок](#12-устранение-неполадок)

---

## 1. Требования к окружению

### Обязательные зависимости

| Компонент        | Минимальная версия | Рекомендуемая версия | Примечание                          |
|------------------|--------------------|----------------------|-------------------------------------|
| Node.js          | 22.0.0             | 22 LTS               | Указано в `engines` в package.json  |
| pnpm             | 9.14.0             | 9.x                  | Указано в `packageManager`          |
| Docker           | 24.0               | 27.x                 | Для запуска инфраструктурных сервисов |
| Docker Compose   | 2.20               | 2.30+                | Compose V2 (встроен в Docker Desktop) |
| Git              | 2.39               | 2.43+                | Для клонирования и worktrees        |

### Системные требования

**Для разработки (development):**
- CPU: 4 ядра
- RAM: 8 ГБ (PostgreSQL + Redis + coturn + Node.js)
- Диск: 10 ГБ свободного места

**Для production (один сервер):**
- CPU: 8 ядер
- RAM: 16 ГБ
- Диск: 50 ГБ SSD (данные PostgreSQL, логи, файлы S3)
- Сеть: фиксированный IP-адрес (обязателен для STUN/TURN)

### Установка зависимостей

**Node.js 22 LTS (через nvm):**

```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
node --version  # v22.x.x
```

**pnpm 9:**

```bash
corepack enable
corepack prepare pnpm@9.14.0 --activate
pnpm --version  # 9.14.0
```

**Docker и Docker Compose:**

```bash
# Ubuntu/Debian
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

# Проверка
docker --version
docker compose version
```

---

## 2. Быстрый старт

Пошаговая инструкция от клонирования репозитория до работающей системы.

### Шаг 1. Клонирование репозитория

```bash
git clone git@github.com:<org>/fitcrm.git
cd fitcrm
```

### Шаг 2. Настройка переменных окружения

```bash
cp .env.example .env
```

Откройте файл `.env` и задайте значения для обязательных переменных (подробности -- в разделе [5. Конфигурация окружения](#5-конфигурация-окружения-env)):

```bash
# Минимум для локальной разработки:
POSTGRES_PASSWORD=my_secure_password_123
REDIS_PASSWORD=my_redis_password_456
TURN_SECRET=my_turn_secret_789
JWT_SECRET=my_jwt_secret_at_least_32_characters_long
JWT_REFRESH_SECRET=my_jwt_refresh_secret_at_least_32_chars
```

### Шаг 3. Запуск инфраструктуры через Docker Compose

```bash
docker compose -f infra/docker/docker-compose.yml up -d
```

Дождитесь готовности сервисов (healthcheck):

```bash
docker compose -f infra/docker/docker-compose.yml ps
```

Все три сервиса должны показывать статус `healthy` или `running`.

### Шаг 4. Установка зависимостей

```bash
pnpm install
```

### Шаг 5. Настройка базы данных

```bash
# Генерация Prisma-клиента
pnpm --filter api prisma:generate

# Применение миграций (создание таблиц)
pnpm --filter api prisma:migrate

# Заполнение начальными данными (опционально)
pnpm --filter api prisma:seed
```

### Шаг 6. Запуск в режиме разработки

```bash
# Все приложения одновременно (через Turborepo)
pnpm dev

# Или отдельные сервисы:
pnpm --filter api dev              # Backend API        (порт 3000)
pnpm --filter web-admin dev        # Веб-рецепция       (порт 3001)
pnpm --filter web-club-admin dev   # Админка клуба      (порт 3002)
pnpm --filter web-platform-admin dev  # Суперадмин      (порт 3003)
```

### Шаг 7. Проверка работоспособности

```bash
# API
curl http://localhost:3000/health

# Или откройте в браузере:
# http://localhost:3001  - Веб-рецепция
# http://localhost:3002  - Админка клуба
# http://localhost:3003  - Суперадминистратор
```

---

## 3. Docker-сервисы

### Обзор архитектуры

Инфраструктура FitCRM состоит из трех сервисов, объединенных в сеть `fitcrm-network`:

```
                    fitcrm-network (bridge)
                    ========================
                    |         |           |
              +---------+ +-------+ +---------+
              |PostgreSQL| | Redis | | coturn  |
              |  :5432   | | :6379 | | :3478   |
              +---------+ +-------+ | :5349   |
                                    +---------+
```

### Файлы конфигурации

| Файл | Назначение |
|------|------------|
| `infra/docker/docker-compose.yml` | Основной Compose-файл (development) |
| `infra/docker/docker-compose.prod.yml` | Production override (лимиты ресурсов, сборка приложений) |
| `infra/docker/Dockerfile.api` | Multi-stage сборка NestJS API |
| `infra/docker/Dockerfile.web` | Multi-stage сборка web-admin (Next.js) |
| `infra/docker/Dockerfile.club-admin` | Multi-stage сборка web-club-admin (Next.js) |
| `infra/docker/Dockerfile.platform-admin` | Multi-stage сборка web-platform-admin (Next.js) |
| `infra/stun-turn/turnserver.conf` | Конфигурация coturn STUN/TURN сервера |

### 3.1. PostgreSQL 17

```yaml
# Образ: postgres:17-alpine
# Контейнер: fitcrm-postgres
# Порт: 5432 (настраивается через POSTGRES_PORT)
# Volume: postgres_data:/var/lib/postgresql/data
```

**Healthcheck:** `pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}` каждые 10 секунд, 5 попыток.

**Особенности:**
- Используется с Row-Level Security (RLS) для multi-tenancy изоляции клубов
- Схема содержит 34 таблицы и 12 перечислений (enums)
- Рекомендуемые параметры для production -- см. раздел [Масштабирование](#11-масштабирование)

### 3.2. Redis 8

```yaml
# Образ: redis:8-alpine
# Контейнер: fitcrm-redis
# Порт: 6379 (настраивается через REDIS_PORT)
# Volume: redis_data:/data
# Режим: appendonly yes (AOF-персистенция)
```

**Healthcheck:** `redis-cli -a ${REDIS_PASSWORD} ping` каждые 10 секунд, 5 попыток.

**Использование в FitCRM:**
- Кэширование данных (сессии, частые запросы)
- Очереди задач BullMQ (push-уведомления, синхронизация 1С, webhooks)
- Rate limiting (скользящее окно per-club)
- Хранение текущего состояния SIP-сессий

**BullMQ очереди:**

Три именованных очереди зарегистрированы в `QueueModule`:

| Очередь | Назначение | Retry-стратегия |
|---------|-----------|----------------|
| `webhook-delivery` | Доставка webhook-событий (HMAC-SHA256) | 3 попытки: 5с → 30с → 5мин |
| `push-notification` | Отправка push через FCM/APNs | 3 попытки с экспоненциальной задержкой |
| `sync-1c` | Синхронизация с 1С:Фитнес | 3 попытки с экспоненциальной задержкой |

Мониторинг очередей:

```bash
# Проверка количества задач в очередях
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" KEYS "bull:*"

# Просмотр задач в конкретной очереди
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" LLEN "bull:webhook-delivery:wait"
```

### 3.3. coturn (STUN/TURN)

```yaml
# Образ: coturn/coturn:latest
# Контейнер: fitcrm-coturn
# Порты:
#   3478/udp, 3478/tcp  - STUN/TURN основной
#   5349/udp, 5349/tcp  - STUN/TURN over TLS
#   49152-65535/udp      - Media relay (RTP/SRTP)
```

**Назначение:** NAT traversal для встроенного SIP-клиента. Обеспечивает прохождение VoIP-звонков через NAT/файрволлы.

**Конфигурация** (`infra/stun-turn/turnserver.conf`):
- Аутентификация: `use-auth-secret` (shared secret передается из env)
- Квоты: 12 аллокаций на пользователя, 1200 суммарно
- Безопасность: запрещены relay на приватные подсети (10.x, 172.16-31.x, 192.168.x)
- Время жизни канала: 600 секунд
- Время жизни permissions: 300 секунд

**Важно:** Для production coturn требует публичного IP-адреса. Добавьте в `turnserver.conf`:

```
external-ip=<PUBLIC_IP>/<PRIVATE_IP>
```

### Управление Docker-сервисами

```bash
# Запуск
docker compose -f infra/docker/docker-compose.yml up -d

# Просмотр статуса
docker compose -f infra/docker/docker-compose.yml ps

# Логи конкретного сервиса
docker compose -f infra/docker/docker-compose.yml logs -f postgres
docker compose -f infra/docker/docker-compose.yml logs -f redis
docker compose -f infra/docker/docker-compose.yml logs -f coturn

# Остановка
docker compose -f infra/docker/docker-compose.yml down

# Остановка с удалением данных (ОСТОРОЖНО!)
docker compose -f infra/docker/docker-compose.yml down -v
```

---

## 4. Настройка базы данных

### 4.1. Prisma ORM

FitCRM использует Prisma 6 как ORM. Схема БД расположена в `apps/api/prisma/schema.prisma`.

**Основные команды:**

```bash
# Генерация Prisma-клиента (TypeScript типы для работы с БД)
pnpm --filter api prisma:generate

# Применение миграций к БД
pnpm --filter api prisma:migrate

# Отправка схемы напрямую в БД (без миграции, для прототипирования)
# Используется в CI для тестов
npx prisma db push --skip-generate

# Заполнение начальными данными
pnpm --filter api prisma:seed

# Открытие визуального интерфейса Prisma Studio
pnpm --filter api prisma:studio
```

### 4.2. Row-Level Security (RLS)

FitCRM использует PostgreSQL RLS для изоляции данных клубов (multi-tenancy). Каждая таблица с данными клуба содержит столбец `club_id`. RLS-политики применяются автоматически через `TenantMiddleware` в NestJS.

**Принцип работы:**

1. Пользователь авторизуется -- из JWT-токена извлекается `clubId`
2. Middleware выполняет `SET app.club_id = <clubId>` для текущего соединения
3. RLS-политика PostgreSQL автоматически фильтрует данные:

```sql
ALTER TABLE clients ENABLE ROW LEVEL SECURITY;
CREATE POLICY club_isolation ON clients
  USING (club_id = current_setting('app.club_id')::uuid);
```

**Важно:** Никогда не отключайте RLS в production. Для административных операций суперадминистратора используется отдельная роль PostgreSQL с `BYPASSRLS`.

### 4.3. Миграции в production

```bash
# Создание новой миграции после изменения schema.prisma
cd apps/api
npx prisma migrate dev --name описание_изменения

# Применение миграций в production
npx prisma migrate deploy
```

**Рекомендации:**
- Всегда проверяйте SQL миграций перед применением в production (`apps/api/prisma/migrations/`)
- Делайте бэкап БД перед каждой миграцией (см. раздел [10. Резервное копирование](#10-резервное-копирование-и-восстановление))
- Не используйте `prisma db push` в production

### 4.4. Подключение к БД напрямую

```bash
# Через Docker
docker exec -it fitcrm-postgres psql -U fitcrm -d fitcrm

# Из хоста (требуется psql)
psql postgresql://fitcrm:your_password@localhost:5432/fitcrm
```

Полезные SQL-запросы для диагностики:

```sql
-- Проверка RLS-политик
SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual
FROM pg_policies ORDER BY tablename;

-- Размер таблиц
SELECT relname AS table, pg_size_pretty(pg_total_relation_size(relid)) AS size
FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC;

-- Активные соединения
SELECT count(*) FROM pg_stat_activity WHERE datname = 'fitcrm';

-- Долгие запросы (более 5 секунд)
SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 seconds'
  AND state != 'idle';
```

---

## 5. Конфигурация окружения (.env)

### Полный список переменных

Скопируйте `.env.example` в `.env` и заполните обязательные значения.

#### PostgreSQL

| Переменная | По умолчанию | Обязательна | Описание |
|------------|-------------|-------------|----------|
| `POSTGRES_USER` | `fitcrm` | Нет | Имя пользователя PostgreSQL |
| `POSTGRES_PASSWORD` | -- | **Да** | Пароль PostgreSQL |
| `POSTGRES_DB` | `fitcrm` | Нет | Имя базы данных |
| `POSTGRES_HOST` | `localhost` | Нет | Хост PostgreSQL (`postgres` в Docker-сети) |
| `POSTGRES_PORT` | `5432` | Нет | Порт PostgreSQL |
| `DATABASE_URL` | -- | **Да** | Полный URL подключения Prisma |

**Формат DATABASE_URL:**

```
postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@<POSTGRES_HOST>:<POSTGRES_PORT>/<POSTGRES_DB>?schema=public
```

#### Redis

| Переменная | По умолчанию | Обязательна | Описание |
|------------|-------------|-------------|----------|
| `REDIS_HOST` | `localhost` | Нет | Хост Redis (`redis` в Docker-сети) |
| `REDIS_PORT` | `6379` | Нет | Порт Redis |
| `REDIS_PASSWORD` | -- | **Да** | Пароль Redis |

#### STUN/TURN (coturn)

| Переменная | По умолчанию | Обязательна | Описание |
|------------|-------------|-------------|----------|
| `TURN_REALM` | `fitcrm.local` | Нет | Realm для STUN/TURN сервера |
| `TURN_SECRET` | -- | **Да** | Shared secret для аутентификации TURN |

#### JWT-аутентификация

| Переменная | По умолчанию | Обязательна | Описание |
|------------|-------------|-------------|----------|
| `JWT_SECRET` | -- | **Да** | Секрет для подписи access-токенов (мин. 32 символа) |
| `JWT_REFRESH_SECRET` | -- | **Да** | Секрет для подписи refresh-токенов (мин. 32 символа) |
| `JWT_EXPIRES_IN` | `15m` | Нет | Время жизни access-токена |
| `JWT_REFRESH_EXPIRES_IN` | `30d` | Нет | Время жизни refresh-токена |

#### API-сервер

| Переменная | По умолчанию | Обязательна | Описание |
|------------|-------------|-------------|----------|
| `PORT` | `3000` | Нет | Порт API-сервера |
| `NODE_ENV` | `development` | Нет | Окружение: `development`, `staging`, `production` |

#### S3-хранилище (опционально)

| Переменная | По умолчанию | Обязательна | Описание |
|------------|-------------|-------------|----------|
| `S3_ENDPOINT` | -- | Нет | URL S3-совместимого хранилища (MinIO, AWS, Yandex Cloud) |
| `S3_BUCKET` | `fitcrm` | Нет | Имя бакета |
| `S3_ACCESS_KEY` | -- | Нет | Access Key ID |
| `S3_SECRET_KEY` | -- | Нет | Secret Access Key |
| `S3_REGION` | `us-east-1` | Нет | Регион |

### Пример готового .env для разработки

```bash
# PostgreSQL
POSTGRES_USER=fitcrm
POSTGRES_PASSWORD=dev_password_123
POSTGRES_DB=fitcrm
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
DATABASE_URL=postgresql://fitcrm:dev_password_123@localhost:5432/fitcrm?schema=public

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=redis_dev_password_456

# STUN/TURN
TURN_REALM=fitcrm.local
TURN_SECRET=turn_dev_secret_789

# JWT
JWT_SECRET=dev_jwt_secret_must_be_at_least_32_chars
JWT_REFRESH_SECRET=dev_jwt_refresh_must_be_at_least_32_chars
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=30d

# API
PORT=3000
NODE_ENV=development
```

### Генерация безопасных секретов

```bash
# JWT_SECRET и JWT_REFRESH_SECRET (64 символа)
openssl rand -base64 48

# REDIS_PASSWORD
openssl rand -base64 32

# POSTGRES_PASSWORD
openssl rand -base64 24

# TURN_SECRET
openssl rand -hex 32
```

### Различия по окружениям

| Параметр | Development | Staging | Production |
|----------|-------------|---------|------------|
| `NODE_ENV` | `development` | `staging` | `production` |
| `JWT_EXPIRES_IN` | `15m` | `15m` | `15m` |
| `POSTGRES_HOST` | `localhost` | `postgres` | `postgres` или managed DB |
| `REDIS_HOST` | `localhost` | `redis` | `redis` или managed Redis |
| Порты наружу | Все открыты | Только API | Только через reverse proxy |

---

## 6. Запуск в режиме разработки

### Полный запуск (все сервисы)

```bash
# 1. Инфраструктура
docker compose -f infra/docker/docker-compose.yml up -d

# 2. Зависимости
pnpm install

# 3. БД
pnpm --filter api prisma:generate
pnpm --filter api prisma:migrate
pnpm --filter api prisma:seed   # опционально

# 4. Все приложения
pnpm dev
```

Turborepo запустит все workspace-приложения параллельно. Приложения будут доступны на:

| Приложение | URL | Описание |
|------------|-----|----------|
| API | http://localhost:3000 | NestJS Backend (REST API) |
| web-admin | http://localhost:3001 | Рецепция + аналитика |
| web-club-admin | http://localhost:3002 | Панель администратора клуба |
| web-platform-admin | http://localhost:3003 | Панель суперадминистратора |

### Запуск отдельных приложений

```bash
# Только Backend API (NestJS watch mode)
pnpm --filter api dev

# Только веб-рецепция
pnpm --filter web-admin dev

# Только админка клуба
pnpm --filter web-club-admin dev

# Только суперадмин-панель
pnpm --filter web-platform-admin dev
```

### Мобильное приложение (React Native)

```bash
# Запуск Metro bundler
pnpm --filter mobile start

# iOS (требуется macOS + Xcode)
pnpm --filter mobile ios

# Android (требуется Android Studio + SDK)
pnpm --filter mobile android
```

### Полезные команды для разработки

```bash
# Линтинг всего проекта
pnpm lint

# Форматирование кода (Prettier)
pnpm format

# Запуск тестов
pnpm test

# Тесты только backend
pnpm --filter api test

# E2E тесты backend
pnpm --filter api test:e2e

# Prisma Studio (визуальный редактор БД)
pnpm --filter api prisma:studio
```

### Горячая перезагрузка

- **NestJS API:** автоматическая перезагрузка при изменении `.ts` файлов (watch mode)
- **Next.js приложения:** Fast Refresh при изменении компонентов
- **React Native:** Metro hot reload

---

## 7. Сборка для production

### 7.1. Сборка через Turborepo

```bash
# Сборка всех приложений и пакетов
pnpm build
```

Turborepo выполнит сборку в правильном порядке с учетом зависимостей между workspace-пакетами:
1. `packages/shared-types` -- общие TypeScript типы
2. `packages/validators` -- Zod-схемы валидации
3. `packages/api-client` -- сгенерированный API-клиент
4. `apps/api` -- NestJS backend
5. `apps/web-admin`, `apps/web-club-admin`, `apps/web-platform-admin` -- Next.js приложения (параллельно)

### 7.2. Docker-образы

Каждое приложение имеет отдельный Dockerfile с multi-stage сборкой для минимизации размера образа.

**Сборка всех образов:**

```bash
# Из корня проекта
docker build -f infra/docker/Dockerfile.api -t fitcrm-api:latest .
docker build -f infra/docker/Dockerfile.web -t fitcrm-web-admin:latest .
docker build -f infra/docker/Dockerfile.club-admin -t fitcrm-web-club-admin:latest .
docker build -f infra/docker/Dockerfile.platform-admin -t fitcrm-web-platform-admin:latest .
```

**Структура Dockerfile (на примере API):**

| Стадия | Базовый образ | Назначение |
|--------|--------------|------------|
| `deps` | `node:22-alpine` | Установка зависимостей (`pnpm install --frozen-lockfile`) |
| `builder` | `node:22-alpine` | Сборка TypeScript, генерация Prisma-клиента |
| `runner` | `node:22-alpine` | Финальный образ: только dist + node_modules + prisma |

**Особенности:**
- Все образы запускаются от непривилегированного пользователя (`nestjs` / `nextjs`, UID 1001)
- Next.js приложения используют standalone output для минимального размера
- Prisma schema и миграции копируются в production образ API для runtime-миграций

### 7.3. Production Docker Compose

Файл `infra/docker/docker-compose.prod.yml` расширяет базовый `docker-compose.yml` и добавляет:

- Сборку и запуск приложений (api, web-admin, web-club-admin, web-platform-admin)
- Лимиты ресурсов для каждого контейнера
- Закрытые порты инфраструктуры (PostgreSQL и Redis не публикуют порты наружу)
- `restart: always` вместо `unless-stopped`

**Ресурсные лимиты (production):**

| Сервис | CPU (лимит) | RAM (лимит) | CPU (резерв) | RAM (резерв) |
|--------|-------------|-------------|--------------|--------------|
| PostgreSQL | 2.0 | 2 ГБ | 0.5 | 512 МБ |
| Redis | 1.0 | 1 ГБ | 0.25 | 256 МБ |
| coturn | 1.0 | 512 МБ | 0.25 | 128 МБ |
| API | 2.0 | 2 ГБ | 0.5 | 512 МБ |
| web-admin | 1.0 | 1 ГБ | 0.25 | 256 МБ |
| web-club-admin | 1.0 | 1 ГБ | 0.25 | 256 МБ |
| web-platform-admin | 1.0 | 1 ГБ | 0.25 | 256 МБ |

**Запуск production:**

```bash
# Сборка и запуск
docker compose -f infra/docker/docker-compose.yml -f infra/docker/docker-compose.prod.yml up -d --build

# Применение миграций после запуска
docker exec fitcrm-api npx prisma migrate deploy
```

### 7.4. Reverse proxy

В production все приложения слушают только внутреннюю Docker-сеть (через `expose`, а не `ports`). Доступ извне предоставляется через reverse proxy (Traefik или Nginx).

**Пример конфигурации Nginx:**

```nginx
# /etc/nginx/sites-available/fitcrm
upstream fitcrm-api {
    server 127.0.0.1:3000;
}

upstream fitcrm-web-admin {
    server 127.0.0.1:3001;
}

upstream fitcrm-web-club-admin {
    server 127.0.0.1:3002;
}

upstream fitcrm-web-platform-admin {
    server 127.0.0.1:3003;
}

# API
server {
    listen 443 ssl http2;
    server_name api.fitcrm.example.com;

    ssl_certificate /etc/letsencrypt/live/fitcrm.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/fitcrm.example.com/privkey.pem;

    location / {
        proxy_pass http://fitcrm-api;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Рецепция
server {
    listen 443 ssl http2;
    server_name reception.fitcrm.example.com;

    ssl_certificate /etc/letsencrypt/live/fitcrm.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/fitcrm.example.com/privkey.pem;

    location / {
        proxy_pass http://fitcrm-web-admin;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Админка клуба
server {
    listen 443 ssl http2;
    server_name club.fitcrm.example.com;

    ssl_certificate /etc/letsencrypt/live/fitcrm.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/fitcrm.example.com/privkey.pem;

    location / {
        proxy_pass http://fitcrm-web-club-admin;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Суперадмин
server {
    listen 443 ssl http2;
    server_name admin.fitcrm.example.com;

    ssl_certificate /etc/letsencrypt/live/fitcrm.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/fitcrm.example.com/privkey.pem;

    location / {
        proxy_pass http://fitcrm-web-platform-admin;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

### 7.5. PM2 (альтернатива Docker)

Для развертывания без Docker можно использовать PM2 -- менеджер процессов для Node.js.

**Установка PM2:**

```bash
npm install -g pm2
```

**Файл ecosystem.config.js** расположен в корне проекта:

```javascript
module.exports = {
  apps: [
    {
      name: 'fitcrm-api',
      script: 'apps/api/dist/main.js',
      instances: 'max',
      exec_mode: 'cluster',
      env: { NODE_ENV: 'production', PORT: 3000 },
    },
    {
      name: 'fitcrm-web-admin',
      script: 'node_modules/.bin/next',
      args: 'start apps/web-admin',
      env: { NODE_ENV: 'production', PORT: 3001 },
    },
    {
      name: 'fitcrm-web-club-admin',
      script: 'node_modules/.bin/next',
      args: 'start apps/web-club-admin',
      env: { NODE_ENV: 'production', PORT: 3002 },
    },
    {
      name: 'fitcrm-web-platform-admin',
      script: 'node_modules/.bin/next',
      args: 'start apps/web-platform-admin',
      env: { NODE_ENV: 'production', PORT: 3003 },
    },
  ],
};
```

**Команды PM2:**

```bash
# Запуск всех приложений
pm2 start ecosystem.config.js

# Просмотр статуса
pm2 status

# Логи
pm2 logs fitcrm-api

# Перезапуск
pm2 reload all

# Автозапуск при перезагрузке сервера
pm2 startup
pm2 save
```

---

## 8. CI/CD пайплайн

### Обзор

FitCRM использует GitHub Actions с двумя workflow-файлами:

| Workflow | Файл | Триггер | Назначение |
|----------|------|---------|------------|
| CI | `.github/workflows/ci.yml` | push/PR в `main`/`master` | Полная проверка: lint, тесты, сборка |
| PR Check | `.github/workflows/pr-check.yml` | PR в `main`/`master` | Быстрая проверка: lint + typecheck |

### 8.1. CI (ci.yml)

Основной пайплайн, запускается при каждом push или PR в ветки `main`/`master`.

```
                    +---------+
                    |  lint   |  (10 мин)
                    | ESLint  |
                    | Prettier|
                    +----+----+
                         |
                +--------+--------+
                |                 |
          +-----+------+   +-----+------+
          |   test     |   |   build    |
          | Jest       |   | turbo build|
          | PostgreSQL |   |            |
          | Redis      |   |            |
          +------------+   +------------+
           (15 мин)          (20 мин)
```

**Job 1: Lint & Format (10 минут)**
- Установка Node.js 22 + pnpm 9
- `pnpm install --frozen-lockfile`
- `pnpm lint` (ESLint)
- `pnpm format --check` (Prettier)

**Job 2: Backend Tests (15 минут, зависит от lint)**
- Запускает service-контейнеры: PostgreSQL 17 + Redis 8
- Генерирует Prisma-клиент
- Применяет схему к тестовой БД (`prisma db push`)
- Запускает Jest-тесты backend

Переменные окружения для тестов:
```
DATABASE_URL=postgresql://fitcrm:test@localhost:5432/fitcrm_test?schema=public
REDIS_HOST=localhost
REDIS_PORT=6379
JWT_SECRET=ci-test-jwt-secret-key-minimum-32-chars
JWT_REFRESH_SECRET=ci-test-jwt-refresh-secret-key-min-32
```

**Job 3: Build All Apps (20 минут, зависит от lint)**
- Генерирует Prisma-клиент
- Собирает все пакеты и приложения через `pnpm build` (Turborepo)

**Оптимизации:**
- `concurrency` с `cancel-in-progress: true` -- отменяет предыдущие запуски для той же ветки
- Кэширование pnpm через `actions/setup-node` с опцией `cache: 'pnpm'`
- `--frozen-lockfile` гарантирует воспроизводимость сборки

### 8.2. PR Check (pr-check.yml)

Быстрая проверка для pull requests. Один job:

1. ESLint
2. Prettier
3. TypeScript typecheck (`tsc --noEmit`)

Запускается параллельно с CI, дает быструю обратную связь разработчику.

### 8.3. Dependabot

Файл `.github/dependabot.yml` настраивает автоматическое обновление зависимостей через GitHub Dependabot.

### 8.4. Расширение пайплайна (рекомендации)

Для production рекомендуется добавить следующие шаги:

```yaml
# Пример: job для сборки и публикации Docker-образов
deploy:
  name: Build & Push Docker Images
  runs-on: ubuntu-latest
  needs: [test, build]
  if: github.ref == 'refs/heads/main'
  steps:
    - uses: actions/checkout@v4
    - uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    - uses: docker/build-push-action@v6
      with:
        context: .
        file: infra/docker/Dockerfile.api
        push: true
        tags: ghcr.io/${{ github.repository }}/api:${{ github.sha }}
```

---

## 9. SSL/TLS и безопасность

### 9.1. SSL-сертификаты

**Let's Encrypt (рекомендуется для production):**

```bash
# Установка certbot
sudo apt install certbot python3-certbot-nginx

# Получение сертификатов
sudo certbot --nginx -d api.fitcrm.example.com \
                     -d reception.fitcrm.example.com \
                     -d club.fitcrm.example.com \
                     -d admin.fitcrm.example.com

# Автообновление (cron)
sudo certbot renew --dry-run
```

**STUN/TURN over TLS:**

coturn слушает порт 5349 для TLS-соединений. Для production сконфигурируйте сертификаты в `turnserver.conf`:

```
cert=/etc/letsencrypt/live/turn.fitcrm.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.fitcrm.example.com/privkey.pem
```

### 9.2. Безопасность секретов

- **Никогда** не коммитьте `.env` файл (он добавлен в `.gitignore`)
- Используйте разные секреты для каждого окружения (dev, staging, production)
- В CI/CD храните секреты в GitHub Secrets
- Для production рекомендуется использовать vault-решения (HashiCorp Vault, AWS Secrets Manager)
- JWT-секреты должны содержать минимум 32 символа

### 9.3. Защита PostgreSQL

```sql
-- Создание отдельных ролей
CREATE ROLE fitcrm_app WITH LOGIN PASSWORD '...';
CREATE ROLE fitcrm_admin WITH LOGIN PASSWORD '...' BYPASSRLS;

-- Ограничение прав приложения
GRANT CONNECT ON DATABASE fitcrm TO fitcrm_app;
GRANT USAGE ON SCHEMA public TO fitcrm_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO fitcrm_app;

-- Для миграций используйте fitcrm_admin
```

### 9.4. Защита Redis

- Всегда устанавливайте пароль (`requirepass`)
- В production закройте порт 6379 от внешнего доступа
- Используйте ACL для разграничения прав (Redis 6+)

### 9.5. Сетевая безопасность

**Файрволл (ufw):**

```bash
# Разрешить только необходимые порты
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp        # SSH
sudo ufw allow 80/tcp        # HTTP (redirect to HTTPS)
sudo ufw allow 443/tcp       # HTTPS
sudo ufw allow 3478/udp      # STUN/TURN
sudo ufw allow 3478/tcp      # STUN/TURN
sudo ufw allow 5349/udp      # STUN/TURN TLS
sudo ufw allow 5349/tcp      # STUN/TURN TLS
sudo ufw allow 49152:65535/udp  # Media relay
sudo ufw enable
```

**Важно:** PostgreSQL (5432) и Redis (6379) НЕ должны быть доступны из интернета.

### 9.6. Суперадминистратор

Панель суперадминистратора (`admin.fitcrm.example.com`) требует дополнительных мер:
- Обязательная двухфакторная аутентификация (TOTP)
- Опциональный IP-whitelisting
- Полный audit log всех действий

### 9.7. Rate Limiting

API защищено глобальным rate limiter через `@nestjs/throttler`:

| Параметр | Значение |
|----------|---------|
| Лимит | 1000 запросов в минуту |
| Хранилище | Redis |
| Применение | Глобальный `APP_GUARD` (все эндпоинты) |

При превышении лимита возвращается `429 Too Many Requests`.

Мониторинг rate limiting:

```bash
# Проверка ключей throttler в Redis
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" KEYS "throttler:*"
```

---

## 10. Резервное копирование и восстановление

### 10.1. PostgreSQL

**Ежедневный бэкап (cron):**

```bash
#!/bin/bash
# /opt/fitcrm/scripts/backup-db.sh

BACKUP_DIR="/opt/fitcrm/backups/postgres"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

mkdir -p "$BACKUP_DIR"

# Создание бэкапа
docker exec fitcrm-postgres pg_dump \
  -U fitcrm \
  -d fitcrm \
  --format=custom \
  --compress=9 \
  > "$BACKUP_DIR/fitcrm_${TIMESTAMP}.dump"

# Проверка успешности
if [ $? -eq 0 ]; then
  echo "[$(date)] Бэкап создан: fitcrm_${TIMESTAMP}.dump"
else
  echo "[$(date)] ОШИБКА: бэкап не удался!" >&2
  exit 1
fi

# Удаление старых бэкапов
find "$BACKUP_DIR" -name "*.dump" -mtime +${RETENTION_DAYS} -delete

echo "[$(date)] Очистка завершена. Бэкапы старше ${RETENTION_DAYS} дней удалены."
```

**Настройка cron:**

```bash
# Ежедневно в 03:00
0 3 * * * /opt/fitcrm/scripts/backup-db.sh >> /var/log/fitcrm-backup.log 2>&1
```

**Восстановление из бэкапа:**

```bash
# Остановить API
docker compose -f infra/docker/docker-compose.yml stop api 2>/dev/null || true

# Восстановление
docker exec -i fitcrm-postgres pg_restore \
  -U fitcrm \
  -d fitcrm \
  --clean \
  --if-exists \
  < /opt/fitcrm/backups/postgres/fitcrm_20260218_030000.dump

# Запустить API
docker compose -f infra/docker/docker-compose.yml start api 2>/dev/null || true
```

### 10.2. Redis

Redis использует AOF-персистенцию (`appendonly yes`). Данные автоматически сохраняются в volume `redis_data`.

**Ручной бэкап:**

```bash
# Команда BGSAVE для создания RDB-снимка
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" BGSAVE

# Копирование файла
docker cp fitcrm-redis:/data/dump.rdb /opt/fitcrm/backups/redis/dump_$(date +%Y%m%d).rdb
```

### 10.3. S3-хранилище

Файлы (аватары, отчеты, логотипы) хранятся в S3-совместимом хранилище с префиксом `/{club_id}/`. Рекомендуется настроить:
- Versioning на бакете
- Cross-region replication (для критичных данных)
- Lifecycle policies для автоматического удаления старых файлов

### 10.4. Стратегия бэкапов

| Компонент | Частота | Хранение | Метод |
|-----------|---------|----------|-------|
| PostgreSQL | Ежедневно 03:00 | 30 дней | `pg_dump --format=custom` |
| PostgreSQL (WAL) | Непрерывно | 7 дней | WAL-G / pgBackRest |
| Redis | Ежедневно | 7 дней | RDB snapshot |
| S3-файлы | Versioning | 90 дней | S3 versioning |
| .env / конфиги | При изменении | Бессрочно | Зашифрованное хранилище |

---

## 11. Масштабирование

### 11.1. Текущая архитектура (MVP)

В MVP все клубы работают на одном инстансе PostgreSQL с Row-Level Security:

```
[Клиенты] --> [Nginx/Traefik] --> [API (NestJS)]
                                       |
                                  [PostgreSQL + RLS]
                                       |
                                    [Redis]
```

### 11.2. Горизонтальное масштабирование API

API-сервер NestJS является stateless (состояние хранится в PostgreSQL и Redis). Можно запустить несколько инстансов:

```yaml
# docker-compose.prod.yml (пример масштабирования)
api:
  deploy:
    replicas: 3
    resources:
      limits:
        cpus: "2.0"
        memory: 2G
```

При этом reverse proxy (Nginx/Traefik) выполняет балансировку нагрузки.

### 11.3. PostgreSQL: оптимизация

**Рекомендуемые параметры для production (8 ГБ RAM):**

```
# postgresql.conf
shared_buffers = 2GB
effective_cache_size = 6GB
maintenance_work_mem = 512MB
work_mem = 16MB
max_connections = 200
wal_buffers = 64MB
checkpoint_completion_target = 0.9
random_page_cost = 1.1  # для SSD
```

**Пул соединений (PgBouncer):**

При большом количестве клубов рекомендуется PgBouncer перед PostgreSQL:

```bash
docker run -d \
  --name fitcrm-pgbouncer \
  --network fitcrm-network \
  -e DATABASE_URL="postgresql://fitcrm:password@postgres:5432/fitcrm" \
  -e POOL_MODE=transaction \
  -e MAX_DB_CONNECTIONS=100 \
  -e DEFAULT_POOL_SIZE=25 \
  -p 6432:6432 \
  edoburu/pgbouncer
```

### 11.4. Redis: кластеризация

Для высоких нагрузок:
- Redis Sentinel (высокая доступность)
- Redis Cluster (шардирование при больших объемах данных BullMQ)

### 11.5. Эволюция multi-tenancy

FitCRM спроектирован для поэтапной миграции:

| Этап | Стратегия | Когда переходить |
|------|-----------|-----------------|
| MVP | Shared DB + RLS | До 50 клубов |
| Стандартная | DB-per-tenant | 50-500 клубов, повышенные требования к изоляции |
| Премиум | K8s namespace | VIP-клубы, требующие полной изоляции |

Код использует Strategy Pattern (`TenantStrategy`), что позволяет переключать стратегии без переписывания бизнес-логики.

### 11.6. Kubernetes (production)

Для production-развертывания планируется использование Kubernetes. Манифесты будут размещены в `infra/k8s/`. Ключевые компоненты:

- **Deployment:** API (3+ реплики), web-приложения (2+ реплики)
- **Service:** ClusterIP для внутренних сервисов
- **Ingress:** Traefik/Nginx для маршрутизации по доменам
- **PersistentVolumeClaim:** для PostgreSQL и Redis
- **HorizontalPodAutoscaler:** автомасштабирование API по CPU/RAM
- **Secrets:** для хранения паролей и ключей

---

## 12. Устранение неполадок

### 12.1. Docker Compose не запускается

**Проблема:** `Cannot connect to the Docker daemon`

```bash
# Проверить статус Docker
sudo systemctl status docker

# Перезапустить Docker
sudo systemctl restart docker

# Проверить, что пользователь в группе docker
groups | grep docker
```

**Проблема:** `port is already allocated`

```bash
# Найти процесс, занимающий порт
sudo lsof -i :5432
sudo lsof -i :6379

# Или убить процесс
sudo kill -9 $(sudo lsof -t -i :5432)
```

### 12.2. PostgreSQL не запускается

**Проблема:** `FATAL: password authentication failed`

```bash
# Проверить переменные окружения
docker exec fitcrm-postgres env | grep POSTGRES

# Пересоздать контейнер с чистым volume (УДАЛИТ ДАННЫЕ!)
docker compose -f infra/docker/docker-compose.yml down -v
docker compose -f infra/docker/docker-compose.yml up -d
```

**Проблема:** `too many connections`

```bash
# Проверить количество соединений
docker exec fitcrm-postgres psql -U fitcrm -c "SELECT count(*) FROM pg_stat_activity;"

# Убить idle-соединения
docker exec fitcrm-postgres psql -U fitcrm -c \
  "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND query_start < now() - interval '10 minutes';"
```

### 12.3. Redis не доступен

**Проблема:** `NOAUTH Authentication required`

```bash
# Проверить, что REDIS_PASSWORD совпадает
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" ping
# Ожидаемый ответ: PONG
```

**Проблема:** `OOM command not allowed when used memory > maxmemory`

```bash
# Проверить использование памяти
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" INFO memory

# Установить maxmemory-policy
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" CONFIG SET maxmemory-policy allkeys-lru
```

### 12.4. Prisma: ошибки миграций

**Проблема:** `P1001: Can't reach database server`

```bash
# Проверить, что PostgreSQL запущен и здоров
docker compose -f infra/docker/docker-compose.yml ps postgres

# Проверить DATABASE_URL
echo $DATABASE_URL

# Проверить подключение напрямую
docker exec fitcrm-postgres pg_isready -U fitcrm -d fitcrm
```

**Проблема:** `P3009: migrate found failed migrations`

```bash
# Посмотреть статус миграций
cd apps/api && npx prisma migrate status

# Отметить проблемную миграцию как примененную (осторожно!)
npx prisma migrate resolve --applied <migration_name>

# Или откатить вручную и повторить
npx prisma migrate resolve --rolled-back <migration_name>
npx prisma migrate deploy
```

### 12.5. Сборка приложений не проходит

**Проблема:** `Cannot find module '@fitcrm/shared-types'`

```bash
# Пересобрать пакеты в правильном порядке
pnpm --filter shared-types build
pnpm --filter validators build
pnpm --filter api-client build
pnpm build
```

**Проблема:** `ENOMEM: not enough memory` при сборке

```bash
# Увеличить лимит памяти для Node.js
export NODE_OPTIONS="--max-old-space-size=4096"
pnpm build
```

### 12.6. coturn (STUN/TURN)

**Проблема:** SIP-звонки не проходят

```bash
# Проверить логи coturn
docker logs fitcrm-coturn --tail 100

# Тестирование STUN (из браузера)
# Откройте: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
# STUN: stun:<SERVER_IP>:3478
# TURN: turn:<SERVER_IP>:3478 (с credentials)
```

**Проблема:** `Allocation failed: cannot allocate relay`

```bash
# Проверить, что диапазон UDP-портов открыт
sudo ufw status | grep 49152

# Проверить сетевые интерфейсы в конфигурации coturn
docker exec fitcrm-coturn cat /etc/turnserver.conf
```

### 12.7. CI/CD не проходит

**Проблема:** `pnpm install --frozen-lockfile` не проходит

```bash
# Обновить lockfile локально
pnpm install
git add pnpm-lock.yaml
git commit -m "chore: update pnpm-lock.yaml"
git push
```

**Проблема:** Тесты проходят локально, но не в CI

Возможные причины:
- Различия в переменных окружения (проверьте `env` в ci.yml)
- Тесты зависят от порядка выполнения (используйте `--runInBand`)
- Превышен timeout (по умолчанию 15 минут для тестов)

### 12.8. Общие рекомендации по диагностике

```bash
# Статус всех контейнеров
docker compose -f infra/docker/docker-compose.yml ps

# Логи всех сервисов (последние 50 строк)
docker compose -f infra/docker/docker-compose.yml logs --tail 50

# Использование ресурсов контейнерами
docker stats --no-stream

# Проверить сеть Docker
docker network inspect fitcrm-network

# Проверить volumes
docker volume ls | grep fitcrm

# Полная очистка (УДАЛИТ ВСЕ ДАННЫЕ!)
docker compose -f infra/docker/docker-compose.yml down -v --remove-orphans
docker system prune -f
```

---

## Приложение: карта портов

| Порт | Протокол | Сервис | Доступ в production |
|------|----------|--------|---------------------|
| 3000 | TCP | API (NestJS) | Только через reverse proxy |
| 3001 | TCP | web-admin (Next.js) | Только через reverse proxy |
| 3002 | TCP | web-club-admin (Next.js) | Только через reverse proxy |
| 3003 | TCP | web-platform-admin (Next.js) | Только через reverse proxy |
| 5432 | TCP | PostgreSQL | Закрыт извне |
| 6379 | TCP | Redis | Закрыт извне |
| 3478 | UDP/TCP | STUN/TURN | Открыт (необходим для VoIP) |
| 5349 | UDP/TCP | STUN/TURN over TLS | Открыт (необходим для VoIP) |
| 49152-65535 | UDP | Media relay (RTP/SRTP) | Открыт (необходим для VoIP) |

---

## Приложение: структура файлов инфраструктуры

```
infra/
├── docker/
│   ├── docker-compose.yml           # Development: PostgreSQL + Redis + coturn
│   ├── docker-compose.prod.yml      # Production: + API + 3 web-приложения + лимиты ресурсов
│   ├── Dockerfile.api               # NestJS API (3 стадии: deps -> builder -> runner)
│   ├── Dockerfile.web               # web-admin Next.js (standalone output)
│   ├── Dockerfile.club-admin        # web-club-admin Next.js (standalone output)
│   └── Dockerfile.platform-admin    # web-platform-admin Next.js (standalone output)
├── stun-turn/
│   └── turnserver.conf              # Конфигурация coturn STUN/TURN сервера
├── k8s/                             # Kubernetes манифесты (планируется)
└── github-actions/                  # Дополнительные workflow (планируется)

.github/
├── workflows/
│   ├── ci.yml                       # CI: lint -> test -> build
│   └── pr-check.yml                 # PR: lint + typecheck
└── dependabot.yml                   # Автообновление зависимостей
```
