Files
fitcrm/docs/devops
root 90efef3afc docs: обновление документации — TOTP, BullMQ, rate limiting, CRUD-панели
- docs/dev: добавлены 10 API-разделов (Webhooks, Catalog, Departments,
  Rooms, Clubs Admin, Licenses, Reports, Integration, Audit Logs),
  TOTP-эндпоинты, разделы BullMQ и Rate Limiting, обновлено содержание
- docs/devops: добавлены BullMQ очереди (Redis 3.2), PM2 (7.5),
  Rate Limiting (9.7)
- docs/user: убрано «в разработке» для панелей клуба и суперадмина,
  добавлена инструкция 2FA, описаны CRUD-диалоги, CSV-экспорт аудита

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:17:32 +00:00
..

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

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


Содержание

  1. Требования к окружению
  2. Быстрый старт
  3. Docker-сервисы
  4. Настройка базы данных
  5. Конфигурация окружения (.env)
  6. Запуск в режиме разработки
  7. Сборка для production
  8. CI/CD пайплайн
  9. SSL/TLS и безопасность
  10. Резервное копирование и восстановление
  11. Масштабирование
  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):

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:

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

Docker и Docker Compose:

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

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

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

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

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

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

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

cp .env.example .env

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

# Минимум для локальной разработки:
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

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

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

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

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

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

pnpm install

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

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

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

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

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

# Все приложения одновременно (через 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. Проверка работоспособности

# 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

# Образ: 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 -- см. раздел Масштабирование

3.2. Redis 8

# Образ: 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 попытки с экспоненциальной задержкой

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

# Проверка количества задач в очередях
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)

# Образ: 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-сервисами

# Запуск
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.

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

# Генерация 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 автоматически фильтрует данные:
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

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

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

Рекомендации:

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

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

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

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

Полезные 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 для разработки

# 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

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

# 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. Запуск в режиме разработки

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

# 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 Панель суперадминистратора

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

# Только 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)

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

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

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

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

# Линтинг всего проекта
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

# Сборка всех приложений и пакетов
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 сборкой для минимизации размера образа.

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

# Из корня проекта
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:

# Сборка и запуск
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:

# /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:

npm install -g pm2

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

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:

# Запуск всех приложений
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 рекомендуется добавить следующие шаги:

# Пример: 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):

# Установка 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

-- Создание отдельных ролей
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):

# Разрешить только необходимые порты
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:

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

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

10.1. PostgreSQL

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

#!/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:

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

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

# Остановить 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.

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

# Команда 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). Можно запустить несколько инстансов:

# 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:

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

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

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

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

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

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

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

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

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

# Проверить переменные окружения
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

# Проверить количество соединений
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

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

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

# Проверить использование памяти
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

# Проверить, что 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

# Посмотреть статус миграций
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'

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

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

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

12.6. coturn (STUN/TURN)

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

# Проверить логи 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

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

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

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

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

# Обновить 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. Общие рекомендации по диагностике

# Статус всех контейнеров
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                   # Автообновление зависимостей