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-Struktur, App- und Paketarchitektur, Request-Lebenszyklus und Ressourcen-Footprint von SnapOtter."
i18n_source_hash: 9e8f80499a37
i18n_provenance: human
i18n_output_hash: af8ecb20c86e
---
# Architektur {#architecture}
SnapOtter ist ein Monorepo, das mit pnpm-Workspaces und Turborepo verwaltet wird. Es wird als 3-Container-Docker-Compose-Stack ausgeliefert: das SnapOtter-App-Image, PostgreSQL 17 und Redis 8.
## Projektstruktur {#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
```
## Pakete {#packages}
### `@snapotter/image-engine` {#snapotter-image-engine}
Die zentrale Bildverarbeitungsbibliothek, aufgebaut auf [Sharp](https://sharp.pixelplumbing.com/). Sie übernimmt alle Nicht-KI-Operationen: Skalieren, Zuschneiden, Drehen, Spiegeln, Konvertieren, Komprimieren, Metadaten entfernen und Farbanpassungen (Helligkeit, Kontrast, Sättigung, Graustufen, Sepia, Invertieren, Farbkanäle).
Dieses Paket hat keine Netzwerkabhängigkeiten und läuft vollständig im Prozess.
### `@snapotter/ai` {#snapotter-ai}
Eine Brückenschicht, die Python-Skripte für ML-Operationen aufruft. Bei der ersten Verwendung startet die Brücke einen persistenten Python-Dispatcher-Prozess, der schwere Bibliotheken (PIL, NumPy, MediaPipe, rembg) vorab importiert, sodass nachfolgende KI-Aufrufe den Import-Overhead überspringen. Ist der Dispatcher noch nicht bereit, weicht die Brücke darauf aus, pro Anfrage einen frischen Python-Subprozess zu starten.
**Modelle werden nicht vorgeladen.** Jedes Werkzeug-Skript lädt seine Modellgewichte zur Anfragezeit von der Festplatte und verwirft sie, sobald die Anfrage abgeschlossen ist. Siehe [Ressourcen-Footprint](#resource-footprint) für das vollständige Speicherprofil.
Unterstützte Operationen: Hintergrundentfernung (rembg/BiRefNet), Hochskalierung (RealESRGAN), Gesichtsunschärfe (MediaPipe), Gesichtsverbesserung (GFPGAN/CodeFormer), Objektradierung (LaMa ONNX), OCR (PaddleOCR/Tesseract), Kolorierung (DDColor), Rauschentfernung, Rote-Augen-Entfernung, Fotorestaurierung, Passfoto-Erzeugung, Transparenzkorrektur (BiRefNet-HR-Matting) und inhaltsbewusstes Skalieren (Go-caire-Binärdatei).
Die Python-Skripte liegen in `packages/ai/python/`. Das Docker-Image lädt alle Modellgewichte während des Builds vorab herunter, sodass der Container vollständig offline funktioniert.
### `@snapotter/shared` {#snapotter-shared}
Gemeinsam genutzte TypeScript-Typen, Konstanten (wie `APP_VERSION` und Werkzeugdefinitionen) und i18n-Übersetzungsstrings, die sowohl vom Frontend als auch vom Backend verwendet werden.
## Anwendungen {#applications}
### API (`apps/api`) {#api-apps-api}
Ein Fastify-v5-Server, der 241 Werkzeug-Routen über fünf Modalitäten (image, video, audio, PDF, file) bereitstellt und Folgendes übernimmt:
- Datei-Uploads, Verwaltung des temporären Arbeitsbereichs und persistenter Dateispeicher
- Benutzer-Dateibibliothek mit Versionsketten (`user_files`-Tabelle) - jedes verarbeitete Ergebnis verweist zurück auf seine Quelldatei und erfasst, welches Werkzeug angewendet wurde, mit automatisch generierten Thumbnails für die Files-Seite
- Werkzeugausführung (leitet jede Werkzeuganfrage an die Image-Engine oder die KI-Brücke weiter)
- Pipeline-Orchestrierung (das sequenzielle Verketten mehrerer Werkzeuge)
- Stapelverarbeitung mit Nebenläufigkeitssteuerung über BullMQ-Job-Warteschlangen (Pools: image, media, ai, docs, system)
- Benutzerauthentifizierung, RBAC (admin-/user-Rollen mit einem vollständigen Berechtigungssatz), API-Schlüsselverwaltung und Ratenbegrenzung
- Teamverwaltung - Admin-only-CRUD; Benutzer werden über das Feld `team` in ihrem Profil einem Team zugewiesen
- Laufzeiteinstellungen - ein Schlüssel-Wert-Speicher in der `settings`-Tabelle, der `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit` und andere betriebliche Stellschrauben ohne erneutes Deployment steuert
- Benutzerdefiniertes Branding und Laufzeiteinstellungen über datenbankgestützte Settings
- Scalar-/OpenAPI-Dokumentation unter `/api/docs`
- Auslieferung des gebauten Frontends als SPA in der Produktion
Wichtige Abhängigkeiten: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod für die Validierung.
Der Server behandelt das kontrollierte Herunterfahren bei SIGTERM/SIGINT: Er lässt HTTP-Verbindungen auslaufen, stoppt die BullMQ-Worker, fährt den Python-Dispatcher herunter und schließt die Datenbankverbindung.
### Web (`apps/web`) {#web-apps-web}
Eine React-19-Single-Page-App, gebaut mit Vite. Nutzt Zustand für die Zustandsverwaltung, Tailwind CSS v4 für das Styling und Lucide für Icons. Kommuniziert mit der API über REST und SSE (für die Fortschrittsverfolgung).
Zu den Seiten gehören ein Werkzeug-Arbeitsbereich, eine Files-Seite zur Verwaltung persistenter Uploads und Ergebnisse, ein Automatisierungs-/Pipeline-Builder und ein Admin-Einstellungspanel.
Das gebaute Frontend wird in der Produktion vom Fastify-Backend ausgeliefert, sodass es im Docker-Container keinen separaten Webserver gibt.
### Docs (`apps/docs`) {#docs-apps-docs}
Diese VitePress-Site. Wird bei jedem Push auf `main` automatisch auf Cloudflare Pages bereitgestellt.
## Wie eine Anfrage abläuft {#how-a-request-flows}
1. Der Benutzer wählt in der Web-UI ein Werkzeug aus und lädt eine Datei hoch.
2. Das Frontend sendet einen Multipart-POST an `/api/v1/tools/:section/:toolId` mit der Datei und den Einstellungen.
3. Die API-Route validiert die Eingabe mit Zod und stellt dann die Verarbeitung zu.
4. Bei Standardwerkzeugen wird der Job in den passenden BullMQ-Pool eingereiht (image, media oder docs je nach Modalität). Der In-Prozess-BullMQ-Worker richtet das Bild anhand der EXIF-Metadaten automatisch aus, führt die Prozessfunktion des Werkzeugs aus und gibt das Ergebnis zurück.
5. Bei KI-Werkzeugen sendet die TypeScript-Brücke eine Anfrage an den persistenten Python-Dispatcher (oder startet ersatzweise einen frischen Subprozess), wartet auf dessen Abschluss und liest die Ausgabedatei.
6. Der Job-Fortschritt wird in der `jobs`-Tabelle in PostgreSQL persistiert, sodass der Zustand Container-Neustarts überdauert. Echtzeit-Updates werden über SSE unter `/api/v1/jobs/:jobId/progress` geliefert.
7. Die API gibt ein `jobId` und ein `downloadUrl` zurück. Der Benutzer lädt die verarbeitete Datei von `/api/v1/download/:jobId/:filename` herunter.
Bei Pipelines führt die API die Ausgabe jedes Schritts als Eingabe an den nächsten weiter und führt sie sequenziell aus.
Bei der Stapelverarbeitung nutzt die API BullMQ-Flows mit Kind-Jobs pro Schritt und gibt eine ZIP-Datei mit allen verarbeiteten Dateien zurück.
## Ressourcen-Footprint {#resource-footprint}
SnapOtter ist auf geringen Speicherverbrauch im Leerlauf ausgelegt. Beim Start wird nichts vorgeladen oder warmgehalten.
### Im Leerlauf {#at-idle}
Der Node.js-/Fastify-Prozess, PostgreSQL und Redis laufen. Der typische Leerlauf-RAM beträgt **~200-300 MB** über alle drei Container hinweg (Node.js-Prozess, Postgres und Redis). Kein Python-Prozess, keine Modellgewichte im Speicher.
### Was startet, und wann {#what-starts-and-when}
| Komponente | Startet bei | Speicher während aktiv |
|-----------|-------------|---------------------|
| Fastify-Server + Postgres + Redis | Containerstart | ~200-300 MB gesamt |
| BullMQ-Worker | Containerstart (im Prozess) | Ein Worker pro Pool (image, media, ai, docs, system) |
| Python-Dispatcher | Erste KI-Werkzeuganfrage | Python-Interpreter + vorab importierte Bibliotheken (PIL, NumPy, MediaPipe, rembg) - keine Modellgewichte |
| KI-Modellgewichte | Während der Anfrage des jeweiligen Werkzeugs | Von der Festplatte geladen, nach Abschluss der Anfrage freigegeben |
### Modellladen {#model-loading}
Alle Modellgewichtsdateien (insgesamt mehrere GB) liegen jederzeit auf der Festplatte in `/opt/models/`. Jedes KI-Werkzeug-Skript lädt nur seine eigenen Modelle für die Dauer einer Anfrage in den Speicher und gibt sie dann frei. Einige Skripte rufen nach der Inferenz explizit `del model` und `torch.cuda.empty_cache()` auf, um sicherzustellen, dass der Speicher sofort zurückgegeben wird.
Es gibt keinen Modell-Cache zwischen Anfragen. Führt man dasselbe KI-Werkzeug direkt hintereinander aus, wird das Modell jedes Mal neu geladen. Das hält den Leerlaufspeicher nahe null, auf Kosten einer Modellladeverzögerung bei jeder KI-Anfrage.
### Kaltstart bei der ersten KI-Anfrage {#first-ai-request-cold-start}
Der Python-Dispatcher läuft nicht, wenn der Container startet. Die erste KI-Anfrage löst zwei Dinge parallel aus: Der Dispatcher beginnt im Hintergrund aufzuwärmen, und die Anfrage selbst weicht auf einen einmaligen Python-Subprozess-Start aus. Sobald der Dispatcher bereit signalisiert, nutzen alle nachfolgenden KI-Anfragen ihn direkt und sparen sich die Kosten des Subprozess-Starts.
+164
View File
@@ -0,0 +1,164 @@
---
description: "Alle SnapOtter-Umgebungsvariablen mit Standardwerten. Konfiguriere Auth, Storage, KI-Modelle, Analyse und mehr."
i18n_source_hash: 8e9e9ca2840c
i18n_provenance: human
i18n_output_hash: 874b73f5ab4e
---
# Konfiguration {#configuration}
Die gesamte Konfiguration erfolgt über Umgebungsvariablen. Jede Variable hat einen sinnvollen Standardwert, sodass SnapOtter ohne das Setzen einer einzigen davon sofort funktioniert.
## Umgebungsvariablen {#environment-variables}
### Server {#server}
| Variable | Standard | Beschreibung |
|---|---|---|
| `PORT` | `1349` | Port, auf dem der Server lauscht. |
| `RATE_LIMIT_PER_MIN` | `1000` | Maximale Anzahl von Anfragen pro Minute pro IP. Auf 0 setzen, um die Ratenbegrenzung zu deaktivieren. |
| `CORS_ORIGIN` | (leer) | Kommagetrennte erlaubte Origins für CORS, oder leer für ausschließlich Same-Origin. |
| `LOG_LEVEL` | `info` | Log-Ausführlichkeit. Eines von: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. |
| `TRUST_PROXY` | `true` | `X-Forwarded-For`-Headern von einem Reverse-Proxy vertrauen. Auf `false` setzen, wenn nicht hinter einem Proxy. |
### Authentifizierung {#authentication}
| Variable | Standard | Beschreibung |
|---|---|---|
| `AUTH_ENABLED` | `false` | Auf `true` setzen, um eine Anmeldung zu erzwingen. Das Docker-Image verwendet standardmäßig `true`. |
| `DEFAULT_USERNAME` | `admin` | Benutzername für das anfängliche Admin-Konto. Wird nur beim ersten Start verwendet. |
| `DEFAULT_PASSWORD` | `admin` | Passwort für das anfängliche Admin-Konto. Ändere dies nach der ersten Anmeldung. |
| `MAX_USERS` | `0` (unbegrenzt) | Maximale Anzahl registrierter Benutzerkonten. Auf 0 setzen für unbegrenzt. |
| `SESSION_DURATION_HOURS` | `168` | Lebensdauer der Anmeldesitzung in Stunden (Standard sind 7 Tage). |
| `SKIP_MUST_CHANGE_PASSWORD` | - | Auf einen beliebigen nicht-leeren Wert setzen, um die erzwungene Passwortänderungsaufforderung bei der ersten Anmeldung zu umgehen |
### Storage {#storage}
| Variable | Standard | Beschreibung |
|---|---|---|
| `STORAGE_MODE` | `local` | `local` oder `s3`. S3/MinIO erfordert eine Lizenz mit dem Feature s3_storage. |
| `DATABASE_URL` | `postgres://snapotter:snapotter@postgres:5432/snapotter` | PostgreSQL-Verbindungszeichenfolge. |
| `REDIS_URL` | `redis://redis:6379` | Redis-Verbindungszeichenfolge (wird für BullMQ-Job-Warteschlangen verwendet). |
| `WORKSPACE_PATH` | `./tmp/workspace` | Verzeichnis für temporäre Dateien während der Verarbeitung. Wird automatisch aufgeräumt. |
| `FILES_STORAGE_PATH` | `./data/files` | Verzeichnis für persistente Benutzerdateien (hochgeladene Bilder, gespeicherte Ergebnisse). |
### Eingebetteter Modus {#embedded-mode}
Wenn du das Image ohne `DATABASE_URL` und ohne `REDIS_URL` startest, startet es sein eigenes PostgreSQL 17 und Redis im Container, an Loopback gebunden, mit allen Daten auf dem `/data`-Volume. Das stellt das Erlebnis `docker run` mit einem einzigen Befehl für Schnellstart, Homelab und Upgrades von 1.x wieder her. Es ist ein Komfortpfad, kein Produktions-Deployment: Für die Produktion betreibe den 3-Container-Compose-Stack mit separatem PostgreSQL und Redis. Der eingebettete Modus erfordert, den Container als Root zu betreiben, und ist mit Runtimes mit beliebiger UID (OpenShift, Kubernetes `runAsNonRoot`) nicht kompatibel; verwende dort Compose.
| Variable | Standard | Beschreibung |
|---|---|---|
| `EMBEDDED` | `auto` | Automatisch aktiviert, wenn sowohl `DATABASE_URL` als auch `REDIS_URL` nicht gesetzt sind. Auf `0` setzen, um es zu deaktivieren (die App schlägt dann sofort fehl, wenn kein externes `DATABASE_URL`/`REDIS_URL` gesetzt ist, statt stillschweigend eine In-Container-Datenbank zu starten). |
| `REDIS_MAXMEMORY` | `512mb` | Speicherobergrenze für das eingebettete Redis (nur im eingebetteten Modus). Senke sie auf speicherbeschränkten Hosts wie einem Raspberry Pi. |
Upgrade von 1.x: Lege deine alte `snapotter.db` unter `/data/snapotter.db` im Volume ab, und der eingebettete Modus importiert sie beim ersten Start in das eingebettete PostgreSQL. Der Import läuft einmal; spätere Starts überspringen ihn.
Telemetrie-Hinweis: Der eingebettete Modus übernimmt den Analyse-Standard des Images wie jede andere Konfiguration. Das veröffentlichte Image wird mit aktivierter Analyse ausgeliefert; baue mit `--build-arg SNAPOTTER_ANALYTICS=off` oder nutze das In-App-Admin-Opt-out, um sie zu deaktivieren.
### Verarbeitungslimits {#processing-limits}
| Variable | Standard | Beschreibung |
|---|---|---|
| `MAX_UPLOAD_SIZE_MB` | `100` | Maximale Dateigröße pro Upload in Megabyte. Auf 0 setzen für unbegrenzt. |
| `MAX_BATCH_SIZE` | `100` | Maximale Anzahl von Dateien in einer einzelnen Stapelanfrage. Auf 0 setzen für unbegrenzt. |
| `CONCURRENT_JOBS` | `0` (auto) | Anzahl der Stapeljobs, die parallel laufen. Auf 0 setzen, um automatisch anhand der verfügbaren CPU-Kerne zu erkennen. |
| `MAX_MEGAPIXELS` | `0` (unbegrenzt) | Maximal erlaubte Bildauflösung in Megapixeln. Auf 0 setzen für unbegrenzt. |
| `MAX_WORKER_THREADS` | `0` (auto) | Maximale Worker-Threads für die Bildverarbeitung. Auf 0 setzen, um automatisch anhand der verfügbaren CPU-Kerne zu erkennen. |
| `PROCESSING_TIMEOUT_S` | `0` (kein Limit) | Maximale Verarbeitungszeit pro Anfrage in Sekunden. Auf 0 setzen für kein Timeout. |
| `MAX_PIPELINE_STEPS` | `20` | Maximale Anzahl von Schritten in einer Pipeline. Auf 0 setzen für kein Limit. |
| `MAX_CANVAS_PIXELS` | `0` (kein Limit) | Maximale Leinwandgröße in Pixeln für Ausgabebilder. Auf 0 setzen für kein Limit. |
| `MAX_SVG_SIZE_MB` | `0` (unbegrenzt) | Maximale SVG-Dateigröße in Megabyte. Auf 0 setzen für unbegrenzt. |
| `MAX_SPLIT_GRID` | `100` | Maximale Rasterdimension für das Bildaufteilungswerkzeug. |
| `MAX_PDF_PAGES` | `0` (unbegrenzt) | Maximale Anzahl von PDF-Seiten für die PDF-zu-Image-Konvertierung. Auf 0 setzen für unbegrenzt. |
### Bereinigung {#cleanup}
| Variable | Standard | Beschreibung |
|---|---|---|
| `FILE_MAX_AGE_HOURS` | `72` | Wie lange ungespeicherte Verarbeitungsergebnisse (rohe Uploads und Werkzeugausgaben) vor der automatischen Löschung aufbewahrt werden. Dateien, die du explizit in der Files-Bibliothek speicherst, sind davon nicht betroffen und bleiben bestehen, bis du sie löschst. |
| `CLEANUP_INTERVAL_MINUTES` | `60` | Wie oft der Bereinigungsjob läuft. |
### Erscheinungsbild {#appearance}
| Variable | Standard | Beschreibung |
|---|---|---|
| `DEFAULT_THEME` | `light` | Standard-Theme für neue Sitzungen. `light` oder `dark`. |
| `DEFAULT_LOCALE` | `en` | Standard-Oberflächensprache. |
| `DEFAULT_TOOL_VIEW` | `sidebar` | Standard-Werkzeuglayout. `sidebar` oder `fullscreen`. |
### Docker-Berechtigungen {#docker-permissions}
| Variable | Standard | Beschreibung |
|---|---|---|
| `PUID` | `999` | Den Containerprozess als diese UID ausführen. Setze sie passend zu deinem Host-Benutzer für Bind-Mounts (`id -u`). |
| `PGID` | `999` | Den Containerprozess als diese GID ausführen. Setze sie passend zu deiner Host-Gruppe für Bind-Mounts (`id -g`). |
## Docker-Beispiel {#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}
Der Docker-Compose-Stack verwendet vier Volumes:
- `/data` (app) - KI-Modelle, Python-venv und Benutzerdateien. Mounte dies, um hochgeladene Dateien und installierte KI-Bundles über Neustarts hinweg zu behalten.
- `/tmp/workspace` (app) - Temporärer Speicher für Dateien, die gerade verarbeitet werden. Dies kann flüchtig sein, aber ein Mount vermeidet, dass die beschreibbare Schicht des Containers voll läuft.
- `SnapOtter-pgdata` (postgres) - PostgreSQL-Datenverzeichnis. Dies enthält alle relationalen Daten (Benutzer, Einstellungen, Pipelines, Jobs, Audit-Log). Sichere es über `pg_dump` oder einen Volume-Snapshot.
- `SnapOtter-redisdata` (redis) - Redis-Append-only-Datei für dauerhafte Job-Warteschlangen.
+131
View File
@@ -0,0 +1,131 @@
---
description: "Wie man zu SnapOtter beiträgt. Fehlerberichte, Feature-Anfragen, Pull Requests und CLA-Anforderungen."
i18n_source_hash: 528802503035
i18n_provenance: human
i18n_output_hash: dad4aeee07c4
---
# Mitwirken {#contributing}
Vielen Dank für dein Interesse am Mitwirken. Dieser Leitfaden erklärt, wie du dich beteiligen kannst, was wir annehmen und wie du loslegst.
## Möglichkeiten zum Mitwirken {#ways-to-contribute}
### Issues (kein Setup erforderlich) {#issues-no-setup-required}
- **Fehlerberichte** - Etwas kaputt? Öffne einen [Fehlerbericht](https://github.com/snapotter-hq/snapotter/issues/new?template=bug_report.yml) mit Schritten zur Reproduktion.
- **Feature-Anfragen** - Hast du eine Idee? Starte eine [Diskussion](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas), damit die Community sich einbringen und dafür stimmen kann.
- **Übersetzungsprobleme** - Eine falsche oder fehlende Übersetzung entdeckt? Öffne ein [Übersetzungs-Issue](https://github.com/snapotter-hq/snapotter/issues/new?template=translation.yml).
- **Dokumentationsprobleme** - Etwas stimmt in der Dokumentation nicht? Öffne ein [Dokumentations-Issue](https://github.com/snapotter-hq/snapotter/issues/new?template=documentation.yml).
### Code (erfordert CLA) {#code-requires-cla}
Wir nehmen Pull Requests an für:
| Typ | Ablauf |
|------|---------|
| Fehlerbehebungen | Öffne direkt einen PR (verlinke das Issue, falls eines existiert) |
| Neue Übersetzungen | Öffne direkt einen PR (siehe [Übersetzungsleitfaden](/de/guide/translations)) |
| Verbesserungen an der Dokumentation | Öffne direkt einen PR |
| Verbesserungen der Testabdeckung | Öffne direkt einen PR |
| Neue Tools oder Features | Starte zuerst eine [Diskussion](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas); ein Maintainer überführt genehmigte Ideen in ein nachverfolgtes Issue, bevor du Code schreibst |
| Refactorings oder Architekturänderungen | Starte zuerst eine [Diskussion](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas) und warte auf die Freigabe eines Maintainers, bevor du Code schreibst |
### Was wir nicht annehmen {#what-we-will-not-accept}
- Änderungen an CI/CD-Workflows, Release-Konfiguration oder Linter-/Compiler-Konfiguration
- PRs ohne unterzeichnete [Contributor License Agreement](#contributor-license-agreement)
- PRs mit mehr als 400 geänderten Zeilen (teile große Arbeiten in kleinere PRs auf)
- Features, die nicht zuvor besprochen und genehmigt wurden
- Änderungen an `packages/ai/` ohne vorherige Absprache
## Contributor License Agreement {#contributor-license-agreement}
Bevor wir deinen ersten PR mergen können, musst du unsere [Individual CLA](https://github.com/snapotter-hq/snapotter/blob/main/CLA.md) unterzeichnen. Das ist eine einmalige Voraussetzung.
**Warum:** SnapOtter ist dual lizenziert (AGPLv3 + kommerziell). Die CLA gewährt uns das Recht, deine Beiträge unter beiden Lizenzen zu verbreiten. Du behältst das volle Urheberrecht an deiner Arbeit.
**Wie:** Wenn du deinen ersten PR öffnest, kommentiert der CLA-Assistant-Bot mit einem Link. Klicke darauf, prüfe die Vereinbarung und unterzeichne mit deinem GitHub-Konto. Das dauert 30 Sekunden.
Wenn du im Auftrag deines Arbeitgebers beiträgst und dein Arbeitgeber die IP-Rechte an deiner Arbeit behält, wende dich an contact@snapotter.com, um vor der Einreichung eine Corporate CLA zu vereinbaren.
## Erste Schritte {#getting-started}
### Voraussetzungen {#prerequisites}
- Node.js 22+
- pnpm 9+
- Python 3.11+ (nur für AI-Tools)
- Docker (optional, für vollständige Integrationstests)
### Setup {#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
```
### Prüfungen ausführen {#running-checks}
Stelle vor dem Einreichen eines PR sicher, dass alle Prüfungen lokal bestehen:
```bash
pnpm lint # Biome lint + format check
pnpm typecheck # TypeScript across monorepo
pnpm test # Vitest unit + integration tests
```
## Ablauf eines Pull Requests {#pull-request-process}
1. Forke das Repository und erstelle einen Branch von `main` (`feat/my-feature` oder `fix/issue-123`)
2. Nimm deine Änderungen in fokussierten, überprüfbaren Commits mit [Conventional Commits](https://www.conventionalcommits.org/) vor
3. Füge Tests für deine Änderungen hinzu oder aktualisiere sie
4. Führe `pnpm lint && pnpm typecheck && pnpm test` lokal aus
5. Öffne einen PR gegen `main` und fülle die Vorlage aus
6. Unterzeichne die CLA, falls du dazu aufgefordert wirst
7. Warte, bis die CI besteht und ein Maintainer den PR prüft
### Was du bei der Prüfung erwarten kannst {#review-expectations}
- Wir bemühen uns, innerhalb von 7 Tagen auf PRs zu reagieren
- Kleine, fokussierte PRs werden schneller geprüft
- Wenn du nach 7 Tagen nichts gehört hast, hinterlasse einen Kommentar und pinge den Thread an
- Wir können Änderungen anfordern, einen anderen Ansatz vorschlagen oder den PR schließen, wenn er nicht zur Projektausrichtung passt
### Nachdem dein PR gemergt wurde {#after-your-pr-is-merged}
Dein Beitrag wird im nächsten Release enthalten sein und im Changelog vermerkt.
## Gute erste Issues {#good-first-issues}
Suchst du etwas, woran du arbeiten kannst? Sieh dir unsere [guten ersten Issues](https://github.com/snapotter-hq/snapotter/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) für einsteigerfreundliche Aufgaben an oder [help wanted](https://github.com/snapotter-hq/snapotter/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) für größere Aufgaben, bei denen wir uns über Unterstützung aus der Community freuen.
## Codestil {#code-style}
- Biome übernimmt Formatierung und Linting (doppelte Anführungszeichen, Semikolons, Einrückung mit 2 Leerzeichen)
- Der Pre-Commit-Hook führt `biome check --write` automatisch auf gestagten Dateien aus
- Wenn der Linter meckert, behebe den Code (ändere nicht die Biome-Konfiguration)
- ES-Module überall (`import`/`export`)
- Conventional Commits: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`
Vollständige Architekturdetails findest du im [Entwicklerleitfaden](/de/guide/developer).
## Sicherheit {#security}
**Öffne keinen öffentlichen PR oder kein öffentliches Issue für Sicherheitslücken.** Melde sie privat über [GitHub Security Advisories](https://github.com/snapotter-hq/snapotter/security/advisories/new) oder per E-Mail an contact@snapotter.com. Alle Details findest du in [SECURITY.md](https://github.com/snapotter-hq/snapotter/blob/main/SECURITY.md).
## Fragen? {#questions}
- [Dokumentation](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-Datenbankschema, Tabellen, Migrationen und Backup-Verfahren für SnapOtter."
i18n_source_hash: b37398ae91a3
i18n_provenance: human
i18n_output_hash: dd3579a83805
---
# Datenbank {#database}
SnapOtter verwendet PostgreSQL 17 mit [Drizzle ORM](https://orm.drizzle.team/) (pg-core / node-postgres) für die Datenpersistenz. Das Schema ist in `apps/api/src/db/schema.ts` definiert.
Die Verbindung wird über die Umgebungsvariable `DATABASE_URL` konfiguriert (Standard `postgres://snapotter:snapotter@postgres:5432/snapotter`). In Docker Compose speichert der Postgres-Container seine Daten im benannten Volume `SnapOtter-pgdata`.
## Tabellen {#tables}
### users {#users}
Speichert Benutzerkonten. Wird beim ersten Start automatisch aus `DEFAULT_USERNAME` und `DEFAULT_PASSWORD` erstellt.
| Spalte | Typ | Hinweise |
|---|---|---|
| `id` | uuid | Primärschlüssel |
| `username` | varchar | Eindeutig, erforderlich |
| `passwordHash` | varchar | scrypt-Hash |
| `role` | varchar | `admin`, `editor` oder `user` |
| `mustChangePassword` | boolean | Flag für erzwungenes Zurücksetzen des Passworts |
| `createdAt` | timestamp | Erstellungszeitpunkt |
| `updatedAt` | timestamp | Zeitpunkt der letzten Aktualisierung |
### sessions {#sessions}
Aktive Anmelde-Sitzungen. Jede Zeile verknüpft ein Sitzungstoken mit einem Benutzer.
| Spalte | Typ | Hinweise |
|---|---|---|
| `id` | varchar | Primärschlüssel (Sitzungstoken) |
| `userId` | uuid | Fremdschlüssel auf `users.id` |
| `expiresAt` | timestamp | Ablaufzeitpunkt |
| `createdAt` | timestamp | Erstellungszeitpunkt |
### teams {#teams}
Gruppen zum Organisieren von Benutzern. Admins können Benutzer Teams zuweisen.
| Spalte | Typ | Beschreibung |
|--------|------|-------------|
| `id` | uuid | Primärschlüssel |
| `name` | varchar (eindeutig, max. 50 Zeichen) | Teamname |
| `createdAt` | timestamp | Erstellungszeitpunkt |
### api_keys {#api-keys}
API-Schlüssel für den programmatischen Zugriff. Der rohe Schlüssel wird nur einmal bei der Erstellung angezeigt; gespeichert wird nur der Hash.
| Spalte | Typ | Hinweise |
|---|---|---|
| `id` | uuid | Primärschlüssel |
| `userId` | uuid | Fremdschlüssel auf `users.id` |
| `keyHash` | varchar | scrypt-Hash des Schlüssels |
| `name` | varchar | Vom Benutzer vergebene Bezeichnung |
| `createdAt` | timestamp | Erstellungszeitpunkt |
| `lastUsedAt` | timestamp | Bei jeder authentifizierten Anfrage aktualisiert |
Schlüssel haben das Präfix `si_` gefolgt von 96 Hex-Zeichen (48 zufällige Bytes).
### pipelines {#pipelines}
Gespeicherte Tool-Ketten, die Benutzer in der Oberfläche erstellen.
| Spalte | Typ | Hinweise |
|---|---|---|
| `id` | uuid | Primärschlüssel |
| `name` | varchar | Pipeline-Name |
| `description` | varchar | Optionale Beschreibung |
| `steps` | jsonb | Array von `{ toolId, settings }`-Objekten |
| `createdAt` | timestamp | Erstellungszeitpunkt |
### user_files {#user-files}
Persistente Dateibibliothek mit Nachverfolgung der Versionskette. Jeder Verarbeitungsschritt, der ein Ergebnis speichert, erstellt eine neue Zeile, die über `parentId` mit ihrem übergeordneten Element verknüpft ist und so einen Versionsbaum bildet.
| Spalte | Typ | Beschreibung |
|--------|------|-------------|
| `id` | uuid | Primärschlüssel |
| `userId` | uuid | FK auf users (CASCADE DELETE) |
| `originalName` | varchar | Ursprünglicher Upload-Dateiname |
| `storedName` | varchar | Dateiname auf dem Datenträger |
| `mimeType` | varchar | MIME-Typ |
| `size` | integer | Dateigröße in Bytes |
| `width` | integer | Bildbreite in px |
| `height` | integer | Bildhöhe in px |
| `version` | integer | Versionsnummer (1 = Original) |
| `parentId` | uuid oder null | FK auf user_files (übergeordnete Version) |
| `toolChain` | jsonb | Tool-IDs, die in Reihenfolge angewendet wurden, um diese Version zu erzeugen |
| `createdAt` | timestamp | Erstellungszeitpunkt |
### jobs {#jobs}
Verfolgt Verarbeitungs-Jobs für Fortschrittsanzeige und Bereinigung.
| Spalte | Typ | Hinweise |
|---|---|---|
| `id` | uuid | Primärschlüssel |
| `type` | varchar | Tool- oder Pipeline-Bezeichner |
| `status` | varchar | `queued`, `processing`, `completed` oder `failed` |
| `progress` | real | Anteil 0.0-1.0 |
| `inputFiles` | jsonb | Array von Eingabedatei-Pfaden |
| `outputPath` | varchar | Pfad zur Ergebnisdatei |
| `settings` | jsonb | Verwendete Tool-Einstellungen |
| `error` | varchar | Fehlermeldung bei Fehlschlag |
| `createdAt` | timestamp | Erstellungszeitpunkt |
| `completedAt` | timestamp | Abschlusszeitpunkt |
### settings {#settings}
Schlüssel-Wert-Speicher für serverweite Einstellungen, die Admins über die Oberfläche ändern können.
| Spalte | Typ | Hinweise |
|---|---|---|
| `key` | varchar | Primärschlüssel |
| `value` | varchar | Einstellungswert |
| `updatedAt` | timestamp | Zeitpunkt der letzten Aktualisierung |
### roles {#roles}
Benutzerdefinierte Rollen mit granularen Berechtigungen.
| Spalte | Typ | Hinweise |
|---|---|---|
| `id` | uuid | Primärschlüssel |
| `name` | varchar | Eindeutiger Rollenname |
| `description` | varchar | Optionale Beschreibung |
| `permissions` | jsonb | Array von Berechtigungs-Strings |
| `createdAt` | timestamp | Erstellungszeitpunkt |
### audit_log {#audit-log}
Protokoll sicherheitsrelevanter Aktionen.
| Spalte | Typ | Hinweise |
|---|---|---|
| `id` | uuid | Primärschlüssel |
| `userId` | uuid | FK auf users |
| `action` | varchar | Aktionstyp |
| `details` | jsonb | Aktionsspezifische Daten |
| `createdAt` | timestamp | Zeitpunkt der Aktion |
## Migrationen {#migrations}
Drizzle übernimmt die Schema-Migrationen. Die Migrationsdateien liegen in `apps/api/drizzle/`. Während der Entwicklung:
```bash
cd apps/api
npx drizzle-kit generate # generate a migration from schema changes
npx drizzle-kit migrate # apply pending migrations
```
In der Produktion werden ausstehende Migrationen beim Start automatisch angewendet.
## Backup und Wiederherstellung {#backup-and-restore}
Die relationale Datenbank liegt im Volume `SnapOtter-pgdata` des Postgres-Containers, nicht im Volume `/data` der App.
**Option 1: pg_dump (empfohlen)**
```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
```
**Option 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 .
```
### Migration von 1.x (SQLite) {#migrating-from-1-x-sqlite}
Das Upgrade von SnapOtter 1.x hat einen eigenen Leitfaden: siehe [Upgrade von 1.x auf 2.0](./upgrading). Kurz gesagt: Verwende dein bestehendes Volume `/data` weiter, und 2.0 erkennt und importiert `/data/snapotter.db` beim ersten Start automatisch (oder setze `SQLITE_MIGRATE_PATH`, um explizit darauf zu verweisen). Sichere zuerst das gesamte Volume `/data`, nicht nur `snapotter.db`: 1.x nutzt den SQLite-WAL-Modus, sodass ein gestoppter Container einen Großteil seiner Daten oft in `snapotter.db-wal` neben einer fast leeren `snapotter.db` ablegt.
+571
View File
@@ -0,0 +1,571 @@
---
description: "SnapOtter mit Docker in die Produktion bringen. Hardware-Anforderungen, GPU-Einrichtung und Reverse-Proxy-Konfigurationen für Nginx, Traefik und Cloudflare."
i18n_source_hash: 6b6957060fa6
i18n_provenance: machine
i18n_output_hash: 07c771bca730
---
# Deployment {#deployment}
SnapOtter wird als Docker-Compose-Stack aus 3 Containern bereitgestellt: dem SnapOtter-App-Image, PostgreSQL 17 und Redis 8. Das App-Image unterstützt **linux/amd64** (mit NVIDIA CUDA für KI-Beschleunigung) und **linux/arm64** (CPU), sodass es nativ auf Intel/AMD-Servern, Apple-Silicon-Macs und ARM-Geräten wie dem Raspberry Pi 4/5 läuft. Intel/AMD-iGPU-Beschleunigung über VA-API, Quick Sync oder OpenCL wird für KI-Inferenz derzeit nicht unterstützt.
Siehe [Docker-Image](./docker-tags) für GPU-Einrichtung, Docker-Compose-Beispiele und Versionsfixierung.
## Schnellstart (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
```
Die App ist dann unter `http://localhost:1349` erreichbar.
> **Docker-Hub-Ratenbegrenzungen?** Ersetze `snapotter/snapotter:latest` durch `ghcr.io/snapotter-hq/snapotter:latest`, um stattdessen aus der GitHub Container Registry zu ziehen. Beide Registries erhalten bei jedem Release dasselbe Image.
## Schnellstart (NVIDIA CUDA) {#quick-start-nvidia-cuda}
Für NVIDIA-CUDA-Beschleunigung bei KI-Tools (Hintergrundentfernung, Hochskalierung, Gesichtsverbesserung, 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
```
Prüfe die CUDA-Erkennung in den Logs:
```bash
docker logs SnapOtter 2>&1 | head -20
# Look for: [gpu] CUDA available via torch
```
## Hardware-Anforderungen {#hardware-requirements}
Diese Werte stammen aus Benchmarks über eine Reihe von Systemen hinweg, von einer modernen amd64-Workstation mit einer NVIDIA RTX 4070 bis hinunter zu einem Raspberry Pi. Auf jedem wurde der gesamte Tool-Katalog ausgeführt und die Docker-Ressourcenlimits durchlaufen, um die tatsächliche Untergrenze zu ermitteln.
### Kurzübersicht {#quick-reference}
| Stufe | Anwendungsfall | CPU | RAM | GPU | Speicher |
|------|----------|-----|-----|-----|---------|
| Minimum | Bild-, Datei- und leichte PDF-Tools; ein Benutzer; kleine Stapel | 2 Kerne | 2 GB | Keine | ~7 GB |
| Empfohlen | Alle fünf Modalitäten inkl. Video, PDF und KI auf CPU; Stapel; einige Benutzer | 4 Kerne | 4 GB | Keine | ~25 GB |
| Voll | Alles in voller Geschwindigkeit inkl. GPU-KI; große Stapel; viele Benutzer | 6-8 Kerne | 8 GB | NVIDIA 8 GB+ VRAM (12 GB komfortabel) | ~35 GB |
**Architektur: nur 64-Bit** (`linux/amd64` oder `linux/arm64`). SnapOtter läuft nativ auf Intel/AMD-Servern, Apple-Silicon-Macs und 64-Bit-ARM-Boards einschließlich des **Raspberry Pi 4 und 5** (4-8 GB). Es läuft **nicht** auf 32-Bit-ARM (`armv7`/`armhf`) - dafür wird kein Image gebaut - und auch nicht auf Boards der 512-MB-Klasse wie dem Pi Zero, die unter der Speicheruntergrenze liegen (siehe unten).
### Minimum (Bild-, Datei- und leichte PDF-Tools; keine KI) {#minimum-image-files-and-light-pdf-tools-no-ai}
| Ressource | Anforderung |
|---|---|
| CPU | 2 Kerne |
| RAM | 2 GB |
| Festplatte | ~5,5 GB (Image) + Datenvolume |
| GPU | Nicht erforderlich |
Alle 222 Nicht-KI-Katalog-Tools - Bild (Größe ändern, zuschneiden, konvertieren, komprimieren, anpassen, Wasserzeichen), Video (trimmen, stummschalten, remuxen), Audio (konvertieren, normalisieren, trimmen), PDF (zusammenführen, teilen, komprimieren, drehen, schützen), Dateikonvertierungen und dedizierte Konvertierungsvorlagen - laufen auf bescheidener Hardware. Die meisten Vorgänge sind selbst bei einer großen Datei in deutlich unter einer Sekunde abgeschlossen: Ein 2,7 MB großes Bild wird in ~0,05 s in der Größe geändert und in ~2 s zu WebP neu kodiert.
Die Speicheruntergrenze ist real, aus einem Durchlauf der Docker-Ressourcenlimits: **512 MB können den Stack nicht starten** (selbst eine einzelne Bildgrößenänderung wird abgebrochen), **1 GB** bewältigt Einzeldatei-Vorgänge, aber einem Mehrdatei-Stapel geht der Speicher aus, und **2 GB / 2 Kerne** ist die kleinste Konfiguration, die Stapel komfortabel bewältigt.
```yaml
deploy:
resources:
limits:
cpus: '2'
memory: 2G
```
**Die eine CPU-intensive Ausnahme ist die Video-Neukodierung.** Stream-Copy-Vorgänge (trimmen, stummschalten, Container-Remux) sind sofort erledigt, aber das Transkodieren in einen anderen Codec ist CPU-gebunden. Ein 1080p-Clip von 45 Sekunden, der zu VP9 (WebM) neu kodiert wird, benötigt auf einer schnellen modernen CPU etwa **~40 s**, ~45 s auf Apple Silicon, ~80 s auf einer älteren mobilen 4-Kern-CPU und **~130 s** auf einem älteren 4-Kern-Server. Wenn deine Arbeitslast videolastig ist, priorisiere CPU-Kerne und Taktrate oder erhöhe das `cpus:`-Limit des Containers - das mitgelieferte Compose begrenzt die App standardmäßig auf 4 Kerne (8 beim GPU-Compose).
### Empfohlen (KI-Tools auf CPU) {#recommended-ai-tools-on-cpu}
| Ressource | Anforderung |
|---|---|
| CPU | 4 Kerne |
| RAM | 4 GB |
| Festplatte | 3 GB (Image) + 24 GB (KI-Modelle) + Arbeitsbereich |
| GPU | Nicht erforderlich (CPU-Fallback) |
**Die Installation der KI-Bundles treibt den RAM auf 4 GB.** Ohne installierte KI läuft die App im Leerlauf bei etwa 360 MB; mit allen sieben installierten Bundles hält sie ~2,6 GB resident, weil das Python-KI-Sidecar seine Modelle (Hintergrundentfernung, Hochskalierung, OCR, Transkription, Gesichtserkennung, Restaurierung) beim Start vorlädt. Nicht-KI-Installationen bleiben leichtgewichtig; KI-Installationen benötigen ≥4 GB.
Die meisten KI-Tools sind auf der CPU einwandfrei nutzbar; einige wenige wollen wirklich eine GPU. Gemessen auf einer modernen 4-Kern-CPU:
| KI-Tool | CPU-Zeit | Auf CPU nutzbar? |
|---|---|---|
| Gesichtserkennung (Gesichter unkenntlich machen, intelligenter Zuschnitt, Rote-Augen), Rauschentfernung | unter 1 s | Ja |
| OCR, Transkription, Untertitel | 1-3 s | Ja |
| Kolorieren, Gesichtsverbesserung | ~10 s | Ja |
| Hintergrundentfernung / -ersetzung / -unschärfe | ~29 s | Ja (du wirst warten) |
| KI-Hochskalierung (RealESRGAN) | ~33 s klein; Minuten bei großen Bildern | Grenzwertig - GPU dringend empfohlen |
| Fotorestaurierung (vollständige Pipeline) | mehrere Minuten | Nein - benötigt eine GPU oder eine schnelle Many-Core-CPU |
SnapOtter backt diese Modell-Downloads bewusst nicht in das Docker-Image ein. KI-Bundles werden nur heruntergeladen, wenn ein Administrator das zugehörige Tool aktiviert, im persistenten `/data/ai`-Volume gespeichert und von jedem Tool geteilt, das vom selben Modell-Stack abhängt. Das hält das finale Container-Image klein und lässt eine vollständige KI-Installation dennoch die größeren Speicherwerte unten erreichen.
Manche Tools hängen von mehr als einem geteilten Bundle ab. Passfoto benötigt beispielsweise sowohl `background-removal` als auch `face-detection`; wenn `background-removal` bereits installiert ist, lädt das Aktivieren von Passfoto nur das fehlende `face-detection`-Bundle herunter. Dieselbe Wiederverwendung gilt für alle KI-Tools.
Download-Größen der KI-Modelle:
| Bundle | Festplattengröße |
|---|---|
| Hintergrundentfernung | 4-5 GB |
| Hochskalierung + Gesichtsverbesserung + Rauschentfernung | 5-6 GB |
| Gesichtserkennung | 200-300 MB |
| Objekt-Radierer + Kolorieren | 1-2 GB |
| OCR | 5-6 GB |
| Fotorestaurierung | 4-5 GB |
| **Alle Bundles** | **~24 GB** |
```yaml
deploy:
resources:
limits:
cpus: '4'
memory: 4G
```
### Voll (KI-Tools auf NVIDIA CUDA) {#full-ai-tools-on-nvidia-cuda}
| Ressource | Anforderung |
|---|---|
| CPU | 6-8 Kerne (Video-Vorbereitung + Nebenläufigkeit laufen auch bei GPU-KI auf der CPU) |
| RAM | 8 GB |
| GPU | NVIDIA mit 8+ GB VRAM (12 GB empfohlen) |
| Festplatte | ~35 GB gesamt |
Eine NVIDIA-GPU (CUDA) beschleunigt die schweren KI-Modelle dramatisch. Gemessen auf einer RTX 4070 gegenüber einer modernen CPU:
| KI-Tool | Beschleunigung mit GPU | Hinweise |
|---|---|---|
| KI-Hochskalierung (RealESRGAN 2×) | **~47×** | Der größte Gewinn - unter einer Sekunde statt ~33 s (Minuten bei großen Bildern) |
| Gesichtsverbesserung (CodeFormer) | **~12×** | ~0,9 s statt ~11 s |
| Transkription (Whisper) | ~4,5× | |
| Hintergrundentfernung / -ersetzung / -unschärfe | ~4× | ~7 s auf GPU statt ~29 s auf CPU |
| Kolorieren | ~1,8× | |
| OCR, Gesichtserkennung, Rote-Augen, Rauschentfernung | ~1× | Bereits schnell auf der CPU - eine GPU hilft nicht |
| Fotorestaurierung | keine | CPU-gebunden selbst auf einer GPU (0 % GPU-Auslastung); eine schnelle CPU zählt hier mehr als eine GPU |
Die Tools, für die sich eine GPU lohnt, sind **Hochskalierung, Gesichtsverbesserung, Transkription und Hintergrundentfernung**. Gesichtserkennung, OCR und Rote-Augen sind CPU-gebunden und bereits schnell, sodass eine GPU nichts bringt.
Die VRAM-Spitzennutzung erreicht 7,5 GB während der Hochskalierung mit Gesichtsverbesserung. Eine 6-GB-NVIDIA-GPU funktioniert für die meisten KI-Tools einzeln, scheitert aber bei der Hochskalierung. 8-12 GB VRAM bewältigen alles.
Intel/AMD-iGPU-Beschleunigung über VA-API, Quick Sync oder OpenCL wird für KI-Inferenz derzeit nicht unterstützt. Das Einbinden von `/dev/dri` in den Container aktiviert keine KI-GPU-Beschleunigung; SnapOtter führt KI-Tools auf der CPU aus, sofern nicht NVIDIA CUDA verfügbar ist.
```yaml
deploy:
resources:
limits:
cpus: '4'
memory: 8G
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
```
### Gleichzeitige Benutzer {#concurrent-users}
Parallele Bildgrößenänderungs-Anfragen gegen den standardmäßig auf 4 Kerne begrenzten App-Container:
| Gleichzeitige Anfragen | Durchschn. Antwortzeit | Fehler |
|---|---|---|
| 1 | 0,4 s | 0 |
| 5 | 1,2 s | 0 |
| 10 | 2,1 s | 0 |
Die Antwortzeit verschlechtert sich sublinear ohne Fehler, während der Worker-Pool gesättigt wird. Das Anheben des `cpus:`-Limits des App-Containers (oder die Verwendung eines Hosts mit mehr Kernen) hebt die Obergrenze an. Beachte, dass schwere Jobs (Video-Transkodierung, CPU-KI) einen Worker für ihre gesamte Dauer belegen, also dimensioniere die CPU nach deiner erwarteten Anzahl gleichzeitiger schwerer Jobs, nicht nur nach der Anfragezahl.
### Unterstützte Bildformate {#supported-image-formats}
SnapOtter unterstützt **55+ Eingabeformate** und **14 Ausgabeformate**, einschließlich RAW-Dateien von 20+ Kameramarken, professionellen Formaten (PSD, EPS, OpenEXR, HDR), modernen Codecs (JPEG XL, AVIF, HEIC, QOI) sowie wissenschaftlichen und Gaming-Formaten (FITS, DDS).
Siehe die [vollständige Formatliste](/de/guide/supported-formats) für Details zu jedem unterstützten Format, dem verwendeten Decoder und den verfügbaren Qualitätsreglern.
### Bekannte Einschränkungen {#known-limitations}
- **Inhaltsbewusste Größenänderung** stürzt bei großen Bildern (>5 MP) aufgrund einer Einschränkung im caire-Binary ab. Funktioniert bei kleineren Bildern einwandfrei.
- **HEIF-Dekodierung** dauert 13-23 Sekunden. HEIC (Apples Variante) ist mit 0,3-0,9 Sekunden deutlich schneller.
- **OCR Japanisch** schlägt auf der CPU aufgrund eines PaddlePaddle-MKLDNN-Fehlers fehl. Funktioniert auf der GPU.
- **Hochskalierung** läuft auf der CPU bei allem jenseits kleiner Bilder in eine Zeitüberschreitung. GPU für den praktischen Einsatz erforderlich.
- **CodeFormer**-Gesichtsverbesserung ist deutlich langsamer als GFPGAN (53 s statt 2 s auf GPU). GFPGAN wird für die meisten Anwendungsfälle empfohlen.
## Volumes {#volumes}
| Mount / Volume | Zweck | Erforderlich? |
|---|---|---|
| `/data` (App) | KI-Modelle, Python-venv, Benutzerdateien | **Ja** - Dateiverlust ohne es |
| `/tmp/workspace` (App) | Temporäre Verarbeitungsdateien (automatisch bereinigt) | Empfohlen |
| `SnapOtter-pgdata` (Postgres) | PostgreSQL-Datenverzeichnis (Benutzer, Einstellungen, Pipelines, Jobs) | **Ja** - Datenverlust ohne es |
| `SnapOtter-redisdata` (Redis) | Redis-Append-Only-Datei für dauerhafte Job-Warteschlangen | Empfohlen |
### Bind-Mounts vs. benannte Volumes {#bind-mounts-vs-named-volumes}
**Benannte Volumes** (empfohlen) - Docker verwaltet die Berechtigungen automatisch:
```yaml
volumes:
- SnapOtter-data:/data
```
**Bind-Mounts** - Du verwaltest die Berechtigungen. Setze `PUID`/`PGID` passend zu deinem Host-Benutzer:
```yaml
volumes:
- ./SnapOtter-data:/data
environment:
- PUID=1000 # Your host UID (run: id -u)
- PGID=1000 # Your host GID (run: id -g)
```
### Speicherberechtigungen {#storage-permissions}
SnapOtter schreibt zur Laufzeit an zwei Orte: `/data` (Benutzerdateien, Logs, KI-Modelle und das Python-venv) und `/tmp/workspace` (temporärer Verarbeitungs-Scratch). Beide müssen für den Benutzer, unter dem der Container läuft, beschreibbar sein. Ist eines von beiden es nicht, **scheitert der Container beim Start sofort** mit einer Meldung, die das Verzeichnis, die laufende UID/GID und die Behebung nennt - statt "gesund" hochzufahren und dann beim ersten Upload mit einem kryptischen Fehler zu scheitern.
Wie Berechtigungen gehandhabt werden, hängt davon ab, wie der Container gestartet wird:
**Standard (startet als root, fällt auf `snapotter` zurück)** - der Entrypoint startet als root, korrigiert die Eigentümerschaft der eingebundenen Volumes und fällt dann über `gosu` auf den unprivilegierten `snapotter`-Benutzer zurück. Benannte Volumes funktionieren ohne Konfiguration. Setze für Bind-Mounts `PUID`/`PGID` auf deinen Host-Benutzer (oben), damit die geschriebenen Dateien dir gehören.
**Kubernetes / OpenShift (non-root über `runAsUser`)** - direkt als Non-Root-Benutzer gestartet, kann der Container die Volumes nicht selbst chownen, daher muss der Orchestrator sie beschreibbar machen. Setze `fsGroup`:
```yaml
securityContext:
runAsUser: 999
runAsGroup: 999
fsGroup: 999 # makes mounted volumes writable by the pod
```
Die beschreibbaren Verzeichnisse des Images gehören der Gruppe GID 0 und sind gruppenbeschreibbar, sodass ein Pod, der mit einer **beliebigen UID** plus der Root-Zusatzgruppe (dem OpenShift-Standard) läuft, ohne `chown` schreiben kann.
**TrueNAS Scale (und andere "Fremd-UID"-Setups)** - TrueNAS führt Apps als Non-Root-Benutzer aus (oft `568:568`) und bindet Host-Datasets ein, die einem anderen Benutzer gehören, sodass weder der Entrypoint noch `fsGroup` sie von sich aus beschreibbar macht. Wähle eine Option:
- **Führe die App als root aus** (empfohlen) - lasse den Benutzer der App ungesetzt oder setze ihn auf `0` und lass den Standard-Entrypoint die Berechtigungen korrigieren und auf `snapotter` zurückfallen.
- **Führe als UID `999` aus** - setze Benutzer/Gruppe der App auf `999:999` (SnapOtters eingebauter `snapotter`-Benutzer), sodass sie zur Eigentümerschaft des Images passt.
- **`chown` das Host-Dataset** auf die UID, unter der der Container läuft, aus der TrueNAS-Shell:
```bash
# Verwende die UID aus dem Startfehler (oder führe `id` im Container aus)
chown -R 568:568 /mnt/<pool>/<dataset>
```
Der Startfehler nennt die genau zu verwendende UID, daher ist der schnellste Weg, die App einmal zu starten, die Meldung zu lesen und dann entsprechend `chown` (oder den Benutzer anzupassen).
## Umgebungsvariablen {#environment-variables}
| Variable | Standard | Beschreibung |
|---|---|---|
| `AUTH_ENABLED` | `true` | Login-Pflicht aktivieren/deaktivieren |
| `DEFAULT_USERNAME` | `admin` | Anfänglicher Admin-Benutzername |
| `DEFAULT_PASSWORD` | `admin` | Anfängliches Admin-Passwort (erzwungene Änderung beim ersten Login) |
| `MAX_UPLOAD_SIZE_MB` | `100` | Upload-Limit pro Datei |
| `MAX_BATCH_SIZE` | `100` | Max. Dateien pro Stapelanfrage |
| `RATE_LIMIT_PER_MIN` | `1000` | API-Anfragen pro Minute und IP (0 zum Deaktivieren) |
| `MAX_USERS` | `0` (unbegrenzt) | Maximale Benutzerkonten |
| `TRUST_PROXY` | `true` | X-Forwarded-For-Header vom Reverse-Proxy vertrauen |
| `PUID` | `999` | Als diese UID ausführen (für Bind-Mount-Berechtigungen) |
| `PGID` | `999` | Als diese GID ausführen (für Bind-Mount-Berechtigungen) |
| `LOG_LEVEL` | `info` | Log-Ausführlichkeit: fatal, error, warn, info, debug, trace |
| `CONCURRENT_JOBS` | `0` (auto) | Max. parallele KI-Verarbeitungsjobs |
| `SESSION_DURATION_HOURS` | `168` | Lebensdauer der Login-Sitzung (7 Tage) |
| `CORS_ORIGIN` | (leer) | Kommagetrennte erlaubte Ursprünge oder leer für Same-Origin |
## Health-Check {#health-check}
Der Container enthält einen eingebauten 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 setzt `TRUST_PROXY=true` standardmäßig, sodass Ratenbegrenzung und Protokollierung die echte Client-IP aus den `X-Forwarded-For`-Headern verwenden.
### 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. Füge einen neuen Proxy Host hinzu
2. Setze den Domain Name auf deine Domain
3. Setze Scheme auf `http`, Forward Hostname auf `SnapOtter` (oder deine Container-IP), Forward Port auf `1349`
4. Aktiviere WebSocket-Unterstützung
5. Füge unter Advanced hinzu: `client_max_body_size 500M;` und `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` deaktiviert die Antwort-Pufferung, was für SSE-Fortschrittsereignisse (Stapelverarbeitung, KI-Tools, Feature-Installationen) erforderlich ist. Die verlängerten Zeitüberschreitungen erlauben es, große Datei-Uploads abzuschließen, ohne dass Caddy die Verbindung vorzeitig schließt.
### Cloudflare Tunnels {#cloudflare-tunnels}
```bash
cloudflared tunnel --url http://localhost:1349
```
Hinweis: Cloudflare hat auf kostenlosen Tarifen ein Upload-Limit von 100 MB. Setze `MAX_UPLOAD_SIZE_MB=100` passend dazu.
## CI/CD {#ci-cd}
Das GitHub-Repository hat drei Workflows:
- **ci.yml** - Läuft automatisch bei jedem Push und PR. Lintet, typechecked, testet, baut und validiert das Docker-Image (ohne Push).
- **release.yml** - Wird manuell über `workflow_dispatch` ausgelöst. Führt semantic-release aus, um ein Versions-Tag und ein GitHub-Release zu erstellen, baut dann ein Multi-Arch-Docker-Image (amd64 + arm64) und pusht zu Docker Hub (`snapotter/snapotter`) und zur GitHub Container Registry (`ghcr.io/snapotter-hq/snapotter`).
- **deploy-docs.yml** - Baut diese Dokumentations-Site und stellt sie bei Push auf `main` auf Cloudflare Pages bereit.
Um ein Release zu erstellen, gehe in der GitHub-Oberfläche auf **Actions > Release > Run workflow** oder führe aus:
```bash
gh workflow run release.yml
```
Semantic-release bestimmt die Version aus der Commit-Historie. Das `latest`-Docker-Tag zeigt immer auf das jüngste Release.
## Analytics {#analytics}
SnapOtter enthält anonyme Produkt-Analytics (Tool-Nutzungsmuster, Fehlerberichte), um Bugs zu erkennen und Funktionen zu verbessern. Sie sind standardmäßig aktiviert. Deine Dateien, Dateinamen und persönlichen Daten sind niemals Teil davon. SnapOtter funktioniert mit deaktivierten Analytics normal.
### Analytics deaktivieren {#disabling-analytics}
Das Laufzeit-Opt-out ist ein Admin-Umschalter mit einem Klick. Öffne Einstellungen > System > Datenschutz und schalte Anonyme Produkt-Analytics aus. Es stoppt sofort für die gesamte Instanz, kein Neuaufbau erforderlich.
Für ein Image, das niemals Analytics senden kann, setze das Build-Time-Hard-Off, indem du das Repository klonst und neu baust:
```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
```
Oder füge das Build-Argument zu deinem vorhandenen `docker-compose.yml` hinzu:
```yaml
services:
snapotter:
build:
context: .
dockerfile: docker/Dockerfile
args:
SNAPOTTER_ANALYTICS: "off"
```
+234
View File
@@ -0,0 +1,234 @@
---
description: "Lokales Entwicklungs-Setup, Befehle, Code-Konventionen und wie man ein neues Tool zu SnapOtter hinzufügt."
i18n_source_hash: cb03724d2829
i18n_provenance: human
i18n_output_hash: 175a711c72f7
---
# Entwicklerleitfaden {#developer-guide}
Wie man eine lokale Entwicklungsumgebung einrichtet und Code zu SnapOtter beiträgt.
## Voraussetzungen {#prerequisites}
- [Node.js](https://nodejs.org/) 22+
- [pnpm](https://pnpm.io/) 9+ (`corepack enable && corepack prepare pnpm@latest --activate`)
- [Docker](https://www.docker.com/) (erforderlich für lokales Postgres + Redis, Container-Builds und AI-Features)
- Git
Python 3.10+ wird nur benötigt, wenn du am AI/ML-Sidecar arbeitest (Hintergrundentfernung, Hochskalierung, OCR).
## Setup {#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
```
Dies startet zwei Dev-Server:
| Dienst | URL | Hinweise |
|----------|--------------------------|------------------------------------|
| Frontend | http://localhost:1349 | Vite-Dev-Server, proxyt /api |
| Backend | http://localhost:13490 | Fastify-API (über Proxy erreichbar) |
Öffne http://localhost:1349 in deinem Browser. Melde dich mit `admin` / `admin` an. Bei der ersten Anmeldung wirst du aufgefordert, das Passwort zu ändern.
## Projektstruktur {#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
```
## Befehle {#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
```
## Code-Konventionen {#code-conventions}
- Doppelte Anführungszeichen, Semikolons, Einrückung mit 2 Leerzeichen (durch Biome erzwungen)
- ES-Module in allen Workspaces
- [Conventional Commits](https://www.conventionalcommits.org/) für semantic-release
- Zod für alle API-Eingabevalidierungen
- Keine Änderungen an Biome-, TypeScript- oder Editor-Konfigurationsdateien. Behebe den Code, nicht den Linter.
## Datenbank {#database}
PostgreSQL 17 über Drizzle ORM (pg-core). Die lokale Entwicklung erfordert ein laufendes Postgres und Redis - starte sie mit:
```bash
docker compose -f docker-compose.dev.yml up -d
```
Dies stellt dir Postgres auf Port 5432 und Redis auf Port 6379 bereit. Generiere und wende dann die Migrationen an:
```bash
cd apps/api
npx drizzle-kit generate # generate a migration from schema changes
npx drizzle-kit migrate # apply pending migrations
```
Das Schema ist in `apps/api/src/db/schema.ts` definiert. Tabellen: users, sessions, settings, jobs, apiKeys, pipelines, teams, userFiles, roles, auditLog.
## Ein neues Tool hinzufügen {#adding-a-new-tool}
Jedes Tool folgt demselben Muster. Hier ein minimales Beispiel.
### 1. Backend-Route {#_1-backend-route}
Erstelle `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",
};
},
});
}
```
Registriere es dann in `apps/api/src/routes/tools/index.ts`.
### 2. Frontend-Einstellungskomponente {#_2-frontend-settings-component}
Erstelle `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>
);
}
```
Registriere es dann in der Frontend-Tool-Registry unter `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 }],
```
Anzeigemodi: `"side-by-side"`, `"before-after"`, `"live-preview"`, `"no-comparison"`, `"interactive-crop"`, `"interactive-eraser"`, `"no-dropzone"`.
### 3. i18n-Eintrag {#_3-i18n-entry}
Füge zu `packages/shared/src/i18n/en.ts` hinzu:
```ts
"my-tool": {
name: "My Tool",
description: "Short description of what this tool does",
},
```
### 4. Tests {#_4-tests}
Füge deinem Aktions-Button ein `data-testid`-Attribut hinzu (wie oben gezeigt), damit e2e-Tests ihn zuverlässig ansteuern können.
## Docker-Builds {#docker-builds}
Baue das vollständige Produktions-Image lokal:
```bash
docker build -f docker/Dockerfile -t snapotter:latest .
```
Verwende BuildKit-Cache-Mounts für schnellere Rebuilds:
```bash
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile -t snapotter:latest .
```
## Umgebungsvariablen {#environment-variables}
Die vollständige Liste findest du im [Konfigurationsleitfaden](/de/guide/configuration). Wichtige für die Entwicklung:
| Variable | Standard | Beschreibung |
|-----------------------------|-----------|------------------------------------------------|
| `AUTH_ENABLED` | `true` | Authentifizierung aktivieren/deaktivieren |
| `DEFAULT_USERNAME` | `admin` | Standard-Admin-Benutzername |
| `DEFAULT_PASSWORD` | `admin` | Standard-Admin-Passwort |
| `SKIP_MUST_CHANGE_PASSWORD` | `false` | Erzwungene Passwortänderung überspringen (nur CI/Dev) |
| `RATE_LIMIT_PER_MIN` | `1000` | API-Ratenlimit pro Minute (0 = deaktiviert) |
| `MAX_UPLOAD_SIZE_MB` | `100` | Maximale Upload-Größe in MB (0 = unbegrenzt) |
+158
View File
@@ -0,0 +1,158 @@
---
description: "SnapOtter Docker-Image-Tags, GPU-Benchmarks, Versionsfixierung und Multi-Plattform-Unterstützung für AMD64 und ARM64."
i18n_source_hash: 148b3608e11a
i18n_provenance: human
i18n_output_hash: bf7df15424fd
---
# Docker-Image {#docker-image}
SnapOtter wird als einzelnes Docker-Image ausgeliefert. Wenn Sie es allein ausführen, startet es ein eingebettetes PostgreSQL 17 und Redis auf der Loopback-Schnittstelle (eingebetteter Modus); für den Produktivbetrieb führen Sie es zusammen mit separaten PostgreSQL-17- und Redis-8-Containern per Compose aus. Das App-Image funktioniert auf allen Plattformen.
## Schnellstart {#quick-start}
```bash
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
```
Ohne gesetztes `DATABASE_URL` läuft dies im eingebetteten Modus: PostgreSQL und Redis starten innerhalb des Containers auf dem Loopback, wobei alle Daten unter dem Volume `SnapOtter-data` liegen. Setzen Sie `DATABASE_URL` und `REDIS_URL` (wie es der [Compose](#docker-compose)-Stack tut), um stattdessen externe Dienste zu verwenden. Siehe [Konfiguration](/de/guide/configuration#embedded-mode).
## NVIDIA-CUDA-Beschleunigung {#nvidia-cuda-acceleration}
Das Image enthält NVIDIA-CUDA-Unterstützung auf amd64. Wenn Sie über eine NVIDIA-GPU mit installiertem [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) verfügen, fügen Sie `--gpus all` hinzu:
```bash
docker run -d --name SnapOtter --gpus all -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
```
Das Image erkennt CUDA zur Laufzeit automatisch. Ohne `--gpus all` oder wenn CUDA nicht verfügbar ist, laufen die KI-Werkzeuge auf der CPU. In beiden Fällen dasselbe Image.
Intel/AMD-iGPU-Beschleunigung über VA-API, Quick Sync oder OpenCL wird für die KI-Inferenz von SnapOtter derzeit nicht unterstützt. Das Einbinden von `/dev/dri` in den Container kann das Rendergerät verfügbar machen, aber die KI-Laufzeit nutzt weiterhin die CPU, sofern CUDA nicht verfügbar ist.
### Benchmarks {#benchmarks}
Getestet auf einer NVIDIA RTX 4070 (12 GB VRAM) mit einem 572x1024-JPEG-Porträt.
#### Warme Leistung {#warm-performance}
| Werkzeug | CPU | GPU | Beschleunigung |
|------|-----|-----|---------|
| Hintergrundentfernung (u2net) | 2.415 ms | 879 ms | 2,7x |
| Hintergrundentfernung (isnet) | 2.457 ms | 1.137 ms | 2,2x |
| Hochskalierung 2x | 350 ms | 309 ms | 1,1x |
| Hochskalierung 4x | 910 ms | 310 ms | 2,9x |
| OCR (PaddleOCR) | 137 ms | 94 ms | 1,5x |
| Gesichtsunschärfe | 139 ms | 122 ms | 1,1x |
#### Kaltstart (erste Anfrage nach Containerstart) {#cold-start-first-request-after-container-start}
| Werkzeug | CPU | GPU | Beschleunigung |
|------|-----|-----|---------|
| Hintergrundentfernung | 22.286 ms | 4.792 ms | 4,7x |
| Hochskalierung 2x | 3.957 ms | 2.318 ms | 1,7x |
| OCR (PaddleOCR) | 1.469 ms | 1.090 ms | 1,3x |
### CUDA-Statusprüfung {#cuda-health-check}
Nach der ersten KI-Anfrage meldet der Admin-Health-Endpunkt den Status der CUDA-GPU:
```
GET /api/v1/admin/health
{"ai": {"gpu": true}}
```
## Docker Compose {#docker-compose}
Der vollständige Compose-Stack umfasst die App, PostgreSQL 17 und Redis 8. Siehe [Bereitstellung](/de/guide/deployment) für die vollständige `docker-compose.yml`. Ein minimales Beispiel:
```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:
```
Für NVIDIA-CUDA-Beschleunigung über Docker Compose fügen Sie den deploy-Abschnitt zum SnapOtter-Dienst hinzu:
```yaml
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
```
## Versionsfixierung {#version-pinning}
| Tag | Beschreibung |
|-----|------------|
| `latest` | Neueste Version |
| `1.11.0` | Exakte Version |
| `1.11` | Neuester Patch in 1.11.x |
| `1` | Neueste Minor-Version in 1.x |
## Plattformen {#platforms}
| Architektur | GPU-Unterstützung | Hinweise |
|---|---|---|
| linux/amd64 | NVIDIA CUDA | Volle CUDA-Beschleunigung für KI-Werkzeuge |
| linux/arm64 | nur CPU | Raspberry Pi 4/5, Apple Silicon über Docker Desktop |
## Migration von früheren Tags {#migration-from-previous-tags}
Wenn Sie den Tag `:cuda` verwendet haben, wechseln Sie zu `:latest` und behalten Sie `--gpus all`. Gleiche GPU-Unterstützung, vereinheitlichtes Image.
Ihre Daten und Einstellungen bleiben in den Volumes erhalten.
+176
View File
@@ -0,0 +1,176 @@
---
description: "SnapOtter mit Docker in einem einzigen Befehl installieren. Enthält Docker-Compose-Einrichtung, Bauen aus dem Quellcode und eine vollständige Funktionsübersicht."
i18n_source_hash: 4536d4558b8e
i18n_provenance: machine
i18n_output_hash: 14356fc91e39
---
# Erste Schritte {#getting-started}
::: tip Vor dem Installieren ausprobieren
Erkunde die vollständige Oberfläche unter [demo.snapotter.com](https://demo.snapotter.com) - keine Anmeldung oder Installation erforderlich.
:::
## Schnellstart {#quick-start}
```bash
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
```
Dieser einzelne Container führt alles aus, was er benötigt: Ohne gesetztes `DATABASE_URL` startet er sein eigenes PostgreSQL und Redis auf der Loopback-Schnittstelle (Embedded-Modus) und hält alle Daten im `SnapOtter-data`-Volume. Es ist der schnellste Weg, SnapOtter auszuprobieren oder in einem Homelab selbst zu hosten. Für die Produktion führe den [Docker-Compose](#docker-compose)-Stack unten aus, der PostgreSQL und Redis in ihren eigenen Containern hält. Der Embedded-Modus läuft als root (der Standard) und schaltet sich automatisch aus, sobald du `DATABASE_URL` setzt.
Du wirst beim ersten Login aufgefordert, dein Passwort zu ändern.
::: tip Anonyme Produkt-Analytics
SnapOtter enthält standardmäßig anonyme Produkt-Analytics. Um sie auszuschalten, öffne **Einstellungen → System → Datenschutz** und schalte **Anonyme Produkt-Analytics** aus. Es stoppt sofort für die gesamte Instanz.
Du kannst auch die Umgebungsvariable `SNAPOTTER_TELEMETRY=0` setzen (`false` und `off` funktionieren ebenfalls), um alle Telemetrie für die Instanz ohne Neuaufbau zu deaktivieren.
Die Fehlerüberwachung wird von [Sentry](https://sentry.io) bereitgestellt, das SnapOtter über sein Open-Source-Programm unterstützt.
Für Details darüber, was erfasst wird, siehe [Was SnapOtter erfasst](/de/guide/telemetry).
:::
::: tip NVIDIA-CUDA-Beschleunigung
Füge `--gpus all` für NVIDIA-CUDA-beschleunigte Hintergrundentfernung, Hochskalierung, OCR, Gesichtsverbesserung und Restaurierung hinzu:
```bash
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest
```
Erfordert das [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). Fällt automatisch auf die CPU zurück, wenn CUDA nicht verfügbar ist. Intel/AMD-iGPU-Beschleunigung über VA-API, Quick Sync oder OpenCL wird für KI-Inferenz derzeit nicht unterstützt. Siehe [Docker-Tags](/de/guide/docker-tags) für Benchmarks.
:::
::: details Auch auf GHCR
```bash
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data ghcr.io/snapotter-hq/snapotter:latest
```
Beide Registries veröffentlichen bei jedem Release dasselbe 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:
```
Siehe [Konfiguration](/de/guide/configuration) für alle Umgebungsvariablen.
## Aus dem Quellcode bauen {#build-from-source}
**Voraussetzungen:** Node.js 22+, pnpm 9+, Docker (für Postgres + Redis), Python 3.10+ (für KI-Funktionen), 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)
## Was du tun kannst {#what-you-can-do}
### Dateiverarbeitung (200+ Tools) {#file-processing-200-tools}
| Modalität | Anzahl | Beispiel-Tools |
|----------|-------|---------------|
| **Bild** | 105 | Größe ändern, zuschneiden, komprimieren, konvertieren, Hintergrund entfernen, hochskalieren, OCR, Wasserzeichen, Collage, kolorieren, GIF-Tools, Format-Vorlagen |
| **Video** | 57 | Trimmen, zuschneiden, komprimieren, konvertieren, zusammenführen, Audio extrahieren, Auto-Untertitel, Video zu GIF, Größe ändern, stabilisieren, Format-Vorlagen |
| **Audio** | 27 | Trimmen, zusammenführen, konvertieren, normalisieren, Rauschunterdrückung, transkribieren, Tonhöhenverschiebung, Ein-/Ausblenden, Klingelton-Ersteller, Format-Vorlagen |
| **PDF / Dokument** | 42 | Zusammenführen, teilen, komprimieren, OCR, Wasserzeichen, schwärzen, Word zu PDF, Excel zu PDF, drehen, schützen, reparieren |
| **Dateien** | 10 | CSV zu JSON, JSON zu XML, CSVs zusammenführen, CSV teilen, ZIP erstellen, ZIP entpacken, Diagramm-Ersteller, YAML/JSON |
### Pipelines {#pipelines}
Verkette Tools zu mehrstufigen Workflows und wende sie auf ein Bild oder einen ganzen Stapel an:
1. Öffne **Pipelines** in der Seitenleiste.
2. Füge Schritte hinzu (beliebiges Tool, beliebige Einstellungen).
3. Führe sie auf einer einzelnen Datei aus - oder auf einem ganzen Stapel auf einmal.
4. Speichere die Pipeline zur späteren Wiederverwendung.
Pipelines erlauben standardmäßig 20 Schritte. Setze `MAX_PIPELINE_STEPS=0`, um das Limit unbegrenzt zu machen.
### Datei-Bibliothek {#file-library}
Jede von dir verarbeitete Datei kann in deiner **Dateien**-Bibliothek gespeichert werden. SnapOtter verfolgt die vollständige Versionshistorie, sodass du jeden Verarbeitungsschritt vom ursprünglichen Upload bis zur finalen Ausgabe nachvollziehen kannst.
Das Speichern ist explizit: Ergebnisse, die du in der Bibliothek speicherst, bleiben erhalten, bis du sie löschst, während Ergebnisse, die du verarbeitest und ungespeichert lässt, nach 72 Stunden automatisch entfernt werden (konfigurierbar über `FILE_MAX_AGE_HOURS`).
### REST-API & API-Schlüssel {#rest-api-api-keys}
Jedes Tool ist über HTTP zugänglich:
```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"}'
```
Generiere API-Schlüssel unter **Einstellungen → API-Schlüssel**. Siehe die [REST-API-Referenz](/de/api/rest) für alle Endpunkte oder besuche [http://localhost:1349/api/docs](http://localhost:1349/api/docs) für die interaktive Referenz.
### Mehrbenutzer & Teams {#multi-user-teams}
Aktiviere mehrere Benutzer mit rollenbasierter Zugriffskontrolle:
- **Admin**: voller Zugriff - Benutzer, Teams, Einstellungen, alle Dateien/Pipelines/API-Schlüssel verwalten
- **Benutzer**: Tools nutzen, eigene Dateien/Pipelines/API-Schlüssel verwalten
Erstelle Teams unter **Einstellungen → Teams**, um Benutzer zu gruppieren.
Setze `AUTH_ENABLED=true` (oder `false` für Einzelbenutzer/Eigennutzung ohne Login).
+170
View File
@@ -0,0 +1,170 @@
---
description: "Richten Sie Single Sign-On mit OpenID Connect ein. Schritt-für-Schritt-Anleitungen für Keycloak, Authentik, Google und andere OIDC-Anbieter."
i18n_source_hash: 4296343b3cc5
i18n_provenance: human
i18n_output_hash: 8f5a0caeda80
---
# OIDC / Single Sign-On {#oidc-single-sign-on}
SnapOtter unterstützt OpenID Connect (OIDC) für Single Sign-On. Benutzer können sich mit einem externen Identitätsanbieter wie Keycloak, Authentik oder Google anmelden, anstatt (oder zusätzlich zu) der lokalen Benutzername/Passwort-Authentifizierung.
::: tip Siehe auch
[SAML SSO](/de/guide/saml) | [SCIM-Bereitstellung](/de/guide/scim) | [Benutzer, Rollen & Berechtigungen](/de/guide/users-roles)
:::
## Schnellstart {#quick-start}
Fügen Sie diese Umgebungsvariablen zu Ihrer `docker-compose.yml` hinzu:
```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"
```
Die Weiterleitungs-URI für Ihren Anbieter lautet immer:
```
${EXTERNAL_URL}/api/auth/oidc/callback
```
Wenn `EXTERNAL_URL` beispielsweise `https://photos.example.com` ist, konfigurieren Sie die Weiterleitungs-URI Ihres Anbieters als `https://photos.example.com/api/auth/oidc/callback`.
## Konfigurationsreferenz {#configuration-reference}
| Variable | Standard | Beschreibung |
|---|---|---|
| `OIDC_ENABLED` | `false` | OIDC-Anmeldung aktivieren. Eine Schaltfläche "Mit SSO anmelden" erscheint auf der Anmeldeseite. |
| `OIDC_ISSUER_URL` | | Issuer-URL des Anbieters. Muss OIDC Discovery (`/.well-known/openid-configuration`) unterstützen. |
| `OIDC_CLIENT_ID` | | Bei Ihrem Anbieter registrierte OAuth-Client-ID. |
| `OIDC_CLIENT_SECRET` | | OAuth-Client-Secret. |
| `OIDC_SCOPES` | `openid profile email` | Durch Leerzeichen getrennte Liste der anzufordernden Scopes. |
| `OIDC_AUTO_CREATE_USERS` | `true` | Bei der ersten OIDC-Anmeldung automatisch ein lokales Benutzerkonto erstellen. |
| `OIDC_DEFAULT_ROLE` | `user` | Rolle, die automatisch erstellten OIDC-Benutzern zugewiesen wird. Eine von `admin`, `editor` oder `user`. |
| `OIDC_AUTO_LINK_USERS` | `false` | Eine OIDC-Identität mit einem bestehenden lokalen Benutzer verknüpfen, wenn die E-Mail-Adresse übereinstimmt. |
| `OIDC_PROVIDER_NAME` | | Auf der Anmeldeschaltfläche angezeigter Anzeigename (z. B. "Keycloak", "Google"). Wenn leer, zeigt die Schaltfläche "SSO" an. |
| `OIDC_CLOCK_TOLERANCE` | `30` | Toleranz für Uhrenabweichungen in Sekunden bei der Token-Validierung. |
| `OIDC_USERNAME_CLAIM` | `preferred_username` | ID-Token-Claim, der als Benutzername für neue Konten verwendet wird. |
| `EXTERNAL_URL` | | Die öffentliche URL, unter der SnapOtter erreichbar ist. Erforderlich, damit OIDC die korrekte Weiterleitungs-URI erstellen kann. |
| `COOKIE_SECRET` | automatisch generiert | Secret zum Signieren von Sitzungscookies. Setzen Sie dies explizit, wenn Sie mehrere Replikate betreiben. |
## Anbieter-Anleitungen {#provider-guides}
### Keycloak {#keycloak}
1. Erstellen Sie einen neuen Realm (oder verwenden Sie einen bestehenden).
2. Gehen Sie zu **Clients** und erstellen Sie einen neuen Client:
- **Client-ID**: `snapotter`
- **Client-Authentifizierung**: An (vertraulich)
- **Authentifizierungsablauf**: Standard-Flow (Authorization Code)
3. Setzen Sie auf der Registerkarte **Settings** des Clients unter **Valid redirect URIs** Ihre Callback-URL (z. B. `https://photos.example.com/api/auth/oidc/callback`).
4. Kopieren Sie das **Client secret** von der Registerkarte **Credentials**.
5. Setzen Sie `OIDC_ISSUER_URL` auf `https://keycloak.example.com/realms/your-realm`.
### Authentik {#authentik}
1. Gehen Sie in der Admin-Oberfläche zu **Applications > Providers** und erstellen Sie einen neuen **OAuth2/OpenID Provider**.
- **Client type**: Confidential
- **Redirect URIs**: Ihre Callback-URL
- **Signing key**: Wählen Sie einen bestehenden Schlüssel oder erstellen Sie einen
2. Erstellen Sie eine **Application** und verknüpfen Sie sie mit dem Provider.
3. Kopieren Sie die **Client ID** und das **Client Secret** aus den Provider-Einstellungen.
4. Setzen Sie `OIDC_ISSUER_URL` auf `https://authentik.example.com/application/o/snapotter/` (der abschließende Schrägstrich ist wichtig).
### Google {#google}
1. Gehen Sie zur [Google Cloud Console](https://console.cloud.google.com/).
2. Erstellen Sie ein Projekt (oder wählen Sie ein bestehendes).
3. Navigieren Sie zu **APIs & Services > OAuth consent screen** und konfigurieren Sie ihn.
4. Gehen Sie zu **APIs & Services > Credentials** und erstellen Sie eine **OAuth 2.0 Client ID**:
- **Application type**: Web application
- **Authorized redirect URIs**: Ihre Callback-URL
5. Kopieren Sie die **Client ID** und das **Client secret**.
6. Setzen Sie `OIDC_ISSUER_URL` auf `https://accounts.google.com`.
7. Setzen Sie `OIDC_USERNAME_CLAIM` auf `email` (Google stellt `preferred_username` nicht bereit).
## Benutzerbereitstellung {#user-provisioning}
### Automatisches Erstellen {#auto-create}
Wenn `OIDC_AUTO_CREATE_USERS` auf `true` (die Voreinstellung) steht, wird beim ersten Anmelden per OIDC ein lokales Benutzerkonto erstellt. Der Benutzername wird aus dem durch `OIDC_USERNAME_CLAIM` angegebenen Claim übernommen, und die Rolle wird auf `OIDC_DEFAULT_ROLE` gesetzt.
Bei einer Kollision des Benutzernamens wird ein numerisches Suffix angehängt (z. B. `jane` wird zu `jane_2`).
### Automatisches Verknüpfen {#auto-link}
Wenn `OIDC_AUTO_LINK_USERS` auf `true` steht, verknüpft SnapOtter eine OIDC-Identität mit einem bestehenden lokalen Konto, wenn die E-Mail-Adressen übereinstimmen. Das ist nützlich, wenn Sie Benutzerkonten vorab erstellt haben und möchten, dass diese SSO nutzen, ohne ihre Daten zu verlieren.
::: warning
Aktivieren Sie das automatische Verknüpfen nur, wenn Sie Ihrem OIDC-Anbieter zutrauen, E-Mail-Adressen zu verifizieren. Eine nicht verifizierte E-Mail könnte es jemandem ermöglichen, das Konto eines anderen Benutzers zu übernehmen.
:::
### Lokale Anmeldung deaktivieren {#disabling-local-login}
OIDC deaktiviert die lokale Benutzername/Passwort-Anmeldung nicht. Beide Methoden bleiben verfügbar. Admins können sich weiterhin mit lokalen Anmeldedaten anmelden, falls der OIDC-Anbieter nicht erreichbar ist.
## Selbstsignierte Zertifikate {#self-signed-certificates}
Wenn Ihr OIDC-Anbieter ein selbstsigniertes oder privates CA-Zertifikat verwendet, binden Sie das CA-Bundle in den Container ein und verweisen Sie `NODE_EXTRA_CA_CERTS` darauf:
```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
Setzen Sie `NODE_TLS_REJECT_UNAUTHORIZED=0` nicht. Dies deaktiviert die gesamte TLS-Verifizierung und ist ein Sicherheitsrisiko.
:::
## Fehlerbehebung {#troubleshooting}
### Nichtübereinstimmung der Weiterleitungs-URI {#redirect-uri-mismatch}
Der häufigste Fehler. Prüfen Sie diese Unterschiede zwischen dem, was Ihr Anbieter erwartet, und dem, was SnapOtter sendet:
- `http` vs. `https` - das Schema muss exakt übereinstimmen
- Abschließender Schrägstrich - manche Anbieter sind hierbei streng
- Portnummer - geben Sie den Port an, wenn er nicht dem Standard entspricht
- Pfad - muss `/api/auth/oidc/callback` sein
Überprüfen Sie `EXTERNAL_URL` genau. Es muss mit der URL übereinstimmen, die Benutzer in ihren Browser eingeben.
### UNABLE_TO_VERIFY_LEAF_SIGNATURE {#unable-to-verify-leaf-signature}
Der OIDC-Anbieter verwendet ein Zertifikat, dem Node.js nicht vertraut. Siehe [Selbstsignierte Zertifikate](#self-signed-certificates) oben.
### Fehler durch Uhrenabweichung {#clock-skew-errors}
Wenn die Uhr Ihres Servers und die Uhr des OIDC-Anbieters nicht synchron sind, kann die Token-Validierung fehlschlagen. Erhöhen Sie `OIDC_CLOCK_TOLERANCE` (Standard ist 30 Sekunden). Eine bessere Lösung ist, NTP auf beiden Maschinen auszuführen.
### "OIDC-Anbieter nicht erreichbar" {#oidc-provider-unreachable}
SnapOtter ruft das Discovery-Dokument des Anbieters beim Start und während der Anmeldung ab. Prüfen Sie:
- DNS-Auflösung aus dem Inneren des Docker-Containers (`docker exec snapotter nslookup auth.example.com`)
- Firewall-Regeln zwischen dem Container und dem Anbieter
- Den Wert `OIDC_ISSUER_URL` - er muss vom Server aus erreichbar sein, nicht nur von Ihrem Browser
### Fehlende Claims {#missing-claims}
Wenn Benutzernamen oder E-Mails nach der Anmeldung leer sind, gibt Ihr Anbieter möglicherweise nicht die erwarteten Claims zurück. Überprüfen Sie:
- Die in `OIDC_SCOPES` konfigurierten Scopes enthalten `profile` und `email`
- Der Anbieter ist so konfiguriert, dass er den in `OIDC_USERNAME_CLAIM` angegebenen Claim in das ID-Token aufnimmt
- Manche Anbieter erfordern eine explizite Mapper-/Scope-Konfiguration, um Claims freizugeben
+224
View File
@@ -0,0 +1,224 @@
---
description: "Richten Sie SAML-2.0-Single-Sign-On für SnapOtter ein. Schritt-für-Schritt-Anleitungen für Okta, Azure AD / Entra ID, Google Workspace und andere SAML-Identitätsanbieter."
i18n_source_hash: 33dfb8b02a22
i18n_provenance: human
i18n_output_hash: 29cf0d9ed663
---
# SAML SSO {#saml-sso}
SnapOtter unterstützt SAML 2.0 für Single Sign-On. Benutzer können sich über einen externen Identitätsanbieter (Okta, Azure AD / Entra ID, Google Workspace oder einen beliebigen standardkonformen SAML-2.0-IdP) anmelden, anstatt sich lokal mit Benutzername/Passwort zu authentifizieren.
::: tip Enterprise-Funktion
SAML SSO erfordert eine **team**- oder **enterprise**-Lizenz mit der Funktion `saml_sso`. Wenn `SAML_ENABLED=true` ohne gültige Lizenz gesetzt ist, werden die SAML-Routen stillschweigend übersprungen und eine Warnung protokolliert.
:::
## Voraussetzungen {#prerequisites}
- Eine laufende SnapOtter-Instanz, die unter einer öffentlichen URL erreichbar ist
- `EXTERNAL_URL` auf diese öffentliche URL gesetzt (z. B. `https://photos.example.com`)
- Ein team- oder enterprise-Lizenzschlüssel mit der Funktion `saml_sso`
- Admin-Zugriff auf Ihren SAML-Identitätsanbieter
## Schnellstart {#quick-start}
Fügen Sie diese Umgebungsvariablen zu Ihrer `docker-compose.yml` hinzu:
```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
```
Starten Sie den Container neu. Eine Schaltfläche "Mit SAML anmelden" (oder die durch `SAML_PROVIDER_NAME` gesetzte Bezeichnung) erscheint auf der Anmeldeseite.
## Konfigurationsreferenz {#configuration-reference}
| Variable | Standard | Beschreibung |
|---|---|---|
| `SAML_ENABLED` | `false` | SAML-Anmeldung aktivieren. |
| `SAML_IDP_SSO_URL` | | SSO-Endpunkt-URL des IdP. **Erforderlich**, wenn SAML aktiviert ist. |
| `SAML_IDP_CERTIFICATE` | | X.509-Signaturzertifikat des IdP im PEM-Format (der Zertifikatstext selbst, nicht ein Dateipfad). **Erforderlich**, wenn SAML aktiviert ist. |
| `EXTERNAL_URL` | | Die öffentliche URL, unter der SnapOtter erreichbar ist. **Erforderlich**, wenn SAML aktiviert ist. |
| `SAML_ENTITY_ID` | `${EXTERNAL_URL}/api/auth/saml/metadata` | An den IdP gesendete SP-Entity-ID / Audience-URI. |
| `SAML_CALLBACK_URL` | `${EXTERNAL_URL}/api/auth/saml/callback` | Assertion-Consumer-Service-URL (ACS). |
| `SAML_AUTO_CREATE_USERS` | `true` | Bei der ersten SAML-Anmeldung automatisch ein lokales Benutzerkonto erstellen. |
| `SAML_AUTO_LINK_USERS` | `false` | Eine SAML-Identität mit einem bestehenden lokalen Benutzer verknüpfen, wenn die E-Mail-Adresse übereinstimmt. |
| `SAML_DEFAULT_ROLE` | `user` | Rolle, die automatisch erstellten SAML-Benutzern zugewiesen wird. Eine von `admin`, `editor` oder `user`. |
| `SAML_PROVIDER_NAME` | | Anzeigebezeichnung für die SAML-Anmeldeschaltfläche im Frontend (z. B. "Okta", "Azure AD"). Wenn leer, zeigt die Schaltfläche "SAML" an. |
| `SAML_USERNAME_ATTRIBUTE` | | SAML-Assertion-Attribut, das als Benutzername verwendet wird. Wenn leer, wird auf den lokalen Teil der E-Mail und dann auf die NameID zurückgegriffen. |
| `SAML_EMAIL_ATTRIBUTE` | `email` | SAML-Assertion-Attribut, das als E-Mail-Adresse des Benutzers verwendet wird. |
Der Server startet nicht, wenn `SAML_ENABLED=true` und eine der drei erforderlichen Variablen (`SAML_IDP_SSO_URL`, `SAML_IDP_CERTIFICATE`, `EXTERNAL_URL`) fehlt.
::: details Sicherheitshinweise
Sowohl `wantAuthnResponseSigned` als auch `wantAssertionsSigned` sind fest auf `true` codiert. SnapOtter lehnt unsignierte oder unsachgemäß signierte SAML-Antworten ab. Assertions von einem vertrauenswürdigen IdP werden als E-Mail-verifiziert behandelt.
Es wird nur SP-initiierte Anmeldung unterstützt. SnapOtter unterstützt keine IdP-initiierte (unaufgeforderte) Anmeldung und kein Single Logout (SLO). Das Abmelden von SnapOtter meldet den Benutzer nicht vom IdP ab.
:::
## SP-Metadaten und URLs {#sp-metadata-and-urls}
Ihr IdP benötigt drei Werte von SnapOtter:
| Feld | Wert |
|---|---|
| **ACS-URL** (Assertion Consumer Service) | `${EXTERNAL_URL}/api/auth/saml/callback` |
| **Entity-ID** / **Audience-URI** | `${EXTERNAL_URL}/api/auth/saml/metadata` |
| **SP-Metadaten** (XML) | `GET ${EXTERNAL_URL}/api/auth/saml/metadata` |
Wenn `EXTERNAL_URL` beispielsweise `https://photos.example.com` ist:
- ACS-URL: `https://photos.example.com/api/auth/saml/callback`
- Entity-ID: `https://photos.example.com/api/auth/saml/metadata`
- Metadaten-Endpunkt: `https://photos.example.com/api/auth/saml/metadata` (gibt XML zurück)
Manche IdPs können die SP-Metadaten-URL direkt importieren, wodurch die ACS-URL und die Entity-ID automatisch ausgefüllt werden.
## Anbietereinrichtung {#provider-setup}
### Okta {#okta}
1. Gehen Sie in der Okta-Admin-Konsole zu **Applications > Create App Integration**.
2. Wählen Sie **SAML 2.0** und klicken Sie auf **Next**.
3. Legen Sie einen Namen fest (z. B. "SnapOtter") und klicken Sie auf **Next**.
4. Konfigurieren Sie die SAML-Einstellungen:
- **Single sign-on URL**: Ihre ACS-URL (z. B. `https://photos.example.com/api/auth/saml/callback`)
- **Audience URI (SP Entity ID)**: Ihre Entity-ID (z. B. `https://photos.example.com/api/auth/saml/metadata`)
- **Name ID format**: EmailAddress
- **Application username**: Email
5. Fügen Sie unter **Attribute Statements** `email` hinzu, zugeordnet zu `user.email`.
6. Klicken Sie auf **Next**, dann auf **Finish**.
7. Gehen Sie zur Registerkarte **Sign On**, klicken Sie auf **View SAML setup instructions** und kopieren Sie:
- **Identity Provider Single Sign-On URL** in `SAML_IDP_SSO_URL`
- **X.509 Certificate** in `SAML_IDP_CERTIFICATE`
### Azure AD / Entra ID {#azure-ad-entra-id}
1. Gehen Sie im Azure-Portal zu **Microsoft Entra ID > Enterprise applications > New application**.
2. Klicken Sie auf **Create your own application**, benennen Sie sie "SnapOtter" und wählen Sie **Integrate any other application you don't find in the gallery**.
3. Gehen Sie zu **Single sign-on > SAML** und klicken Sie im Abschnitt **Basic SAML Configuration** auf **Edit**:
- **Identifier (Entity ID)**: Ihre Entity-ID (z. B. `https://photos.example.com/api/auth/saml/metadata`)
- **Reply URL (ACS URL)**: Ihre ACS-URL (z. B. `https://photos.example.com/api/auth/saml/callback`)
4. Laden Sie unter **SAML Certificates** das **Certificate (Base64)** herunter.
5. Kopieren Sie unter **Set up SnapOtter** die **Login URL**.
6. Setzen Sie `SAML_IDP_SSO_URL` auf die Login-URL und `SAML_IDP_CERTIFICATE` auf den Inhalt des heruntergeladenen Zertifikats.
7. Weisen Sie der Anwendung unter **Users and groups** Benutzer oder Gruppen zu.
### Google Workspace {#google-workspace}
1. Gehen Sie in der Google-Admin-Konsole zu **Apps > Web and mobile apps > Add app > Add custom SAML app**.
2. Benennen Sie die App "SnapOtter" und klicken Sie auf **Continue**.
3. Kopieren Sie auf der Seite **Google Identity Provider details** die **SSO URL** und laden Sie das **Certificate** herunter. Klicken Sie auf **Continue**.
4. Konfigurieren Sie die Service-Provider-Details:
- **ACS URL**: Ihre ACS-URL (z. B. `https://photos.example.com/api/auth/saml/callback`)
- **Entity ID**: Ihre Entity-ID (z. B. `https://photos.example.com/api/auth/saml/metadata`)
- **Name ID format**: EMAIL
- **Name ID**: Basic Information > Primary email
5. Klicken Sie auf **Continue**, dann auf **Finish**.
6. Schalten Sie die App für Ihre Organisationseinheiten **ON**.
7. Setzen Sie `SAML_IDP_SSO_URL` auf die SSO-URL aus Schritt 3 und `SAML_IDP_CERTIFICATE` auf den Inhalt des heruntergeladenen Zertifikats.
### Generischer SAML-2.0-IdP {#generic-saml-2-0-idp}
Für jeden SAML-2.0-konformen Identitätsanbieter:
1. Erstellen Sie eine neue SAML-Anwendung/einen neuen Service Provider in Ihrem IdP.
2. Setzen Sie die **ACS-URL** auf `${EXTERNAL_URL}/api/auth/saml/callback`.
3. Setzen Sie die **Entity-ID** / **Audience** auf `${EXTERNAL_URL}/api/auth/saml/metadata`.
4. Konfigurieren Sie den IdP so, dass er die E-Mail des Benutzers in einem Attribut namens `email` sendet (oder setzen Sie `SAML_EMAIL_ATTRIBUTE`, damit es dem Attributnamen Ihres IdP entspricht).
5. Kopieren Sie die **IdP-SSO-URL** und das **Signaturzertifikat** in `SAML_IDP_SSO_URL` und `SAML_IDP_CERTIFICATE`.
## Benutzerbereitstellung {#user-provisioning}
### Automatisches Erstellen {#auto-create}
Wenn `SAML_AUTO_CREATE_USERS` auf `true` (die Voreinstellung) steht, wird beim ersten Anmelden per SAML ein lokales Benutzerkonto erstellt. Die Rolle wird auf `SAML_DEFAULT_ROLE` gesetzt.
Der Benutzername wird in dieser Reihenfolge abgeleitet:
1. Der Wert des durch `SAML_USERNAME_ATTRIBUTE` angegebenen Assertion-Attributs (falls gesetzt und vorhanden)
2. Der lokale Teil der E-Mail-Adresse (alles vor `@`)
3. Die SAML-NameID
Bei einer Kollision des Benutzernamens wird ein numerisches Suffix angehängt (z. B. `jane` wird zu `jane_2`).
### Automatisches Verknüpfen {#auto-link}
Wenn `SAML_AUTO_LINK_USERS` auf `true` steht, verknüpft SnapOtter eine SAML-Identität mit einem bestehenden lokalen Konto, wenn die E-Mail-Adressen übereinstimmen. Das ist nützlich, wenn Sie Benutzerkonten vorab erstellt haben und möchten, dass diese SSO nutzen, ohne ihre Daten zu verlieren.
::: warning
Aktivieren Sie das automatische Verknüpfen nur, wenn Sie Ihrem SAML-IdP zutrauen, E-Mail-Adressen zu verifizieren. Eine nicht verifizierte E-Mail von einem fehlkonfigurierten IdP könnte es jemandem ermöglichen, das Konto eines anderen Benutzers zu übernehmen.
:::
### Attributzuordnung {#attribute-mapping}
| SnapOtter-Feld | Quelle | Konfiguration |
|---|---|---|
| E-Mail | Assertion-Attribut | `SAML_EMAIL_ATTRIBUTE` (Standard: `email`) |
| Benutzername | Assertion-Attribut, E-Mail oder NameID | `SAML_USERNAME_ATTRIBUTE` (siehe Ableitungsreihenfolge oben) |
| Externe ID | NameID | Immer die SAML-NameID, nicht konfigurierbar |
## SSO-Erzwingung {#sso-enforcement}
Wenn Sie verlangen möchten, dass sich alle Benutzer per SAML (oder OIDC) anmelden, und die lokale Passwortanmeldung blockieren möchten, aktivieren Sie die SSO-Erzwingung:
1. Stellen Sie sicher, dass die Enterprise-Funktion `sso_enforcement` lizenziert ist (verfügbar in den team- und enterprise-Plänen).
2. Schalten Sie unter **Admin-Einstellungen > Sicherheit** die **SSO-Erzwingung** ein.
3. Legen Sie einen **Break-Glass-Benutzernamen** fest: Dies ist das eine lokale Konto, das sich bei Notfallzugriff weiterhin mit einem Passwort anmelden kann, falls der IdP nicht erreichbar ist.
Wenn die SSO-Erzwingung aktiv ist, gibt jeder lokale Anmeldeversuch (außer für den Break-Glass-Benutzer) einen 403-Fehler mit der Meldung "Local password login is disabled. Please use SSO." zurück.
::: tip
Konfigurieren Sie immer einen Break-Glass-Benutzernamen, bevor Sie die SSO-Erzwingung aktivieren. Andernfalls könnten Sie aus SnapOtter ausgesperrt werden, falls Ihr IdP ausfällt.
:::
## SAML zusammen mit OIDC verwenden {#using-saml-alongside-oidc}
SAML und OIDC können gleichzeitig aktiviert werden. Wenn beide aktiv sind, zeigt die Anmeldeseite separate Schaltflächen für jeden Anbieter an (beschriftet durch `SAML_PROVIDER_NAME` und `OIDC_PROVIDER_NAME`). Benutzer können sich mit beiden Methoden anmelden.
Beide Anbieter teilen sich die Einstellungen für automatisches Erstellen, automatisches Verknüpfen und SSO-Erzwingung unabhängig voneinander: Jeder hat seine eigenen Variablen `*_AUTO_CREATE_USERS`, `*_AUTO_LINK_USERS` und `*_DEFAULT_ROLE`.
## Fehlerbehebung {#troubleshooting}
### Assertion-Validierung fehlgeschlagen {#assertion-validation-failed}
Die Signatur der SAML-Antwort oder der Assertion konnte nicht verifiziert werden. Prüfen Sie:
- Das Zertifikat in `SAML_IDP_CERTIFICATE` stimmt mit dem aktuellen Signaturzertifikat in Ihrem IdP überein (Zertifikate rotieren, prüfen Sie also auf Ablauf)
- Das Zertifikat ist im PEM-Format (beginnt mit `-----BEGIN CERTIFICATE-----`)
- Das Zertifikat ist der vollständige Text, kein Dateipfad
- Die in Ihrem IdP konfigurierte ACS-URL und Entity-ID stimmen exakt mit den Werten von SnapOtter überein (Schema, Host, Port, Pfad)
### Fehlende Attribute {#missing-attributes}
Wenn Benutzernamen oder E-Mails nach der Anmeldung leer sind, sendet Ihr IdP möglicherweise nicht die erwarteten Attribute. Prüfen Sie:
- Ihr IdP ist so konfiguriert, dass er ein Attribut `email` freigibt (oder das, worauf `SAML_EMAIL_ATTRIBUTE` gesetzt ist)
- Wenn Sie `SAML_USERNAME_ATTRIBUTE` verwenden, prüfen Sie, ob dieses Attribut in der Assertion enthalten ist
- Manche IdPs erfordern eine explizite Attributzuordnungskonfiguration, bevor sie Claims freigeben
### Uhrenabweichung {#clock-skew}
SAML-Assertions enthalten Zeitstempelbedingungen (`NotBefore`, `NotOnOrAfter`). Wenn die Uhr Ihres Servers und die Uhr des IdP nicht synchron sind, schlägt die Assertion-Validierung fehl. Führen Sie NTP auf beiden Maschinen aus, um die Uhren aufeinander abzustimmen.
### "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}
Diese Warnung erscheint in den Serverprotokollen, wenn `SAML_ENABLED=true`, die Lizenz aber die Funktion `saml_sso` nicht enthält. Überprüfen Sie Ihren Lizenzschlüssel und Plan. Die Funktion `saml_sso` ist in den team- und enterprise-Plänen verfügbar.
### Anmeldung leitet mit Fehler zurück {#login-redirects-back-with-error}
Wenn das Klicken auf die SAML-Anmeldeschaltfläche mit einem Fehler zurück auf die Anmeldeseite leitet, prüfen Sie die Serverprotokolle auf Details. Häufige Ursachen:
- Die IdP-SSO-URL ist vom Server aus nicht erreichbar
- Der IdP hat die Authentifizierungsanfrage abgelehnt (prüfen Sie die Audit-Protokolle des IdP)
- Der IdP hat eine unsignierte Antwort zurückgegeben (SnapOtter verlangt, dass sowohl die Antwort als auch die Assertion signiert sind)
+298
View File
@@ -0,0 +1,298 @@
---
description: "Richten Sie SCIM-2.0-Provisionierung ein, um Benutzer und Gruppen von Ihrem Identitätsanbieter mit SnapOtter zu synchronisieren. Behandelt Okta, Azure AD / Entra ID und benutzerdefinierte Integrationen."
i18n_source_hash: bbd50119ec12
i18n_provenance: human
i18n_output_hash: 58dab63bf748
---
# SCIM-Provisionierung {#scim-provisioning}
SnapOtter implementiert SCIM 2.0 (System for Cross-domain Identity Management) für die automatisierte Provisionierung von Benutzern und Gruppen. Ihr Identitätsanbieter kann Benutzerkonten automatisch erstellen, aktualisieren, deaktivieren und reaktivieren sowie Gruppenmitgliedschaften synchronisieren.
::: tip Enterprise-Funktion
Die SCIM-Provisionierung erfordert eine **Enterprise**-Lizenz mit der Funktion `scim`. Sie ist im Team-Plan nicht verfügbar. Ohne diese Funktion geben alle SCIM-Endpunkte (außer Discovery) 403 zurück.
:::
## Voraussetzungen {#prerequisites}
- Eine laufende SnapOtter-Instanz, die unter einer öffentlichen URL erreichbar ist
- Einen Enterprise-Lizenzschlüssel mit der Funktion `scim`
- Admin-Zugriff auf SnapOtter (die Berechtigung `users:manage` ist erforderlich, um ein SCIM-Token zu erzeugen oder zu widerrufen)
- Admin-Zugriff auf die Provisionierungseinstellungen Ihres Identitätsanbieters
## Schnellstart {#quick-start}
1. Erzeugen Sie ein 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"
```
Die Antwort enthält das Token. Speichern Sie es sofort; es kann nicht erneut abgerufen werden.
```json
{
"token": "a1b2c3d4e5f6...",
"message": "Save this token - it cannot be retrieved again"
}
```
2. Konfigurieren Sie in Ihrem Identitätsanbieter die SCIM-Provisionierung mit:
- **Basis-URL**: `https://photos.example.com/api/v1/scim/v2`
- **Authentifizierung**: Bearer-Token (fügen Sie das Token aus Schritt 1 ein)
## Authentifizierung {#authentication}
SCIM-Endpunkte verwenden ein dediziertes Bearer-Token, getrennt von Benutzersitzungen und API-Schlüsseln.
### Ein Token erzeugen {#generating-a-token}
`POST /api/v1/enterprise/scim/token` erzeugt ein neues SCIM-Token. Dieser Endpunkt erfordert eine gültige Sitzung mit der Berechtigung `users:manage`.
Das Token wird genau einmal im Klartext zurückgegeben. SnapOtter speichert nur einen scrypt-Hash. Wenn Sie das Token verlieren, widerrufen Sie es und erzeugen ein neues.
Es ist immer nur ein SCIM-Token gleichzeitig aktiv. Das Erzeugen eines neuen Tokens ersetzt das vorherige.
### Ein Token widerrufen {#revoking-a-token}
`DELETE /api/v1/enterprise/scim/token` widerruft das aktuelle SCIM-Token. Dieser Endpunkt erfordert ebenfalls `users:manage`.
### Ratenbegrenzung {#rate-limiting}
SCIM-Endpunkte sind auf 1000 Anfragen pro Minute und Token begrenzt. Ein Überschreiten dieses Limits gibt HTTP 429 zurück.
## Unterstützte Ressourcen {#supported-resources}
| SCIM-Ressource | SnapOtter-Konzept | Erstellen | Lesen | Aktualisieren | Löschen |
|---|---|---|---|---|---|
| User | Benutzerkonto | Ja | Ja | Ja | Soft Delete |
| Group | Team | Ja | Ja | Ja | Ja |
::: warning
SCIM-Groups werden SnapOtter-**Teams** zugeordnet, nicht Rollen. SCIM kann die Rolle eines Benutzers nicht festlegen. Alle über SCIM erstellten Benutzer erhalten die Rolle `user`. Um die Rolle eines Benutzers zu ändern, verwenden Sie die SnapOtter-Admin-UI.
:::
## Benutzeroperationen {#user-operations}
### Benutzer erstellen {#create-user}
`POST /api/v1/scim/v2/Users`
Erstellt ein neues Benutzerkonto mit `authProvider` auf `scim` und der Rolle `user`. Der Benutzer wird dem Default-Team zugewiesen. Wenn `active` gleich `false` ist, wird stattdessen die Rolle auf `disabled` gesetzt.
Erforderliche Attribute: `userName`. Optional: `externalId`, `emails`, `active` (Standard `true`).
### Benutzer auflisten und filtern {#list-and-filter-users}
`GET /api/v1/scim/v2/Users`
Gibt eine paginierte Liste von Benutzern zurück. Unterstützt die Abfrageparameter `startIndex` und `count` (maximal 200 Ergebnisse pro Seite).
Die Filterung unterstützt nur `eq` (gleich), und zwar für diese Attribute:
- `userName eq "jane"`
- `externalId eq "ext-12345"`
Andere Filteroperatoren und Attribute geben HTTP 400 zurück.
### Benutzer abrufen {#get-user}
`GET /api/v1/scim/v2/Users/:id`
Gibt einen einzelnen Benutzer anhand seiner SnapOtter-Benutzer-ID zurück.
### Benutzer ersetzen {#replace-user}
`PUT /api/v1/scim/v2/Users/:id`
Ersetzt die Attribute des Benutzers. Unterstützt `userName`, `externalId`, `emails` und `active`. Änderungen des Benutzernamens werden auf Konflikte geprüft (409, wenn der neue Benutzername bereits von einem anderen Benutzer belegt ist).
### Benutzer patchen {#patch-user}
`PATCH /api/v1/scim/v2/Users/:id`
Teilweise Aktualisierung über SCIM PatchOp. Unterstützte Operationen:
| Operation | Pfade |
|---|---|
| `replace` | `active`, `userName`, `externalId`, `emails`, `emails[type eq "work"].value`, `name.formatted`, `displayName` |
| `add` | Wie `replace` |
| `remove` | `externalId`, `emails` |
Die Pfade `name.formatted` und `displayName` werden aus Kompatibilitätsgründen akzeptiert, haben aber keine dauerhafte Wirkung (SnapOtter speichert keinen separaten Anzeigenamen).
Wertlose `replace`-Operationen (bei denen der Wert ein Objekt ohne `path` ist) werden ebenfalls unterstützt, mit den Schlüsseln `userName`, `externalId`, `emails` und `active`.
### Benutzer deaktivieren (Soft Delete) {#deactivate-user-soft-delete}
`DELETE /api/v1/scim/v2/Users/:id`
SnapOtter löscht Benutzer über SCIM nicht endgültig. Stattdessen führt DELETE eine sanfte Deaktivierung durch:
1. Die Rolle des Benutzers wird von ihrem aktuellen Wert (z. B. `editor`) auf `disabled:editor` geändert, wobei die ursprüngliche Rolle erhalten bleibt.
2. Das Passwort des Benutzers wird gelöscht.
3. Alle aktiven Sitzungen werden widerrufen.
4. Alle API-Schlüssel werden widerrufen.
Der Benutzer kann sich nicht mehr anmelden und keine API-Schlüssel mehr verwenden. Seine Daten (Dateien, Verlauf) bleiben erhalten.
### Benutzer reaktivieren {#reactivate-user}
Um einen zuvor deaktivierten Benutzer zu reaktivieren, senden Sie eine `PUT`- oder `PATCH`-Anfrage mit `active: true`. SnapOtter stellt die ursprüngliche Rolle von vor der Deaktivierung wieder her (z. B. wird aus `disabled:editor` wieder `editor`). Wenn die ursprüngliche Rolle nicht ermittelt werden kann, wird auf `user` zurückgegriffen.
::: details Beispiel: Deaktivieren und Reaktivieren per 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 }
]
}
```
:::
## Gruppenoperationen {#group-operations}
SCIM-Groups werden SnapOtter-Teams zugeordnet. Das Erstellen einer Gruppe erstellt ein Team. Die Gruppenmitgliedschaft steuert, zu welchem Team ein Benutzer gehört.
### Gruppe erstellen {#create-group}
`POST /api/v1/scim/v2/Groups`
Erforderlich: `displayName`. Optional: `members` (Array von `{ value: userId }`).
### Gruppen auflisten und filtern {#list-and-filter-groups}
`GET /api/v1/scim/v2/Groups`
Die Filterung unterstützt nur `displayName eq "..."`. Paginiert mit `startIndex` und `count` (maximal 200 Ergebnisse pro Seite).
### Gruppe abrufen {#get-group}
`GET /api/v1/scim/v2/Groups/:id`
### Gruppe ersetzen {#replace-group}
`PUT /api/v1/scim/v2/Groups/:id`
Ersetzt den Gruppennamen und die vollständige Mitgliederliste. Vorhandene Mitglieder, die nicht in der neuen Liste stehen, werden in das Default-Team verschoben.
### Gruppe patchen {#patch-group}
`PATCH /api/v1/scim/v2/Groups/:id`
Unterstützt diese Operationen:
| Operation | Pfad | Effekt |
|---|---|---|
| `add` | `members` | Fügt Benutzer zum Team hinzu |
| `remove` | `members[value eq "userId"]` | Verschiebt den Benutzer in das Default-Team |
| `replace` | `displayName` | Benennt das Team um |
| `replace` | `members` | Ersetzt alle Mitglieder (entfernte Mitglieder werden in das Default-Team verschoben) |
### Gruppe löschen {#delete-group}
`DELETE /api/v1/scim/v2/Groups/:id`
Löscht das Team. Alle Mitglieder des gelöschten Teams werden in das Default-Team verschoben. Benutzer werden nicht deaktiviert oder gelöscht.
## IdP-Einrichtung {#idp-setup}
### Okta {#okta}
1. Öffnen Sie in der Okta-Admin-Konsole Ihre SnapOtter-Anwendung (oder erstellen Sie eine).
2. Gehen Sie zum Tab **Provisioning** und klicken Sie auf **Configure API Integration**.
3. Aktivieren Sie **Enable API Integration** und geben Sie ein:
- **Base URL**: `https://photos.example.com/api/v1/scim/v2`
- **API Token**: Das oben erzeugte SCIM-Bearer-Token
4. Klicken Sie auf **Test API Credentials** und dann auf **Save**.
5. Aktivieren Sie unter **Provisioning > To App**:
- **Create Users**
- **Update User Attributes**
- **Deactivate Users**
6. Konfigurieren Sie unter **Push Groups**, welche Okta-Gruppen als SnapOtter-Teams synchronisiert werden sollen.
### Azure AD / Entra ID {#azure-ad-entra-id}
1. Gehen Sie im Azure-Portal zu Ihrer SnapOtter-Enterprise-Anwendung.
2. Gehen Sie zu **Provisioning** und setzen Sie **Provisioning Mode** auf **Automatic**.
3. Geben Sie unter **Admin Credentials** ein:
- **Tenant URL**: `https://photos.example.com/api/v1/scim/v2`
- **Secret Token**: Das oben erzeugte SCIM-Bearer-Token
4. Klicken Sie auf **Test Connection** und dann auf **Save**.
5. Konfigurieren Sie unter **Mappings** die Attributzuordnungen für Benutzer und Gruppen. Die Standardwerte funktionieren in der Regel, prüfen Sie aber, dass `userName` wie gewünscht auf `userPrincipalName` oder `mail` abgebildet wird.
6. Setzen Sie **Provisioning Status** auf **On** und speichern Sie.
Azure provisioniert Benutzer und Gruppen in einem festen Synchronisationszyklus (typischerweise alle 40 Minuten).
## Discovery-Endpunkte {#discovery-endpoints}
Diese drei Endpunkte sind ohne Authentifizierung verfügbar und beschreiben die Fähigkeiten des SCIM-Servers:
| Endpunkt | Beschreibung |
|---|---|
| `GET /api/v1/scim/v2/ServiceProviderConfig` | Serverfähigkeiten und unterstützte Funktionen |
| `GET /api/v1/scim/v2/Schemas` | Schemadefinitionen für User und Group |
| `GET /api/v1/scim/v2/ResourceTypes` | Verfügbare Ressourcentypen (User, Group) |
`ServiceProviderConfig` bewirbt diese Fähigkeiten:
| Funktion | Unterstützt |
|---|---|
| Patch | Ja |
| Bulk | Nein |
| Filter | Ja (max. 200 Ergebnisse, nur `eq`-Operator) |
| Passwort ändern | Nein |
| Sort | Nein |
| ETag | Nein |
## Einschränkungen {#limitations}
- **Filterung**: Nur der `eq`-Operator wird unterstützt. Komplexe Filter, die Operatoren `and`/`or`, `co` (enthält) und `sw` (beginnt mit) sind nicht implementiert.
- **Massenoperationen**: Nicht unterstützt.
- **Sort und ETag**: Nicht unterstützt.
- **Rollen**: SCIM kann keine SnapOtter-Rollen zuweisen. Alle provisionierten Benutzer erhalten die Rolle `user`.
- **MAX_USERS**: Die Grenze der Umgebungsvariablen `MAX_USERS` wird bei der SCIM-Benutzererstellung nicht durchgesetzt. Wenn Sie die Benutzeranzahl begrenzen müssen, verwalten Sie die Zuweisungen in Ihrem IdP.
- **Ein Token**: Es kann immer nur ein SCIM-Token gleichzeitig aktiv sein. Wenn mehrere IdPs SCIM-Zugriff benötigen, müssen sie sich das Token teilen.
- **Gruppen sind Teams**: SCIM-Groups entsprechen Teams, nicht Rollen oder Berechtigungsgruppen.
## Fehlerbehebung {#troubleshooting}
### 403 "SCIM provisioning requires an enterprise license with the scim feature" {#_403-scim-provisioning-requires-an-enterprise-license-with-the-scim-feature}
Ihre Lizenz enthält die Funktion `scim` nicht, oder es ist keine Lizenz konfiguriert. SCIM erfordert eine Enterprise-Plan-Lizenz. Stellen Sie sicher, dass `SNAPOTTER_LICENSE_KEY` gesetzt ist und die Lizenz die Funktion `scim` enthält.
### 401 "Bearer token required" {#_401-bearer-token-required}
Die SCIM-Anfrage enthielt keinen `Authorization: Bearer <token>`-Header. Prüfen Sie die Provisionierungskonfiguration Ihres IdP.
### 401 "Invalid token" {#_401-invalid-token}
Das Token stimmt nicht mit dem gespeicherten Hash überein. Das passiert, wenn das Token widerrufen und neu erzeugt wurde. Aktualisieren Sie das Token in den Provisionierungseinstellungen Ihres IdP.
### 401 "SCIM not configured" {#_401-scim-not-configured}
Es wurde noch kein SCIM-Token erzeugt. Verwenden Sie den Endpunkt `POST /api/v1/enterprise/scim/token`, um eines zu erstellen.
### 409 "User already exists" / "userName already taken" {#_409-user-already-exists-username-already-taken}
Ein Benutzer mit demselben Benutzernamen existiert bereits. Das kann passieren, wenn ein IdP eine fehlgeschlagene Erstellung wiederholt. Prüfen Sie im SnapOtter-Admin-Panel auf doppelte Benutzernamen.
### 429 "SCIM rate limit exceeded" {#_429-scim-rate-limit-exceeded}
Der IdP sendet mehr als 1000 Anfragen pro Minute. Das passiert typischerweise während einer großen initialen Synchronisation. Die meisten IdPs wiederholen automatisch, nachdem das Zeitfenster der Ratenbegrenzung zurückgesetzt wurde. Wenn das Problem weiterhin besteht, prüfen Sie das Provisionierungs-Synchronisationsintervall Ihres IdP.
### Benutzer wurden deprovisioniert, aber nicht aus der UI entfernt {#users-deprovisioned-but-not-removed-from-the-ui}
SCIM DELETE ist eine sanfte Deaktivierung. Deaktivierte Benutzer erscheinen weiterhin in der Admin-Benutzerliste mit einem deaktivierten Status. Das ist so beabsichtigt, damit ihre Daten erhalten bleiben. Ihre Rolle wird als `disabled:<original-role>` angezeigt.
+339
View File
@@ -0,0 +1,339 @@
---
description: "Leitfaden zur Sicherheitshärtung für SnapOtter. Container-Sicherheit, Netzwerkisolierung, Docker-Secrets, Kubernetes-Deployment und Compliance-Artefakte."
i18n_source_hash: 986f7658430c
i18n_provenance: machine
i18n_output_hash: 807a330f6ec7
---
# Sicherheit & Härtung {#security-hardening}
SnapOtter verarbeitet Dateien vollständig auf deiner Infrastruktur. Es sendet standardmäßig anonyme, inhaltsfreie Produkt-Analytics und Absturzberichte, um das Projekt zu verbessern. Es sendet niemals deine Dateien, Dateinamen, Dateiinhalte, OCR-Ausgaben, Bild-Metadaten oder Dokumenttext. Optionales Feedback wird nur gesendet, nachdem ein Benutzer es abgeschickt hat, nur wenn Analytics aktiviert ist, und Kontaktfelder werden nur mit ausdrücklicher Kontaktzustimmung einbezogen. Ein Administrator kann Analytics und Feedback-Erfassung mit einem Klick unter Einstellungen > System > Datenschutz ausschalten, kein Neuaufbau erforderlich. Die Dateiverarbeitung bleibt immer innerhalb deines Containers.
Der Container läuft als dedizierter Non-Root-Benutzer (`snapotter`) mit allen entfernten Linux-Capabilities außer dem minimal erforderlichen Satz. Für die vollständige Richtlinie zur Offenlegung von Schwachstellen und die Sicherheitsarchitektur siehe [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) auf GitHub.
## Container-Härtung {#container-hardening}
Die [Standard-docker-compose.yml](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose.yml) enthält Produktions-Sicherheitshärtung. Hier ist eine Aufschlüsselung jeder Option und warum sie wichtig ist:
```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:
```
### Warum `no-new-privileges` nicht gesetzt ist {#why-no-new-privileges-is-not-set}
`security_opt: [no-new-privileges:true]` wird bewusst weggelassen. Der Entrypoint startet als root, um die Volume-Eigentümerschaft zu korrigieren, und fällt dann über [gosu](https://github.com/tianon/gosu) auf den `snapotter`-Benutzer zurück, was setuid erfordert. Sobald der Privilegienabwurf abgeschlossen ist, läuft der Prozess als `snapotter` mit allen entfernten Capabilities außer den fünf oben aufgeführten.
Wenn du Kubernetes oder Dockers `--user`-Flag verwendest, um direkt als Non-Root zu laufen (und gosu zu umgehen), kann `no-new-privileges` sicher aktiviert werden.
### Warum `read_only` nicht gesetzt ist {#why-read-only-is-not-set}
`read_only: true` ist nicht gesetzt, weil die PUID/PGID-Neuzuordnung beim Start nach `/etc/passwd` und `/etc/group` schreibt. Wenn du Dockers `--user`-Flag oder Kubernetes `runAsUser` anstelle von PUID/PGID verwendest, kannst du ein schreibgeschütztes Root-Dateisystem sicher aktivieren.
## Netzwerkisolierung {#network-isolation}
Während des normalen Betriebs stellt der Container **null ausgehende Netzwerkverbindungen** her. Die gesamte Dateiverarbeitung geschieht lokal mit mitgelieferten Bibliotheken.
```
Browser --> Reverse Proxy (TLS) --> SnapOtter container --> (nothing)
```
Die einzige Ausnahme sind **KI-Modell-Downloads**: Wenn ein Benutzer ein KI-Feature-Bundle über die Oberfläche installiert, lädt der Container das vorgefertigte Bundle-Archiv von Hugging Face herunter, plus einige einzelne Modelldateien von GitHub Releases, Google Storage und PyPI. Diese Downloads geschehen einmal pro Bundle und werden im `/data`-Volume gespeichert.
**Firewall-Empfehlungen:**
| Szenario | Ausgehende Regel |
|---|---|
| Air-Gapped (keine KI) | Blockiere allen ausgehenden Verkehr vom Container |
| KI-Bundles benötigt | Erlaube HTTPS zu `huggingface.co`, `*.xethub.hf.co`, `cdn-lfs.huggingface.co`, `github.com`, `objects.githubusercontent.com`, `storage.googleapis.com`, `pypi.org`, `files.pythonhosted.org` während der Installation, dann blockieren |
| Nach der KI-Installation | Blockiere allen ausgehenden Verkehr - Modelle sind lokal zwischengespeichert |
Bundle-Archive werden aus dem Xet-Storage von Hugging Face bereitgestellt, das über die `*.xethub.hf.co`-Endpunkte parallel überträgt und die Multi-GB-Bundle-Downloads schnell macht. Wenn deine Firewall `huggingface.co` erlaubt, aber `*.xethub.hf.co` blockiert, gelingen die Installationen dennoch, fallen aber auf einen langsameren Single-Stream-Download zurück; setze also die Xet-Hosts auf die Allowlist, um auf dem schnellen Pfad zu bleiben. Vollständig offline durchgeführte Installationen können all dies überspringen und stattdessen den [Offline-Bundle-Import](/de/guide/deployment) verwenden.
Für die Reverse-Proxy-Konfiguration (Nginx, Traefik, Caddy, Cloudflare Tunnels) siehe den [Deployment-Leitfaden](/de/guide/deployment#reverse-proxy).
## Docker-Secrets {#docker-secrets}
Für Produktions-Deployments solltest du das Übergeben von Secrets als Klartext-Umgebungsvariablen vermeiden. Der Entrypoint unterstützt Dockers `_FILE`-Konvention: Binde ein Secret als Datei ein und setze die entsprechende `_FILE`-Variable auf ihren Pfad.
**Unterstützte Secrets:**
| Variable | `_FILE`-Entsprechung |
|---|---|
| `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` |
**Beispiel mit 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 (ohne Swarm) erfordern Compose v2.23 oder neuer.
:::
## Kubernetes-Deployment {#kubernetes-deployment}
Der Entrypoint erkennt, wenn der Container bereits als Non-Root läuft (z. B. über Kubernetes `runAsUser`), und überspringt den gosu-Privilegienabwurf automatisch. In diesem Fall kann er die eingebundenen Volumes nicht selbst chownen, daher überprüft er, ob sie beschreibbar sind, und beendet sich frühzeitig mit umsetzbaren Hinweisen, falls nicht - siehe [Speicherberechtigungen](/de/guide/deployment#storage-permissions) für `fsGroup`- und Fremd-UID-Setups (TrueNAS, OpenShift).
**Empfohlener 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
```
Da `runAsUser: 999` auf Pod-Ebene gesetzt ist, überspringt der Entrypoint gosu vollständig. Dies erlaubt die Capabilities `allowPrivilegeEscalation: false` und `drop: [ALL]` ohne Konflikt.
Für die Ressourcendimensionierung siehe [Hardware-Anforderungen](/de/guide/deployment#hardware-requirements).
## Backup und Wiederherstellung {#backup-and-recovery}
Der persistente Zustand ist über zwei Volumes verteilt:
| Volume | Inhalt | Kritisch? |
|---|---|---|
| `SnapOtter-pgdata` | PostgreSQL-Datenbank (Benutzer, Einstellungen, Pipelines, Jobs, Audit-Log) | Ja |
| `/data` (App-Volume) | Von Benutzern hochgeladene Dateien, KI-Modelle, Python-venv | Teilweise (siehe unten) |
Innerhalb des `/data`-Volumes:
| Pfad | Inhalt | Kritisch? |
|---|---|---|
| `/data/uploads/`, `/data/outputs/` | Benutzerdateien und Verarbeitungsergebnisse | Ja |
| `/data/ai/` | Heruntergeladene KI-Modelldateien | Nein (erneut herunterladbar) |
| `/data/venv/` | Virtuelle Python-Umgebung | Nein (beim Start neu gebaut) |
### Datenbank-Backup {#database-backup}
Verwende `pg_dump`, um die Datenbank zu sichern, während der Stack läuft:
```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
```
Alternativ stoppe den Stack und erstelle einen Snapshot des `SnapOtter-pgdata`-Volumes:
```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 .
```
### Backup der Benutzerdateien {#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 .
```
KI-Modelle machen über alle Bundles hinweg bis zu etwa 24 GB aus. Da sie erneut herunterladbar sind, schließe `/data/ai/` und `/data/venv/` von Backups aus, um Platz zu sparen. Nur die Datenbank und die Benutzerdateien sind kritisch.
## Compliance-Artefakte {#compliance-artifacts}
Jedes SnapOtter-Release enthält die folgenden Sicherheitsartefakte:
| Artefakt | Format | Wo zu finden |
|---|---|---|
| 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` |
| Schwachstellen-Scan | Trivy JSON | [GitHub-Release](https://github.com/snapotter-hq/SnapOtter/releases)-Asset: `snapotter-v{version}-trivy.json` |
| Schwachstellen-Scan | SARIF | Tab [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security) |
| Statische Analyse | CodeQL (JS/TS + Python) | Tab [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security), läuft wöchentlich + pro PR |
| Dependency-Review | GitHub nativ | Prüfung pro PR, scheitert bei Ergänzungen hoher Schwere |
| Python-Dependency-Audit | pip-audit | CI-Laufprotokoll bei jedem Push |
| Sicherheitsrichtlinie | Markdown | [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) im Repository |
| Dependency-Updates | Dependabot | Automatisierte wöchentliche PRs für npm, pip, Docker, Actions |
**Deinen eigenen Scan ausführen:**
Lade die SBOM aus dem Release herunter und scanne sie mit deinem bevorzugten Tool:
```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
Die SBOM und der Schwachstellen-Scan spiegeln genau das für dieses Release veröffentlichte Image wider. Nach dem Deployment installierte KI-Modell-Bundles sind nicht in der SBOM enthalten, da sie zur Laufzeit heruntergeladen werden.
:::
+239
View File
@@ -0,0 +1,239 @@
---
description: "Unterstützte Dateiformate über alle Modalitäten hinweg - 55+ Bild-Eingabeformate, Video, Audio, PDF und Dateiformate."
i18n_source_hash: e53ecf65be25
i18n_provenance: human
i18n_output_hash: 3b6d27434067
---
# Unterstützte Formate {#supported-formats}
SnapOtter verarbeitet Dateien über fünf Modalitäten hinweg: Bild, Video, Audio, PDF und Dateien. Diese Seite listet alle unterstützten Formate auf.
## Bildformate {#image-formats}
SnapOtter unterstützt 55+ Bildformate für die Eingabe und 13 Formate für die Ausgabe.
## Eingabeformate {#input-formats}
### Web-Standards (9) {#web-standards-9}
| Format | Erweiterungen | Decoder | Hinweise |
|--------|-----------|---------|-------|
| JPEG | .jpg, .jpeg | Sharp (nativ) | |
| PNG | .png | Sharp (nativ) | APNG erstes Bild extrahiert |
| WebP | .webp | Sharp (nativ) | |
| GIF | .gif | Sharp (nativ) | Animiert unterstützt |
| AVIF | .avif | Sharp (nativ) | |
| SVG | .svg | Sharp (librsvg) | Bereinigt gegen XXE/SSRF |
| SVGZ | .svgz | gunzip + Sharp | Gzip-Bomben-Schutz |
| APNG | .apng | Sharp (nativ) | Nur erstes Bild |
| JPEG XL | .jxl | djxl / ImageMagick | Zweistufiger Fallback |
### Professionell (7) {#professional-7}
| Format | Erweiterungen | Decoder | Hinweise |
|--------|-----------|---------|-------|
| TIFF | .tiff, .tif | Sharp (nativ) | Mehrseitig unterstützt |
| PSD | .psd | ImageMagick | Zusammengeführtes Komposit |
| EPS | .eps, .epsf | ImageMagick + Ghostscript | Rasterung mit 300dpi, sicherheitsgehärtet |
| OpenEXR | .exr | ImageMagick | Linear-zu-sRGB-Konvertierung |
| Radiance HDR | .hdr | ImageMagick | Linear-zu-sRGB-Konvertierung |
| DPX | .dpx | ImageMagick | Log-zu-sRGB-Konvertierung |
| Cineon | .cin | ImageMagick | Film/VFX-Format |
### Kamera-RAW (23) {#camera-raw-23}
| Format | Erweiterungen | Kameramarke | Decoder |
|--------|-----------|-------------|---------|
| DNG | .dng | Adobe (universell) | exiftool / ImageMagick + LibRaw |
| CR2 | .cr2 | Canon (vor 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 (kompakt) | exiftool / ImageMagick + LibRaw |
### Moderne Formate (3) {#modern-formats-3}
| Format | Erweiterungen | Decoder | Hinweise |
|--------|-----------|---------|-------|
| JPEG 2000 | .jp2, .j2k, .j2c, .jpc, .jpf, .jpx | opj_decompress / ImageMagick | Digital Cinema, medizinische Bildgebung |
| QOI | .qoi | Inline-TypeScript-Codec | Spieleentwicklung, eingebettete Systeme |
| HEIC/HEIF | .heic, .heif | heif-convert / heif-dec | iPhone-Fotos |
### Legacy/System (4) {#legacy-system-4}
| Format | Erweiterungen | Decoder | Hinweise |
|--------|-----------|---------|-------|
| BMP | .bmp | ImageMagick | |
| ICO | .ico | ImageMagick | Größte Ebene extrahiert |
| CUR | .cur | ImageMagick | Windows-Cursor (ICO-Variante) |
| TGA | .tga | ImageMagick | Erkennung nur über Erweiterung |
### Wissenschaft und Gaming (2) {#scientific-and-gaming-2}
| Format | Erweiterungen | Decoder | Hinweise |
|--------|-----------|---------|-------|
| FITS | .fits, .fit, .fts | ImageMagick | Astronomie (NASA-Standard) |
| DDS | .dds | ImageMagick | Spieletexturen (DirectX) |
### Interchange (6) {#interchange-6}
| Format | Erweiterungen | Decoder | Hinweise |
|--------|-----------|---------|-------|
| PPM | .ppm | Sharp (nativ) | Farb-Pixmap |
| PGM | .pgm | Sharp (nativ) | Graustufen |
| PBM | .pbm | Sharp (nativ) | 1-Bit-Bitmap |
| PNM | .pnm | Sharp (nativ) | Sammelformat |
| PAM | .pam | Sharp (nativ) | Beliebige Map |
| PFM | .pfm | Sharp (nativ) | Float-Map |
## Ausgabeformate (13) {#output-formats-13}
| Format | Encoder | Qualitätssteuerung | Verfügbar in |
|--------|---------|----------------|-------------|
| JPEG | Sharp nativ | 1-100 | Allen Werkzeugen |
| PNG | Sharp nativ | Kompression 0-9 | Allen Werkzeugen |
| WebP | Sharp nativ | 1-100 | Allen Werkzeugen |
| AVIF | Sharp nativ | 1-100 | Allen Werkzeugen |
| TIFF | Sharp nativ | 1-100 | Vollständige Konvertierungswerkzeuge |
| GIF | Sharp nativ | 1-100 | Vollständige Konvertierungswerkzeuge |
| JXL | Sharp nativ | 1-100 | Allen Werkzeugen |
| HEIC | heif-enc CLI | 1-100 | Vollständige Konvertierungswerkzeuge |
| HEIF | heif-enc CLI | 1-100 | Vollständige Konvertierungswerkzeuge |
| BMP | ImageMagick CLI | Verlustfrei | Konvertierungswerkzeug |
| ICO | ImageMagick CLI | Verlustfrei | Konvertierungswerkzeug |
| JP2 | opj_compress CLI | Kompressionsverhältnis | Konvertierungswerkzeug |
| QOI | Inline-Codec | Verlustfrei | Konvertierungswerkzeug |
## Videoformate {#video-formats}
Die Video-Dekodierung und -Kodierung werden von FFmpeg (statischer Build) übernommen, sodass jeder gängige Container und Codec bei der Eingabe unterstützt wird.
### Eingabe-Container (15) {#input-containers-15}
| Format | Erweiterungen | Typische Codecs | Hinweise |
|--------|-----------|----------------|-------|
| MP4 | .mp4 | H.264, H.265, AV1 | Am weitesten verbreiteter Container |
| QuickTime | .mov | H.264, ProRes | Apple-Aufnahme/-Bearbeitung |
| WebM | .webm | VP8, VP9, AV1 | Lizenzfreies Web-Format |
| Matroska | .mkv | Beliebig | Flexibler offener Container |
| AVI | .avi | Verschiedene | Legacy-Microsoft-Container |
| M4V | .m4v | H.264 | Apple-MP4-Variante |
| AVCHD | .mts | H.264 | Camcorder-Aufnahmen |
| BDAV | .m2ts | H.264 | Blu-ray / AVCHD-Transportstream |
| 3GP | .3gp | H.264, MPEG-4 | Mobile Aufnahme |
| Flash Video | .flv | H.264, VP6 | Legacy-Streaming |
| Windows Media | .wmv | VC-1, WMV | Windows Media |
| MPEG | .mpg, .mpeg | MPEG-1, MPEG-2 | Video aus der DVD-Ära |
| MPEG-TS | .ts | MPEG-2, H.264 | Broadcast-Transportstream |
| Ogg | .ogv | Theora | Offenes Ogg-Video |
### Ausgabeformate {#output-formats}
| Format | Erweiterung | Video-Codec | Erzeugt von |
|--------|-----------|-------------|-------------|
| MP4 | .mp4 | H.264 | Konvertieren, Komprimieren und den meisten Video-Werkzeugen |
| QuickTime | .mov | H.264 | Video konvertieren |
| WebM | .webm | VP9 | Video konvertieren |
| GIF | .gif | - | Video zu GIF |
| WebP | .webp | - | Video zu WebP (animiert) |
### Untertitel {#subtitles}
| Format | Erweiterung | Operationen |
|--------|-----------|-----------|
| SubRip | .srt | Einbetten, Einbrennen, Extrahieren, Auto-Generieren |
| WebVTT | .vtt | Einbetten, Einbrennen, Extrahieren, Auto-Generieren |
| ASS / SSA | .ass | Einbetten, Einbrennen (unterstützt Styling) |
## Audioformate {#audio-formats}
Audio wird ebenfalls von FFmpeg verarbeitet.
### Eingabeformate (11) {#input-formats-11}
| Format | Erweiterungen | Kompression | Hinweise |
|--------|-----------|-------------|-------|
| MP3 | .mp3 | Verlustbehaftet | Universelle Kompatibilität |
| WAV | .wav | Unkomprimiert (PCM) | Studio / Bearbeitung |
| FLAC | .flac | Verlustfrei | Offener verlustfreier Codec |
| AAC | .aac | Verlustbehaftet | Roher AAC-Stream |
| M4A | .m4a | Verlustbehaftet (AAC) / Verlustfrei (ALAC) | MPEG-4-Audio |
| Ogg Vorbis | .ogg | Verlustbehaftet | Offenes Format |
| Opus | .opus | Verlustbehaftet | Modern, geringe Latenz |
| WMA | .wma | Verlustbehaftet | Windows Media Audio |
| AIFF | .aiff | Unkomprimiert (PCM) | Apple unkomprimiert |
| AMR | .amr | Verlustbehaftet | Sprache / Mobil |
| AC-3 | .ac3 | Verlustbehaftet | Dolby Digital |
### Ausgabeformate {#output-formats-1}
| Format | Erweiterung | Codec | Erzeugt von |
|--------|-----------|-------|-------------|
| MP3 | .mp3 | LAME | Audio konvertieren, Audio extrahieren |
| WAV | .wav | PCM | Audio konvertieren, Audio extrahieren |
| FLAC | .flac | FLAC (verlustfrei) | Audio konvertieren |
| Ogg | .ogg | Vorbis | Audio konvertieren |
| M4A | .m4a | AAC | Audio konvertieren, Audio extrahieren |
## Dokumentformate {#document-formats}
Die Dokumentverarbeitung verwendet qpdf, LibreOffice, Ghostscript, Pandoc und WeasyPrint.
### Eingabeformate (15) {#input-formats-15}
| Format | Erweiterungen | Engine | Hinweise |
|--------|-----------|--------|-------|
| PDF | .pdf | qpdf, Ghostscript, pdfcpu | Kern-Dokumentformat |
| Word | .docx, .doc | LibreOffice | Microsoft Word |
| Excel | .xlsx, .xls | LibreOffice | Microsoft Excel |
| PowerPoint | .pptx, .ppt | LibreOffice | Microsoft PowerPoint |
| OpenDocument | .odt, .ods, .odp | LibreOffice | Text, Tabelle, Präsentation |
| Rich Text | .rtf | LibreOffice | App-übergreifender Rich Text |
| Plain Text | .txt | LibreOffice, Pandoc | UTF-8-Text |
| Markdown | .md | Pandoc | CommonMark / GFM |
| HTML | .html | WeasyPrint | Als PDF gerendert |
| EPUB | .epub | Pandoc, LibreOffice | E-Book-Format |
### Ausgabeformate {#output-formats-2}
| Format | Erweiterungen | Erzeugt von |
|--------|-----------|-------------|
| PDF | .pdf | Word/Excel/PowerPoint zu PDF, Markdown zu PDF, HTML zu PDF |
| PDF/A | .pdf | PDF/A-Konvertierung (Archivierung) |
| Word | .docx, .odt, .rtf, .txt | Dokument konvertieren, PDF zu Word, Markdown zu Word |
| Präsentation | .pptx, .odp | Präsentation konvertieren |
| Tabelle | .xlsx, .ods, .csv | Tabelle konvertieren |
| HTML | .html | Markdown zu HTML |
| EPUB | .epub | Zu EPUB konvertieren |
| Bilder | .png, .jpg | PDF zu Bild |
## Dateiformate {#file-formats}
Daten- und Archivwerkzeuge konvertieren zwischen strukturierten Formaten und bündeln Dateien.
| Format | Erweiterungen | Konvertierungen |
|--------|-----------|-------------|
| CSV | .csv | Nach/von JSON und Excel; teilen und zusammenführen; aus XML |
| JSON | .json | Nach/von CSV, XML und YAML |
| XML | .xml | Nach/von JSON; nach CSV |
| YAML | .yaml, .yml | Nach/von JSON |
| Excel | .xlsx | Nach/von CSV |
| ZIP | .zip | Archive erstellen, Inhalte extrahieren |
+31
View File
@@ -0,0 +1,31 @@
---
description: "Welche anonymen Nutzungsdaten SnapOtter erfasst, wann sie gesendet werden und wie Sie instanzweite Produktanalysen deaktivieren."
i18n_source_hash: 5d72dedaeb23
i18n_provenance: human
i18n_output_hash: 80f93f5f790f
---
# Was SnapOtter erfasst {#what-snapotter-collects}
Anonyme Produktanalysen sind standardmäßig aktiviert und werden von einem Administrator für die gesamte Instanz festgelegt. Deaktivieren Sie sie unter Einstellungen > System > Datenschutz.
## Ereignisse, die wir senden (wenn aktiviert) {#events-we-send-when-enabled}
- tool_used: Werkzeug-ID, Status, Dauer, Kategorie, ob es ein AI-Werkzeug ist, ein Fehlercode bei Fehlschlag.
- pipeline_executed: Schrittanzahl, Werkzeug-IDs, Batch-Flag, Dateianzahl, Dauer, Status.
- ai_bundle_action: Bündel-ID, Aktion, Dauer.
- Frontend-Nutzung: welche Werkzeugseiten geöffnet werden, hinzugefügte Dateien (nur Anzahl), gestartetes Werkzeug, Downloads, Speicherungen, Suche (nur Ergebnisanzahl), Batch verarbeitet.
- Absturzberichte: Fehlertyp und ein Quell-Stack nur mit Datei-Basisnamen.
## Was wir niemals erfassen {#what-we-never-collect}
- Dateinamen oder Pfade
- Dateiinhalte
- OCR-Ausgabetext
- Bildmetadaten (EXIF)
- Extrahierten Dokumenttext
- Ihre IP-Adresse oder Kontoidentität
## Deaktivieren {#turning-it-off}
Admins: Einstellungen > System > Datenschutz, schalten Sie "Anonyme Produktanalysen" aus. Sie stoppt sofort, instanzweit. Um ein Image zu erstellen, das niemals senden kann, setzen Sie das Build-Argument `SNAPOTTER_ANALYTICS=off`.
+235
View File
@@ -0,0 +1,235 @@
---
description: "21 unterstützte Sprachen und wie man Übersetzungen für SnapOtter mit dem TypeScript-erzwungenen i18n-System erstellt oder verbessert."
i18n_source_hash: 55837d9fdaef
i18n_provenance: human
i18n_output_hash: f48a6892b5fc
---
# Übersetzungsleitfaden {#translation-guide}
SnapOtter wird von Haus aus mit 21 Sprachen ausgeliefert. Das i18n-System verwendet eine leichtgewichtige, eigene Laufzeitumgebung mit TypeScript-erzwungener Vollständigkeit der Locales und dynamischem Code-Splitting.
## Unterstützte Sprachen {#supported-languages}
| Code | Sprache | Native Name | Direction |
|------|----------|-------------|-----------|
| `en` | Englisch | English | LTR |
| `zh-CN` | Chinesisch (Vereinfacht) | 简体中文 | LTR |
| `zh-TW` | Chinesisch (Traditionell) | 繁體中文 | LTR |
| `ja` | Japanisch | 日本語 | LTR |
| `ko` | Koreanisch | 한국어 | LTR |
| `es` | Spanisch | Español | LTR |
| `fr` | Französisch | Français | LTR |
| `it` | Italienisch | Italiano | LTR |
| `pt-BR` | Portugiesisch (Brasilien) | Português (Brasil) | LTR |
| `de` | Deutsch | Deutsch | LTR |
| `nl` | Niederländisch | Nederlands | LTR |
| `sv` | Schwedisch | Svenska | LTR |
| `ru` | Russisch | Русский | LTR |
| `pl` | Polnisch | Polski | LTR |
| `uk` | Ukrainisch | Українська | LTR |
| `ar` | Arabisch | العربية | RTL |
| `tr` | Türkisch | Türkçe | LTR |
| `hi` | Hindi | हिन्दी | LTR |
| `vi` | Vietnamesisch | Tiếng Việt | LTR |
| `id` | Indonesisch | Bahasa Indonesia | LTR |
| `th` | Thailändisch | ไทย | LTR |
## Wie die Spracherkennung funktioniert {#how-language-detection-works}
SnapOtter verwendet eine dreistufige Auflösungsreihenfolge:
1. **Benutzereinstellung** - gespeichert in `localStorage("snapotter-locale")` und mit den Benutzereinstellungen synchronisiert, sobald angemeldet
2. **Automatische Browsererkennung** - durchläuft das `navigator.languages`-Array mit BCP 47-Präfixabgleich
3. **Instanzstandard** - die `DEFAULT_LOCALE`-Umgebungsvariable des Admins (abgerufen von `GET /api/v1/config/locale`)
4. **Englischer Fallback** - immer verfügbar
Benutzer können die Sprache ändern über:
- Den **Globus-Auswähler in der Fußzeile** (Desktop, immer sichtbar)
- Den Sprachauswähler auf der **Anmeldeseite** (vor der Anmeldung)
- Den Abschnitt **Einstellungen > Allgemein** (Einstellung pro Benutzer)
- Das Sprach-Dropdown in der **mobilen Seitenleiste**
- Der Abschnitt **Einstellungen > System** legt den instanzweiten Standard fest (nur Admin)
## Wie Übersetzungen funktionieren {#how-translations-work}
Alle UI-Zeichenketten befinden sich in `packages/shared/src/i18n/`. Die Referenzdatei ist `en.ts`, die ein typisiertes Objekt mit jeder von der App verwendeten Zeichenkette exportiert (~1500 Schlüssel). Andere Sprachen sind separate Dateien (z. B. `de.ts`, `fr.ts`), die dieselbe Struktur exportieren.
Der Typ `TranslationKeys` verwendet `DeepStringRecord`, um jeden Zeichenkettenwert zu akzeptieren und gleichzeitig die Schlüsselstruktur zu erzwingen. TypeScript erkennt fehlende Schlüssel in jeder Übersetzungsdatei zur Kompilierzeit.
Zur Laufzeit wird nur die aktive Locale über dynamisches `import()` geladen, wodurch das Haupt-Bundle klein bleibt.
## Übersetzungen in Komponenten verwenden {#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>
);
}
```
## Eine Übersetzung beitragen {#contributing-a-translation}
Wir begrüßen Übersetzungs-PRs direkt. Du kannst eine bestehende Locale verbessern oder eine neue hinzufügen.
Um eine Fehlübersetzung zu melden, ohne Code einzureichen, öffne ein [GitHub Issue](https://github.com/snapotter-hq/SnapOtter/issues) mit der Sprache, der fehlerhaften Zeichenkette und dem vorgeschlagenen Fix.
::: tip
Übersetzungs-PRs erfordern keine vorherige Genehmigung. Forke das Repository, nimm deine Änderungen vor und öffne einen PR. Siehe den [Contributing Guide](/de/guide/contributing) für den vollständigen PR-Prozess und die CLA-Anforderung.
:::
## Wie man eine Übersetzung erstellt oder aktualisiert {#how-to-create-or-update-a-translation}
### 1. Forken und klonen {#_1-fork-and-clone}
```bash
git clone https://github.com/<your-username>/snapotter.git
cd snapotter
pnpm install
```
### 2. Die Referenzdatei kopieren (nur bei neuer Sprache) {#_2-copy-the-reference-file-new-language-only}
Überspringe diesen Schritt, wenn du eine bestehende Übersetzung verbesserst.
```bash
cp packages/shared/src/i18n/en.ts packages/shared/src/i18n/XX.ts
```
### 3. Die Zeichenketten übersetzen {#_3-translate-the-strings}
Öffne deine neue Datei und übersetze jeden Zeichenkettenwert. Behalte die Objektstruktur und die Schlüssel exakt gleich bei.
```ts
import type { TranslationKeys } from "./en.js";
export const xx: TranslationKeys = {
common: {
upload: "Your translation here",
// ... translate all entries
},
// ... translate all sections
} as const;
```
Regeln:
- Übersetze keine Objektschlüssel, nur Zeichenkettenwerte
- Behalte `as const` am Ende bei
- Importiere `TranslationKeys` aus `./en.js` und typisiere deinen Export
- Behalte `{variable}`-Platzhalter exakt so bei, wie sie sind
- Arrays (`rotatingPhrases`, `progressMessages`) müssen dieselbe Anzahl von Einträgen haben
- Nicht übersetzen: SnapOtter, JPEG, PNG, WebP, EXIF, API und andere Fachbegriffe
### 4. Die Locale registrieren (nur bei neuer Sprache) {#_4-register-the-locale-new-language-only}
Füge deine Locale zu `SUPPORTED_LOCALES` in `packages/shared/src/i18n/index.ts` hinzu:
```ts
{ code: "xx", name: "Language Name", nativeName: "Native Name", dir: "ltr" },
```
### 5. Überprüfen {#_5-verify}
```bash
pnpm typecheck # catches missing or mistyped keys
pnpm lint # formatting check
pnpm dev # manually verify strings appear correctly
```
### 6. Einreichen {#_6-submit}
Öffne einen PR gegen `main` mit einem Titel wie `feat(i18n): add Swedish translation` oder `fix(i18n): correct German typos`. Der CLA-Bot wird dich bei deinem ersten Beitrag um eine Unterschrift bitten.
## Neue Übersetzungsschlüssel hinzufügen {#adding-new-translation-keys}
Wenn du ein neues Feature hinzufügst, das neue UI-Zeichenketten benötigt:
1. Füge die neuen Schlüssel zuerst zu `en.ts` hinzu (der Referenzdatei)
2. Führe `pnpm typecheck` aus - jede Locale-Datei schlägt fehl, wenn der neue Schlüssel fehlt
3. Füge den neuen Schlüssel zu allen Locale-Dateien hinzu (verwende Englisch als vorübergehenden Fallback)
## Konfiguration {#configuration}
Lege die Standardsprache der Instanz über eine Umgebungsvariable fest:
```yaml
DEFAULT_LOCALE: "de" # German as the default for all new users
```
## Dateireferenz {#file-reference}
| Datei | Zweck |
|------|---------|
| `packages/shared/src/i18n/en.ts` | Englische Zeichenketten (Referenz-Locale, ~1500 Schlüssel) |
| `packages/shared/src/i18n/index.ts` | `SUPPORTED_LOCALES`, `loadTranslations()`, Typ-Exporte |
| `packages/shared/src/i18n/<locale>.ts` | Übersetzungsdateien pro Sprache |
| `apps/web/src/contexts/i18n-context.tsx` | `I18nProvider`, `useTranslation()`-Hook |
| `apps/web/src/lib/format.ts` | `format()`-, `plural()`-, `formatFileSize()`-Helfer |
| `apps/api/src/routes/config.ts` | Öffentlicher `GET /api/v1/config/locale`-Endpunkt |
## Website, Docs und API-Referenz übersetzen {#translating-the-web-surfaces}
Die 21-Sprachen-Unterstützung oben deckt die **App** ab. Die öffentliche Website
(snapotter.com), diese Dokumentationsseite und die REST-API-Referenz werden ebenfalls
in alle 21 Sprachen übersetzt, durch eine separate, hash-gesteuerte Pipeline, die
dieselben Tool-Namen und Beschreibungen aus `packages/shared/src/i18n` wiederverwendet, sodass die
Terminologie überall konsistent bleibt.
### Standardmäßig maschinell übersetzt {#machine-translated-by-default}
Jede nicht-englische Seite auf der Website und in den Docs wird im ersten Durchlauf
**maschinell übersetzt** (durch eine Claude-Code-Sitzung, nicht durch einen Drittanbieterdienst)
und trägt ein kleines, ausblendbares Banner, das darauf hinweist, mit einem Link zurück
hierher. Das ist Absicht: So werden alle 21 Sprachen schnell und ehrlich ausgeliefert, und
die Community wird eingeladen, die Seiten zu verfeinern, die am wichtigsten sind. Maschinelle
Übersetzung vermittelt die Bedeutung; menschliche Überprüfung sorgt dafür, dass sie sich
natürlich liest.
### Wie die Pipeline entscheidet, was übersetzt wird {#how-the-web-pipeline-decides}
Jede übersetzbare Einheit des englischen Quelltexts wird gehasht, und der Hash wird neben
ihrer Übersetzung gespeichert. Bei jedem Durchlauf:
- übersetzt die Pipeline jede Einheit, die noch keine Übersetzung hat,
- überspringt jede Einheit, deren gespeicherter Hash noch mit dem englischen Quelltext übereinstimmt,
- übersetzt eine **maschinelle** Einheit neu, wenn sich ihr englischer Quelltext ändert,
- und markiert eine von einem **Menschen** verfeinerte Einheit als `stale` (benötigt Überprüfung),
wenn sich ihr englischer Quelltext ändert, anstatt deine Arbeit zu überschreiben.
### Eine Web-Übersetzung per PR verfeinern {#refining-a-web-translation-by-pr}
Du verbesserst eine Übersetzung der Website, der Docs oder der API-Referenz auf dieselbe Weise,
wie du eine App-Locale verbesserst: indem du die generierte Datei bearbeitest und einen PR öffnest.
1. Finde die generierte Übersetzung für deine Sprache:
- Website-UI-Zeichenketten: `apps/landing/src/i18n/<locale>.json`
- eine Docs-Seite: `apps/docs/<locale>/**.md`
- die API-Referenz: `apps/api/src/openapi.<locale>.yaml`
2. Bearbeite den Text. Behalte Code, Links, `{placeholders}` und alle `⸤I18N…⸥`-Markierungen
exakt so bei, wie sie sind; der Validator der Pipeline weist eine Übersetzung zurück, die sie
auslässt oder umsortiert.
3. Öffne einen PR. Das Bearbeiten einer Einheit ändert ihre Herkunft von `machine` zu `human`, sodass
die Pipeline sie bei einem späteren Durchlauf **niemals überschreibt**. Wenn sich der englische
Quelltext danach ändert, wird deine Einheit als `stale` zur Überprüfung markiert, anstatt
stillschweigend ersetzt zu werden.
Um eine Fehlübersetzung zu melden, ohne Code einzureichen, öffne ein
[GitHub Issue](https://github.com/snapotter-hq/SnapOtter/issues) mit der
Seiten-URL, der Sprache, dem fehlerhaften Text und deinem vorgeschlagenen Fix.
::: tip
Maintainer führen die Übersetzungs-Pipeline aus; du benötigst keinen API-Schlüssel, um
beizutragen. Bearbeite einfach die generierte Datei und öffne einen PR. Siehe
[`scripts/i18n/README.md`](https://github.com/snapotter-hq/SnapOtter/blob/main/scripts/i18n/README.md)
für Informationen dazu, wie die Pipeline läuft.
:::
+117
View File
@@ -0,0 +1,117 @@
---
i18n_source_hash: 9a6abf3fc8ae
i18n_provenance: human
i18n_output_hash: c55503cb8d0c
---
# Upgrade von 1.x auf 2.0 {#upgrading-from-1-x-to-2-0}
SnapOtter 1.x speicherte alles in einer einzigen SQLite-Datei und lief als ein einzelner Container. SnapOtter 2.0 verwendet PostgreSQL und Redis. Dieser Leitfaden führt durch die Migration einer 1.x-Installation auf 2.0 ohne Datenverlust.
Die Kurzfassung: Verwende dein bestehendes `/data`-Volume weiter, und 2.0 importiert deine 1.x-Datenbank beim ersten Start automatisch. Deine Benutzer, gespeicherten Dateien, Einstellungen, API-Schlüssel und Pipelines werden übernommen. Die alte Datenbank wird nie verändert, sodass du jederzeit zurückrollen kannst.
::: tip Ein Hinweis für unsere 1.x-Nutzer
Viele von euch vertrauen SnapOtter seit dem ersten Tag, und euer Feedback hat dieses Release geprägt. 2.0 ändert vieles unter der Haube, und dieser Leitfaden sorgt dafür, dass euch der Umstieg nichts kostet, was euch wichtig ist. Eure Konten, Dateien, Einstellungen, API-Schlüssel und Pipelines werden übernommen, und eure alte Datenbank wird nie angerührt. Danke, dass ihr mit uns aktualisiert.
:::
## Bevor du beginnst: sichere das gesamte `/data`-Volume {#before-you-start-back-up-the-whole-data-volume}
Mach das jedes Mal zuerst. Sichere das **gesamte** `/data`-Volume, nicht nur die `snapotter.db`-Datei.
Warum das wichtig ist: 1.x betreibt SQLite im WAL-Modus, sodass ein gestoppter 1.x-Container die meisten seiner festgeschriebenen Daten regelmäßig in `snapotter.db-wal` neben einer nahezu leeren `snapotter.db` liegen lässt. Kopierst du nur `snapotter.db`, erfasst du eine leere Datenbank und verlierst stillschweigend alles. Das Volume trägt `snapotter.db`, `snapotter.db-wal`, `snapotter.db-shm` und dein `files/`-Verzeichnis gemeinsam, und sie müssen als Einheit wandern.
```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 .
```
## Zuerst auf 1.17.2 aktualisieren {#upgrade-to-1-17-2-first}
Aktualisiere deine 1.x-Installation auf das neueste 1.x-Release (1.17.2), bevor du auf 2.0 wechselst. So kann 1.x seine eigenen letzten Schemamigrationen ausführen, sodass 2.0 aus einem bekannten, vollständigen Schema importiert. Ein Upgrade von einem älteren 1.x direkt auf 2.0 wird nicht unterstützt.
## Prüfe deinen Volume-Namen {#check-your-volume-name}
Der Importer sieht deine Daten nur, wenn der 2.0-Stack dasselbe Volume mountet, das deine 1.x-Installation verwendet hat. Docker-Volume-Namen unterscheiden Groß- und Kleinschreibung, und ältere README-Snippets verwendeten ein kleingeschriebenes `snapotter-data`, während die Compose-Dateien `SnapOtter-data` verwenden. Bestätige, welches du hast:
```bash
docker volume ls | grep -i snapotter
```
Verwende genau diesen Namen in deiner 2.0-Konfiguration.
## Pfad A: einzelner Container (am schnellsten) {#path-a-single-container-quickest}
Wenn du SnapOtter mit einem einzelnen `docker run` betreibst, mach das weiter so. 2.0 startet ein eingebettetes PostgreSQL und Redis innerhalb des Containers, wenn du `DATABASE_URL` oder `REDIS_URL` nicht setzt, und es erkennt und importiert `/data/snapotter.db` beim ersten Start automatisch.
```bash
docker run -d --name snapotter -p 1349:1349 \
-v SnapOtter-data:/data \
snapotter/snapotter:latest
```
Achte in den Logs auf eine Zeile wie:
```
Imported 1.x SQLite database: {"tables":{"users":2,"teams":1,...},"blobs":{"present":1,"missing":0}}
```
Das ist alles. Melde dich mit deinen bestehenden Zugangsdaten an.
## Pfad B: Compose (empfohlen für die Produktion) {#path-b-compose-recommended-for-production}
Der 2.0-Compose-Stack betreibt drei Dienste (App, Postgres, Redis). Verwende dein 1.x-`/data`-Volume für den App-Dienst weiter. Die App erkennt `/data/snapotter.db` automatisch und importiert es beim ersten Start 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
# ...
```
Wenn du lieber explizit auf die alte Datenbank zeigen möchtest, setze `SQLITE_MIGRATE_PATH=/data/snapotter.db`. Ein expliziter Pfad hat immer Vorrang vor der automatischen Erkennung.
## Den Import zuerst in der Vorschau ansehen (optional) {#preview-the-import-first-optional}
Um genau zu sehen, was importiert würde, ohne etwas zu schreiben, führe einen Trockenlauf gegen deine Datenbankdatei aus:
```bash
pnpm --filter @snapotter/api migrate:sqlite -- /path/to/snapotter.db --dry-run
```
Es gibt die Zeilenanzahl pro Tabelle aus, wie viele Dateien der gespeicherten Bibliothek es auf der Festplatte gefunden hat und welche Job-Status es normalisieren wird. Es benötigt kein laufendes Postgres.
## Was übernommen wird und was nicht {#what-carries-over-and-what-does-not}
Übernommen:
- Benutzer und die Möglichkeit, sich anzumelden. Passwort-Hashes bleiben unverändert, sodass derselbe Benutzername und dasselbe Passwort funktionieren.
- Teams, Einstellungen (einschließlich deiner Instanz-Identität), Rollen, API-Schlüssel (sie funktionieren weiter) und gespeicherte Pipelines.
- Job-Verlaufsdatensätze.
- Deine gespeicherte Dateibibliothek, sowohl die Datensätze als auch die tatsächlichen Dateien, denn `/data/files` bleibt auf dem Volume erhalten.
Nicht übernommen:
- Anmeldesitzungen. Alle melden sich nach dem Upgrade einmal neu an. Die Zugangsdaten bleiben unverändert, es ist also eine einmalige erneute Anmeldung, mehr nicht.
- Die Eingabe- und Ausgabedateien alter Verarbeitungs-Jobs. Diese lagen in einem temporären Arbeitsbereich und sind bewusst weg. Die Job-Verlaufsdatensätze bleiben erhalten.
- Analyse-Einwilligungs-Flags pro Benutzer aus 1.x, die in 2.0 kein Äquivalent haben (die Analyse in 2.0 ist eine instanzweite Einstellung).
## Den Import ausschalten {#turning-the-import-off}
Wenn du bewusst eine frische Datenbank willst, obwohl eine `snapotter.db` auf dem Volume vorhanden ist, setze `SQLITE_MIGRATE_PATH=off`.
## Wenn du bereits Daten in der 2.0-Instanz hast {#if-you-already-have-data-in-the-2-0-instance}
Der Importer läuft nur bei einer leeren Datenbank an. Wenn du 2.0 frisch gestartet hast (und Daten erzeugt hast) und danach ein altes `snapotter.db` gemountet hast, erkennt 2.0 es, importiert aber nicht, weil das Zusammenführen zweier Datensätze bei IDs kollidieren kann. Du siehst eine Warnung in den Logs. Um die 1.x-Daten zu importieren, brauchst du eine leere Instanz:
- Wenn die 2.0-Instanz nur den Standard-Administrator enthält (du sie also nicht wirklich genutzt hast), stoppe den Stack, entferne das Postgres-Volume (`SnapOtter-pgdata`) und starte erneut mit vorhandener alter `/data`. Es wird sauber importiert. Dies löscht nur die wegwerfbaren Postgres-Daten, nicht deine 1.x-Datenbank.
- Wenn die 2.0-Instanz echte Daten enthält, die du behalten möchtest, können die beiden Datensätze nicht automatisch zusammengeführt werden. Exportiere, was du brauchst, und importiere die 1.x-Daten in ein separates, frisches Deployment.
## Zurückrollen {#rolling-back}
Das Upgrade verändert oder löscht deine 1.x-`snapotter.db` nie. Wenn du zu 1.x zurückkehren musst, deploye das 1.x-Image erneut gegen dasselbe Volume. Alles, was du nach dem Upgrade in 2.0 erstellt hast, liegt in Postgres und wäre nicht in der 1.x-Datenbank, also rolle zeitnah zurück, wenn du es vorhast.
+264
View File
@@ -0,0 +1,264 @@
---
description: "Verwalte Benutzer, integrierte und benutzerdefinierte Rollen, Berechtigungen, API-Schlüssel, Teams, Sitzungen und das Audit-Log in SnapOtter."
i18n_source_hash: 5e28af686c96
i18n_provenance: human
i18n_output_hash: 5794e14e4e84
---
# Benutzer, Rollen & Berechtigungen {#users-roles-permissions}
SnapOtter wird mit drei integrierten Rollen, 17 granularen Berechtigungen und Unterstützung für benutzerdefinierte Rollen mit optionaler Zugriffssteuerung pro Werkzeug ausgeliefert. Diese Seite behandelt das vollständige Autorisierungsmodell, die Bereichseinschränkung von API-Schlüsseln, die Teamverwaltung und das Audit-Logging.
::: tip Verwandte Seiten
[OIDC / SSO](/de/guide/oidc) | [SAML SSO](/de/guide/saml) | [SCIM-Bereitstellung](/de/guide/scim) | [Sicherheit & Härtung](/de/guide/security)
:::
## Benutzer {#users}
### Benutzer erstellen {#creating-users}
Administratoren können Benutzer über das Admin-Panel oder den `POST /api/auth/register`-Endpunkt erstellen. Jeder Benutzer hat einen Benutzernamen, eine Rolle, eine Teamzuordnung und eine optionale E-Mail-Adresse.
### Standard-Administrator {#default-admin}
Beim ersten Start erstellt SnapOtter ein Standard-Administratorkonto. Die Zugangsdaten stammen aus Umgebungsvariablen:
| Variable | Standard | Beschreibung |
|---|---|---|
| `DEFAULT_USERNAME` | `admin` | Benutzername für das anfängliche Administratorkonto |
| `DEFAULT_PASSWORD` | `admin` | Passwort für das anfängliche Administratorkonto |
Der Standard-Administrator muss beim ersten Login sein Passwort ändern.
### Authentifizierungsanbieter {#authentication-providers}
Benutzer können sich über mehrere Methoden authentifizieren:
- **Lokal** - Benutzername und Passwort, gespeichert in der SnapOtter-Datenbank
- **OIDC** - jeder OpenID-Connect-Anbieter (siehe [OIDC / SSO](/de/guide/oidc))
- **SAML** - SAML-2.0-Identitätsanbieter (siehe [SAML SSO](/de/guide/saml))
- **SCIM** - automatisierte Bereitstellung durch einen Identitätsanbieter (siehe [SCIM-Bereitstellung](/de/guide/scim))
### Authentifizierung deaktivieren {#disabling-authentication}
Setze `AUTH_ENABLED=false`, um die Authentifizierung vollständig zu deaktivieren. In diesem Modus wird für alle Anfragen ein synthetischer anonymer Benutzer mit der Rolle `admin` verwendet. Es ist kein Login erforderlich.
::: warning
Das Deaktivieren der Authentifizierung gewährt jedem, der die Instanz erreichen kann, vollen Administratorzugriff. Verwende dies nur in vertrauenswürdigen Umgebungen.
:::
## Integrierte Rollen {#built-in-roles}
SnapOtter enthält drei integrierte Rollen. Sie können weder geändert noch gelöscht werden.
### Admin {#admin}
Alle 17 Berechtigungen. Volle Kontrolle über die Instanz.
`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 Berechtigungen. Kann alle Werkzeuge verwenden und alle Dateien und Pipelines verwalten, aber nicht auf Administratorfunktionen zugreifen.
`tools:use` `files:own` `files:all` `apikeys:own` `pipelines:own` `pipelines:all` `settings:read`
### User {#user}
5 Berechtigungen. Kann Werkzeuge verwenden und eigene Ressourcen verwalten.
`tools:use` `files:own` `apikeys:own` `pipelines:own` `settings:read`
## Berechtigungsreferenz {#permissions-reference}
| Berechtigung | Beschreibung |
|---|---|
| `tools:use` | Jedes Verarbeitungswerkzeug verwenden |
| `files:own` | Eigene Dateien ansehen und verwalten |
| `files:all` | Dateien aller Benutzer ansehen und verwalten |
| `apikeys:own` | Eigene API-Schlüssel erstellen und verwalten |
| `apikeys:all` | API-Schlüssel aller Benutzer ansehen |
| `pipelines:own` | Eigene Pipelines erstellen und verwalten |
| `pipelines:all` | Pipelines aller Benutzer ansehen und verwalten |
| `settings:read` | Instanzeinstellungen ansehen |
| `settings:write` | Instanzeinstellungen ändern |
| `users:manage` | Benutzerkonten erstellen, aktualisieren und löschen |
| `teams:manage` | Teams erstellen, aktualisieren und löschen |
| `features:manage` | KI-Feature-Bundles installieren und verwalten |
| `system:health` | Auf Health- und Readiness-Endpunkte zugreifen |
| `audit:read` | Das Audit-Log ansehen und Rollen auflisten |
| `compliance:manage` | DSGVO-Lebenszyklus und Compliance-Funktionen verwalten |
| `webhooks:manage` | Ausgehende Webhooks konfigurieren |
| `security:manage` | Sicherheitseinstellungen verwalten (IP-Zulassungsliste, SSO-Erzwingung) |
## Benutzerdefinierte Rollen {#custom-roles}
Administratoren mit der Berechtigung `security:manage` können benutzerdefinierte Rollen über das Admin-Panel oder die Rollen-API erstellen. Das Auflisten von Rollen erfordert `audit:read`.
### Eine benutzerdefinierte Rolle erstellen {#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"]
}'
```
Rollennamen müssen 2 bis 30 Zeichen lang sein, kleingeschrieben alphanumerisch mit Bindestrichen und Unterstrichen.
### Für Administratoren reservierte Berechtigungen {#admin-reserved-permissions}
Drei Berechtigungen sind für integrierte Rollen reserviert und können benutzerdefinierten Rollen nicht zugewiesen werden:
- `compliance:manage`
- `webhooks:manage`
- `security:manage`
Die Rollen-API weist jede Anfrage ab, die diese Berechtigungen enthält. Nur die integrierte Rolle `admin` hat Zugriff darauf.
### Berechtigungen auf Werkzeugebene {#tool-level-permissions}
Benutzerdefinierte Rollen können optional einschränken, auf welche Werkzeuge Benutzer zugreifen dürfen. Zwei Modi sind verfügbar:
| Modus | Verhalten | Lizenzanforderung |
|---|---|---|
| `category` | Einschränkung nach Modalität (Bild, Video, Audio, Dokument, Datei) | Keine (kostenlos) |
| `tool` | Einschränkung nach einzelner Werkzeug-ID | Erfordert das Enterprise-Feature `per_tool_permissions` |
Wenn der Modus `tool` gesetzt ist, das Enterprise-Feature aber nicht verfügbar ist, degradiert SnapOtter kontrolliert und erlaubt den Zugriff auf alle Werkzeuge.
```json
{
"name": "image-only",
"permissions": ["tools:use", "files:own"],
"toolPermissions": {
"mode": "category",
"allowed": ["image"]
}
}
```
### Eine benutzerdefinierte Rolle löschen {#deleting-a-custom-role}
Wenn eine benutzerdefinierte Rolle gelöscht wird, werden alle ihr zugewiesenen Benutzer automatisch der Rolle `user` neu zugewiesen.
## Teams {#teams}
Teams gruppieren Benutzer für die Speicher- und Aufbewahrungsverwaltung. Ein `Default`-Team wird beim ersten Start erstellt.
| Feld | Typ | Beschreibung |
|---|---|---|
| `name` | string | Eindeutiger Teamname (1 bis 50 Zeichen) |
| `storageQuota` | number | Speicherlimit pro Team in Bytes (funktioniert ohne Enterprise) |
| `retentionHours` | number | Ausgaben nach dieser Anzahl von Stunden automatisch löschen (erfordert `team_retention_overrides`, Enterprise) |
| `legalHold` | boolean | Automatisches Löschen der Dateien von Teammitgliedern verhindern (erfordert `legal_hold`, Enterprise) |
::: info
Das `Default`-Team kann nicht gelöscht werden. Teams, die noch Mitglieder haben, können nicht gelöscht werden. Weise die Mitglieder zuerst neu zu.
:::
## API-Schlüssel {#api-keys}
Benutzer können API-Schlüssel für programmatischen Zugriff generieren. Jeder Schlüssel verwendet das Präfix `si_` und wird nur einmal bei der Erstellung angezeigt.
### Bereichseingeschränkte Berechtigungen {#scoped-permissions}
API-Schlüssel können optional ein `permissions`-Array tragen. Wenn gesetzt, sind die effektiven Berechtigungen für eine Anfrage die **Schnittmenge** der Rollenberechtigungen des Benutzers und der bereichseingeschränkten Berechtigungen des Schlüssels. Das bedeutet, ein API-Schlüssel kann nie über die eigenen Berechtigungen des Benutzers hinaus eskalieren.
```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"
}'
```
### Ablauf {#expiration}
Schlüssel akzeptieren einen optionalen `expiresAt`-Zeitstempel. Abgelaufene Schlüssel werden bei der Authentifizierung abgewiesen.
## Audit-Log {#audit-log}
SnapOtter zeichnet sicherheitsrelevante Ereignisse in einem strukturierten Audit-Log auf, das in der Datenbanktabelle `audit_log` gespeichert wird.
### Das Audit-Log ansehen {#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
```
Erfordert die Berechtigung `audit:read`. Unterstützt Seitennummerierung (`page`, `limit`) und Filter (`action`, `ip`, `from`, `to`).
### Auditing von Werkzeugoperationen {#tool-operation-auditing}
::: warning
`TOOL_EXECUTED`-Ereignisse werden standardmäßig **nicht** protokolliert. Sie sind über einen von zwei Wegen aktivierbar (Opt-in):
1. Setze die Admin-Einstellung `auditToolOperations` auf `true`.
2. Halte eine aktive Lizenz mit dem Feature `audit_export` (verfügbar sowohl in den Team- als auch in den Enterprise-Tarifen).
Ohne eine dieser Optionen werden einzelne Werkzeugausführungen nicht im Audit-Log erfasst.
:::
### Exportieren {#exporting}
```
GET /api/v1/enterprise/audit/export?format=csv&from=2026-01-01T00:00:00Z
```
Erfordert die Berechtigung `audit:read` und das Enterprise-Feature `audit_export` (verfügbar sowohl in den Team- als auch in den Enterprise-Tarifen). Unterstützt die Formate CSV und JSON, gefiltert nach `action`, `actorId`, `targetType`, `targetId`, `from` und `to`.
### Manipulationssichere Signierung {#tamper-resistant-signing}
Wenn aktiviert, wird jeder Audit-Log-Eintrag mit einem HMAC signiert, der aus `DATA_ENCRYPTION_KEY` abgeleitet wird. Dies erfordert:
1. Das Setzen von `DATA_ENCRYPTION_KEY` in deiner Umgebung.
2. Das Aktivieren der Admin-Einstellung `tamperResistantAudit`.
3. Eine Enterprise-Lizenz mit dem Feature `tamper_resistant_audit`.
### Aufbewahrung {#retention}
Setze `AUDIT_RETENTION_DAYS`, um alte Einträge automatisch zu bereinigen. Der Standard ist `0`, was bedeutet, dass Einträge unbegrenzt aufbewahrt werden.
### Ereignisreferenz {#event-reference}
| Ereignis | Kategorie |
|---|---|
| `LOGIN_SUCCESS`, `LOGIN_FAILED` | Authentifizierung |
| `OIDC_LOGIN_SUCCESS`, `OIDC_LOGIN_FAILED` | Authentifizierung |
| `SAML_LOGIN_SUCCESS`, `SAML_LOGIN_FAILED` | Authentifizierung |
| `LOGOUT` | Authentifizierung |
| `USER_CREATED`, `USER_UPDATED`, `USER_DELETED` | Benutzerverwaltung |
| `PASSWORD_CHANGED`, `PASSWORD_RESET` | Benutzerverwaltung |
| `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-Schlüssel |
| `SETTINGS_UPDATED`, `IP_ALLOWLIST_UPDATED` | Einstellungen |
| `FILE_UPLOADED`, `FILE_DELETED` | Dateien |
| `TOOL_EXECUTED` | Werkzeuge (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` | Konfiguration |
## Sitzungsverwaltung {#session-management}
Sitzungen sind cookiebasiert und werden über `SESSION_DURATION_HOURS` gesteuert (Standard: 168 Stunden / 7 Tage).
### Rollenänderungen machen Sitzungen ungültig {#role-changes-invalidate-sessions}
Wenn ein Administrator die Rolle eines Benutzers ändert, werden alle aktiven Sitzungen dieses Benutzers gelöscht. Der Benutzer muss sich erneut anmelden, um seine neuen Berechtigungen zu übernehmen.
### Schutzmechanismen {#safety-guards}
- **Schutz des letzten Administrators**: Der letzte verbleibende Administrator kann nicht auf eine niedrigere Rolle herabgestuft werden. Die API gibt einen Fehler zurück, wenn du es versuchst.
- **Selbstlöschungsschutz**: Administratoren können ihr eigenes Konto nicht über die API löschen.