Appearance
Partizap Runbook
Серверы
| Alias | IP | SSH | Роль |
|---|---|---|---|
| main | 85.239.48.136 | ssh serpens@192.168.0.4 (из VPS 2) | Production: partizap.ru, admin.partizap.ru |
| dev | 192.168.0.5 | localhost (мы на VPS 2) | Development: dev.partizap.ru, dev-admin.partizap.ru, GitLab, YouTrack |
Сервисы
main (85.239.48.136)
| Сервис | Тип | Порт | Управление |
|---|---|---|---|
| nginx | systemd | 80, 443 | systemctl reload nginx (v1.28.3, nginx.org) |
| Nuxt (prod) | docker | 3001 | docker restart partizap-frontend-prod |
| PHP-FPM (prod) | systemd | sock | systemctl restart php8.3-fpm |
| Centrifugo (prod) | docker | 8002 | docker restart partizap-centrifugo-prod |
| Admin panel (prod) | PHP-FPM | php8.3-fpm-prod.sock | /var/www/partizap/admin-production (mTLS) |
dev (192.168.0.5)
| Сервис | Тип | Порт | Управление |
|---|---|---|---|
| nginx | systemd | 80 | systemctl reload nginx |
| Nuxt (dev) | docker | 3000 | см. ниже |
| Nuxt (prod) | docker | 3001 | docker restart partizap-frontend-prod |
| PHP-FPM (dev) | systemd | sock | systemctl restart php8.3-fpm |
| Centrifugo (dev) | docker | — | docker restart partizap-centrifugo-dev |
| Admin panel (dev) | PHP-FPM | 8084 (nginx) | /var/www/partizap/admin-development |
| Queue Worker (dev) | systemd | — | systemctl restart partizap-worker-dev |
| GitLab | omnibus | 8081 | gitlab-ctl restart |
| YouTrack | docker | 8080 | docker restart youtrack |
Queue Worker (systemd)
Воркер запускается через systemd unit partizap-worker-dev (dev) / partizap-worker-prod (prod).
Важно: unit-файл НЕ должен содержать Environment=APP_ENV=.... Все переменные окружения загружаются из .env через Dotenv в config/app.php. Если systemd задаёт APP_ENV через Environment=, то Dotenv::createImmutable() видит её в getenv() и не записывает в $_ENV, из-за чего приложение падает с пустым $_ENV['APP_ENV'].
502 Bad Gateway: диагностика и восстановление
Быстрая диагностика
bash
# 1. Определить какой сайт отдает 502
curl -sI https://partizap.ru # production
curl -sI https://dev.partizap.ru # development (через basic auth)
# 2. Проверить nginx
ssh root@85.239.48.136 "nginx -t && systemctl status nginx"
ssh -J root@85.239.48.136 root@192.168.0.5 "nginx -t && systemctl status nginx"
# 3. Посмотреть логи nginx (последние ошибки покажут какой upstream упал)
ssh root@85.239.48.136 "tail -20 /var/log/nginx/error.log"
ssh -J root@85.239.48.136 root@192.168.0.5 "tail -20 /var/log/partizap/development/nginx-error.log"partizap.ru — 502
Причина: один из upstream-сервисов на main не отвечает.
Nuxt (порт 3001):
bash
ssh root@85.239.48.136
curl -sI http://127.0.0.1:3001 # проверить
docker ps | grep front # статус контейнера
docker restart partizap-frontend-prod # перезапуск
docker logs partizap-frontend-prod --tail 50 # логиPHP-FPM API (sock):
bash
ssh root@85.239.48.136
systemctl status php8.3-fpm # статус
systemctl restart php8.3-fpm # перезапуск
ls -la /run/php/php8.3-fpm-prod.sock # проверить сокетCentrifugo WebSocket (порт 8002):
bash
ssh root@85.239.48.136
docker ps | grep centrifugo
docker restart partizap-centrifugo-proddev.partizap.ru — 502
Трафик идет: клиент → main (SSL + proxy) → dev (HTTP). 502 может быть на любом из двух серверов.
Шаг 1: проверить что VPS 2 отвечает (с VPS 1):
bash
ssh root@85.239.48.136 "curl -sI http://192.168.0.5:80 -H 'Host: dev.partizap.ru'"Если 502 — проблема на VPS 2. Если connection refused — nginx на VPS 2 не работает.
Шаг 2: на VPS 2 проверить upstream-сервисы:
bash
ssh -J root@85.239.48.136 root@192.168.0.5
# Nuxt dev (порт 3000) — самая частая причина
curl -sI http://127.0.0.1:3000
docker ps | grep partizap-frontend-dev
# Если контейнер не запущен:
cd /home/gitlab-runner/builds/SfDoCZhxV/0/root/partizap-frontend
docker compose -f docker-compose.dev.yml up -d
# Если контейнер есть, но не отвечает:
docker restart partizap-frontend-dev
docker logs partizap-frontend-dev --tail 50
# PHP-FPM dev
systemctl status php8.3-fpm
systemctl restart php8.3-fpmgitlab.partizap.ru — 502
bash
ssh -J root@85.239.48.136 root@192.168.0.5
gitlab-ctl status # статус всех компонентов
gitlab-ctl restart # полный перезапускtrack.partizap.ru — 502
bash
ssh -J root@85.239.48.136 root@192.168.0.5
docker ps | grep youtrack
docker restart youtrackДеплой бэкенда
Единый скрипт deploy/deploy.sh деплоит оба приложения (marketplace + admin) за один запуск. Оператору не нужно деплоить их отдельно — это невозможно забыть.
Как запускается
| Среда | Триггер | Скрипт | Откуда выполняется |
|---|---|---|---|
| develop | Пуш в develop | deploy/deploy.sh development | Автоматически (GitLab CI), на VPS 2 |
| production | Пуш в main | deploy/deploy.sh production | Вручную (кнопка в GitLab CI), VPS 2 → SSH на VPS 1 |
Что делает скрипт
Порядок выполнения для каждого окружения:
1. Marketplace (/var/www/partizap/{env})
├── git fetch + reset --hard origin/{branch}
├── composer install
├── rm -rf var/cache/* (DI container)
├── migrations:migrate (только marketplace, от partizap_owner)
├── grants/admin.sql (GRANT привилегий для admin DB role)
└── regenerate Doctrine proxies + clear metadata cache
2. Admin (/var/www/partizap/admin-{env})
├── git fetch + reset --hard origin/{branch}
├── composer install
├── rm -rf var/cache/*
└── regenerate Doctrine proxies + clear metadata cache
3. Shared runtime refresh (если хотя бы одно приложение обновилось)
├── OPcache reset (cachetool)
├── systemctl restart partizap-worker-{env} (только если marketplace изменился)
└── systemctl reload php8.3-fpm
4. Health check
└── curl https://{domain}/api/health (только marketplace, admin за mTLS)Миграции и гранты
Миграции запускаются только из marketplace. Admin-приложение использует DB role partizap_admin_{env}, у которой нет DDL-привилегий. grants/admin.sql применяется после миграций, чтобы новые таблицы получили нужные GRANT.
Деплой на develop
Автоматический — при пуше в develop CI запускает deploy-dev job.
bash
# Ручной деплой (если нужно без CI):
cd /var/www/partizap/development && sudo -u serpens git pull
cd /var/www/partizap/admin-development && sudo -u serpens git pull
sudo systemctl reload php8.3-fpmДеплой на production
- Создать MR из
develop→mainв GitLab - Смержить MR
- В GitLab CI → pipeline на
main→ нажать кнопку deploy-prod (manual job) - Скрипт выполнится по SSH на VPS 1 (
serpens@192.168.0.4)
bash
# Ручной деплой (если CI недоступен):
ssh serpens@192.168.0.4
cd /var/www/partizap/production && git fetch origin && git reset --hard origin/main
composer install --no-dev --optimize-autoloader --no-interaction
rm -rf var/cache/*
php bin/console migrations:migrate --no-interaction
cd /var/www/partizap/admin-production && git fetch origin && git reset --hard origin/main
composer install --no-dev --optimize-autoloader --no-interaction
rm -rf var/cache/*
sudo systemctl reload php8.3-fpm
curl -sI https://partizap.ru/api/health # должен быть 200Сброс кэша после деплоя
deploy.sh сбрасывает DI container (var/cache/), Doctrine metadata (Redis) и proxies автоматически. Дополнительные действия нужны только при изменении справочных данных:
bash
# Сброс кэша справочников (категории, авто, гео) — Redis DB 1
redis-cli -n 1 KEYS "prod:*" | xargs -r redis-cli -n 1 DEL # production
redis-cli -n 1 KEYS "dev:*" | xargs -r redis-cli -n 1 DEL # developmentОткат
Скрипт делает git reset --hard origin/{branch} — откат = force-push предыдущего коммита в main/develop и повторный запуск деплоя. Миграции с down() можно откатить вручную:
bash
php bin/console migrations:migrate prev --no-interactionПермишены var/
var/ должен быть настроен один раз per environment. Deploy-скрипт работает от serpens, PHP-FPM от www-data:
bash
sudo chown -R serpens:www-data /var/www/partizap/{env}/var
sudo chmod -R u+rwX,g+rwX /var/www/partizap/{env}/var
sudo find /var/www/partizap/{env}/var -type d -exec chmod g+s {} \;g+s (setgid) на директориях гарантирует, что файлы созданные www-data наследуют группу www-data и доступны serpens через group.
SSR: пустые данные на главной странице
Симптомы
- Категории и объявления на главной пусты при холодной загрузке (F5, очистка кэша)
- При клиентской навигации (SPA-переход) данные появляются
- В Network-табе нет запросов к
/api/store/categoriesи/api/store/products—useAsyncDataне перезапрашивает на клиенте, т.к. считает данные уже hydrated
Причина
Nitro devProxy не перехватывает внутренние $fetch-вызовы во время SSR. Когда Nuxt рендерит страницу на сервере, $fetch('/api/...') делает internal call к Nitro H3, а не HTTP-запрос через proxy. Nitro не находит handler → 404.
На клиенте запрос идёт через браузер → Nginx → API, поэтому работает.
Диагностика
bash
# Проверить SSR-ответ — есть ли данные в HTML:
curl -s http://localhost:3000/ | grep -oE 'home-category-[0-9]+'
# Если пусто — SSR не получает данные
# Проверить Nuxt payload на наличие NuxtError:
curl -s http://localhost:3000/ | grep -oE 'NuxtError'
# Если есть — SSR-запросы к API падаютРешение
Для локальной разработки обязательно задать NUXT_API_BASE_SERVER в .env — абсолютный URL бэкенда для SSR:
dotenv
# .env (local dev)
NUXT_API_BASE_SERVER=https://dev.partizap.ru/api
NUXT_DEV_BASIC_AUTH=Basic <credentials>На dev/prod серверах эти переменные задаются в docker-compose.*.yml → environment.
Как это работает
# Клиент (браузер): /api → Nginx devProxy → API ✓
# SSR (Nitro $fetch): /api → internal H3 call → нет handler → 404 ✗
# SSR с NUXT_API_BASE_SERVER: https://dev.partizap.ru/api → прямой HTTP → API ✓Логика выбора baseURL (shared/api/client.ts):
typescript
const baseURL = import.meta.server
? (config.apiBaseServer || config.public.apiBase) // SSR: абсолютный URL
: config.public.apiBase // Client: /api (через proxy)