diff --git a/deploy/apache/social-graph.conf b/deploy/apache/social-graph.conf index 5a974d0..0318c3c 100644 --- a/deploy/apache/social-graph.conf +++ b/deploy/apache/social-graph.conf @@ -11,21 +11,23 @@ # sudo apache2ctl configtest && sudo systemctl reload apache2 - ServerName your-domain.com - ServerAlias www.your-domain.com + ServerName social.deepfishing.ru ProxyPreserveHost On - RequestHeader set X-Forwarded-Proto "http" + RequestHeader set X-Forwarded-Proto "https" RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s - # Backend API (раскомментируйте при VITE_DATA_MODE=remote и profile with-backend) - # ProxyPass /api http://127.0.0.1:8000/api - # ProxyPassReverse /api http://127.0.0.1:8000/api - - # Frontend SPA (nginx в контейнере sg_frontend) + # Вариант A (рекомендуется): весь трафик на frontend-контейнер. + # В remote-сборке frontend сам проксирует /api → backend. ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ + # Вариант B: проксировать /api напрямую на backend (если frontend без nginx.remote.conf) + # ProxyPass /api http://127.0.0.1:8000/api + # ProxyPassReverse /api http://127.0.0.1:8000/api + # ProxyPass / http://127.0.0.1:8080/ + # ProxyPassReverse / http://127.0.0.1:8080/ + ErrorLog ${APACHE_LOG_DIR}/social-graph-error.log CustomLog ${APACHE_LOG_DIR}/social-graph-access.log combined diff --git a/deploy/social.deepfishing.ru.md b/deploy/social.deepfishing.ru.md index e2ce68f..ea73c7f 100644 --- a/deploy/social.deepfishing.ru.md +++ b/deploy/social.deepfishing.ru.md @@ -116,12 +116,15 @@ cd /opt/social-graph ### Remote (frontend + backend) +> Обязательно: `VITE_DATA_MODE=remote` в `.env.prod` и профиль `with-backend` — иначе вход/регистрация не работают (ошибка 405). + ```bash docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod build -docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod up -d --build frontend -docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod --profile with-backend up -d --build backend +docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod --profile with-backend up -d --build ``` +Команда поднимает **оба** контейнера. Frontend в remote-сборке проксирует `/api` → backend внутри Docker. + ### Только frontend (local) ```bash @@ -320,8 +323,9 @@ sudo ufw enable | Симптом | Решение | |---------|---------| +| **405 Not Allowed** при входе/регистрации | Запросы `/api` попали во frontend вместо backend. Пересоберите с `VITE_DATA_MODE=remote` (в образе включится `nginx.remote.conf`) **и** поднимите backend: `--profile with-backend` | | 502 на https://social.deepfishing.ru | `docker ps`, логи `sg_frontend` / `sg_backend` | -| Страница открывается, API 404 | В прокси включён `ProxyPass /api` → `:8000` | +| Страница открывается, API 404 | Backend не запущен или нет прокси `/api` | | 401 / не пускает | `USE_JWT_AUTH=true` в `.env.prod`, пересобрать backend | | Данные не сохраняются между устройствами | `VITE_DATA_MODE=remote`, пересобрать frontend | | Белый экран | `docker logs sg_frontend`, пересборка с `--build` | diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index ddac846..ae4d737 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -9,6 +9,10 @@ services: ports: - "${FRONTEND_BIND:-127.0.0.1}:${FRONTEND_PORT:-8080}:80" restart: unless-stopped + depends_on: + backend: + condition: service_started + required: false backend: profiles: ["with-backend"] diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod index b346baa..2fd1abc 100644 --- a/frontend/Dockerfile.prod +++ b/frontend/Dockerfile.prod @@ -16,7 +16,14 @@ RUN npm run build FROM nginx:1.27-alpine AS runtime -COPY nginx.conf /etc/nginx/conf.d/default.conf +ARG VITE_DATA_MODE=local +COPY nginx.conf /tmp/nginx.local.conf +COPY nginx.remote.conf /tmp/nginx.remote.conf +RUN if [ "$VITE_DATA_MODE" = "remote" ]; then \ + cp /tmp/nginx.remote.conf /etc/nginx/conf.d/default.conf; \ + else \ + cp /tmp/nginx.local.conf /etc/nginx/conf.d/default.conf; \ + fi COPY --from=build /app/dist /usr/share/nginx/html EXPOSE 80 diff --git a/frontend/nginx.remote.conf b/frontend/nginx.remote.conf new file mode 100644 index 0000000..1254eff --- /dev/null +++ b/frontend/nginx.remote.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_types text/css application/javascript application/json image/svg+xml; + + # API → Django backend (контейнер backend в docker-compose) + location /api/ { + proxy_pass http://backend:8000/api/; + proxy_http_version 1.1; + 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_connect_timeout 10s; + proxy_read_timeout 120s; + } + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { + expires 7d; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } +} diff --git a/frontend/src/lib/api/errors.js b/frontend/src/lib/api/errors.js index b199e55..aab0b74 100644 --- a/frontend/src/lib/api/errors.js +++ b/frontend/src/lib/api/errors.js @@ -1,6 +1,14 @@ export function formatApiErrorData(data) { if (!data) return '' - if (typeof data === 'string' && data.trim()) return data.trim() + if (typeof data === 'string' && data.trim()) { + const text = data.trim() + if (text.startsWith('<') && text.includes('')) { + const title = text.match(/([^<]+)<\/title>/i)?.[1]?.trim() + if (title) return title + return 'Сервер вернул HTML вместо JSON. Проверьте проксирование /api на backend.' + } + return text + } if (typeof data?.detail === 'string' && data.detail.trim()) return data.detail.trim() if (Array.isArray(data?.non_field_errors) && data.non_field_errors.length) { return data.non_field_errors.join('; ')