Перейти к содержанию

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

Содержание

  1. Требования
  2. Локальное развертывание
  3. Развертывание с Docker
  4. Production (docker-compose.yml)
  5. Production развертывание
  6. Фоновые задачи: очередь и просроченные брони
  7. Настройка Nginx
  8. Настройка SSL
  9. Резервное копирование
  10. Мониторинг и логирование
  11. Обновление и troubleshooting

Требования

Минимальные требования

  • Node.js: 18.x или выше
  • PostgreSQL: 14.x или выше
  • Redis: 6.x или выше (обязателен в production для общего rate-limit store между PM2-воркерами; в dev/test допускается in-memory)
  • npm: 9.x или выше

Рекомендуемые требования для production

  • CPU: 2+ ядра
  • RAM: 4GB+
  • Диск: 20GB+ свободного места
  • Сеть: Стабильное подключение к интернету

Локальное развертывание

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

git clone <repository-url>
cd library_systemISPRAV-main

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

Frontend

npm install

Backend

cd server
npm install
cd ..

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

Создание базы данных

# Подключитесь к PostgreSQL
psql -U postgres

# Создайте базу данных
CREATE DATABASE library_system;

# Создайте пользователя (опционально)
CREATE USER library_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE library_system TO library_user;

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

Скопируйте примеры файлов окружения:

cp server/env.example server/.env
cp env.example .env

Отредактируйте server/.env:

# База данных
DB_HOST=localhost
DB_PORT=5432
DB_NAME=library_system
DB_USER=library_user
DB_PASSWORD=your_password

# JWT
JWT_SECRET=your_secret_key_here
JWT_EXPIRES_IN=7d

# Сервер
PORT=3001
NODE_ENV=development
ALLOWED_ORIGINS=http://localhost:8080

Отредактируйте .env (корень проекта):

VITE_API_URL=/api
DATABASE_URL=postgresql://library_user:password@localhost:5432/library
JWT_SECRET=...
CSRF_SECRET=...

Выполнение миграций

Рекомендуемый путь (новая установка и обновления):

cd server
npm run migrate

Миграции 001049 создают схему; 030_seed_reference_data.sql заполняет справочники (pending, loan, return, conditions и др.) — идемпотентно (ON CONFLICT DO NOTHING).

Первичный импорт prod-дампа (только при переносе со старой БД, не для CI):

npm run migrate:init   # migrations/init.sql целиком через psql-совместимый runner
npm run migrate

Дополнительные справочники (идемпотентно):

node scripts/seed-reference-data.js

Тестовая БД (локально и в CI):

npm run reset:test-db

В NODE_ENV=test / CI=true скрипт пропускает init.sql и собирает БД только из миграций (SKIP_INIT_SQL).

4. Запуск приложения

Development режим

Терминал 1 - Backend:

cd server
npm run dev

Терминал 2 - Frontend:

npm run dev

Приложение будет доступно: - Frontend: http://localhost:8080 - Backend API: http://localhost:3001/api (или через прокси Vite /api)

Production режим

Backend:

cd server
npm run build
npm start

Frontend:

npm run build
npm run preview

Фоновые задачи (очередь бронирований, просроченные брони) — см. отдельный раздел.

Развертывание с Docker

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

Создайте .env файлы для Docker Compose (см. docker-compose.yml).

2. Запуск с Docker Compose

Development

docker-compose -f docker-compose.dev.yml up -d

Production (полный стек со SSL)

docker compose -f docker-compose.yml -f docker-compose.ssl.yml up -d

Быстрый production (рекомендуется для VPS)

См. Быстрый production и QUICK_DEPLOY_UBUNTU.md.

3. Выполнение миграций

docker compose -f docker-compose.yml exec backend node migrations/migration-runner.js

4. Просмотр логов

docker compose -f docker-compose.yml logs -f backend
docker compose -f docker-compose.yml logs -f frontend

Production (docker-compose.yml)

Полная шпаргалка по файлам и профилям: DOCKER.md.

Типичная схема: nginx + frontend + backend, PostgreSQL на том же сервере или по DATABASE_URL из .env.

