Files
SnapOtter/apps/docs/sv/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

9.3 KiB

description, i18n_source_hash, i18n_provenance, i18n_output_hash, i18n_hash_version
description i18n_source_hash i18n_provenance i18n_output_hash i18n_hash_version
Monorepo-struktur, app- och paketarkitektur, förfrågningslivscykel och resursavtryck för SnapOtter. 50e076925c4b human 4691aa895e15 2

Arkitektur

SnapOtter är ett monorepo som hanteras med pnpm workspaces och Turborepo. Det distribueras som en Docker Compose-stack med 3 containrar: SnapOtter-appavbildningen, PostgreSQL 17 och Redis 8.

Projektstruktur

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

Paket

@snapotter/image-engine

Kärnbiblioteket för bildbehandling byggt på Sharp. Det hanterar alla icke-AI-operationer: storleksändring, beskärning, rotation, spegling, konvertering, komprimering, borttagning av metadata och färgjusteringar (ljusstyrka, kontrast, mättnad, gråskala, sepia, invertering, färgkanaler).

Detta paket har inga nätverksberoenden och körs helt in-process.

@snapotter/ai

Ett brygglager som anropar native och Python ML körtider. De flesta Python-verktyg använder en beständig dispatcher som förimporterar tunga bibliotek (PIL, NumPy, MediaPipe, rembg) så att efterföljande anrop hoppar över importkostnader. OCR är isolerad från den föränderliga delade miljön: fast anropar inbyggd Tesseract, medan balanced och best använder en dedikerad beständig JSONL dispatcher som är fäst vid den aktiva oföränderliga RapidOCR/ONNX-generationen. Varje begäran innehåller en generation lease. Aktivering kör först en smoke test på en kandidat och växlar sedan atomärt till dess dispatcher. Den tidigare dispatcher dräneras innan den genereras sopsamlas.

Modeller är inte förinlästa. Varje verktygsskript laddar sina modellvikter från disk vid förfrågningstillfället och kasserar dem när förfrågan är klar. Se Resursavtryck för den fullständiga minnesprofilen.

Operationer som stöds: bakgrundsborttagning (rembg/BiRefNet), uppskalning (RealESRGAN), ansiktsoskärpa (MediaPipe), ansiktsförbättring (GFPGAN/CodeFormer), objektradering (LaMa ONNX), OCR (Tesseract och RapidOCR med PP-OCR ONNX-modeller), färgläggning (DDColor), bullerborttagning, borttagning av röda ögon, foto restaurering, generering av passfoto, transparensfixering (BiRefNet HR-matta), och innehållsmedveten storleksändring (Go caire binär).

Python-skript live i packages/ai/python/. Stora valfria modellpaket installeras på begäran i den ihållande /data/ai-volymen. Exakt OCR använder signerade, plattformsspecifika artefakter; den inbyggda Tesseract-nivån kräver ingen nedladdning av modellpaket.

@snapotter/shared

Delade TypeScript-typer, konstanter (som APP_VERSION och verktygsdefinitioner) och i18n-översättningssträngar som används av både frontend och backend.

Applikationer

API (apps/api)

En Fastify v5-server som exponerar 243 verktygsrutter över fem modaliteter (image, video, audio, PDF, file) och som hanterar:

  • Filuppladdningar, hantering av tillfällig arbetsyta och beständig fillagring
  • Användarens filbibliotek (user_files-tabellen): en sparad ändring lagras som standard som en oberoende ny fil, eller som en förälderlänkad version när du skriver över originalet. Det registrerar vilka verktyg som tillämpades (toolChain) och får en autogenererad miniatyrbild för Files-sidan
  • Verktygsexekvering (dirigerar varje verktygsförfrågan till bildmotorn eller AI-bryggan)
  • Pipeline-orkestrering (kedjar samman flera verktyg sekventiellt)
  • Batchbearbetning med samtidighetskontroll via BullMQ-jobbköer (pooler: image, media, ai, docs, system)
  • Användarautentisering, RBAC (admin/user-roller med en fullständig behörighetsuppsättning), hantering av API-nycklar och hastighetsbegränsning
  • Teamhantering - endast admin-CRUD; användare tilldelas ett team via team-fältet på sin profil
  • Körtidsinställningar - ett nyckel-värde-lager i settings-tabellen som styr disabledTools, enableExperimentalTools, loginAttemptLimit och andra driftsrattar utan att distribuera om
  • Anpassad varumärkesprofilering och körtidsinställningar via databasbaserade inställningar
  • Scalar/OpenAPI-dokumentation på /api/docs
  • Serverar den byggda frontenden som en SPA i produktion

