From fb74a4a25d4107aa053f8e693b42f8b95038517b Mon Sep 17 00:00:00 2001 From: "Andrey K. Choi" Date: Sat, 8 Nov 2025 18:45:20 +0900 Subject: [PATCH] some fixes --- .env.example | 4 + Makefile | 20 +++++ backend/backend/settings.py | 16 +++- docker-compose.yml | 2 +- docs/EMAIL_SETUP.md | 114 +++++++++++++++++++++++++++ scripts/check-api-endpoints.sh | 95 +++++++++++++++++++++++ scripts/check-db-connection.sh | 78 +++++++++++++++++++ scripts/diagnose-server.sh | 136 +++++++++++++++++++++++++++++++++ scripts/fix-nginx-admin.sh | 75 ++++++++++++++++++ 9 files changed, 538 insertions(+), 2 deletions(-) create mode 100644 docs/EMAIL_SETUP.md create mode 100755 scripts/check-api-endpoints.sh create mode 100755 scripts/check-db-connection.sh create mode 100755 scripts/diagnose-server.sh create mode 100755 scripts/fix-nginx-admin.sh diff --git a/.env.example b/.env.example index d5f7346..5d1785d 100644 --- a/.env.example +++ b/.env.example @@ -59,6 +59,10 @@ DJANGO_EMAIL_PORT=587 DJANGO_EMAIL_HOST_USER= DJANGO_EMAIL_HOST_PASSWORD= DJANGO_EMAIL_USE_TLS=True +DJANGO_EMAIL_USE_SSL=False +DJANGO_EMAIL_TIMEOUT=30 +DJANGO_DEFAULT_FROM_EMAIL= +DJANGO_SERVER_EMAIL= # SSL настройки (для Let's Encrypt) DOMAIN= diff --git a/Makefile b/Makefile index 1742705..b0ab0ca 100644 --- a/Makefile +++ b/Makefile @@ -452,6 +452,26 @@ reset: ## Сброс к заводским настройкам # === Security Commands === +diagnose: ## Диагностика проблем на сервере + @echo "🔍 Запуск диагностики сервера..." + @./scripts/diagnose-server.sh + +check-db: ## Проверка подключения к базе данных + @echo "🔗 Проверка подключения к БД..." + @./scripts/check-db-connection.sh + +check-api: ## Проверка всех API эндпоинтов + @echo "🌐 Проверка API эндпоинтов..." + @./scripts/check-api-endpoints.sh + +check-users: ## Показать данные пользователей + @echo "👥 Данные пользователей:" + @$(DOCKER_COMPOSE) exec db psql -U links_user -d links_db -c "SELECT id, username, email, first_name, last_name, is_active, is_staff, is_superuser, date_joined FROM users_user ORDER BY date_joined DESC;" + +fix-admin: ## Исправить маршрут /admin в nginx + @echo "🔧 Исправление маршрута /admin..." + @./scripts/fix-nginx-admin.sh + security-audit: ## Аудит безопасности PostgreSQL @echo "🔍 Запуск аудита безопасности PostgreSQL..." @./scripts/audit-db-security.sh diff --git a/backend/backend/settings.py b/backend/backend/settings.py index db21b77..7fbf2cc 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -190,4 +190,18 @@ SECURE_HSTS_INCLUDE_SUBDOMAINS = os.getenv('DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAIN SECURE_HSTS_PRELOAD = os.getenv('DJANGO_SECURE_HSTS_PRELOAD', 'False') == 'True' SECURE_CONTENT_TYPE_NOSNIFF = os.getenv('DJANGO_SECURE_CONTENT_TYPE_NOSNIFF', 'True') == 'True' SECURE_BROWSER_XSS_FILTER = os.getenv('DJANGO_SECURE_BROWSER_XSS_FILTER', 'True') == 'True' -X_FRAME_OPTIONS = os.getenv('DJANGO_X_FRAME_OPTIONS', 'DENY') \ No newline at end of file +X_FRAME_OPTIONS = os.getenv('DJANGO_X_FRAME_OPTIONS', 'DENY') + +# Email настройки из переменных окружения +EMAIL_BACKEND = os.getenv('DJANGO_EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend') +EMAIL_HOST = os.getenv('DJANGO_EMAIL_HOST', '') +EMAIL_PORT = int(os.getenv('DJANGO_EMAIL_PORT', '587')) +EMAIL_HOST_USER = os.getenv('DJANGO_EMAIL_HOST_USER', '') +EMAIL_HOST_PASSWORD = os.getenv('DJANGO_EMAIL_HOST_PASSWORD', '') +EMAIL_USE_TLS = os.getenv('DJANGO_EMAIL_USE_TLS', 'True') == 'True' +EMAIL_USE_SSL = os.getenv('DJANGO_EMAIL_USE_SSL', 'False') == 'True' +EMAIL_TIMEOUT = int(os.getenv('DJANGO_EMAIL_TIMEOUT', '30')) + +# Адреса отправителей по умолчанию +DEFAULT_FROM_EMAIL = os.getenv('DJANGO_DEFAULT_FROM_EMAIL', EMAIL_HOST_USER) +SERVER_EMAIL = os.getenv('DJANGO_SERVER_EMAIL', EMAIL_HOST_USER) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 89be7c5..e40416a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' + services: web: build: ./backend diff --git a/docs/EMAIL_SETUP.md b/docs/EMAIL_SETUP.md new file mode 100644 index 0000000..c32b3e3 --- /dev/null +++ b/docs/EMAIL_SETUP.md @@ -0,0 +1,114 @@ +# Email Configuration Guide + +## Настройка email для различных провайдеров + +### 📧 Mail.ru +```env +DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +DJANGO_EMAIL_HOST=smtp.mail.ru +DJANGO_EMAIL_PORT=587 +DJANGO_EMAIL_HOST_USER=your-email@mail.ru +DJANGO_EMAIL_HOST_PASSWORD=your-app-password +DJANGO_EMAIL_USE_TLS=True +DJANGO_EMAIL_USE_SSL=False +DJANGO_DEFAULT_FROM_EMAIL=your-email@mail.ru +DJANGO_SERVER_EMAIL=your-email@mail.ru +``` + +### 📧 Gmail +```env +DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +DJANGO_EMAIL_HOST=smtp.gmail.com +DJANGO_EMAIL_PORT=587 +DJANGO_EMAIL_HOST_USER=your-email@gmail.com +DJANGO_EMAIL_HOST_PASSWORD=your-app-password +DJANGO_EMAIL_USE_TLS=True +DJANGO_EMAIL_USE_SSL=False +DJANGO_DEFAULT_FROM_EMAIL=your-email@gmail.com +DJANGO_SERVER_EMAIL=your-email@gmail.com +``` + +### 📧 Yandex +```env +DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +DJANGO_EMAIL_HOST=smtp.yandex.ru +DJANGO_EMAIL_PORT=587 +DJANGO_EMAIL_HOST_USER=your-email@yandex.ru +DJANGO_EMAIL_HOST_PASSWORD=your-app-password +DJANGO_EMAIL_USE_TLS=True +DJANGO_EMAIL_USE_SSL=False +DJANGO_DEFAULT_FROM_EMAIL=your-email@yandex.ru +DJANGO_SERVER_EMAIL=your-email@yandex.ru +``` + +### 📧 Rambler +```env +DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +DJANGO_EMAIL_HOST=smtp.rambler.ru +DJANGO_EMAIL_PORT=587 +DJANGO_EMAIL_HOST_USER=your-email@rambler.ru +DJANGO_EMAIL_HOST_PASSWORD=your-password +DJANGO_EMAIL_USE_TLS=True +DJANGO_EMAIL_USE_SSL=False +DJANGO_DEFAULT_FROM_EMAIL=your-email@rambler.ru +DJANGO_SERVER_EMAIL=your-email@rambler.ru +``` + +## 🔐 Важные моменты безопасности + +### Mail.ru +1. Перейдите в настройки почты +2. Включите "Пароль для внешних приложений" +3. Создайте специальный пароль приложения +4. Используйте этот пароль в `DJANGO_EMAIL_HOST_PASSWORD` + +### Gmail +1. Включите двухфакторную аутентификацию +2. Создайте "Пароль приложения" в настройках Google Account +3. Используйте этот пароль в `DJANGO_EMAIL_HOST_PASSWORD` + +### Yandex +1. Перейдите в настройки безопасности +2. Создайте "Пароль приложения" +3. Используйте этот пароль в `DJANGO_EMAIL_HOST_PASSWORD` + +## 🧪 Тестирование email + +Создайте файл для тестирования email в Django shell: + +```python +# В Django shell: python manage.py shell +from django.core.mail import send_mail + +send_mail( + 'Тестовое письмо', + 'Это тестовое сообщение для проверки email настроек.', + 'from@example.com', # будет заменено на DEFAULT_FROM_EMAIL + ['to@example.com'], + fail_silently=False, +) +``` + +## 🔧 Режим разработки + +Для разработки используйте консольный backend: +```env +DJANGO_EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend +``` + +Письма будут выводиться в консоль вместо отправки. + +## 📁 Сохранение в файл + +Для сохранения писем в файл: +```env +DJANGO_EMAIL_BACKEND=django.core.mail.backends.filebased.EmailBackend +DJANGO_EMAIL_FILE_PATH=/app/sent_emails +``` + +## 🚫 Отключение email + +Для полного отключения email: +```env +DJANGO_EMAIL_BACKEND=django.core.mail.backends.dummy.EmailBackend +``` \ No newline at end of file diff --git a/scripts/check-api-endpoints.sh b/scripts/check-api-endpoints.sh new file mode 100755 index 0000000..dd5eacc --- /dev/null +++ b/scripts/check-api-endpoints.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# scripts/check-api-endpoints.sh - Проверка всех API эндпоинтов + +set -e + +echo "🌐 Проверка API эндпоинтов..." +echo "=============================" + +# Функция для тестирования эндпоинта +test_endpoint() { + local url="$1" + local expected_codes="$2" + local description="$3" + + echo -n "🔗 $description: " + + local response_code + response_code=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000") + + if echo "$expected_codes" | grep -q "$response_code"; then + echo "✅ ($response_code)" + return 0 + else + echo "❌ ($response_code)" + return 1 + fi +} + +echo "" +echo "📡 1. Прямые подключения к контейнерам:" + +# Backend endpoints +test_endpoint "http://localhost:8000/admin/" "200 302" "Django Admin" +test_endpoint "http://localhost:8000/api/" "200 404" "Django API Root" +test_endpoint "http://localhost:8000/api/auth/register/" "405" "Register API" +test_endpoint "http://localhost:8000/static/" "404" "Static Files" + +# Frontend endpoint +test_endpoint "http://localhost:3000/" "200" "Frontend Home" + +echo "" +echo "📡 2. Через nginx (внешние подключения):" + +# Through nginx +test_endpoint "http://localhost/admin/" "200 302" "Admin через nginx" +test_endpoint "http://localhost/api/" "200 404" "API через nginx" +test_endpoint "http://localhost/" "200" "Frontend через nginx" + +echo "" +echo "📡 3. HTTPS эндпоинты (если SSL настроен):" + +# HTTPS endpoints +test_endpoint "https://localhost/admin/" "200 302" "Admin HTTPS" || true +test_endpoint "https://localhost/api/" "200 404" "API HTTPS" || true +test_endpoint "https://localhost/" "200" "Frontend HTTPS" || true + +# С доменным именем +test_endpoint "https://links.shareon.kr/admin/" "200 302" "Admin на домене" || true +test_endpoint "https://links.shareon.kr/api/" "200 404" "API на домене" || true +test_endpoint "https://links.shareon.kr/" "200" "Frontend на домене" || true + +echo "" +echo "📡 4. Детальная проверка API:" + +echo -n "🔗 API Schema (Swagger): " +swagger_code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8000/api/schema/" 2>/dev/null || echo "000") +if [ "$swagger_code" = "200" ]; then + echo "✅ ($swagger_code)" +else + echo "❌ ($swagger_code)" +fi + +echo -n "🔗 API Documentation: " +docs_code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8000/api/docs/" 2>/dev/null || echo "000") +if [ "$docs_code" = "200" ]; then + echo "✅ ($docs_code)" +else + echo "❌ ($docs_code)" +fi + +echo "" +echo "📡 5. Проверка специфических API эндпоинтов:" + +# Проверяем конкретные API endpoints +echo -n "🔗 Auth endpoints: " +if curl -s -X POST -H "Content-Type: application/json" \ + -d '{"username":"test","password":"test","email":"test@test.com"}' \ + http://localhost:8000/api/auth/register/ 2>/dev/null | grep -q -E "(error|success|created|username|email)" 2>/dev/null; then + echo "✅ (отвечает)" +else + echo "❌ (не отвечает)" +fi + +echo "" +echo "🏁 Проверка эндпоинтов завершена!" \ No newline at end of file diff --git a/scripts/check-db-connection.sh b/scripts/check-db-connection.sh new file mode 100755 index 0000000..0273788 --- /dev/null +++ b/scripts/check-db-connection.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# scripts/check-db-connection.sh - Быстрая проверка подключения к БД + +set -e + +echo "🔗 Проверка подключения к базе данных..." + +# Проверка из контейнера web +echo "📡 Тест 1: Django ORM подключение..." +if docker-compose exec -T web python -c " +import os +import django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') +django.setup() +from django.db import connection +from django.contrib.auth import get_user_model + +try: + # Тест подключения + with connection.cursor() as cursor: + cursor.execute('SELECT 1') + print('✅ База данных отвечает') + + # Тест ORM + User = get_user_model() + user_count = User.objects.count() + print(f'✅ Пользователей в системе: {user_count}') + + # Тест миграций + from django.core.management import call_command + from io import StringIO + out = StringIO() + call_command('showmigrations', '--plan', stdout=out) + migrations_output = out.getvalue() + if '[X]' in migrations_output: + print('✅ Миграции применены') + else: + print('⚠️ Есть неприменённые миграции') + +except Exception as e: + print(f'❌ Ошибка Django: {e}') + exit(1) +" 2>/dev/null; then + echo "✅ Django успешно подключается к БД" +else + echo "❌ Ошибка подключения Django к БД" + exit 1 +fi + +# Прямая проверка PostgreSQL +echo "" +echo "📡 Тест 2: Прямое подключение к PostgreSQL..." +if docker-compose exec -T db pg_isready -U links_user -d links_db; then + echo "✅ PostgreSQL готов к подключениям" +else + echo "❌ PostgreSQL не готов" + exit 1 +fi + +# Проверка таблиц +echo "" +echo "📡 Тест 3: Проверка структуры БД..." +if docker-compose exec -T db psql -U links_user -d links_db -c " +SELECT + schemaname, + tablename +FROM pg_tables +WHERE schemaname = 'public' +ORDER BY tablename; +" 2>/dev/null; then + echo "✅ Таблицы базы данных доступны" +else + echo "❌ Не удается получить список таблиц" + exit 1 +fi + +echo "" +echo "✅ Все проверки подключения к БД пройдены успешно!" \ No newline at end of file diff --git a/scripts/diagnose-server.sh b/scripts/diagnose-server.sh new file mode 100755 index 0000000..f95df72 --- /dev/null +++ b/scripts/diagnose-server.sh @@ -0,0 +1,136 @@ +#!/bin/bash +# scripts/diagnose-server.sh - Диагностика проблем на сервере + +set -e + +echo "🔍 Диагностика сервера CatLink..." +echo "=================================" + +echo "" +echo "📊 1. Статус Docker контейнеров:" +docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + +echo "" +echo "🔗 2. Статус сетей Docker:" +docker network ls + +echo "" +echo "📡 3. Проверка подключения к базе данных:" +echo " Тестируем подключение из контейнера web к базе данных..." +if docker-compose exec -T web python -c " +import os +import django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') +django.setup() +from django.db import connection +try: + with connection.cursor() as cursor: + cursor.execute('SELECT 1') + print('✅ Подключение к базе данных успешно') +except Exception as e: + print(f'❌ Ошибка подключения к БД: {e}') +" 2>/dev/null; then + echo "✅ База данных доступна" +else + echo "❌ База данных недоступна" +fi + +echo "" +echo "👥 4. Данные пользователей в базе:" +if docker-compose exec -T db psql -U links_user -d links_db -c "SELECT COUNT(*) as user_count FROM users_user;" 2>/dev/null; then + echo "✅ Таблица пользователей доступна" + docker-compose exec -T db psql -U links_user -d links_db -c "SELECT id, username, email, is_active, is_staff, is_superuser FROM users_user ORDER BY date_joined DESC LIMIT 5;" +else + echo "❌ Не удается получить данные пользователей" +fi + +echo "" +echo "🌐 5. Проверка nginx конфигурации:" +if sudo nginx -t 2>/dev/null; then + echo "✅ Конфигурация nginx корректна" +else + echo "❌ Ошибка в конфигурации nginx" +fi + +echo "" +echo "🔧 6. Статус nginx сервиса:" +sudo systemctl status nginx --no-pager -l + +echo "" +echo "🚪 7. Проверка портов:" +echo " Порты, которые слушает система:" +sudo netstat -tlnp | grep -E ":(80|443|3000|8000|5432)" || echo "Нет активных портов" + +echo "" +echo "📋 8. Логи nginx (последние 10 строк):" +sudo tail -10 /var/log/nginx/error.log 2>/dev/null || echo "Логи nginx недоступны" + +echo "" +echo "📋 9. Логи Docker контейнеров:" +echo " --- Web контейнер (последние 5 строк) ---" +docker-compose logs --tail=5 web 2>/dev/null || echo "Логи web недоступны" + +echo "" +echo " --- Frontend контейнер (последние 5 строк) ---" +docker-compose logs --tail=5 frontend 2>/dev/null || echo "Логи frontend недоступны" + +echo "" +echo "🔗 10. Тест API эндпоинтов локально:" +echo " Тестируем доступность API..." +if curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/admin/ | grep -q "200\|302"; then + echo "✅ Django admin доступен по http://localhost:8000/admin/" +else + echo "❌ Django admin недоступен по http://localhost:8000/admin/" +fi + +if curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/api/ | grep -q "200\|404"; then + echo "✅ Django API доступен по http://localhost:8000/api/" +else + echo "❌ Django API недоступен по http://localhost:8000/api/" +fi + +if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/ | grep -q "200"; then + echo "✅ Frontend доступен по http://localhost:3000/" +else + echo "❌ Frontend недоступен по http://localhost:3000/" +fi + +echo "" +echo "🌍 11. Тест внешних эндпоинтов:" +echo " Тестируем доступность через nginx..." +if curl -s -o /dev/null -w "%{http_code}" http://localhost/admin/ | grep -q "200\|302"; then + echo "✅ Admin доступен через nginx: http://localhost/admin/" +else + echo "❌ Admin недоступен через nginx: http://localhost/admin/" +fi + +if curl -s -o /dev/null -w "%{http_code}" http://localhost/api/ | grep -q "200\|404"; then + echo "✅ API доступен через nginx: http://localhost/api/" +else + echo "❌ API недоступен через nginx: http://localhost/api/" +fi + +echo "" +echo "🔐 12. SSL сертификаты:" +if [ -d "/etc/letsencrypt/live" ]; then + echo "✅ Директория SSL сертификатов существует:" + sudo ls -la /etc/letsencrypt/live/ 2>/dev/null || echo "Не удается прочитать директорию сертификатов" +else + echo "❌ SSL сертификаты не найдены" +fi + +echo "" +echo "📄 13. Переменные окружения (.env):" +echo " Проверяем ключевые переменные..." +if [ -f ".env" ]; then + echo "✅ Файл .env существует" + echo " DJANGO_DEBUG: $(grep '^DJANGO_DEBUG=' .env | cut -d= -f2)" + echo " NEXT_PUBLIC_API_URL: $(grep '^NEXT_PUBLIC_API_URL=' .env | cut -d= -f2)" + echo " DATABASE_HOST: $(grep '^DATABASE_HOST=' .env | cut -d= -f2)" +else + echo "❌ Файл .env не найден" +fi + +echo "" +echo "🏁 Диагностика завершена!" +echo "=================================" \ No newline at end of file diff --git a/scripts/fix-nginx-admin.sh b/scripts/fix-nginx-admin.sh new file mode 100755 index 0000000..7dd76ac --- /dev/null +++ b/scripts/fix-nginx-admin.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# scripts/fix-nginx-admin.sh - Исправление маршрута admin в nginx + +set -e + +echo "🔧 Исправление маршрута /admin в nginx..." + +# Проверяем, существует ли файл конфигурации +if [ ! -f "/etc/nginx/sites-available/links" ]; then + echo "❌ Конфигурация nginx не найдена. Создаем новую..." + sudo ./scripts/setup-nginx.sh + exit 0 +fi + +echo "📄 Текущая конфигурация nginx:" +sudo cat /etc/nginx/sites-available/links | grep -A 5 -B 2 "location /admin" + +echo "" +echo "🔍 Проверяем текущую конфигурацию..." + +# Проверим, есть ли маршрут admin +if sudo grep -q "location /admin/" /etc/nginx/sites-available/links; then + echo "✅ Маршрут /admin/ найден в конфигурации" +else + echo "❌ Маршрут /admin/ не найден. Добавляем..." + + # Создаем backup + sudo cp /etc/nginx/sites-available/links "/etc/nginx/sites-available/links.backup.$(date +%Y%m%d_%H%M%S)" + + # Добавляем маршрут admin после API + sudo sed -i '/location \/api\// a\\n # Proxy admin requests to backend (Django)\n location /admin/ {\n proxy_pass http://localhost:8000;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n }' /etc/nginx/sites-available/links +fi + +echo "" +echo "🧪 Проверяем конфигурацию nginx..." +if sudo nginx -t; then + echo "✅ Конфигурация nginx корректна" +else + echo "❌ Ошибка в конфигурации nginx" + exit 1 +fi + +echo "" +echo "🔄 Перезагружаем nginx..." +sudo systemctl reload nginx + +echo "" +echo "🧪 Тестируем маршрут /admin..." +sleep 2 + +if curl -s -o /dev/null -w "%{http_code}" http://localhost/admin/ | grep -q "200\|302"; then + echo "✅ Маршрут /admin/ работает через nginx" +else + echo "❌ Маршрут /admin/ не работает" + echo "" + echo "🔍 Дополнительная диагностика:" + echo " - Проверяем статус контейнеров:" + docker-compose ps + echo "" + echo " - Проверяем прямое подключение к Django:" + if curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/admin/ | grep -q "200\|302"; then + echo " ✅ Django admin доступен напрямую" + echo " ❌ Проблема в конфигурации nginx" + else + echo " ❌ Django admin недоступен напрямую" + echo " ❌ Проблема в Django контейнере" + fi +fi + +echo "" +echo "📋 Полная конфигурация nginx:" +sudo cat /etc/nginx/sites-available/links + +echo "" +echo "✅ Исправление завершено!" \ No newline at end of file