full
Deploy to Production / deploy-production (push) Successful in 26s
Details
Deploy to Production / deploy-production (push) Successful in 26s
Details
This commit is contained in:
parent
083fd4d826
commit
118f33f77b
|
|
@ -0,0 +1,93 @@
|
||||||
|
# Настройка автоматического резервного копирования БД
|
||||||
|
|
||||||
|
## 🎯 Автоматический бэкап дважды в день
|
||||||
|
|
||||||
|
Система автоматически создаёт бэкапы PROD и DEV БД:
|
||||||
|
- **00:00** (полночь)
|
||||||
|
- **12:00** (полдень)
|
||||||
|
|
||||||
|
## 📋 Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /var/www/platform/prod
|
||||||
|
|
||||||
|
# Сделать скрипты исполняемыми
|
||||||
|
chmod +x backup-db-auto.sh setup-cron-backup.sh remove-cron-backup.sh
|
||||||
|
|
||||||
|
# Настроить автоматический бэкап
|
||||||
|
./setup-cron-backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Проверка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить, что задача добавлена в cron
|
||||||
|
crontab -l | grep backup-db-auto
|
||||||
|
|
||||||
|
# Должно быть:
|
||||||
|
# 0 0,12 * * * PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /var/www/platform/prod/backup-db-auto.sh >> /var/www/platform/prod/backups/cron.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Логи
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Логи автоматических бэкапов
|
||||||
|
tail -f /var/www/platform/prod/backups/backup.log
|
||||||
|
|
||||||
|
# Логи cron (ошибки выполнения)
|
||||||
|
tail -f /var/www/platform/prod/backups/cron.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🗂️ Хранение бэкапов
|
||||||
|
|
||||||
|
- **Директория**: `/var/www/platform/prod/backups/`
|
||||||
|
- **Формат файлов**: `platform_prod_db_YYYYMMDD_HHMMSS.sql.gz`
|
||||||
|
- **Автоочистка**: Бэкапы старше 30 дней удаляются автоматически
|
||||||
|
- **Проверка места**: При использовании диска > 80% в лог пишется предупреждение
|
||||||
|
|
||||||
|
## 🔄 Ручной запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запустить бэкап вручную (для тестирования)
|
||||||
|
/var/www/platform/prod/backup-db-auto.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🗑️ Удаление автоматического бэкапа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Удалить задачу из cron
|
||||||
|
./remove-cron-backup.sh
|
||||||
|
|
||||||
|
# Или вручную
|
||||||
|
crontab -l | grep -v backup-db-auto | crontab -
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Что делает скрипт
|
||||||
|
|
||||||
|
1. ✅ Проверяет, что контейнеры БД запущены
|
||||||
|
2. ✅ Создаёт бэкапы PROD и DEV БД
|
||||||
|
3. ✅ Сжимает бэкапы (gzip)
|
||||||
|
4. ✅ Проверяет размер бэкапов
|
||||||
|
5. ✅ Удаляет бэкапы старше 30 дней
|
||||||
|
6. ✅ Логирует все действия
|
||||||
|
7. ✅ Предупреждает о нехватке места на диске
|
||||||
|
|
||||||
|
## ⚠️ Важно
|
||||||
|
|
||||||
|
- Скрипт работает от пользователя `root` (нужен доступ к Docker)
|
||||||
|
- Бэкапы сохраняются в `/var/www/platform/prod/backups/`
|
||||||
|
- Старые бэкапы (30+ дней) удаляются автоматически
|
||||||
|
- При ошибках информация записывается в лог
|
||||||
|
|
||||||
|
## 🔍 Мониторинг
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Посмотреть последние бэкапы
|
||||||
|
ls -lh /var/www/platform/prod/backups/*.sql.gz | tail -10
|
||||||
|
|
||||||
|
# Проверить размер всех бэкапов
|
||||||
|
du -sh /var/www/platform/prod/backups/
|
||||||
|
|
||||||
|
# Посмотреть последние записи в логе
|
||||||
|
tail -20 /var/www/platform/prod/backups/backup.log
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
# PROD Окружение - Инструкция по управлению
|
||||||
|
|
||||||
|
## ⚠️ ВАЖНО: Защита данных
|
||||||
|
|
||||||
|
**PROD окружение использует отдельную сеть (`prod_network`) и именованные volumes для изоляции от dev.**
|
||||||
|
|
||||||
|
### Что было исправлено:
|
||||||
|
|
||||||
|
1. ✅ **Отдельная сеть** - `prod_network` вместо общей `dev_network`
|
||||||
|
2. ✅ **Именованные volumes** - все volumes имеют префикс `platform_prod_`
|
||||||
|
3. ✅ **Полные имена контейнеров** - в `DATABASE_URL` и `REDIS_URL` используются полные имена контейнеров
|
||||||
|
4. ✅ **Изоляция от dev** - prod не может случайно подключиться к dev БД
|
||||||
|
|
||||||
|
## 📋 Основные команды
|
||||||
|
|
||||||
|
### Безопасная остановка (СОХРАНЯЕТ данные БД):
|
||||||
|
```bash
|
||||||
|
# Использовать скрипт из /var/www/service
|
||||||
|
/var/www/service/platform/safe-down-prod.sh
|
||||||
|
|
||||||
|
# Или вручную
|
||||||
|
cd /var/www/platform/prod
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⚠️ ОСТОРОЖНО: Полная очистка (УДАЛЯЕТ данные БД):
|
||||||
|
```bash
|
||||||
|
# Сначала создайте бэкап!
|
||||||
|
/var/www/service/backup/backup-prod-db.sh
|
||||||
|
|
||||||
|
# Затем можно удалить volumes
|
||||||
|
cd /var/www/platform/prod
|
||||||
|
docker compose down --volumes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Запуск:
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Создание бэкапа БД:
|
||||||
|
```bash
|
||||||
|
# Бэкап PROD БД
|
||||||
|
/var/www/service/backup/backup-all-db.sh
|
||||||
|
|
||||||
|
# Альтернативный скрипт для PROD БД
|
||||||
|
/var/www/service/backup/backup-prod-db.sh
|
||||||
|
|
||||||
|
# Автоматический бэкап PROD БД (для cron)
|
||||||
|
/var/www/service/backup/backup-db-auto.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Примечание:** DEV БД не бэкапится, так как это окружение разработки.
|
||||||
|
|
||||||
|
### Настройка автоматического бэкапа PROD БД (2 раза в день: 00:00 и 12:00):
|
||||||
|
```bash
|
||||||
|
# Установить автоматический бэкап PROD БД
|
||||||
|
/var/www/service/backup/setup-cron-backup.sh
|
||||||
|
|
||||||
|
# Удалить автоматический бэкап
|
||||||
|
/var/www/service/backup/remove-cron-backup.sh
|
||||||
|
|
||||||
|
# Проверить расписание
|
||||||
|
crontab -l | grep backup-db-auto
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
tail -f /var/www/platform/prod/backups/backup.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**Примечание:** Автоматически бэкапится только PROD БД. DEV БД не бэкапится.
|
||||||
|
|
||||||
|
### Полная пересборка PROD (с бэкапом):
|
||||||
|
```bash
|
||||||
|
/var/www/service/platform/rebuild-prod.sh
|
||||||
|
```
|
||||||
|
Этот скрипт:
|
||||||
|
1. Создаёт бэкап БД
|
||||||
|
2. Останавливает контейнеры
|
||||||
|
3. Пересобирает образы без кэша
|
||||||
|
4. Запускает контейнеры
|
||||||
|
5. Применяет миграции
|
||||||
|
|
||||||
|
### Применение миграций:
|
||||||
|
```bash
|
||||||
|
docker exec platform_prod_web python manage.py migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Создание суперпользователя:
|
||||||
|
```bash
|
||||||
|
docker exec -it platform_prod_web python manage.py createsuperuser
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Структура volumes
|
||||||
|
|
||||||
|
- `platform_prod_postgres_data` - данные PostgreSQL БД
|
||||||
|
- `platform_prod_redis_data` - данные Redis
|
||||||
|
- `platform_prod_front_material_node_modules` - node_modules для frontend
|
||||||
|
- `platform_prod_front_material_next` - кэш Next.js
|
||||||
|
|
||||||
|
## 🌐 Сеть
|
||||||
|
|
||||||
|
- **Prod сеть**: `platform_prod_network` (изолирована от dev)
|
||||||
|
- **Dev сеть**: `dev_network` (отдельная)
|
||||||
|
|
||||||
|
## 🔗 Подключения
|
||||||
|
|
||||||
|
Все сервисы используют полные имена контейнеров:
|
||||||
|
- БД: `platform_prod_db` (не `db`)
|
||||||
|
- Redis: `platform_prod_redis` (не `redis`)
|
||||||
|
|
||||||
|
Это гарантирует, что даже при запуске dev и prod одновременно, они не будут конфликтовать.
|
||||||
|
|
||||||
|
## 📝 Что делать если данные потеряны
|
||||||
|
|
||||||
|
1. Проверьте бэкапы: `ls -la ./backups/`
|
||||||
|
2. Если бэкапа нет, но данные есть в dev БД, можно скопировать:
|
||||||
|
```bash
|
||||||
|
# Создать бэкап из dev
|
||||||
|
docker exec platform_dev_db pg_dump -U platform_dev_user -d platform_dev_db > /tmp/dev_backup.sql
|
||||||
|
|
||||||
|
# Применить миграции в prod
|
||||||
|
docker exec platform_prod_web python manage.py migrate
|
||||||
|
|
||||||
|
# Восстановить данные (осторожно!)
|
||||||
|
docker exec -i platform_prod_db psql -U platform_prod_user -d platform_prod_db < /tmp/dev_backup.sql
|
||||||
|
```
|
||||||
|
3. Если данных нет нигде - создайте пользователей заново через `createsuperuser`
|
||||||
|
|
||||||
|
## 🚨 Частые ошибки
|
||||||
|
|
||||||
|
### ❌ НЕ делайте:
|
||||||
|
- `docker compose down --volumes` без бэкапа
|
||||||
|
- Использование коротких имен (`db`, `redis`) в переменных окружения
|
||||||
|
- Общая сеть для dev и prod
|
||||||
|
|
||||||
|
### ✅ Делайте:
|
||||||
|
- Всегда используйте `docker compose down` (без `--volumes`)
|
||||||
|
- Регулярно создавайте бэкапы PROD БД: `/var/www/service/backup/backup-all-db.sh`
|
||||||
|
- Используйте полные имена контейнеров в конфигурации
|
||||||
|
|
||||||
|
## 📁 Расположение скриптов
|
||||||
|
|
||||||
|
Все служебные скрипты перенесены в `/var/www/service/`:
|
||||||
|
|
||||||
|
- **Бэкапы**: `/var/www/service/backup/`
|
||||||
|
- **Управление платформой**: `/var/www/service/platform/`
|
||||||
|
|
||||||
|
Подробнее: `/var/www/service/README.md`
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Инструкция по пересборке PROD и созданию бэкапов
|
||||||
|
|
||||||
|
## 🎯 Что нужно сделать:
|
||||||
|
|
||||||
|
### 1. Создать бэкап PROD БД
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Сделать скрипты исполняемыми (первый раз)
|
||||||
|
chmod +x /var/www/service/backup/*.sh
|
||||||
|
chmod +x /var/www/service/platform/*.sh
|
||||||
|
|
||||||
|
# Создать бэкап PROD БД
|
||||||
|
/var/www/service/backup/backup-all-db.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Это создаст бэкап:
|
||||||
|
- `/var/www/platform/prod/backups/platform_prod_db_YYYYMMDD_HHMMSS.sql.gz`
|
||||||
|
|
||||||
|
**Примечание:** DEV БД не бэкапится, так как это окружение разработки.
|
||||||
|
|
||||||
|
### 2. Пересобрать PROD окружение
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Автоматическая пересборка (с бэкапом)
|
||||||
|
/var/www/service/platform/rebuild-prod.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Или вручную:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /var/www/platform/prod
|
||||||
|
|
||||||
|
# Остановить контейнеры
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Пересобрать без кэша
|
||||||
|
docker compose build --no-cache --pull
|
||||||
|
|
||||||
|
# Запустить
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Подождать запуска БД
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Применить миграции
|
||||||
|
docker exec platform_prod_web python manage.py migrate
|
||||||
|
|
||||||
|
# Проверить статус
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Проверить, что всё работает
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить логи
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
# Проверить подключение к БД
|
||||||
|
docker exec platform_prod_web python manage.py shell -c "from django.db import connection; print('DB:', connection.settings_dict['NAME'])"
|
||||||
|
|
||||||
|
# Проверить количество пользователей (если таблица существует)
|
||||||
|
docker exec platform_prod_db psql -U platform_prod_user -d platform_prod_db -c "SELECT COUNT(*) FROM users_user;" 2>/dev/null || echo "Таблица не создана, нужно применить миграции"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Если пользователей нет - создать суперпользователя
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it platform_prod_web python manage.py createsuperuser
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Что было исправлено:
|
||||||
|
|
||||||
|
✅ **Отдельная сеть для prod** - `prod_network` (изолирована от dev)
|
||||||
|
✅ **Именованные volumes** - все volumes имеют префикс `platform_prod_`
|
||||||
|
✅ **Полные имена контейнеров** - используются `platform_prod_db` и `platform_prod_redis`
|
||||||
|
✅ **Защита от случайного удаления данных** - volumes не удаляются при `docker compose down`
|
||||||
|
|
||||||
|
## ⚠️ Важно:
|
||||||
|
|
||||||
|
- **НЕ используйте** `docker compose down --volumes` без бэкапа!
|
||||||
|
- Всегда создавайте бэкапы перед пересборкой
|
||||||
|
- Используйте `./safe-down.sh` для безопасной остановки
|
||||||
|
|
||||||
|
## 🔄 Восстановление из бэкапа (если нужно):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Восстановить PROD БД
|
||||||
|
gunzip < /var/www/platform/prod/backups/platform_prod_db_*.sql.gz | docker exec -i platform_prod_db psql -U platform_prod_user -d postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 Расположение скриптов
|
||||||
|
|
||||||
|
Все служебные скрипты находятся в `/var/www/service/`:
|
||||||
|
|
||||||
|
- **Бэкапы**: `/var/www/service/backup/`
|
||||||
|
- **Управление платформой**: `/var/www/service/platform/`
|
||||||
|
|
||||||
|
Подробнее: `/var/www/service/README.md`
|
||||||
|
|
@ -403,11 +403,12 @@ def start_lessons_automatically():
|
||||||
logger.info(f'Занятие {lesson.id} автоматически переведено в статус "in_progress"')
|
logger.info(f'Занятие {lesson.id} автоматически переведено в статус "in_progress"')
|
||||||
|
|
||||||
# Находим занятия, которые уже прошли и должны быть завершены
|
# Находим занятия, которые уже прошли и должны быть завершены
|
||||||
# end_time < now (время окончания прошло)
|
# end_time < now - 5 минут (время окончания прошло более 5 минут назад - даём время на завершение)
|
||||||
# status in ['scheduled', 'in_progress'] (еще не завершены)
|
# status in ['scheduled', 'in_progress'] (еще не завершены)
|
||||||
|
five_minutes_ago = now - timedelta(minutes=5)
|
||||||
lessons_to_complete = Lesson.objects.filter(
|
lessons_to_complete = Lesson.objects.filter(
|
||||||
status__in=['scheduled', 'in_progress'],
|
status__in=['scheduled', 'in_progress'],
|
||||||
end_time__lt=now
|
end_time__lt=five_minutes_ago
|
||||||
).select_related('mentor', 'client')
|
).select_related('mentor', 'client')
|
||||||
|
|
||||||
# Оптимизация: используем bulk_update вместо цикла с save()
|
# Оптимизация: используем bulk_update вместо цикла с save()
|
||||||
|
|
@ -420,6 +421,22 @@ def start_lessons_automatically():
|
||||||
completed_count = len(lessons_to_complete_list)
|
completed_count = len(lessons_to_complete_list)
|
||||||
for lesson in lessons_to_complete_list:
|
for lesson in lessons_to_complete_list:
|
||||||
logger.info(f'Занятие {lesson.id} автоматически переведено в статус "completed" (время окончания прошло)')
|
logger.info(f'Занятие {lesson.id} автоматически переведено в статус "completed" (время окончания прошло)')
|
||||||
|
|
||||||
|
# Закрываем LiveKit комнату, если она есть
|
||||||
|
try:
|
||||||
|
from apps.video.models import VideoRoom
|
||||||
|
from apps.video.services import get_sfu_client, SFUClientError
|
||||||
|
|
||||||
|
video_room = VideoRoom.objects.filter(lesson=lesson).first()
|
||||||
|
if video_room and video_room.room_id:
|
||||||
|
sfu_client = get_sfu_client()
|
||||||
|
try:
|
||||||
|
sfu_client.delete_room(str(video_room.room_id))
|
||||||
|
logger.info(f'LiveKit комната {video_room.room_id} закрыта для урока {lesson.id}')
|
||||||
|
except SFUClientError as e:
|
||||||
|
logger.warning(f'Не удалось закрыть LiveKit комнату {video_room.room_id} для урока {lesson.id}: {e}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Ошибка закрытия LiveKit комнаты для урока {lesson.id}: {str(e)}', exc_info=True)
|
||||||
|
|
||||||
if started_count > 0 or completed_count > 0:
|
if started_count > 0 or completed_count > 0:
|
||||||
logger.info(f'[start_lessons_automatically] Начато: {started_count}, Завершено: {completed_count}')
|
logger.info(f'[start_lessons_automatically] Начато: {started_count}, Завершено: {completed_count}')
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для создания бэкапов БД PROD и DEV
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BACKUP_DIR="./backups"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Создание бэкапов БД (PROD и DEV)"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Создать директорию для бэкапов
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Функция для создания бэкапа
|
||||||
|
backup_db() {
|
||||||
|
local CONTAINER_NAME=$1
|
||||||
|
local DB_USER=$2
|
||||||
|
local DB_NAME=$3
|
||||||
|
local BACKUP_NAME=$4
|
||||||
|
|
||||||
|
echo "Создание бэкапа: $BACKUP_NAME"
|
||||||
|
|
||||||
|
# Проверить, что контейнер запущен
|
||||||
|
if ! docker ps | grep -q "$CONTAINER_NAME"; then
|
||||||
|
echo "⚠️ Контейнер $CONTAINER_NAME не запущен, пропускаем..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/${BACKUP_NAME}_${TIMESTAMP}.sql.gz"
|
||||||
|
|
||||||
|
# Создать бэкап
|
||||||
|
if docker exec "$CONTAINER_NAME" pg_dumpall -U "$DB_USER" -c 2>/dev/null | gzip > "$BACKUP_FILE"; then
|
||||||
|
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||||
|
echo " ✓ Бэкап создан: $BACKUP_FILE ($BACKUP_SIZE)"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ✗ Ошибка создания бэкапа для $BACKUP_NAME"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Бэкап PROD БД
|
||||||
|
echo "--- PROD БД ---"
|
||||||
|
backup_db "platform_prod_db" "platform_prod_user" "platform_prod_db" "platform_prod_db"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Бэкап DEV БД
|
||||||
|
echo "--- DEV БД ---"
|
||||||
|
backup_db "platform_dev_db" "platform_dev_user" "platform_dev_db" "platform_dev_db"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Бэкапы сохранены в: $BACKUP_DIR"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Для восстановления PROD БД:"
|
||||||
|
echo " gunzip < $BACKUP_DIR/platform_prod_db_*.sql.gz | docker exec -i platform_prod_db psql -U platform_prod_user -d postgres"
|
||||||
|
echo ""
|
||||||
|
echo "Для восстановления DEV БД:"
|
||||||
|
echo " gunzip < $BACKUP_DIR/platform_dev_db_*.sql.gz | docker exec -i platform_dev_db psql -U platform_dev_user -d postgres"
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Автоматический скрипт для создания бэкапов БД PROD и DEV
|
||||||
|
# Запускается через cron дважды в день (00:00 и 12:00)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BACKUP_DIR="/var/www/platform/prod/backups"
|
||||||
|
LOG_FILE="/var/www/platform/prod/backups/backup.log"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
DATE=$(date +%Y-%m-%d\ %H:%M:%S)
|
||||||
|
|
||||||
|
# Создать директорию для бэкапов и логов
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Функция для логирования
|
||||||
|
log() {
|
||||||
|
echo "[$DATE] $1" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "=========================================="
|
||||||
|
log "Начало автоматического бэкапа БД"
|
||||||
|
log "=========================================="
|
||||||
|
|
||||||
|
# Функция для создания бэкапа
|
||||||
|
backup_db() {
|
||||||
|
local CONTAINER_NAME=$1
|
||||||
|
local DB_USER=$2
|
||||||
|
local DB_NAME=$3
|
||||||
|
local BACKUP_NAME=$4
|
||||||
|
|
||||||
|
log "Создание бэкапа: $BACKUP_NAME"
|
||||||
|
|
||||||
|
# Проверить, что контейнер запущен
|
||||||
|
if ! docker ps | grep -q "$CONTAINER_NAME"; then
|
||||||
|
log "⚠️ Контейнер $CONTAINER_NAME не запущен, пропускаем..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/${BACKUP_NAME}_${TIMESTAMP}.sql.gz"
|
||||||
|
|
||||||
|
# Создать бэкап
|
||||||
|
if docker exec "$CONTAINER_NAME" pg_dumpall -U "$DB_USER" -c 2>/dev/null | gzip > "$BACKUP_FILE"; then
|
||||||
|
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||||
|
log " ✓ Бэкап создан: $BACKUP_FILE ($BACKUP_SIZE)"
|
||||||
|
|
||||||
|
# Проверить размер файла (должен быть больше 0)
|
||||||
|
if [ ! -s "$BACKUP_FILE" ]; then
|
||||||
|
log " ✗ ОШИБКА: Бэкап пустой!"
|
||||||
|
rm -f "$BACKUP_FILE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log " ✗ Ошибка создания бэкапа для $BACKUP_NAME"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Бэкап PROD БД
|
||||||
|
PROD_SUCCESS=false
|
||||||
|
if backup_db "platform_prod_db" "platform_prod_user" "platform_prod_db" "platform_prod_db"; then
|
||||||
|
PROD_SUCCESS=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Бэкап DEV БД
|
||||||
|
DEV_SUCCESS=false
|
||||||
|
if backup_db "platform_dev_db" "platform_dev_user" "platform_dev_db" "platform_dev_db"; then
|
||||||
|
DEV_SUCCESS=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Очистка старых бэкапов (оставляем последние 30 дней)
|
||||||
|
log "Очистка старых бэкапов (старше 30 дней)..."
|
||||||
|
find "$BACKUP_DIR" -name "*.sql.gz" -type f -mtime +30 -delete 2>/dev/null || true
|
||||||
|
DELETED_COUNT=$(find "$BACKUP_DIR" -name "*.sql.gz" -type f 2>/dev/null | wc -l)
|
||||||
|
log "Осталось бэкапов: $DELETED_COUNT"
|
||||||
|
|
||||||
|
# Итоги
|
||||||
|
log "=========================================="
|
||||||
|
if [ "$PROD_SUCCESS" = true ] && [ "$DEV_SUCCESS" = true ]; then
|
||||||
|
log "✓ Бэкапы созданы успешно (PROD и DEV)"
|
||||||
|
elif [ "$PROD_SUCCESS" = true ]; then
|
||||||
|
log "⚠️ Бэкап PROD создан, DEV пропущен"
|
||||||
|
elif [ "$DEV_SUCCESS" = true ]; then
|
||||||
|
log "⚠️ Бэкап DEV создан, PROD пропущен"
|
||||||
|
else
|
||||||
|
log "✗ Ошибка: бэкапы не созданы!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "=========================================="
|
||||||
|
|
||||||
|
# Проверка места на диске
|
||||||
|
DISK_USAGE=$(df -h "$BACKUP_DIR" | tail -1 | awk '{print $5}' | sed 's/%//')
|
||||||
|
if [ "$DISK_USAGE" -gt 80 ]; then
|
||||||
|
log "⚠️ ВНИМАНИЕ: Использовано дискового пространства: ${DISK_USAGE}%"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для создания бэкапа БД PROD
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BACKUP_DIR="./backups"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/platform_prod_db_backup_$TIMESTAMP.sql.gz"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Создание бэкапа PROD БД"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Создать директорию для бэкапов
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Проверить, что контейнер БД запущен
|
||||||
|
if ! docker ps | grep -q platform_prod_db; then
|
||||||
|
echo "Ошибка: Контейнер platform_prod_db не запущен"
|
||||||
|
echo "Запустите БД: docker compose up -d db"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Создание бэкапа..."
|
||||||
|
echo "Файл: $BACKUP_FILE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Создать бэкап
|
||||||
|
if docker exec platform_prod_db pg_dumpall -U platform_prod_user -c | gzip > "$BACKUP_FILE"; then
|
||||||
|
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||||
|
echo "✓ Бэкап создан успешно"
|
||||||
|
echo " Размер: $BACKUP_SIZE"
|
||||||
|
echo " Файл: $BACKUP_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "Для восстановления:"
|
||||||
|
echo " gunzip < $BACKUP_FILE | docker exec -i platform_prod_db psql -U platform_prod_user -d postgres"
|
||||||
|
else
|
||||||
|
echo "✗ Ошибка создания бэкапа!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
# front_material 3010, yjs 1236, excalidraw 3004, whiteboard 8083,
|
# front_material 3010, yjs 1236, excalidraw 3004, whiteboard 8083,
|
||||||
# livekit 7880/7881, celery/beat — без портов (внутренние)
|
# livekit 7880/7881, celery/beat — без портов (внутренние)
|
||||||
# Dev использует: 5433, 6380, 8124, 8081, 3002, 1235, 3003, 8082, livekit 7890/7891
|
# Dev использует: 5433, 6380, 8124, 8081, 3002, 1235, 3003, 8082, livekit 7890/7891
|
||||||
|
#
|
||||||
|
# ВАЖНО: PROD использует отдельную сеть (prod_network) и именованные volumes
|
||||||
|
# НЕ используйте: docker compose down --volumes (удалит данные БД!)
|
||||||
|
# Используйте: docker compose down (остановит контейнеры, сохранит volumes)
|
||||||
|
# Для полной очистки: сначала сделайте бэкап БД, затем docker compose down --volumes
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
|
|
@ -20,7 +25,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- prod_postgres_data:/var/lib/postgresql/data
|
- prod_postgres_data:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
|
|
@ -31,7 +36,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- prod_redis_data:/data
|
- prod_redis_data:/data
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build:
|
build:
|
||||||
|
|
@ -44,13 +49,15 @@ services:
|
||||||
# Daphne (ASGI): HTTP + WebSocket (/ws/notifications/, /ws/chat/, /ws/board/ и т.д.)
|
# Daphne (ASGI): HTTP + WebSocket (/ws/notifications/, /ws/chat/, /ws/board/ и т.д.)
|
||||||
command: sh -c "python manage.py migrate && python manage.py init_subjects && daphne -b 0.0.0.0 -p 8000 config.asgi:application"
|
command: sh -c "python manage.py migrate && python manage.py init_subjects && daphne -b 0.0.0.0 -p 8000 config.asgi:application"
|
||||||
environment:
|
environment:
|
||||||
- DEBUG=${DEBUG:-True}
|
- DEBUG=${DEBUG:-False}
|
||||||
- SECRET_KEY=dev_secret_key
|
- SECRET_KEY=${SECRET_KEY}
|
||||||
- ALLOWED_HOSTS=api.uchill.online,app.uchill.online,uchill.online,www.uchill.online,localhost,127.0.0.1,85.192.56.185
|
- ALLOWED_HOSTS=${ALLOWED_HOSTS}
|
||||||
- DATABASE_URL=postgresql://platform_prod_user:platform_prod_password@db:5432/platform_prod_db
|
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}
|
||||||
- REDIS_URL=redis://redis:6379/0
|
- CSRF_TRUSTED_ORIGINS=${CSRF_TRUSTED_ORIGINS}
|
||||||
- CELERY_BROKER_URL=redis://redis:6379/1
|
- DATABASE_URL=postgresql://platform_prod_user:platform_prod_password@platform_prod_db:5432/platform_prod_db
|
||||||
- CELERY_RESULT_BACKEND=redis://redis:6379/2
|
- REDIS_URL=redis://platform_prod_redis:6379/0
|
||||||
|
- CELERY_BROKER_URL=redis://platform_prod_redis:6379/1
|
||||||
|
- CELERY_RESULT_BACKEND=redis://platform_prod_redis:6379/2
|
||||||
# Явно передаём переменные почты из .env (иначе контейнер может не видеть их)
|
# Явно передаём переменные почты из .env (иначе контейнер может не видеть их)
|
||||||
- EMAIL_BACKEND=${EMAIL_BACKEND:-smtp}
|
- EMAIL_BACKEND=${EMAIL_BACKEND:-smtp}
|
||||||
- EMAIL_HOST=${EMAIL_HOST}
|
- EMAIL_HOST=${EMAIL_HOST}
|
||||||
|
|
@ -78,7 +85,7 @@ services:
|
||||||
- db
|
- db
|
||||||
- redis
|
- redis
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
celery:
|
celery:
|
||||||
build:
|
build:
|
||||||
|
|
@ -90,11 +97,11 @@ services:
|
||||||
env_file: .env
|
env_file: .env
|
||||||
command: celery -A config worker -l info
|
command: celery -A config worker -l info
|
||||||
environment:
|
environment:
|
||||||
- DEBUG=${DEBUG:-True}
|
- DEBUG=${DEBUG:-False}
|
||||||
- DATABASE_URL=postgresql://platform_prod_user:platform_prod_password@db:5432/platform_prod_db
|
- DATABASE_URL=postgresql://platform_prod_user:platform_prod_password@platform_prod_db:5432/platform_prod_db
|
||||||
- REDIS_URL=redis://redis:6379/0
|
- REDIS_URL=redis://platform_prod_redis:6379/0
|
||||||
- CELERY_BROKER_URL=redis://redis:6379/1
|
- CELERY_BROKER_URL=redis://platform_prod_redis:6379/1
|
||||||
- CELERY_RESULT_BACKEND=redis://redis:6379/2
|
- CELERY_RESULT_BACKEND=redis://platform_prod_redis:6379/2
|
||||||
- EMAIL_BACKEND=${EMAIL_BACKEND:-smtp}
|
- EMAIL_BACKEND=${EMAIL_BACKEND:-smtp}
|
||||||
- EMAIL_HOST=${EMAIL_HOST}
|
- EMAIL_HOST=${EMAIL_HOST}
|
||||||
- EMAIL_PORT=${EMAIL_PORT:-2525}
|
- EMAIL_PORT=${EMAIL_PORT:-2525}
|
||||||
|
|
@ -116,7 +123,7 @@ services:
|
||||||
- redis
|
- redis
|
||||||
- web
|
- web
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
celery-beat:
|
celery-beat:
|
||||||
build:
|
build:
|
||||||
|
|
@ -128,11 +135,11 @@ services:
|
||||||
env_file: .env
|
env_file: .env
|
||||||
command: celery -A config beat -l info
|
command: celery -A config beat -l info
|
||||||
environment:
|
environment:
|
||||||
- DEBUG=${DEBUG:-True}
|
- DEBUG=${DEBUG:-False}
|
||||||
- DATABASE_URL=postgresql://platform_prod_user:platform_prod_password@db:5432/platform_prod_db
|
- DATABASE_URL=postgresql://platform_prod_user:platform_prod_password@platform_prod_db:5432/platform_prod_db
|
||||||
- REDIS_URL=redis://redis:6379/0
|
- REDIS_URL=redis://platform_prod_redis:6379/0
|
||||||
- CELERY_BROKER_URL=redis://redis:6379/1
|
- CELERY_BROKER_URL=redis://platform_prod_redis:6379/1
|
||||||
- CELERY_RESULT_BACKEND=redis://redis:6379/2
|
- CELERY_RESULT_BACKEND=redis://platform_prod_redis:6379/2
|
||||||
- EMAIL_BACKEND=${EMAIL_BACKEND:-smtp}
|
- EMAIL_BACKEND=${EMAIL_BACKEND:-smtp}
|
||||||
- EMAIL_HOST=${EMAIL_HOST}
|
- EMAIL_HOST=${EMAIL_HOST}
|
||||||
- EMAIL_PORT=${EMAIL_PORT:-2525}
|
- EMAIL_PORT=${EMAIL_PORT:-2525}
|
||||||
|
|
@ -154,7 +161,7 @@ services:
|
||||||
- redis
|
- redis
|
||||||
- web
|
- web
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
# Telegram бот (polling): получает /start, /link <код> и т.д. Если используете webhook — не поднимайте этот сервис.
|
# Telegram бот (polling): получает /start, /link <код> и т.д. Если используете webhook — не поднимайте этот сервис.
|
||||||
telegram-bot:
|
telegram-bot:
|
||||||
|
|
@ -167,9 +174,9 @@ services:
|
||||||
env_file: .env
|
env_file: .env
|
||||||
command: python manage.py runtelegrambot
|
command: python manage.py runtelegrambot
|
||||||
environment:
|
environment:
|
||||||
- DEBUG=${DEBUG:-True}
|
- DEBUG=${DEBUG:-False}
|
||||||
- DATABASE_URL=postgresql://platform_prod_user:platform_prod_password@db:5432/platform_prod_db
|
- DATABASE_URL=postgresql://platform_prod_user:platform_prod_password@platform_prod_db:5432/platform_prod_db
|
||||||
- REDIS_URL=redis://redis:6379/0
|
- REDIS_URL=redis://platform_prod_redis:6379/0
|
||||||
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
|
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
|
||||||
- TELEGRAM_USE_WEBHOOK=${TELEGRAM_USE_WEBHOOK:-False}
|
- TELEGRAM_USE_WEBHOOK=${TELEGRAM_USE_WEBHOOK:-False}
|
||||||
- TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL:-}
|
- TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL:-}
|
||||||
|
|
@ -181,7 +188,7 @@ services:
|
||||||
- redis
|
- redis
|
||||||
- web
|
- web
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
# Видеоуроки: хост nginx (api.uchill.online) проксирует /livekit на 7880. Dev на том же хосте — 7890.
|
# Видеоуроки: хост nginx (api.uchill.online) проксирует /livekit на 7880. Dev на том же хосте — 7890.
|
||||||
# LIVEKIT_KEYS — строго один ключ в формате "key: secret" (пробел после двоеточия). В .env задайте одну строку: LIVEKIT_KEYS=APIKeyPlatform2024Secret: ThisIsAVerySecureSecretKeyForPlatform2024VideoConf
|
# LIVEKIT_KEYS — строго один ключ в формате "key: secret" (пробел после двоеточия). В .env задайте одну строку: LIVEKIT_KEYS=APIKeyPlatform2024Secret: ThisIsAVerySecureSecretKeyForPlatform2024VideoConf
|
||||||
|
|
@ -196,7 +203,7 @@ services:
|
||||||
- "7880:7880"
|
- "7880:7880"
|
||||||
- "7881:7881"
|
- "7881:7881"
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
|
|
@ -210,32 +217,31 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- web
|
- web
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
front_material:
|
front_material:
|
||||||
build:
|
build:
|
||||||
context: ./front_material
|
context: ./front_material
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
target: development
|
target: production
|
||||||
|
args:
|
||||||
|
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
|
- NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL}
|
||||||
|
- NEXT_PUBLIC_LIVEKIT_URL=${NEXT_PUBLIC_LIVEKIT_URL}
|
||||||
|
- NEXT_PUBLIC_EXCALIDRAW_URL=${NEXT_PUBLIC_EXCALIDRAW_URL:-}
|
||||||
container_name: platform_prod_front_material
|
container_name: platform_prod_front_material
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file: .env
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=production
|
||||||
- WATCHPACK_POLLING=true
|
|
||||||
- HOSTNAME=0.0.0.0
|
- HOSTNAME=0.0.0.0
|
||||||
- CHOKIDAR_USEPOLLING=true
|
|
||||||
# Доска: поддомен board.uchill.online (прокси nginx на 3004) или путь на том же домене
|
# Доска: поддомен board.uchill.online (прокси nginx на 3004) или путь на том же домене
|
||||||
- NEXT_PUBLIC_EXCALIDRAW_URL=${NEXT_PUBLIC_EXCALIDRAW_URL:-}
|
- NEXT_PUBLIC_EXCALIDRAW_URL=${NEXT_PUBLIC_EXCALIDRAW_URL:-}
|
||||||
- NEXT_PUBLIC_EXCALIDRAW_PATH=${NEXT_PUBLIC_EXCALIDRAW_PATH:-/excalidraw}
|
- NEXT_PUBLIC_EXCALIDRAW_PATH=${NEXT_PUBLIC_EXCALIDRAW_PATH:-/excalidraw}
|
||||||
ports:
|
ports:
|
||||||
- "3010:3000"
|
- "3010:3000"
|
||||||
volumes:
|
|
||||||
- ./front_material:/app
|
|
||||||
- front_material_node_modules:/app/node_modules
|
|
||||||
- front_material_next:/app/.next
|
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
yjs-whiteboard:
|
yjs-whiteboard:
|
||||||
build:
|
build:
|
||||||
|
|
@ -246,21 +252,23 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "1236:1234"
|
- "1236:1234"
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
excalidraw:
|
excalidraw:
|
||||||
build:
|
build:
|
||||||
context: ./excalidraw-server
|
context: ./excalidraw-server
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: production
|
||||||
container_name: platform_prod_excalidraw
|
container_name: platform_prod_excalidraw
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
# basePath в next.config.js: иначе /_next/ запросы уходят на основной фронт и доска пустая
|
- NODE_ENV=production
|
||||||
- NEXT_PUBLIC_BASE_PATH=/excalidraw
|
# Поддомен board.uchill.online: basePath не нужен (приложение на корне поддомена)
|
||||||
|
- NEXT_PUBLIC_BASE_PATH=
|
||||||
ports:
|
ports:
|
||||||
- "3004:3001"
|
- "3004:3001"
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
whiteboard:
|
whiteboard:
|
||||||
build:
|
build:
|
||||||
|
|
@ -271,14 +279,21 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8083:8080"
|
- "8083:8080"
|
||||||
networks:
|
networks:
|
||||||
- dev_network
|
- prod_network
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
# ВАЖНО: Эти volumes содержат данные БД и Redis
|
||||||
|
# НЕ используйте docker compose down --volumes без бэкапа!
|
||||||
prod_postgres_data:
|
prod_postgres_data:
|
||||||
|
name: platform_prod_postgres_data
|
||||||
prod_redis_data:
|
prod_redis_data:
|
||||||
|
name: platform_prod_redis_data
|
||||||
front_material_node_modules:
|
front_material_node_modules:
|
||||||
|
name: platform_prod_front_material_node_modules
|
||||||
front_material_next:
|
front_material_next:
|
||||||
|
name: platform_prod_front_material_next
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dev_network:
|
prod_network:
|
||||||
|
name: platform_prod_network
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,49 @@
|
||||||
FROM node:18-alpine
|
# Multi-stage build для production
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
WORKDIR /app
|
|
||||||
|
WORKDIR /app
|
||||||
# Копируем package.json и patches (для patch-package)
|
|
||||||
COPY package*.json ./
|
# Копируем package.json и patches
|
||||||
COPY patches ./patches/
|
COPY package*.json ./
|
||||||
|
COPY patches ./patches/
|
||||||
# Устанавливаем зависимости (postinstall применит патч y-excalidraw)
|
|
||||||
RUN npm install
|
# Устанавливаем зависимости
|
||||||
|
RUN npm ci
|
||||||
# Гарантированно применяем патчи (fix generateKeyBetween при вставке изображения)
|
|
||||||
RUN npx patch-package
|
# Гарантированно применяем патчи
|
||||||
|
RUN npx patch-package
|
||||||
# Копируем все файлы
|
|
||||||
COPY . .
|
# Копируем исходный код
|
||||||
|
COPY . .
|
||||||
# Запуск в dev режиме
|
|
||||||
CMD ["npm", "run", "dev"]
|
# Собираем приложение
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:18-alpine AS production
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Копируем собранное приложение (standalone mode)
|
||||||
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
# Создаём пустую директорию public (если её нет в проекте, Next.js может её использовать)
|
||||||
|
RUN mkdir -p ./public
|
||||||
|
|
||||||
|
# Создаем непривилегированного пользователя
|
||||||
|
RUN addgroup --system --gid 1001 nodejs && \
|
||||||
|
adduser --system --uid 1001 nextjs && \
|
||||||
|
chown -R nextjs:nodejs /app
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3001
|
||||||
|
|
||||||
|
ENV PORT=3001
|
||||||
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,16 @@ export default function ExcalidrawPage() {
|
||||||
|
|
||||||
setBoardId(id);
|
setBoardId(id);
|
||||||
|
|
||||||
// Yjs: хост из apiUrl
|
// Yjs: через nginx /yjs (prod) или прямой порт (dev/localhost)
|
||||||
const port = params.get('yjsPort') || '1234';
|
|
||||||
const apiHost = new URL(api).hostname;
|
const apiHost = new URL(api).hostname;
|
||||||
|
const isLocalhost = apiHost === 'localhost' || apiHost === '127.0.0.1';
|
||||||
const wsProtocol = api.startsWith('https') ? 'wss:' : 'ws:';
|
const wsProtocol = api.startsWith('https') ? 'wss:' : 'ws:';
|
||||||
setWsUrl(`${wsProtocol}//${apiHost}:${port}`);
|
const yjsPort = params.get('yjsPort') || '1234';
|
||||||
|
// Prod: через nginx /yjs (без порта), Dev: прямой порт
|
||||||
|
const wsUrl = isLocalhost
|
||||||
|
? `${wsProtocol}//${apiHost}:${yjsPort}`
|
||||||
|
: `${wsProtocol}//${apiHost}/yjs`;
|
||||||
|
setWsUrl(wsUrl);
|
||||||
|
|
||||||
// Имя из API (UTF-8, без проблем с кодировкой URL/postMessage)
|
// Имя из API (UTF-8, без проблем с кодировкой URL/postMessage)
|
||||||
if (token) {
|
if (token) {
|
||||||
|
|
@ -229,7 +234,6 @@ export default function ExcalidrawPage() {
|
||||||
}, [username]);
|
}, [username]);
|
||||||
|
|
||||||
if (!boardId) {
|
if (!boardId) {
|
||||||
const example = `${window.location.origin}${window.location.pathname}?boardId=your-board-id&apiUrl=http://127.0.0.1:8123`;
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
@ -247,10 +251,6 @@ export default function ExcalidrawPage() {
|
||||||
<code style={{ fontSize: 12, background: '#f5f5f5', padding: '8px 12px', borderRadius: 8, wordBreak: 'break-all' }}>
|
<code style={{ fontSize: 12, background: '#f5f5f5', padding: '8px 12px', borderRadius: 8, wordBreak: 'break-all' }}>
|
||||||
?boardId=xxx&apiUrl=http://127.0.0.1:8123
|
?boardId=xxx&apiUrl=http://127.0.0.1:8123
|
||||||
</code>
|
</code>
|
||||||
<p style={{ margin: 0, fontSize: 14, color: '#666' }}>Пример:</p>
|
|
||||||
<code style={{ fontSize: 11, background: '#f5f5f5', padding: '8px 12px', borderRadius: 8, wordBreak: 'break-all', maxWidth: '100%' }}>
|
|
||||||
{example}
|
|
||||||
</code>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@ WORKDIR /app
|
||||||
ARG NEXT_PUBLIC_API_URL
|
ARG NEXT_PUBLIC_API_URL
|
||||||
ARG NEXT_PUBLIC_WS_URL
|
ARG NEXT_PUBLIC_WS_URL
|
||||||
ARG NEXT_PUBLIC_LIVEKIT_URL
|
ARG NEXT_PUBLIC_LIVEKIT_URL
|
||||||
|
ARG NEXT_PUBLIC_EXCALIDRAW_URL
|
||||||
|
|
||||||
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
||||||
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
|
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
|
||||||
ENV NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL
|
ENV NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL
|
||||||
|
ENV NEXT_PUBLIC_EXCALIDRAW_URL=$NEXT_PUBLIC_EXCALIDRAW_URL
|
||||||
ENV NODE_ENV=development
|
ENV NODE_ENV=development
|
||||||
ENV HOSTNAME=0.0.0.0
|
ENV HOSTNAME=0.0.0.0
|
||||||
ENV WATCHPACK_POLLING=true
|
ENV WATCHPACK_POLLING=true
|
||||||
|
|
@ -59,10 +61,12 @@ WORKDIR /app
|
||||||
ARG NEXT_PUBLIC_API_URL
|
ARG NEXT_PUBLIC_API_URL
|
||||||
ARG NEXT_PUBLIC_WS_URL
|
ARG NEXT_PUBLIC_WS_URL
|
||||||
ARG NEXT_PUBLIC_LIVEKIT_URL
|
ARG NEXT_PUBLIC_LIVEKIT_URL
|
||||||
|
ARG NEXT_PUBLIC_EXCALIDRAW_URL
|
||||||
|
|
||||||
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
||||||
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
|
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
|
||||||
ENV NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL
|
ENV NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL
|
||||||
|
ENV NEXT_PUBLIC_EXCALIDRAW_URL=$NEXT_PUBLIC_EXCALIDRAW_URL
|
||||||
|
|
||||||
# Копируем package files
|
# Копируем package files
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,45 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Если пользователь авторизован — редирект на дашборд.
|
* Если пользователь авторизован — редирект на дашборд.
|
||||||
* Страницы логина/регистрации и т.д. не должны быть доступны авторизованным.
|
* Страницы логина/регистрации и т.д. не должны быть доступны авторизованным.
|
||||||
*/
|
*/
|
||||||
export function AuthRedirect({ children }: { children: React.ReactNode }) {
|
export function AuthRedirect({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { user, loading } = useAuth();
|
const { user, loading } = useAuth();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
if (user) {
|
if (user) {
|
||||||
router.replace('/dashboard');
|
router.replace('/dashboard');
|
||||||
}
|
}
|
||||||
}, [user, loading, router]);
|
}, [user, loading, router]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontSize: 16, color: 'var(--md-sys-color-on-surface-variant)' }}>
|
<div style={{ fontSize: 16, color: 'var(--md-sys-color-on-surface-variant)' }}>
|
||||||
Загрузка...
|
Загрузка...
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
return null; // редирект уже идёт
|
return null; // редирект уже идёт
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ export function WhiteboardIframe({
|
||||||
})();
|
})();
|
||||||
url.searchParams.set('boardId', boardId);
|
url.searchParams.set('boardId', boardId);
|
||||||
url.searchParams.set('apiUrl', apiUrl);
|
url.searchParams.set('apiUrl', apiUrl);
|
||||||
|
// Yjs WebSocket порт: 1236 (внешний порт yjs-whiteboard контейнера) или через nginx прокси
|
||||||
|
const yjsPort = process.env.NEXT_PUBLIC_YJS_PORT || '1236';
|
||||||
|
url.searchParams.set('yjsPort', yjsPort);
|
||||||
if (token) url.searchParams.set('token', token);
|
if (token) url.searchParams.set('token', token);
|
||||||
if (isMentor) url.searchParams.set('isMentor', '1');
|
if (isMentor) url.searchParams.set('isMentor', '1');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для полной пересборки PROD окружения с бэкапом БД
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Полная пересборка PROD окружения"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ ВНИМАНИЕ: Это пересоберёт все контейнеры без кэша"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Шаг 1: Создать бэкап БД
|
||||||
|
echo "Шаг 1: Создание бэкапа БД..."
|
||||||
|
if [ -f "./backup-all-db.sh" ]; then
|
||||||
|
./backup-all-db.sh
|
||||||
|
else
|
||||||
|
echo "⚠️ Скрипт backup-all-db.sh не найден, создаём бэкап вручную..."
|
||||||
|
mkdir -p ./backups
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
if docker ps | grep -q platform_prod_db; then
|
||||||
|
docker exec platform_prod_db pg_dumpall -U platform_prod_user -c | gzip > "./backups/platform_prod_db_backup_${TIMESTAMP}.sql.gz"
|
||||||
|
echo "✓ Бэкап создан: ./backups/platform_prod_db_backup_${TIMESTAMP}.sql.gz"
|
||||||
|
else
|
||||||
|
echo "⚠️ Контейнер БД не запущен, пропускаем бэкап"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Шаг 2: Остановка контейнеров..."
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Шаг 3: Пересборка образов без кэша..."
|
||||||
|
echo "Это может занять несколько минут..."
|
||||||
|
docker compose build --no-cache --pull
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Шаг 4: Запуск контейнеров..."
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Шаг 5: Ожидание запуска БД..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Шаг 6: Применение миграций..."
|
||||||
|
docker exec platform_prod_web python manage.py migrate
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Шаг 7: Проверка статуса контейнеров..."
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "✓ Пересборка завершена!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Проверьте логи:"
|
||||||
|
echo " docker compose logs -f"
|
||||||
|
echo ""
|
||||||
|
echo "Если нужно создать суперпользователя:"
|
||||||
|
echo " docker exec -it platform_prod_web python manage.py createsuperuser"
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для удаления автоматического бэкапа из cron
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="/var/www/platform/prod"
|
||||||
|
BACKUP_SCRIPT="$SCRIPT_DIR/backup-db-auto.sh"
|
||||||
|
CRON_USER="root"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Удаление автоматического бэкапа из cron"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Проверить, есть ли запись в crontab
|
||||||
|
if crontab -u "$CRON_USER" -l 2>/dev/null | grep -q "$BACKUP_SCRIPT"; then
|
||||||
|
echo "Найдена запись в crontab:"
|
||||||
|
crontab -u "$CRON_USER" -l | grep "$BACKUP_SCRIPT"
|
||||||
|
echo ""
|
||||||
|
read -p "Удалить? (y/N): " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
crontab -u "$CRON_USER" -l 2>/dev/null | grep -v "$BACKUP_SCRIPT" | crontab -u "$CRON_USER" -
|
||||||
|
echo "✓ Запись удалена из crontab"
|
||||||
|
else
|
||||||
|
echo "Отменено."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Запись в crontab не найдена."
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Безопасная остановка PROD окружения
|
||||||
|
# Этот скрипт останавливает контейнеры БЕЗ удаления volumes (данных БД)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Безопасная остановка PROD окружения"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Это остановит контейнеры, но СОХРАНИТ данные БД и Redis"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Остановить контейнеры без удаления volumes
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ Контейнеры остановлены"
|
||||||
|
echo "✓ Volumes сохранены (данные БД не потеряны)"
|
||||||
|
echo ""
|
||||||
|
echo "Для запуска: docker compose up -d"
|
||||||
|
echo "Для полной очистки (с удалением данных): docker compose down --volumes"
|
||||||
|
echo " (ВНИМАНИЕ: это удалит все данные БД!)"
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для настройки автоматического бэкапа БД через cron
|
||||||
|
# Запускается дважды в день: в 00:00 и 12:00
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="/var/www/platform/prod"
|
||||||
|
BACKUP_SCRIPT="$SCRIPT_DIR/backup-db-auto.sh"
|
||||||
|
CRON_USER="root"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Настройка автоматического бэкапа БД"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Проверить, что скрипт существует
|
||||||
|
if [ ! -f "$BACKUP_SCRIPT" ]; then
|
||||||
|
echo "Ошибка: Скрипт $BACKUP_SCRIPT не найден!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Сделать скрипт исполняемым
|
||||||
|
chmod +x "$BACKUP_SCRIPT"
|
||||||
|
echo "✓ Скрипт сделан исполняемым"
|
||||||
|
|
||||||
|
# Создать директорию для бэкапов
|
||||||
|
mkdir -p "$SCRIPT_DIR/backups"
|
||||||
|
echo "✓ Директория для бэкапов создана"
|
||||||
|
|
||||||
|
# Найти путь к docker (для cron)
|
||||||
|
DOCKER_PATH=$(which docker 2>/dev/null || echo "/usr/bin/docker")
|
||||||
|
|
||||||
|
# Проверить, есть ли уже запись в crontab
|
||||||
|
# Используем PATH с docker и bash для надежности
|
||||||
|
CRON_CMD="0 0,12 * * * PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin $BACKUP_SCRIPT >> $SCRIPT_DIR/backups/cron.log 2>&1"
|
||||||
|
|
||||||
|
if crontab -u "$CRON_USER" -l 2>/dev/null | grep -q "$BACKUP_SCRIPT"; then
|
||||||
|
echo "⚠️ Запись в crontab уже существует"
|
||||||
|
echo ""
|
||||||
|
echo "Текущий crontab:"
|
||||||
|
crontab -u "$CRON_USER" -l | grep "$BACKUP_SCRIPT"
|
||||||
|
echo ""
|
||||||
|
read -p "Заменить существующую запись? (y/N): " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
# Удалить старую запись
|
||||||
|
crontab -u "$CRON_USER" -l 2>/dev/null | grep -v "$BACKUP_SCRIPT" | crontab -u "$CRON_USER" -
|
||||||
|
# Добавить новую
|
||||||
|
(crontab -u "$CRON_USER" -l 2>/dev/null; echo "$CRON_CMD") | crontab -u "$CRON_USER" -
|
||||||
|
echo "✓ Запись в crontab обновлена"
|
||||||
|
else
|
||||||
|
echo "Отменено. Существующая запись сохранена."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Добавить новую запись
|
||||||
|
(crontab -u "$CRON_USER" -l 2>/dev/null; echo "$CRON_CMD") | crontab -u "$CRON_USER" -
|
||||||
|
echo "✓ Запись в crontab добавлена"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Настройка завершена!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Расписание бэкапов:"
|
||||||
|
echo " - Каждый день в 00:00 (полночь)"
|
||||||
|
echo " - Каждый день в 12:00 (полдень)"
|
||||||
|
echo ""
|
||||||
|
echo "Проверить crontab:"
|
||||||
|
echo " crontab -u $CRON_USER -l"
|
||||||
|
echo ""
|
||||||
|
echo "Просмотр логов бэкапов:"
|
||||||
|
echo " tail -f $SCRIPT_DIR/backups/backup.log"
|
||||||
|
echo " tail -f $SCRIPT_DIR/backups/cron.log"
|
||||||
|
echo ""
|
||||||
|
echo "Удалить автоматический бэкап:"
|
||||||
|
echo " crontab -u $CRON_USER -l | grep -v '$BACKUP_SCRIPT' | crontab -u $CRON_USER -"
|
||||||
Loading…
Reference in New Issue