AI Photo Studio — Deployment Runbook

Гайд по подъёму ai-photo-studio (gensvc) на test VPS. Это сторонний продукт (не ERP-микросервис), интегрированный в админку франшизы как iframe-modal на ProductView/EditPage. См. ADR-021.

Архитектура

ai-photostudio-test.nirbi.ru     → ai-photostudio-frontend (nginx static, React/Vite)
ai-photostudio-api-test.nirbi.ru → ai-photostudio-api (Go, gensvc :8080)
                                       ├── PostgreSQL (общий контейнер erp-postgres, БД gensvc_db)
                                       ├── Redis (общий erp-redis, DB index 2)
                                       └── MinIO (общий erp-minio, buckets gensvc-{inputs,outputs,presets})

Iframe из https://erp-test.nirbi.ru/admin/catalog/products/{id} открывается в cross-origin режиме. Token приходит query-параметром ?erp_token=, parent-origin для postMessage — ?parent_origin=.

Pre-requirements (разовая настройка)

1. DNS A-records

В DNS-зоне nirbi.ru добавить:

ai-photostudio-test.nirbi.ru.       A   185.152.93.77
ai-photostudio-api-test.nirbi.ru.   A   185.152.93.77

Проверка: dig +short ai-photostudio-test.nirbi.ru → должен вернуть IP VPS.

DNS обязателен для certbot

Без A-records Let’s Encrypt не пройдёт http-01 challenge.

2. Клонирование репозиториев на VPS

VPS использует HTTPS-clone с Personal Access Token (как для других репо). Токен живёт в URL других origin’ов — переиспользуется.

ssh root@185.152.93.77
cd /root/erp
 
# Принять github.com host key (один раз)
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
 
# Clone (PAT см. в любом другом erp-* репо: git remote -v)
git clone https://<PAT>@github.com/nearbyErp/ai-photo-studio-frontend.git
git clone https://<PAT>@github.com/nearbyErp/ai-photo-studio-backend.git

3. Известные баги в Dockerfile коллеги (хотфиксы на VPS)

Пока коллега не сделает PR, на VPS нужны локальные правки:

ФайлПроблемаHotfix
ai-photo-studio-backend/DockerfileFROM golang:1.22-alpine но go.mod требует Go 1.26sed -i 's|golang:1.22-alpine|golang:1.26-alpine|' ai-photo-studio-backend/Dockerfile
ai-photo-studio-frontend/DockerfileFROM node:20-alpine + pnpm latest требует Node 22+sed -i 's|node:20-alpine|node:22-alpine|' ai-photo-studio-frontend/Dockerfile
ai-photo-studio-frontend/Dockerfilecorepack prepare pnpm@latest ставит pnpm 11, который ломается на [ERR_PNPM_IGNORED_BUILDS] для esbuildsed -i 's|pnpm@latest|pnpm@9.15.0|' ai-photo-studio-frontend/Dockerfile
ai-photo-studio-frontend/pnpm-workspace.yamlСодержит только onlyBuiltDependencies без packages — pnpm 9.15 фейлится с packages field missing or emptyДобавить packages:\n - .\n в начало файла

Передать коллеге

Эти 4 хотфикса — временные. Создать PR в фотостудию с правильными версиями, чтобы убрать sed-патчи на VPS.

4. env-файл

/root/erp/erp-infrastructure/envs/ai-photostudio-api.env — копируется из .example с подстановкой реальных секретов.

cd /root/erp/erp-infrastructure
cp envs/ai-photostudio-api.env.example envs/ai-photostudio-api.env
 
# Подставить секреты из .env (POSTGRES_PASSWORD, REDIS_PASSWORD, MINIO_*)
# и значение JWT_LOCAL_SECRET = JWT_SECRET из envs/auth-service.env
sed -i \
    -e "s|\${POSTGRES_PASSWORD}|<значение из .env>|g" \
    -e "s|\${REDIS_PASSWORD}|<значение из .env>|g" \
    -e "s|\${MINIO_ROOT_USER}|<значение>|g" \
    -e "s|\${MINIO_ROOT_PASSWORD}|<значение>|g" \
    -e "s|__SAME_AS_auth-service.env__app.jwt.secret__|<значение JWT_SECRET из auth-service.env>|" \
    envs/ai-photostudio-api.env
 
# AI_API_KEY — реальный ключ Polza от бизнеса
nano envs/ai-photostudio-api.env  # → AI_API_KEY=sk-...