Viktiga beroenden: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod för validering.

Servern hanterar smidig avstängning vid SIGTERM/SIGINT: den dränerar HTTP-anslutningar, stoppar BullMQ-workers, stänger av Python-dispatchern och stänger databasanslutningen.

Web (apps/web)

En React 19 single-page-app byggd med Vite. Använder Zustand för tillståndshantering, Tailwind CSS v4 för styling och Lucide för ikoner. Kommunicerar med API:et över REST och SSE (för förloppsspårning).

Sidorna inkluderar en verktygsarbetsyta, en Files-sida för hantering av beständiga uppladdningar och resultat, en automatisering/pipelinebyggare och en admininställningspanel.

Den byggda frontenden serveras av Fastify-backenden i produktion, så det finns ingen separat webbserver i Docker-containern.

Docs (apps/docs)

Denna VitePress-webbplats. Distribueras automatiskt till Cloudflare Pages vid push till main.

Hur en förfrågan flödar

  1. Användaren väljer ett verktyg i webbgränssnittet och laddar upp en fil.
  2. Frontenden skickar en multipart-POST till /api/v1/tools/:section/:toolId med filen och inställningarna.
  3. API-rutten validerar indata med Zod och dirigerar sedan bearbetningen.
  4. För standardverktyg köas jobbet till lämplig BullMQ-pool (image, media eller docs baserat på modalitet). Den in-process-körda BullMQ-workern orienterar bilden automatiskt baserat på EXIF-metadata, kör verktygets bearbetningsfunktion och returnerar resultatet.
  5. För de flesta AI-verktyg skickar TypeScript-bryggan en begäran till den beständiga Python dispatcher. Snabb OCR anropar istället Tesseract, och exakt OCR startar den fästa körbara filen från den aktiva oföränderliga OCR-generationen. Den begärda OCR-nivån är fixerad vid inträde och ändras aldrig tyst under exekvering.
  6. Jobbförlopp bevaras i jobs-tabellen i PostgreSQL så att tillståndet överlever containeromstarter. Realtidsuppdateringar levereras via SSE på /api/v1/jobs/:jobId/progress.
  7. API:et returnerar en jobId och downloadUrl. Användaren laddar ner den bearbetade filen från /api/v1/download/:jobId/:filename.

För pipelines matar API:et utdata från varje steg som indata till nästa och kör dem sekventiellt.

För batchbearbetning använder API:et BullMQ-flöden med underjobb per steg och returnerar en ZIP-fil med alla bearbetade filer.

Resursavtryck

SnapOtter är utformat för låg minnesanvändning i viloläge. Ingenting förinläses eller hålls varmt vid start.

I viloläge

Node.js/Fastify-processen, PostgreSQL och Redis körs. Typiskt vilo-RAM är ~200-300 MB över alla tre containrar (Node.js-processen, Postgres och Redis). Ingen Python-process, inga modellvikter i minnet.

Vad som startar, och när

Komponent Startar när Minne medan aktiv
Fastify-server + Postgres + Redis Containerstart ~200-300 MB totalt
BullMQ-workers Containerstart (in-process) En worker per pool (image, media, ai, docs, system)
Python-dispatcher Första AI-verktygsförfrågan Python-tolk + förimporterade bibliotek (PIL, NumPy, MediaPipe, rembg) - inga modellvikter
AI-modellvikter Under det specifika verktygets förfrågan Laddade från disk, frigjorda när förfrågan är klar

Modellinläsning

Alla modellviktsfiler (totalt flera GB) ligger på disk i /opt/models/ hela tiden. Varje AI-verktygsskript laddar endast sin egen modell(er) i minnet under en förfrågans varaktighet och frigör dem sedan. Vissa skript anropar uttryckligen del model och torch.cuda.empty_cache() efter inferens för att säkerställa att minne returneras omedelbart.

Det finns ingen modellcache mellan förfrågningar. Att köra samma AI-verktyg direkt efter varandra laddar om modellen varje gång. Detta håller vilominnet nära noll till priset av en modellinläsningsfördröjning vid varje AI-förfrågan.

Kallstart vid första AI-förfrågan

Python-dispatchern körs inte när containern startar. Den första AI-förfrågan utlöser två saker parallellt: dispatchern börjar värmas upp i bakgrunden, och själva förfrågan faller tillbaka på en engångsskapad Python-subprocess. När dispatchern signalerar redo använder alla efterföljande AI-förfrågningar den direkt och hoppar över kostnaden för subprocess-skapande.