Dateien mit cURL nach Supabase exportieren
Um eine lokale Datei in privaten Supabase Storage zu exportieren, senden Sie ihre Bytes mit einem POST mit Authentifizierung. Laden Sie anschließend das Objekt herunter und vergleichen Sie es mit dem Original. Diese Anleitung nutzt zwei gewöhnliche Auth-Nutzer: Einer lädt die Datei hoch und liest sie, der andere prüft, ob die Zugriffsrichtlinie denselben Download verweigert.
Einen privaten Bucket und seine Richtlinien einrichten
Sie benötigen ein Supabase-Projekt, das Sie administrieren, und zwei verschiedene Auth-Nutzer mit E-Mail-Adresse und Passwort, die sich bereits anmelden können. Bestätigen Sie die E-Mail-Adressen, wenn Ihr Projekt dies verlangt; siehe passwortbasierte Authentifizierung. Ein Supabase-Konto allein erstellt diese Anwendungsnutzer nicht.
Erstellen Sie einen neuen privaten Storage-Bucket mit dem Namen curl-exports
gemäß der Bucket-Schnellstartanleitung. Lassen Sie
application/octet-stream zu, wenn Sie MIME-Typen einschränken, und wählen Sie eine Datei, die
das Größenlimit einhält. Verwenden Sie für dieses Beispiel einen neuen Bucket. Prüfen Sie auch
bestehende Richtlinien für storage.objects: Eine großzügige Regel, die für diesen
Bucket gilt, kann ebenfalls Zugriff gewähren. Die Prüfung mit dem zweiten Nutzer weiter unten
testet die tatsächlich wirksamen Leseberechtigungen.
Führen Sie dieses SQL einmal als Projektadministrator aus. Es erlaubt Uploads und Lesezugriffe nur innerhalb eines Ordners, dessen Name der UUID des angemeldeten Nutzers entspricht:
create policy "curl_exports_insert"
on storage.objects for insert to authenticated
with check (
bucket_id = 'curl-exports'
and (storage.foldername(name))[1] = (select auth.uid())::text
);
create policy "curl_exports_select"
on storage.objects for select to authenticated
using (
bucket_id = 'curl-exports'
and (storage.foldername(name))[1] = (select auth.uid())::text
and owner_id = (select auth.uid())::text
);
Die Storage-Zugriffskontrolle
erläutert die Richtlinien für storage.objects. Der Dateiuploader unten verwendet
das Zugriffstoken eines Nutzers; ein geheimer Schlüssel oder ein Service-Role-Schlüssel würde
RLS umgehen und die Datenschutzprüfung ungültig machen.
Shell-Werkzeuge und Zugangsdaten prüfen
Verwenden Sie Bash unter Linux mit cURL, jq
und den üblichen, bereits installierten Werkzeugen cmp,
mktemp und rm. Getestet wurden Bash 5.3.15,
cURL 8.22.0 und jq 1.8.2. Dies sind getestete Versionen, keine exakten Versionsvorgaben;
das Skript verwendet die Option --fail-with-body von cURL und prüft jedes benötigte
Werkzeug, bevor es temporäre Dateien erstellt. Seine Anfragen ignorieren die Einstellungen in
.curlrc. Führen Sie die Einrichtungsbefehle in Bash aus.
curl --disable --version && jq --version && bash --version
Diese Anleitung nutzt einen vorhandenen Legacy-API-Schlüssel anon,
passend zum getesteten lokalen Stack. Supabase plant, Legacy-Schlüssel bis Ende 2026 als veraltet
zu kennzeichnen; die API-Schlüssel-Anleitung beschreibt den Abruf
und die Migration. Legen Sie die Projekt-URL, den Schlüssel und den Bucket dieses Stacks in der
Shell fest, aus der Sie das Skript ausführen werden:
export SUPABASE_URL='https://YOUR_PROJECT_ID.supabase.co' \
SUPABASE_KEY='YOUR_ANON_KEY' \
SUPABASE_BUCKET='curl-exports'
Lassen Sie Ihren Zugangsdatenmanager oder Job-Runner die Werte SUPABASE_EMAIL,
SUPABASE_PASSWORD, SUPABASE_OTHER_EMAIL und SUPABASE_OTHER_PASSWORD
als Umgebungsvariablen bereitstellen. Das erste Paar gehört zum Nutzer, der die Datei hochlädt;
das zweite zum Nutzer, dem der Zugriff verweigert werden muss. Vermeiden Sie Passwörter im
Shell-Verlauf. Der Projektschlüssel ersetzt nicht das Nutzer-JWT im Header
Authorization.
Dieses Beispiel wurde mit lokalem Supabase Auth v2.196.0 und Storage v1.74.0 sowie PostgreSQL 17.6 getestet. Die Erstellung eines gehosteten Projekts, TLS, die Dashboard-Konfiguration und Tarifkontingente sind separate Voraussetzungen. Verwenden Sie HTTPS für ein gehostetes Projekt; Loopback-HTTP eignet sich für einen lokalen Entwicklungsstack.
Eine Datei hochladen, herunterladen und verifizieren
Speichern Sie den folgenden Inhalt als neue Datei namens export-to-supabase.sh.
Führen Sie sie mit Bash aus, statt sie mit source in Ihre aktuelle Shell einzulesen. Das erste
Argument ist die lokale Datei; das zweite ist der Objektpfad unterhalb der UUID des Nutzers,
der sie hochlädt. Die Segmente des Objektpfads müssen mit einem ASCII-Buchstaben oder einer
Ziffer beginnen und dürfen nur ASCII-Buchstaben, Ziffern, Leerzeichen, Punkte, Unterstriche und
Bindestriche enthalten. Lokale Dateinamen müssen dieser engeren Regel für Objektnamen nicht folgen.
#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C
die() { printf '%s\n' "$1" >&2; exit 1; }
for tool in curl jq cmp mktemp rm; do
command -v "$tool" >/dev/null || die "Missing required tool: $tool"
done
[[ $# -eq 2 ]] || die 'Usage: bash export-to-supabase.sh LOCAL_FILE OBJECT_PATH'
: "${SUPABASE_URL:?Set SUPABASE_URL}" "${SUPABASE_KEY:?Set SUPABASE_KEY}"
: "${SUPABASE_BUCKET:?Set SUPABASE_BUCKET}"
: "${SUPABASE_EMAIL:?Set SUPABASE_EMAIL}" "${SUPABASE_PASSWORD:?Set SUPABASE_PASSWORD}"
: "${SUPABASE_OTHER_EMAIL:?Set SUPABASE_OTHER_EMAIL}"
: "${SUPABASE_OTHER_PASSWORD:?Set SUPABASE_OTHER_PASSWORD}"
file=$1
case "$file" in /*) ;; *) file="./$file" ;; esac
[[ -f "$file" && -r "$file" ]] || die 'Select a readable regular file'
object_path=$2
path_pattern='^[A-Za-z0-9][A-Za-z0-9._ -]*(/[A-Za-z0-9][A-Za-z0-9._ -]*)*$'
[[ "$object_path" =~ $path_pattern ]] || die 'Unsupported object path'
[[ "$SUPABASE_BUCKET" =~ ^[a-z0-9][a-z0-9-]*$ ]] || die 'Unsupported bucket name'
base_url=${SUPABASE_URL%/}
umask 077
work=$(mktemp -d)
trap 'rm -rf -- "$work"' EXIT
sign_in() {
AUTH_EMAIL=$1 AUTH_PASSWORD=$2 jq -n \
'{email: env.AUTH_EMAIL, password: env.AUTH_PASSWORD}' |
curl --disable --fail-with-body --silent --show-error \
"$base_url/auth/v1/token?grant_type=password" \
-H "apikey: $SUPABASE_KEY" -H 'Content-Type: application/json' \
--data-binary @- --output "$3" || die 'Password sign-in failed'
jq -e '.user.role == "authenticated"' "$3" >/dev/null || die 'Expected an authenticated Auth user'
}
sign_in "$SUPABASE_EMAIL" "$SUPABASE_PASSWORD" "$work/owner.json"
sign_in "$SUPABASE_OTHER_EMAIL" "$SUPABASE_OTHER_PASSWORD" "$work/other.json"
token=$(jq -er '.access_token | select(type == "string" and length > 0)' "$work/owner.json")
other_token=$(jq -er '.access_token | select(type == "string" and length > 0)' "$work/other.json")
user_id=$(jq -er '.user.id | select(type == "string" and length > 0)' "$work/owner.json")
other_id=$(jq -er '.user.id | select(type == "string" and length > 0)' "$work/other.json")
[[ "$user_id" != "$other_id" ]] || die 'Use two distinct Auth users'
encoded_path=$(jq -rn --arg path "$user_id/$object_path" \
'$path | split("/") | map(@uri) | join("/")')
object_url="$base_url/storage/v1/object/$SUPABASE_BUCKET/$encoded_path"
download_url="$base_url/storage/v1/object/authenticated/$SUPABASE_BUCKET/$encoded_path"
curl --disable --fail-with-body --silent --show-error --request POST "$object_url" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $token" \
-H 'Content-Type: application/octet-stream' \
--data-binary "@$file" --output "$work/upload.json" || die 'File upload failed'
curl --disable --fail-with-body --silent --show-error "$download_url" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $token" \
--output "$work/download.bin" || die 'Owner download failed'
cmp -s -- "$file" "$work/download.bin" || die 'Downloaded bytes differ from the selected file'
denied_http=$(curl --disable --silent --show-error "$download_url" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $other_token" \
--output "$work/denied.json" --write-out '%{http_code}') || die 'Access-check request failed'
case "$denied_http" in
400)
jq -e '(.statusCode | tostring) == "403" or (.statusCode | tostring) == "404"' \
"$work/denied.json" >/dev/null || die 'Unexpected Storage error'
;;
403|404) ;;
*) die "Expected access denial; received HTTP $denied_http" ;;
esac
printf 'Verified upload and private access.\n'
Ersetzen Sie ./report.bin durch die gewählte Datei. Verändern Sie diese nicht,
während das Skript läuft. Der entfernte Dateiname kann vom lokalen Namen abweichen:
bash export-to-supabase.sh './report.bin' 'exports/report.bin'
Bei erfolgreicher Ausführung wird Folgendes ausgegeben:
Verified upload and private access.
Das Objekt bleibt unter curl-exports/<uploader-uuid>/exports/report.bin gespeichert. Die heruntergeladene Kopie
und die Auth-Antworten sind temporär und werden beim Beenden des Skripts entfernt. Bei einer
Abweichung oder einem unerwartet erfolgreichen Download durch den anderen Nutzer endet das
Skript mit einem Exit-Code ungleich null, ohne diese Erfolgsmeldung auszugeben.
Der Endpunkt für private Downloads
verwendet das Nutzer-JWT und die Leserichtlinie; der API-Schlüssel allein gewährt diesen Zugriff nicht.
Bestehende Objekte behandeln, ohne frühere Exporte zu verlieren
Der POST erstellt ein neues Objekt. Wird derselbe Befehl erneut ausgeführt, zielt er auf dieselbe UUID und denselben Objektpfad. Ein bereits vorhandenes Objekt wird daher abgewiesen. Das frühere Objekt bleibt gespeichert. Verwenden Sie für einen neuen Export einen anderen Objektpfad.
Die Anleitung für Standard-Uploads von Supabase
beschreibt das gezielte Ersetzen mit x-upsert: true, wofür auch Richtlinien für
SELECT und UPDATE nötig sind. Diese Anleitung
gewährt keine Aktualisierungsberechtigung und überschreibt keine Objekte. Scheitert eine Verbindung,
nachdem der Server die Datei gespeichert hat, ist das Ergebnis des Uploads ungewiss. Prüfen Sie
genau dieses Objekt und laden Sie es herunter, bevor Sie entscheiden, ob Sie es behalten oder einen
neuen Pfad verwenden. Eine Duplikatmeldung bei einem erneuten Versuch kann bedeuten, dass der erste
Upload erfolgreich war.
Dateigrößenlimits verstehen
Wählen Sie eine kleine Datei, die sowohl das globale Limit des Projekts als auch das Bucket-Limit
einhält. Die Obergrenzen gehosteter Tarife und Bucket-Einschränkungen beschreibt die
Dokumentation zu Dateigrößenlimits von Supabase.
Die Option --data-binary @file von cURL
puffert die Eingabe im Arbeitsspeicher,
und dieses Beispiel setzt eine unterbrochene Übertragung nicht fort. Folgen Sie für größere Dateien
der separaten Anleitung für fortsetzbare Uploads.
Den Dateiexport im täglichen Workflow automatisieren
Das gespeicherte Skript meldet sich bei jedem Aufruf an. Die Automatisierung ist daher nicht darauf angewiesen, dass ein eingefügtes JWT gültig bleibt. Lassen Sie Ihren Scheduler die Umgebungsvariablen bereitstellen, verwenden Sie absolute Pfade zum Skript und zur Eingabedatei und wählen Sie für jeden Export einen neuen Objektpfad. Behalten Sie das zweite Konto zur Berechtigungsprüfung bei und werten Sie einen Exit-Code ungleich null als fehlgeschlagene Verifizierung, auch wenn der Upload bereits auf dem Server vorhanden ist.
Häufige Probleme beheben
Authentifizierungsfehler (401 nicht autorisiert)
Prüfen Sie zuerst die Projekt-URL und den API-Schlüssel. Prüfen Sie bei einer fehlgeschlagenen Passwortanmeldung die E-Mail-Adresse, das Passwort und den Bestätigungsstatus des Nutzers. Das Skript stoppt bei einer fehlgeschlagenen Anmeldung; es ersetzt den Schlüssel nicht durch einen Administratorschlüssel. Läuft das Token während einer Übertragung ab, prüfen Sie, ob der frühere Upload gespeichert wurde, und führen Sie das Skript danach mit einem neuen Objektpfad erneut aus.
Fehler durch doppelte Dateien
Ein doppelter Pfad ist ein Fehler, bei Storage meist HTTP 400. Ändern Sie für einen neuen Export das zweite Skriptargument. Fügen Sie keine automatischen Retries oder Upsert hinzu, nur um den Konflikt zu verbergen.
Dateigrößenlimits (413 Nutzlast zu groß)
Ein zu großer Upload kann HTTP 413 auslösen. Die getestete Storage-Version gab bei einer Ablehnung
wegen des Bucket-Größenlimits HTTP 400 mit Status 413 im Antwortkörper zurück. Prüfen Sie beide
Größenlimits und bei einer Ablehnung wegen des Dateityps auch die Liste erlaubter MIME-Typen des
Buckets. Dieses Skript sendet application/octet-stream; es erkennt den Medientyp der Datei
nicht. Die Storage-Fehlercodes erläutern die Diagnosen des Dienstes.
Zugriff verweigert
Prüfen Sie, ob der Bucket privat ist und die Richtlinien für seinen exakten Namen, den UUID-Ordner
des hochladenden Nutzers und owner_id gelten. Ein nicht zugängliches privates
Objekt kann so erscheinen, als fehle es: Die getestete Storage-Version gab für den zweiten Nutzer
HTTP 400 mit Status 404 im Antwortkörper zurück. Das Skript liest und vergleicht dasselbe Objekt
zuerst als dessen Eigentümer. Ein fehlendes Objekt kann diese Prüfung daher nicht bestehen.
Erhält der andere Nutzer HTTP 200, prüfen Sie bestehende Richtlinien und die Sichtbarkeit des
Buckets, bevor Sie sensible Daten exportieren.