1. Подготовка

cd /opt/library_system
cp .env.example .env
# Заполните DATABASE_URL, JWT_SECRET, CSRF_SECRET, VITE_API_URL=/api
mkdir -p uploads logs backups

В корневом .env (минимум для production):

BACKUP_ENABLED=true
REDIS_URL=redis://redis:6379   # обязательно в production
METRICS_TOKEN=<случайный_токен>
PM2_INSTANCES=max
# После создания первого админа:
# SETUP_ADMIN_ENABLED=false

Без REDIS_URL в production backend не стартует (валидация в server/config/env.js).

В Docker по умолчанию: - backend — API + PM2 cluster (PM2_INSTANCES=max), OUTBOX_WORKER_ENABLED=false - outbox-worker — отдельный контейнер для очереди бронирований - redis — rate-limit и кэш справочников - тома ./backups, ./uploads, ./logs

2. Запуск

docker compose build --no-cache backend frontend
docker compose up -d
docker compose exec backend node migrations/migration-runner.js

3. Проверка

curl -s http://127.0.0.1:3001/health/ready
docker compose logs outbox-worker | grep "Outbox worker"
docker compose logs backend | grep "Rate limit"

GET /health/ready должен вернуть 200 и status: ready (БД доступна).

4. Горизонтальное масштабирование backend

docker compose -f docker-compose.yml -f docker-compose.scale.yml up -d --scale backend=2

Nginx балансирует запросы между контейнерами backend (см. nginx.prod.conf).

5. Профили Docker Compose

Профиль Команда Сервисы
jobs docker compose --profile jobs up -d booking-cleanup, backup-cron
monitoring docker compose --profile monitoring up -d Prometheus (:9090), Grafana (:3000)
db docker compose --profile db up -d PostgreSQL в контейнере

Мониторинг: задайте METRICS_TOKEN в .env, откройте Grafana на http://127.0.0.1:3000. Подробная настройка (в т.ч. с уже запущенными Prometheus/Grafana на сервере): MONITORING.md.

6. Обновление после git pull

git pull
docker compose build --no-cache backend frontend outbox-worker
docker compose up -d
docker compose exec backend node migrations/migration-runner.js

Если менялся только backend: достаточно пересобрать backend и outbox-worker.

Фоновые задачи: очередь и просроченные брони

Outbox worker (Docker)

В docker-compose.yml очередь обрабатывает контейнер outbox-worker (scripts/run-outbox-daemon.js).
У backend по умолчанию OUTBOX_WORKER_ENABLED=false, чтобы не дублировать обработку при PM2 cluster и нескольких репликах.

Для локального node index.js без Docker задайте OUTBOX_WORKER_ENABLED=true в server/.env.

Переменная По умолчанию (Docker backend) Описание
OUTBOX_WORKER_ENABLED false на API, true в outbox-worker Обработка CopyBecameAvailable
OUTBOX_WORKER_INTERVAL_MS 10000 Интервал опроса, мс
OUTBOX_WORKER_MAX_EVENTS 100 Событий за раунд
OUTBOX_WORKER_MAX_ROUNDS 20 Раундов за один тик

После возврата книги outbox-worker также вызывается сразу (triggerOutboxProcessing), без ожидания интервала.

Cron (если воркер отключён или для подстраховки)

# Docker
* * * * * cd /opt/library_system && docker compose -f docker-compose.yml exec -T backend node scripts/process-outbox-events.js

# Без Docker
* * * * * cd /path/to/library_system/server && node scripts/process-outbox-events.js

Просроченные бронирования и плановые бэкапы

В Docker (профиль jobs):

docker compose --profile jobs up -d
  • booking-cleanup — раз в сутки отменяет просроченные брони (массовая очистка; в runtime просроченные также снимаются lazy при работе с очередью)
  • backup-cron — плановый pg_dump в ./backups (BACKUP_RETENTION_COUNT, BACKUP_CRON_INTERVAL_SEC)

Альтернатива — cron на хосте:

