feat(docs-i18n): translate all documentation into 20 languages

All 181 docs markdown files translated into 20 languages (apps/docs/<locale>/**). Companion to the i18n code PR; admin-merged because the file count exceeds GitHub's per-PR CI trigger limit. Validated by pnpm i18n:check (all surfaces, 0 stale/missing) and a clean all-locale docs build.
This commit is contained in:
SnapOtter
2026-07-11 13:52:47 +08:00
committed by GitHub
parent 00b651c9f8
commit 4963ab3bbd
3620 changed files with 306134 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
---
description: "Monorepo-structuur, app- en package-architectuur, request-levenscyclus en resourcegebruik van SnapOtter."
i18n_source_hash: 9e8f80499a37
i18n_provenance: human
i18n_output_hash: 5122b85d1d84
---
# Architectuur {#architecture}
SnapOtter is een monorepo beheerd met pnpm-workspaces en Turborepo. Het wordt uitgerold als een Docker Compose-stack met 3 containers: de SnapOtter-app-image, PostgreSQL 17 en Redis 8.
## Projectstructuur {#project-structure}
```
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
```
## Packages {#packages}
### `@snapotter/image-engine` {#snapotter-image-engine}
De kernbibliotheek voor beeldverwerking, gebouwd op [Sharp](https://sharp.pixelplumbing.com/). Deze handelt alle niet-AI-bewerkingen af: vergroten/verkleinen, bijsnijden, roteren, spiegelen, converteren, comprimeren, metadata verwijderen en kleuraanpassingen (helderheid, contrast, verzadiging, grijstinten, sepia, inverteren, kleurkanalen).
Dit package heeft geen netwerkafhankelijkheden en draait volledig in-process.
### `@snapotter/ai` {#snapotter-ai}
Een brugjlaag die Python-scripts aanroept voor ML-bewerkingen. Bij het eerste gebruik start de brug een persistent Python-dispatcherproces dat zware bibliotheken (PIL, NumPy, MediaPipe, rembg) vooraf importeert, zodat latere AI-aanroepen de importoverhead overslaan. Als de dispatcher nog niet klaar is, valt de brug terug op het opstarten van een verse Python-subprocess per verzoek.
**Modellen worden niet vooraf geladen.** Elk toolscript laadt zijn modelgewichten bij het verzoek van schijf en verwijdert ze zodra het verzoek klaar is. Zie [Resourcegebruik](#resource-footprint) voor het volledige geheugenprofiel.
Ondersteunde bewerkingen: achtergrondverwijdering (rembg/BiRefNet), upscaling (RealESRGAN), gezichtsvervaging (MediaPipe), gezichtsverbetering (GFPGAN/CodeFormer), objecten wissen (LaMa ONNX), OCR (PaddleOCR/Tesseract), inkleuren (DDColor), ruisverwijdering, rode-ogenverwijdering, fotorestauratie, generatie van pasfoto's, transparantie herstellen (BiRefNet HR-matting) en contentbewust vergroten/verkleinen (Go caire-binary).
De Python-scripts staan in `packages/ai/python/`. De Docker-image downloadt alle modelgewichten vooraf tijdens de build, zodat de container volledig offline werkt.
### `@snapotter/shared` {#snapotter-shared}
Gedeelde TypeScript-types, constanten (zoals `APP_VERSION` en tooldefinities) en i18n-vertaalstrings die door zowel de frontend als de backend worden gebruikt.
## Applicaties {#applications}
### API (`apps/api`) {#api-apps-api}
Een Fastify v5-server die 241 toolroutes over vijf modaliteiten (image, video, audio, PDF, file) blootstelt en het volgende afhandelt:
- Bestandsuploads, beheer van tijdelijke werkruimte en persistente bestandsopslag
- Gebruikersbibliotheek voor bestanden met versieketens (`user_files`-tabel) - elk verwerkt resultaat verwijst terug naar het bronbestand en registreert welke tool is toegepast, met automatisch gegenereerde miniaturen voor de Files-pagina
- Tooluitvoering (routeert elk toolverzoek naar de image-engine of AI-brug)
- Pijplijnorkestratie (meerdere tools sequentieel aan elkaar koppelen)
- Batchverwerking met concurrentiebeheer via BullMQ-taakwachtrijen (pools: image, media, ai, docs, system)
- Gebruikersauthenticatie, RBAC (admin/user-rollen met een volledige set permissies), beheer van API-sleutels en rate limiting
- Teambeheer - alleen voor admins, CRUD; gebruikers worden aan een team toegewezen via het `team`-veld op hun profiel
- Runtime-instellingen - een key-value store in de `settings`-tabel die `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit` en andere operationele knoppen aanstuurt zonder opnieuw uit te rollen
- Aangepaste branding en runtime-voorkeuren via database-ondersteunde instellingen
- Scalar/OpenAPI-documentatie op `/api/docs`
- De gebouwde frontend als SPA serveren in productie
Belangrijkste dependencies: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod voor validatie.
De server handelt een nette afsluiting af bij SIGTERM/SIGINT: hij drainert HTTP-verbindingen, stopt BullMQ-workers, sluit de Python-dispatcher af en sluit de databaseverbinding.
### Web (`apps/web`) {#web-apps-web}
Een single-page app in React 19, gebouwd met Vite. Gebruikt Zustand voor statebeheer, Tailwind CSS v4 voor styling en Lucide voor iconen. Communiceert met de API via REST en SSE (voor voortgangsregistratie).
Pagina's omvatten een toolwerkruimte, een Files-pagina voor het beheren van persistente uploads en resultaten, een automatiserings-/pijplijnbouwer en een admin-instellingenpaneel.
De gebouwde frontend wordt in productie geserveerd door de Fastify-backend, dus er is geen aparte webserver in de Docker-container.
### Docs (`apps/docs`) {#docs-apps-docs}
Deze VitePress-site. Wordt automatisch uitgerold naar Cloudflare Pages bij een push naar `main`.
## Hoe een verzoek verloopt {#how-a-request-flows}
1. De gebruiker kiest een tool in de web-UI en uploadt een bestand.
2. De frontend stuurt een multipart POST naar `/api/v1/tools/:section/:toolId` met het bestand en de instellingen.
3. De API-route valideert de invoer met Zod en start vervolgens de verwerking.
4. Voor standaardtools wordt de taak in de juiste BullMQ-pool geplaatst (image, media of docs op basis van modaliteit). De in-process BullMQ-worker oriënteert de afbeelding automatisch op basis van EXIF-metadata, voert de procesfunctie van de tool uit en geeft het resultaat terug.
5. Voor AI-tools stuurt de TypeScript-brug een verzoek naar de persistent Python-dispatcher (of start een verse subprocess als fallback), wacht tot deze klaar is en leest het uitvoerbestand.
6. Taakvoortgang wordt vastgelegd in de `jobs`-tabel in PostgreSQL, zodat de state herstarts van de container overleeft. Realtime-updates worden geleverd via SSE op `/api/v1/jobs/:jobId/progress`.
7. De API retourneert een `jobId` en `downloadUrl`. De gebruiker downloadt het verwerkte bestand vanaf `/api/v1/download/:jobId/:filename`.
Voor pijplijnen voert de API de uitvoer van elke stap als invoer aan de volgende, en draait ze sequentieel.
Voor batchverwerking gebruikt de API BullMQ-flows met per-stap onderliggende taken en retourneert een ZIP-bestand met alle verwerkte bestanden.
## Resourcegebruik {#resource-footprint}
SnapOtter is ontworpen voor laag geheugengebruik bij inactiviteit. Er wordt bij het opstarten niets vooraf geladen of warm gehouden.
### Bij inactiviteit {#at-idle}
Het Node.js/Fastify-proces, PostgreSQL en Redis draaien. Typisch RAM-gebruik bij inactiviteit is **~200-300 MB** verdeeld over alle drie de containers (Node.js-proces, Postgres en Redis). Geen Python-proces, geen modelgewichten in het geheugen.
### Wat er start, en wanneer {#what-starts-and-when}
| Component | Start wanneer | Geheugen tijdens actief zijn |
|-----------|-------------|---------------------|
| Fastify-server + Postgres + Redis | Bij het starten van de container | ~200-300 MB totaal |
| BullMQ-workers | Bij het starten van de container (in-process) | Eén worker per pool (image, media, ai, docs, system) |
| Python-dispatcher | Bij het eerste AI-toolverzoek | Python-interpreter + vooraf geïmporteerde bibliotheken (PIL, NumPy, MediaPipe, rembg) - geen modelgewichten |
| AI-modelgewichten | Tijdens het verzoek van de specifieke tool | Van schijf geladen, vrijgegeven wanneer het verzoek klaar is |
### Modellen laden {#model-loading}
Alle modelgewichtbestanden (samen enkele GB) staan te allen tijde op schijf in `/opt/models/`. Elk AI-toolscript laadt alleen zijn eigen model(len) in het geheugen voor de duur van een verzoek en geeft ze daarna vrij. Sommige scripts roepen expliciet `del model` en `torch.cuda.empty_cache()` aan na de inferentie om ervoor te zorgen dat het geheugen onmiddellijk wordt teruggegeven.
Er is geen modelcache tussen verzoeken. Dezelfde AI-tool achter elkaar draaien laadt het model telkens opnieuw. Dit houdt het geheugengebruik bij inactiviteit vrijwel op nul, ten koste van een laadvertraging voor het model bij elk AI-verzoek.
### Cold start bij het eerste AI-verzoek {#first-ai-request-cold-start}
De Python-dispatcher draait niet wanneer de container start. Het eerste AI-verzoek zet twee dingen parallel in gang: de dispatcher begint op de achtergrond op te warmen, en het verzoek zelf valt terug op het opstarten van een eenmalige Python-subprocess. Zodra de dispatcher aangeeft klaar te zijn, gebruiken alle volgende AI-verzoeken deze rechtstreeks en slaan ze de kosten van het opstarten van een subprocess over.
+164
View File
@@ -0,0 +1,164 @@
---
description: "Alle SnapOtter-omgevingsvariabelen met standaardwaarden. Configureer authenticatie, opslag, AI-modellen, analytics en meer."
i18n_source_hash: 8e9e9ca2840c
i18n_provenance: human
i18n_output_hash: 17a9658bf5d8
---
# Configuratie {#configuration}
Alle configuratie gebeurt via omgevingsvariabelen. Elke variabele heeft een verstandige standaardwaarde, zodat SnapOtter direct werkt zonder er ook maar één in te stellen.
## Omgevingsvariabelen {#environment-variables}
### Server {#server}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `PORT` | `1349` | Poort waarop de server luistert. |
| `RATE_LIMIT_PER_MIN` | `1000` | Maximaal aantal verzoeken per minuut per IP. Stel in op 0 om rate limiting uit te schakelen. |
| `CORS_ORIGIN` | (leeg) | Door komma's gescheiden toegestane origins voor CORS, of leeg voor alleen dezelfde origin. |
| `LOG_LEVEL` | `info` | Uitgebreidheid van logging. Een van: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. |
| `TRUST_PROXY` | `true` | Vertrouw `X-Forwarded-For`-headers van een reverse proxy. Stel in op `false` als er geen proxy vóór zit. |
### Authenticatie {#authentication}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `AUTH_ENABLED` | `false` | Stel in op `true` om aanmelden te vereisen. De Docker-image gebruikt standaard `true`. |
| `DEFAULT_USERNAME` | `admin` | Gebruikersnaam voor het initiële adminaccount. Wordt alleen bij de eerste keer opstarten gebruikt. |
| `DEFAULT_PASSWORD` | `admin` | Wachtwoord voor het initiële adminaccount. Wijzig dit na de eerste keer aanmelden. |
| `MAX_USERS` | `0` (onbeperkt) | Maximaal aantal geregistreerde gebruikersaccounts. Stel in op 0 voor onbeperkt. |
| `SESSION_DURATION_HOURS` | `168` | Levensduur van de aanmeldsessie in uren (standaard 7 dagen). |
| `SKIP_MUST_CHANGE_PASSWORD` | - | Stel in op een niet-lege waarde om de verplichte wachtwoordwijzigingsprompt bij de eerste aanmelding over te slaan |
### Opslag {#storage}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `STORAGE_MODE` | `local` | `local` of `s3`. S3/MinIO vereist een licentie met de s3_storage-functie. |
| `DATABASE_URL` | `postgres://snapotter:snapotter@postgres:5432/snapotter` | PostgreSQL-connectiestring. |
| `REDIS_URL` | `redis://redis:6379` | Redis-connectiestring (gebruikt voor BullMQ-taakwachtrijen). |
| `WORKSPACE_PATH` | `./tmp/workspace` | Map voor tijdelijke bestanden tijdens de verwerking. Wordt automatisch opgeschoond. |
| `FILES_STORAGE_PATH` | `./data/files` | Map voor persistente gebruikersbestanden (geüploade afbeeldingen, opgeslagen resultaten). |
### Ingebedde modus {#embedded-mode}
Draai de image zonder `DATABASE_URL` en zonder `REDIS_URL` en hij start zijn eigen PostgreSQL 17 en Redis binnen de container, gebonden aan loopback, met alle gegevens op het `/data`-volume. Dit herstelt de `docker run`-ervaring met één commando voor snelle start, homelab en upgrades vanaf 1.x. Het is een gemakspad, geen productiedeployment: draai voor productie de Compose-stack met 3 containers met aparte PostgreSQL en Redis. De ingebedde modus vereist dat de container als root draait en is niet compatibel met runtimes met een willekeurige UID (OpenShift, Kubernetes `runAsNonRoot`); gebruik daar Compose.
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `EMBEDDED` | `auto` | Automatisch ingeschakeld wanneer zowel `DATABASE_URL` als `REDIS_URL` niet zijn ingesteld. Stel in op `0` om het uit te schakelen (de app faalt dan direct als er geen externe `DATABASE_URL`/`REDIS_URL` is ingesteld, in plaats van stilletjes een database binnen de container te starten). |
| `REDIS_MAXMEMORY` | `512mb` | Geheugenlimiet voor de ingebedde Redis (alleen in de ingebedde modus). Verlaag deze op hosts met beperkt geheugen, zoals een Raspberry Pi. |
Upgraden vanaf 1.x: plaats je oude `snapotter.db` op `/data/snapotter.db` in het volume en de ingebedde modus importeert het bij de eerste keer opstarten in de ingebedde PostgreSQL. De import draait één keer; latere opstarts slaan deze over.
Opmerking over telemetrie: de ingebedde modus erft de analytics-standaard van de image net als elke andere configuratie. De gepubliceerde image wordt geleverd met analytics aan; bouw met `--build-arg SNAPOTTER_ANALYTICS=off`, of gebruik de admin-opt-out in de app, om het uit te schakelen.
### Verwerkingslimieten {#processing-limits}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `MAX_UPLOAD_SIZE_MB` | `100` | Maximale bestandsgrootte per upload in megabytes. Stel in op 0 voor onbeperkt. |
| `MAX_BATCH_SIZE` | `100` | Maximaal aantal bestanden in één batchverzoek. Stel in op 0 voor onbeperkt. |
| `CONCURRENT_JOBS` | `0` (auto) | Aantal batchtaken dat parallel draait. Stel in op 0 om automatisch te detecteren op basis van beschikbare CPU-cores. |
| `MAX_MEGAPIXELS` | `0` (onbeperkt) | Maximaal toegestane beeldresolutie in megapixels. Stel in op 0 voor onbeperkt. |
| `MAX_WORKER_THREADS` | `0` (auto) | Maximaal aantal worker-threads voor beeldverwerking. Stel in op 0 om automatisch te detecteren op basis van beschikbare CPU-cores. |
| `PROCESSING_TIMEOUT_S` | `0` (geen limiet) | Maximale verwerkingstijd per verzoek in seconden. Stel in op 0 voor geen timeout. |
| `MAX_PIPELINE_STEPS` | `20` | Maximaal aantal stappen in een pijplijn. Stel in op 0 voor geen limiet. |
| `MAX_CANVAS_PIXELS` | `0` (geen limiet) | Maximale canvasgrootte in pixels voor uitvoerafbeeldingen. Stel in op 0 voor geen limiet. |
| `MAX_SVG_SIZE_MB` | `0` (onbeperkt) | Maximale SVG-bestandsgrootte in megabytes. Stel in op 0 voor onbeperkt. |
| `MAX_SPLIT_GRID` | `100` | Maximale rasterdimensie voor de tool om afbeeldingen te splitsen. |
| `MAX_PDF_PAGES` | `0` (onbeperkt) | Maximaal aantal PDF-pagina's voor PDF-naar-image-conversie. Stel in op 0 voor onbeperkt. |
### Opschoning {#cleanup}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `FILE_MAX_AGE_HOURS` | `72` | Hoe lang niet-opgeslagen verwerkingsresultaten (ruwe uploads en tooluitvoer) worden bewaard vóór automatische verwijdering. Bestanden die je expliciet opslaat in de Files-bibliotheek worden niet beïnvloed en blijven bestaan totdat je ze verwijdert. |
| `CLEANUP_INTERVAL_MINUTES` | `60` | Hoe vaak de opschoontaak draait. |
### Weergave {#appearance}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `DEFAULT_THEME` | `light` | Standaardthema voor nieuwe sessies. `light` of `dark`. |
| `DEFAULT_LOCALE` | `en` | Standaardtaal van de interface. |
| `DEFAULT_TOOL_VIEW` | `sidebar` | Standaard toollay-out. `sidebar` of `fullscreen`. |
### Docker-permissies {#docker-permissions}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `PUID` | `999` | Draai het containerproces als deze UID. Stel in om overeen te komen met je hostgebruiker voor bind mounts (`id -u`). |
| `PGID` | `999` | Draai het containerproces als deze GID. Stel in om overeen te komen met je hostgroep voor bind mounts (`id -g`). |
## Docker-voorbeeld {#docker-example}
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest
ports:
- "1349:1349"
volumes:
- SnapOtter-data:/data
- SnapOtter-workspace:/tmp/workspace
environment:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=changeme
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
- MAX_UPLOAD_SIZE_MB=200
- CONCURRENT_JOBS=4
- FILE_MAX_AGE_HOURS=12
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
volumes:
SnapOtter-data:
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
## Volumes {#volumes}
De Docker Compose-stack gebruikt vier volumes:
- `/data` (app) - AI-modellen, Python-venv en gebruikersbestanden. Koppel dit om geüploade bestanden en geïnstalleerde AI-bundels te behouden bij herstarts.
- `/tmp/workspace` (app) - Tijdelijke opslag voor bestanden die worden verwerkt. Dit mag vluchtig zijn, maar het koppelen ervan voorkomt dat de beschrijfbare laag van de container volloopt.
- `SnapOtter-pgdata` (postgres) - PostgreSQL-datamap. Deze bevat alle relationele gegevens (gebruikers, instellingen, pijplijnen, taken, auditlog). Maak een back-up via `pg_dump` of een volumesnapshot.
- `SnapOtter-redisdata` (redis) - Redis append-only-bestand voor duurzame taakwachtrijen.
+131
View File
@@ -0,0 +1,131 @@
---
description: "Hoe je kunt bijdragen aan SnapOtter. Bugmeldingen, functieverzoeken, pull requests en CLA-vereisten."
i18n_source_hash: 528802503035
i18n_provenance: human
i18n_output_hash: fb98487dded5
---
# Bijdragen {#contributing}
Bedankt voor je interesse om bij te dragen. Deze gids beschrijft hoe je kunt meedoen, wat we accepteren en hoe je begint.
## Manieren om bij te dragen {#ways-to-contribute}
### Issues (geen installatie vereist) {#issues-no-setup-required}
- **Bugmeldingen** - Werkt er iets niet? Open een [bugmelding](https://github.com/snapotter-hq/snapotter/issues/new?template=bug_report.yml) met stappen om het te reproduceren.
- **Functieverzoeken** - Heb je een idee? Start een [discussie](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas) zodat de community erop kan reageren en erop kan stemmen.
- **Vertaalproblemen** - Zie je een verkeerde of ontbrekende vertaling? Open een [vertaalprobleem](https://github.com/snapotter-hq/snapotter/issues/new?template=translation.yml).
- **Documentatieproblemen** - Klopt er iets niet in de documentatie? Open een [documentatieprobleem](https://github.com/snapotter-hq/snapotter/issues/new?template=documentation.yml).
### Code (vereist CLA) {#code-requires-cla}
We accepteren pull requests voor:
| Type | Proces |
|------|---------|
| Bugfixes | Open direct een PR (link de issue als die bestaat) |
| Nieuwe vertalingen | Open direct een PR (zie [Vertaalgids](/nl/guide/translations)) |
| Documentatieverbeteringen | Open direct een PR |
| Verbeteringen aan testdekking | Open direct een PR |
| Nieuwe tools of functies | Start eerst een [discussie](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas); een maintainer zet goedgekeurde ideeën om in een bijgehouden issue voordat je code schrijft |
| Refactors of architectuurwijzigingen | Start eerst een [discussie](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas) en wacht op goedkeuring van een maintainer voordat je code schrijft |
### Wat we niet accepteren {#what-we-will-not-accept}
- Wijzigingen aan CI/CD-workflows, release-configuratie of linter-/compilerconfiguratie
- PR's zonder een ondertekende [Contributor License Agreement](#contributor-license-agreement)
- PR's met meer dan 400 gewijzigde regels (splits groot werk op in kleinere PR's)
- Functies die niet vooraf zijn besproken en goedgekeurd
- Wijzigingen aan `packages/ai/` zonder voorafgaand overleg
## Contributor License Agreement {#contributor-license-agreement}
Voordat we je eerste PR kunnen samenvoegen, moet je onze [Individual CLA](https://github.com/snapotter-hq/snapotter/blob/main/CLA.md) ondertekenen. Dit is een eenmalige vereiste.
**Waarom:** SnapOtter heeft een duale licentie (AGPLv3 + commercieel). De CLA geeft ons het recht om je bijdragen onder beide licenties te verspreiden. Je behoudt het volledige auteursrecht op je werk.
**Hoe:** Wanneer je je eerste PR opent, plaatst de CLA Assistant-bot een reactie met een link. Klik erop, bekijk de overeenkomst en onderteken met je GitHub-account. Kost 30 seconden.
Als je bijdraagt namens je werkgever en je werkgever de IP-rechten op je werk behoudt, neem dan contact op met contact@snapotter.com om een Corporate CLA te regelen voordat je iets indient.
## Aan de slag {#getting-started}
### Vereisten {#prerequisites}
- Node.js 22+
- pnpm 9+
- Python 3.11+ (alleen voor AI-tools)
- Docker (optioneel, voor volledige integratietests)
### Installatie {#setup}
```bash
# Fork and clone
git clone https://github.com/<your-username>/snapotter.git
cd snapotter
# Start Postgres + Redis for local dev
docker compose -f docker-compose.dev.yml up -d
# Install dependencies
pnpm install
# Start dev servers (web on :1349, API on :13490)
pnpm dev
```
### Controles uitvoeren {#running-checks}
Zorg voordat je een PR indient dat alle controles lokaal slagen:
```bash
pnpm lint # Biome lint + format check
pnpm typecheck # TypeScript across monorepo
pnpm test # Vitest unit + integration tests
```
## Pull request-proces {#pull-request-process}
1. Fork de repo en maak een branch aan vanaf `main` (`feat/my-feature` of `fix/issue-123`)
2. Breng je wijzigingen aan in gerichte, beoordeelbare commits met [conventional commits](https://www.conventionalcommits.org/)
3. Voeg tests toe of werk ze bij voor je wijzigingen
4. Voer `pnpm lint && pnpm typecheck && pnpm test` lokaal uit
5. Open een PR tegen `main` en vul het sjabloon in
6. Onderteken de CLA als daarom wordt gevraagd
7. Wacht tot CI slaagt en een maintainer het beoordeelt
### Beoordelingsverwachtingen {#review-expectations}
- We streven ernaar om binnen 7 dagen op PR's te reageren
- Kleine, gerichte PR's worden sneller beoordeeld
- Als je binnen 7 dagen niets hebt gehoord, plaats dan een reactie om de thread te pingen
- We kunnen wijzigingen vragen, een andere aanpak voorstellen of de PR sluiten als die niet aansluit bij de richting van het project
### Nadat je PR is samengevoegd {#after-your-pr-is-merged}
Je bijdrage wordt opgenomen in de volgende release en vermeld in de changelog.
## Goede eerste issues {#good-first-issues}
Op zoek naar iets om aan te werken? Bekijk onze [good first issues](https://github.com/snapotter-hq/snapotter/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) voor toegankelijke taken, of [help wanted](https://github.com/snapotter-hq/snapotter/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) voor grotere onderdelen waarbij we hulp uit de community waarderen.
## Codestijl {#code-style}
- Biome verzorgt de opmaak en linting (dubbele aanhalingstekens, puntkomma's, inspringen met 2 spaties)
- De pre-commit-hook voert `biome check --write` automatisch uit op gestagede bestanden
- Als de linter klaagt, pas dan de code aan (wijzig de Biome-configuratie niet)
- Overal ES-modules (`import`/`export`)
- Conventional commits: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`
Zie voor volledige architectuurdetails de [Ontwikkelaarsgids](/nl/guide/developer).
## Beveiliging {#security}
**Open geen publieke PR of issue voor beveiligingslekken.** Meld ze privé via [GitHub Security Advisories](https://github.com/snapotter-hq/snapotter/security/advisories/new) of e-mail contact@snapotter.com. Zie [SECURITY.md](https://github.com/snapotter-hq/snapotter/blob/main/SECURITY.md) voor alle details.
## Vragen? {#questions}
- [Documentatie](https://docs.snapotter.com/)
- [Discord](https://discord.gg/hr3s7HPUsr)
- [GitHub Discussions](https://github.com/snapotter-hq/snapotter/discussions)
+185
View File
@@ -0,0 +1,185 @@
---
description: "PostgreSQL-databaseschema, tabellen, migraties en back-upprocedures voor SnapOtter."
i18n_source_hash: b37398ae91a3
i18n_provenance: human
i18n_output_hash: e283a792e124
---
# Database {#database}
SnapOtter gebruikt PostgreSQL 17 met [Drizzle ORM](https://orm.drizzle.team/) (pg-core / node-postgres) voor gegevensopslag. Het schema is gedefinieerd in `apps/api/src/db/schema.ts`.
De verbinding wordt geconfigureerd via de omgevingsvariabele `DATABASE_URL` (standaard `postgres://snapotter:snapotter@postgres:5432/snapotter`). In Docker Compose slaat de Postgres-container zijn gegevens op in het benoemde volume `SnapOtter-pgdata`.
## Tabellen {#tables}
### users {#users}
Slaat gebruikersaccounts op. Wordt bij de eerste run automatisch aangemaakt op basis van `DEFAULT_USERNAME` en `DEFAULT_PASSWORD`.
| Kolom | Type | Opmerkingen |
|---|---|---|
| `id` | uuid | Primaire sleutel |
| `username` | varchar | Uniek, vereist |
| `passwordHash` | varchar | scrypt-hash |
| `role` | varchar | `admin`, `editor` of `user` |
| `mustChangePassword` | boolean | Vlag voor geforceerde wachtwoordreset |
| `createdAt` | timestamp | Aanmaaktijd |
| `updatedAt` | timestamp | Tijd van laatste update |
### sessions {#sessions}
Actieve aanmeldsessies. Elke rij koppelt een sessietoken aan een gebruiker.
| Kolom | Type | Opmerkingen |
|---|---|---|
| `id` | varchar | Primaire sleutel (sessietoken) |
| `userId` | uuid | Vreemde sleutel naar `users.id` |
| `expiresAt` | timestamp | Vervaltijd |
| `createdAt` | timestamp | Aanmaaktijd |
### teams {#teams}
Groepen om gebruikers te organiseren. Beheerders kunnen gebruikers aan teams toewijzen.
| Kolom | Type | Beschrijving |
|--------|------|-------------|
| `id` | uuid | Primaire sleutel |
| `name` | varchar (uniek, max. 50 tekens) | Teamnaam |
| `createdAt` | timestamp | Aanmaaktijd |
### api_keys {#api-keys}
API-sleutels voor programmatische toegang. De onbewerkte sleutel wordt eenmalig getoond bij aanmaken; alleen de hash wordt opgeslagen.
| Kolom | Type | Opmerkingen |
|---|---|---|
| `id` | uuid | Primaire sleutel |
| `userId` | uuid | Vreemde sleutel naar `users.id` |
| `keyHash` | varchar | scrypt-hash van de sleutel |
| `name` | varchar | Door de gebruiker opgegeven label |
| `createdAt` | timestamp | Aanmaaktijd |
| `lastUsedAt` | timestamp | Bijgewerkt bij elk geauthenticeerd verzoek |
Sleutels beginnen met het voorvoegsel `si_` gevolgd door 96 hexadecimale tekens (48 willekeurige bytes).
### pipelines {#pipelines}
Opgeslagen toolketens die gebruikers in de UI aanmaken.
| Kolom | Type | Opmerkingen |
|---|---|---|
| `id` | uuid | Primaire sleutel |
| `name` | varchar | Pipelinenaam |
| `description` | varchar | Optionele beschrijving |
| `steps` | jsonb | Array van `{ toolId, settings }`-objecten |
| `createdAt` | timestamp | Aanmaaktijd |
### user_files {#user-files}
Persistente bestandsbibliotheek met versieketentracering. Elke verwerkingsstap die een resultaat opslaat, maakt een nieuwe rij aan die via `parentId` aan de bovenliggende rij wordt gekoppeld, wat een versieboom vormt.
| Kolom | Type | Beschrijving |
|--------|------|-------------|
| `id` | uuid | Primaire sleutel |
| `userId` | uuid | FK naar users (CASCADE DELETE) |
| `originalName` | varchar | Oorspronkelijke bestandsnaam bij upload |
| `storedName` | varchar | Bestandsnaam op schijf |
| `mimeType` | varchar | MIME-type |
| `size` | integer | Bestandsgrootte in bytes |
| `width` | integer | Breedte van de afbeelding in px |
| `height` | integer | Hoogte van de afbeelding in px |
| `version` | integer | Versienummer (1 = origineel) |
| `parentId` | uuid of null | FK naar user_files (bovenliggende versie) |
| `toolChain` | jsonb | Tool-ID's die op volgorde zijn toegepast om deze versie te maken |
| `createdAt` | timestamp | Aanmaaktijd |
### jobs {#jobs}
Volgt verwerkingsjobs voor voortgangsrapportage en opschoning.
| Kolom | Type | Opmerkingen |
|---|---|---|
| `id` | uuid | Primaire sleutel |
| `type` | varchar | Tool- of pipeline-identifier |
| `status` | varchar | `queued`, `processing`, `completed` of `failed` |
| `progress` | real | Fractie van 0.0-1.0 |
| `inputFiles` | jsonb | Array van invoerbestandspaden |
| `outputPath` | varchar | Pad naar het resultaatbestand |
| `settings` | jsonb | Gebruikte toolinstellingen |
| `error` | varchar | Foutmelding bij mislukken |
| `createdAt` | timestamp | Aanmaaktijd |
| `completedAt` | timestamp | Voltooiingstijd |
### settings {#settings}
Sleutel-waardeopslag voor serverbrede instellingen die beheerders vanuit de UI kunnen wijzigen.
| Kolom | Type | Opmerkingen |
|---|---|---|
| `key` | varchar | Primaire sleutel |
| `value` | varchar | Instellingswaarde |
| `updatedAt` | timestamp | Tijd van laatste update |
### roles {#roles}
Aangepaste rollen met granulaire rechten.
| Kolom | Type | Opmerkingen |
|---|---|---|
| `id` | uuid | Primaire sleutel |
| `name` | varchar | Unieke rolnaam |
| `description` | varchar | Optionele beschrijving |
| `permissions` | jsonb | Array van rechtenstrings |
| `createdAt` | timestamp | Aanmaaktijd |
### audit_log {#audit-log}
Logboek van beveiligingsrelevante acties.
| Kolom | Type | Opmerkingen |
|---|---|---|
| `id` | uuid | Primaire sleutel |
| `userId` | uuid | FK naar users |
| `action` | varchar | Actietype |
| `details` | jsonb | Actiespecifieke gegevens |
| `createdAt` | timestamp | Tijd van de actie |
## Migraties {#migrations}
Drizzle verzorgt schemamigraties. Migratiebestanden staan in `apps/api/drizzle/`. Tijdens ontwikkeling:
```bash
cd apps/api
npx drizzle-kit generate # generate a migration from schema changes
npx drizzle-kit migrate # apply pending migrations
```
In productie worden openstaande migraties automatisch toegepast bij het opstarten.
## Back-up en herstel {#backup-and-restore}
De relationele database bevindt zich in het `SnapOtter-pgdata`-volume van de Postgres-container, niet in het `/data`-volume van de app.
**Optie 1: pg_dump (aanbevolen)**
```bash
# Dump the database while the stack is running
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
# Restore into a fresh database
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
```
**Optie 2: Volume-snapshot**
```bash
# Stop the stack, then snapshot the pgdata volume
docker compose down
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
```
### Migreren vanaf 1.x (SQLite) {#migrating-from-1-x-sqlite}
Upgraden vanaf SnapOtter 1.x heeft een eigen gids: zie [Upgraden van 1.x naar 2.0](./upgrading). Kort gezegd: hergebruik je bestaande `/data`-volume, en 2.0 detecteert en importeert `/data/snapotter.db` automatisch bij de eerste keer opstarten (of stel `SQLITE_MIGRATE_PATH` in om er expliciet naar te verwijzen). Maak eerst een back-up van het volledige `/data`-volume, niet alleen van `snapotter.db`: 1.x gebruikt de SQLite WAL-modus, dus een gestopte container laat vaak het grootste deel van zijn gegevens in `snapotter.db-wal` staan naast een bijna leeg `snapotter.db`.
+571
View File
@@ -0,0 +1,571 @@
---
description: "Implementeer SnapOtter in productie met Docker. Hardwarevereisten, GPU-installatie en reverse-proxyconfiguraties voor Nginx, Traefik en Cloudflare."
i18n_source_hash: 6b6957060fa6
i18n_provenance: machine
i18n_output_hash: 6fdbf01d5c9a
---
# Implementatie {#deployment}
SnapOtter wordt geïmplementeerd als een Docker Compose-stack met 3 containers: de SnapOtter-app-image, PostgreSQL 17 en Redis 8. De app-image ondersteunt **linux/amd64** (met NVIDIA CUDA voor AI-versnelling) en **linux/arm64** (CPU), waardoor deze native draait op Intel/AMD-servers, Apple Silicon-Macs en ARM-apparaten zoals de Raspberry Pi 4/5. Intel/AMD iGPU-versnelling via VA-API, Quick Sync of OpenCL wordt vandaag niet ondersteund voor AI-inferentie.
Zie [Docker Image](./docker-tags) voor GPU-installatie, Docker Compose-voorbeelden en versievastlegging.
## Snelstart (CPU) {#quick-start-cpu}
```yaml
# docker-compose.yml - Copy this file and run: docker compose up -d
services:
SnapOtter:
image: snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
container_name: SnapOtter
ports:
- "1349:1349" # Web UI + API
volumes:
- SnapOtter-data:/data # AI models, user files (PERSISTENT)
- SnapOtter-workspace:/tmp/workspace # Temp processing files (can be tmpfs)
environment:
# --- Authentication ---
- AUTH_ENABLED=true # Set to false to disable login entirely
- DEFAULT_USERNAME=admin # First-run admin username
- DEFAULT_PASSWORD=admin # First-run admin password (you'll be forced to change it)
# --- Database + Queue ---
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
# --- Limits (set 0 for unlimited) ---
# - MAX_UPLOAD_SIZE_MB=100 # Per-file upload limit in MB
# - MAX_BATCH_SIZE=100 # Max files per batch request
# - RATE_LIMIT_PER_MIN=1000 # API rate limit per IP, default shown (0 = disabled)
# - MAX_USERS=0 # Max user accounts
# --- Networking ---
# - TRUST_PROXY=true # Trust X-Forwarded-For headers (set false if not behind a proxy)
# --- Bind mount permissions ---
# - PUID=1000 # Match your host user's UID (run: id -u)
# - PGID=1000 # Match your host user's GID (run: id -g)
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
interval: 30s
timeout: 5s
start_period: 60s
retries: 3
shm_size: "2gb" # Needed for Python ML shared memory
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
postgres:
image: postgres:17-alpine
container_name: SnapOtter-postgres
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter # Change this for non-local deployments
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
redis:
image: redis:8-alpine
container_name: SnapOtter-redis
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
volumes:
SnapOtter-data: # Named volume - Docker manages permissions automatically
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
```bash
docker compose up -d
```
De app is daarna beschikbaar op `http://localhost:1349`.
> **Docker Hub-ratelimieten?** Vervang `snapotter/snapotter:latest` door `ghcr.io/snapotter-hq/snapotter:latest` om in plaats daarvan van de GitHub Container Registry te halen. Beide registries ontvangen bij elke release dezelfde image.
## Snelstart (NVIDIA CUDA) {#quick-start-nvidia-cuda}
Voor NVIDIA CUDA-versnelling op AI-tools (achtergrond verwijderen, upscalen, gezichtsverbetering, OCR):
```yaml
# docker-compose-gpu.yml - Requires: NVIDIA GPU + nvidia-container-toolkit
# Install toolkit: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
services:
SnapOtter:
image: snapotter/snapotter:latest
container_name: SnapOtter
ports:
- "1349:1349"
volumes:
- SnapOtter-data:/data
- SnapOtter-workspace:/tmp/workspace
environment:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=admin
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
interval: 30s
timeout: 5s
start_period: 60s
retries: 3
shm_size: "2gb" # Required for PyTorch CUDA shared memory
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all # Or set to 1 for a specific GPU
capabilities: [gpu]
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
postgres:
image: postgres:17-alpine
container_name: SnapOtter-postgres
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
redis:
image: redis:8-alpine
container_name: SnapOtter-redis
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
volumes:
SnapOtter-data:
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
```bash
docker compose -f docker-compose-gpu.yml up -d
```
Controleer de CUDA-detectie in de logs:
```bash
docker logs SnapOtter 2>&1 | head -20
# Look for: [gpu] CUDA available via torch
```
## Hardwarevereisten {#hardware-requirements}
Deze cijfers komen uit benchmarks op een reeks systemen, van een moderne amd64-werkstation met een NVIDIA RTX 4070 tot een Raspberry Pi, waarbij op elk systeem de volledige toolcatalogus werd uitgevoerd en de Docker-resourcelimieten werden doorlopen om de echte ondergrens te vinden.
### Snelle referentie {#quick-reference}
| Niveau | Gebruikssituatie | CPU | RAM | GPU | Opslag |
|------|----------|-----|-----|-----|---------|
| Minimum | Afbeeldings-, bestands- en lichte PDF-tools; één gebruiker; kleine batches | 2 cores | 2 GB | Geen | ~7 GB |
| Aanbevolen | Alle vijf modaliteiten incl. video, PDF en AI op CPU; batches; enkele gebruikers | 4 cores | 4 GB | Geen | ~25 GB |
| Volledig | Alles op snelheid incl. GPU-AI; grote batches; veel gebruikers | 6-8 cores | 8 GB | NVIDIA 8 GB+ VRAM (12 GB comfortabel) | ~35 GB |
**Architectuur: uitsluitend 64-bit** (`linux/amd64` of `linux/arm64`). SnapOtter draait native op Intel/AMD-servers, Apple Silicon-Macs en 64-bit ARM-boards, waaronder de **Raspberry Pi 4 en 5** (4-8 GB). Het draait **niet** op 32-bit ARM (`armv7`/`armhf`) — er wordt geen image voor gebouwd — en ook niet op boards van de 512 MB-klasse zoals de Pi Zero, die onder de geheugenondergrens liggen (zie hieronder).
### Minimum (afbeeldings-, bestands- en lichte PDF-tools; geen AI) {#minimum-image-files-and-light-pdf-tools-no-ai}
| Resource | Vereiste |
|---|---|
| CPU | 2 cores |
| RAM | 2 GB |
| Schijf | ~5.5 GB (image) + datavolume |
| GPU | Niet vereist |
Alle 222 niet-AI-catalogustools - afbeelding (formaat wijzigen, bijsnijden, converteren, comprimeren, aanpassen, watermerk), video (trimmen, dempen, remux), audio (converteren, normaliseren, trimmen), PDF (samenvoegen, splitsen, comprimeren, roteren, beveiligen), bestandsconversies en speciale conversiepresets - draaien op bescheiden hardware. De meeste bewerkingen zijn zelfs bij een groot bestand ruim binnen een seconde klaar: een afbeelding van 2.7 MB wordt in ~0.05 s van formaat gewijzigd en in ~2 s naar WebP hercodeerd.
De geheugenondergrens is reëel, uit een Docker-resourcelimietsweep: **512 MB kan de stack niet starten** (zelfs één enkele formaatwijziging van een afbeelding wordt afgebroken), **1 GB** verwerkt bewerkingen op één bestand, maar een batch met meerdere bestanden raakt door het geheugen heen, en **2 GB / 2 cores** is de kleinste configuratie die batches comfortabel aankan.
```yaml
deploy:
resources:
limits:
cpus: '2'
memory: 2G
```
**De enige CPU-intensieve uitzondering is video-hercodering.** Stream-copy-bewerkingen (trimmen, dempen, container-remux) zijn direct, maar transcoderen naar een andere codec is CPU-gebonden. Een clip van 1080p / 45 seconden die naar VP9 (WebM) wordt hercodeerd, duurt ongeveer **~40 s** op een snelle moderne CPU, ~45 s op Apple Silicon, ~80 s op een oudere mobiele 4-core en **~130 s** op een oudere 4-core server. Als je werklast video-intensief is, geef dan prioriteit aan CPU-cores en kloksnelheid, of verhoog de `cpus:`-limiet van de container — de meegeleverde compose beperkt de app standaard tot 4 cores (8 op de GPU-compose).
### Aanbevolen (AI-tools op CPU) {#recommended-ai-tools-on-cpu}
| Resource | Vereiste |
|---|---|
| CPU | 4 cores |
| RAM | 4 GB |
| Schijf | 3 GB (image) + 24 GB (AI-modellen) + werkruimte |
| GPU | Niet vereist (CPU-terugval) |
**Het installeren van de AI-bundels is wat het RAM naar 4 GB duwt.** Zonder geïnstalleerde AI blijft de app rond 360 MB in ruststand; met alle zeven bundels geïnstalleerd houdt hij ~2.6 GB resident, omdat de Python-AI-sidecar zijn modellen (achtergrond verwijderen, upscalen, OCR, transcriptie, gezichtsdetectie, restauratie) bij het opstarten vooraf laadt. Niet-AI-installaties blijven licht; AI-installaties hebben ≥4 GB nodig.
De meeste AI-tools zijn prima bruikbaar op CPU; een paar willen echt een GPU. Gemeten op een moderne 4-core CPU:
| AI-tool | CPU-tijd | Bruikbaar op CPU? |
|---|---|---|
| Gezichtsdetectie (gezichten vervagen, smart-crop, rode ogen), ruisverwijdering | onder 1 s | Ja |
| OCR, transcriptie, ondertitels | 1-3 s | Ja |
| Inkleuren, gezichtsverbetering | ~10 s | Ja |
| Achtergrond verwijderen / vervangen / vervagen | ~29 s | Ja (je wacht even) |
| AI-upscale (RealESRGAN) | ~33 s klein; minuten bij grote afbeeldingen | Marginaal — GPU sterk aanbevolen |
| Fotorestauratie (volledige pijplijn) | enkele minuten | Nee — vereist een GPU of een snelle CPU met veel cores |
SnapOtter bakt deze modeldownloads bewust niet in de Docker-image. AI-bundels worden pas opgehaald wanneer een beheerder de bijbehorende tool inschakelt, opgeslagen in het persistente `/data/ai`-volume en gedeeld door elke tool die van dezelfde modelstack afhankelijk is. Dit houdt de uiteindelijke containerimage klein en laat een volledige AI-installatie toch de grotere opslagcijfers hieronder bereiken.
Sommige tools zijn afhankelijk van meer dan één gedeelde bundel. Zo heeft Pasfoto zowel `background-removal` als `face-detection` nodig; als `background-removal` al is geïnstalleerd, downloadt het inschakelen van Pasfoto alleen de ontbrekende `face-detection`-bundel. Hetzelfde hergebruik geldt voor alle AI-tools.
Downloadgroottes van AI-modellen:
| Bundel | Schijfgrootte |
|---|---|
| Achtergrond verwijderen | 4-5 GB |
| Upscale + Gezichtsverbetering + Ruisverwijdering | 5-6 GB |
| Gezichtsdetectie | 200-300 MB |
| Objectgom + Inkleuren | 1-2 GB |
| OCR | 5-6 GB |
| Fotorestauratie | 4-5 GB |
| **Alle bundels** | **~24 GB** |
```yaml
deploy:
resources:
limits:
cpus: '4'
memory: 4G
```
### Volledig (AI-tools op NVIDIA CUDA) {#full-ai-tools-on-nvidia-cuda}
| Resource | Vereiste |
|---|---|
| CPU | 6-8 cores (videovoorbereiding + concurrency draaien op CPU, zelfs met GPU-AI) |
| RAM | 8 GB |
| GPU | NVIDIA met 8+ GB VRAM (12 GB aanbevolen) |
| Schijf | ~35 GB totaal |
Een NVIDIA-GPU (CUDA) versnelt de zware AI-modellen dramatisch. Gemeten op een RTX 4070 versus een moderne CPU:
| AI-tool | Versnelling met GPU | Opmerkingen |
|---|---|---|
| AI-upscale (RealESRGAN 2×) | **~47×** | De grootste winst — onder een seconde versus ~33 s (minuten bij grote afbeeldingen) |
| Gezichtsverbetering (CodeFormer) | **~12×** | ~0.9 s versus ~11 s |
| Transcriptie (Whisper) | ~4.5× | |
| Achtergrond verwijderen / vervangen / vervagen | ~4× | ~7 s op GPU versus ~29 s op CPU |
| Inkleuren | ~1.8× | |
| OCR, gezichtsdetectie, rode ogen, ruisverwijdering | ~1× | Al snel op CPU — een GPU helpt niet |
| Fotorestauratie | geen | CPU-gebonden, zelfs op een GPU (0% GPU-benutting); een snelle CPU telt hier meer dan een GPU |
De tools die een GPU waard zijn, zijn **upscale, gezichtsverbetering, transcriptie en achtergrond verwijderen**. Gezichtsdetectie, OCR en rode ogen zijn CPU-gebonden en al snel, dus een GPU voegt niets toe.
Het piek-VRAM-gebruik bereikt 7.5 GB tijdens upscalen met gezichtsverbetering. Een NVIDIA-GPU van 6 GB werkt voor de meeste AI-tools afzonderlijk, maar zal falen bij upscalen. 8-12 GB VRAM verwerkt alles.
Intel/AMD iGPU-versnelling via VA-API, Quick Sync of OpenCL wordt vandaag niet ondersteund voor AI-inferentie. Het toewijzen van `/dev/dri` aan de container schakelt geen AI-GPU-versnelling in; SnapOtter draait AI-tools op CPU tenzij NVIDIA CUDA beschikbaar is.
```yaml
deploy:
resources:
limits:
cpus: '4'
memory: 8G
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
```
### Gelijktijdige gebruikers {#concurrent-users}
Parallelle verzoeken voor het wijzigen van afbeeldingsformaat tegen de standaard app-container die op 4 cores is beperkt:
| Gelijktijdige verzoeken | Gem. reactietijd | Fouten |
|---|---|---|
| 1 | 0.4s | 0 |
| 5 | 1.2s | 0 |
| 10 | 2.1s | 0 |
De reactietijd verslechtert sublineair zonder fouten naarmate de workerpool verzadigd raakt. Het verhogen van de `cpus:`-limiet van de app-container (of het gebruik van een host met meer cores) tilt het plafond op. Merk op dat zware jobs (video-transcodering, CPU-AI) een worker voor hun volledige duur vasthouden, dus dimensioneer de CPU op je verwachte aantal gelijktijdige zware jobs, niet alleen op het aantal verzoeken.
### Ondersteunde afbeeldingsformaten {#supported-image-formats}
SnapOtter ondersteunt **55+ invoerformaten** en **14 uitvoerformaten**, waaronder RAW-bestanden van 20+ cameramerken, professionele formaten (PSD, EPS, OpenEXR, HDR), moderne codecs (JPEG XL, AVIF, HEIC, QOI) en wetenschappelijke/gaming-formaten (FITS, DDS).
Zie de [volledige formaatlijst](/nl/guide/supported-formats) voor details over elk ondersteund formaat, de gebruikte decoder en de beschikbare kwaliteitsregelaars.
### Bekende beperkingen {#known-limitations}
- **Content-aware resize** loopt vast op grote afbeeldingen (>5 MP) door een beperking in de caire-binary. Werkt prima met kleinere afbeeldingen.
- **HEIF-decodering** duurt 13-23 seconden. HEIC (Apples variant) is veel sneller met 0.3-0.9 seconden.
- **OCR Japans** faalt op CPU door een PaddlePaddle MKLDNN-bug. Werkt op GPU.
- **Upscale** verloopt via time-out op CPU voor alles boven kleine afbeeldingen. GPU vereist voor praktisch gebruik.
- **CodeFormer**-gezichtsverbetering is aanzienlijk trager dan GFPGAN (53s versus 2s op GPU). GFPGAN wordt voor de meeste gebruikssituaties aanbevolen.
## Volumes {#volumes}
| Mount / Volume | Doel | Vereist? |
|---|---|---|
| `/data` (app) | AI-modellen, Python-venv, gebruikersbestanden | **Ja** - bestandsverlies zonder dit |
| `/tmp/workspace` (app) | Tijdelijke verwerkingsbestanden (automatisch opgeschoond) | Aanbevolen |
| `SnapOtter-pgdata` (postgres) | PostgreSQL-datamap (gebruikers, instellingen, pijplijnen, jobs) | **Ja** - dataverlies zonder dit |
| `SnapOtter-redisdata` (redis) | Redis append-only-bestand voor duurzame jobwachtrijen | Aanbevolen |
### Bind mounts versus named volumes {#bind-mounts-vs-named-volumes}
**Named volumes** (aanbevolen) — Docker beheert de permissies automatisch:
```yaml
volumes:
- SnapOtter-data:/data
```
**Bind mounts** — Jij beheert de permissies. Stel `PUID`/`PGID` in zodat ze overeenkomen met je host-gebruiker:
```yaml
volumes:
- ./SnapOtter-data:/data
environment:
- PUID=1000 # Your host UID (run: id -u)
- PGID=1000 # Your host GID (run: id -g)
```
### Opslagpermissies {#storage-permissions}
SnapOtter schrijft tijdens runtime naar twee locaties: `/data` (gebruikersbestanden, logs, AI-modellen en de Python-venv) en `/tmp/workspace` (tijdelijke verwerkingsscratch). Beide moeten beschrijfbaar zijn door de gebruiker waaronder de container draait. Als een van beide dat niet is, **faalt de container direct bij het opstarten** met een bericht dat de map, de draaiende UID/GID en de oplossing noemt — in plaats van "gezond" op te starten en dan bij de eerste upload met een cryptische fout te falen.
Hoe de permissies worden afgehandeld, hangt af van hoe de container wordt gestart:
**Standaard (start als root, zakt naar `snapotter`)** — de entrypoint start als root, herstelt het eigenaarschap van de gekoppelde volumes en zakt dan via `gosu` naar de niet-geprivilegieerde `snapotter`-gebruiker. Named volumes werken zonder configuratie. Stel voor bind mounts `PUID`/`PGID` in op je host-gebruiker (hierboven) zodat de bestanden die het schrijft eigendom van jou zijn.
**Kubernetes / OpenShift (niet-root via `runAsUser`)** — rechtstreeks gestart als niet-root-gebruiker, kan de container de volumes niet zelf chown'en, dus de orchestrator moet ze beschrijfbaar maken. Stel `fsGroup` in:
```yaml
securityContext:
runAsUser: 999
runAsGroup: 999
fsGroup: 999 # makes mounted volumes writable by the pod
```
De beschrijfbare mappen van de image zijn groepseigendom van GID 0 en groepsbeschrijfbaar, zodat een pod die met een **willekeurige UID** plus de root-supplementaire groep draait (de OpenShift-standaard) kan schrijven zonder `chown`.
**TrueNAS Scale (en andere "foreign UID"-configuraties)** — TrueNAS draait apps als een niet-root-gebruiker (vaak `568:568`) en koppelt host-datasets die eigendom zijn van een andere gebruiker, dus noch de entrypoint noch `fsGroup` maakt ze op eigen kracht beschrijfbaar. Kies er één:
- **Draai de app als root** (aanbevolen) — laat de gebruiker van de app ongedefinieerd of stel deze in op `0`, en laat de standaard-entrypoint de permissies herstellen en naar `snapotter` zakken.
- **Draai als UID `999`** — stel de gebruiker/groep van de app in op `999:999` (SnapOtters ingebouwde `snapotter`-gebruiker) zodat deze overeenkomt met het eigenaarschap van de image.
- **`chown` de host-dataset** naar de UID waaronder de container draait, vanuit de TrueNAS-shell:
```bash
# Gebruik de UID uit de opstartfout (of voer `id` uit in de container)
chown -R 568:568 /mnt/<pool>/<dataset>
```
De opstartfout noemt de exacte UID die je moet gebruiken, dus de snelste weg is de app één keer te starten, het bericht te lezen en dan overeenkomstig te `chown` (of de gebruiker aan te passen).
## Omgevingsvariabelen {#environment-variables}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `AUTH_ENABLED` | `true` | Inlogvereiste in-/uitschakelen |
| `DEFAULT_USERNAME` | `admin` | Initiële beheerdersgebruikersnaam |
| `DEFAULT_PASSWORD` | `admin` | Initieel beheerderswachtwoord (wijziging verplicht bij eerste login) |
| `MAX_UPLOAD_SIZE_MB` | `100` | Uploadlimiet per bestand |
| `MAX_BATCH_SIZE` | `100` | Max. bestanden per batchverzoek |
| `RATE_LIMIT_PER_MIN` | `1000` | API-verzoeken per minuut per IP (stel 0 in om uit te schakelen) |
| `MAX_USERS` | `0` (onbeperkt) | Maximaal aantal gebruikersaccounts |
| `TRUST_PROXY` | `true` | Vertrouw X-Forwarded-For-headers van reverse proxy |
| `PUID` | `999` | Draaien onder deze UID (voor bind-mount-permissies) |
| `PGID` | `999` | Draaien onder deze GID (voor bind-mount-permissies) |
| `LOG_LEVEL` | `info` | Logbreedsprakigheid: fatal, error, warn, info, debug, trace |
| `CONCURRENT_JOBS` | `0` (auto) | Max. parallelle AI-verwerkingsjobs |
| `SESSION_DURATION_HOURS` | `168` | Levensduur van inlogsessie (7 dagen) |
| `CORS_ORIGIN` | (leeg) | Komma-gescheiden toegestane origins, of leeg voor same-origin |
## Health check {#health-check}
De container bevat een ingebouwde health check:
```bash
# Check container health status
docker inspect --format='{{.State.Health.Status}}' SnapOtter
# Manual health check
curl http://localhost:1349/api/v1/health
# {"status":"healthy","version":"x.y.z"}
```
## Reverse proxy {#reverse-proxy}
SnapOtter stelt `TRUST_PROXY=true` standaard in zodat ratelimiting en logging het echte client-IP uit de `X-Forwarded-For`-headers gebruiken.
### Nginx {#nginx}
```nginx
server {
listen 80;
server_name images.example.com;
# Match MAX_UPLOAD_SIZE_MB (0 = nginx default 1M, so set high for unlimited)
client_max_body_size 500M;
location / {
proxy_pass http://localhost:1349;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE support (batch progress, feature install progress)
proxy_buffering off;
proxy_read_timeout 300s;
}
}
```
### Nginx Proxy Manager {#nginx-proxy-manager}
1. Voeg een nieuwe Proxy Host toe
2. Stel Domain Name in op je domein
3. Stel Scheme in op `http`, Forward Hostname op `SnapOtter` (of je container-IP), Forward Port op `1349`
4. Schakel WebSocket-ondersteuning in
5. Voeg onder Advanced toe: `client_max_body_size 500M;` en `proxy_buffering off;`
### Traefik {#traefik}
```yaml
# Add these labels to the SnapOtter service in docker-compose.yml
labels:
- "traefik.enable=true"
- "traefik.http.routers.snapotter.rule=Host(`images.example.com`)"
- "traefik.http.routers.snapotter.entrypoints=websecure"
- "traefik.http.routers.snapotter.tls.certresolver=letsencrypt"
- "traefik.http.services.snapotter.loadbalancer.server.port=1349"
# Increase upload limit (default 2MB is too low)
- "traefik.http.middlewares.snapotter-body.buffering.maxRequestBodyBytes=524288000"
- "traefik.http.routers.snapotter.middlewares=snapotter-body"
```
### Caddy {#caddy}
```txt
images.example.com {
reverse_proxy localhost:1349 {
flush_interval -1
transport http {
read_timeout 300s
write_timeout 300s
}
}
}
```
`flush_interval -1` schakelt responsbuffering uit, wat vereist is voor SSE-voortgangsgebeurtenissen (batchverwerking, AI-tools, feature-installaties). De verlengde time-outs laten grote bestandsuploads voltooien zonder dat Caddy de verbinding vroegtijdig sluit.
### Cloudflare Tunnels {#cloudflare-tunnels}
```bash
cloudflared tunnel --url http://localhost:1349
```
Opmerking: Cloudflare heeft een uploadlimiet van 100 MB op gratis abonnementen. Stel `MAX_UPLOAD_SIZE_MB=100` hierop in.
## CI/CD {#ci-cd}
De GitHub-repository heeft drie workflows:
- **ci.yml** - Draait automatisch bij elke push en PR. Lint, typechecked, test, bouwt en valideert de Docker-image (zonder te pushen).
- **release.yml** - Handmatig geactiveerd via `workflow_dispatch`. Draait semantic-release om een versietag en GitHub-release te maken, bouwt dan een multi-arch Docker-image (amd64 + arm64) en pusht naar Docker Hub (`snapotter/snapotter`) en GitHub Container Registry (`ghcr.io/snapotter-hq/snapotter`).
- **deploy-docs.yml** - Bouwt deze documentatiesite en implementeert deze naar Cloudflare Pages bij een push naar `main`.
Ga om een release te maken naar **Actions > Release > Run workflow** in de GitHub-UI, of voer uit:
```bash
gh workflow run release.yml
```
Semantic-release bepaalt de versie op basis van de commitgeschiedenis. De `latest` Docker-tag wijst altijd naar de meest recente release.
## Analytics {#analytics}
SnapOtter bevat anonieme productanalytics (patronen van toolgebruik, foutrapporten) om bugs te helpen opsporen en functies te verbeteren. Het staat standaard aan. Je bestanden, bestandsnamen en persoonlijke gegevens maken hier nooit deel van uit. SnapOtter werkt normaal met analytics uitgeschakeld.
### Analytics uitschakelen {#disabling-analytics}
De runtime-opt-out is een beheerderstoggle met één klik. Open Instellingen > Systeem > Privacy en zet Anonieme Productanalytics uit. Het stopt onmiddellijk voor de hele instance, geen herbouw vereist.
Voor een image die nooit analytics kan uitzenden, stel je de build-time-harde-uitschakeling in door de repository te klonen en te herbouwen:
```bash
git clone https://github.com/snapotter-hq/SnapOtter.git
cd SnapOtter
docker compose -f docker/docker-compose.yml build --build-arg SNAPOTTER_ANALYTICS=off
docker compose -f docker/docker-compose.yml up -d
```
Of voeg het build-argument toe aan je bestaande `docker-compose.yml`:
```yaml
services:
snapotter:
build:
context: .
dockerfile: docker/Dockerfile
args:
SNAPOTTER_ANALYTICS: "off"
```
+234
View File
@@ -0,0 +1,234 @@
---
description: "Lokale ontwikkelomgeving opzetten, commando's, codeconventies en hoe je een nieuwe tool aan SnapOtter toevoegt."
i18n_source_hash: cb03724d2829
i18n_provenance: human
i18n_output_hash: 057d7364f4cc
---
# Ontwikkelaarsgids {#developer-guide}
Hoe je een lokale ontwikkelomgeving opzet en code bijdraagt aan SnapOtter.
## Vereisten {#prerequisites}
- [Node.js](https://nodejs.org/) 22+
- [pnpm](https://pnpm.io/) 9+ (`corepack enable && corepack prepare pnpm@latest --activate`)
- [Docker](https://www.docker.com/) (vereist voor lokale Postgres + Redis, containerbuilds en AI-functies)
- Git
Python 3.10+ is alleen nodig als je aan de AI/ML-sidecar werkt (achtergrond verwijderen, opschalen, OCR).
## Installatie {#setup}
```bash
git clone https://github.com/snapotter-hq/snapotter.git
cd snapotter
docker compose -f docker-compose.dev.yml up -d # start Postgres + Redis
pnpm install
pnpm dev
```
Dit start twee dev-servers:
| Service | URL | Opmerkingen |
|----------|--------------------------|------------------------------------|
| Frontend | http://localhost:1349 | Vite-dev-server, proxyt /api |
| Backend | http://localhost:13490 | Fastify-API (bereikbaar via proxy) |
Open http://localhost:1349 in je browser. Meld je aan met `admin` / `admin`. Je wordt gevraagd het wachtwoord te wijzigen bij de eerste aanmelding.
## Projectstructuur {#project-structure}
```
apps/
api/ Fastify backend
web/ Vite + React frontend
docs/ VitePress documentation (this site)
packages/
shared/ Constants, types, i18n strings
image-engine/ Sharp-based image operations
media-engine/ FFmpeg spawn + progress parsing
doc-engine/ qpdf, LibreOffice, ghostscript wrappers
ai/ Python sidecar bridge for ML models
tests/
unit/ Vitest unit tests
integration/ Vitest integration tests (full API)
e2e/ Playwright end-to-end specs
fixtures/ Small test images
```
## Commando's {#commands}
```bash
pnpm dev # start frontend + backend
pnpm build # build all workspaces
pnpm typecheck # TypeScript check across monorepo
pnpm lint # Biome lint + format check
pnpm lint:fix # auto-fix lint + format
pnpm test # unit + integration tests
pnpm test:unit # unit tests only
pnpm test:integration # integration tests only
pnpm test:e2e # Playwright e2e tests
pnpm test:coverage # tests with coverage report
```
## Codeconventies {#code-conventions}
- Dubbele aanhalingstekens, puntkomma's, inspringen met 2 spaties (afgedwongen door Biome)
- ES-modules in alle workspaces
- [Conventional commits](https://www.conventionalcommits.org/) voor semantic-release
- Zod voor alle API-invoervalidatie
- Geen wijzigingen aan Biome-, TypeScript- of editor-configuratiebestanden. Fix de code, niet de linter.
## Database {#database}
PostgreSQL 17 via Drizzle ORM (pg-core). Lokale ontwikkeling vereist dat Postgres en Redis draaien - start ze met:
```bash
docker compose -f docker-compose.dev.yml up -d
```
Dit geeft je Postgres op poort 5432 en Redis op poort 6379. Genereer en pas vervolgens migraties toe:
```bash
cd apps/api
npx drizzle-kit generate # generate a migration from schema changes
npx drizzle-kit migrate # apply pending migrations
```
Het schema is gedefinieerd in `apps/api/src/db/schema.ts`. Tabellen: users, sessions, settings, jobs, apiKeys, pipelines, teams, userFiles, roles, auditLog.
## Een nieuwe tool toevoegen {#adding-a-new-tool}
Elke tool volgt hetzelfde patroon. Hier is een minimaal voorbeeld.
### 1. Backend-route {#_1-backend-route}
Maak `apps/api/src/routes/tools/my-tool.ts`:
```ts
import { z } from "zod";
import type { FastifyInstance } from "fastify";
import { createToolRoute } from "../tool-factory.js";
const settingsSchema = z.object({
intensity: z.number().min(0).max(100).default(50),
});
export function registerMyTool(app: FastifyInstance) {
createToolRoute(app, {
toolId: "my-tool",
settingsSchema,
async process(inputBuffer, settings, filename) {
// Use sharp or other libraries to process the image
const sharp = (await import("sharp")).default;
const result = await sharp(inputBuffer)
// ... your processing logic
.toBuffer();
return {
buffer: result,
filename: filename.replace(/\.[^.]+$/, ".png"),
contentType: "image/png",
};
},
});
}
```
Registreer het vervolgens in `apps/api/src/routes/tools/index.ts`.
### 2. Frontend-instellingencomponent {#_2-frontend-settings-component}
Maak `apps/web/src/components/tools/my-tool-settings.tsx`:
```tsx
import { useState } from "react";
import { useToolProcessor } from "@/hooks/use-tool-processor";
import { useFileStore } from "@/stores/file-store";
export function MyToolSettings() {
const { files } = useFileStore();
const { processFiles, processing, error, downloadUrl } =
useToolProcessor("my-tool");
const [intensity, setIntensity] = useState(50);
const handleProcess = () => {
processFiles(files, { intensity });
};
return (
<div className="space-y-4">
{/* your controls here */}
<button
type="button"
onClick={handleProcess}
disabled={files.length === 0 || processing}
data-testid="my-tool-submit"
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50"
>
Process
</button>
</div>
);
}
```
Registreer het vervolgens in het frontend-toolregister op `apps/web/src/lib/tool-registry.tsx`:
```tsx
// Add the lazy import
const MyToolSettings = lazy(() =>
import("@/components/tools/my-tool-settings").then((m) => ({
default: m.MyToolSettings,
})),
);
// Add to the toolRegistry Map
["my-tool", { displayMode: "before-after", Settings: MyToolSettings }],
```
Weergavemodi: `"side-by-side"`, `"before-after"`, `"live-preview"`, `"no-comparison"`, `"interactive-crop"`, `"interactive-eraser"`, `"no-dropzone"`.
### 3. i18n-vermelding {#_3-i18n-entry}
Voeg toe aan `packages/shared/src/i18n/en.ts`:
```ts
"my-tool": {
name: "My Tool",
description: "Short description of what this tool does",
},
```
### 4. Tests {#_4-tests}
Voeg een `data-testid`-attribuut toe aan je actieknop (zoals hierboven getoond) zodat e2e-tests het betrouwbaar kunnen aanspreken.
## Docker-builds {#docker-builds}
Bouw de volledige productie-image lokaal:
```bash
docker build -f docker/Dockerfile -t snapotter:latest .
```
Gebruik BuildKit-cachemounts voor snellere rebuilds:
```bash
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile -t snapotter:latest .
```
## Omgevingsvariabelen {#environment-variables}
Zie de [Configuratiegids](/nl/guide/configuration) voor de volledige lijst. De belangrijkste voor ontwikkeling:
| Variabele | Standaard | Beschrijving |
|-----------------------------|-----------|------------------------------------------------|
| `AUTH_ENABLED` | `true` | Authenticatie in-/uitschakelen |
| `DEFAULT_USERNAME` | `admin` | Standaard beheerdersgebruikersnaam |
| `DEFAULT_PASSWORD` | `admin` | Standaard beheerderswachtwoord |
| `SKIP_MUST_CHANGE_PASSWORD` | `false` | Geforceerde wachtwoordwijziging overslaan (alleen CI/dev) |
| `RATE_LIMIT_PER_MIN` | `1000` | API-ratelimiet per minuut (0 = uitgeschakeld) |
| `MAX_UPLOAD_SIZE_MB` | `100` | Maximale uploadgrootte in MB (0 = onbeperkt) |
+158
View File
@@ -0,0 +1,158 @@
---
description: "SnapOtter Docker-image-tags, GPU-benchmarks, versievastzetting en multiplatformondersteuning voor AMD64 en ARM64."
i18n_source_hash: 148b3608e11a
i18n_provenance: human
i18n_output_hash: ae5482dbdd3c
---
# Docker-image {#docker-image}
SnapOtter wordt geleverd als één enkele Docker-image. Draai deze op zichzelf en er wordt een ingebedde PostgreSQL 17 en Redis op de loopback-interface gestart (ingebedde modus); voor productie draai je deze naast aparte PostgreSQL 17- en Redis 8-containers met Compose. De app-image werkt op alle platforms.
## Snelstart {#quick-start}
```bash
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
```
Zonder ingestelde `DATABASE_URL` draait dit in ingebedde modus: PostgreSQL en Redis starten binnen de container op loopback, met alle gegevens onder het `SnapOtter-data`-volume. Stel `DATABASE_URL` en `REDIS_URL` in (zoals de [Compose](#docker-compose)-stack doet) om in plaats daarvan externe services te gebruiken. Zie [Configuratie](/nl/guide/configuration#embedded-mode).
## NVIDIA CUDA-versnelling {#nvidia-cuda-acceleration}
De image bevat NVIDIA CUDA-ondersteuning op amd64. Als je een NVIDIA-GPU met de [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) geïnstalleerd hebt, voeg dan `--gpus all` toe:
```bash
docker run -d --name SnapOtter --gpus all -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
```
De image detecteert CUDA automatisch tijdens runtime. Zonder `--gpus all`, of wanneer CUDA niet beschikbaar is, draaien AI-tools op de CPU. Hoe dan ook dezelfde image.
Intel/AMD-iGPU-versnelling via VA-API, Quick Sync of OpenCL wordt momenteel niet ondersteund voor SnapOtter AI-inferentie. Het toewijzen van `/dev/dri` aan de container kan het render-apparaat blootstellen, maar de AI-runtime blijft de CPU gebruiken tenzij CUDA beschikbaar is.
### Benchmarks {#benchmarks}
Getest op een NVIDIA RTX 4070 (12 GB VRAM) met een 572x1024 JPEG-portret.
#### Warme prestaties {#warm-performance}
| Tool | CPU | GPU | Versnelling |
|------|-----|-----|---------|
| Achtergrond verwijderen (u2net) | 2.415ms | 879ms | 2,7x |
| Achtergrond verwijderen (isnet) | 2.457ms | 1.137ms | 2,2x |
| Upscalen 2x | 350ms | 309ms | 1,1x |
| Upscalen 4x | 910ms | 310ms | 2,9x |
| OCR (PaddleOCR) | 137ms | 94ms | 1,5x |
| Gezicht vervagen | 139ms | 122ms | 1,1x |
#### Koude start (eerste verzoek na containerstart) {#cold-start-first-request-after-container-start}
| Tool | CPU | GPU | Versnelling |
|------|-----|-----|---------|
| Achtergrond verwijderen | 22.286ms | 4.792ms | 4,7x |
| Upscalen 2x | 3.957ms | 2.318ms | 1,7x |
| OCR (PaddleOCR) | 1.469ms | 1.090ms | 1,3x |
### CUDA-gezondheidscontrole {#cuda-health-check}
Na het eerste AI-verzoek rapporteert het admin-gezondheidseindpunt de CUDA GPU-status:
```
GET /api/v1/admin/health
{"ai": {"gpu": true}}
```
## Docker Compose {#docker-compose}
De volledige Compose-stack bevat de app, PostgreSQL 17 en Redis 8. Zie [Implementatie](/nl/guide/deployment) voor de volledige `docker-compose.yml`. Een minimaal voorbeeld:
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest
ports:
- "1349:1349"
volumes:
- SnapOtter-data:/data
- SnapOtter-workspace:/tmp/workspace
environment:
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
volumes:
SnapOtter-data:
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
Voeg voor NVIDIA CUDA-versnelling via Docker Compose de deploy-sectie toe aan de SnapOtter-service:
```yaml
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
```
## Versievastzetting {#version-pinning}
| Tag | Beschrijving |
|-----|------------|
| `latest` | Nieuwste release |
| `1.11.0` | Exacte versie |
| `1.11` | Nieuwste patch in 1.11.x |
| `1` | Nieuwste minor in 1.x |
## Platforms {#platforms}
| Architectuur | GPU-ondersteuning | Opmerkingen |
|---|---|---|
| linux/amd64 | NVIDIA CUDA | Volledige CUDA-versnelling voor AI-tools |
| linux/arm64 | Alleen CPU | Raspberry Pi 4/5, Apple Silicon via Docker Desktop |
## Migratie van vorige tags {#migration-from-previous-tags}
Gebruikte je de `:cuda`-tag, schakel dan over naar `:latest` en houd `--gpus all` aan. Dezelfde GPU-ondersteuning, verenigde image.
Je gegevens en instellingen blijven behouden in de volumes.
+176
View File
@@ -0,0 +1,176 @@
---
description: "Installeer SnapOtter met Docker in één commando. Inclusief Docker Compose-installatie, bouwen vanaf broncode en een volledig functieoverzicht."
i18n_source_hash: 4536d4558b8e
i18n_provenance: machine
i18n_output_hash: d29d27e8097b
---
# Aan de slag {#getting-started}
::: tip Probeer voor je installeert
Verken de volledige UI op [demo.snapotter.com](https://demo.snapotter.com) - geen aanmelding of installatie vereist.
:::
## Snelstart {#quick-start}
```bash
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
```
Deze enkele container draait alles wat hij nodig heeft: zonder ingestelde `DATABASE_URL` start hij zijn eigen PostgreSQL en Redis op de loopback-interface (embedded-modus) en houdt alle data in het `SnapOtter-data`-volume. Het is de snelste manier om SnapOtter te proberen of zelf te hosten op een homelab. Draai voor productie de [Docker Compose](#docker-compose)-stack hieronder, die PostgreSQL en Redis in hun eigen containers houdt. De embedded-modus draait als root (de standaard) en schakelt zichzelf automatisch uit zodra je `DATABASE_URL` instelt.
Je wordt bij de eerste login gevraagd je wachtwoord te wijzigen.
::: tip Anonieme Productanalytics
SnapOtter bevat standaard anonieme productanalytics. Om het uit te schakelen, open je **Instellingen → Systeem → Privacy** en zet je **Anonieme Productanalytics** uit. Het stopt onmiddellijk voor de hele instance.
Je kunt ook de omgevingsvariabele `SNAPOTTER_TELEMETRY=0` instellen (`false` en `off` werken ook) om alle telemetrie voor de instance uit te schakelen zonder herbouw.
Foutmonitoring wordt aangedreven door [Sentry](https://sentry.io), dat SnapOtter sponsort via zijn open-source-programma.
Zie [Wat SnapOtter verzamelt](/nl/guide/telemetry) voor details over wat er wordt verzameld.
:::
::: tip NVIDIA CUDA-versnelling
Voeg `--gpus all` toe voor NVIDIA CUDA-versnelde achtergrondverwijdering, upscaling, OCR, gezichtsverbetering en restauratie:
```bash
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest
```
Vereist de [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). Valt automatisch terug op CPU wanneer CUDA niet beschikbaar is. Intel/AMD iGPU-versnelling via VA-API, Quick Sync of OpenCL wordt vandaag niet ondersteund voor AI-inferentie. Zie [Docker Tags](/nl/guide/docker-tags) voor benchmarks.
:::
::: details Ook op GHCR
```bash
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data ghcr.io/snapotter-hq/snapotter:latest
```
Beide registries publiceren bij elke release dezelfde image.
:::
## Docker Compose {#docker-compose}
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
ports:
- "1349:1349"
volumes:
- SnapOtter-data:/data
environment:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=admin
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
volumes:
SnapOtter-data:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
Zie [Configuratie](/nl/guide/configuration) voor alle omgevingsvariabelen.
## Bouwen vanaf broncode {#build-from-source}
**Vereisten:** Node.js 22+, pnpm 9+, Docker (voor Postgres + Redis), Python 3.10+ (voor AI-functies), Git.
```bash
git clone https://github.com/snapotter-hq/SnapOtter.git
cd SnapOtter
docker compose -f docker-compose.dev.yml up -d # start Postgres + Redis
pnpm install
pnpm dev
```
- Frontend: [http://localhost:1349](http://localhost:1349)
- Backend: [http://localhost:13490](http://localhost:13490)
## Wat je kunt doen {#what-you-can-do}
### Bestandsverwerking (200+ tools) {#file-processing-200-tools}
| Modaliteit | Aantal | Voorbeeldtools |
|----------|-------|---------------|
| **Afbeelding** | 105 | Formaat wijzigen, Bijsnijden, Comprimeren, Converteren, Achtergrond verwijderen, Upscale, OCR, Watermerk, Collage, Inkleuren, GIF-tools, formaatpresets |
| **Video** | 57 | Trimmen, Bijsnijden, Comprimeren, Converteren, Samenvoegen, Audio extraheren, Automatische ondertitels, Video naar GIF, Formaat wijzigen, Stabiliseren, formaatpresets |
| **Audio** | 27 | Trimmen, Samenvoegen, Converteren, Normaliseren, Ruisonderdrukking, Transcriberen, Pitch verschuiven, Fade, Beltoonmaker, formaatpresets |
| **PDF / Document** | 42 | Samenvoegen, Splitsen, Comprimeren, OCR, Watermerk, Redigeren, Word naar PDF, Excel naar PDF, Roteren, Beveiligen, Repareren |
| **Bestanden** | 10 | CSV naar JSON, JSON naar XML, CSV's samenvoegen, CSV splitsen, ZIP maken, ZIP uitpakken, Grafiekmaker, YAML/JSON |
### Pijplijnen {#pipelines}
Koppel tools aan elkaar tot workflows met meerdere stappen en pas ze toe op één afbeelding of een hele batch:
1. Open **Pijplijnen** in de zijbalk.
2. Voeg stappen toe (elke tool, alle instellingen).
3. Draai op één bestand - of een hele batch tegelijk.
4. Sla de pijplijn op voor later hergebruik.
Pijplijnen staan standaard 20 stappen toe. Stel `MAX_PIPELINE_STEPS=0` in om de limiet onbeperkt te maken.
### Bestandsbibliotheek {#file-library}
Elk bestand dat je verwerkt, kan worden opgeslagen in je **Bestanden**-bibliotheek. SnapOtter houdt de volledige versiegeschiedenis bij zodat je elke verwerkingsstap kunt traceren van de oorspronkelijke upload tot de uiteindelijke uitvoer.
Opslaan is expliciet: resultaten die je in de bibliotheek opslaat, blijven bewaard tot je ze verwijdert, terwijl resultaten die je verwerkt en niet opslaat automatisch na 72 uur worden gewist (configureerbaar via `FILE_MAX_AGE_HOURS`).
### REST API & API-sleutels {#rest-api-api-keys}
Elke tool is toegankelijk via HTTP:
```bash
curl -X POST http://localhost:1349/api/v1/tools/image/resize \
-H "Authorization: Bearer si_<your-api-key>" \
-F "file=@photo.jpg" \
-F 'settings={"width":800,"height":600,"fit":"cover"}'
```
Genereer API-sleutels onder **Instellingen → API-sleutels**. Zie de [REST API-referentie](/nl/api/rest) voor alle endpoints, of bezoek [http://localhost:1349/api/docs](http://localhost:1349/api/docs) voor de interactieve referentie.
### Meerdere gebruikers & teams {#multi-user-teams}
Schakel meerdere gebruikers in met op rollen gebaseerde toegangscontrole:
- **Beheerder**: volledige toegang - beheer gebruikers, teams, instellingen, alle bestanden/pijplijnen/API-sleutels
- **Gebruiker**: gebruik tools, beheer eigen bestanden/pijplijnen/API-sleutels
Maak teams aan onder **Instellingen → Teams** om gebruikers te groeperen.
Stel `AUTH_ENABLED=true` in (of `false` voor gebruik met één gebruiker/eigen gebruik zonder login).
+170
View File
@@ -0,0 +1,170 @@
---
description: "Stel Single Sign-On in met OpenID Connect. Stapsgewijze handleidingen voor Keycloak, Authentik, Google en andere OIDC-providers."
i18n_source_hash: 4296343b3cc5
i18n_provenance: human
i18n_output_hash: 82d34f4b3c9e
---
# OIDC / Single Sign-On {#oidc-single-sign-on}
SnapOtter ondersteunt OpenID Connect (OIDC) voor single sign-on. Gebruikers kunnen inloggen met een externe identiteitsprovider zoals Keycloak, Authentik of Google in plaats van (of naast) lokale authenticatie met gebruikersnaam/wachtwoord.
::: tip Zie ook
[SAML SSO](/nl/guide/saml) | [SCIM-provisioning](/nl/guide/scim) | [Gebruikers, rollen & rechten](/nl/guide/users-roles)
:::
## Snelstart {#quick-start}
Voeg deze omgevingsvariabelen toe aan je `docker-compose.yml`:
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest
environment:
EXTERNAL_URL: "https://photos.example.com"
OIDC_ENABLED: "true"
OIDC_ISSUER_URL: "https://auth.example.com/realms/myrealm"
OIDC_CLIENT_ID: "snapotter"
OIDC_CLIENT_SECRET: "your-secret-here"
```
De redirect-URI voor je provider is altijd:
```
${EXTERNAL_URL}/api/auth/oidc/callback
```
Als `EXTERNAL_URL` bijvoorbeeld `https://photos.example.com` is, configureer dan de redirect-URI van je provider als `https://photos.example.com/api/auth/oidc/callback`.
## Configuratiereferentie {#configuration-reference}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `OIDC_ENABLED` | `false` | OIDC-login inschakelen. Een knop "Aanmelden met SSO" verschijnt op de aanmeldpagina. |
| `OIDC_ISSUER_URL` | | Issuer-URL van de provider. Moet OIDC Discovery ondersteunen (`/.well-known/openid-configuration`). |
| `OIDC_CLIENT_ID` | | OAuth-client-ID geregistreerd bij je provider. |
| `OIDC_CLIENT_SECRET` | | OAuth-clientgeheim. |
| `OIDC_SCOPES` | `openid profile email` | Door spaties gescheiden lijst van aan te vragen scopes. |
| `OIDC_AUTO_CREATE_USERS` | `true` | Maak automatisch een lokaal gebruikersaccount aan bij de eerste OIDC-login. |
| `OIDC_DEFAULT_ROLE` | `user` | Rol toegewezen aan automatisch aangemaakte OIDC-gebruikers. Een van `admin`, `editor` of `user`. |
| `OIDC_AUTO_LINK_USERS` | `false` | Koppel een OIDC-identiteit aan een bestaande lokale gebruiker als het e-mailadres overeenkomt. |
| `OIDC_PROVIDER_NAME` | | Weergavenaam getoond op de aanmeldknop (bijv. "Keycloak", "Google"). Indien leeg, staat er "SSO" op de knop. |
| `OIDC_CLOCK_TOLERANCE` | `30` | Tolerantie voor klokverschil in seconden voor tokenvalidatie. |
| `OIDC_USERNAME_CLAIM` | `preferred_username` | ID-token-claim die als gebruikersnaam wordt gebruikt voor nieuwe accounts. |
| `EXTERNAL_URL` | | De publieke URL waarop SnapOtter bereikbaar is. Vereist voor OIDC om de juiste redirect-URI op te bouwen. |
| `COOKIE_SECRET` | automatisch gegenereerd | Geheim voor het ondertekenen van sessiecookies. Stel dit expliciet in bij het draaien van meerdere replica's. |
## Providerhandleidingen {#provider-guides}
### Keycloak {#keycloak}
1. Maak een nieuw realm aan (of gebruik een bestaand realm).
2. Ga naar **Clients** en maak een nieuwe client aan:
- **Client ID**: `snapotter`
- **Client authentication**: On (vertrouwelijk)
- **Authentication flow**: Standard flow (Authorization Code)
3. Stel onder het tabblad **Settings** van de client **Valid redirect URIs** in op je callback-URL (bijv. `https://photos.example.com/api/auth/oidc/callback`).
4. Kopieer het **Client secret** van het tabblad **Credentials**.
5. Stel `OIDC_ISSUER_URL` in op `https://keycloak.example.com/realms/your-realm`.
### Authentik {#authentik}
1. Ga in de beheerinterface naar **Applications > Providers** en maak een nieuwe **OAuth2/OpenID Provider** aan.
- **Client type**: Confidential
- **Redirect URIs**: Je callback-URL
- **Signing key**: Selecteer een bestaande sleutel of maak er een aan
2. Maak een **Application** aan en koppel deze aan de provider.
3. Kopieer de **Client ID** en het **Client Secret** uit de providerinstellingen.
4. Stel `OIDC_ISSUER_URL` in op `https://authentik.example.com/application/o/snapotter/` (de afsluitende schuine streep is belangrijk).
### Google {#google}
1. Ga naar de [Google Cloud Console](https://console.cloud.google.com/).
2. Maak een project aan (of selecteer een bestaand project).
3. Ga naar **APIs & Services > OAuth consent screen** en configureer dit.
4. Ga naar **APIs & Services > Credentials** en maak een **OAuth 2.0 Client ID** aan:
- **Application type**: Web application
- **Authorized redirect URIs**: Je callback-URL
5. Kopieer de **Client ID** en het **Client secret**.
6. Stel `OIDC_ISSUER_URL` in op `https://accounts.google.com`.
7. Stel `OIDC_USERNAME_CLAIM` in op `email` (Google levert geen `preferred_username`).
## Gebruikersprovisioning {#user-provisioning}
### Automatisch aanmaken {#auto-create}
Wanneer `OIDC_AUTO_CREATE_USERS` op `true` staat (de standaard), wordt er een lokaal gebruikersaccount aangemaakt wanneer iemand voor het eerst via OIDC inlogt. De gebruikersnaam wordt overgenomen uit de claim opgegeven door `OIDC_USERNAME_CLAIM`, en de rol wordt ingesteld op `OIDC_DEFAULT_ROLE`.
Als er een botsing van gebruikersnamen optreedt, wordt er een numeriek achtervoegsel toegevoegd (bijv. `jane` wordt `jane_2`).
### Automatisch koppelen {#auto-link}
Wanneer `OIDC_AUTO_LINK_USERS` op `true` staat, koppelt SnapOtter een OIDC-identiteit aan een bestaand lokaal account als de e-mailadressen overeenkomen. Dit is handig wanneer je vooraf aangemaakte gebruikersaccounts hebt en wilt dat ze SSO gaan gebruiken zonder hun gegevens te verliezen.
::: warning
Schakel automatisch koppelen alleen in als je je OIDC-provider vertrouwt om e-mailadressen te verifiëren. Een niet-geverifieerd e-mailadres zou iemand in staat kunnen stellen het account van een andere gebruiker over te nemen.
:::
### Lokale login uitschakelen {#disabling-local-login}
OIDC schakelt lokale login met gebruikersnaam/wachtwoord niet uit. Beide methoden blijven beschikbaar. Beheerders kunnen nog steeds inloggen met lokale inloggegevens als de OIDC-provider onbereikbaar is.
## Zelfondertekende certificaten {#self-signed-certificates}
Als je OIDC-provider een zelfondertekend of privé CA-certificaat gebruikt, koppel dan de CA-bundel in de container en verwijs `NODE_EXTRA_CA_CERTS` ernaar:
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest
volumes:
- ./my-ca.pem:/etc/ssl/certs/custom-ca.pem:ro
environment:
NODE_EXTRA_CA_CERTS: /etc/ssl/certs/custom-ca.pem
OIDC_ENABLED: "true"
OIDC_ISSUER_URL: "https://auth.internal.example.com/realms/myrealm"
OIDC_CLIENT_ID: "snapotter"
OIDC_CLIENT_SECRET: "your-secret-here"
```
::: danger
Stel `NODE_TLS_REJECT_UNAUTHORIZED=0` niet in. Dit schakelt alle TLS-verificatie uit en vormt een beveiligingsrisico.
:::
## Problemen oplossen {#troubleshooting}
### Redirect-URI komt niet overeen {#redirect-uri-mismatch}
De meest voorkomende fout. Controleer op deze verschillen tussen wat je provider verwacht en wat SnapOtter verzendt:
- `http` versus `https` - het schema moet exact overeenkomen
- Afsluitende schuine streep - sommige providers zijn hier strikt in
- Poortnummer - neem de poort op als deze niet-standaard is
- Pad - moet `/api/auth/oidc/callback` zijn
Controleer `EXTERNAL_URL` nogmaals. Het moet overeenkomen met de URL die gebruikers in hun browser typen.
### UNABLE_TO_VERIFY_LEAF_SIGNATURE {#unable-to-verify-leaf-signature}
De OIDC-provider gebruikt een certificaat dat Node.js niet vertrouwt. Zie [Zelfondertekende certificaten](#self-signed-certificates) hierboven.
### Fouten door klokverschil {#clock-skew-errors}
Als je serverklok en de klok van de OIDC-provider niet gelijklopen, kan de tokenvalidatie mislukken. Verhoog `OIDC_CLOCK_TOLERANCE` (standaard is 30 seconden). Een betere oplossing is om NTP op beide machines te draaien.
### "OIDC-provider onbereikbaar" {#oidc-provider-unreachable}
SnapOtter haalt het discovery-document van de provider op bij het opstarten en tijdens het inloggen. Controleer:
- DNS-resolutie vanuit de Docker-container (`docker exec snapotter nslookup auth.example.com`)
- Firewallregels tussen de container en de provider
- De `OIDC_ISSUER_URL`-waarde - deze moet bereikbaar zijn vanaf de server, niet alleen vanuit je browser
### Ontbrekende claims {#missing-claims}
Als gebruikersnamen of e-mailadressen leeg zijn na het inloggen, retourneert je provider mogelijk niet de verwachte claims. Verifieer:
- De scopes geconfigureerd in `OIDC_SCOPES` bevatten `profile` en `email`
- De provider is geconfigureerd om de claim opgegeven in `OIDC_USERNAME_CLAIM` in het ID-token op te nemen
- Sommige providers vereisen expliciete mapper-/scope-configuratie om claims vrij te geven
+224
View File
@@ -0,0 +1,224 @@
---
description: "Stel SAML 2.0 Single Sign-On in voor SnapOtter. Stapsgewijze handleidingen voor Okta, Azure AD / Entra ID, Google Workspace en andere SAML-identiteitsproviders."
i18n_source_hash: 33dfb8b02a22
i18n_provenance: human
i18n_output_hash: 046e75acd88e
---
# SAML SSO {#saml-sso}
SnapOtter ondersteunt SAML 2.0 voor single sign-on. Gebruikers kunnen inloggen via een externe identiteitsprovider (Okta, Azure AD / Entra ID, Google Workspace of een standaard SAML 2.0-IdP) in plaats van lokale authenticatie met gebruikersnaam/wachtwoord.
::: tip Enterprise-functie
SAML SSO vereist een **team**- of **enterprise**-licentie met de `saml_sso`-functie. Als `SAML_ENABLED=true` is ingesteld zonder een geldige licentie, worden de SAML-routes stilzwijgend overgeslagen en wordt er een waarschuwing gelogd.
:::
## Vereisten {#prerequisites}
- Een draaiende SnapOtter-instantie bereikbaar op een publieke URL
- `EXTERNAL_URL` ingesteld op die publieke URL (bijv. `https://photos.example.com`)
- Een team- of enterprise-licentiesleutel met de `saml_sso`-functie
- Adminrechten voor je SAML-identiteitsprovider
## Snelstart {#quick-start}
Voeg deze omgevingsvariabelen toe aan je `docker-compose.yml`:
```yaml
services:
snapotter:
image: snapotter/snapotter:latest
environment:
EXTERNAL_URL: "https://photos.example.com"
SNAPOTTER_LICENSE_KEY: "your-license-key"
SAML_ENABLED: "true"
SAML_IDP_SSO_URL: "https://idp.example.com/sso/saml"
SAML_IDP_CERTIFICATE: |
MIICpDCCAYwCCQDU+pQ4pHgSpDANBgkqhkiG9w0BAQsFADAUMRIw
...your IdP's signing certificate in PEM format...
EAYHKoZIzj0CAQYFK4EEACIDYgAE
```
Herstart de container. Een knop "Aanmelden met SAML" (of het label ingesteld door `SAML_PROVIDER_NAME`) verschijnt op de aanmeldpagina.
## Configuratiereferentie {#configuration-reference}
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `SAML_ENABLED` | `false` | SAML-login inschakelen. |
| `SAML_IDP_SSO_URL` | | SSO-eindpunt-URL van de IdP. **Vereist** wanneer SAML is ingeschakeld. |
| `SAML_IDP_CERTIFICATE` | | X.509-ondertekeningscertificaat van de IdP in PEM-formaat (de certificaattekst zelf, niet een bestandspad). **Vereist** wanneer SAML is ingeschakeld. |
| `EXTERNAL_URL` | | De publieke URL waarop SnapOtter bereikbaar is. **Vereist** wanneer SAML is ingeschakeld. |
| `SAML_ENTITY_ID` | `${EXTERNAL_URL}/api/auth/saml/metadata` | SP Entity ID / Audience URI verzonden naar de IdP. |
| `SAML_CALLBACK_URL` | `${EXTERNAL_URL}/api/auth/saml/callback` | Assertion Consumer Service (ACS)-URL. |
| `SAML_AUTO_CREATE_USERS` | `true` | Maak automatisch een lokaal gebruikersaccount aan bij de eerste SAML-login. |
| `SAML_AUTO_LINK_USERS` | `false` | Koppel een SAML-identiteit aan een bestaande lokale gebruiker als het e-mailadres overeenkomt. |
| `SAML_DEFAULT_ROLE` | `user` | Rol toegewezen aan automatisch aangemaakte SAML-gebruikers. Een van `admin`, `editor` of `user`. |
| `SAML_PROVIDER_NAME` | | Weergavelabel voor de SAML-aanmeldknop in de frontend (bijv. "Okta", "Azure AD"). Indien leeg, staat er "SAML" op de knop. |
| `SAML_USERNAME_ATTRIBUTE` | | SAML-assertie-attribuut dat als gebruikersnaam wordt gebruikt. Indien leeg, wordt teruggevallen op het lokale deel van de e-mail, daarna NameID. |
| `SAML_EMAIL_ATTRIBUTE` | `email` | SAML-assertie-attribuut dat als e-mailadres van de gebruiker wordt gebruikt. |
De server weigert te starten als `SAML_ENABLED=true` en een van de drie vereiste variabelen (`SAML_IDP_SSO_URL`, `SAML_IDP_CERTIFICATE`, `EXTERNAL_URL`) ontbreekt.
::: details Beveiligingsnotities
Zowel `wantAuthnResponseSigned` als `wantAssertionsSigned` zijn hardgecodeerd op `true`. SnapOtter wijst niet-ondertekende of onjuist ondertekende SAML-responses af. Asserties van een vertrouwde IdP worden behandeld als e-mailgeverifieerd.
Alleen SP-geïnitieerde login wordt ondersteund. SnapOtter ondersteunt geen IdP-geïnitieerde (ongevraagde) login of Single Logout (SLO). Uitloggen bij SnapOtter logt de gebruiker niet uit bij de IdP.
:::
## SP-metadata en URL's {#sp-metadata-and-urls}
Je IdP heeft drie waarden van SnapOtter nodig:
| Veld | Waarde |
|---|---|
| **ACS-URL** (Assertion Consumer Service) | `${EXTERNAL_URL}/api/auth/saml/callback` |
| **Entity ID** / **Audience URI** | `${EXTERNAL_URL}/api/auth/saml/metadata` |
| **SP-metadata** (XML) | `GET ${EXTERNAL_URL}/api/auth/saml/metadata` |
Als `EXTERNAL_URL` bijvoorbeeld `https://photos.example.com` is:
- ACS-URL: `https://photos.example.com/api/auth/saml/callback`
- Entity ID: `https://photos.example.com/api/auth/saml/metadata`
- Metadata-eindpunt: `https://photos.example.com/api/auth/saml/metadata` (retourneert XML)
Sommige IdP's kunnen de SP-metadata-URL rechtstreeks importeren, waardoor de ACS-URL en Entity ID automatisch worden ingevuld.
## Providerinstellingen {#provider-setup}
### Okta {#okta}
1. Ga in de Okta-adminconsole naar **Applications > Create App Integration**.
2. Selecteer **SAML 2.0** en klik op **Next**.
3. Stel een naam in (bijv. "SnapOtter") en klik op **Next**.
4. Configureer de SAML-instellingen:
- **Single sign-on URL**: Je ACS-URL (bijv. `https://photos.example.com/api/auth/saml/callback`)
- **Audience URI (SP Entity ID)**: Je Entity ID (bijv. `https://photos.example.com/api/auth/saml/metadata`)
- **Name ID format**: EmailAddress
- **Application username**: Email
5. Voeg onder **Attribute Statements** `email` toe, toegewezen aan `user.email`.
6. Klik op **Next**, daarna op **Finish**.
7. Ga naar het tabblad **Sign On**, klik op **View SAML setup instructions** en kopieer:
- **Identity Provider Single Sign-On URL** naar `SAML_IDP_SSO_URL`
- **X.509 Certificate** naar `SAML_IDP_CERTIFICATE`
### Azure AD / Entra ID {#azure-ad-entra-id}
1. Ga in de Azure-portal naar **Microsoft Entra ID > Enterprise applications > New application**.
2. Klik op **Create your own application**, noem het "SnapOtter" en selecteer **Integrate any other application you don't find in the gallery**.
3. Ga naar **Single sign-on > SAML** en klik op **Edit** in de sectie **Basic SAML Configuration**:
- **Identifier (Entity ID)**: Je Entity ID (bijv. `https://photos.example.com/api/auth/saml/metadata`)
- **Reply URL (ACS URL)**: Je ACS-URL (bijv. `https://photos.example.com/api/auth/saml/callback`)
4. Download onder **SAML Certificates** het **Certificate (Base64)**.
5. Kopieer onder **Set up SnapOtter** de **Login URL**.
6. Stel `SAML_IDP_SSO_URL` in op de Login URL en `SAML_IDP_CERTIFICATE` op de inhoud van het gedownloade certificaat.
7. Wijs gebruikers of groepen toe aan de applicatie onder **Users and groups**.
### Google Workspace {#google-workspace}
1. Ga in de Google-adminconsole naar **Apps > Web and mobile apps > Add app > Add custom SAML app**.
2. Noem de app "SnapOtter" en klik op **Continue**.
3. Kopieer op de pagina **Google Identity Provider details** de **SSO URL** en download het **Certificate**. Klik op **Continue**.
4. Configureer de Service Provider-details:
- **ACS URL**: Je ACS-URL (bijv. `https://photos.example.com/api/auth/saml/callback`)
- **Entity ID**: Je Entity ID (bijv. `https://photos.example.com/api/auth/saml/metadata`)
- **Name ID format**: EMAIL
- **Name ID**: Basic Information > Primary email
5. Klik op **Continue**, daarna op **Finish**.
6. Zet de app **ON** voor je organisatie-eenheden.
7. Stel `SAML_IDP_SSO_URL` in op de SSO URL uit stap 3 en `SAML_IDP_CERTIFICATE` op de inhoud van het gedownloade certificaat.
### Generieke SAML 2.0-IdP {#generic-saml-2-0-idp}
Voor elke SAML 2.0-compatibele identiteitsprovider:
1. Maak een nieuwe SAML-applicatie/serviceprovider aan in je IdP.
2. Stel de **ACS-URL** in op `${EXTERNAL_URL}/api/auth/saml/callback`.
3. Stel de **Entity ID** / **Audience** in op `${EXTERNAL_URL}/api/auth/saml/metadata`.
4. Configureer de IdP om het e-mailadres van de gebruiker te verzenden in een attribuut genaamd `email` (of stel `SAML_EMAIL_ATTRIBUTE` in om overeen te komen met de attribuutnaam van je IdP).
5. Kopieer de **IdP SSO URL** en het **ondertekeningscertificaat** naar `SAML_IDP_SSO_URL` en `SAML_IDP_CERTIFICATE`.
## Gebruikersprovisioning {#user-provisioning}
### Automatisch aanmaken {#auto-create}
Wanneer `SAML_AUTO_CREATE_USERS` op `true` staat (de standaard), wordt er een lokaal gebruikersaccount aangemaakt wanneer iemand voor het eerst via SAML inlogt. De rol wordt ingesteld op `SAML_DEFAULT_ROLE`.
De gebruikersnaam wordt afgeleid in deze volgorde:
1. De waarde van het assertie-attribuut opgegeven door `SAML_USERNAME_ATTRIBUTE` (indien ingesteld en aanwezig)
2. Het lokale deel van het e-mailadres (alles vóór `@`)
3. De SAML NameID
Als er een botsing van gebruikersnamen optreedt, wordt er een numeriek achtervoegsel toegevoegd (bijv. `jane` wordt `jane_2`).
### Automatisch koppelen {#auto-link}
Wanneer `SAML_AUTO_LINK_USERS` op `true` staat, koppelt SnapOtter een SAML-identiteit aan een bestaand lokaal account als de e-mailadressen overeenkomen. Dit is handig wanneer je vooraf aangemaakte gebruikersaccounts hebt en wilt dat ze SSO gaan gebruiken zonder hun gegevens te verliezen.
::: warning
Schakel automatisch koppelen alleen in als je je SAML-IdP vertrouwt om e-mailadressen te verifiëren. Een niet-geverifieerd e-mailadres van een verkeerd geconfigureerde IdP zou iemand in staat kunnen stellen het account van een andere gebruiker over te nemen.
:::
### Attribuuttoewijzing {#attribute-mapping}
| SnapOtter-veld | Bron | Configuratie |
|---|---|---|
| E-mail | Assertie-attribuut | `SAML_EMAIL_ATTRIBUTE` (standaard: `email`) |
| Gebruikersnaam | Assertie-attribuut, e-mail of NameID | `SAML_USERNAME_ATTRIBUTE` (zie afleidingsvolgorde hierboven) |
| Externe ID | NameID | Altijd de SAML NameID, niet configureerbaar |
## SSO-afdwinging {#sso-enforcement}
Als je wilt vereisen dat alle gebruikers via SAML (of OIDC) inloggen en lokale wachtwoordlogin wilt blokkeren, schakel dan SSO-afdwinging in:
1. Zorg dat de `sso_enforcement`-enterprisefunctie gelicentieerd is (beschikbaar op team- en enterprise-plannen).
2. Zet in **Admin Settings > Security** de schakelaar **SSO Enforcement** aan.
3. Stel een **break-glass-gebruikersnaam** in: dit is het ene lokale account dat nog steeds met een wachtwoord kan inloggen, voor noodtoegang als de IdP onbereikbaar is.
Wanneer SSO-afdwinging actief is, retourneert elke lokale inlogpoging (behalve voor de break-glass-gebruiker) een 403-fout met de melding "Local password login is disabled. Please use SSO."
::: tip
Configureer altijd een break-glass-gebruikersnaam voordat je SSO-afdwinging inschakelt. Zonder deze kun je buitengesloten raken van SnapOtter als je IdP uitvalt.
:::
## SAML naast OIDC gebruiken {#using-saml-alongside-oidc}
SAML en OIDC kunnen tegelijkertijd worden ingeschakeld. Wanneer beide actief zijn, toont de aanmeldpagina aparte knoppen voor elke provider (gelabeld door `SAML_PROVIDER_NAME` en `OIDC_PROVIDER_NAME`). Gebruikers kunnen met beide methoden inloggen.
Beide providers delen dezelfde instellingen voor automatisch aanmaken, automatisch koppelen en SSO-afdwinging onafhankelijk van elkaar: elk heeft zijn eigen `*_AUTO_CREATE_USERS`-, `*_AUTO_LINK_USERS`- en `*_DEFAULT_ROLE`-variabelen.
## Problemen oplossen {#troubleshooting}
### Assertievalidatie mislukt {#assertion-validation-failed}
De handtekening van de SAML-response of de assertiehandtekening kon niet worden geverifieerd. Controleer:
- Het certificaat in `SAML_IDP_CERTIFICATE` komt overeen met het huidige ondertekeningscertificaat in je IdP (certificaten roteren, dus controleer op vervaldatum)
- Het certificaat is in PEM-formaat (begint met `-----BEGIN CERTIFICATE-----`)
- Het certificaat is de volledige tekst, niet een bestandspad
- De ACS-URL en Entity ID geconfigureerd in je IdP komen exact overeen met de waarden van SnapOtter (schema, host, poort, pad)
### Ontbrekende attributen {#missing-attributes}
Als gebruikersnamen of e-mailadressen leeg zijn na het inloggen, verzendt je IdP mogelijk niet de verwachte attributen. Controleer:
- Je IdP is geconfigureerd om een `email`-attribuut vrij te geven (of waar `SAML_EMAIL_ATTRIBUTE` ook op is ingesteld)
- Verifieer bij gebruik van `SAML_USERNAME_ATTRIBUTE` dat dat attribuut in de assertie is opgenomen
- Sommige IdP's vereisen expliciete configuratie van attribuuttoewijzing voordat ze claims vrijgeven
### Klokverschil {#clock-skew}
SAML-asserties bevatten tijdstempelvoorwaarden (`NotBefore`, `NotOnOrAfter`). Als je serverklok en de klok van de IdP niet gelijklopen, mislukt de assertievalidatie. Draai NTP op beide machines om de klokken uitgelijnd te houden.
### "SAML is enabled via env but saml_sso enterprise feature is not licensed" {#saml-is-enabled-via-env-but-saml-sso-enterprise-feature-is-not-licensed}
Deze waarschuwing verschijnt in de serverlogs wanneer `SAML_ENABLED=true` maar de licentie de `saml_sso`-functie niet bevat. Verifieer je licentiesleutel en plan. De `saml_sso`-functie is beschikbaar op team- en enterprise-plannen.
### Login stuurt terug met fout {#login-redirects-back-with-error}
Als het klikken op de SAML-aanmeldknop je terugstuurt naar de aanmeldpagina met een fout, controleer dan de serverlogs voor details. Veelvoorkomende oorzaken:
- De IdP SSO URL is onbereikbaar vanaf de server
- De IdP heeft het authenticatieverzoek geweigerd (controleer de auditlogs van de IdP)
- De IdP retourneerde een niet-ondertekende response (SnapOtter vereist dat zowel de response als de assertie ondertekend zijn)
+298
View File
@@ -0,0 +1,298 @@
---
description: "Stel SCIM 2.0-provisioning in om gebruikers en groepen vanuit je identity provider naar SnapOtter te synchroniseren. Behandelt Okta, Azure AD / Entra ID en aangepaste integraties."
i18n_source_hash: bbd50119ec12
i18n_provenance: human
i18n_output_hash: 9c1e925bdb7c
---
# SCIM-provisioning {#scim-provisioning}
SnapOtter implementeert SCIM 2.0 (System for Cross-domain Identity Management) voor geautomatiseerde provisioning van gebruikers en groepen. Je identity provider kan gebruikersaccounts automatisch aanmaken, bijwerken, deactiveren en heractiveren, en groepslidmaatschappen synchroniseren.
::: tip Enterprise-functie
SCIM-provisioning vereist een **enterprise**-licentie met de `scim`-functie. Het is niet beschikbaar op het team-plan. Zonder de functie geven alle SCIM-eindpunten (behalve discovery) een 403 terug.
:::
## Vereisten {#prerequisites}
- Een draaiende SnapOtter-instance die bereikbaar is via een publieke URL
- Een enterprise-licentiesleutel met de `scim`-functie
- Beheerderstoegang tot SnapOtter (de `users:manage`-permissie is vereist om een SCIM-token te genereren of in te trekken)
- Beheerderstoegang tot de provisioning-instellingen van je identity provider
## Snelstart {#quick-start}
1. Genereer een SCIM-bearer-token:
```bash
curl -X POST https://photos.example.com/api/v1/enterprise/scim/token \
-H "Cookie: snapotter-session=YOUR_SESSION" \
-H "Content-Type: application/json"
```
Het antwoord bevat het token. Sla het meteen op; het kan niet opnieuw worden opgehaald.
```json
{
"token": "a1b2c3d4e5f6...",
"message": "Save this token - it cannot be retrieved again"
}
```
2. Configureer in je identity provider SCIM-provisioning met:
- **Base URL**: `https://photos.example.com/api/v1/scim/v2`
- **Authenticatie**: Bearer-token (plak het token uit stap 1)
## Authenticatie {#authentication}
SCIM-eindpunten gebruiken een specifiek Bearer-token, los van gebruikerssessies en API-sleutels.
### Een token genereren {#generating-a-token}
`POST /api/v1/enterprise/scim/token` genereert een nieuw SCIM-token. Dit eindpunt vereist een geldige sessie met de `users:manage`-permissie.
Het token wordt precies één keer in platte tekst teruggegeven. SnapOtter slaat alleen een scrypt-hash op. Als je het token kwijtraakt, trek het dan in en genereer een nieuw token.
Er is telkens maar één SCIM-token actief. Een nieuw token genereren vervangt het vorige.
### Een token intrekken {#revoking-a-token}
`DELETE /api/v1/enterprise/scim/token` trekt het huidige SCIM-token in. Dit eindpunt vereist ook `users:manage`.
### Rate limiting {#rate-limiting}
SCIM-eindpunten zijn per token beperkt tot 1000 verzoeken per minuut. Wie deze limiet overschrijdt, krijgt HTTP 429 terug.
## Ondersteunde resources {#supported-resources}
| SCIM-resource | SnapOtter-concept | Aanmaken | Lezen | Bijwerken | Verwijderen |
|---|---|---|---|---|---|
| User | Gebruikersaccount | Ja | Ja | Ja | Soft delete |
| Group | Team | Ja | Ja | Ja | Ja |
::: warning
SCIM-groepen worden gekoppeld aan SnapOtter-**teams**, niet aan rollen. SCIM kan de rol van een gebruiker niet instellen. Alle via SCIM aangemaakte gebruikers krijgen de `user`-rol. Gebruik de SnapOtter-beheerdersinterface om de rol van een gebruiker te wijzigen.
:::
## Gebruikersbewerkingen {#user-operations}
### Gebruiker aanmaken {#create-user}
`POST /api/v1/scim/v2/Users`
Maakt een nieuw gebruikersaccount aan met `authProvider` ingesteld op `scim` en de `user`-rol. De gebruiker wordt toegewezen aan het Default-team. Als `active` `false` is, wordt de rol in plaats daarvan op `disabled` gezet.
Verplichte attributen: `userName`. Optioneel: `externalId`, `emails`, `active` (standaard `true`).
### Gebruikers weergeven en filteren {#list-and-filter-users}
`GET /api/v1/scim/v2/Users`
Geeft een gepagineerde lijst van gebruikers terug. Ondersteunt de queryparameters `startIndex` en `count` (maximaal 200 resultaten per pagina).
Filteren ondersteunt alleen `eq` (gelijk aan), op deze attributen:
- `userName eq "jane"`
- `externalId eq "ext-12345"`
Andere filteroperatoren en attributen geven HTTP 400 terug.
### Gebruiker ophalen {#get-user}
`GET /api/v1/scim/v2/Users/:id`
Geeft één gebruiker terug op basis van hun SnapOtter-gebruikers-ID.
### Gebruiker vervangen {#replace-user}
`PUT /api/v1/scim/v2/Users/:id`
Vervangt de attributen van de gebruiker. Ondersteunt `userName`, `externalId`, `emails` en `active`. Wijzigingen van de gebruikersnaam worden gecontroleerd op conflicten (409 als de nieuwe gebruikersnaam al door een andere gebruiker in gebruik is).
### Gebruiker patchen {#patch-user}
`PATCH /api/v1/scim/v2/Users/:id`
Gedeeltelijke update met SCIM PatchOp. Ondersteunde bewerkingen:
| Bewerking | Paden |
|---|---|
| `replace` | `active`, `userName`, `externalId`, `emails`, `emails[type eq "work"].value`, `name.formatted`, `displayName` |
| `add` | Gelijk aan `replace` |
| `remove` | `externalId`, `emails` |
De paden `name.formatted` en `displayName` worden voor compatibiliteit geaccepteerd maar hebben geen blijvend effect (SnapOtter slaat geen aparte weergavenaam op).
Waardeloze `replace`-bewerkingen (waarbij de waarde een object is zonder een `path`) worden eveneens ondersteund, met de sleutels `userName`, `externalId`, `emails` en `active`.
### Gebruiker deactiveren (soft delete) {#deactivate-user-soft-delete}
`DELETE /api/v1/scim/v2/Users/:id`
SnapOtter verwijdert gebruikers niet definitief via SCIM. In plaats daarvan voert DELETE een zachte deactivatie uit:
1. De rol van de gebruiker wordt gewijzigd van de huidige waarde (bijv. `editor`) naar `disabled:editor`, waarbij de oorspronkelijke rol behouden blijft.
2. Het wachtwoord van de gebruiker wordt gewist.
3. Alle actieve sessies worden ingetrokken.
4. Alle API-sleutels worden ingetrokken.
De gebruiker kan niet meer inloggen of API-sleutels gebruiken. Hun gegevens (bestanden, geschiedenis) blijven behouden.
### Gebruiker heractiveren {#reactivate-user}
Om een eerder gedeactiveerde gebruiker te heractiveren, stuur je een `PUT`- of `PATCH`-verzoek met `active: true`. SnapOtter herstelt de oorspronkelijke rol van vóór de deactivatie (bijv. `disabled:editor` wordt weer `editor`). Als de oorspronkelijke rol niet kan worden bepaald, valt het terug op `user`.
::: details Voorbeeld: deactiveren en heractiveren via PATCH
```json
// Deactivate
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false }
]
}
// Reactivate
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": true }
]
}
```
:::
## Groepsbewerkingen {#group-operations}
SCIM-groepen worden gekoppeld aan SnapOtter-teams. Een groep aanmaken maakt een team aan. Groepslidmaatschap bepaalt tot welk team een gebruiker behoort.
### Groep aanmaken {#create-group}
`POST /api/v1/scim/v2/Groups`
Verplicht: `displayName`. Optioneel: `members` (array van `{ value: userId }`).
### Groepen weergeven en filteren {#list-and-filter-groups}
`GET /api/v1/scim/v2/Groups`
Filteren ondersteunt alleen `displayName eq "..."`. Gepagineerd met `startIndex` en `count` (maximaal 200 resultaten per pagina).
### Groep ophalen {#get-group}
`GET /api/v1/scim/v2/Groups/:id`
### Groep vervangen {#replace-group}
`PUT /api/v1/scim/v2/Groups/:id`
Vervangt de groepsnaam en de volledige ledenlijst. Bestaande leden die niet in de nieuwe lijst staan, worden naar het Default-team verplaatst.
### Groep patchen {#patch-group}
`PATCH /api/v1/scim/v2/Groups/:id`
Ondersteunt deze bewerkingen:
| Bewerking | Pad | Effect |
|---|---|---|
| `add` | `members` | Voegt gebruikers toe aan het team |
| `remove` | `members[value eq "userId"]` | Verplaatst de gebruiker naar het Default-team |
| `replace` | `displayName` | Hernoemt het team |
| `replace` | `members` | Vervangt alle leden (verwijderde leden gaan naar het Default-team) |
### Groep verwijderen {#delete-group}
`DELETE /api/v1/scim/v2/Groups/:id`
Verwijdert het team. Alle leden van het verwijderde team worden naar het Default-team verplaatst. Gebruikers worden niet gedeactiveerd of verwijderd.
## IdP-configuratie {#idp-setup}
### Okta {#okta}
1. Open in de Okta-beheerconsole je SnapOtter-applicatie (of maak er een aan).
2. Ga naar het tabblad **Provisioning** en klik op **Configure API Integration**.
3. Vink **Enable API Integration** aan en voer in:
- **Base URL**: `https://photos.example.com/api/v1/scim/v2`
- **API Token**: Het hierboven gegenereerde SCIM-bearer-token
4. Klik op **Test API Credentials** en vervolgens op **Save**.
5. Schakel onder **Provisioning > To App** in:
- **Create Users**
- **Update User Attributes**
- **Deactivate Users**
6. Configureer onder **Push Groups** welke Okta-groepen als SnapOtter-teams gesynchroniseerd moeten worden.
### Azure AD / Entra ID {#azure-ad-entra-id}
1. Ga in de Azure-portal naar je SnapOtter-enterprise-applicatie.
2. Ga naar **Provisioning** en stel **Provisioning Mode** in op **Automatic**.
3. Voer onder **Admin Credentials** in:
- **Tenant URL**: `https://photos.example.com/api/v1/scim/v2`
- **Secret Token**: Het hierboven gegenereerde SCIM-bearer-token
4. Klik op **Test Connection** en vervolgens op **Save**.
5. Configureer onder **Mappings** de attribuutmappings voor gebruikers en groepen. De standaardinstellingen werken meestal, maar controleer of `userName` naar wens aan `userPrincipalName` of `mail` wordt gekoppeld.
6. Stel **Provisioning Status** in op **On** en sla op.
Azure provisioneert gebruikers en groepen op een vaste synchronisatiecyclus (doorgaans elke 40 minuten).
## Discovery-eindpunten {#discovery-endpoints}
Deze drie eindpunten zijn zonder authenticatie beschikbaar en beschrijven de mogelijkheden van de SCIM-server:
| Eindpunt | Beschrijving |
|---|---|
| `GET /api/v1/scim/v2/ServiceProviderConfig` | Servermogelijkheden en ondersteunde functies |
| `GET /api/v1/scim/v2/Schemas` | Schema-definities voor User en Group |
| `GET /api/v1/scim/v2/ResourceTypes` | Beschikbare resourcetypes (User, Group) |
De `ServiceProviderConfig` adverteert deze mogelijkheden:
| Functie | Ondersteund |
|---|---|
| Patch | Ja |
| Bulk | Nee |
| Filter | Ja (max. 200 resultaten, alleen de `eq`-operator) |
| Change password | Nee |
| Sort | Nee |
| ETag | Nee |
## Beperkingen {#limitations}
- **Filteren**: Alleen de `eq`-operator wordt ondersteund. Complexe filters, de operatoren `and`/`or`, `co` (bevat) en `sw` (begint met) zijn niet geïmplementeerd.
- **Bulkbewerkingen**: Niet ondersteund.
- **Sort en ETag**: Niet ondersteund.
- **Rollen**: SCIM kan geen SnapOtter-rollen toewijzen. Alle geprovisioneerde gebruikers krijgen de `user`-rol.
- **MAX_USERS**: De limiet van de omgevingsvariabele `MAX_USERS` wordt niet afgedwongen bij het aanmaken van SCIM-gebruikers. Als je het aantal gebruikers wilt beperken, beheer de toewijzingen dan in je IdP.
- **Eén token**: Er kan telkens maar één SCIM-token actief zijn. Als meerdere IdP's SCIM-toegang nodig hebben, moeten ze het token delen.
- **Groepen zijn teams**: SCIM-groepen komen overeen met teams, niet met rollen of permissiegroepen.
## Probleemoplossing {#troubleshooting}
### 403 "SCIM provisioning requires an enterprise license with the scim feature" {#_403-scim-provisioning-requires-an-enterprise-license-with-the-scim-feature}
Je licentie bevat de `scim`-functie niet, of er is geen licentie geconfigureerd. SCIM vereist een enterprise-plan-licentie. Controleer of `SNAPOTTER_LICENSE_KEY` is ingesteld en of de licentie de `scim`-functie bevat.
### 401 "Bearer token required" {#_401-bearer-token-required}
Het SCIM-verzoek bevatte geen `Authorization: Bearer <token>`-header. Controleer de provisioning-configuratie van je IdP.
### 401 "Invalid token" {#_401-invalid-token}
Het token komt niet overeen met de opgeslagen hash. Dit gebeurt als het token is ingetrokken en opnieuw gegenereerd. Werk het token bij in de provisioning-instellingen van je IdP.
### 401 "SCIM not configured" {#_401-scim-not-configured}
Er is nog geen SCIM-token gegenereerd. Gebruik het `POST /api/v1/enterprise/scim/token`-eindpunt om er een aan te maken.
### 409 "User already exists" / "userName already taken" {#_409-user-already-exists-username-already-taken}
Er bestaat al een gebruiker met dezelfde gebruikersnaam. Dit kan gebeuren wanneer een IdP een mislukte aanmaak opnieuw probeert. Controleer op dubbele gebruikersnamen in het SnapOtter-beheerdersvenster.
### 429 "SCIM rate limit exceeded" {#_429-scim-rate-limit-exceeded}
De IdP verstuurt meer dan 1000 verzoeken per minuut. Dit gebeurt doorgaans tijdens een grote eerste synchronisatie. De meeste IdP's proberen het automatisch opnieuw nadat het rate-limit-venster is gereset. Als het probleem aanhoudt, controleer dan het synchronisatie-interval van de provisioning van je IdP.
### Gebruikers gedeprovisioneerd maar niet uit de UI verwijderd {#users-deprovisioned-but-not-removed-from-the-ui}
SCIM DELETE is een zachte deactivatie. Gedeactiveerde gebruikers verschijnen nog steeds in de beheerdersgebruikerslijst met een uitgeschakelde status. Dit is opzettelijk zo, zodat hun gegevens behouden blijven. Hun rol wordt weergegeven als `disabled:<original-role>`.
+339
View File
@@ -0,0 +1,339 @@
---
description: "Handleiding voor beveiligingsverharding van SnapOtter. Containerbeveiliging, netwerkisolatie, Docker-secrets, Kubernetes-implementatie en compliance-artefacten."
i18n_source_hash: 986f7658430c
i18n_provenance: machine
i18n_output_hash: 2131ba905ef5
---
# Beveiliging & verharding {#security-hardening}
SnapOtter verwerkt bestanden volledig op je eigen infrastructuur. Het verstuurt standaard anonieme, inhoudsloze productanalytics en crashrapporten om het project te helpen verbeteren. Het verstuurt nooit je bestanden, bestandsnamen, bestandsinhoud, OCR-uitvoer, afbeeldingsmetadata of documenttekst. Optionele feedback wordt alleen verzonden nadat een gebruiker deze indient, alleen wanneer analytics is ingeschakeld, en contactvelden worden alleen opgenomen met expliciete contacttoestemming. Een beheerder kan analytics en het vastleggen van feedback met één klik uitschakelen onder Instellingen > Systeem > Privacy, geen herbouw vereist. Bestandsverwerking blijft altijd binnen je container.
De container draait als een dedicated niet-root-gebruiker (`snapotter`) met alle Linux-capabilities verwijderd behalve de minimaal vereiste set. Zie voor het volledige beleid voor kwetsbaarheidsonthulling en de beveiligingsarchitectuur [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) op GitHub.
## Containerverharding {#container-hardening}
De [standaard docker-compose.yml](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose.yml) bevat productiebeveiligingsverharding. Hier is een uitsplitsing van elke optie en waarom deze belangrijk is:
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest
ports:
# Bind to localhost only for internet-facing deployments:
- "127.0.0.1:1349:1349"
volumes:
- SnapOtter-data:/data
- SnapOtter-workspace:/tmp/workspace
environment:
- AUTH_ENABLED=true
- DEFAULT_PASSWORD=change-me-immediately
- RATE_LIMIT_PER_MIN=1000
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# --- Resource limits ---
mem_limit: 6g # Prevents runaway memory from crashing the host
memswap_limit: 6g # No swap - fail fast instead of degrading the host
cpus: 4 # Cap CPU usage to 4 cores
pids_limit: 512 # Prevents fork bombs
# --- Capability restrictions ---
cap_drop:
- ALL # Drop ALL Linux capabilities first
cap_add:
- CHOWN # Needed for volume permission setup
- SETUID # Needed for gosu privilege drop (root -> snapotter)
- SETGID # Needed for gosu privilege drop
- DAC_OVERRIDE # Needed for volume permission setup
- FOWNER # Needed for volume permission setup
# --- Logging ---
logging:
driver: json-file
options:
max-size: "50m" # Rotate logs at 50 MB
max-file: "5" # Keep 5 rotated log files
# --- Health check ---
healthcheck:
test: ["CMD", "curl", "-sf", "--max-time", "5", "http://localhost:1349/api/v1/health"]
interval: 30s
timeout: 5s
start_period: 60s
retries: 3
shm_size: "2gb" # Required for Python ML shared memory
restart: unless-stopped
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
volumes:
SnapOtter-data:
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
### Waarom `no-new-privileges` niet is ingesteld {#why-no-new-privileges-is-not-set}
`security_opt: [no-new-privileges:true]` is bewust weggelaten. De entrypoint start als root om het volume-eigenaarschap te herstellen en zakt dan via [gosu](https://github.com/tianon/gosu), dat setuid vereist, naar de `snapotter`-gebruiker. Zodra de privilegeverlaging is voltooid, draait het proces als `snapotter` met alle capabilities behalve de vijf hierboven genoemde verwijderd.
Als je Kubernetes of Dockers `--user`-vlag gebruikt om rechtstreeks als niet-root te draaien (met omzeiling van gosu), is `no-new-privileges` veilig om in te schakelen.
### Waarom `read_only` niet is ingesteld {#why-read-only-is-not-set}
`read_only: true` is niet ingesteld omdat PUID/PGID-remapping bij het opstarten naar `/etc/passwd` en `/etc/group` schrijft. Als je Dockers `--user`-vlag of Kubernetes `runAsUser` gebruikt in plaats van PUID/PGID, kun je veilig een alleen-lezen root-bestandssysteem inschakelen.
## Netwerkisolatie {#network-isolation}
Tijdens normaal gebruik maakt de container **nul uitgaande netwerkverbindingen**. Alle bestandsverwerking gebeurt lokaal met gebundelde bibliotheken.
```
Browser --> Reverse Proxy (TLS) --> SnapOtter container --> (nothing)
```
De enige uitzondering is **AI-modeldownloads**: wanneer een gebruiker een AI-featurebundel via de UI installeert, downloadt de container het vooraf gebouwde bundelarchief van Hugging Face, plus enkele individuele modelbestanden van GitHub Releases, Google Storage en PyPI. Deze downloads gebeuren één keer per bundel en worden opgeslagen in het `/data`-volume.
**Firewallaanbevelingen:**
| Scenario | Uitgaande regel |
|---|---|
| Air-gapped (geen AI) | Blokkeer al het uitgaande verkeer van de container |
| AI-bundels nodig | Sta HTTPS toe naar `huggingface.co`, `*.xethub.hf.co`, `cdn-lfs.huggingface.co`, `github.com`, `objects.githubusercontent.com`, `storage.googleapis.com`, `pypi.org`, `files.pythonhosted.org` tijdens de installatie, blokkeer daarna |
| Na AI-installatie | Blokkeer al het uitgaande verkeer - modellen worden lokaal gecachet |
Bundelarchieven worden geserveerd vanaf Hugging Faces Xet-opslag, die parallel over de `*.xethub.hf.co`-endpoints overdraagt en wat multi-GB-bundeldownloads snel maakt. Als je firewall `huggingface.co` toestaat maar `*.xethub.hf.co` blokkeert, slagen installaties nog steeds maar vallen ze terug op een tragere single-stream-download, dus zet de Xet-hosts op de allowlist om op het snelle pad te blijven. Volledig offline installaties kunnen dit alles overslaan en in plaats daarvan [Offline Bundelimport](/nl/guide/deployment) gebruiken.
Zie voor de configuratie van de reverse proxy (Nginx, Traefik, Caddy, Cloudflare Tunnels) de [Implementatiehandleiding](/nl/guide/deployment#reverse-proxy).
## Docker-secrets {#docker-secrets}
Vermijd bij productie-implementaties het doorgeven van secrets als platte-tekst-omgevingsvariabelen. De entrypoint ondersteunt Dockers `_FILE`-conventie: koppel een secret als bestand en stel de bijbehorende `_FILE`-variabele in op het pad ervan.
**Ondersteunde secrets:**
| Variabele | `_FILE`-equivalent |
|---|---|
| `DEFAULT_PASSWORD` | `DEFAULT_PASSWORD_FILE` |
| `COOKIE_SECRET` | `COOKIE_SECRET_FILE` |
| `OIDC_CLIENT_SECRET` | `OIDC_CLIENT_SECRET_FILE` |
| `S3_ACCESS_KEY_ID` | `S3_ACCESS_KEY_ID_FILE` |
| `S3_SECRET_ACCESS_KEY` | `S3_SECRET_ACCESS_KEY_FILE` |
| `SNAPOTTER_LICENSE_KEY` | `SNAPOTTER_LICENSE_KEY_FILE` |
**Voorbeeld met Docker Compose-secrets:**
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest
environment:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD_FILE=/run/secrets/snapotter_password
- COOKIE_SECRET_FILE=/run/secrets/cookie_secret
secrets:
- snapotter_password
- cookie_secret
secrets:
snapotter_password:
file: ./secrets/snapotter_password.txt
cookie_secret:
file: ./secrets/cookie_secret.txt
```
::: tip
Docker Compose-secrets (zonder Swarm) vereisen Compose v2.23 of later.
:::
## Kubernetes-implementatie {#kubernetes-deployment}
De entrypoint detecteert wanneer de container al als niet-root draait (bijv. via Kubernetes `runAsUser`) en slaat de gosu-privilegeverlaging automatisch over. In dat geval kan het de gekoppelde volumes niet zelf chown'en, dus verifieert het of ze beschrijfbaar zijn en stopt het vroegtijdig met bruikbare aanwijzingen als dat niet zo is — zie [Opslagpermissies](/nl/guide/deployment#storage-permissions) voor `fsGroup` en foreign-UID-configuraties (TrueNAS, OpenShift).
**Aanbevolen Pod SecurityContext:**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: snapotter
spec:
replicas: 1
selector:
matchLabels:
app: snapotter
template:
metadata:
labels:
app: snapotter
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
containers:
- name: snapotter
image: snapotter/snapotter:latest
ports:
- containerPort: 1349
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "4"
memory: 6Gi
livenessProbe:
httpGet:
path: /api/v1/health
port: 1349
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /api/v1/health
port: 1349
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
volumeMounts:
- name: data
mountPath: /data
- name: workspace
mountPath: /tmp/workspace
volumes:
- name: data
persistentVolumeClaim:
claimName: snapotter-data
- name: workspace
emptyDir:
medium: Memory
sizeLimit: 2Gi
```
Omdat `runAsUser: 999` op podniveau is ingesteld, slaat de entrypoint gosu volledig over. Dit maakt `allowPrivilegeEscalation: false`- en `drop: [ALL]`-capabilities zonder conflict mogelijk.
Zie voor de dimensionering van resources [Hardwarevereisten](/nl/guide/deployment#hardware-requirements).
## Back-up en herstel {#backup-and-recovery}
Persistente staat is verdeeld over twee volumes:
| Volume | Inhoud | Kritiek? |
|---|---|---|
| `SnapOtter-pgdata` | PostgreSQL-database (gebruikers, instellingen, pijplijnen, jobs, auditlog) | Ja |
| `/data` (app-volume) | Door gebruikers geüploade bestanden, AI-modellen, Python-venv | Gedeeltelijk (zie hieronder) |
Binnen het `/data`-volume:
| Pad | Inhoud | Kritiek? |
|---|---|---|
| `/data/uploads/`, `/data/outputs/` | Gebruikersbestanden en verwerkingsresultaten | Ja |
| `/data/ai/` | Gedownloade AI-modelbestanden | Nee (opnieuw te downloaden) |
| `/data/venv/` | Python virtual environment | Nee (opnieuw gebouwd bij start) |
### Databaseback-up {#database-backup}
Gebruik `pg_dump` om de database te back-uppen terwijl de stack draait:
```bash
# Dump the database
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
# Restore into a fresh database
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
```
Stop anders de stack en maak een snapshot van het `SnapOtter-pgdata`-volume:
```bash
docker compose down
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
```
### Back-up van gebruikersbestanden {#user-files-backup}
```bash
# Snapshot the app data volume (excluding re-downloadable AI models)
docker run --rm -v SnapOtter-data:/data -v $(pwd)/backup:/backup \
alpine tar czf /backup/snapotter-files.tar.gz \
--exclude='ai' --exclude='venv' -C /data .
```
AI-modellen tellen op tot ongeveer 24 GB over alle bundels. Aangezien ze opnieuw te downloaden zijn, sluit je `/data/ai/` en `/data/venv/` uit van back-ups om ruimte te besparen. Alleen de database en gebruikersbestanden zijn kritiek.
## Compliance-artefacten {#compliance-artifacts}
Elke SnapOtter-release bevat de volgende beveiligingsartefacten:
| Artefact | Formaat | Waar te vinden |
|---|---|---|
| SBOM (CycloneDX) | JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases)-asset: `snapotter-v{version}-sbom.cdx.json` |
| SBOM (SPDX) | JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases)-asset: `snapotter-v{version}-sbom.spdx.json` |
| Kwetsbaarheidsscan | Trivy JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases)-asset: `snapotter-v{version}-trivy.json` |
| Kwetsbaarheidsscan | SARIF | [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security)-tabblad |
| Statische analyse | CodeQL (JS/TS + Python) | [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security)-tabblad, draait wekelijks + per PR |
| Dependency review | GitHub-native | Controle per PR, faalt op toevoegingen met hoge ernst |
| Python-dependency-audit | pip-audit | CI-runlog bij elke push |
| Beveiligingsbeleid | Markdown | [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) in de repository |
| Dependency-updates | Dependabot | Geautomatiseerde wekelijkse PR's voor npm, pip, Docker, Actions |
**Je eigen scan uitvoeren:**
Download de SBOM van de release en scan deze met je voorkeurstool:
```bash
# Scan with Grype using the CycloneDX SBOM
grype sbom:snapotter-v1.17.2-sbom.cdx.json
# Scan with Trivy using the SPDX SBOM
trivy sbom snapotter-v1.17.2-sbom.spdx.json
# Scan the Docker image directly
trivy image snapotter/snapotter:1.17.2
```
::: info
De SBOM en kwetsbaarheidsscan weerspiegelen de exacte image die voor die release is gepubliceerd. AI-modelbundels die na implementatie zijn geïnstalleerd, zijn niet in de SBOM opgenomen omdat ze tijdens runtime worden gedownload.
:::
+239
View File
@@ -0,0 +1,239 @@
---
description: "Ondersteunde bestandsformaten over alle modaliteiten - 55+ afbeeldingsinvoerformaten, video, audio, PDF en bestandsformaten."
i18n_source_hash: e53ecf65be25
i18n_provenance: human
i18n_output_hash: 791254c201df
---
# Ondersteunde formaten {#supported-formats}
SnapOtter verwerkt bestanden over vijf modaliteiten: afbeelding, video, audio, PDF en bestanden. Deze pagina somt alle ondersteunde formaten op.
## Afbeeldingsformaten {#image-formats}
SnapOtter ondersteunt 55+ afbeeldingsformaten voor invoer en 13 formaten voor uitvoer.
## Invoerformaten {#input-formats}
### Webstandaarden (9) {#web-standards-9}
| Formaat | Extensies | Decoder | Opmerkingen |
|--------|-----------|---------|-------|
| JPEG | .jpg, .jpeg | Sharp (native) | |
| PNG | .png | Sharp (native) | Eerste frame van APNG geëxtraheerd |
| WebP | .webp | Sharp (native) | |
| GIF | .gif | Sharp (native) | Geanimeerd ondersteund |
| AVIF | .avif | Sharp (native) | |
| SVG | .svg | Sharp (librsvg) | Gesaneerd tegen XXE/SSRF |
| SVGZ | .svgz | gunzip + Sharp | Bescherming tegen gzip-bombs |
| APNG | .apng | Sharp (native) | Alleen eerste frame |
| JPEG XL | .jxl | djxl / ImageMagick | Fallback in twee niveaus |
### Professioneel (7) {#professional-7}
| Formaat | Extensies | Decoder | Opmerkingen |
|--------|-----------|---------|-------|
| TIFF | .tiff, .tif | Sharp (native) | Meerdere pagina's ondersteund |
| PSD | .psd | ImageMagick | Afgeplatte composiet |
| EPS | .eps, .epsf | ImageMagick + Ghostscript | Rasterisatie op 300 dpi, beveiligingsgehard |
| OpenEXR | .exr | ImageMagick | Lineair-naar-sRGB-conversie |
| Radiance HDR | .hdr | ImageMagick | Lineair-naar-sRGB-conversie |
| DPX | .dpx | ImageMagick | Log-naar-sRGB-conversie |
| Cineon | .cin | ImageMagick | Film/VFX-formaat |
### Camera-RAW (23) {#camera-raw-23}
| Formaat | Extensies | Cameramerk | Decoder |
|--------|-----------|-------------|---------|
| DNG | .dng | Adobe (universeel) | exiftool / ImageMagick + LibRaw |
| CR2 | .cr2 | Canon (vóór 2018) | exiftool / ImageMagick + LibRaw |
| CR3 | .cr3 | Canon (2018+) | exiftool / ImageMagick + LibRaw |
| NEF | .nef | Nikon | exiftool / ImageMagick + LibRaw |
| NRW | .nrw | Nikon (Coolpix) | exiftool / ImageMagick + LibRaw |
| ARW | .arw | Sony | exiftool / ImageMagick + LibRaw |
| ORF | .orf | Olympus | exiftool / ImageMagick + LibRaw |
| RW2 | .rw2 | Panasonic | exiftool / ImageMagick + LibRaw |
| RAF | .raf | Fujifilm | exiftool / ImageMagick + LibRaw |
| PEF | .pef | Pentax/Ricoh | exiftool / ImageMagick + LibRaw |
| 3FR | .3fr | Hasselblad | exiftool / ImageMagick + LibRaw |
| IIQ | .iiq | Phase One | exiftool / ImageMagick + LibRaw |
| SRW | .srw | Samsung | exiftool / ImageMagick + LibRaw |
| X3F | .x3f | Sigma | exiftool / ImageMagick + LibRaw |
| RWL | .rwl | Leica | exiftool / ImageMagick + LibRaw |
| GPR | .gpr | GoPro | exiftool / ImageMagick + LibRaw |
| FFF | .fff | Hasselblad (legacy) | exiftool / ImageMagick + LibRaw |
| MRW | .mrw | Minolta | exiftool / ImageMagick + LibRaw |
| MEF | .mef | Mamiya | exiftool / ImageMagick + LibRaw |
| KDC | .kdc | Kodak | exiftool / ImageMagick + LibRaw |
| DCR | .dcr | Kodak | exiftool / ImageMagick + LibRaw |
| ERF | .erf | Epson | exiftool / ImageMagick + LibRaw |
| PTX | .ptx | Pentax (compact) | exiftool / ImageMagick + LibRaw |
### Moderne formaten (3) {#modern-formats-3}
| Formaat | Extensies | Decoder | Opmerkingen |
|--------|-----------|---------|-------|
| JPEG 2000 | .jp2, .j2k, .j2c, .jpc, .jpf, .jpx | opj_decompress / ImageMagick | Digital cinema, medische beeldvorming |
| QOI | .qoi | Inline TypeScript-codec | Game-ontwikkeling, embedded systemen |
| HEIC/HEIF | .heic, .heif | heif-convert / heif-dec | iPhone-foto's |
### Legacy/systeem (4) {#legacy-system-4}
| Formaat | Extensies | Decoder | Opmerkingen |
|--------|-----------|---------|-------|
| BMP | .bmp | ImageMagick | |
| ICO | .ico | ImageMagick | Grootste laag geëxtraheerd |
| CUR | .cur | ImageMagick | Windows-cursor (ICO-variant) |
| TGA | .tga | ImageMagick | Detectie alleen op extensie |
### Wetenschappelijk en gaming (2) {#scientific-and-gaming-2}
| Formaat | Extensies | Decoder | Opmerkingen |
|--------|-----------|---------|-------|
| FITS | .fits, .fit, .fts | ImageMagick | Astronomie (NASA-standaard) |
| DDS | .dds | ImageMagick | Game-textures (DirectX) |
### Interchange (6) {#interchange-6}
| Formaat | Extensies | Decoder | Opmerkingen |
|--------|-----------|---------|-------|
| PPM | .ppm | Sharp (native) | Kleuren-pixmap |
| PGM | .pgm | Sharp (native) | Grijswaarden |
| PBM | .pbm | Sharp (native) | 1-bits bitmap |
| PNM | .pnm | Sharp (native) | Overkoepelend formaat |
| PAM | .pam | Sharp (native) | Willekeurige map |
| PFM | .pfm | Sharp (native) | Float-map |
## Uitvoerformaten (13) {#output-formats-13}
| Formaat | Encoder | Kwaliteitsregeling | Beschikbaar in |
|--------|---------|----------------|-------------|
| JPEG | Sharp native | 1-100 | Alle tools |
| PNG | Sharp native | Compressie 0-9 | Alle tools |
| WebP | Sharp native | 1-100 | Alle tools |
| AVIF | Sharp native | 1-100 | Alle tools |
| TIFF | Sharp native | 1-100 | Volledige conversietools |
| GIF | Sharp native | 1-100 | Volledige conversietools |
| JXL | Sharp native | 1-100 | Alle tools |
| HEIC | heif-enc CLI | 1-100 | Volledige conversietools |
| HEIF | heif-enc CLI | 1-100 | Volledige conversietools |
| BMP | ImageMagick CLI | Lossless | Convert-tool |
| ICO | ImageMagick CLI | Lossless | Convert-tool |
| JP2 | opj_compress CLI | Compressieverhouding | Convert-tool |
| QOI | Inline codec | Lossless | Convert-tool |
## Videoformaten {#video-formats}
Videodecodering en -codering worden afgehandeld door FFmpeg (statische build), dus elke gangbare container en codec wordt bij de invoer ondersteund.
### Invoercontainers (15) {#input-containers-15}
| Formaat | Extensies | Typische codecs | Opmerkingen |
|--------|-----------|----------------|-------|
| MP4 | .mp4 | H.264, H.265, AV1 | Meest gebruikte container |
| QuickTime | .mov | H.264, ProRes | Apple opname/bewerking |
| WebM | .webm | VP8, VP9, AV1 | Royaltyvrij webformaat |
| Matroska | .mkv | Elke | Flexibele open container |
| AVI | .avi | Diverse | Legacy Microsoft-container |
| M4V | .m4v | H.264 | Apple MP4-variant |
| AVCHD | .mts | H.264 | Camcorder-opnamen |
| BDAV | .m2ts | H.264 | Blu-ray / AVCHD-transportstroom |
| 3GP | .3gp | H.264, MPEG-4 | Mobiele opname |
| Flash Video | .flv | H.264, VP6 | Legacy streaming |
| Windows Media | .wmv | VC-1, WMV | Windows Media |
| MPEG | .mpg, .mpeg | MPEG-1, MPEG-2 | Video uit het dvd-tijdperk |
| MPEG-TS | .ts | MPEG-2, H.264 | Broadcast-transportstroom |
| Ogg | .ogv | Theora | Open Ogg-video |
### Uitvoerformaten {#output-formats}
| Formaat | Extensie | Videocodec | Geproduceerd door |
|--------|-----------|-------------|-------------|
| MP4 | .mp4 | H.264 | Convert, compress en de meeste videotools |
| QuickTime | .mov | H.264 | Convert Video |
| WebM | .webm | VP9 | Convert Video |
| GIF | .gif | - | Video naar GIF |
| WebP | .webp | - | Video naar WebP (geanimeerd) |
### Ondertitels {#subtitles}
| Formaat | Extensie | Bewerkingen |
|--------|-----------|-----------|
| SubRip | .srt | Embedden, inbranden, extraheren, automatisch genereren |
| WebVTT | .vtt | Embedden, inbranden, extraheren, automatisch genereren |
| ASS / SSA | .ass | Embedden, inbranden (ondersteunt styling) |
## Audioformaten {#audio-formats}
Audio wordt eveneens verwerkt door FFmpeg.
### Invoerformaten (11) {#input-formats-11}
| Formaat | Extensies | Compressie | Opmerkingen |
|--------|-----------|-------------|-------|
| MP3 | .mp3 | Lossy | Universele compatibiliteit |
| WAV | .wav | Ongecomprimeerd (PCM) | Studio / bewerking |
| FLAC | .flac | Lossless | Open lossless-codec |
| AAC | .aac | Lossy | Ruwe AAC-stream |
| M4A | .m4a | Lossy (AAC) / Lossless (ALAC) | MPEG-4-audio |
| Ogg Vorbis | .ogg | Lossy | Open formaat |
| Opus | .opus | Lossy | Modern, lage latency |
| WMA | .wma | Lossy | Windows Media Audio |
| AIFF | .aiff | Ongecomprimeerd (PCM) | Apple ongecomprimeerd |
| AMR | .amr | Lossy | Spraak / mobiel |
| AC-3 | .ac3 | Lossy | Dolby Digital |
### Uitvoerformaten {#output-formats-1}
| Formaat | Extensie | Codec | Geproduceerd door |
|--------|-----------|-------|-------------|
| MP3 | .mp3 | LAME | Convert Audio, Extract Audio |
| WAV | .wav | PCM | Convert Audio, Extract Audio |
| FLAC | .flac | FLAC (lossless) | Convert Audio |
| Ogg | .ogg | Vorbis | Convert Audio |
| M4A | .m4a | AAC | Convert Audio, Extract Audio |
## Documentformaten {#document-formats}
Documentverwerking gebruikt qpdf, LibreOffice, Ghostscript, Pandoc en WeasyPrint.
### Invoerformaten (15) {#input-formats-15}
| Formaat | Extensies | Engine | Opmerkingen |
|--------|-----------|--------|-------|
| PDF | .pdf | qpdf, Ghostscript, pdfcpu | Kern-documentformaat |
| Word | .docx, .doc | LibreOffice | Microsoft Word |
| Excel | .xlsx, .xls | LibreOffice | Microsoft Excel |
| PowerPoint | .pptx, .ppt | LibreOffice | Microsoft PowerPoint |
| OpenDocument | .odt, .ods, .odp | LibreOffice | Tekst, spreadsheet, presentatie |
| Rich Text | .rtf | LibreOffice | Rich text tussen apps |
| Platte tekst | .txt | LibreOffice, Pandoc | UTF-8-tekst |
| Markdown | .md | Pandoc | CommonMark / GFM |
| HTML | .html | WeasyPrint | Naar PDF gerenderd |
| EPUB | .epub | Pandoc, LibreOffice | E-boekformaat |
### Uitvoerformaten {#output-formats-2}
| Formaat | Extensies | Geproduceerd door |
|--------|-----------|-------------|
| PDF | .pdf | Word/Excel/PowerPoint naar PDF, Markdown naar PDF, HTML naar PDF |
| PDF/A | .pdf | PDF/A Convert (archivering) |
| Word | .docx, .odt, .rtf, .txt | Convert Document, PDF naar Word, Markdown naar Word |
| Presentatie | .pptx, .odp | Convert Presentation |
| Spreadsheet | .xlsx, .ods, .csv | Convert Spreadsheet |
| HTML | .html | Markdown naar HTML |
| EPUB | .epub | Convert to EPUB |
| Afbeeldingen | .png, .jpg | PDF naar Image |
## Bestandsformaten {#file-formats}
Data- en archieftools converteren tussen gestructureerde formaten en bundelen bestanden.
| Formaat | Extensies | Conversies |
|--------|-----------|-------------|
| CSV | .csv | Van/naar JSON en Excel; splitsen en samenvoegen; vanuit XML |
| JSON | .json | Van/naar CSV, XML en YAML |
| XML | .xml | Van/naar JSON; naar CSV |
| YAML | .yaml, .yml | Van/naar JSON |
| Excel | .xlsx | Van/naar CSV |
| ZIP | .zip | Archieven maken, inhoud extraheren |
+31
View File
@@ -0,0 +1,31 @@
---
description: "Welke anonieme gebruiksgegevens SnapOtter verzamelt, wanneer die worden verstuurd, en hoe je instance-brede productanalytics uitschakelt."
i18n_source_hash: 5d72dedaeb23
i18n_provenance: human
i18n_output_hash: b5dad7c63a7d
---
# Wat SnapOtter verzamelt {#what-snapotter-collects}
Anonieme productanalytics staat standaard aan en wordt door een beheerder voor de hele instance ingesteld. Schakel het uit onder Settings > System > Privacy.
## Events die we versturen (indien ingeschakeld) {#events-we-send-when-enabled}
- tool_used: tool-id, status, duur, categorie, of het een AI-tool is, een foutcode bij een mislukking.
- pipeline_executed: aantal stappen, tool-id's, batch-flag, aantal bestanden, duur, status.
- ai_bundle_action: bundle-id, actie, duur.
- Frontend-gebruik: welke tool-pagina's geopend worden, toegevoegde bestanden (alleen aantallen), gestarte tool, downloads, opslagacties, zoekopdrachten (alleen aantal resultaten), verwerkte batches.
- Crashrapporten: fouttype en een broncall-stack met alleen basisbestandsnamen.
## Wat we nooit verzamelen {#what-we-never-collect}
- Bestandsnamen of paden
- Bestandsinhoud
- OCR-uitvoertekst
- Afbeeldingsmetadata (EXIF)
- Geëxtraheerde documenttekst
- Je IP-adres of accountidentiteit
## Uitschakelen {#turning-it-off}
Beheerders: Settings > System > Privacy, zet "Anonymous Product Analytics" uit. Het stopt onmiddellijk, instance-breed. Om een image te bouwen die nooit iets kan versturen, stel je de build-arg `SNAPOTTER_ANALYTICS=off` in.
+234
View File
@@ -0,0 +1,234 @@
---
description: "21 ondersteunde talen en hoe je vertalingen voor SnapOtter maakt of verbetert met het door TypeScript afgedwongen i18n-systeem."
i18n_source_hash: 55837d9fdaef
i18n_provenance: human
i18n_output_hash: d9f7dd1568a9
---
# Vertaalgids {#translation-guide}
SnapOtter wordt standaard geleverd met 21 talen. Het i18n-systeem gebruikt een lichtgewicht eigen runtime met door TypeScript afgedwongen volledigheid van locales en dynamische code-splitting.
## Ondersteunde talen {#supported-languages}
| Code | Taal | Native Name | Direction |
|------|----------|-------------|-----------|
| `en` | Engels | English | LTR |
| `zh-CN` | Chinees (vereenvoudigd) | 简体中文 | LTR |
| `zh-TW` | Chinees (traditioneel) | 繁體中文 | LTR |
| `ja` | Japans | 日本語 | LTR |
| `ko` | Koreaans | 한국어 | LTR |
| `es` | Spaans | Español | LTR |
| `fr` | Frans | Français | LTR |
| `it` | Italiaans | Italiano | LTR |
| `pt-BR` | Portugees (Brazilië) | Português (Brasil) | LTR |
| `de` | Duits | Deutsch | LTR |
| `nl` | Nederlands | Nederlands | LTR |
| `sv` | Zweeds | Svenska | LTR |
| `ru` | Russisch | Русский | LTR |
| `pl` | Pools | Polski | LTR |
| `uk` | Oekraïens | Українська | LTR |
| `ar` | Arabisch | العربية | RTL |
| `tr` | Turks | Türkçe | LTR |
| `hi` | Hindi | हिन्दी | LTR |
| `vi` | Vietnamees | Tiếng Việt | LTR |
| `id` | Indonesisch | Bahasa Indonesia | LTR |
| `th` | Thai | ไทย | LTR |
## Hoe taaldetectie werkt {#how-language-detection-works}
SnapOtter gebruikt een resolutievolgorde met drie niveaus:
1. **Gebruikersvoorkeur** - opgeslagen in `localStorage("snapotter-locale")` en gesynchroniseerd met de gebruikersinstellingen bij authenticatie
2. **Automatische browserdetectie** - loopt door de `navigator.languages`-array met BCP 47-prefixmatching
3. **Standaard van de instantie** - de `DEFAULT_LOCALE` env-variabele van de beheerder (opgehaald uit `GET /api/v1/config/locale`)
4. **Engelse terugval** - altijd beschikbaar
Gebruikers kunnen de taal wijzigen via:
- De **Globe-selector in de footer** (desktop, altijd zichtbaar)
- De taalselector op de **loginpagina** (vóór authenticatie)
- De sectie **Instellingen > Algemeen** (voorkeur per gebruiker)
- De taalkeuzelijst in de **mobiele zijbalk**
- De sectie **Instellingen > Systeem** stelt de standaardtaal voor de hele instantie in (alleen beheerder)
## Hoe vertalingen werken {#how-translations-work}
Alle UI-strings staan in `packages/shared/src/i18n/`. Het referentiebestand is `en.ts`, dat een getypeerd object exporteert met elke string die de app gebruikt (~1500 sleutels). Andere talen zijn aparte bestanden (bijv. `de.ts`, `fr.ts`) die dezelfde vorm exporteren.
Het type `TranslationKeys` gebruikt `DeepStringRecord` om elke stringwaarde te accepteren terwijl de sleutelstructuur wordt afgedwongen. TypeScript vangt ontbrekende sleutels in elk vertaalbestand op tijdens het compileren.
Alleen de actieve locale wordt tijdens runtime geladen via een dynamische `import()`, zodat de hoofdbundel klein blijft.
## Vertalingen gebruiken in componenten {#using-translations-in-components}
```tsx
import { useTranslation } from "@/contexts/i18n-context";
import { format, plural } from "@/lib/format";
function MyComponent() {
const { t, locale, setLocale } = useTranslation();
return (
<div>
<h1>{t.common.settings}</h1>
<p>{format(t.settings.people.deleteConfirm, { username: "admin" })}</p>
<p>{plural(count, t.automate.fileCount, t.automate.fileCountPlural)}</p>
</div>
);
}
```
## Een vertaling bijdragen {#contributing-a-translation}
We verwelkomen vertaal-PR's rechtstreeks. Je kunt een bestaande locale verbeteren of een nieuwe toevoegen.
Om een verkeerde vertaling te melden zonder code in te dienen, open je een [GitHub Issue](https://github.com/snapotter-hq/SnapOtter/issues) met de taal, de onjuiste string en de voorgestelde correctie.
::: tip
Vertaal-PR's vereisen geen voorafgaande goedkeuring. Fork de repo, breng je wijzigingen aan en open een PR. Zie de [Contributing Guide](/nl/guide/contributing) voor het volledige PR-proces en de CLA-vereiste.
:::
## Een vertaling maken of bijwerken {#how-to-create-or-update-a-translation}
### 1. Fork en clone {#_1-fork-and-clone}
```bash
git clone https://github.com/<your-username>/snapotter.git
cd snapotter
pnpm install
```
### 2. Kopieer het referentiebestand (alleen nieuwe taal) {#_2-copy-the-reference-file-new-language-only}
Sla deze stap over als je een bestaande vertaling verbetert.
```bash
cp packages/shared/src/i18n/en.ts packages/shared/src/i18n/XX.ts
```
### 3. Vertaal de strings {#_3-translate-the-strings}
Open je nieuwe bestand en vertaal elke stringwaarde. Houd de objectstructuur en sleutels exact hetzelfde.
```ts
import type { TranslationKeys } from "./en.js";
export const xx: TranslationKeys = {
common: {
upload: "Your translation here",
// ... translate all entries
},
// ... translate all sections
} as const;
```
Regels:
- Vertaal geen objectsleutels, alleen stringwaarden
- Houd `as const` aan het einde
- Importeer `TranslationKeys` uit `./en.js` en typeer je export
- Houd `{variable}`-placeholders exact zoals ze zijn
- Arrays (`rotatingPhrases`, `progressMessages`) moeten hetzelfde aantal items hebben
- Vertaal niet: SnapOtter, JPEG, PNG, WebP, EXIF, API en andere technische termen
### 4. Registreer de locale (alleen nieuwe taal) {#_4-register-the-locale-new-language-only}
Voeg je locale toe aan `SUPPORTED_LOCALES` in `packages/shared/src/i18n/index.ts`:
```ts
{ code: "xx", name: "Language Name", nativeName: "Native Name", dir: "ltr" },
```
### 5. Verifieer {#_5-verify}
```bash
pnpm typecheck # catches missing or mistyped keys
pnpm lint # formatting check
pnpm dev # manually verify strings appear correctly
```
### 6. Dien in {#_6-submit}
Open een PR tegen `main` met een titel zoals `feat(i18n): add Swedish translation` of `fix(i18n): correct German typos`. De CLA-bot vraagt je om te ondertekenen bij je eerste bijdrage.
## Nieuwe vertaalsleutels toevoegen {#adding-new-translation-keys}
Wanneer je een nieuwe functie toevoegt die nieuwe UI-strings nodig heeft:
1. Voeg de nieuwe sleutels eerst toe aan `en.ts` (het referentiebestand)
2. Voer `pnpm typecheck` uit - elk localebestand faalt als de nieuwe sleutel ontbreekt
3. Voeg de nieuwe sleutel toe aan alle localebestanden (gebruik Engels als tijdelijke terugval)
## Configuratie {#configuration}
Stel de standaardtaal van de instantie in via een omgevingsvariabele:
```yaml
DEFAULT_LOCALE: "de" # German as the default for all new users
```
## Bestandsreferentie {#file-reference}
| File | Purpose |
|------|---------|
| `packages/shared/src/i18n/en.ts` | Engelse strings (referentielocale, ~1500 sleutels) |
| `packages/shared/src/i18n/index.ts` | `SUPPORTED_LOCALES`, `loadTranslations()`, type-exports |
| `packages/shared/src/i18n/<locale>.ts` | Vertaalbestanden per taal |
| `apps/web/src/contexts/i18n-context.tsx` | `I18nProvider`, `useTranslation()` hook |
| `apps/web/src/lib/format.ts` | `format()`, `plural()`, `formatFileSize()` helpers |
| `apps/api/src/routes/config.ts` | `GET /api/v1/config/locale` openbaar endpoint |
## De website, docs en API-referentie vertalen {#translating-the-web-surfaces}
De ondersteuning voor 21 talen hierboven geldt voor de **app**. De openbare website
(snapotter.com), deze documentatiesite en de REST API-referentie worden ook
in alle 21 talen vertaald, door een aparte hash-gated pipeline die dezelfde
toolnamen en beschrijvingen uit `packages/shared/src/i18n` hergebruikt, zodat
de terminologie overal consistent blijft.
### Standaard machinaal vertaald {#machine-translated-by-default}
Elke niet-Engelse pagina op de website en in de docs wordt in de eerste ronde
**machinaal vertaald** (door een Claude Code-sessie, niet door een externe dienst) en
draagt een kleine, wegklikbare banner die dat aangeeft, met een link terug naar hier. Dat is bewust:
het levert alle 21 talen snel en eerlijk, en nodigt de community vervolgens uit om
de belangrijkste pagina's te verfijnen. Machinevertaling brengt de betekenis over;
menselijke revisie zorgt dat het natuurlijk leest.
### Hoe de pipeline beslist wat er wordt vertaald {#how-the-web-pipeline-decides}
Elke vertaalbare eenheid Engelse bron wordt gehasht, en de hash wordt naast
de vertaling opgeslagen. Bij elke run doet de pipeline het volgende:
- vertaalt elke eenheid die nog geen vertaling heeft,
- slaat elke eenheid over waarvan de opgeslagen hash nog overeenkomt met de Engelse bron,
- hervertaalt een **machine**-eenheid wanneer de Engelse bron ervan verandert,
- en markeert een door een **mens** verfijnde eenheid als `stale` (moet worden gecontroleerd) wanneer de
Engelse bron verandert, in plaats van je werk te overschrijven.
### Een webvertaling verfijnen via een PR {#refining-a-web-translation-by-pr}
Je verbetert een vertaling van de website, docs of API-referentie op dezelfde manier als
je een app-locale verbetert: door het gegenereerde bestand te bewerken en een PR te openen.
1. Vind de gegenereerde vertaling voor jouw taal:
- UI-strings van de website: `apps/landing/src/i18n/<locale>.json`
- een docs-pagina: `apps/docs/<locale>/**.md`
- de API-referentie: `apps/api/src/openapi.<locale>.yaml`
2. Bewerk de tekst. Houd code, links, `{placeholders}` en eventuele `⸤I18N…⸥`-markeringen
exact zoals ze zijn; de validator van de pipeline weigert een vertaling die ze weglaat
of herordent.
3. Open een PR. Het bewerken van een eenheid wijzigt de herkomst van `machine` naar `human`, zodat
de pipeline die **nooit zal overschrijven** bij een latere run. Als de Engelse bron
daarna verandert, wordt je eenheid gemarkeerd als `stale` voor revisie in plaats van
stilzwijgend vervangen.
Om een verkeerde vertaling te melden zonder code in te dienen, open je een
[GitHub Issue](https://github.com/snapotter-hq/SnapOtter/issues) met de pagina-
URL, de taal, de onjuiste tekst en je voorgestelde correctie.
::: tip
Beheerders draaien de vertaalpipeline; je hebt geen API-sleutel nodig om
bij te dragen. Bewerk gewoon het gegenereerde bestand en open een PR. Zie
[`scripts/i18n/README.md`](https://github.com/snapotter-hq/SnapOtter/blob/main/scripts/i18n/README.md)
voor hoe de pipeline draait.
:::
+117
View File
@@ -0,0 +1,117 @@
---
i18n_source_hash: 9a6abf3fc8ae
i18n_provenance: human
i18n_output_hash: 37eca3f2f461
---
# Upgraden van 1.x naar 2.0 {#upgrading-from-1-x-to-2-0}
SnapOtter 1.x sloeg alles op in één enkel SQLite-bestand en draaide als één container. SnapOtter 2.0 gebruikt PostgreSQL en Redis. Deze gids leidt je door het verplaatsen van een 1.x-installatie naar 2.0 zonder gegevensverlies.
De korte versie: hergebruik je bestaande `/data`-volume, en 2.0 importeert je 1.x-database automatisch bij de eerste boot. Je gebruikers, opgeslagen bestanden, instellingen, API-sleutels en pipelines gaan mee. De oude database wordt nooit gewijzigd, zodat je altijd kunt terugrollen.
::: tip Een opmerking voor onze 1.x-gebruikers
Velen van jullie vertrouwen SnapOtter sinds dag één, en jullie feedback heeft deze release vormgegeven. 2.0 verandert veel onder de motorkap, en deze gids bestaat zodat de overstap je niets kost wat je belangrijk vindt. Je accounts, bestanden, instellingen, API-sleutels en pipelines gaan mee, en je oude database wordt nooit aangeraakt. Bedankt dat je met ons upgradet.
:::
## Voordat je begint: maak een back-up van het hele `/data`-volume {#before-you-start-back-up-the-whole-data-volume}
Doe dit eerst, elke keer. Maak een back-up van het **hele** `/data`-volume, niet alleen van het `snapotter.db`-bestand.
Dit is waarom het belangrijk is. 1.x draait SQLite in WAL-modus, dus een gestopte 1.x-container laat routinematig het grootste deel van zijn vastgelegde gegevens achter in `snapotter.db-wal` naast een bijna-lege `snapotter.db`. Als je alleen `snapotter.db` kopieert, leg je een lege database vast en verlies je stilzwijgend alles. Het volume draagt `snapotter.db`, `snapotter.db-wal`, `snapotter.db-shm` en je `files/`-directory samen, en ze moeten als één geheel meereizen.
```bash
# Adjust the volume name to match yours (see "Check your volume name" below).
docker run --rm -v SnapOtter-data:/data -v "$PWD":/backup \
alpine tar czf /backup/snapotter-1x-data.tgz -C /data .
```
## Upgrade eerst naar 1.17.2 {#upgrade-to-1-17-2-first}
Upgrade je 1.x-installatie naar de nieuwste 1.x-release (1.17.2) voordat je naar 2.0 gaat. Zo kan 1.x zijn eigen laatste schemamigraties uitvoeren, zodat 2.0 importeert vanuit een bekend, volledig schema. Rechtstreeks upgraden van een ouder 1.x naar 2.0 wordt niet ondersteund.
## Controleer je volumenaam {#check-your-volume-name}
De importer ziet je gegevens alleen als de 2.0-stack hetzelfde volume mount dat je 1.x-installatie gebruikte. Docker-volumenamen zijn hoofdlettergevoelig, en oudere README-fragmenten gebruikten een kleine letter `snapotter-data` terwijl de Compose-bestanden `SnapOtter-data` gebruiken. Bevestig welke je hebt:
```bash
docker volume ls | grep -i snapotter
```
Gebruik precies die naam in je 2.0-configuratie.
## Pad A: enkele container (snelst) {#path-a-single-container-quickest}
Als je SnapOtter met een enkele `docker run` draait, blijf dat dan doen. 2.0 boot een ingebedde PostgreSQL en Redis binnen de container wanneer je `DATABASE_URL` of `REDIS_URL` niet instelt, en het detecteert en importeert `/data/snapotter.db` automatisch bij de eerste boot.
```bash
docker run -d --name snapotter -p 1349:1349 \
-v SnapOtter-data:/data \
snapotter/snapotter:latest
```
Houd de logs in de gaten voor een regel als:
```
Imported 1.x SQLite database: {"tables":{"users":2,"teams":1,...},"blobs":{"present":1,"missing":0}}
```
Dat is het. Meld je aan met je bestaande inloggegevens.
## Pad B: Compose (aanbevolen voor productie) {#path-b-compose-recommended-for-production}
De 2.0-Compose-stack draait drie services (app, Postgres, Redis). Hergebruik je 1.x `/data`-volume voor de app-service. De app detecteert `/data/snapotter.db` automatisch en importeert het bij de eerste boot in Postgres.
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest
volumes:
- SnapOtter-data:/data # your existing 1.x volume
- SnapOtter-workspace:/tmp/workspace
environment:
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://:snapotter@redis:6379
# ...
```
Als je liever expliciet naar de oude database wijst, stel dan `SQLITE_MIGRATE_PATH=/data/snapotter.db` in. Een expliciet pad wint altijd van automatische detectie.
## Bekijk de import eerst als voorbeeld (optioneel) {#preview-the-import-first-optional}
Om precies te zien wat er zou worden geïmporteerd zonder iets te schrijven, voer je een dry run uit tegen je databasebestand:
```bash
pnpm --filter @snapotter/api migrate:sqlite -- /path/to/snapotter.db --dry-run
```
Het drukt de rijaantallen per tabel af, hoeveel opgeslagen bibliotheekbestanden het op schijf heeft gevonden, en eventuele taakstatussen die het zal normaliseren. Er is geen draaiende Postgres voor nodig.
## Wat gaat mee, en wat niet {#what-carries-over-and-what-does-not}
Gaat mee:
- Gebruikers, en de mogelijkheid om in te loggen. Wachtwoord-hashes zijn ongewijzigd, dus dezelfde gebruikersnaam en hetzelfde wachtwoord werken.
- Teams, instellingen (inclusief je instantie-identiteit), rollen, API-sleutels (die blijven werken) en opgeslagen pipelines.
- Records van de taakgeschiedenis.
- Je bibliotheek met opgeslagen bestanden, zowel de records als de daadwerkelijke bestanden, omdat `/data/files` op het volume behouden blijft.
Gaat niet mee:
- Aanmeldsessies. Iedereen meldt zich één keer aan na de upgrade. Inloggegevens zijn ongewijzigd, dus het is één keer opnieuw inloggen, meer niet.
- De invoer- en uitvoerbestanden van oude verwerkingstaken. Die stonden in een tijdelijke werkruimte en zijn opzettelijk verdwenen. De records van de taakgeschiedenis blijven.
- Analytics-toestemmingsvlaggen per gebruiker uit 1.x, die geen 2.0-equivalent hebben (2.0-analytics is een instelling op instantieniveau).
## De import uitschakelen {#turning-the-import-off}
Als je bewust een verse database wilt hoewel er een `snapotter.db` op het volume aanwezig is, stel dan `SQLITE_MIGRATE_PATH=off` in.
## Als je al gegevens in de 2.0-instantie hebt {#if-you-already-have-data-in-the-2-0-instance}
De importer draait alleen in een lege database. Als je 2.0 vers hebt gestart (gegevens aangemaakt) en later een oude `snapotter.db` hebt gemount, detecteert 2.0 die wel maar importeert die niet, omdat het samenvoegen van twee datasets kan botsen op ID's. Je ziet een waarschuwing in de logs. Om de 1.x-gegevens te importeren heb je een lege instantie nodig:
- Als de 2.0-instantie alleen de standaardbeheerder bevat (je hebt hem niet echt gebruikt), stop dan de stack, verwijder het Postgres-volume (`SnapOtter-pgdata`) en boot opnieuw met de oude `/data` aanwezig. Het importeert dan schoon. Dit wist alleen de wegwerpbare Postgres-gegevens, niet je 1.x-database.
- Als de 2.0-instantie echte gegevens bevat die je wilt behouden, kunnen de twee datasets niet automatisch worden samengevoegd. Exporteer wat je nodig hebt en importeer de 1.x-gegevens in een aparte, verse deployment.
## Terugrollen {#rolling-back}
De upgrade wijzigt of verwijdert je 1.x `snapotter.db` nooit. Als je terug moet naar 1.x, deploy dan de 1.x-image opnieuw tegen hetzelfde volume. Alles wat je na de upgrade in 2.0 hebt aangemaakt, staat in Postgres en zou niet in de 1.x-database staan, dus rol snel terug als je dat gaat doen.
+264
View File
@@ -0,0 +1,264 @@
---
description: "Beheer gebruikers, ingebouwde en aangepaste rollen, permissies, API-sleutels, teams, sessies en het auditlogboek in SnapOtter."
i18n_source_hash: 5e28af686c96
i18n_provenance: human
i18n_output_hash: ddd4d1d21c1b
---
# Gebruikers, rollen en permissies {#users-roles-permissions}
SnapOtter wordt geleverd met drie ingebouwde rollen, 17 granulaire permissies en ondersteuning voor aangepaste rollen met optionele toegangscontrole per tool. Deze pagina behandelt het volledige autorisatiemodel, scoping van API-sleutels, teambeheer en auditlogging.
::: tip Gerelateerde pagina's
[OIDC / SSO](/nl/guide/oidc) | [SAML SSO](/nl/guide/saml) | [SCIM-provisioning](/nl/guide/scim) | [Beveiliging en hardening](/nl/guide/security)
:::
## Gebruikers {#users}
### Gebruikers aanmaken {#creating-users}
Beheerders kunnen gebruikers aanmaken via het beheerderspaneel of het `POST /api/auth/register`-endpoint. Elke gebruiker heeft een gebruikersnaam, rol, teamtoewijzing en een optioneel e-mailadres.
### Standaardbeheerder {#default-admin}
Bij de eerste start maakt SnapOtter een standaardbeheerdersaccount aan. De inloggegevens komen uit omgevingsvariabelen:
| Variabele | Standaard | Beschrijving |
|---|---|---|
| `DEFAULT_USERNAME` | `admin` | Gebruikersnaam voor het initiële beheerdersaccount |
| `DEFAULT_PASSWORD` | `admin` | Wachtwoord voor het initiële beheerdersaccount |
De standaardbeheerder moet zijn wachtwoord wijzigen bij de eerste aanmelding.
### Authenticatieproviders {#authentication-providers}
Gebruikers kunnen zich authenticeren via verschillende methoden:
- **Lokaal** - gebruikersnaam en wachtwoord opgeslagen in de SnapOtter-database
- **OIDC** - elke OpenID Connect-provider (zie [OIDC / SSO](/nl/guide/oidc))
- **SAML** - SAML 2.0 identity providers (zie [SAML SSO](/nl/guide/saml))
- **SCIM** - geautomatiseerde provisioning vanuit een identity provider (zie [SCIM-provisioning](/nl/guide/scim))
### Authenticatie uitschakelen {#disabling-authentication}
Stel `AUTH_ENABLED=false` in om authenticatie volledig uit te schakelen. In deze modus wordt een synthetische anonieme gebruiker met de rol `admin` gebruikt voor alle verzoeken. Er is geen aanmelding vereist.
::: warning
Het uitschakelen van authenticatie geeft volledige beheerderstoegang aan iedereen die de instantie kan bereiken. Gebruik dit alleen in vertrouwde omgevingen.
:::
## Ingebouwde rollen {#built-in-roles}
SnapOtter bevat drie ingebouwde rollen. Ze kunnen niet worden gewijzigd of verwijderd.
### Admin {#admin}
Alle 17 permissies. Volledige controle over de instantie.
`tools:use` `files:own` `files:all` `apikeys:own` `apikeys:all` `pipelines:own` `pipelines:all` `settings:read` `settings:write` `users:manage` `teams:manage` `features:manage` `system:health` `audit:read` `compliance:manage` `webhooks:manage` `security:manage`
### Editor {#editor}
7 permissies. Kan alle tools gebruiken en alle bestanden en pipelines beheren, maar heeft geen toegang tot beheerdersfuncties.
`tools:use` `files:own` `files:all` `apikeys:own` `pipelines:own` `pipelines:all` `settings:read`
### User {#user}
5 permissies. Kan tools gebruiken en eigen resources beheren.
`tools:use` `files:own` `apikeys:own` `pipelines:own` `settings:read`
## Permissiereferentie {#permissions-reference}
| Permissie | Beschrijving |
|---|---|
| `tools:use` | Elke verwerkingstool gebruiken |
| `files:own` | Eigen bestanden bekijken en beheren |
| `files:all` | Bestanden van alle gebruikers bekijken en beheren |
| `apikeys:own` | Eigen API-sleutels aanmaken en beheren |
| `apikeys:all` | API-sleutels van alle gebruikers bekijken |
| `pipelines:own` | Eigen pipelines aanmaken en beheren |
| `pipelines:all` | Pipelines van alle gebruikers bekijken en beheren |
| `settings:read` | Instantie-instellingen bekijken |
| `settings:write` | Instantie-instellingen wijzigen |
| `users:manage` | Gebruikersaccounts aanmaken, bijwerken en verwijderen |
| `teams:manage` | Teams aanmaken, bijwerken en verwijderen |
| `features:manage` | AI-featurebundels installeren en beheren |
| `system:health` | Toegang tot health- en readiness-endpoints |
| `audit:read` | Het auditlogboek bekijken en rollen weergeven |
| `compliance:manage` | GDPR-lifecycle en compliancefuncties beheren |
| `webhooks:manage` | Uitgaande webhooks configureren |
| `security:manage` | Beveiligingsinstellingen beheren (IP-allowlist, SSO-afdwinging) |
## Aangepaste rollen {#custom-roles}
Beheerders met de permissie `security:manage` kunnen aangepaste rollen aanmaken via het beheerderspaneel of de rollen-API. Voor het weergeven van rollen is `audit:read` vereist.
### Een aangepaste rol aanmaken {#creating-a-custom-role}
```bash
curl -X POST http://localhost:1349/api/v1/roles \
-H "Authorization: Bearer si_..." \
-H "Content-Type: application/json" \
-d '{
"name": "reviewer",
"description": "Can use tools and view all files",
"permissions": ["tools:use", "files:own", "files:all", "settings:read"]
}'
```
Rolnamen moeten 2-30 tekens zijn, kleine letters, alfanumeriek met koppeltekens en underscores.
### Voor beheerders gereserveerde permissies {#admin-reserved-permissions}
Drie permissies zijn gereserveerd voor ingebouwde rollen en kunnen niet aan aangepaste rollen worden toegewezen:
- `compliance:manage`
- `webhooks:manage`
- `security:manage`
De rollen-API weigert elk verzoek dat deze permissies bevat. Alleen de ingebouwde rol `admin` heeft er toegang toe.
### Permissies op toolniveau {#tool-level-permissions}
Aangepaste rollen kunnen optioneel beperken tot welke tools gebruikers toegang hebben. Twee modi zijn beschikbaar:
| Modus | Gedrag | Licentievereiste |
|---|---|---|
| `category` | Beperken per modaliteit (image, video, audio, document, file) | Geen (gratis) |
| `tool` | Beperken per individuele tool-ID | Vereist de enterprise-feature `per_tool_permissions` |
Wanneer de modus `tool` is ingesteld maar de enterprise-feature niet beschikbaar is, degradeert SnapOtter netjes en staat het toegang tot alle tools toe.
```json
{
"name": "image-only",
"permissions": ["tools:use", "files:own"],
"toolPermissions": {
"mode": "category",
"allowed": ["image"]
}
}
```
### Een aangepaste rol verwijderen {#deleting-a-custom-role}
Wanneer een aangepaste rol wordt verwijderd, worden alle daaraan toegewezen gebruikers automatisch opnieuw toegewezen aan de rol `user`.
## Teams {#teams}
Teams groeperen gebruikers voor opslag- en bewaarbeheer. Een `Default`-team wordt aangemaakt bij de eerste start.
| Veld | Type | Beschrijving |
|---|---|---|
| `name` | string | Unieke teamnaam (1-50 tekens) |
| `storageQuota` | number | Opslaglimiet per team in bytes (werkt zonder enterprise) |
| `retentionHours` | number | Uitvoer automatisch verwijderen na dit aantal uren (vereist `team_retention_overrides`, enterprise) |
| `legalHold` | boolean | Automatische verwijdering van bestanden van teamleden voorkomen (vereist `legal_hold`, enterprise) |
::: info
Het `Default`-team kan niet worden verwijderd. Teams die nog leden hebben, kunnen niet worden verwijderd. Wijs de leden eerst opnieuw toe.
:::
## API-sleutels {#api-keys}
Gebruikers kunnen API-sleutels genereren voor programmatische toegang. Elke sleutel gebruikt het `si_`-voorvoegsel en wordt slechts één keer getoond bij het aanmaken.
### Gescopede permissies {#scoped-permissions}
API-sleutels kunnen optioneel een `permissions`-array dragen. Wanneer ingesteld, zijn de effectieve permissies voor een verzoek de **doorsnede** van de rolpermissies van de gebruiker en de gescopede permissies van de sleutel. Dit betekent dat een API-sleutel nooit verder kan escaleren dan de eigen permissies van de gebruiker.
```bash
curl -X POST http://localhost:1349/api/v1/api-keys \
-H "Authorization: Bearer si_..." \
-H "Content-Type: application/json" \
-d '{
"name": "CI pipeline key",
"permissions": ["tools:use", "files:own"],
"expiresAt": "2027-01-01T00:00:00Z"
}'
```
### Vervaldatum {#expiration}
Sleutels accepteren een optionele `expiresAt`-timestamp. Verlopen sleutels worden bij authenticatie geweigerd.
## Auditlogboek {#audit-log}
SnapOtter registreert beveiligingsrelevante gebeurtenissen in een gestructureerd auditlogboek dat is opgeslagen in de databasetabel `audit_log`.
### Het auditlogboek bekijken {#viewing-the-audit-log}
```
GET /api/v1/audit-log?page=1&limit=50&action=LOGIN_FAILED&from=2026-01-01T00:00:00Z&to=2026-12-31T23:59:59Z
```
Vereist de permissie `audit:read`. Ondersteunt paginering (`page`, `limit`) en filters (`action`, `ip`, `from`, `to`).
### Auditing van tooloperaties {#tool-operation-auditing}
::: warning
`TOOL_EXECUTED`-gebeurtenissen worden standaard **niet** gelogd. Ze zijn opt-in via een van twee paden:
1. Stel de beheerdersinstelling `auditToolOperations` in op `true`.
2. Bezit een actieve licentie met de feature `audit_export` (beschikbaar op zowel team- als enterprise-plannen).
Zonder een van deze worden individuele tooluitvoeringen niet in het auditlogboek vastgelegd.
:::
### Exporteren {#exporting}
```
GET /api/v1/enterprise/audit/export?format=csv&from=2026-01-01T00:00:00Z
```
Vereist de permissie `audit:read` en de enterprise-feature `audit_export` (beschikbaar op zowel team- als enterprise-plannen). Ondersteunt CSV- en JSON-formaten, gefilterd op `action`, `actorId`, `targetType`, `targetId`, `from` en `to`.
### Sabotagebestendige ondertekening {#tamper-resistant-signing}
Wanneer ingeschakeld, wordt elke auditlogboekvermelding ondertekend met een HMAC afgeleid van `DATA_ENCRYPTION_KEY`. Dit vereist:
1. Het instellen van `DATA_ENCRYPTION_KEY` in je omgeving.
2. Het inschakelen van de beheerdersinstelling `tamperResistantAudit`.
3. Een enterprise-licentie met de feature `tamper_resistant_audit`.
### Bewaring {#retention}
Stel `AUDIT_RETENTION_DAYS` in om oude vermeldingen automatisch op te schonen. De standaard is `0`, wat betekent dat vermeldingen onbeperkt worden bewaard.
### Gebeurtenisreferentie {#event-reference}
| Gebeurtenis | Categorie |
|---|---|
| `LOGIN_SUCCESS`, `LOGIN_FAILED` | Authenticatie |
| `OIDC_LOGIN_SUCCESS`, `OIDC_LOGIN_FAILED` | Authenticatie |
| `SAML_LOGIN_SUCCESS`, `SAML_LOGIN_FAILED` | Authenticatie |
| `LOGOUT` | Authenticatie |
| `USER_CREATED`, `USER_UPDATED`, `USER_DELETED` | Gebruikersbeheer |
| `PASSWORD_CHANGED`, `PASSWORD_RESET` | Gebruikersbeheer |
| `MFA_ENROLLED`, `MFA_DISABLED`, `MFA_VERIFIED`, `MFA_VERIFY_FAILED` | MFA |
| `MFA_CHALLENGE_ISSUED`, `MFA_RECOVERY_USED`, `MFA_RESET` | MFA |
| `ROLE_CREATED`, `ROLE_UPDATED`, `ROLE_DELETED` | Rollen |
| `API_KEY_CREATED`, `API_KEY_DELETED` | API-sleutels |
| `SETTINGS_UPDATED`, `IP_ALLOWLIST_UPDATED` | Instellingen |
| `FILE_UPLOADED`, `FILE_DELETED` | Bestanden |
| `TOOL_EXECUTED` | Tools (opt-in) |
| `SCIM_USER_PROVISIONED`, `SCIM_USER_UPDATED`, `SCIM_USER_DEPROVISIONED` | SCIM |
| `SCIM_GROUP_SYNCED` | SCIM |
| `LEGAL_HOLD_APPLIED`, `LEGAL_HOLD_RELEASED` | Compliance |
| `GDPR_EXPORT_INITIATED`, `GDPR_USER_PURGED`, `GDPR_TEAM_PURGED` | Compliance |
| `CONFIG_EXPORTED`, `CONFIG_IMPORTED` | Configuratie |
## Sessiebeheer {#session-management}
Sessies zijn cookie-gebaseerd, geregeld door `SESSION_DURATION_HOURS` (standaard: 168 uur / 7 dagen).
### Rolwijzigingen maken sessies ongeldig {#role-changes-invalidate-sessions}
Wanneer een beheerder de rol van een gebruiker wijzigt, worden alle actieve sessies van die gebruiker verwijderd. De gebruiker moet opnieuw inloggen om de nieuwe permissies op te pikken.
### Veiligheidsmaatregelen {#safety-guards}
- **Bescherming van de laatste beheerder**: de laatst overgebleven beheerder kan niet worden gedegradeerd naar een lagere rol. De API retourneert een fout als je het probeert.
- **Voorkoming van zelfverwijdering**: beheerders kunnen hun eigen account niet via de API verwijderen.