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

10 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
Estrutura do monorepo, arquitetura de apps e pacotes, ciclo de vida das requisições e uso de recursos do SnapOtter. 50e076925c4b human e541eef7cbf1 2

Arquitetura

O SnapOtter é um monorepo gerenciado com pnpm workspaces e Turborepo. Ele é implantado como uma pilha Docker Compose de 3 contêineres: a imagem do app SnapOtter, o PostgreSQL 17 e o Redis 8.

Estrutura do projeto

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

Pacotes

@snapotter/image-engine

A biblioteca principal de processamento de imagem construída sobre o Sharp. Ela lida com todas as operações que não são de IA: redimensionar, recortar, girar, espelhar, converter, comprimir, remover metadados e ajustes de cor (brilho, contraste, saturação, escala de cinza, sépia, inversão, canais de cor).

Este pacote não tem dependências de rede e roda inteiramente em processo.

@snapotter/ai

Uma camada de ponte que chama tempos de execução nativos e Python ML. A maioria das ferramentas Python usa um dispatcher persistente que pré-importa bibliotecas pesadas (PIL, NumPy, MediaPipe, rembg) para que as chamadas subsequentes ignorem a sobrecarga de importação. OCR é isolado desse ambiente compartilhado mutável: fast invoca Tesseract nativo, enquanto balanced e best usam um JSONL dispatcher persistente dedicado fixado na geração RapidOCR/ONNX ativa e imutável. Cada solicitação contém um generation lease. A ativação primeiro executa um smoke test em um candidato e depois alterna atomicamente para seu dispatcher. O dispatcher anterior é drenado antes que sua geração seja coletada como lixo.

Os modelos não são pré-carregados. Cada script de ferramenta carrega os pesos do seu modelo do disco no momento da requisição e os descarta quando a requisição termina. Consulte Uso de recursos para o perfil de memória completo.

Operações suportadas: remoção de fundo (rembg/BiRefNet), upscaling (RealESRGAN), desfoque de rosto (MediaPipe), aprimoramento de rosto (GFPGAN/CodeFormer), apagamento de objeto (LaMa ONNX), OCR (Tesseract e RapidOCR com modelos PP-OCR ONNX), colorização (DDColor), remoção de ruído, remoção de olhos vermelhos, restauração de fotos, foto de passaporte geração, correção de transparência (BiRefNet HR-matting) e redimensionamento com reconhecimento de conteúdo (Go caire binário).

Os scripts Python residem em packages/ai/python/. Grandes pacotes de modelos opcionais são instalados sob demanda no volume /data/ai persistente. O OCR preciso usa artefatos assinados e específicos da plataforma; a camada Tesseract integrada não requer download de pacote de modelo.

@snapotter/shared

Tipos TypeScript compartilhados, constantes (como APP_VERSION e as definições de ferramentas) e strings de tradução i18n usadas tanto pelo frontend quanto pelo backend.

Aplicações

API (apps/api)

Um servidor Fastify v5 que expõe 243 rotas de ferramentas em cinco modalidades (image, video, audio, PDF, file) e lida com:

  • Uploads de arquivos, gerenciamento de espaço de trabalho temporário e armazenamento persistente de arquivos
  • Biblioteca de arquivos do usuário (tabela user_files): uma edição salva é armazenada por padrão como um novo arquivo independente, ou como uma versão vinculada ao pai quando você sobrescreve o original. Ela registra quais ferramentas foram aplicadas (toolChain) e recebe uma miniatura gerada automaticamente para a página Files
  • Execução de ferramentas (roteia cada requisição de ferramenta para o motor de imagem ou para a ponte de IA)
  • Orquestração de pipelines (encadeamento de várias ferramentas em sequência)
  • Processamento em lote com controle de concorrência via filas de jobs do BullMQ (pools: image, media, ai, docs, system)
  • Autenticação de usuários, RBAC (funções admin/user com um conjunto completo de permissões), gerenciamento de chaves de API e limitação de taxa
  • Gerenciamento de equipes - CRUD apenas para admin; os usuários são atribuídos a uma equipe por meio do campo team no seu perfil
  • Configurações de runtime - um armazenamento chave-valor na tabela settings que controla disabledTools, enableExperimentalTools, loginAttemptLimit e outros parâmetros operacionais sem reimplantar
  • Marca personalizada e preferências de runtime por meio de configurações respaldadas pelo banco de dados
  • Documentação Scalar/OpenAPI em /api/docs
  • Serviço do frontend compilado como uma SPA em produção

Dependências principais: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod para validação.

