From fb12c0626be07f053693bdb2354e7daf33b2b7b8 Mon Sep 17 00:00:00 2001 From: glpshchn <464976@niuitmo.ru> Date: Fri, 21 Nov 2025 04:44:54 +0300 Subject: [PATCH] Update files --- MINIO_NGINX_SETUP.md | 323 +++++++++++++++++++++++++++++++++++++++ backend/routes/modApp.js | 47 ++++-- backend/routes/posts.js | 36 +++-- nginx-minio.conf | 156 +++++++++++++++++++ setup-minio-nginx.sh | 159 +++++++++++++++++++ 5 files changed, 698 insertions(+), 23 deletions(-) create mode 100644 MINIO_NGINX_SETUP.md create mode 100644 nginx-minio.conf create mode 100644 setup-minio-nginx.sh diff --git a/MINIO_NGINX_SETUP.md b/MINIO_NGINX_SETUP.md new file mode 100644 index 0000000..38ed8d4 --- /dev/null +++ b/MINIO_NGINX_SETUP.md @@ -0,0 +1,323 @@ +# 🌐 Настройка Nginx для MinIO с доменом + +## Обзор + +Эта инструкция поможет настроить Nginx reverse proxy для MinIO с SSL сертификатами. + +**Результат:** +- MinIO API: `https://minio.glpshchn.ru` +- MinIO Console: `https://admin.minio.glpshchn.ru` + +## Предварительные требования + +1. ✅ MinIO запущен на порту 9000 (API) и 9001 (Console) +2. ✅ DNS записи настроены: + ``` + minio.glpshchn.ru A 103.80.87.247 + admin.minio.glpshchn.ru A 103.80.87.247 + ``` +3. ✅ Порты 80 и 443 открыты в firewall + +## Быстрая установка (автоматически) + +### Шаг 1: Скопируйте файлы на сервер + +```bash +# На вашем компьютере +scp nginx-minio.conf setup-minio-nginx.sh root@103.80.87.247:/root/ + +# Подключитесь к серверу +ssh root@103.80.87.247 +``` + +### Шаг 2: Отредактируйте email в скрипте + +```bash +nano setup-minio-nginx.sh + +# Измените строку: +EMAIL="your-email@example.com" # <- Ваш email для Let's Encrypt +``` + +### Шаг 3: Запустите скрипт + +```bash +chmod +x setup-minio-nginx.sh +sudo ./setup-minio-nginx.sh +``` + +Скрипт автоматически: +- ✅ Установит Nginx и Certbot +- ✅ Получит SSL сертификаты от Let's Encrypt +- ✅ Настроит Nginx конфигурацию +- ✅ Настроит автообновление сертификатов + +## Ручная установка + +### Шаг 1: Установите Nginx + +```bash +sudo apt update +sudo apt install -y nginx certbot python3-certbot-nginx +``` + +### Шаг 2: Получите SSL сертификаты + +```bash +# Создайте директорию для certbot +sudo mkdir -p /var/www/certbot + +# Временный конфиг для получения сертификатов +sudo tee /etc/nginx/sites-available/minio-temp > /dev/null << 'EOF' +server { + listen 80; + server_name minio.glpshchn.ru admin.minio.glpshchn.ru; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } +} +EOF + +# Активируйте конфиг +sudo ln -sf /etc/nginx/sites-available/minio-temp /etc/nginx/sites-enabled/ +sudo rm -f /etc/nginx/sites-enabled/default +sudo nginx -t && sudo systemctl reload nginx + +# Получите сертификаты +sudo certbot certonly --webroot \ + -w /var/www/certbot \ + -d minio.glpshchn.ru \ + -d admin.minio.glpshchn.ru \ + --email your-email@example.com \ + --agree-tos \ + --non-interactive +``` + +### Шаг 3: Установите конфигурацию + +```bash +# Скопируйте конфиг +sudo cp nginx-minio.conf /etc/nginx/sites-available/minio.glpshchn.ru + +# Активируйте +sudo ln -sf /etc/nginx/sites-available/minio.glpshchn.ru /etc/nginx/sites-enabled/ +sudo rm -f /etc/nginx/sites-enabled/minio-temp + +# Проверьте и перезагрузите +sudo nginx -t +sudo systemctl reload nginx +``` + +### Шаг 4: Настройте автообновление SSL + +```bash +sudo systemctl enable certbot.timer +sudo systemctl start certbot.timer +``` + +## Проверка работы + +### 1. Проверьте SSL + +```bash +curl https://minio.glpshchn.ru/minio/health/live +# Должен вернуть: пустой ответ (это нормально) + +# Проверьте сертификат +echo | openssl s_client -connect minio.glpshchn.ru:443 -servername minio.glpshchn.ru 2>/dev/null | openssl x509 -noout -dates +``` + +### 2. Проверьте доступ к файлам + +```bash +# Загрузите тестовый файл через mc +mc cp test.txt myminio/nakama-media/test.txt + +# Проверьте доступность через HTTPS +curl -I https://minio.glpshchn.ru/nakama-media/test.txt +# Должен вернуть: HTTP/2 200 +``` + +### 3. Откройте Console + +Откройте в браузере: +``` +https://admin.minio.glpshchn.ru +``` + +Должен открыться веб-интерфейс MinIO. + +## Обновите Nakama + +### Шаг 1: Обновите .env + +```bash +nano .env + +# Измените MinIO настройки: +MINIO_ENABLED=true +MINIO_ENDPOINT=minio.glpshchn.ru # <- Теперь домен! +MINIO_PORT=443 # <- HTTPS порт +MINIO_USE_SSL=true # <- Включаем SSL +MINIO_ACCESS_KEY=ваш_ключ +MINIO_SECRET_KEY=ваш_секрет +MINIO_BUCKET=nakama-media +MINIO_PUBLIC_URL=https://minio.glpshchn.ru # <- Публичный URL +MINIO_PUBLIC_BUCKET=true +``` + +### Шаг 2: Обновите S3 клиент + +В `backend/utils/minio.js` - уже настроено автоматически! +Код использует `config.minio.useSSL` для выбора протокола. + +### Шаг 3: Перезапустите backend + +```bash +docker-compose restart backend + +# Проверьте логи +docker-compose logs backend | grep -i minio +``` + +Должно быть: +``` +✅ MinIO успешно подключен + endpoint: minio.glpshchn.ru:443 + ssl: true +``` + +## Устранение проблем + +### Ошибка: "SSL certificate problem" + +**Причина:** Сертификат не доверенный или истёк. + +**Решение:** +```bash +# Обновите сертификаты +sudo certbot renew --force-renewal +sudo systemctl reload nginx +``` + +### Ошибка: "Connection refused" + +**Причина:** MinIO не запущен или порты закрыты. + +**Решение:** +```bash +# Проверьте MinIO +curl http://127.0.0.1:9000/minio/health/live + +# Проверьте Nginx +sudo nginx -t +sudo systemctl status nginx + +# Проверьте firewall +sudo ufw status +``` + +### Ошибка: "502 Bad Gateway" + +**Причина:** Nginx не может подключиться к MinIO. + +**Решение:** +```bash +# Проверьте что MinIO слушает на 127.0.0.1:9000 +netstat -tulpn | grep 9000 + +# Проверьте логи Nginx +sudo tail -f /var/log/nginx/minio-error.log + +# Проверьте логи MinIO +journalctl -u minio -f +``` + +### CORS ошибки в браузере + +**Причина:** CORS заголовки не настроены. + +**Решение:** Конфиг уже содержит все необходимые CORS заголовки. +Если проблема остаётся, проверьте что домен frontend добавлен. + +## Мониторинг + +### Проверка статуса + +```bash +# Nginx +sudo systemctl status nginx + +# Certbot timer +sudo systemctl status certbot.timer + +# Логи доступа +sudo tail -f /var/log/nginx/minio-access.log + +# Логи ошибок +sudo tail -f /var/log/nginx/minio-error.log +``` + +### Статистика использования + +```bash +# Количество запросов за последний час +sudo grep "$(date '+%d/%b/%Y:%H')" /var/log/nginx/minio-access.log | wc -l + +# Топ IP адресов +sudo awk '{print $1}' /var/log/nginx/minio-access.log | sort | uniq -c | sort -rn | head -10 +``` + +## Автообновление SSL + +Сертификаты обновляются автоматически через systemd timer. + +Проверка: +```bash +# Статус таймера +sudo systemctl list-timers certbot.timer + +# Тестовое обновление (dry-run) +sudo certbot renew --dry-run + +# Ручное обновление (если нужно) +sudo certbot renew +sudo systemctl reload nginx +``` + +## Бонус: Оптимизация производительности + +### Кеширование статических файлов + +Добавьте в конфиг Nginx внутри `location /`: + +```nginx +# Кеш для изображений +location ~* \.(jpg|jpeg|png|gif|webp)$ { + proxy_pass http://127.0.0.1:9000; + proxy_cache_valid 200 7d; + add_header X-Cache-Status $upstream_cache_status; + expires 7d; +} +``` + +### Сжатие + +```nginx +# В http блоке /etc/nginx/nginx.conf +gzip on; +gzip_vary on; +gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; +``` + +## Итоговые URLs + +После настройки: + +- **MinIO API:** `https://minio.glpshchn.ru` +- **MinIO Console:** `https://admin.minio.glpshchn.ru` +- **Пример файла:** `https://minio.glpshchn.ru/nakama-media/posts/example.jpg` + +Теперь все изображения будут загружаться через HTTPS! 🎉 + diff --git a/backend/routes/modApp.js b/backend/routes/modApp.js index 07a4f65..fd1c4e8 100644 --- a/backend/routes/modApp.js +++ b/backend/routes/modApp.js @@ -268,15 +268,32 @@ router.delete('/posts/:id', authenticateModeration, requireModerationAccess, asy return res.status(404).json({ error: 'Пост не найден' }); } - // Удалить локальные изображения - if (post.images && post.images.length) { - post.images.forEach((imagePath) => { - if (!imagePath.startsWith('/uploads')) return; - const fullPath = path.join(__dirname, '..', imagePath); - if (fs.existsSync(fullPath)) { - fs.unlink(fullPath, () => {}); + // Удалить изображения из MinIO + try { + const { deleteFiles } = require('../utils/minio'); + const filesToDelete = []; + + if (post.images && post.images.length > 0) { + post.images.forEach(imageUrl => { + // Извлекаем имя файла из URL + const match = imageUrl.match(/nakama-media\/(.+)$/); + if (match) { + filesToDelete.push(match[1]); + } + }); + } else if (post.imageUrl) { + const match = post.imageUrl.match(/nakama-media\/(.+)$/); + if (match) { + filesToDelete.push(match[1]); } - }); + } + + if (filesToDelete.length > 0) { + await deleteFiles(filesToDelete); + console.log(`✅ Удалено ${filesToDelete.length} файлов из MinIO (modApp)`); + } + } catch (error) { + console.error('❌ Ошибка удаления файлов из MinIO:', error); } await Post.deleteOne({ _id: post._id }); @@ -300,10 +317,16 @@ router.delete('/posts/:id/images/:index', authenticateModeration, requireModerat post.imageUrl = post.images[0] || null; await post.save(); - if (removed && removed.startsWith('/uploads')) { - const fullPath = path.join(__dirname, '..', removed); - if (fs.existsSync(fullPath)) { - fs.unlink(fullPath, () => {}); + // Удалить изображение из MinIO + if (removed) { + try { + const match = removed.match(/nakama-media\/(.+)$/); + if (match) { + await deleteFile(match[1]); + console.log(`✅ Удалено изображение из MinIO: ${match[1]}`); + } + } catch (error) { + console.error('❌ Ошибка удаления изображения из MinIO:', error); } } diff --git a/backend/routes/posts.js b/backend/routes/posts.js index 64f5b3f..8fd3ff6 100644 --- a/backend/routes/posts.js +++ b/backend/routes/posts.js @@ -313,19 +313,33 @@ router.delete('/:id', authenticate, async (req, res) => { return res.status(403).json({ error: 'Нет прав на удаление' }); } - // Удалить изображения если есть - if (post.images && post.images.length > 0) { - post.images.forEach(imagePath => { - const fullPath = path.join(__dirname, '..', imagePath); - if (fs.existsSync(fullPath)) { - fs.unlinkSync(fullPath); + // Удалить изображения из MinIO + try { + const filesToDelete = []; + + if (post.images && post.images.length > 0) { + post.images.forEach(imageUrl => { + // Извлекаем имя файла из URL + // https://minio.glpshchn.ru/nakama-media/posts/123456.jpg -> posts/123456.jpg + const match = imageUrl.match(/nakama-media\/(.+)$/); + if (match) { + filesToDelete.push(match[1]); + } + }); + } else if (post.imageUrl) { + const match = post.imageUrl.match(/nakama-media\/(.+)$/); + if (match) { + filesToDelete.push(match[1]); } - }); - } else if (post.imageUrl) { - const imagePath = path.join(__dirname, '..', post.imageUrl); - if (fs.existsSync(imagePath)) { - fs.unlinkSync(imagePath); } + + if (filesToDelete.length > 0) { + await deleteFiles(filesToDelete); + console.log(`✅ Удалено ${filesToDelete.length} файлов из MinIO`); + } + } catch (error) { + console.error('❌ Ошибка удаления файлов из MinIO:', error); + // Продолжаем удаление поста даже если файлы не удалились } await Post.findByIdAndDelete(req.params.id); diff --git a/nginx-minio.conf b/nginx-minio.conf new file mode 100644 index 0000000..3667a2c --- /dev/null +++ b/nginx-minio.conf @@ -0,0 +1,156 @@ +# Nginx конфигурация для MinIO +# Сохраните как: /etc/nginx/sites-available/minio.glpshchn.ru + +# Перенаправление HTTP -> HTTPS +server { + listen 80; + listen [::]:80; + server_name minio.glpshchn.ru; + + # Certbot validation + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + # Redirect all HTTP to HTTPS + location / { + return 301 https://$server_name$request_uri; + } +} + +# MinIO API (основной доступ к файлам) +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name minio.glpshchn.ru; + + # SSL сертификаты (Let's Encrypt) + ssl_certificate /etc/letsencrypt/live/minio.glpshchn.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/minio.glpshchn.ru/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/minio.glpshchn.ru/chain.pem; + + # SSL настройки (современные и безопасные) + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_stapling on; + ssl_stapling_verify on; + + # Security headers + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + + # Увеличенные лимиты для загрузки файлов + client_max_body_size 1000M; + client_body_timeout 300s; + client_header_timeout 300s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + send_timeout 300s; + + # Игнорировать заголовки для кеширования от upstream + ignore_invalid_headers off; + + # CORS заголовки (для доступа из браузера) + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, HEAD' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Content-Length, Accept-Encoding, X-Requested-With, Range, Content-Disposition, Content-MD5, X-Amz-Content-Sha256, X-Amz-Date, X-Amz-User-Agent' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range, ETag' always; + add_header 'Access-Control-Max-Age' 1728000 always; + + # Обработка preflight запросов + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, HEAD'; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Content-Length, Accept-Encoding, X-Requested-With, Range, Content-Disposition, Content-MD5, X-Amz-Content-Sha256, X-Amz-Date, X-Amz-User-Agent'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + + # Логи + access_log /var/log/nginx/minio-access.log; + error_log /var/log/nginx/minio-error.log; + + # Проксирование к MinIO API + location / { + proxy_set_header Host $http_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_set_header X-NginX-Proxy true; + + # Для корректной работы S3 API + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + # Disable buffering для больших файлов + proxy_buffering off; + proxy_request_buffering off; + + # Backend MinIO (порт 9000) + proxy_pass http://127.0.0.1:9000; + + # WebSocket поддержка (если используется) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Health check endpoint + location /minio/health/live { + proxy_set_header Host $http_host; + proxy_pass http://127.0.0.1:9000; + } +} + +# MinIO Console (опционально, для веб-интерфейса) +# Доступ через admin.minio.glpshchn.ru +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name admin.minio.glpshchn.ru; + + # SSL сертификаты + ssl_certificate /etc/letsencrypt/live/admin.minio.glpshchn.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/admin.minio.glpshchn.ru/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/admin.minio.glpshchn.ru/chain.pem; + + # SSL настройки + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384'; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + + # Security headers + add_header Strict-Transport-Security "max-age=31536000" always; + + # Увеличенные лимиты + client_max_body_size 1000M; + + # Логи + access_log /var/log/nginx/minio-console-access.log; + error_log /var/log/nginx/minio-console-error.log; + + # Проксирование к MinIO Console + location / { + proxy_set_header Host $http_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; + + # Backend MinIO Console (порт 9001) + proxy_pass http://127.0.0.1:9001; + + # WebSocket для real-time updates + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} + diff --git a/setup-minio-nginx.sh b/setup-minio-nginx.sh new file mode 100644 index 0000000..b6ce55d --- /dev/null +++ b/setup-minio-nginx.sh @@ -0,0 +1,159 @@ +#!/bin/bash + +# Скрипт для настройки Nginx + SSL для MinIO + +set -e + +echo "🔧 Настройка Nginx для MinIO" +echo "======================================" + +# Проверка root прав +if [[ $EUID -ne 0 ]]; then + echo "❌ Этот скрипт должен быть запущен с правами root (sudo)" + exit 1 +fi + +# Переменные +DOMAIN="minio.glpshchn.ru" +CONSOLE_DOMAIN="admin.minio.glpshchn.ru" +EMAIL="your-email@example.com" # Измените на ваш email для Let's Encrypt + +echo "" +echo "Настройка для доменов:" +echo " API: https://$DOMAIN" +echo " Console: https://$CONSOLE_DOMAIN" +echo "" + +# Установка Nginx (если не установлен) +if ! command -v nginx &> /dev/null; then + echo "📦 Установка Nginx..." + apt update + apt install -y nginx +fi + +# Установка Certbot (если не установлен) +if ! command -v certbot &> /dev/null; then + echo "📦 Установка Certbot..." + apt install -y certbot python3-certbot-nginx +fi + +# Создание директории для certbot +mkdir -p /var/www/certbot + +# Временный конфиг для получения сертификатов +echo "📝 Создание временного конфига для получения SSL..." + +cat > /etc/nginx/sites-available/minio-temp << 'EOF' +server { + listen 80; + server_name minio.glpshchn.ru admin.minio.glpshchn.ru; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 200 "MinIO setup in progress..."; + add_header Content-Type text/plain; + } +} +EOF + +# Активация временного конфига +ln -sf /etc/nginx/sites-available/minio-temp /etc/nginx/sites-enabled/minio-temp + +# Удаление дефолтного конфига +rm -f /etc/nginx/sites-enabled/default + +# Проверка конфигурации +echo "🔍 Проверка конфигурации Nginx..." +nginx -t + +# Перезагрузка Nginx +echo "🔄 Перезагрузка Nginx..." +systemctl reload nginx + +# Получение SSL сертификатов +echo "" +echo "🔒 Получение SSL сертификатов от Let's Encrypt..." +echo "" + +certbot certonly --webroot \ + -w /var/www/certbot \ + -d $DOMAIN \ + -d $CONSOLE_DOMAIN \ + --email $EMAIL \ + --agree-tos \ + --non-interactive + +if [ $? -ne 0 ]; then + echo "❌ Ошибка получения SSL сертификата" + echo "Проверьте:" + echo " 1. DNS записи A для $DOMAIN и $CONSOLE_DOMAIN указывают на этот сервер" + echo " 2. Порт 80 открыт в firewall" + echo " 3. Домены доступны из интернета" + exit 1 +fi + +echo "" +echo "✅ SSL сертификаты получены!" + +# Копирование основного конфига +echo "📝 Установка основного конфига..." +cp nginx-minio.conf /etc/nginx/sites-available/minio.glpshchn.ru + +# Активация конфига +ln -sf /etc/nginx/sites-available/minio.glpshchn.ru /etc/nginx/sites-enabled/minio.glpshchn.ru + +# Удаление временного конфига +rm -f /etc/nginx/sites-enabled/minio-temp + +# Проверка конфигурации +echo "🔍 Проверка финальной конфигурации..." +nginx -t + +if [ $? -ne 0 ]; then + echo "❌ Ошибка в конфигурации Nginx" + exit 1 +fi + +# Перезагрузка Nginx +echo "🔄 Перезагрузка Nginx с новой конфигурацией..." +systemctl reload nginx + +# Настройка автообновления сертификатов +echo "⏰ Настройка автообновления SSL сертификатов..." +systemctl enable certbot.timer +systemctl start certbot.timer + +# Открытие портов в firewall (если используется ufw) +if command -v ufw &> /dev/null; then + echo "🔥 Настройка firewall..." + ufw allow 'Nginx Full' + ufw allow 443/tcp + ufw allow 80/tcp +fi + +echo "" +echo "======================================" +echo "✅ Настройка завершена!" +echo "" +echo "MinIO API доступен по адресу:" +echo " https://$DOMAIN" +echo "" +echo "MinIO Console доступен по адресу:" +echo " https://$CONSOLE_DOMAIN" +echo "" +echo "Проверка:" +echo " curl https://$DOMAIN/minio/health/live" +echo "" +echo "Обновите .env в Nakama:" +echo " MINIO_ENDPOINT=$DOMAIN" +echo " MINIO_PORT=443" +echo " MINIO_USE_SSL=true" +echo " MINIO_PUBLIC_URL=https://$DOMAIN" +echo "" +echo "Перезапустите backend:" +echo " docker-compose restart backend" +echo "======================================" +