Diese Seite wurde maschinell übersetzt. Einen Fehler entdeckt?Helfen Sie mit, sie zu verbessern.
Skip to content

Datenbank

SnapOtter verwendet PostgreSQL 17 mit Drizzle ORM (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. Anfragen werden über eine Rolle bedient, die ausschließlich Zeilen lesen und schreiben kann; Näheres dazu weiter unten unter Rollen mit minimalen Rechten.

Tabellen

users

Speichert Benutzerkonten. Wird beim ersten Start automatisch aus DEFAULT_USERNAME und DEFAULT_PASSWORD erstellt.

SpalteTypHinweise
iduuidPrimärschlüssel
usernamevarcharEindeutig, erforderlich
passwordHashvarcharscrypt-Hash
rolevarcharadmin, editor oder user
mustChangePasswordbooleanFlag für erzwungenes Zurücksetzen des Passworts
createdAttimestampErstellungszeitpunkt
updatedAttimestampZeitpunkt der letzten Aktualisierung

sessions

Aktive Anmelde-Sitzungen. Jede Zeile verknüpft ein Sitzungstoken mit einem Benutzer.

SpalteTypHinweise
idvarcharPrimärschlüssel (Sitzungstoken)
userIduuidFremdschlüssel auf users.id
expiresAttimestampAblaufzeitpunkt
createdAttimestampErstellungszeitpunkt

teams

Gruppen zum Organisieren von Benutzern. Admins können Benutzer Teams zuweisen.

SpalteTypBeschreibung
iduuidPrimärschlüssel
namevarchar (eindeutig, max. 50 Zeichen)Teamname
createdAttimestampErstellungszeitpunkt

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.

SpalteTypHinweise
iduuidPrimärschlüssel
userIduuidFremdschlüssel auf users.id
keyHashvarcharscrypt-Hash des Schlüssels
namevarcharVom Benutzer vergebene Bezeichnung
createdAttimestampErstellungszeitpunkt
lastUsedAttimestampBei jeder authentifizierten Anfrage aktualisiert

Schlüssel haben das Präfix si_ gefolgt von 96 Hex-Zeichen (48 zufällige Bytes).

pipelines

Gespeicherte Tool-Ketten, die Benutzer in der Oberfläche erstellen.

SpalteTypHinweise
iduuidPrimärschlüssel
namevarcharPipeline-Name
descriptionvarcharOptionale Beschreibung
stepsjsonbArray von { toolId, settings }-Objekten
createdAttimestampErstellungszeitpunkt

user_files

Persistente Dateibibliothek. Ein gespeicherter Edit wird standardmäßig als eigenständige Root-Zeile eingefügt ("Als neu speichern": version 1, parentId null, sodass das Original weiterhin gelistet bleibt), oder als übergeordnet verknüpfte Version, wenn du das Original überschreibst (parentId gesetzt, version erhöht, das Original wird abgelöst). Die Spalte toolChain erfasst die angewendeten Werkzeuge.

SpalteTypBeschreibung
iduuidPrimärschlüssel
userIduuidFK auf users (CASCADE DELETE)
originalNamevarcharUrsprünglicher Upload-Dateiname
storedNamevarcharDateiname auf dem Datenträger
mimeTypevarcharMIME-Typ
sizeintegerDateigröße in Bytes
widthintegerBildbreite in px
heightintegerBildhöhe in px
versionintegerVersionsnummer (1 = Original)
parentIduuid oder nullFK auf user_files (übergeordnete Version)
toolChainjsonbTool-IDs, die in Reihenfolge angewendet wurden, um diese Version zu erzeugen
createdAttimestampErstellungszeitpunkt

jobs

Verfolgt Verarbeitungs-Jobs für Fortschrittsanzeige und Bereinigung.

SpalteTypHinweise
iduuidPrimärschlüssel
typevarcharTool- oder Pipeline-Bezeichner
statusvarcharqueued, processing, completed oder failed
progressrealAnteil 0.0-1.0
inputFilesjsonbArray von Eingabedatei-Pfaden
outputPathvarcharPfad zur Ergebnisdatei
settingsjsonbVerwendete Tool-Einstellungen
errorvarcharFehlermeldung bei Fehlschlag
createdAttimestampErstellungszeitpunkt
completedAttimestampAbschlusszeitpunkt

settings

Schlüssel-Wert-Speicher für serverweite Einstellungen, die Admins über die Oberfläche ändern können.

SpalteTypHinweise
keyvarcharPrimärschlüssel
valuevarcharEinstellungswert
updatedAttimestampZeitpunkt der letzten Aktualisierung

roles

Benutzerdefinierte Rollen mit granularen Berechtigungen.

SpalteTypHinweise
iduuidPrimärschlüssel
namevarcharEindeutiger Rollenname
descriptionvarcharOptionale Beschreibung
permissionsjsonbArray von Berechtigungs-Strings
createdAttimestampErstellungszeitpunkt

audit_log

Protokoll sicherheitsrelevanter Aktionen.

SpalteTypHinweise
iduuidPrimärschlüssel
userIduuidFK auf users
actionvarcharAktionstyp
detailsjsonbAktionsspezifische Daten
createdAttimestampZeitpunkt der Aktion

user_preferences

Oberflächenzustand pro Benutzer, abgelegt unter einem Präferenznamen. Speichert die angehefteten Tools der Startseite, die über PUT /api/v1/preferences geschrieben werden.

SpalteTypHinweise
userIdtextFK auf users, kaskadierendes Löschen. Zusammen mit key der Primärschlüssel
keytextName der Präferenz. Zusammen mit userId der Primärschlüssel
valuejsonbInhalt der Präferenz
updatedAttimestampZeitpunkt des letzten Schreibvorgangs

Migrationen

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.

Rollen mit minimalen Rechten

Zwei Rollen, zwei Aufgaben. DATABASE_URL bedient Anfragen und besitzt SELECT, INSERT, UPDATE, DELETE auf den Tabellen der App sowie USAGE und SELECT auf deren Sequenzen. Mehr ist es nicht. Sie kann keine Tabelle anlegen oder löschen, keine Erweiterung installieren, kein TRUNCATE ausführen, pg_authid nicht lesen, keine Datenbank anlegen, keine Rolle ändern und das Schema drizzle, in dem die Migrationshistorie liegt, nicht anfassen.

DATABASE_MIGRATION_URL ist die privilegierte Verbindung. Sie führt beim Start die Migrationen aus und vergibt die Rechte an die Laufzeitrolle, danach wird sie geschlossen, bevor die erste Anfrage bedient wird.

Compose und das All-in-One-Image sind bereits so verdrahtet, bestehende Installationen eingeschlossen. Beim Start legt SnapOtter die Laufzeitrolle an, falls sie fehlt, vergibt die Rechte, migriert und zieht die Rechte anschließend über die Tabellen nach, die schon vorher da waren. Für ein Upgrade ist kein manuelles SQL nötig.

Bleibt DATABASE_MIGRATION_URL leer, läuft SnapOtter mit einer einzigen Rolle, und DATABASE_URL übernimmt beide Aufgaben genau wie vor der Trennung. Das ist eine unterstützte Konfiguration und keine veraltete. Bei verwaltetem Postgres ist sie oft die richtige Wahl, denn dort liegt das Anlegen von Rollen häufig nicht in Ihrer Hand.

Externes und verwaltetes Postgres

Bei RDS, Supabase, Cloud SQL oder einem selbst betriebenen Cluster ist die Trennung optional. Legen Sie die Laufzeitrolle einmalig an:

sql
CREATE ROLE snapotter_app LOGIN PASSWORD 'choose-a-strong-password'
  NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS;

Übergeben Sie SnapOtter anschließend beide Verbindungszeichenfolgen, die auf denselben Host, denselben Port und dieselbe Datenbank zeigen:

bash
DATABASE_URL=postgres://snapotter_app:[email protected]:5432/snapotter
DATABASE_MIGRATION_URL=postgres://snapotter:[email protected]:5432/snapotter

Mehr ist nicht nötig. SnapOtter vergibt die Rechte selbst und erneuert sie nach jeder Migration, sodass eine Tabelle aus einem künftigen Release abgedeckt ist, ohne dass jemand dafür SQL ausführt.

Die Rolle in DATABASE_MIGRATION_URL muss Eigentümerin der SnapOtter-Tabellen sein, denn nur der Eigentümer einer Tabelle kann Rechte darauf vergeben. Bei einer bestehenden Installation ist das die Rolle, unter der SnapOtter bisher lief, und keine eigens dafür angelegte. Zeigt der Eintrag auf eine neue Rolle, der nichts gehört, schlägt der Start mit genau dieser Fehlermeldung fehl. Zusätzlich braucht die Rolle CREATEROLE, um die Laufzeitrolle anzulegen und zu pflegen, sowie das Recht, das Schema drizzle zu erstellen.

Steht in beiden URLs dieselbe Rolle, ist die Trennung aufgehoben, und SnapOtter schreibt das ins Log, statt etwas anderes vorzugeben. Bietet Ihr Anbieter keine Rolle, die zugleich die Tabellen besitzt und CREATEROLE hat, betreiben Sie SnapOtter mit einer einzigen Rolle.

Warum das Superuser-Bit unangetastet bleibt

SnapOtter entzieht einer Rolle niemals von sich aus SUPERUSER. Bei einer Installation, die vor der Trennung entstanden ist, ist snapotter der einzige Superuser des Clusters, und eine Herabstufung ließe das Cluster ohne einen solchen zurück, wiederherstellbar nur über den Single-User-Modus bei gestopptem Server. Den Schutz bringt stattdessen die Verlagerung der langlebigen Verbindung auf die eingeschränkte Rolle. Der Superuser ist nur für die wenigen Sekunden des Starts auf der Leitung und danach weg.

Neue All-in-One-Installationen haben dieses Problem nie. Sie bekommen drei Rollen: postgres (Bootstrap-Superuser, in keiner von SnapOtter genutzten Verbindungszeichenfolge enthalten), snapotter (NOSUPERUSER, Eigentümerin der Daten, verbindet sich nur beim Start) und snapotter_app (nur Zeilen, bedient Anfragen).

Wer ein älteres snapotter dennoch herabstufen möchte, legt zuerst einen zweiten Superuser an und meldet sich damit an, um zu prüfen, dass er funktioniert. Danach ALTER ROLE snapotter NOSUPERUSER.

Sichern und Wiederherstellen von

Die relationale Datenbank befindet sich im SnapOtter-pgdata-Volume des Postgres-Containers, nicht im /data-Volume der App.

Logische Sicherung mit Validierung (empfohlen)

bash
# Dump into PostgreSQL's portable custom archive format
docker exec SnapOtter-postgres \
  pg_dump --format=custom --no-owner -U snapotter snapotter > snapotter.dump
test -s snapotter.dump
docker exec -i SnapOtter-postgres pg_restore --list < snapotter.dump >/dev/null

# Restore into a fresh/disposable target first and fail on the first SQL error
docker exec -i SnapOtter-postgres \
  pg_restore --exit-on-error --clean --if-exists --no-owner \
  -U snapotter -d snapotter < snapotter.dump

Beide Befehle verbinden sich als snapotter, also als Eigentümer, und sollten das auch weiterhin tun. Die Laufzeitrolle sieht das Schema drizzle nicht, ein als diese Rolle erstellter Dump wäre also unvollständig. --no-owner überlässt die wiederhergestellten Objekte demjenigen, der die Wiederherstellung ausführt; als Eigentümer ausgeführt landet die Eigentümerschaft damit dort, wo die Rechte sie erwarten. Ein Haken bei einem frischen Cluster: pg_dump überträgt zwar die Rechte, nicht aber die darin genannten Rollen. Legen Sie snapotter_app deshalb vor der Wiederherstellung an, sonst bricht --exit-on-error beim ersten GRANT ab. Die Rechte vergibt SnapOtter beim nächsten Start ohnehin erneut.

Dieser Datenbank-Dump enthält keine gespeicherten Bibliotheksobjekte im /data/files- oder dauerhaften BullMQ-Status in Redis. Sichern und wiederherstellen Sie diese mit dem koordinierten Verfahren in Sicherheit und Härtung.

Schnappschuss des kalten Volumens

bash
# Stop every service first, then use your storage platform to snapshot the
# PostgreSQL, app-data, and Redis volumes as one crash-consistent set.
docker compose -f docker/docker-compose.yml stop

Kopieren Sie kein Live-PostgreSQL-Datenverzeichnis mit tar. Verfassen Sie Volume-Namen mit Präfixen nach Projekt. Lösen Sie daher die gemounteten Volume-IDs von docker inspect oder Ihrer Speicherplattform auf, anstatt die wörtliche Bezeichnung SnapOtter-pgdata anzunehmen.

Migration von 1.x (SQLite)

Das Upgrade von SnapOtter 1.x hat einen eigenen Leitfaden: siehe Upgrade von 1.x auf 2.0. 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.