Coder 6d5b8d5f05
All checks were successful
test / test (push) Successful in 11s
fix: .gitignore игнорировал src/core/history (паттерн history/ без якоря) — CI падал с MODULE_NOT_FOUND
Паттерн 'history/' в .gitignore матчил любой каталог с таким именем, включая служебный модуль src/core/history/. Привязаны /history/ и /data/ к корню репозитория; модуль истории добавлен в индекс.
2026-08-27 13:04:46 +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%