Some checks failed
test / test (push) Failing after 24s
Реализация целевого дизайна (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/
137 lines
10 KiB
Markdown
137 lines
10 KiB
Markdown
# Кадр — локальный 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.5–2 раза.
|
||
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.
|