Руководство по развертыванию¶
Содержание¶
- Требования
- Локальное развертывание
- Развертывание с Docker
- Production (docker-compose.yml)
- Production развертывание
- Фоновые задачи: очередь и просроченные брони
- Настройка Nginx
- Настройка SSL
- Резервное копирование
- Мониторинг и логирование
- Обновление и 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
Миграции 001–049 создают схему; 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.
Резервное копирование¶
Через панель администратора (рекомендуется)¶
- В корневом
.envили в environment backend:BACKUP_ENABLED=true - Каталог бэкапов:
BACKUP_DIR=/app/backups(в Docker смонтирован./backups) - Контейнер backend запускается от пользователя
nodejs(uid 1001); каталог./backupsна хосте должен быть доступен на запись (entrypoint выполняетchownпри старте) - Администрирование → Система: создание дампа, список, скачивание, восстановление из файла
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, BearerMETRICS_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
Безопасность¶
Рекомендации¶
-
Firewall: Настройте firewall для ограничения доступа
sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable -
Обновления: Регулярно обновляйте систему и зависимости
sudo apt update && sudo apt upgrade npm audit fix -
Пароли: Используйте сильные пароли для БД и JWT секретов
-
Права доступа: Ограничьте права доступа к файлам
sudo chown -R www-data:www-data /var/www/library_system sudo chmod -R 755 /var/www/library_system -
Логирование: Настройте ротацию логов
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-чанков.
- Жёсткая перезагрузка: Ctrl+Shift+R
- Или «Очистить данные и войти заново» в приложении
- На сервере:
docker compose up -d --build frontend nginx - В nginx для
index.htmlдолжен бытьCache-Control: no-store(см.nginx.conf,nginx.quick.conf)
Приложение также пытается перезагрузиться автоматически (src/lib/chunkLoadRecovery.ts).
Очередь бронирований не продвигается¶
- Убедитесь, что запущен контейнер outbox-worker (
docker compose ps) илиOUTBOX_WORKER_ENABLED=trueпри локальномnode index.js - Проверьте, что backend и frontend обновлены вместе (синхронное продвижение после возврата/отмены — в API)
- Запустите
scripts/process-outbox-events.jsи проверьте счётчик необработанных событий (см. выше) - При «зависших» событиях:
scripts/reconcile-outbox-stuck.js - Убедитесь, что у читателя в голове очереди меньше 5 активных броней и учётная запись активна
- Проверьте, не блокирует ли экземпляр просроченная
pending-бронь — при новых версиях она снимается автоматически при следующем обращении к очереди; иначеPOST /api/bookings/cleanup-expiredили cronbooking-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