0 * * * * cd /opt/library_system && docker compose exec -T backend node scripts/cleanup-expired-bookings.js
0 2 * * * cd /opt/library_system && docker compose exec -T backend node scripts/run-scheduled-backup.js

Вручную: POST /api/bookings/cleanup-expired (admin, librarian).

Диагностика очереди

# Необработанные outbox-события
docker compose -f docker-compose.yml exec backend node -e "
require('dotenv').config();
const pool=require('./config/database');
pool.query('SELECT COUNT(*) FROM outbox_events WHERE processed_at IS NULL')
  .then(r=>{console.log('необработано:',r.rows[0].count);pool.end();});
"

# Ручной прогон
docker compose -f docker-compose.yml exec backend node scripts/process-outbox-events.js

# Закрыть устаревшие/дубликаты событий
docker compose -f docker-compose.yml exec backend node scripts/reconcile-outbox-stuck.js

Production развертывание

1. Подготовка сервера

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

# Ubuntu/Debian
sudo apt update
sudo apt install -y nodejs npm postgresql redis-server nginx

# CentOS/RHEL
sudo yum install -y nodejs npm postgresql-server redis nginx

Настройка PostgreSQL

sudo systemctl start postgresql
sudo systemctl enable postgresql

Настройка Redis (опционально)

sudo systemctl start redis
sudo systemctl enable redis

2. Развертывание приложения

Клонирование и установка

cd /var/www
git clone <repository-url> library_system
cd library_system

# Установка зависимостей
npm install
cd server && npm install && cd ..

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

# Создайте production .env файлы
cp server/env.example server/.env
cp env.example .env

# Отредактируйте с production значениями
nano server/.env
nano .env

Сборка приложения

# Frontend
npm run build

# Backend (если используется TypeScript)
cd server
npm run build
cd ..

3. Настройка systemd сервисов

Backend сервис

Создайте /etc/systemd/system/library-backend.service:

[Unit]
Description=Library System Backend
After=network.target postgresql.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/library_system/server
Environment=NODE_ENV=production
ExecStart=/usr/bin/node index.js
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Запустите сервис:

sudo systemctl daemon-reload
sudo systemctl enable library-backend
sudo systemctl start library-backend

Frontend сервис (если используется отдельный сервер)

Настройте Nginx для статических файлов (см. раздел "Настройка Nginx").

Настройка Nginx

Конфигурация для production

Создайте /etc/nginx/sites-available/library_system:

