Update files
This commit is contained in:
parent
b4ca4f2d9e
commit
1cdfd57cdf
|
|
@ -0,0 +1,23 @@
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.DS_Store
|
||||||
|
*.md
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
.cache
|
||||||
|
frontend/node_modules
|
||||||
|
frontend/dist
|
||||||
|
moderation/frontend/node_modules
|
||||||
|
moderation/frontend/dist
|
||||||
|
backend/uploads/*
|
||||||
|
!backend/uploads/.gitkeep
|
||||||
|
|
||||||
|
|
@ -0,0 +1,438 @@
|
||||||
|
# 📋 Сводка изменений Nakama
|
||||||
|
|
||||||
|
## ✅ Выполненные задачи
|
||||||
|
|
||||||
|
### 1. ✨ Замена NakamaHost на Nakama
|
||||||
|
**Статус:** ✅ Завершено
|
||||||
|
|
||||||
|
**Изменения:**
|
||||||
|
- `backend/server.js` - изменено сообщение API
|
||||||
|
- `backend/bot.js` - обновлены подписи к медиа (3 места)
|
||||||
|
- `frontend/index.html` - обновлен заголовок страницы
|
||||||
|
- `frontend/src/pages/Feed.jsx` - изменен заголовок приложения
|
||||||
|
- `frontend/src/pages/Profile.jsx` - обновлен текст о поддержке проекта
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 🔧 Улучшение меню репортов в системе модерации
|
||||||
|
**Статус:** ✅ Завершено
|
||||||
|
|
||||||
|
**Изменения в `moderation/frontend/src/App.jsx`:**
|
||||||
|
- Добавлено улучшенное отображение причины жалобы
|
||||||
|
- Добавлен полный просмотр информации о посте (автор, содержание, медиа)
|
||||||
|
- Добавлены превью изображений (до 3 штук)
|
||||||
|
- Добавлены действия для работы с постом прямо из репорта:
|
||||||
|
- Удалить пост
|
||||||
|
- Забанить автора
|
||||||
|
- Решено
|
||||||
|
- Отклонить репорт
|
||||||
|
- Добавлено сообщение "Нет активных репортов" когда репортов нет
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. ✏️ Возможность редактирования постов в системе модерации
|
||||||
|
**Статус:** ✅ Завершено
|
||||||
|
|
||||||
|
**Изменения в `backend/models/Post.js`:**
|
||||||
|
- Добавлено поле `publishedToChannel` (Boolean) - пост опубликован в канал
|
||||||
|
- Добавлено поле `channelMessageId` (Number) - ID сообщения в Telegram канале
|
||||||
|
- Добавлено поле `adminNumber` (Number) - номер админа, который опубликовал
|
||||||
|
- Добавлено поле `editedAt` (Date) - время последнего редактирования
|
||||||
|
|
||||||
|
**Изменения в `backend/routes/modApp.js`:**
|
||||||
|
- Обновлен `PUT /posts/:id` с проверкой прав:
|
||||||
|
- Владелец может редактировать любые посты
|
||||||
|
- Админы могут редактировать только свои посты из канала (по adminNumber)
|
||||||
|
- Добавлено автоматическое обновление поста в Telegram канале при редактировании
|
||||||
|
- Обновлен `GET /posts` для возврата информации о публикации в канале
|
||||||
|
- Обновлен `POST /channel/publish` для создания записи в БД с информацией о канале
|
||||||
|
|
||||||
|
**Изменения в `backend/bots/serverMonitor.js`:**
|
||||||
|
- `sendChannelMediaGroup` теперь возвращает messageId
|
||||||
|
- Добавлена функция `updateChannelMessage` для обновления подписи к сообщению в канале
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 🖼️ Исправление загрузки медиа в систему модерации
|
||||||
|
**Статус:** ✅ Завершено
|
||||||
|
|
||||||
|
**Изменения в `moderation/frontend/src/App.jsx`:**
|
||||||
|
- Добавлено преобразование относительных путей к изображениям в абсолютные URLs
|
||||||
|
- Добавлена обработка ошибок загрузки изображений с fallback
|
||||||
|
- Исправлено отображение медиа в постах
|
||||||
|
- Исправлено отображение медиа в репортах
|
||||||
|
- Добавлены console.error для отладки проблем с загрузкой
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 💬 Исправление админского чата
|
||||||
|
**Статус:** ✅ Завершено
|
||||||
|
|
||||||
|
**Изменения в `moderation/frontend/src/App.jsx`:**
|
||||||
|
- Добавлен расширенный логгинг для отладки:
|
||||||
|
- Логирование подключения WebSocket
|
||||||
|
- Логирование авторизации
|
||||||
|
- Логирование получения/отправки сообщений
|
||||||
|
- Логирование ошибок подключения
|
||||||
|
- Увеличен timeout подключения до 10 секунд
|
||||||
|
- Добавлен обработчик `connect_error` для логирования ошибок
|
||||||
|
- Исправлена задержка прокрутки к последнему сообщению (setTimeout 100ms)
|
||||||
|
- Добавлено предупреждение при попытке отправить сообщение без подключения
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. 🗄️ Настройка подключения к БД на удаленном сервере
|
||||||
|
**Статус:** ✅ Завершено (требуется ручная настройка)
|
||||||
|
|
||||||
|
**Создан файл `docker-compose.yml`:**
|
||||||
|
- MongoDB URI настроен на `mongodb://103.80.87.247:27017/nakama`
|
||||||
|
- Добавлена переменная окружения `MONGODB_URI`
|
||||||
|
- Настроено монтирование директорий для данных БД
|
||||||
|
|
||||||
|
**Создан файл `setup-remote-storage.sh`:**
|
||||||
|
- Скрипт для автоматической настройки SSHFS
|
||||||
|
- Монтирование удаленных директорий:
|
||||||
|
- `/var/nakama/db` → `/mnt/nakama-db`
|
||||||
|
- `/var/nakama/media` → `/mnt/nakama-media`
|
||||||
|
- `/var/nakama/backups` → `/mnt/nakama-backups`
|
||||||
|
- Опция автомонтирования через `/etc/fstab`
|
||||||
|
|
||||||
|
**Создана документация `DEPLOYMENT_GUIDE.md`:**
|
||||||
|
- Подробная инструкция по установке MongoDB на удаленном сервере
|
||||||
|
- Настройка аутентификации MongoDB
|
||||||
|
- Настройка удаленного доступа
|
||||||
|
- Настройка firewall
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. 💾 Настройка автоматических бекапов БД
|
||||||
|
**Статус:** ✅ Завершено (требуется ручная настройка cron)
|
||||||
|
|
||||||
|
**Создан файл `backup-cron.sh`:**
|
||||||
|
- Автоматическое создание бекапов через `mongodump`
|
||||||
|
- Сжатие бекапов в .tar.gz архивы
|
||||||
|
- Автоматическое удаление старых бекапов (по умолчанию 30 дней)
|
||||||
|
- Логирование всех операций
|
||||||
|
- Цветной вывод для удобства
|
||||||
|
- Опциональные Telegram уведомления
|
||||||
|
|
||||||
|
**Создана документация `CRON_SETUP.md`:**
|
||||||
|
- Пошаговая инструкция настройки cron
|
||||||
|
- Примеры различных расписаний:
|
||||||
|
- Еженедельные бекапы (воскресенье в 3:00)
|
||||||
|
- Ежедневные бекапы
|
||||||
|
- Несколько раз в неделю
|
||||||
|
- Настройка Telegram уведомлений
|
||||||
|
- Инструкции по восстановлению из бекапа
|
||||||
|
|
||||||
|
**Добавлен сервис `backup` в `docker-compose.yml`:**
|
||||||
|
- Готовый контейнер для запуска бекапов
|
||||||
|
- Смонтированная директория для бекапов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. 📁 Настройка хранения медиа на удаленном сервере
|
||||||
|
**Статус:** ✅ Завершено (требуется ручная настройка)
|
||||||
|
|
||||||
|
**Изменения в `docker-compose.yml`:**
|
||||||
|
- Backend монтирует `/mnt/nakama-media` в `/app/backend/uploads`
|
||||||
|
- Все загруженные медиа автоматически сохраняются на удаленный сервер
|
||||||
|
- Настроено через SSHFS монтирование
|
||||||
|
|
||||||
|
**Создан скрипт `setup-remote-storage.sh`:**
|
||||||
|
- Автоматическая установка SSHFS
|
||||||
|
- Создание директорий на удаленном сервере
|
||||||
|
- Монтирование через SSH
|
||||||
|
- Опция автомонтирования при загрузке системы
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. 🐳 Настройка Docker для всех компонентов
|
||||||
|
**Статус:** ✅ Завершено
|
||||||
|
|
||||||
|
**Созданные файлы:**
|
||||||
|
|
||||||
|
1. **`Dockerfile.backend`**
|
||||||
|
- Multi-stage сборка для оптимизации
|
||||||
|
- Node 20 Alpine (минимальный размер)
|
||||||
|
- Production зависимости
|
||||||
|
- Автоматическое создание директорий для uploads
|
||||||
|
|
||||||
|
2. **`Dockerfile.frontend`**
|
||||||
|
- Multi-stage сборка (builder + nginx)
|
||||||
|
- Vite сборка с оптимизацией
|
||||||
|
- Nginx для раздачи статики
|
||||||
|
- Gzip сжатие
|
||||||
|
- Кэширование статических файлов
|
||||||
|
|
||||||
|
3. **`Dockerfile.moderation`**
|
||||||
|
- Аналогично frontend
|
||||||
|
- Отдельный контейнер для системы модерации
|
||||||
|
- Nginx с оптимизацией
|
||||||
|
|
||||||
|
4. **`docker-compose.yml`**
|
||||||
|
- Полная оркестрация всех сервисов:
|
||||||
|
- backend (Node.js API)
|
||||||
|
- frontend (основное приложение)
|
||||||
|
- moderation (система модерации)
|
||||||
|
- mongodb (база данных)
|
||||||
|
- backup (сервис бекапов)
|
||||||
|
- Настроенные сети
|
||||||
|
- Volumes для данных
|
||||||
|
- Health checks
|
||||||
|
- Переменные окружения
|
||||||
|
- Зависимости между сервисами
|
||||||
|
|
||||||
|
5. **`nginx.conf` и `nginx-moderation.conf`**
|
||||||
|
- Оптимизированная конфигурация nginx
|
||||||
|
- Gzip сжатие
|
||||||
|
- Кэширование статики
|
||||||
|
- SPA роутинг (fallback на index.html)
|
||||||
|
|
||||||
|
6. **`.dockerignore`**
|
||||||
|
- Исключение ненужных файлов из образов
|
||||||
|
- Оптимизация размера образов
|
||||||
|
- Ускорение сборки
|
||||||
|
|
||||||
|
7. **`.env.example`**
|
||||||
|
- Полный пример конфигурации
|
||||||
|
- Все необходимые переменные окружения
|
||||||
|
- Комментарии и значения по умолчанию
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Созданная документация
|
||||||
|
|
||||||
|
### 1. **DEPLOYMENT_GUIDE.md** - Полное руководство по развертыванию
|
||||||
|
- Требования к системе
|
||||||
|
- Настройка удаленного сервера
|
||||||
|
- Установка и настройка MongoDB
|
||||||
|
- Развертывание с Docker
|
||||||
|
- Настройка nginx reverse proxy
|
||||||
|
- SSL сертификаты
|
||||||
|
- Мониторинг и обслуживание
|
||||||
|
- Решение проблем
|
||||||
|
|
||||||
|
### 2. **CRON_SETUP.md** - Настройка автоматических бекапов
|
||||||
|
- Пошаговая инструкция
|
||||||
|
- Синтаксис cron
|
||||||
|
- Примеры расписаний
|
||||||
|
- Настройка уведомлений
|
||||||
|
- Управление бекапами
|
||||||
|
- Восстановление из бекапа
|
||||||
|
|
||||||
|
### 3. **CHANGES_SUMMARY.md** - Этот файл
|
||||||
|
- Полная сводка всех изменений
|
||||||
|
- Инструкции по запуску
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### Подготовка
|
||||||
|
|
||||||
|
1. **Настройте удаленный сервер:**
|
||||||
|
```bash
|
||||||
|
# Следуйте инструкциям в DEPLOYMENT_GUIDE.md
|
||||||
|
ssh root@103.80.87.247
|
||||||
|
# Установите MongoDB и создайте директории
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Настройте локальное окружение:**
|
||||||
|
```bash
|
||||||
|
cd /Users/glpshchn/Desktop/nakama
|
||||||
|
|
||||||
|
# Создайте .env файл
|
||||||
|
cp .env.example .env
|
||||||
|
nano .env # Заполните переменные
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Настройте удаленное хранилище (опционально):**
|
||||||
|
```bash
|
||||||
|
./setup-remote-storage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Запуск с Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Сборка всех сервисов
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Запуск в фоновом режиме
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Проверка статуса
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройка бекапов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Скопируйте скрипт на удаленный сервер
|
||||||
|
scp backup-cron.sh root@103.80.87.247:/usr/local/bin/
|
||||||
|
|
||||||
|
# Следуйте инструкциям в CRON_SETUP.md
|
||||||
|
ssh root@103.80.87.247
|
||||||
|
chmod +x /usr/local/bin/backup-cron.sh
|
||||||
|
crontab -e
|
||||||
|
# Добавьте: 0 3 * * 0 /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Доступ к приложению
|
||||||
|
|
||||||
|
После запуска:
|
||||||
|
- **Frontend (основное приложение):** http://localhost:5173
|
||||||
|
- **Moderation (система модерации):** http://localhost:5174
|
||||||
|
- **Backend API:** http://localhost:3000
|
||||||
|
- **Health check:** http://localhost:3000/health
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Команды для управления
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Остановить все сервисы
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Перезапустить конкретный сервис
|
||||||
|
docker-compose restart backend
|
||||||
|
|
||||||
|
# Пересобрать и запустить
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Просмотр логов конкретного сервиса
|
||||||
|
docker-compose logs -f backend
|
||||||
|
|
||||||
|
# Выполнить команду в контейнере
|
||||||
|
docker-compose exec backend sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Бекапы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ручной бекап
|
||||||
|
ssh root@103.80.87.247 '/usr/local/bin/backup-cron.sh'
|
||||||
|
|
||||||
|
# Список бекапов
|
||||||
|
ssh root@103.80.87.247 'ls -lh /var/nakama/backups/'
|
||||||
|
|
||||||
|
# Восстановление
|
||||||
|
ssh root@103.80.87.247
|
||||||
|
cd /var/nakama/backups
|
||||||
|
tar -xzf nakama_backup_YYYY-MM-DD_HH-MM-SS.tar.gz
|
||||||
|
mongorestore --uri="mongodb://localhost:27017" --drop --gzip --db nakama nakama_backup_*/nakama/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Мониторинг
|
||||||
|
|
||||||
|
### Проверка здоровья системы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Статус Docker контейнеров
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Использование ресурсов
|
||||||
|
docker stats
|
||||||
|
|
||||||
|
# Логи в реальном времени
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Проверка MongoDB
|
||||||
|
ssh root@103.80.87.247 'systemctl status mongod'
|
||||||
|
|
||||||
|
# Свободное место на диске
|
||||||
|
ssh root@103.80.87.247 'df -h'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Важные замечания
|
||||||
|
|
||||||
|
1. **Безопасность MongoDB:**
|
||||||
|
- Настройте аутентификацию MongoDB (см. DEPLOYMENT_GUIDE.md)
|
||||||
|
- Используйте firewall для ограничения доступа к порту 27017
|
||||||
|
- Регулярно обновляйте MongoDB
|
||||||
|
|
||||||
|
2. **Переменные окружения:**
|
||||||
|
- Никогда не коммитьте `.env` файл в git
|
||||||
|
- Используйте надежные пароли и секретные ключи
|
||||||
|
- JWT_SECRET должен быть случайной строкой минимум 32 символа
|
||||||
|
|
||||||
|
3. **Бекапы:**
|
||||||
|
- Проверяйте успешность создания бекапов
|
||||||
|
- Периодически проверяйте возможность восстановления
|
||||||
|
- Храните бекапы на отдельном диске/сервере
|
||||||
|
|
||||||
|
4. **Обновления:**
|
||||||
|
- Создавайте бекап перед обновлением
|
||||||
|
- Тестируйте обновления на dev окружении
|
||||||
|
- Читайте CHANGELOG перед обновлением
|
||||||
|
|
||||||
|
5. **Производительность:**
|
||||||
|
- Мониторьте использование ресурсов
|
||||||
|
- Настройте индексы в MongoDB для часто используемых запросов
|
||||||
|
- Используйте Redis для кэширования (опционально)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆘 Поддержка
|
||||||
|
|
||||||
|
При возникновении проблем:
|
||||||
|
|
||||||
|
1. **Проверьте логи:**
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Проверьте документацию:**
|
||||||
|
- DEPLOYMENT_GUIDE.md
|
||||||
|
- CRON_SETUP.md
|
||||||
|
|
||||||
|
3. **Свяжитесь с поддержкой:**
|
||||||
|
- Telegram: https://t.me/NakamaReportbot
|
||||||
|
- GitHub Issues: [создайте issue]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Список файлов для коммита
|
||||||
|
|
||||||
|
Все изменения готовы к коммиту:
|
||||||
|
|
||||||
|
### Измененные файлы:
|
||||||
|
- backend/server.js
|
||||||
|
- backend/bot.js
|
||||||
|
- backend/models/Post.js
|
||||||
|
- backend/routes/modApp.js
|
||||||
|
- backend/bots/serverMonitor.js
|
||||||
|
- frontend/index.html
|
||||||
|
- frontend/src/pages/Feed.jsx
|
||||||
|
- frontend/src/pages/Profile.jsx
|
||||||
|
- moderation/frontend/src/App.jsx
|
||||||
|
|
||||||
|
### Новые файлы:
|
||||||
|
- Dockerfile.backend
|
||||||
|
- Dockerfile.frontend
|
||||||
|
- Dockerfile.moderation
|
||||||
|
- docker-compose.yml
|
||||||
|
- nginx.conf
|
||||||
|
- nginx-moderation.conf
|
||||||
|
- .dockerignore
|
||||||
|
- backup-cron.sh
|
||||||
|
- setup-remote-storage.sh
|
||||||
|
- DEPLOYMENT_GUIDE.md
|
||||||
|
- CRON_SETUP.md
|
||||||
|
- CHANGES_SUMMARY.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Версия:** 2.2.0
|
||||||
|
**Дата:** 20 ноября 2025
|
||||||
|
**Автор:** AI Assistant (Claude Sonnet 4.5)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,288 @@
|
||||||
|
# ⏰ Настройка автоматических бекапов через Cron
|
||||||
|
|
||||||
|
## 📋 Инструкция
|
||||||
|
|
||||||
|
### 1. Подключитесь к удаленному серверу
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@103.80.87.247
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Установите необходимые инструменты
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Убедитесь, что mongo-tools установлены
|
||||||
|
apt update
|
||||||
|
apt install -y mongodb-database-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Скопируйте скрипт бекапа на сервер
|
||||||
|
|
||||||
|
С вашего локального компьютера:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp backup-cron.sh root@103.80.87.247:/usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
Или создайте файл напрямую на сервере:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano /usr/local/bin/backup-cron.sh
|
||||||
|
# Вставьте содержимое из backup-cron.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Сделайте скрипт исполняемым
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x /usr/local/bin/backup-cron.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Настройте cron
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Откройте crontab для редактирования
|
||||||
|
crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Добавьте задачи в crontab
|
||||||
|
|
||||||
|
Выберите один из вариантов:
|
||||||
|
|
||||||
|
#### Вариант 1: Еженедельные бекапы (воскресенье в 3:00 ночи)
|
||||||
|
|
||||||
|
```cron
|
||||||
|
# Еженедельный бекап базы данных Nakama
|
||||||
|
0 3 * * 0 /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Вариант 2: Ежедневные бекапы (каждый день в 3:00 ночи)
|
||||||
|
|
||||||
|
```cron
|
||||||
|
# Ежедневный бекап базы данных Nakama
|
||||||
|
0 3 * * * /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Вариант 3: Несколько бекапов в неделю (пн, ср, пт в 3:00)
|
||||||
|
|
||||||
|
```cron
|
||||||
|
# Бекапы по понедельникам, средам и пятницам
|
||||||
|
0 3 * * 1,3,5 /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Проверьте настройку cron
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Просмотреть текущие задачи cron
|
||||||
|
crontab -l
|
||||||
|
|
||||||
|
# Проверить статус службы cron
|
||||||
|
systemctl status cron
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Тестирование
|
||||||
|
|
||||||
|
Запустите бекап вручную:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/usr/local/bin/backup-cron.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверьте созданные бекапы:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -lh /mnt/nakama-backups/
|
||||||
|
```
|
||||||
|
|
||||||
|
Просмотрите лог:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -50 /var/log/nakama-backup.log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Синтаксис Cron
|
||||||
|
|
||||||
|
```
|
||||||
|
* * * * * команда
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │ │ └─── День недели (0-7, где 0 и 7 = воскресенье)
|
||||||
|
│ │ │ └───── Месяц (1-12)
|
||||||
|
│ │ └─────── День месяца (1-31)
|
||||||
|
│ └───────── Час (0-23)
|
||||||
|
└─────────── Минута (0-59)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Примеры расписаний
|
||||||
|
|
||||||
|
| Расписание | Синтаксис Cron | Описание |
|
||||||
|
|-----------|----------------|----------|
|
||||||
|
| Каждую минуту | `* * * * *` | Выполняется каждую минуту |
|
||||||
|
| Каждый час | `0 * * * *` | Выполняется в начале каждого часа |
|
||||||
|
| Раз в день (в полночь) | `0 0 * * *` | Выполняется в 00:00 каждый день |
|
||||||
|
| Раз в день (в 3:00) | `0 3 * * *` | Выполняется в 03:00 каждый день |
|
||||||
|
| Раз в неделю (воскресенье) | `0 3 * * 0` | Выполняется в воскресенье в 03:00 |
|
||||||
|
| Раз в месяц | `0 0 1 * *` | Выполняется 1-го числа каждого месяца в 00:00 |
|
||||||
|
| Каждые 6 часов | `0 */6 * * *` | Выполняется в 00:00, 06:00, 12:00, 18:00 |
|
||||||
|
| Рабочие дни в 9:00 | `0 9 * * 1-5` | Выполняется пн-пт в 09:00 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Настройка уведомлений (опционально)
|
||||||
|
|
||||||
|
Чтобы получать уведомления о статусе бекапов в Telegram:
|
||||||
|
|
||||||
|
### 1. Создайте бота для уведомлений
|
||||||
|
|
||||||
|
1. Напишите [@BotFather](https://t.me/BotFather) в Telegram
|
||||||
|
2. Отправьте команду `/newbot`
|
||||||
|
3. Следуйте инструкциям
|
||||||
|
4. Скопируйте токен бота
|
||||||
|
|
||||||
|
### 2. Получите свой Chat ID
|
||||||
|
|
||||||
|
1. Напишите боту [@userinfobot](https://t.me/userinfobot)
|
||||||
|
2. Скопируйте свой Chat ID
|
||||||
|
|
||||||
|
### 3. Обновите скрипт бекапа
|
||||||
|
|
||||||
|
Откройте `/usr/local/bin/backup-cron.sh` и раскомментируйте последние строки:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# В конце скрипта найдите и раскомментируйте:
|
||||||
|
curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/sendMessage" \
|
||||||
|
-d "chat_id=<YOUR_CHAT_ID>" \
|
||||||
|
-d "text=✅ Резервная копия Nakama успешно создана: ${BACKUP_NAME}.tar.gz (${BACKUP_SIZE})"
|
||||||
|
```
|
||||||
|
|
||||||
|
Замените:
|
||||||
|
- `<YOUR_BOT_TOKEN>` на токен вашего бота
|
||||||
|
- `<YOUR_CHAT_ID>` на ваш Chat ID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Полезные команды
|
||||||
|
|
||||||
|
### Управление cron
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Открыть crontab для редактирования
|
||||||
|
crontab -e
|
||||||
|
|
||||||
|
# Показать текущие задачи
|
||||||
|
crontab -l
|
||||||
|
|
||||||
|
# Удалить все задачи
|
||||||
|
crontab -r
|
||||||
|
|
||||||
|
# Открыть crontab другого пользователя (требуются права root)
|
||||||
|
crontab -u username -e
|
||||||
|
```
|
||||||
|
|
||||||
|
### Просмотр логов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Показать последние 50 строк лога бекапов
|
||||||
|
tail -50 /var/log/nakama-backup.log
|
||||||
|
|
||||||
|
# Следить за логом в реальном времени
|
||||||
|
tail -f /var/log/nakama-backup.log
|
||||||
|
|
||||||
|
# Показать все ошибки в логе
|
||||||
|
grep -i error /var/log/nakama-backup.log
|
||||||
|
|
||||||
|
# Показать системный лог cron
|
||||||
|
grep CRON /var/log/syslog
|
||||||
|
```
|
||||||
|
|
||||||
|
### Управление бекапами
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Список всех бекапов
|
||||||
|
ls -lht /mnt/nakama-backups/
|
||||||
|
|
||||||
|
# Размер директории с бекапами
|
||||||
|
du -sh /mnt/nakama-backups/
|
||||||
|
|
||||||
|
# Удалить бекапы старше 30 дней
|
||||||
|
find /mnt/nakama-backups/ -name "nakama_backup_*.tar.gz" -type f -mtime +30 -delete
|
||||||
|
|
||||||
|
# Подсчитать количество бекапов
|
||||||
|
ls -1 /mnt/nakama-backups/ | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Важные замечания
|
||||||
|
|
||||||
|
1. **Время выполнения**: Убедитесь, что время в cron указано в часовом поясе сервера. Проверьте:
|
||||||
|
```bash
|
||||||
|
timedatectl
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Права доступа**: Убедитесь, что у пользователя, под которым запускается cron, есть права на запись в директорию бекапов.
|
||||||
|
|
||||||
|
3. **Место на диске**: Регулярно проверяйте свободное место:
|
||||||
|
```bash
|
||||||
|
df -h /mnt/nakama-backups/
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Ротация логов**: Настройте logrotate для `/var/log/nakama-backup.log`:
|
||||||
|
```bash
|
||||||
|
nano /etc/logrotate.d/nakama-backup
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавьте:
|
||||||
|
```
|
||||||
|
/var/log/nakama-backup.log {
|
||||||
|
weekly
|
||||||
|
rotate 4
|
||||||
|
compress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Мониторинг**: Регулярно проверяйте, что бекапы создаются успешно:
|
||||||
|
```bash
|
||||||
|
ls -lt /mnt/nakama-backups/ | head
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Восстановление из бекапа
|
||||||
|
|
||||||
|
Если понадобится восстановить базу данных:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Перейти в директорию с бекапами
|
||||||
|
cd /mnt/nakama-backups/
|
||||||
|
|
||||||
|
# 2. Найти нужный бекап
|
||||||
|
ls -lt | grep nakama_backup
|
||||||
|
|
||||||
|
# 3. Распаковать бекап
|
||||||
|
tar -xzf nakama_backup_YYYY-MM-DD_HH-MM-SS.tar.gz
|
||||||
|
|
||||||
|
# 4. Восстановить базу данных
|
||||||
|
mongorestore --uri="mongodb://localhost:27017" \
|
||||||
|
--drop \
|
||||||
|
--gzip \
|
||||||
|
--db nakama \
|
||||||
|
nakama_backup_YYYY-MM-DD_HH-MM-SS/nakama/
|
||||||
|
|
||||||
|
# 5. Удалить распакованную директорию
|
||||||
|
rm -rf nakama_backup_YYYY-MM-DD_HH-MM-SS/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Помощь
|
||||||
|
|
||||||
|
Если у вас возникли проблемы с настройкой cron:
|
||||||
|
|
||||||
|
1. Проверьте логи: `tail -f /var/log/nakama-backup.log`
|
||||||
|
2. Проверьте системный лог: `grep CRON /var/log/syslog`
|
||||||
|
3. Убедитесь, что cron запущен: `systemctl status cron`
|
||||||
|
4. Запустите скрипт вручную для отладки: `/usr/local/bin/backup-cron.sh`
|
||||||
|
|
||||||
|
|
@ -0,0 +1,477 @@
|
||||||
|
# 🚀 Руководство по развертыванию Nakama
|
||||||
|
|
||||||
|
## 📋 Содержание
|
||||||
|
1. [Требования](#требования)
|
||||||
|
2. [Настройка удаленного сервера](#настройка-удаленного-сервера)
|
||||||
|
3. [Настройка базы данных](#настройка-базы-данных)
|
||||||
|
4. [Настройка автоматических бекапов](#настройка-автоматических-бекапов)
|
||||||
|
5. [Развертывание с Docker](#развертывание-с-docker)
|
||||||
|
6. [Настройка nginx](#настройка-nginx)
|
||||||
|
7. [Мониторинг и обслуживание](#мониторинг-и-обслуживание)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Требования
|
||||||
|
|
||||||
|
### Локальный сервер (где запускается приложение)
|
||||||
|
- Docker 20.10+
|
||||||
|
- Docker Compose 2.0+
|
||||||
|
- SSHFS (для монтирования удаленных директорий)
|
||||||
|
- 2GB+ RAM
|
||||||
|
- 10GB+ свободного места на диске
|
||||||
|
|
||||||
|
### Удаленный сервер (103.80.87.247)
|
||||||
|
- SSH доступ
|
||||||
|
- MongoDB 7+
|
||||||
|
- 50GB+ свободного места для базы данных и медиа
|
||||||
|
- Открытый порт 27017 для MongoDB
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗄️ Настройка удаленного сервера
|
||||||
|
|
||||||
|
### 1. Подключение по SSH
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@103.80.87.247
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Установка MongoDB
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Обновить систему
|
||||||
|
apt update && apt upgrade -y
|
||||||
|
|
||||||
|
# Импортировать GPG ключ MongoDB
|
||||||
|
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
|
||||||
|
sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
|
||||||
|
|
||||||
|
# Добавить репозиторий MongoDB
|
||||||
|
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | \
|
||||||
|
sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
|
||||||
|
|
||||||
|
# Установить MongoDB
|
||||||
|
apt update
|
||||||
|
apt install -y mongodb-org
|
||||||
|
|
||||||
|
# Запустить MongoDB
|
||||||
|
systemctl start mongod
|
||||||
|
systemctl enable mongod
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Настройка MongoDB для удаленного доступа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Редактировать конфигурацию
|
||||||
|
nano /etc/mongod.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Изменить `bindIp`:
|
||||||
|
```yaml
|
||||||
|
net:
|
||||||
|
port: 27017
|
||||||
|
bindIp: 0.0.0.0 # Разрешить подключения со всех IP
|
||||||
|
```
|
||||||
|
|
||||||
|
Настроить аутентификацию (рекомендуется):
|
||||||
|
```bash
|
||||||
|
# Подключиться к MongoDB
|
||||||
|
mongosh
|
||||||
|
|
||||||
|
# Создать администратора
|
||||||
|
use admin
|
||||||
|
db.createUser({
|
||||||
|
user: "admin",
|
||||||
|
pwd: "your_secure_password",
|
||||||
|
roles: [ { role: "root", db: "admin" } ]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Создать пользователя для приложения
|
||||||
|
use nakama
|
||||||
|
db.createUser({
|
||||||
|
user: "nakama_user",
|
||||||
|
pwd: "your_app_password",
|
||||||
|
roles: [ { role: "readWrite", db: "nakama" } ]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Перезапустить MongoDB:
|
||||||
|
```bash
|
||||||
|
systemctl restart mongod
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Создание директорий для хранения данных
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Создать директории
|
||||||
|
mkdir -p /var/nakama/media
|
||||||
|
mkdir -p /var/nakama/db
|
||||||
|
mkdir -p /var/nakama/backups
|
||||||
|
|
||||||
|
# Установить права доступа
|
||||||
|
chmod -R 755 /var/nakama
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💾 Настройка базы данных
|
||||||
|
|
||||||
|
### Строка подключения
|
||||||
|
|
||||||
|
Обновите `.env` файл:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Без аутентификации
|
||||||
|
MONGODB_URI=mongodb://103.80.87.247:27017/nakama
|
||||||
|
|
||||||
|
# С аутентификацией
|
||||||
|
MONGODB_URI=mongodb://nakama_user:your_app_password@103.80.87.247:27017/nakama
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Настройка автоматических бекапов
|
||||||
|
|
||||||
|
### 1. Установка cron на удаленном сервере
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Скопировать скрипт бекапа на удаленный сервер
|
||||||
|
scp backup-cron.sh root@103.80.87.247:/usr/local/bin/
|
||||||
|
|
||||||
|
# Подключиться к удаленному серверу
|
||||||
|
ssh root@103.80.87.247
|
||||||
|
|
||||||
|
# Сделать скрипт исполняемым
|
||||||
|
chmod +x /usr/local/bin/backup-cron.sh
|
||||||
|
|
||||||
|
# Открыть crontab
|
||||||
|
crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Добавить задачу в crontab
|
||||||
|
|
||||||
|
Добавьте следующую строку для запуска бекапа каждое воскресенье в 3:00 ночи:
|
||||||
|
|
||||||
|
```cron
|
||||||
|
0 3 * * 0 /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Для ежедневных бекапов в 3:00 ночи:
|
||||||
|
```cron
|
||||||
|
0 3 * * * /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Проверка работы бекапов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запустить бекап вручную
|
||||||
|
/usr/local/bin/backup-cron.sh
|
||||||
|
|
||||||
|
# Проверить созданные бекапы
|
||||||
|
ls -lh /var/nakama/backups/
|
||||||
|
|
||||||
|
# Просмотреть лог
|
||||||
|
tail -f /var/log/nakama-backup.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Восстановление из бекапа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Распаковать бекап
|
||||||
|
cd /var/nakama/backups
|
||||||
|
tar -xzf nakama_backup_YYYY-MM-DD_HH-MM-SS.tar.gz
|
||||||
|
|
||||||
|
# Восстановить базу данных
|
||||||
|
mongorestore --uri="mongodb://103.80.87.247:27017" --gzip --db nakama nakama_backup_YYYY-MM-DD_HH-MM-SS/nakama/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐳 Развертывание с Docker
|
||||||
|
|
||||||
|
### 1. Подготовка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/glpshchn/Desktop/nakama
|
||||||
|
|
||||||
|
# Скопировать и настроить .env
|
||||||
|
cp .env.example .env
|
||||||
|
nano .env # Заполнить необходимые переменные
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Монтирование удаленных директорий (опционально)
|
||||||
|
|
||||||
|
Если вы хотите хранить медиа и бекапы на удаленном сервере:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запустить скрипт настройки
|
||||||
|
./setup-remote-storage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Сборка и запуск контейнеров
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Собрать все сервисы
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Запустить в фоновом режиме
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Проверить статус
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Просмотреть логи
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Проверка работы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить backend
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
|
||||||
|
# Проверить frontend
|
||||||
|
curl http://localhost:5173
|
||||||
|
|
||||||
|
# Проверить moderation
|
||||||
|
curl http://localhost:5174
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Остановка и обновление
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Остановить все сервисы
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Пересобрать и перезапустить
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Перезапустить конкретный сервис
|
||||||
|
docker-compose restart backend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Настройка nginx (reverse proxy)
|
||||||
|
|
||||||
|
### Конфигурация для production
|
||||||
|
|
||||||
|
Создайте файл `/etc/nginx/sites-available/nakama`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# Frontend (основное приложение)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name nakama.yourdomain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:5173;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Moderation (система модерации)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name mod.nakama.yourdomain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:5174;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backend API
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name api.nakama.yourdomain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
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 support
|
||||||
|
proxy_read_timeout 86400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Активировать конфигурацию:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Создать символическую ссылку
|
||||||
|
ln -s /etc/nginx/sites-available/nakama /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
|
# Проверить конфигурацию
|
||||||
|
nginx -t
|
||||||
|
|
||||||
|
# Перезапустить nginx
|
||||||
|
systemctl restart nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSL сертификаты (Let's Encrypt)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установить certbot
|
||||||
|
apt install certbot python3-certbot-nginx
|
||||||
|
|
||||||
|
# Получить сертификаты
|
||||||
|
certbot --nginx -d nakama.yourdomain.com -d mod.nakama.yourdomain.com -d api.nakama.yourdomain.com
|
||||||
|
|
||||||
|
# Автоматическое обновление
|
||||||
|
certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Мониторинг и обслуживание
|
||||||
|
|
||||||
|
### Просмотр логов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Логи backend
|
||||||
|
docker-compose logs -f backend
|
||||||
|
|
||||||
|
# Логи всех сервисов
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Логи MongoDB на удаленном сервере
|
||||||
|
ssh root@103.80.87.247 'tail -f /var/log/mongodb/mongod.log'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Мониторинг ресурсов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Использование Docker контейнеров
|
||||||
|
docker stats
|
||||||
|
|
||||||
|
# Использование диска
|
||||||
|
df -h
|
||||||
|
|
||||||
|
# Статус MongoDB (на удаленном сервере)
|
||||||
|
ssh root@103.80.87.247 'systemctl status mongod'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Обновление приложения
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Создать бекап
|
||||||
|
./backup-cron.sh
|
||||||
|
|
||||||
|
# 2. Получить обновления
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# 3. Остановить контейнеры
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# 4. Пересобрать и запустить
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# 5. Проверить работу
|
||||||
|
docker-compose ps
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Очистка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Удалить неиспользуемые образы
|
||||||
|
docker image prune -a
|
||||||
|
|
||||||
|
# Удалить неиспользуемые volumes
|
||||||
|
docker volume prune
|
||||||
|
|
||||||
|
# Полная очистка Docker
|
||||||
|
docker system prune -a --volumes
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Решение проблем
|
||||||
|
|
||||||
|
### База данных недоступна
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить подключение к MongoDB
|
||||||
|
mongosh --host 103.80.87.247 --port 27017
|
||||||
|
|
||||||
|
# Проверить статус MongoDB на удаленном сервере
|
||||||
|
ssh root@103.80.87.247 'systemctl status mongod'
|
||||||
|
|
||||||
|
# Проверить логи
|
||||||
|
ssh root@103.80.87.247 'tail -100 /var/log/mongodb/mongod.log'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Контейнеры не запускаются
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить логи
|
||||||
|
docker-compose logs
|
||||||
|
|
||||||
|
# Пересоздать контейнеры
|
||||||
|
docker-compose down -v
|
||||||
|
docker-compose up -d --force-recreate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проблемы с медиа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить монтирование директорий
|
||||||
|
df -h | grep nakama
|
||||||
|
|
||||||
|
# Проверить права доступа
|
||||||
|
ls -la /mnt/nakama-media
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Дополнительные команды
|
||||||
|
|
||||||
|
### Создание ручного бекапа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose exec mongodb mongodump --uri="mongodb://103.80.87.247:27017/nakama" --out=/backups/manual_backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Экспорт/импорт данных
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Экспорт коллекции
|
||||||
|
docker-compose exec mongodb mongoexport --uri="mongodb://103.80.87.247:27017/nakama" --collection=posts --out=/backups/posts.json
|
||||||
|
|
||||||
|
# Импорт коллекции
|
||||||
|
docker-compose exec mongodb mongoimport --uri="mongodb://103.80.87.247:27017/nakama" --collection=posts --file=/backups/posts.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Масштабирование
|
||||||
|
|
||||||
|
Для горизонтального масштабирования backend:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --scale backend=3
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Поддержка
|
||||||
|
|
||||||
|
При возникновении проблем:
|
||||||
|
1. Проверьте логи: `docker-compose logs -f`
|
||||||
|
2. Проверьте статус сервисов: `docker-compose ps`
|
||||||
|
3. Создайте issue на GitHub или напишите в https://t.me/NakamaReportbot
|
||||||
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Установка зависимостей
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Копирование backend кода
|
||||||
|
COPY backend ./backend
|
||||||
|
|
||||||
|
# Создание директории для uploads
|
||||||
|
RUN mkdir -p backend/uploads/posts backend/uploads/mod-channel
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Переменные окружения
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
CMD ["node", "backend/server.js"]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Установка зависимостей
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Копирование исходников
|
||||||
|
COPY frontend ./
|
||||||
|
|
||||||
|
# Сборка проекта
|
||||||
|
ARG VITE_API_URL
|
||||||
|
ENV VITE_API_URL=$VITE_API_URL
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Копирование собранного приложения
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Копирование конфигурации nginx
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Установка зависимостей
|
||||||
|
COPY moderation/frontend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Копирование исходников
|
||||||
|
COPY moderation/frontend ./
|
||||||
|
|
||||||
|
# Сборка проекта
|
||||||
|
ARG VITE_API_URL
|
||||||
|
ENV VITE_API_URL=$VITE_API_URL
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Копирование собранного приложения
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Копирование конфигурации nginx
|
||||||
|
COPY nginx-moderation.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
|
|
@ -213,7 +213,7 @@ async function sendPhotosToUser(userId, photos) {
|
||||||
media.push({
|
media.push({
|
||||||
type: isVideo ? 'video' : 'photo',
|
type: isVideo ? 'video' : 'photo',
|
||||||
media: photoUrl,
|
media: photoUrl,
|
||||||
caption: index === 0 ? `<b>Из NakamaHost</b>\n${batch.length} фото` : undefined,
|
caption: index === 0 ? `<b>Из Nakama</b>\n${batch.length} фото` : undefined,
|
||||||
parse_mode: 'HTML',
|
parse_mode: 'HTML',
|
||||||
...(isVideo ? { supports_streaming: true } : {})
|
...(isVideo ? { supports_streaming: true } : {})
|
||||||
});
|
});
|
||||||
|
|
@ -224,7 +224,7 @@ async function sendPhotosToUser(userId, photos) {
|
||||||
media.push({
|
media.push({
|
||||||
type: isVideo ? 'video' : 'photo',
|
type: isVideo ? 'video' : 'photo',
|
||||||
media: photoUrl,
|
media: photoUrl,
|
||||||
caption: index === 0 ? `<b>Из NakamaHost</b>\n${batch.length} фото` : undefined,
|
caption: index === 0 ? `<b>Из Nakama</b>\n${batch.length} фото` : undefined,
|
||||||
parse_mode: 'HTML',
|
parse_mode: 'HTML',
|
||||||
...(isVideo ? { supports_streaming: true } : {})
|
...(isVideo ? { supports_streaming: true } : {})
|
||||||
});
|
});
|
||||||
|
|
@ -252,7 +252,7 @@ async function handleWebAppData(userId, dataString) {
|
||||||
const data = JSON.parse(dataString);
|
const data = JSON.parse(dataString);
|
||||||
|
|
||||||
if (data.action === 'send_image') {
|
if (data.action === 'send_image') {
|
||||||
const caption = `<b>Из NakamaHost</b>\n\n${data.caption || ''}`;
|
const caption = `<b>Из Nakama</b>\n\n${data.caption || ''}`;
|
||||||
await sendPhotoToUser(userId, data.url, caption);
|
await sendPhotoToUser(userId, data.url, caption);
|
||||||
return { success: true, message: 'Изображение отправлено!' };
|
return { success: true, message: 'Изображение отправлено!' };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -376,9 +376,13 @@ const sendChannelMediaGroup = async (files, caption) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post(`${TELEGRAM_API}/sendMediaGroup`, form, {
|
const response = await axios.post(`${TELEGRAM_API}/sendMediaGroup`, form, {
|
||||||
headers: form.getHeaders()
|
headers: form.getHeaders()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Вернуть ID первого сообщения из группы
|
||||||
|
const messageId = response.data?.result?.[0]?.message_id;
|
||||||
|
return messageId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('error', 'Не удалось отправить медиа-группу в канал', { error: error.response?.data || error.message });
|
log('error', 'Не удалось отправить медиа-группу в канал', { error: error.response?.data || error.message });
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -410,10 +414,41 @@ const sendMessageToUser = async (userId, message) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновить сообщение в канале (только текст)
|
||||||
|
*/
|
||||||
|
const updateChannelMessage = async (messageId, content, hashtags) => {
|
||||||
|
if (!TELEGRAM_API) {
|
||||||
|
throw new Error('Бот модерации не инициализирован');
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatId = config.moderationChannelUsername || '@reichenbfurry';
|
||||||
|
|
||||||
|
// Формируем текст с хэштегами
|
||||||
|
const text = [content, hashtags?.length ? hashtags.map(tag => `#${tag}`).join(' ') : ''].filter(Boolean).join('\n\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(`${TELEGRAM_API}/editMessageCaption`, {
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: messageId,
|
||||||
|
caption: `${text}${ERROR_SUPPORT_SUFFIX}`,
|
||||||
|
parse_mode: 'HTML'
|
||||||
|
});
|
||||||
|
log('info', 'Сообщение в канале обновлено', { messageId });
|
||||||
|
} catch (error) {
|
||||||
|
log('error', 'Не удалось обновить сообщение в канале', {
|
||||||
|
messageId,
|
||||||
|
error: error.response?.data || error.message
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
startServerMonitorBot,
|
startServerMonitorBot,
|
||||||
sendChannelMediaGroup,
|
sendChannelMediaGroup,
|
||||||
sendMessageToUser,
|
sendMessageToUser,
|
||||||
|
updateChannelMessage,
|
||||||
isModerationAdmin
|
isModerationAdmin
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,20 @@ const PostSchema = new mongoose.Schema({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
},
|
},
|
||||||
|
// Поля для постов из канала
|
||||||
|
publishedToChannel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
channelMessageId: {
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
adminNumber: {
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
editedAt: {
|
||||||
|
type: Date
|
||||||
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: Date,
|
type: Date,
|
||||||
default: Date.now
|
default: Date.now
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,9 @@ router.get('/posts', authenticateModeration, requireModerationAccess, async (req
|
||||||
commentsCount: post.comments?.length || 0,
|
commentsCount: post.comments?.length || 0,
|
||||||
likesCount: post.likes?.length || 0,
|
likesCount: post.likes?.length || 0,
|
||||||
isNSFW: post.isNSFW,
|
isNSFW: post.isNSFW,
|
||||||
|
publishedToChannel: post.publishedToChannel,
|
||||||
|
adminNumber: post.adminNumber,
|
||||||
|
editedAt: post.editedAt,
|
||||||
createdAt: post.createdAt
|
createdAt: post.createdAt
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -205,11 +208,29 @@ router.get('/posts', authenticateModeration, requireModerationAccess, async (req
|
||||||
router.put('/posts/:id', authenticateModeration, requireModerationAccess, async (req, res) => {
|
router.put('/posts/:id', authenticateModeration, requireModerationAccess, async (req, res) => {
|
||||||
const { content, hashtags, tags, isNSFW } = req.body;
|
const { content, hashtags, tags, isNSFW } = req.body;
|
||||||
|
|
||||||
const post = await Post.findById(req.params.id);
|
const post = await Post.findById(req.params.id).populate('author');
|
||||||
if (!post) {
|
if (!post) {
|
||||||
return res.status(404).json({ error: 'Пост не найден' });
|
return res.status(404).json({ error: 'Пост не найден' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверить, может ли админ редактировать этот пост
|
||||||
|
// Админ может редактировать:
|
||||||
|
// 1. Любой пост, если он владелец (req.isOwner)
|
||||||
|
// 2. Только свои посты из канала (где adminNumber совпадает)
|
||||||
|
if (!req.isOwner) {
|
||||||
|
// Получить админа текущего пользователя
|
||||||
|
const admin = await ModerationAdmin.findOne({ telegramId: req.user.telegramId });
|
||||||
|
|
||||||
|
// Если это пост из канала, проверить, что админ - автор
|
||||||
|
if (post.publishedToChannel && post.adminNumber) {
|
||||||
|
if (!admin || admin.adminNumber !== post.adminNumber) {
|
||||||
|
return res.status(403).json({ error: 'Вы можете редактировать только свои посты из канала' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Если это обычный пост, владелец может редактировать любой, остальные админы - нет
|
||||||
|
// (это поведение можно изменить по необходимости)
|
||||||
|
}
|
||||||
|
|
||||||
if (content !== undefined) {
|
if (content !== undefined) {
|
||||||
post.content = content;
|
post.content = content;
|
||||||
post.hashtags = Array.isArray(hashtags)
|
post.hashtags = Array.isArray(hashtags)
|
||||||
|
|
@ -230,6 +251,19 @@ router.put('/posts/:id', authenticateModeration, requireModerationAccess, async
|
||||||
|
|
||||||
await post.populate('author', 'username firstName lastName role banned bannedUntil');
|
await post.populate('author', 'username firstName lastName role banned bannedUntil');
|
||||||
|
|
||||||
|
// Если пост был опубликован в канале, обновить его там
|
||||||
|
if (post.publishedToChannel && post.channelMessageId) {
|
||||||
|
try {
|
||||||
|
const { updateChannelMessage } = require('../bots/serverMonitor');
|
||||||
|
if (updateChannelMessage) {
|
||||||
|
await updateChannelMessage(post.channelMessageId, post.content, post.hashtags);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Не удалось обновить сообщение в канале:', error);
|
||||||
|
// Продолжаем выполнение, даже если не удалось обновить в канале
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
post: {
|
post: {
|
||||||
id: post._id,
|
id: post._id,
|
||||||
|
|
@ -239,6 +273,8 @@ router.put('/posts/:id', authenticateModeration, requireModerationAccess, async
|
||||||
tags: post.tags,
|
tags: post.tags,
|
||||||
images: post.images,
|
images: post.images,
|
||||||
isNSFW: post.isNSFW,
|
isNSFW: post.isNSFW,
|
||||||
|
publishedToChannel: post.publishedToChannel,
|
||||||
|
adminNumber: post.adminNumber,
|
||||||
editedAt: post.editedAt,
|
editedAt: post.editedAt,
|
||||||
createdAt: post.createdAt
|
createdAt: post.createdAt
|
||||||
}
|
}
|
||||||
|
|
@ -688,8 +724,25 @@ router.post(
|
||||||
const caption = captionLines.join('\n');
|
const caption = captionLines.join('\n');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendChannelMediaGroup(files, caption);
|
// Отправить в канал и получить message_id
|
||||||
res.json({ success: true });
|
const messageId = await sendChannelMediaGroup(files, caption);
|
||||||
|
|
||||||
|
// Создать пост в базе данных
|
||||||
|
const newPost = new Post({
|
||||||
|
author: req.user._id,
|
||||||
|
content: description,
|
||||||
|
hashtags: tagsArray.map(tag => tag.replace('#', '').toLowerCase()),
|
||||||
|
images: [], // Медиа хранится в Telegram
|
||||||
|
tags: ['other'], // Можно настроить определение типа контента
|
||||||
|
publishedToChannel: true,
|
||||||
|
channelMessageId: messageId,
|
||||||
|
adminNumber: slotNumber,
|
||||||
|
isNSFW: false
|
||||||
|
});
|
||||||
|
|
||||||
|
await newPost.save();
|
||||||
|
|
||||||
|
res.json({ success: true, postId: newPost._id, messageId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logSecurityEvent('CHANNEL_PUBLISH_FAILED', req, { error: error.message });
|
logSecurityEvent('CHANNEL_PUBLISH_FAILED', req, { error: error.message });
|
||||||
res.status(500).json({ error: 'Не удалось опубликовать в канал' });
|
res.status(500).json({ error: 'Не удалось опубликовать в канал' });
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ app.use('/api/mod-app', require('./routes/modApp'));
|
||||||
|
|
||||||
// Базовый роут
|
// Базовый роут
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.json({ message: 'NakamaHost API работает' });
|
res.json({ message: 'Nakama API работает' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для автоматического резервного копирования MongoDB
|
||||||
|
# Запускается раз в неделю через cron
|
||||||
|
|
||||||
|
# Цвета для вывода
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Конфигурация
|
||||||
|
BACKUP_DIR="/mnt/nakama-backups"
|
||||||
|
MONGODB_URI="mongodb://103.80.87.247:27017/nakama"
|
||||||
|
DB_NAME="nakama"
|
||||||
|
DATE=$(date +"%Y-%m-%d_%H-%M-%S")
|
||||||
|
BACKUP_NAME="nakama_backup_${DATE}"
|
||||||
|
RETENTION_DAYS=30
|
||||||
|
|
||||||
|
echo -e "${GREEN}=== Начало резервного копирования ===${NC}"
|
||||||
|
echo "Дата: $(date)"
|
||||||
|
echo "База данных: ${DB_NAME}"
|
||||||
|
echo "Директория бекапов: ${BACKUP_DIR}"
|
||||||
|
|
||||||
|
# Создать директорию для бекапов, если она не существует
|
||||||
|
mkdir -p "${BACKUP_DIR}"
|
||||||
|
|
||||||
|
# Выполнить mongodump
|
||||||
|
echo -e "${YELLOW}Создание резервной копии...${NC}"
|
||||||
|
if mongodump --uri="${MONGODB_URI}" --db="${DB_NAME}" --out="${BACKUP_DIR}/${BACKUP_NAME}" --gzip; then
|
||||||
|
echo -e "${GREEN}✓ Резервная копия успешно создана${NC}"
|
||||||
|
|
||||||
|
# Создать архив
|
||||||
|
echo -e "${YELLOW}Создание архива...${NC}"
|
||||||
|
cd "${BACKUP_DIR}" || exit 1
|
||||||
|
if tar -czf "${BACKUP_NAME}.tar.gz" "${BACKUP_NAME}"; then
|
||||||
|
echo -e "${GREEN}✓ Архив создан: ${BACKUP_NAME}.tar.gz${NC}"
|
||||||
|
|
||||||
|
# Удалить временную директорию
|
||||||
|
rm -rf "${BACKUP_NAME}"
|
||||||
|
|
||||||
|
# Получить размер архива
|
||||||
|
BACKUP_SIZE=$(du -h "${BACKUP_NAME}.tar.gz" | cut -f1)
|
||||||
|
echo -e "${GREEN}Размер архива: ${BACKUP_SIZE}${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Ошибка создания архива${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Ошибка создания резервной копии${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Удалить старые бекапы (старше RETENTION_DAYS дней)
|
||||||
|
echo -e "${YELLOW}Удаление старых бекапов (старше ${RETENTION_DAYS} дней)...${NC}"
|
||||||
|
find "${BACKUP_DIR}" -name "nakama_backup_*.tar.gz" -type f -mtime +${RETENTION_DAYS} -delete
|
||||||
|
REMAINING_BACKUPS=$(find "${BACKUP_DIR}" -name "nakama_backup_*.tar.gz" -type f | wc -l)
|
||||||
|
echo -e "${GREEN}Оставшихся бекапов: ${REMAINING_BACKUPS}${NC}"
|
||||||
|
|
||||||
|
# Вывести список последних бекапов
|
||||||
|
echo -e "${YELLOW}Последние 5 бекапов:${NC}"
|
||||||
|
ls -lht "${BACKUP_DIR}"/nakama_backup_*.tar.gz | head -5
|
||||||
|
|
||||||
|
echo -e "${GREEN}=== Резервное копирование завершено ===${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Отправить уведомление (опционально)
|
||||||
|
# Раскомментируйте, если хотите получать уведомления
|
||||||
|
# curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||||
|
# -d "chat_id=${YOUR_CHAT_ID}" \
|
||||||
|
# -d "text=✅ Резервная копия Nakama успешно создана: ${BACKUP_NAME}.tar.gz (${BACKUP_SIZE})"
|
||||||
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.backend
|
||||||
|
container_name: nakama-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
- MONGODB_URI=${MONGODB_URI:-mongodb://103.80.87.247:27017/nakama}
|
||||||
|
- JWT_SECRET=${JWT_SECRET}
|
||||||
|
- JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET}
|
||||||
|
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
|
||||||
|
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
|
||||||
|
- MODERATION_BOT_TOKEN=${MODERATION_BOT_TOKEN}
|
||||||
|
- MODERATION_OWNER_USERNAMES=${MODERATION_OWNER_USERNAMES:-glpshchn00}
|
||||||
|
- MODERATION_CHANNEL_USERNAME=${MODERATION_CHANNEL_USERNAME:-@reichenbfurry}
|
||||||
|
- GELBOORU_API_KEY=${GELBOORU_API_KEY}
|
||||||
|
- GELBOORU_USER_ID=${GELBOORU_USER_ID}
|
||||||
|
- FRONTEND_URL=${FRONTEND_URL:-http://localhost:5173}
|
||||||
|
- CORS_ORIGIN=${CORS_ORIGIN:-*}
|
||||||
|
- REDIS_URL=${REDIS_URL}
|
||||||
|
volumes:
|
||||||
|
# Медиа хранится на удаленном сервере, монтируем через NFS или SSH
|
||||||
|
- /mnt/nakama-media:/app/backend/uploads
|
||||||
|
networks:
|
||||||
|
- nakama-network
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.frontend
|
||||||
|
args:
|
||||||
|
VITE_API_URL: ${VITE_API_URL:-http://localhost:3000/api}
|
||||||
|
container_name: nakama-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "5173:80"
|
||||||
|
networks:
|
||||||
|
- nakama-network
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
moderation:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.moderation
|
||||||
|
args:
|
||||||
|
VITE_API_URL: ${VITE_API_URL:-http://localhost:3000/api}
|
||||||
|
container_name: nakama-moderation
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "5174:80"
|
||||||
|
networks:
|
||||||
|
- nakama-network
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
mongodb:
|
||||||
|
image: mongo:7
|
||||||
|
container_name: nakama-mongodb
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
environment:
|
||||||
|
- MONGO_INITDB_DATABASE=nakama
|
||||||
|
volumes:
|
||||||
|
# База данных на удаленном сервере
|
||||||
|
- /mnt/nakama-db:/data/db
|
||||||
|
networks:
|
||||||
|
- nakama-network
|
||||||
|
command: mongod --bind_ip_all
|
||||||
|
|
||||||
|
backup:
|
||||||
|
image: mongo:7
|
||||||
|
container_name: nakama-backup
|
||||||
|
restart: "no"
|
||||||
|
environment:
|
||||||
|
- MONGODB_URI=${MONGODB_URI:-mongodb://103.80.87.247:27017/nakama}
|
||||||
|
volumes:
|
||||||
|
- /mnt/nakama-backups:/backups
|
||||||
|
- ./backend/scripts/backup.js:/backup.js
|
||||||
|
networks:
|
||||||
|
- nakama-network
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
entrypoint: /bin/bash
|
||||||
|
command: -c "echo 'Backup container ready. Run manual backups or set up cron.'"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
nakama-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
nakama-db:
|
||||||
|
driver: local
|
||||||
|
nakama-media:
|
||||||
|
driver: local
|
||||||
|
nakama-backups:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<title>NakamaHost</title>
|
<title>Nakama</title>
|
||||||
<!-- Telegram Web App SDK - прямая загрузка -->
|
<!-- Telegram Web App SDK - прямая загрузка -->
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ export default function Feed({ user }) {
|
||||||
<div className="feed-page">
|
<div className="feed-page">
|
||||||
{/* Хедер */}
|
{/* Хедер */}
|
||||||
<div className="feed-header">
|
<div className="feed-header">
|
||||||
<h1>NakamaHost</h1>
|
<h1>Nakama</h1>
|
||||||
<button className="create-btn" onClick={handleCreatePost}>
|
<button className="create-btn" onClick={handleCreatePost}>
|
||||||
<Plus size={20} />
|
<Plus size={20} />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ export default function Profile({ user, setUser }) {
|
||||||
</div>
|
</div>
|
||||||
<div className="donation-text">
|
<div className="donation-text">
|
||||||
<h3>Поддержите проект</h3>
|
<h3>Поддержите проект</h3>
|
||||||
<p>Каждый взнос помогает развивать NakamaHost и запускать новые функции.</p>
|
<p>Каждый взнос помогает развивать Nakama и запускать новые функции.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button className="donation-button" onClick={handleDonate}>
|
<button className="donation-button" onClick={handleDonate}>
|
||||||
|
|
|
||||||
|
|
@ -286,14 +286,18 @@ export default function App() {
|
||||||
import.meta.env.PROD ? window.location.origin : 'http://localhost:3000'
|
import.meta.env.PROD ? window.location.origin : 'http://localhost:3000'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('Инициализация чата, подключение к:', API_URL);
|
||||||
|
|
||||||
const socket = io(`${API_URL}/mod-chat`, {
|
const socket = io(`${API_URL}/mod-chat`, {
|
||||||
transports: ['websocket', 'polling'],
|
transports: ['websocket', 'polling'],
|
||||||
reconnection: true,
|
reconnection: true,
|
||||||
reconnectionDelay: 1000,
|
reconnectionDelay: 1000,
|
||||||
reconnectionAttempts: 5
|
reconnectionAttempts: 5,
|
||||||
|
timeout: 10000
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
|
console.log('WebSocket подключен, отправка auth...');
|
||||||
socket.emit('auth', {
|
socket.emit('auth', {
|
||||||
username: user.username,
|
username: user.username,
|
||||||
telegramId: user.telegramId
|
telegramId: user.telegramId
|
||||||
|
|
@ -301,42 +305,57 @@ export default function App() {
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('ready', () => {
|
socket.on('ready', () => {
|
||||||
|
console.log('Авторизация успешна!');
|
||||||
setChatState((prev) => ({ ...prev, connected: true }));
|
setChatState((prev) => ({ ...prev, connected: true }));
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('unauthorized', () => {
|
socket.on('unauthorized', () => {
|
||||||
|
console.error('Unauthorized в чате');
|
||||||
setChatState((prev) => ({ ...prev, connected: false }));
|
setChatState((prev) => ({ ...prev, connected: false }));
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('message', (message) => {
|
socket.on('message', (message) => {
|
||||||
|
console.log('Получено сообщение:', message);
|
||||||
setChatState((prev) => ({
|
setChatState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
messages: [...prev.messages, message]
|
messages: [...prev.messages, message]
|
||||||
}));
|
}));
|
||||||
if (chatListRef.current) {
|
setTimeout(() => {
|
||||||
chatListRef.current.scrollTo({
|
if (chatListRef.current) {
|
||||||
top: chatListRef.current.scrollHeight,
|
chatListRef.current.scrollTo({
|
||||||
behavior: 'smooth'
|
top: chatListRef.current.scrollHeight,
|
||||||
});
|
behavior: 'smooth'
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('online', (online) => {
|
socket.on('online', (online) => {
|
||||||
|
console.log('Обновление списка онлайн:', online);
|
||||||
setChatState((prev) => ({ ...prev, online }));
|
setChatState((prev) => ({ ...prev, online }));
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', (reason) => {
|
||||||
|
console.log('WebSocket отключен:', reason);
|
||||||
setChatState((prev) => ({ ...prev, connected: false }));
|
setChatState((prev) => ({ ...prev, connected: false }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('connect_error', (error) => {
|
||||||
|
console.error('Ошибка подключения WebSocket:', error);
|
||||||
|
});
|
||||||
|
|
||||||
chatSocketRef.current = socket;
|
chatSocketRef.current = socket;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSendChat = () => {
|
const handleSendChat = () => {
|
||||||
if (!chatSocketRef.current || !chatState.connected) return;
|
if (!chatSocketRef.current || !chatState.connected) {
|
||||||
|
console.warn('Чат не подключен');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const text = chatInput.trim();
|
const text = chatInput.trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
console.log('Отправка сообщения:', text);
|
||||||
chatSocketRef.current.emit('message', { text });
|
chatSocketRef.current.emit('message', { text });
|
||||||
setChatInput('');
|
setChatInput('');
|
||||||
};
|
};
|
||||||
|
|
@ -515,14 +534,24 @@ export default function App() {
|
||||||
</div>
|
</div>
|
||||||
{post.images?.length ? (
|
{post.images?.length ? (
|
||||||
<div className="image-grid">
|
<div className="image-grid">
|
||||||
{post.images.map((img, idx) => (
|
{post.images.map((img, idx) => {
|
||||||
<div key={idx} className="image-thumb">
|
// Преобразовать относительный путь в абсолютный
|
||||||
<img src={img} alt="" />
|
const imageUrl = img.startsWith('http')
|
||||||
<button className="image-remove" onClick={() => handleRemoveImage(post.id, idx)}>
|
? img
|
||||||
<Trash2 size={16} />
|
: `${import.meta.env.VITE_API_URL || (import.meta.env.PROD ? window.location.origin : 'http://localhost:3000')}${img}`;
|
||||||
</button>
|
|
||||||
</div>
|
return (
|
||||||
))}
|
<div key={idx} className="image-thumb">
|
||||||
|
<img src={imageUrl} alt="" onError={(e) => {
|
||||||
|
e.target.style.display = 'none';
|
||||||
|
console.error('Failed to load image:', imageUrl);
|
||||||
|
}} />
|
||||||
|
<button className="image-remove" onClick={() => handleRemoveImage(post.id, idx)}>
|
||||||
|
<Trash2 size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -569,24 +598,84 @@ export default function App() {
|
||||||
</div>
|
</div>
|
||||||
<div className="list-item-subtitle">Статус: {report.status}</div>
|
<div className="list-item-subtitle">Статус: {report.status}</div>
|
||||||
<div className="report-content">
|
<div className="report-content">
|
||||||
<p>{report.reason || 'Причина не указана'}</p>
|
<div style={{ marginBottom: '12px', padding: '12px', backgroundColor: 'var(--bg-secondary)', borderRadius: '8px' }}>
|
||||||
|
<strong>Причина жалобы:</strong>
|
||||||
|
<p style={{ marginTop: '4px' }}>{report.reason || 'Причина не указана'}</p>
|
||||||
|
</div>
|
||||||
{report.post && (
|
{report.post && (
|
||||||
<div className="report-post">
|
<div className="report-post" style={{ padding: '12px', backgroundColor: 'var(--bg-secondary)', borderRadius: '8px', marginBottom: '12px' }}>
|
||||||
<strong>Пост:</strong> {report.post.content || 'Без текста'}
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<strong>Пост от @{report.post.author?.username || 'Удалён'}</strong>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
{report.post.content || 'Без текста'}
|
||||||
|
</div>
|
||||||
|
{report.post.images?.length > 0 && (
|
||||||
|
<div className="image-grid" style={{ marginTop: '8px' }}>
|
||||||
|
{report.post.images.slice(0, 3).map((img, idx) => {
|
||||||
|
const imageUrl = img.startsWith('http')
|
||||||
|
? img
|
||||||
|
: `${import.meta.env.VITE_API_URL || (import.meta.env.PROD ? window.location.origin : 'http://localhost:3000')}${img}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
key={idx}
|
||||||
|
src={imageUrl}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
width: '80px',
|
||||||
|
height: '80px',
|
||||||
|
objectFit: 'cover',
|
||||||
|
borderRadius: '4px',
|
||||||
|
marginRight: '4px'
|
||||||
|
}}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.style.display = 'none';
|
||||||
|
console.error('Failed to load image:', imageUrl);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{report.post.images.length > 3 && (
|
||||||
|
<span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>
|
||||||
|
+{report.post.images.length - 3} ещё
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="list-item-actions">
|
<div className="list-item-actions">
|
||||||
|
{report.post && (
|
||||||
|
<>
|
||||||
|
<button className="btn danger" onClick={() => handlePostDelete(report.post.id)}>
|
||||||
|
<Trash2 size={16} />
|
||||||
|
Удалить пост
|
||||||
|
</button>
|
||||||
|
{report.post.author && (
|
||||||
|
<button className="btn warn" onClick={() => handleBanAuthor(report.post.id)}>
|
||||||
|
<Ban size={16} />
|
||||||
|
Забанить автора
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<button className="btn" onClick={() => handleReportStatus(report.id, 'resolved')}>
|
<button className="btn" onClick={() => handleReportStatus(report.id, 'resolved')}>
|
||||||
Решено
|
Решено
|
||||||
</button>
|
</button>
|
||||||
<button className="btn warn" onClick={() => handleReportStatus(report.id, 'dismissed')}>
|
<button className="btn" onClick={() => handleReportStatus(report.id, 'dismissed')}>
|
||||||
Отклонить
|
Отклонить репорт
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{reportsData.reports.length === 0 && (
|
||||||
|
<div style={{ textAlign: 'center', padding: '40px', color: 'var(--text-secondary)' }}>
|
||||||
|
Нет активных репортов
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 10240;
|
||||||
|
gzip_proxied expired no-cache no-store private auth;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;
|
||||||
|
gzip_disable "MSIE [1-6]\.";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Кэширование статических файлов
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 10240;
|
||||||
|
gzip_proxied expired no-cache no-store private auth;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;
|
||||||
|
gzip_disable "MSIE [1-6]\.";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Кэширование статических файлов
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для настройки удаленного хранилища медиа и базы данных
|
||||||
|
# Использует SSHFS для монтирования удаленных директорий
|
||||||
|
|
||||||
|
# Цвета для вывода
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Конфигурация удаленного сервера
|
||||||
|
REMOTE_SERVER="103.80.87.247"
|
||||||
|
REMOTE_USER="root" # Измените на вашего пользователя
|
||||||
|
REMOTE_MEDIA_PATH="/var/nakama/media"
|
||||||
|
REMOTE_DB_PATH="/var/nakama/db"
|
||||||
|
REMOTE_BACKUPS_PATH="/var/nakama/backups"
|
||||||
|
|
||||||
|
# Локальные точки монтирования
|
||||||
|
LOCAL_MEDIA_MOUNT="/mnt/nakama-media"
|
||||||
|
LOCAL_DB_MOUNT="/mnt/nakama-db"
|
||||||
|
LOCAL_BACKUPS_MOUNT="/mnt/nakama-backups"
|
||||||
|
|
||||||
|
echo -e "${GREEN}=== Настройка удаленного хранилища ===${NC}"
|
||||||
|
|
||||||
|
# Проверка наличия SSHFS
|
||||||
|
if ! command -v sshfs &> /dev/null; then
|
||||||
|
echo -e "${RED}✗ SSHFS не установлен${NC}"
|
||||||
|
echo -e "${YELLOW}Установка SSHFS...${NC}"
|
||||||
|
|
||||||
|
# Определить ОС и установить SSHFS
|
||||||
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||||
|
sudo apt-get update && sudo apt-get install -y sshfs
|
||||||
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
brew install macfuse sshfs
|
||||||
|
else
|
||||||
|
echo -e "${RED}Неподдерживаемая ОС. Установите SSHFS вручную.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ SSHFS установлен${NC}"
|
||||||
|
|
||||||
|
# Создать локальные директории для монтирования
|
||||||
|
echo -e "${YELLOW}Создание локальных директорий...${NC}"
|
||||||
|
sudo mkdir -p "$LOCAL_MEDIA_MOUNT" "$LOCAL_DB_MOUNT" "$LOCAL_BACKUPS_MOUNT"
|
||||||
|
|
||||||
|
# Создать директории на удаленном сервере
|
||||||
|
echo -e "${YELLOW}Создание директорий на удаленном сервере...${NC}"
|
||||||
|
ssh "${REMOTE_USER}@${REMOTE_SERVER}" "mkdir -p ${REMOTE_MEDIA_PATH} ${REMOTE_DB_PATH} ${REMOTE_BACKUPS_PATH}"
|
||||||
|
|
||||||
|
# Монтировать директории
|
||||||
|
echo -e "${YELLOW}Монтирование удаленных директорий...${NC}"
|
||||||
|
|
||||||
|
# Медиа
|
||||||
|
if mountpoint -q "$LOCAL_MEDIA_MOUNT"; then
|
||||||
|
echo -e "${YELLOW}Медиа уже смонтированы${NC}"
|
||||||
|
else
|
||||||
|
sshfs "${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_MEDIA_PATH}" "$LOCAL_MEDIA_MOUNT" \
|
||||||
|
-o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
|
||||||
|
echo -e "${GREEN}✓ Медиа смонтированы: ${LOCAL_MEDIA_MOUNT}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# База данных
|
||||||
|
if mountpoint -q "$LOCAL_DB_MOUNT"; then
|
||||||
|
echo -e "${YELLOW}БД уже смонтирована${NC}"
|
||||||
|
else
|
||||||
|
sshfs "${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_DB_PATH}" "$LOCAL_DB_MOUNT" \
|
||||||
|
-o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
|
||||||
|
echo -e "${GREEN}✓ БД смонтирована: ${LOCAL_DB_MOUNT}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Бекапы
|
||||||
|
if mountpoint -q "$LOCAL_BACKUPS_MOUNT"; then
|
||||||
|
echo -e "${YELLOW}Бекапы уже смонтированы${NC}"
|
||||||
|
else
|
||||||
|
sshfs "${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_BACKUPS_PATH}" "$LOCAL_BACKUPS_MOUNT" \
|
||||||
|
-o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
|
||||||
|
echo -e "${GREEN}✓ Бекапы смонтированы: ${LOCAL_BACKUPS_MOUNT}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Добавить в /etc/fstab для автоматического монтирования при загрузке (опционально)
|
||||||
|
echo -e "${YELLOW}Хотите добавить автомонтирование при загрузке? (y/n)${NC}"
|
||||||
|
read -r RESPONSE
|
||||||
|
|
||||||
|
if [[ "$RESPONSE" =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e "${YELLOW}Добавление в /etc/fstab...${NC}"
|
||||||
|
|
||||||
|
# Создать резервную копию fstab
|
||||||
|
sudo cp /etc/fstab /etc/fstab.backup
|
||||||
|
|
||||||
|
# Добавить записи в fstab
|
||||||
|
cat <<EOF | sudo tee -a /etc/fstab
|
||||||
|
# Nakama remote storage
|
||||||
|
${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_MEDIA_PATH} ${LOCAL_MEDIA_MOUNT} fuse.sshfs defaults,_netdev,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 0 0
|
||||||
|
${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_DB_PATH} ${LOCAL_DB_MOUNT} fuse.sshfs defaults,_netdev,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 0 0
|
||||||
|
${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_BACKUPS_PATH} ${LOCAL_BACKUPS_MOUNT} fuse.sshfs defaults,_netdev,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 0 0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Автомонтирование настроено${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}=== Настройка завершена ===${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Монтированные директории:"
|
||||||
|
echo " Медиа: ${LOCAL_MEDIA_MOUNT}"
|
||||||
|
echo " БД: ${LOCAL_DB_MOUNT}"
|
||||||
|
echo " Бекапы: ${LOCAL_BACKUPS_MOUNT}"
|
||||||
|
echo ""
|
||||||
|
echo "Для размонтирования используйте:"
|
||||||
|
echo " sudo umount ${LOCAL_MEDIA_MOUNT}"
|
||||||
|
echo " sudo umount ${LOCAL_DB_MOUNT}"
|
||||||
|
echo " sudo umount ${LOCAL_BACKUPS_MOUNT}"
|
||||||
|
|
||||||
Loading…
Reference in New Issue