6d5b8d5f053a1217559574d6ed64169cb77eff1f
All checks were successful
test / test (push) Successful in 11s
Паттерн 'history/' в .gitignore матчил любой каталог с таким именем, включая служебный модуль src/core/history/. Привязаны /history/ и /data/ к корню репозитория; модуль истории добавлен в индекс.
Кадр — локальный AI-фоторедактор (v2)
Веб-приложение редактирует фотографии по текстовому промпту (через локальный Codex CLI) и увеличивает разрешение через домашний сервер ComfyUI.
Поддерживаются фотографии JPEG, PNG, WebP и HEIC/HEIF. Файлы HEIC/HEIF
преобразуются на сервере в JPEG с помощью heic-convert до передачи в ComfyUI
или Codex.
Архитектура v2
В версии v2 сервис переработан в модульный монолит (src/):
- Устойчивость к перезапуску:
- Персистентный append-only JSONL-журнал заданий (
data/jobs.jsonl). При рестарте сервера незавершённые задания Codex получают статусinterruptedс возможностью повтора в один клик, а задания ComfyUI автоматически возвращаются в очередь. - Персистентное хранилище сессий (
data/sessions.json, хранятся sha256-хеши токенов). Владелец остаётся авторизован после обновления или перезапуска контейнера. - Атомарная запись манифеста истории (
history.json.tmp+fsync+rename) защищает от повреждения данных при внезапном выключении.
- Персистентный append-only JSONL-журнал заданий (
- Очередь заданий (FIFO) и группы конкурентности:
- Вместо блокировки single-flight (429) запросы на редактирование и апскейл ставятся в очередь.
- Лимит параллельности: Codex (edit, suggest) = 1 поток; ComfyUI (upscale) = 2 потока.
- Лимит очереди: до 5 заданий на группу (защита от накликивания).
- Безопасность:
- Пароль защищён scrypt-хешированием (
crypto.scrypt) с постоянным временем сверки (timingSafeEqual). - Ограничение попыток входа (Rate Limiting): 5 попыток за 15 минут на IP.
- Проверка Origin на state-changing запросах (CSRF).
- Проверка сигнатур файлов (magic bytes) для защиты от поддельных MIME-типов.
- Пароль защищён scrypt-хешированием (
- Ускорение редактирования (Downscale):
- Автоматический даунскейл
sharpдоEDIT_MAX_DIMENSION(по умолчанию 1024 px) перед передачей в Codex с пропорциональным масштабированием области референса (scaleRegion). Сокращает время обработки больших фото в 1.5–2 раза.
- Автоматический даунскейл
- Мгновенные обновления статуса (SSE):
- Server-Sent Events (
GET /api/events) передают смену статуса очереди и прогресс мгновенно. - Автоматический фолбэк на опрос (
/api/jobs) при сбоях сети или неподдерживаемых прокси.
- Server-Sent Events (
- Клиентская часть на 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
- Workflow
.gitea/workflows/test.ymlавтоматически запускает автотесты при push вmain. - Workflow
.gitea/workflows/build.ymlсобирает образ на базеnode:22-bookworm-slimи пушит в Gitea Registry. - Workflow
.gitea/workflows/deploy.ymlобновляет стек в Portainer с томами:kadr-data:/app/data— данные v2 (сессии, журнал, история);kadr-history:/app/history— старый том истории (автоматически мигрируется при первом старте v2);/root/.codex:/root/.codex— конфигурация и авторизация Codex.
Description
Languages
JavaScript
85.9%
CSS
9.3%
HTML
4.2%
Dockerfile
0.6%