План фото-ИИ с восстановлением лиц разбит на этапы; этот коммит закрывает
раздел 0 — семь инвариантов, которые нельзя ломать дальше. Два из них были
нарушены в текущем коде и исправлены здесь.
I5 (мягкие ошибки не сжигают попытки). Раньше любой сбой photo-ai —
503, обрыв сети, таймаут — попадал в общий catch, инкрементил attempts и
через три попытки переводил задание в error. Теперь ошибки разделены:
5xx/429/425/408 и сетевая недоступность возвращают задание в pending без
инкремента attempts, с экспоненциальной паузой 10 с → 300 с; лимит мягких
повторов (по умолчанию 60) даёт одну честную ошибку с понятным текстом.
Таймаут AbortSignal.timeout — жёсткая ошибка с попытками, как и раньше.
Счётчик мягких повторов живёт в памяти процесса и в счётчиках воркера,
метаданные повтора — в audit_log.target (soft_attempt/soft_limit), без
новых колонок. Новый аудит-код photo.job.soft_retry и подпись в audit.js.
Переменные PHOTO_AI_SOFT_MAX_RETRIES, PHOTO_AI_SOFT_BACKOFF_MS,
PHOTO_AI_SOFT_BACKOFF_MAX_MS описаны в .env.example и отдаются в
worker.config в GET /api/photo-jobs/status.
I7 (никаких прямых fs.* по uploads/). runAiEnhance писал результат
fs.writeFileSync в uploads/ и только потом persist в S3; enhanceWithSharp
делал то же через sharp toFile. Оба теперь считают буфер и пишут его
через storage.put — драйвер выбирает сам, локальной копии не остаётся.
Из worker.js убраны require('fs'), require('path') и параметр uploadsDir.
I3 (photo-ai не обязателен). photo_ai_enabled вычислялся внутри
cacheWrap('public-settings'), поэтому после перезапуска с пустым
PHOTO_AI_URL кнопка «🤖 ИИ» оставалась видимой до истечения кэша (60 с),
хотя enhance-ai уже отдавал 503. Флаг вынесен из кэша: он выводится из
PHOTO_AI_URL в памяти процесса и всегда актуален.
Проверено на стенде (журнал — в TODO_PHOTO_FACE_AI.md, раздел 0):
- I1: эталон /enhance снят на 5 фото (3 реальных, 2 синтетических),
два независимых прогона и прогон после правок совпали байт-в-байт
(sha256), /health отдаёт ok. Скрипты и эталон — в backups/ (вне git)
- I2: задание с params IS NULL и action='ai' дошло до done при
attempts=0, результат отдан из S3 (200)
- I3: с пустым PHOTO_AI_URL photo_ai_enabled=false сразу после старта,
enhance-ai → 503, остальные маршруты API живы
- I4: nvidia-ctk и nvidia-container-runtime на хосте отсутствуют, runtime
только runc — фото-ИИ поднялся на CPU, /health не падает. Проверка
PHOTO_AI_DEVICE переносится на приёмку Stage 1 (переменной ещё нет)
- I6: db/ не тронут, состав колонок photo_jobs прежний
- I7: node --check для всех изменённых JS, комментариев в диффе нет,
весь SQL параметризован
api.smoketest.js: контракт фото-воркера — согласованность
photo_ai_enabled и ai_configured, ключи мягких повторов в worker.config,
503/404 для enhance-ai на несуществующей записи (тест не создаёт реальных
заданий). Вместе с планом и чек-листом этапов.
74 lines
5.2 KiB
Bash
74 lines
5.2 KiB
Bash
ADMIN_USERNAME=admin
|
||
ADMIN_PASSWORD=сложный-пароль-админки
|
||
DB_PASSWORD=случайная-длинная-строка
|
||
# Лимит размера загружаемого на восстановление бэкапа, МБ (по умолчанию 500)
|
||
BACKUP_UPLOAD_LIMIT_MB=500
|
||
|
||
# === Redis (кэш, rate limit, баны IP, pub/sub) ===
|
||
# Пароль Redis. Обязателен, если Redis включён в docker-compose.
|
||
REDIS_PASSWORD=замените-на-длинный-секрет
|
||
# Префикс ключей — позволяет держать несколько инстансов в одном Redis.
|
||
REDIS_PREFIX=whatido
|
||
# Лимит памяти Redis. При превышении вытесняются ключи с наименьшим TTL (allkeys-lru).
|
||
REDIS_MAXMEMORY=256mb
|
||
|
||
# Hugging Face token (нужен для закрытых/приватных репозиториев моделей)
|
||
HUGGINGFACE_TOKEN=
|
||
# Имя GGUF-файла модели для text-corrector (скачивается с Hugging Face, если отсутствует)
|
||
AI_MODEL=qwen2.5-1.5b-instruct-q4_k_m.gguf
|
||
# Репозиторий Hugging Face, откуда скачивается модель
|
||
AI_MODEL_REPO=Qwen/Qwen2.5-1.5B-Instruct-GGUF
|
||
# Промпт по умолчанию для автоисправления текстов (переопределяет встроенный, если в настройках не сохранён свой)
|
||
AI_PROMPT=Ты — редактор текстов. Исправь ТОЛЬКО грамматические, орфографические и пунктуационные ошибки в тексте. Приведи к правильному регистру буквы. НЕ меняй слова, структуру предложений, стиль или смысл текста. Верни ТОЛЬКО исправленный текст без пояснений.
|
||
# Таймаут запроса к AI (мс), по умолчанию 120000 (2 минуты)
|
||
AI_REQUEST_TIMEOUT_MS=120000
|
||
|
||
# === Cloudflare Tunnel (контейнер cloudflared) ===
|
||
# По умолчанию — Quick Tunnel на trycloudflare.com: случайный публичный URL,
|
||
# адрес печатается в логах: docker compose logs -f cloudflared.
|
||
# Чтобы привязать свой домен, замените command сервиса cloudflared
|
||
# в docker-compose.yml на: command: ["tunnel", "run", "--token", "${CLOUDFLARE_TUNNEL_TOKEN}"]
|
||
# (токен вида "eyJ..." создаётся в Cloudflare Zero Trust -> Networks -> Tunnels).
|
||
CLOUDFLARE_TUNNEL_TOKEN=
|
||
# Внутренний адрес приложения, на который указывает Quick Tunnel.
|
||
CLOUDFLARE_TUNNEL_URL=http://app:3003
|
||
# Максимальное ожидание WireGuard handshake перед fallback на прямой запуск
|
||
# туннеля без VPN (сек). Если VPN-провайдер не отвечает — сайт всё равно поднимется.
|
||
WG_HANDSHAKE_TIMEOUT=60
|
||
|
||
# ИИ-улучшение фото (Real-ESRGAN), пусто = контейнер photo-ai
|
||
PHOTO_AI_URL=
|
||
PHOTO_AI_MAX_PIXELS=4000000
|
||
# Сколько раз воркер повторит задание, если photo-ai недоступен (503/обрыв сети),
|
||
# не увеличивая attempts; после исчерпания — одна честная ошибка в задании.
|
||
PHOTO_AI_SOFT_MAX_RETRIES=60
|
||
# Пауза перед мягким повтором и её потолок (мс), задержка растёт вдвое до потолка.
|
||
PHOTO_AI_SOFT_BACKOFF_MS=10000
|
||
PHOTO_AI_SOFT_BACKOFF_MAX_MS=300000
|
||
|
||
# === Хранилище файлов (S3: SeaweedFS по умолчанию / MinIO) ===
|
||
# local — файлы в ./uploads (по умолчанию), s3 — объекты в бакете S3/MinIO.
|
||
STORAGE_DRIVER=local
|
||
# Читать локальную копию, если объект ещё не перенесён в S3 (1 — включено).
|
||
STORAGE_LOCAL_FALLBACK=1
|
||
# Оставлять локальную копию после выгрузки в S3 (1 — оставлять, 0 — удалять).
|
||
STORAGE_KEEP_LOCAL=0
|
||
# Сколько часов хранить локальный кэш оригиналов (для sharp/миниатюр), 0 — не чистить.
|
||
STORAGE_CACHE_MAX_AGE_HOURS=168
|
||
# Образ S3-сервиса. По умолчанию SeaweedFS (свободный S3-сервер).
|
||
# Для MinIO: S3_IMAGE=minio/minio:<тег> и запуск через docker-compose.minio.yml.
|
||
S3_IMAGE=chrislusf/seaweedfs:latest
|
||
S3_ENDPOINT=http://s3:9000
|
||
S3_REGION=us-east-1
|
||
S3_BUCKET=whatido
|
||
# Логин/пароль S3 (для SeaweedFS — AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY,
|
||
# для MinIO — MINIO_ROOT_USER/MINIO_ROOT_PASSWORD).
|
||
S3_ACCESS_KEY=whatido
|
||
S3_SECRET_KEY=замените-на-длинный-секрет
|
||
# Path-style адресация (1 — включено, нужно для MinIO и SeaweedFS).
|
||
S3_FORCE_PATH_STYLE=1
|
||
# Необязательный префикс ключей внутри бакета (например, prod).
|
||
S3_PREFIX=
|
||
# Показывать веб-консоль MinIO (on/off), только для docker-compose.minio.yml
|
||
MINIO_BROWSER=off
|