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

Кадр — локальный 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)

Запуск и разработка

Локальный запуск

# Установка зависимостей
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:

{"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

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.
Description
No description provided
Readme 476 KiB
Languages
JavaScript 85.9%
CSS 9.3%
HTML 4.2%
Dockerfile 0.6%