O servidor lida com o encerramento gracioso em SIGTERM/SIGINT: ele drena as conexões HTTP, para os workers do BullMQ, desliga o dispatcher Python e fecha a conexão com o banco de dados.

Web (apps/web)

Um app de página única em React 19 construído com Vite. Usa Zustand para gerenciamento de estado, Tailwind CSS v4 para estilização e Lucide para ícones. Comunica-se com a API por REST e SSE (para acompanhamento de progresso).

As páginas incluem um espaço de trabalho de ferramentas, uma página Files para gerenciar uploads e resultados persistentes, um construtor de automação/pipelines e um painel de configurações de admin.

O frontend compilado é servido pelo backend Fastify em produção, portanto não há um servidor web separado no contêiner Docker.

Docs (apps/docs)

Este site VitePress. Implantado no Cloudflare Pages automaticamente a cada push para main.

Como uma requisição flui

  1. O usuário escolhe uma ferramenta na interface web e envia um arquivo.
  2. O frontend envia um POST multipart para /api/v1/tools/:section/:toolId com o arquivo e as configurações.
  3. A rota da API valida a entrada com o Zod e depois despacha o processamento.
  4. Para ferramentas padrão, o job é enfileirado no pool BullMQ apropriado (image, media ou docs, conforme a modalidade). O worker BullMQ em processo orienta automaticamente a imagem com base nos metadados EXIF, executa a função de processamento da ferramenta e retorna o resultado.
  5. Para a maioria das ferramentas de IA, a ponte TypeScript envia uma solicitação ao Python dispatcher persistente. Em vez disso, o OCR rápido invoca Tesseract, e o OCR preciso inicia o executável fixado a partir da geração OCR imutável ativa. A camada OCR solicitada é fixada na entrada e nunca é alterada silenciosamente durante a execução.
  6. O progresso do job é persistido na tabela jobs no PostgreSQL, de modo que o estado sobrevive a reinícios do contêiner. As atualizações em tempo real são entregues via SSE em /api/v1/jobs/:jobId/progress.
  7. A API retorna um jobId e um downloadUrl. O usuário baixa o arquivo processado de /api/v1/download/:jobId/:filename.

Para pipelines, a API alimenta a saída de cada etapa como entrada da próxima, executando-as em sequência.

Para o processamento em lote, a API usa flows do BullMQ com jobs filhos por etapa e retorna um arquivo ZIP com todos os arquivos processados.

Uso de recursos

O SnapOtter foi projetado para baixo uso de memória em repouso. Nada é pré-carregado ou mantido aquecido na inicialização.

Em repouso

O processo Node.js/Fastify, o PostgreSQL e o Redis estão em execução. A RAM típica em repouso é de cerca de 200 a 300 MB entre os três contêineres (processo Node.js, Postgres e Redis). Sem processo Python, sem pesos de modelo na memória.

O que inicia, e quando

Componente Inicia quando Memória enquanto ativo
Servidor Fastify + Postgres + Redis Início do contêiner ~200-300 MB no total
Workers do BullMQ Início do contêiner (em processo) Um worker por pool (image, media, ai, docs, system)
Dispatcher Python Primeira requisição de ferramenta de IA Interpretador Python + bibliotecas pré-importadas (PIL, NumPy, MediaPipe, rembg) - sem pesos de modelo
Pesos de modelo de IA Durante a requisição da ferramenta específica Carregados do disco, liberados quando a requisição termina

Carregamento de modelos

Todos os arquivos de pesos de modelo (somando vários GB) ficam no disco em /opt/models/ o tempo todo. Cada script de ferramenta de IA carrega na memória apenas o(s) seu(s) próprio(s) modelo(s) durante uma requisição e depois os libera. Alguns scripts chamam explicitamente del model e torch.cuda.empty_cache() após a inferência para garantir que a memória seja devolvida imediatamente.

Não há cache de modelo entre requisições. Executar a mesma ferramenta de IA em sequência recarrega o modelo a cada vez. Isso mantém a memória em repouso próxima de zero, ao custo de um atraso de carregamento do modelo em cada requisição de IA.

Cold start da primeira requisição de IA

O dispatcher Python não está em execução quando o contêiner inicia. A primeira requisição de IA dispara duas coisas em paralelo: o dispatcher começa a aquecer em segundo plano e a própria requisição recorre a gerar um subprocesso Python único e avulso. Assim que o dispatcher sinaliza que está pronto, todas as requisições de IA subsequentes o usam diretamente e evitam o custo de gerar subprocessos.