JWT_LOCAL_SECRET coordination

Тот же JWT_SECRET нужно передать коллеге фотостудии — gensvc валидирует HS256 токены этим секретом. Безопасный канал передачи (не публикуем).

5. Создание БД (если postgres-volume уже инициализирован)

init-databases.sql срабатывает только при первом старте postgres. Для существующего volume — создать БД руками:

docker exec erp-postgres psql -U erp -d postgres -c 'CREATE DATABASE gensvc_db;'
docker exec erp-postgres psql -U erp -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE gensvc_db TO erp;'

6. TLS-сертификаты (после DNS готов)

cd /root/erp/erp-infrastructure
docker compose run --rm certbot certonly \
    --webroot -w /var/www/certbot \
    -d ai-photostudio-test.nirbi.ru \
    -d ai-photostudio-api-test.nirbi.ru \
    --email <ваш email> --agree-tos --no-eff-email

После успеха — снять .disabled с nginx-конфига:

mv nginx/conf.d/ai-photostudio.conf.disabled nginx/conf.d/ai-photostudio.conf
docker exec erp-nginx nginx -s reload

7. Pre-seed 288 webp пресетов

Без референс-картинок генерация падает («preset reference image not found»). Залить webp-файлы в bucket gensvc-presets:

# Вариант: через seed-presets binary (требует CSV + dir с webp)
docker compose run --rm ai-photostudio-api /app/seed-presets \
    --presets-dir=/path/to/presets \
    --csv=/path/to/preset-names-ru.csv

Source присетов — у бизнеса/маркетинга

288 reference-фото — отдельная разовая работа. Пока её нет, AI-генерация будет валиться даже при работающем API ключе.

Регулярный деплой

Через /deploy-all:

docker compose build --no-cache ai-photostudio-api ai-photostudio-frontend
docker compose up -d --force-recreate ai-photostudio-api ai-photostudio-frontend

Health: оба контейнера healthy через 30 сек.

Verification

Smoke-test изнутри сети (без TLS)

# Backend healthz
docker exec ai-photostudio-api wget -qO- http://localhost:8080/healthz
# → 200 OK
 
# Frontend index
docker exec ai-photostudio-frontend wget -qO- http://localhost:80/index.html | head -10

E2E с TLS

# Frontend SPA
curl -sI https://ai-photostudio-test.nirbi.ru/ | head -3
 
# API healthz
curl -sI https://ai-photostudio-api-test.nirbi.ru/healthz | head -3

Из админки (полный e2e)

  1. Логин в https://erp-test.nirbi.ru/admin/ под аккаунтом с permission gensvc.photo.create (системная роль «Администратор» имеет автоматически)
  2. Открыть товар: /admin/catalog/products/{any}
  3. Над кнопкой «Заменить» — большая красная «Улучшить с помощью AI»
  4. Click → modal-overlay 1100×800 с iframe фотостудии (без header)
  5. Загрузить тест-фото → выбрать пресет → submit
  6. Дождаться succeeded (~30с)
  7. В ResultViewer кнопки «Применить к товару» (красная) + «Отмена»
  8. Click «Применить» → блок с лоадером «Применяем фото к товару» → модалка закрывается → новое фото товара видно в карточке
  9. POS-desktop этой же ТТ должен моментально подхватить новое фото через SSE (без 60с polling)

Troubleshooting

database "gensvc_db" does not exist

Postgres-volume инициализирован раньше, чем добавлен init-script. Создать БД руками (см. шаг 5).

using local HS256 auth — NOT for production

Это expected на test VPS — AUTH_MODE=local по решению из ADR-021. На прод поменять на JWKS когда auth-service отдаст endpoint.

permission denied: gensvc.photo.create

Юзер не имеет permission. Проверить:

docker exec erp-postgres psql -U erp -d user_db -c \
    "SELECT r.name FROM roles r JOIN role_permissions rp ON rp.role_id=r.id WHERE rp.permission_key='gensvc.photo.create';"

Должно показать «Администратор» как минимум. Если нет — миграция 030 не применилась.

400 preset reference image not found

В gensvc-presets bucket не залиты webp. Pre-seed (шаг 7).

nginx после reload падает

TLS-серты для ai-photostudio-доменов отсутствуют. Пока certbot не пройдёт — держать конфиг как .disabled.

postMessage не приходит в admin

Проверить что origin совпадает с VITE_PHOTO_STUDIO_URL в admin frontend. Console будет логировать игнорируемые сообщения.

Ссылки