A release-readiness QA pass over the whole product. The commits split into defects a user would hit and gates that were reporting green while measuring nothing. ## Fixes that change behaviour Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so request.ip came from a client-set header and a forged X-Forwarded-For got past the login limiter. The default is now a private-network trust list. A transient Postgres outage stranded in-flight jobs, leaving finished output on disk with no row pointing at it. A reconciler now resolves those rows and adopts the bytes rather than dropping the work. A Redis connection that moved to a new address wedged every read-blocked consumer, so completions stopped signalling while health still answered 200. Socket timeouts plus subscriber pings recover it. Installing more than one AI bundle left the shared venv multi-versioned and silently broke three tools. The installer now reconciles distributions to one version each. Converting an image to JXL at quality 1 through 4 returned a 500, because libjxl 0.7 rejects the distance those values compute. The quality is floored at what the encoder honours. A missing ffmpeg was also reported to the user as a corrupt upload; it now says the engine is unavailable. RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at 0.22.2, and the release scan was split so it can fail on an unfixed critical instead of hiding it behind ignore-unfixed. ## Gates that could not fail Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs build; coverage discarded its whole report on any failing test; the lint gate skipped root tests, scripts, and two workspaces; and several generated matrices counted a host missing ffmpeg as a passing tool. Each now measures what it claims. Full evidence and the outstanding release items are tracked locally and are not part of this branch.
6.9 KiB
i18n_source_hash, i18n_provenance, i18n_output_hash, i18n_hash_version
| i18n_source_hash | i18n_provenance | i18n_output_hash | i18n_hash_version |
|---|---|---|---|
| 521c03a6416c | machine | 78970ccc33b0 | 2 |
Ressourcenarme Setups
SnapOtter läuft gut auf kleiner Hardware: einem Raspberry Pi 4 oder 5, einem alten Laptop oder einem 2-GB-VPS. Diese Seite ist der praktische Leitfaden für solche Maschinen: was dich erwartet, ein Copy-Paste-Setup mit vernünftigen Limits und welche Funktionen du auslassen solltest. Die vollständigen Benchmark-Daten hinter diesen Zahlen findest du unter Hardware-Anforderungen.
Zwei harte Einschränkungen vorweg:
- Nur 64-Bit. Das Image wird für
linux/amd64undlinux/arm64gebaut. 32-Bit-ARM (armv7/armhf) wird nicht unterstützt; Pis der ersten Generation und die Pi-Zero-Familie fallen damit weg. - Speicheruntergrenze 2 GB. Mit 512 MB startet der Stack nicht, und 1 GB scheitert an Stapeln mit mehreren Dateien. 2 GB mit 2 Kernen sind die kleinste Konfiguration, die komfortabel funktioniert.
Was auf kleiner Hardware gut läuft
Jedes Nicht-KI-Tool funktioniert auf einer Maschine mit 2 GB und 2 Kernen: die Bereiche Bild und Dateien komplett, die PDF-Tools und die Stream-Copy-Operationen für Video und Audio (Trimmen, Stummschalten, Container-Remux). Die meisten sind in unter einer Sekunde fertig.
Zwei Workloads sind die Ausnahme:
- Video-Neukodierung (Konvertieren zwischen Codecs) ist CPU-gebunden. Ein 1080p-Clip, der auf einer schnellen Desktop-CPU ~40 s braucht, kann auf einer CPU der Pi-Klasse mehrere Minuten dauern. Stream-Copy-Operationen sind weiterhin sofort erledigt.
- KI-Tools brauchen RAM (4 GB empfohlen) und Festplattenplatz (die größeren Bundles sind je 4-5 GB groß), und die schweren (Hochskalierung, Foto-Wiederherstellung, Hintergrundentfernung) sind auf CPUs der Pi-Klasse nicht praktikabel. Leichte KI wie Gesichtserkennung und OCR ist nutzbar, wenn der Speicher dafür reicht.
Beides ist weder installiert noch aktiv, solange du es nicht benutzt: Ohne installierte KI-Bundles braucht die App im Leerlauf rund 360 MB, und KI-Bundles werden erst heruntergeladen, wenn ein Admin sie aktiviert.
Schritt für Schritt: Raspberry Pi / alter Laptop
Das ist die Standard-Compose-Installation aus Erste Schritte, plus Ressourcenlimits und konservative Obergrenzen. Sie setzt ein 64-Bit-Betriebssystem voraus (auf einem Pi: Raspberry Pi OS 64-bit oder Ubuntu Server arm64).
services:
snapotter:
image: snapotter/snapotter:latest
ports:
- "1349:1349"
volumes:
- ./snapotter-data:/data
environment:
- DATABASE_URL=postgres://snapotter:snapotter@db:5432/snapotter
- REDIS_URL=redis://redis:6379
# Small-box profile: see the table below for what each cap does.
- CONCURRENT_JOBS=1
- MAX_WORKER_THREADS=2
- MAX_BATCH_SIZE=5
- MAX_UPLOAD_SIZE_MB=100
- MAX_MEGAPIXELS=50
- MAX_VIDEO_DURATION_S=300
deploy:
resources:
limits:
cpus: "2"
memory: 2G
depends_on:
- db
- redis
restart: unless-stopped
db:
image: postgres:17-alpine
environment:
- POSTGRES_USER=snapotter
- POSTGRES_PASSWORD=snapotter # Ändern Sie dies für nicht lokale Bereitstellungen
- POSTGRES_DB=snapotter
volumes:
- ./postgres-data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:8-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy noeviction
restart: unless-stopped
Hinweise für Maschinen der Pi-Klasse:
- Nimm lieber eine USB-SSD statt einer SD-Karte für das Daten-Volume und Postgres. Job-Workspaces erzeugen echtes Disk-IO, und SD-Karten sind langsam und schnell verschlissen.
- Der All-in-One-Einzelcontainer funktioniert hier ebenfalls (eingebettetes Postgres und Redis, wenn
DATABASE_URL/REDIS_URLnicht gesetzt sind). Auf einem Host mit knappem Speicher solltest du das Limit seines eingebetteten Redis mitREDIS_MAXMEMORYsenken (siehe Konfiguration). Compose gibt dir feinere Kontrolle pro Service, deshalb nutzt diese Anleitung Compose. - Richte auf 2-GB-Geräten Swap ein. Das verhindert, dass die gelegentliche Spitze (ein großes PDF, ein Stapel, den du zu begrenzen vergessen hast) in einem Out-of-Memory-Kill endet. zram ist die SD-Karten-freundliche Variante.
- Das arm64-Image läuft nur auf der CPU; auf ARM-Boards gibt es kein CUDA.
Die Stellschrauben
Alle Obergrenzen sind Umgebungsvariablen und vollständig unter Konfiguration dokumentiert. 0 bedeutet unbegrenzt oder automatisch. Die wichtigen auf kleiner Hardware:
| Variable | Vorschlag für kleine Maschinen | Wovor sie schützt |
|---|---|---|
CONCURRENT_JOBS |
1 |
Wie viele Jobs parallel laufen. Die Auto-Erkennung nimmt CPU-Kerne minus eins; auf großen Maschinen passt das, auf einer 2-Kern-Box unter Speicherdruck ist es zu forsch. |
MAX_WORKER_THREADS |
2 |
Thread-Pool der Bildverarbeitung. |
MAX_BATCH_SIZE |
5 |
Bei Stapeln geht Maschinen mit 1-2 GB zuerst der Speicher aus. |
MAX_UPLOAD_SIZE_MB |
100 |
Verhindert, dass eine einzelne riesige Datei den gesamten Workspace belegt. |
MAX_MEGAPIXELS |
50 |
Das Dekodieren eines Bilds mit 100+ MP kostet RAM, unabhängig von der Dateigröße. |
MAX_VIDEO_DURATION_S |
300 |
Lange Transkodierungen blockieren eine kleine CPU für Minuten bis Stunden. |
PROCESSING_TIMEOUT_S |
600 |
Harte Obergrenze, damit ein außer Kontrolle geratener Job die Maschine irgendwann wieder freigibt. |
Diese Obergrenzen gelten für das, was der Server annimmt. Setze sie also passend zu dem, was du tatsächlich nutzt, nicht so klein wie möglich. Wenn du Video nie anfasst, kostet ein MAX_VIDEO_DURATION_S-Limit nichts; wenn du täglich Dokumente scannst, begrenze MAX_PDF_PAGES nicht.
Was du auslassen solltest
- Schwere KI-Bundles. Hochskalierung, Foto-Wiederherstellung und Hintergrundentfernung wollen eine GPU oder eine schnelle CPU mit vielen Kernen, und jedes Bundle kostet 4-5 GB Festplattenplatz. Auf einer kleinen Maschine installierst du sie einfach nicht; Tools, deren Bundle fehlt, zeigen eine Installationsaufforderung, statt zu laufen.
- Video-Neukodierung als Dauer-Workload. Gelegentliche Transkodierungen sind in Ordnung (sie sind nur langsam); eine stetige Transcode-Warteschlange braucht CPU-Kerne, keinen Pi.
- Ungenutzte Tools generell. Ein Admin kann einzelne Tools in den Einstellungen abschalten; das entfernt sie aus der Oberfläche und registriert ihre API-Routen nicht mehr. Für sich genommen spart das keinen Speicher, aber es verhindert, dass eine geteilte kleine Instanz für genau den Workload benutzt wird, den die Hardware nicht stemmen kann.
Wenn du die Instanz später auf größere Hardware umziehst, entferne die Obergrenzen (setze sie zurück auf 0); dasselbe Daten-Volume nimmst du einfach mit.