Files
photo-editor/README.md
Coder 401969cfdb
Some checks failed
test / test (push) Failing after 24s
v2: модульный монолит — очередь, персистентность, безопасность, SSE, тесты
Реализация целевого дизайна (docs/architecture/target-design.md):
- src/: модульная структура (config, logger, errors, http, core: auth/history/jobs/engines/comfy/codex/images)
- Очередь заданий: FIFO, кониурентность codex=1 / comfy-upscale=2, MAX_QUEUED_JOBS=5, JSONL-журнал и восстановление после рестарта (running → interrupted, upscale requeue)
- Персистентные сессии (sha256-хеши токенов), scrypt-хеш пароля, rate-limit входа, Origin-проверка, magic-byte валидация аплоадов
- Атомарная запись манифеста истории, каскадное удаление, URL с фактическим расширением + 302-алиас /image.jpg, миграция history/ → data/history
- Даунскейл sharp до 1024px перед Codex + масштабирование области референса
- SSE /api/events с фолбэком на polling, фронтенд переведён на ES-модули (public/js/)
- Тесты node:test + supertest (27), CI workflow test.yml, Docker/деплой: том kadr-data
- Документация: README, docs/architecture/
2026-08-27 12:52:27 +07:00

137 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Кадр — локальный AI-фоторедактор (v2)
Веб-приложение редактирует фотографии по текстовому промпту (через локальный
Codex CLI) и увеличивает разрешение через домашний сервер ComfyUI.
Поддерживаются фотографии JPEG, PNG, WebP и HEIC/HEIF. Файлы HEIC/HEIF
преобразуются на сервере в JPEG с помощью `heic-convert` до передачи в ComfyUI
или Codex.
---
## Архитектура v2
В версии v2 сервис переработан в **модульный монолит** (`src/`):
1. **Устойчивость к перезапуску**:
- Персистентный append-only JSONL-журнал заданий (`data/jobs.jsonl`). При рестарте сервера незавершённые задания Codex получают статус `interrupted` с возможностью повтора в один клик, а задания ComfyUI автоматически возвращаются в очередь.
- Персистентное хранилище сессий (`data/sessions.json`, хранятся sha256-хеши токенов). Владелец остаётся авторизован после обновления или перезапуска контейнера.
- Атомарная запись манифеста истории (`history.json.tmp` + `fsync` + `rename`) защищает от повреждения данных при внезапном выключении.
2. **Очередь заданий (FIFO) и группы конкурентности**:
- Вместо блокировки single-flight (429) запросы на редактирование и апскейл ставятся в очередь.
- Лимит параллельности: Codex (edit, suggest) = 1 поток; ComfyUI (upscale) = 2 потока.
- Лимит очереди: до 5 заданий на группу (защита от накликивания).
3. **Безопасность**:
- Пароль защищён scrypt-хешированием (`crypto.scrypt`) с постоянным временем сверки (`timingSafeEqual`).
- Ограничение попыток входа (Rate Limiting): 5 попыток за 15 минут на IP.
- Проверка Origin на state-changing запросах (CSRF).
- Проверка сигнатур файлов (magic bytes) для защиты от поддельных MIME-типов.
4. **Ускорение редактирования (Downscale)**:
- Автоматический даунскейл `sharp` до `EDIT_MAX_DIMENSION` (по умолчанию 1024 px) перед передачей в Codex с пропорциональным масштабированием области референса (`scaleRegion`). Сокращает время обработки больших фото в 1.52 раза.
5. **Мгновенные обновления статуса (SSE)**:
- Server-Sent Events (`GET /api/events`) передают смену статуса очереди и прогресс мгновенно.
- Автоматический фолбэк на опрос (`/api/jobs`) при сбоях сети или неподдерживаемых прокси.
6. **Клиентская часть на Vanilla ES-модулях**:
- Модули в `public/js/` загружаются напрямую браузером (`<script type="module">`) без этапа сборки.
---
## Как это работает
- **История версий**: каждая загрузка и результат (редактирование или апскейл) сохраняются в `data/history/` как отдельная версия. Доступно интерактивное дерево версий с ветвлением от исходника, каскадным удалением и свёрткой групп. Хранятся последние 50 версий (настраивается через `HISTORY_LIMIT`).
- **Редактирование**: сервер передаёт выбранную версию и промпт агенту Codex CLI (`codex exec`). Агент редактирует изображение и сохраняет результат в JPEG.
- **Увеличение разрешения**: 4× upscale через ComfyUI моделью `RealESRGAN_x4plus.safetensors` (или `4x_UltraSharp`).
- **Редактирование с референсом и областью**: можно задать референс — файл или версию из истории. На превью референса выделяется прямоугольник области; координаты передаются в промпт Codex.
- **Подсказки промптов**: мгновенные варианты из недавней истории и генерация новых вариантов через Codex.
---
## Переменные окружения
| Переменная | Тип | Дефолт | Описание |
|---|---|---|---|
| `PORT` | number | `3000` | Порт HTTP-сервера (кандидаты по умолчанию: 3000, 8080, 8090) |
| `COMFY_URL` | url | `http://192.168.31.240:8188` | Адрес домашнего сервера ComfyUI |
| `APP_USER` | string | `admin` | Логин владельца |
| `APP_PASSWORD` | string | *автогенерация* | Пароль владельца (если не задан — печатается в лог при старте) |
| `LOG_LEVEL` | string | `info` | Уровень логов (`debug`, `info`, `warn`, `error`) |
| `DATA_DIR` | path | `./data` | Каталог данных (сессии, журнал заданий, история) |
| `HISTORY_LIMIT` | number | `50` | Максимальное количество версий в истории |
| `MAX_UPLOAD_BYTES` | number | `26214400` (25 МБ) | Максимальный размер загружаемого файла |
| `EDIT_MAX_DIMENSION` | number | `1024` | Максимальный размер стороны при даунскейле перед Codex (0 — отключить) |
| `UPSCALE_MAX_DIMENSION`| number | `2048` | Предел входного разрешения перед 4× апскейлом |
| `CODEX_CLI_PATH` | string | `codex` / авто-поиск | Путь к бинарнику Codex CLI |
| `CODEX_EDIT_TIMEOUT_MS`| number | `420000` (7 мин) | Таймаут выполнения операции в Codex |
| `JOB_TIMEOUT_MS` | number | `300000` (5 мин) | Таймаут задачи в ComfyUI |
| `REQUEST_TIMEOUT_MS` | number | `15000` (15 с) | Таймаут сетевых HTTP-запросов |
| `SESSION_TTL_MS` | number | `604800000` (7 дней) | Срок жизни cookie-сессии (скользящий) |
| `SESSION_PERSIST` | bool | `true` | Сохранение сессий между перезапусками сервера |
| `CODEX_CONCURRENCY` | number | `1` | Число параллельных потоков Codex |
| `UPSCALE_CONCURRENCY` | number | `2` | Число параллельных потоков ComfyUI |
| `MAX_QUEUED_JOBS` | number | `5` | Максимальный размер очереди на группу |
| `SSE_ENABLED` | bool | `true` | Включение Server-Sent Events |
| `TRUST_PROXY` | bool | `false` | Доверие заголовкам прокси (`X-Forwarded-For` для rate-limiting) |
---
## Запуск и разработка
### Локальный запуск
```powershell
# Установка зависимостей
npm install
# Запуск тестов
npm test
# Запуск приложения
npm start
# Запуск в режиме разработки с автоперезагрузкой
npm run start:dev
```
При первом запуске без `APP_PASSWORD` в консоль выводится баннер со случайным сгенерированным паролем.
---
## Авторизация и безопасность
Редактор защищён cookie-сессией `kadr_session` (`HttpOnly`, `SameSite=Lax`):
- Публичные маршруты: `GET /api/health`, `POST /api/login`, `GET /api/me`, `POST /api/logout`.
- Все остальные маршруты `/api/*` требуют авторизации (`401 Unauthorized`).
- Защита от перебора: после 5 неудачных попыток входа IP блокируется на 15 минут.
- Пароль проверяется функцией scrypt (`crypto.scrypt`) с защитой от атак по времени (`timingSafeEqual`).
---
## Логирование и диагностика
Все логи формируются в структурированном формате JSON-lines с обязательным полем `ts`, `level`, `msg` и `requestId`:
```json
{"ts":"2026-08-27T12:00:00.000Z","level":"info","msg":"HTTP запрос завершён","requestId":"req_a1b2c3d4","method":"GET","url":"/api/history","status":200,"durationMs":3}
```
Пароли, сессионные токены и персональные секреты автоматически маскируются (`[REDACTED]`).
---
## Docker и развёртывание
### Локальный Docker
```powershell
docker compose up -d --build
```
### Развёртывание в Portainer через Gitea Actions
1. Workflow `.gitea/workflows/test.yml` автоматически запускает автотесты при push в `main`.
2. Workflow `.gitea/workflows/build.yml` собирает образ на базе `node:22-bookworm-slim` и пушит в Gitea Registry.
3. Workflow `.gitea/workflows/deploy.yml` обновляет стек в Portainer с томами:
- `kadr-data:/app/data` — данные v2 (сессии, журнал, история);
- `kadr-history:/app/history` — старый том истории (автоматически мигрируется при первом старте v2);
- `/root/.codex:/root/.codex` — конфигурация и авторизация Codex.