server {
    listen 80;
    server_name your-domain.com;

    # Редирект на HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-domain.com;

    # SSL сертификаты
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # Frontend
    location / {
        root /var/www/library_system/dist;
        try_files $uri $uri/ /index.html;

        # Кэширование статических файлов
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }

    # Backend API
    location /api {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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;
        proxy_cache_bypass $http_upgrade;
    }

    # WebSocket поддержка (если используется)
    location /ws {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Активируйте конфигурацию:

sudo ln -s /etc/nginx/sites-available/library_system /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Настройка SSL

Использование Let's Encrypt

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

# Получение сертификата
sudo certbot --nginx -d your-domain.com

# Автоматическое обновление
sudo certbot renew --dry-run

Использование собственного сертификата

Поместите сертификаты в безопасное место и укажите пути в конфигурации Nginx.

Резервное копирование

Через панель администратора (рекомендуется)

  1. В корневом .env или в environment backend: BACKUP_ENABLED=true
  2. Каталог бэкапов: BACKUP_DIR=/app/backups (в Docker смонтирован ./backups)
  3. Контейнер backend запускается от пользователя nodejs (uid 1001); каталог ./backups на хосте должен быть доступен на запись (entrypoint выполняет chown при старте)
  4. Администрирование → Система: создание дампа, список, скачивание, восстановление из файла

API (роль admin): GET /api/admin/backups, GET /api/admin/backups/:filename/download, POST /api/admin/backups/restore.

Автоматическое резервное копирование БД (cron / pg_dump)

Создайте скрипт /usr/local/bin/backup-library-db.sh:

#!/bin/bash
BACKUP_DIR="/var/backups/library_system"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="library_system"
DB_USER="library_user"

mkdir -p $BACKUP_DIR

# Создание бэкапа
pg_dump -U $DB_USER $DB_NAME | gzip > $BACKUP_DIR/backup_$DATE.sql.gz

# Удаление старых бэкапов (старше 30 дней)
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +30 -delete

echo "Backup completed: backup_$DATE.sql.gz"

Сделайте скрипт исполняемым:

sudo chmod +x /usr/local/bin/backup-library-db.sh

Добавьте в crontab (ежедневно в 2:00):

0 2 * * * /usr/local/bin/backup-library-db.sh

Резервное копирование файлов

# Бэкап загруженных файлов (обложки книг)
tar -czf /var/backups/library_system/uploads_$(date +%Y%m%d).tar.gz /var/www/library_system/uploads

Мониторинг и логирование

Просмотр логов

Backend логи

# Systemd журнал
sudo journalctl -u library-backend -f

# Файловые логи
tail -f /var/www/library_system/server/logs/combined.log
tail -f /var/www/library_system/server/logs/error.log

Nginx логи

tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

Мониторинг производительности

Prometheus + Grafana (Docker)

# В .env: METRICS_TOKEN=...
docker compose --profile monitoring up -d
  • Prometheus: http://127.0.0.1:9090 (DNS SD по имени backend, Bearer METRICS_TOKEN; при --scale backend=N — все реплики)
  • Grafana: http://127.0.0.1:3000 — дашборд Library System Overview
  • Healthcheck: GET /health/ready (БД, Redis soft-degrade, backlog outbox)

Уведомления Grafana.env перед docker compose --profile monitoring up -d):

GF_SMTP_ENABLED=true
GF_SMTP_HOST=smtp.example.com:587
GF_SMTP_USER=alerts@example.com
GF_SMTP_PASSWORD=...
GF_SMTP_FROM_ADDRESS=alerts@example.com
ALERT_EMAIL_TO=ops@example.com

# Опционально Telegram для critical-алертов:
TELEGRAM_BOT_TOKEN=...
TELEGRAM_CHAT_ID=...

Без ALERT_EMAIL_TO / TELEGRAM_* алерты создаются в Grafana, но каналы уведомлений не провиженятся.

При горизонтальном масштабировании (docker compose -f docker-compose.yml -f docker-compose.scale.yml up -d --scale backend=2) Prometheus автоматически обнаруживает все IP через DNS SD. Проверка: http://127.0.0.1:9090/targets — несколько library-backend в состоянии UP. Опционально: PROMETHEUS_BACKEND_DNS_NAME=backend в .env (имя сервиса в compose-сети).

Connection pool и rate limiting

Пул Sequelize — не превышайте max_connections PostgreSQL:

PM2_INSTANCES × backend_replicas × SEQUELIZE_POOL_MAX < max_connections − 15

Пример для Docker (PM2_INSTANCES=max ≈ 4 CPU, --scale backend=2, max_connections=100):

SEQUELIZE_POOL_MAX=3
SEQUELIZE_POOL_MIN=1

Rate limit (с Redis store — общий счётчик между воркерами):

Переменная По умолчанию Назначение
RATE_LIMIT_API_MAX_REQUESTS 3000 / 15 мин Per-user (JWT) или per-IP
RATE_LIMIT_AUTH_MAX_ATTEMPTS 10 / 15 мин Per login+IP
RATE_LIMIT_KEY_STRATEGY user ip — только по IP (legacy)

За institutional NAT обязательны Redis и ключ per-user, не per-IP.

PM2 в Docker

В контейнере backend PM2 запускается автоматически (ecosystem.config.cjs, PM2_INSTANCES=max).
Без Docker:

cd server
npm run start:cluster

Мониторинг БД

# Подключение к PostgreSQL
psql -U library_user -d library_system

# Просмотр активных подключений
SELECT * FROM pg_stat_activity;

# Размер базы данных
SELECT pg_size_pretty(pg_database_size('library_system'));

Настройка алертов

Настройте мониторинг для отслеживания: - Доступности сервисов - Использования ресурсов (CPU, RAM, диск) - Ошибок в логах - Производительности БД

Обновление приложения

Процесс обновления

# 1. Создание бэкапа
/usr/local/bin/backup-library-db.sh

# 2. Остановка сервисов
sudo systemctl stop library-backend

# 3. Обновление кода
cd /var/www/library_system
git pull origin main

# 4. Установка зависимостей
npm install
cd server && npm install && cd ..

# 5. Выполнение миграций
cd server
npm run migrate

# 6. Пересборка (если нужно)
npm run build
cd ..
npm run build

# 7. Запуск сервисов
sudo systemctl start library-backend
sudo systemctl reload nginx

Откат изменений

# 1. Восстановление БД из бэкапа
gunzip < /var/backups/library_system/backup_YYYYMMDD_HHMMSS.sql.gz | psql -U library_user library_system

# 2. Откат к предыдущей версии кода
cd /var/www/library_system
git checkout <previous-commit-hash>

# 3. Перезапуск сервисов
sudo systemctl restart library-backend

Безопасность

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

  1. Firewall: Настройте firewall для ограничения доступа

    sudo ufw allow 22/tcp
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw enable
    

  2. Обновления: Регулярно обновляйте систему и зависимости

    sudo apt update && sudo apt upgrade
    npm audit fix
    

  3. Пароли: Используйте сильные пароли для БД и JWT секретов

  4. Права доступа: Ограничьте права доступа к файлам

    sudo chown -R www-data:www-data /var/www/library_system
    sudo chmod -R 755 /var/www/library_system
    

  5. Логирование: Настройте ротацию логов

    sudo logrotate -d /etc/logrotate.d/library_system
    

Troubleshooting

CI: Backend tests падают на миграциях

Симптом Решение
booking_status with code=pending not found Убедитесь, что в репозитории есть 030_seed_reference_data.sql и CI использует SKIP_INIT_SQL=true
Ошибки при построчном init.sql В CI init.sql не применяется — только миграции 001+
Таймаут auth-тестов 30 с В CI задайте REDIS_URL=""; в test-режиме Redis store отключён

Конфигурация: .github/workflows/ci.yml, server/scripts/setup-test-db.js.

После деплоя frontend: «Failed to fetch dynamically imported module»

Браузер кэшировал старый index.html с устаревшими именами JS-чанков.

  1. Жёсткая перезагрузка: Ctrl+Shift+R
  2. Или «Очистить данные и войти заново» в приложении
  3. На сервере: docker compose up -d --build frontend nginx
  4. В nginx для index.html должен быть Cache-Control: no-store (см. nginx.conf, nginx.quick.conf)

Приложение также пытается перезагрузиться автоматически (src/lib/chunkLoadRecovery.ts).

Очередь бронирований не продвигается

  1. Убедитесь, что запущен контейнер outbox-worker (docker compose ps) или OUTBOX_WORKER_ENABLED=true при локальном node index.js
  2. Проверьте, что backend и frontend обновлены вместе (синхронное продвижение после возврата/отмены — в API)
  3. Запустите scripts/process-outbox-events.js и проверьте счётчик необработанных событий (см. выше)
  4. При «зависших» событиях: scripts/reconcile-outbox-stuck.js
  5. Убедитесь, что у читателя в голове очереди меньше 5 активных броней и учётная запись активна
  6. Проверьте, не блокирует ли экземпляр просроченная pending-бронь — при новых версиях она снимается автоматически при следующем обращении к очереди; иначе POST /api/bookings/cleanup-expired или cron booking-cleanup

Проблемы с подключением к БД

# Проверка статуса PostgreSQL
sudo systemctl status postgresql

# Проверка подключения
psql -U library_user -d library_system -h localhost

Проблемы с портами

# Проверка занятых портов
sudo netstat -tulpn | grep :3001
sudo netstat -tulpn | grep :8080

Проблемы с правами доступа

# Проверка прав на файлы
ls -la /var/www/library_system

# Исправление прав
sudo chown -R www-data:www-data /var/www/library_system