Files
SnapOtter/apps/docs/uk/guide/architecture.md
T
SnapOtterandGitHub d10d0f544f fix: release QA hardening across processing, media, security, and CI gates (#649)
A release-readiness QA pass over the whole product. The commits split into
defects a user would hit and gates that were reporting green while measuring
nothing.

## Fixes that change behaviour

Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so
request.ip came from a client-set header and a forged X-Forwarded-For got past
the login limiter. The default is now a private-network trust list.

A transient Postgres outage stranded in-flight jobs, leaving finished output on
disk with no row pointing at it. A reconciler now resolves those rows and adopts
the bytes rather than dropping the work.

A Redis connection that moved to a new address wedged every read-blocked
consumer, so completions stopped signalling while health still answered 200.
Socket timeouts plus subscriber pings recover it.

Installing more than one AI bundle left the shared venv multi-versioned and
silently broke three tools. The installer now reconciles distributions to one
version each.

Converting an image to JXL at quality 1 through 4 returned a 500, because
libjxl 0.7 rejects the distance those values compute. The quality is floored at
what the encoder honours. A missing ffmpeg was also reported to the user as a
corrupt upload; it now says the engine is unavailable.

RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at
0.22.2, and the release scan was split so it can fail on an unfixed critical
instead of hiding it behind ignore-unfixed.

## Gates that could not fail

Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs
build; coverage discarded its whole report on any failing test; the lint gate
skipped root tests, scripts, and two workspaces; and several generated matrices
counted a host missing ffmpeg as a passing tool. Each now measures what it
claims.

Full evidence and the outstanding release items are tracked locally and are not
part of this branch.
2026-07-27 15:37:30 +08:00

15 KiB
Raw Blame History

description, i18n_source_hash, i18n_provenance, i18n_output_hash, i18n_hash_version
description i18n_source_hash i18n_provenance i18n_output_hash i18n_hash_version
Структура монорепозиторію, архітектура застосунків і пакетів, життєвий цикл запиту та споживання ресурсів SnapOtter. 50e076925c4b human 4490df3a46a7 2

Архітектура

SnapOtter є монорепозиторієм, що керується робочими просторами pnpm та Turborepo. Він розгортається як стек Docker Compose з 3 контейнерів: образ застосунку SnapOtter, PostgreSQL 17 і Redis 8.

Структура проєкту

snapotter/
├── apps/
│   ├── api/          # Fastify backend
│   ├── web/          # React + Vite frontend
│   └── docs/         # This VitePress site
├── packages/
│   ├── image-engine/ # Sharp-based image operations
│   ├── media-engine/ # FFmpeg spawn + progress parsing
│   ├── doc-engine/   # qpdf, LibreOffice, ghostscript wrappers
│   ├── ai/           # Python AI model bridge
│   └── shared/       # Types, constants, i18n
└── docker/           # Dockerfile and Compose config

Пакети

@snapotter/image-engine

Основна бібліотека обробки зображень, побудована на Sharp. Вона обробляє всі не-AI операції: зміну розміру, обрізку, обертання, віддзеркалення, конвертацію, стиснення, видалення метаданих та коригування кольорів (яскравість, контраст, насиченість, відтінки сірого, сепія, інверсія, канали кольору).

Цей пакет не має мережевих залежностей і працює повністю в процесі.

@snapotter/ai

Рівень мосту, який викликає нативну та Python ML середовища виконання. Більшість інструментів Python використовують постійний dispatcher, який попередньо імпортує важкі бібліотеки (PIL, NumPy, MediaPipe, rembg), тому наступні виклики пропускають накладні витрати на імпорт. OCR ізольовано від цього змінного спільного середовища: fast викликає рідний Tesseract, тоді як balanced і best використовують виділений постійний JSONL dispatcher, закріплений на активному незмінному RapidOCR/ONNX покоління. Кожен запит містить generation lease. Активація спочатку запускає smoke test на кандидаті, а потім атомарно перемикається на його dispatcher. Попередній dispatcher зливається перед тим, як його генерацію буде зібрано сміттям.

Моделі не завантажуються заздалегідь. Скрипт кожного інструмента завантажує ваги своєї моделі з диска під час запиту і відкидає їх після завершення запиту. Дивіться Споживання ресурсів для повного профілю пам'яті.

Підтримувані операції: видалення фону (rembg/BiRefNet), масштабування (RealESRGAN), розмиття обличчя (MediaPipe), покращення обличчя (GFPGAN/CodeFormer), стирання об’єктів (LaMa ONNX), OCR (Tesseract і RapidOCR з моделями PP-OCR ONNX), розфарбовування (DDColor), видалення шуму, видалення ефекту червоних очей, відновлення фотографій, створення фотографій на паспорт, фіксація прозорості (BiRefNet HR-matting) і зміна розміру з урахуванням вмісту (двійковий файл Go caire).

Сценарії Python знаходяться в packages/ai/python/. Великі додаткові пакети моделей встановлюються на вимогу в постійний том /data/ai. Точний OCR використовує підписані, специфічні для платформи артефакти; вбудований рівень Tesseract не вимагає завантаження пакета моделей.

@snapotter/shared

Спільні типи TypeScript, константи (як-от APP_VERSION та визначення інструментів) і рядки перекладів i18n, які використовуються і фронтендом, і бекендом.

Застосунки

API (apps/api)

Сервер Fastify v5, що надає 243 маршрут інструментів у п'яти модальностях (image, video, audio, PDF, file) і обробляє:

  • Завантаження файлів, керування тимчасовим робочим простором та постійне зберігання файлів
  • Бібліотеку файлів користувача (таблиця user_files): за замовчуванням збережене редагування зберігається як незалежний новий файл, або як пов'язана з батьківською версія, коли ви перезаписуєте оригінал. Вона фіксує, які інструменти було застосовано (toolChain), і отримує автоматично згенеровану мініатюру для сторінки Files
  • Виконання інструментів (маршрутизує кожен запит інструмента до рушія зображень або мосту AI)
  • Оркестрацію конвеєрів (послідовне поєднання кількох інструментів)
  • Пакетну обробку з контролем паралельності через черги завдань BullMQ (пули: image, media, ai, docs, system)
  • Автентифікацію користувачів, RBAC (ролі admin/user з повним набором дозволів), керування ключами API та обмеження частоти запитів
  • Керування командами - CRUD лише для адміністраторів; користувачі призначаються до команди через поле team у своєму профілі
  • Налаштування середовища виконання - сховище «ключ-значення» в таблиці settings, яке керує disabledTools, enableExperimentalTools, loginAttemptLimit та іншими операційними важелями без повторного розгортання
  • Власний брендинг та налаштування середовища виконання через параметри, що зберігаються в базі даних
  • Документацію Scalar/OpenAPI за адресою /api/docs
  • Обслуговування зібраного фронтенду як SPA у продакшні

Ключові залежності: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod для валідації.

Сервер обробляє коректне завершення роботи за SIGTERM/SIGINT: він осушує HTTP-з'єднання, зупиняє воркери BullMQ, вимикає диспетчер Python і закриває з'єднання з базою даних.

Web (apps/web)

Односторінковий застосунок на React 19, зібраний за допомогою Vite. Використовує Zustand для керування станом, Tailwind CSS v4 для стилізації та Lucide для іконок. Взаємодіє з API через REST та SSE (для відстеження прогресу).

Сторінки включають робочий простір інструментів, сторінку Files для керування постійними завантаженнями та результатами, конструктор автоматизації/конвеєрів та панель адміністративних налаштувань.

Зібраний фронтенд обслуговується бекендом Fastify у продакшні, тож окремого вебсервера в контейнері Docker немає.

Docs (apps/docs)

Цей сайт VitePress. Розгортається на Cloudflare Pages автоматично під час відправлення в main.

Як проходить запит

  1. Користувач обирає інструмент у вебінтерфейсі та завантажує файл.
  2. Фронтенд надсилає multipart POST на /api/v1/tools/:section/:toolId з файлом і налаштуваннями.
  3. Маршрут API валідує вхідні дані за допомогою Zod, а потім розподіляє обробку.
  4. Для стандартних інструментів завдання ставиться в чергу до відповідного пулу BullMQ (image, media або docs залежно від модальності). Воркер BullMQ у процесі автоматично орієнтує зображення на основі метаданих EXIF, виконує функцію обробки інструмента й повертає результат.
  5. Для більшості інструментів ШІ міст TypeScript надсилає запит до постійного Python dispatcher. Натомість швидкий OCR викликає Tesseract, а точний OCR запускає закріплений виконуваний файл із активного незмінного покоління OCR. Запитуваний рівень OCR фіксується на вході та ніколи не мовчки змінюється під час виконання.
  6. Прогрес завдання зберігається в таблиці jobs у PostgreSQL, тож стан переживає перезапуски контейнера. Оновлення в реальному часі доставляються через SSE за адресою /api/v1/jobs/:jobId/progress.
  7. API повертає jobId та downloadUrl. Користувач завантажує оброблений файл з /api/v1/download/:jobId/:filename.

Для конвеєрів API подає вихід кожного кроку як вхід для наступного, виконуючи їх послідовно.

Для пакетної обробки API використовує потоки BullMQ з дочірніми завданнями для кожного кроку й повертає ZIP-файл з усіма обробленими файлами.

Споживання ресурсів

SnapOtter розроблений для низького споживання пам'яті в стані спокою. Нічого не завантажується заздалегідь і не тримається «розігрітим» під час запуску.

У стані спокою

Процес Node.js/Fastify, PostgreSQL і Redis працюють. Типове споживання RAM у стані спокою становить ~200-300 МБ для всіх трьох контейнерів (процес Node.js, Postgres і Redis). Жодного процесу Python, жодних ваг моделей у пам'яті.

Що запускається і коли

Компонент Запускається коли Пам'ять під час роботи
Сервер Fastify + Postgres + Redis Запуск контейнера ~200-300 МБ загалом
Воркери BullMQ Запуск контейнера (у процесі) Один воркер на пул (image, media, ai, docs, system)
Диспетчер Python Перший запит AI-інструмента Інтерпретатор Python + заздалегідь імпортовані бібліотеки (PIL, NumPy, MediaPipe, rembg) - без ваг моделей
Ваги моделей AI Під час запиту конкретного інструмента Завантажуються з диска, звільняються після завершення запиту

Завантаження моделей

Усі файли ваг моделей (загалом кілька ГБ) весь час лежать на диску в /opt/models/. Скрипт кожного AI-інструмента завантажує в пам'ять лише свою модель (моделі) на час запиту, а потім звільняє їх. Деякі скрипти явно викликають del model і torch.cuda.empty_cache() після виведення, щоб пам'ять повернулася негайно.

Кешу моделей між запитами немає. Виконання того самого AI-інструмента підряд щоразу перезавантажує модель. Це утримує споживання пам'яті в стані спокою близьким до нуля ціною затримки на завантаження моделі під час кожного AI-запиту.

Холодний старт першого AI-запиту

Диспетчер Python не працює під час запуску контейнера. Перший AI-запит запускає паралельно дві речі: диспетчер починає розігріватися у фоні, а сам запит повертається до одноразового породження підпроцесу Python. Щойно диспетчер сигналізує про готовність, усі наступні AI-запити використовують його безпосередньо й пропускають витрати на породження підпроцесу.