Dateien mit Ruby in Supabase-Buckets exportieren
Um eine Datei mit Ruby nach Supabase zu exportieren, senden Sie ihre Bytes an die Storage REST API. Laden Sie anschließend das ausgewählte Objekt herunter und vergleichen Sie es mit der Eingabe. Dieses Beispiel meldet sich als gewöhnlicher Nutzer an, lehnt bestehende Objektpfade ab und belässt einen verifizierten Upload im Bucket. Das Löschen erfolgt mit einem separaten Befehl.
Voraussetzungen
- Ruby 4.0.7, Bundler sowie ein Compiler und
make, um das JSON-Gem zu installieren. Die Downloadseite von Ruby listet aktuelle Versionen und Installationsmöglichkeiten auf. Die Linux-Einrichtung wurde mit Bundler 4.0.20 sowie mit Ruby 3.3.12 und Bundler 2.5.22 getestet. - Bash für die folgenden Shell-Befehle. Beginnen Sie in einem beschreibbaren Verzeichnis ohne
bestehendes Unterverzeichnis namens
supabase-storage-demound ohne exportierte Überschreibungen durchBUNDLE_*,GEM_HOMEoderGEM_PATH. - Ein Supabase-Projekt, ein bestätigter Auth-Nutzer mit E-Mail und Passwort sowie ein dedizierter,
privater Storage-Bucket namens
ruby-exports. Erstellen Sie ihn anhand der offiziellen Anleitung zur Bucket-Erstellung; erlauben Sie den Inhaltstypapplication/octet-streamund Dateien bis zu 1 MiB. - Die Projekt-URL, der Projektschlüssel sowie E-Mail und Passwort des Nutzers als Umgebungsvariablen.
Verwenden Sie für gehostete Projekte einen
veröffentlichbaren Projektschlüssel. Er gehört in den Header
apikey; das JWT des angemeldeten Nutzers gehört in Authorization: Bearer.
Supabase kündigt an, die bisherigen Schlüssel anon und service_role bis Ende 2026 als veraltet einzustufen.
Bestehende Projekte können während der Migration beide Schlüsselsysteme verwenden. Die native
Überprüfung dieses Beispiels nutzt ein lokales, selbst gehostetes Projekt mit einem bisherigen
Schlüssel vom Typ anon, kein gehostetes Projekt mit veröffentlichbarem Schlüssel.
Die Bereitstellung gehosteter Schlüssel und TLS wurden hier nicht getestet.
Führen Sie die folgenden SQL-Anweisungen als Projektadministrator für diesen dedizierten Bucket aus.
Diese Storage-Richtlinien erlauben Uploads unter der UUID des Nutzers
und lassen ihn seine eigenen Objekte lesen und löschen. Für den Upload ist
INSERT erforderlich, für das Zurücklesen SELECT.
Das Löschen erfordert außerdem SELECT und DELETE, wie in der
Referenz zum Löschen aus Storage dokumentiert.
Prüfen Sie zuerst, ob bereits weiter gefasste Richtlinien bestehen: Freigebende Richtlinien wirken
zusammen. Diese Regeln schränken daher keinen Zugriff ein, den eine andere Richtlinie bereits gewährt.
create policy ruby_exports_insert on storage.objects
for insert to authenticated with check (
bucket_id = 'ruby-exports'
and (storage.foldername(name))[1] = (select auth.uid())::text
);
create policy ruby_exports_select on storage.objects
for select to authenticated using (
bucket_id = 'ruby-exports'
and (storage.foldername(name))[1] = (select auth.uid())::text
and owner_id = (select auth.uid())::text
);
create policy ruby_exports_delete on storage.objects
for delete to authenticated using (
bucket_id = 'ruby-exports'
and (storage.foldername(name))[1] = (select auth.uid())::text
and owner_id = (select auth.uid())::text
);
Stellen Sie SUPABASE_URL, SUPABASE_PROJECT_KEY, SUPABASE_EMAIL und SUPABASE_PASSWORD über
Ihren Secret-Manager oder eine geschützte Prozessumgebung bereit. Verwenden Sie die Stamm-URL des
Projekts, etwa https://PROJECT_REF.supabase.co. Speichern Sie keine Passwörter, Nutzertokens oder
Administrationsschlüssel im Quellcode oder Shell-Verlauf. Das Programm erhält bei jedem Aufruf über
den Password Grant von Auth eine neue Nutzersitzung; es benötigt
weder einen Secret-Key noch einen Schlüssel vom Typ service_role.
Umgebung einrichten
Fügen Sie diesen Block in Bash ein. Die Subshell bewahrt Ihr aktuelles Verzeichnis und Ihre Shell-Optionen, auch wenn die Einrichtung fehlschlägt. Ein bestehendes Projektverzeichnis bleibt erhalten.
(
command -v ruby >/dev/null &&
command -v bundle >/dev/null &&
mkdir supabase-storage-demo &&
cd supabase-storage-demo &&
bundle init &&
bundle config set --local path vendor/bundle
)
Ersetzen Sie die neu erstellte Datei supabase-storage-demo/Gemfile durch:
source 'https://rubygems.org'
gem 'json', '3.0.2'
gem 'net-http', '0.10.0'
Die festgelegten Versionen verwenden die aktuellen Veröffentlichungen von
JSON und Net::HTTP,
geprüft am 12. Oktober 2026. Installieren Sie sie im Verzeichnis vendor/bundle
dieses Projekts, ohne die Gems systemweit zu installieren:
(cd supabase-storage-demo && bundle install)
Falls die Installation fehlschlägt, beheben Sie das gemeldete Problem mit dem Netzwerk oder einer Compiler-Voraussetzung und führen Sie den Installationsblock im selben Projekt erneut aus. Behalten Sie dessen Gemfile und lokale Bundler-Konfiguration bei. Wiederholen Sie nicht den Block zur Verzeichniserstellung und entfernen Sie kein bestehendes Projekt für einen erneuten Versuch.
Einen REST API-Client in Ruby konfigurieren
Speichern Sie dieses vollständige Programm als supabase-storage-demo/storage.rb. Es verarbeitet jeweils
eine Datei, einschließlich leerer und binärer Dateien, mit einer anwendungsseitigen Obergrenze von
1 MiB. Lokale Dateinamen müssen gültige UTF-8-Bytes ohne Steuerzeichen enthalten; die Validierung
hängt nicht von den Spracheinstellungen der Shell ab. Objektschlüssel verwenden ASCII-Buchstaben,
Ziffern, Leerzeichen, _, -, . und /.
Ordnersegmente dürfen weder leer sein noch . oder .. lauten.
Wählen Sie unabhängig vom lokalen Dateinamen einen unterstützten Schlüssel: Die getestete
Storage-Version lehnt Unicode und Prozentzeichen in Schlüsseln ab.
Das Programm codiert jedes unterstützte Schlüsselsegment für die URL.
require 'digest'
require 'json'
require 'net/http'
require 'uri'
class StorageError < StandardError
end
class SupabaseStorage
MAX_BYTES = 1024 * 1024
attr_reader :user_id
def initialize
@url = ENV.fetch('SUPABASE_URL').chomp('/')
origin = URI(@url)
secure = origin.is_a?(URI::HTTPS)
local = origin.is_a?(URI::HTTP) && %w[localhost 127.0.0.1 [::1]].include?(origin.host)
unless (secure || local) && origin.userinfo.nil? && origin.query.nil? &&
origin.fragment.nil? && origin.path.empty?
raise StorageError, 'Use an HTTPS project root URL, or HTTP on localhost for local testing'
end
@project_key = ENV.fetch('SUPABASE_PROJECT_KEY')
login = Net::HTTP::Post.new('/auth/v1/token?grant_type=password')
login['Content-Type'] = 'application/json'
login.body = JSON.generate(email: ENV.fetch('SUPABASE_EMAIL'),
password: ENV.fetch('SUPABASE_PASSWORD'))
session = JSON.parse(request(login).body)
@token = session.fetch('access_token')
user = JSON.parse(request(Net::HTTP::Get.new('/auth/v1/user')).body)
@user_id = user.fetch('id')
end
def upload(bucket, key, path)
bytes = input_bytes(path)
upload = Net::HTTP::Post.new(object_path(bucket, key))
upload['Content-Type'] = 'application/octet-stream'
upload['x-upsert'] = 'false'
upload.body = bytes
request(upload)
verify_bytes(bucket, key, bytes)
end
def verify(bucket, key, path)
verify_bytes(bucket, key, input_bytes(path))
end
def delete(bucket, key)
key = text(key)
object_path(bucket, key)
deletion = Net::HTTP::Delete.new("/storage/v1/object/#{segment(bucket)}")
deletion['Content-Type'] = 'application/json'
deletion.body = JSON.generate(prefixes: [key])
deleted = JSON.parse(request(deletion).body)
unless deleted.is_a?(Array) && deleted.any? { |object| object['name'] == key }
raise StorageError, 'Deletion was not confirmed; the object may be missing or inaccessible'
end
puts 'Deletion confirmed'
end
private
def request(message)
message['apikey'] = @project_key
message['Authorization'] = "Bearer #{@token}" if @token
uri = URI(@url)
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 30, write_timeout: 30) do |http|
http.max_retries = 0
response = http.request(message)
unless response.is_a?(Net::HTTPSuccess)
raise StorageError, "Request failed (HTTP #{response.code})"
end
response
end
end
def text(value)
value = value.dup.force_encoding(Encoding::UTF_8)
unless value.valid_encoding? && !value.match?(/[\x00-\x1f\x7f]/)
raise StorageError, 'Paths must be valid UTF-8 without control characters'
end
value
end
def segment(value)
URI.encode_www_form_component(text(value)).gsub('+', '%20')
end
def object_path(bucket, key)
raise StorageError, 'Invalid bucket name' unless bucket.match?(/\A[a-z0-9][a-z0-9-]*\z/)
key = text(key)
unless key.match?(/\A[A-Za-z0-9_.\/ -]+\z/)
raise StorageError, 'Use an ASCII object key with letters, digits, spaces, _, -, ., and /'
end
parts = key.split('/', -1)
if parts.any? { |part| ['', '.', '..'].include?(part) } || key.include?('\\')
raise StorageError, 'Use a nonempty object key with ordinary folder segments'
end
"/storage/v1/object/#{segment(bucket)}/#{parts.map { |part| segment(part) }.join('/')}"
end
def input_bytes(path)
path = text(path)
raise StorageError, 'Input must be a regular file' unless File.file?(path)
bytes = File.binread(path, MAX_BYTES + 1) || ''.b
raise StorageError, 'Input exceeds the 1 MiB application limit' if bytes.bytesize > MAX_BYTES
bytes
end
def verify_bytes(bucket, key, expected)
download = Net::HTTP::Get.new(object_path(bucket, key))
download['Accept-Encoding'] = 'identity'
actual = request(download).body || ''.b
unless actual.b == expected.b
raise StorageError, 'Stored bytes differ from the input; inspect the object before retrying'
end
puts "Verified #{expected.bytesize} bytes; SHA-256 #{Digest::SHA256.hexdigest(actual)}"
end
end
begin
command, bucket, key, path = ARGV
valid = (command == 'whoami' && ARGV.length == 1) ||
(%w[upload verify].include?(command) && ARGV.length == 4) ||
(command == 'delete' && ARGV.length == 3)
unless valid
raise StorageError, 'Usage: storage.rb whoami | upload/verify BUCKET KEY FILE | delete BUCKET KEY'
end
storage = SupabaseStorage.new
case command
when 'whoami' then puts storage.user_id
when 'upload' then storage.upload(bucket, key, path)
when 'verify' then storage.verify(bucket, key, path)
when 'delete' then storage.delete(bucket, key)
end
rescue StandardError => error
# Only our deliberate messages are safe to print; library errors can contain private data.
safe = error.instance_of?(StorageError) ? error.message : 'Check configuration, input, and connectivity'
warn "Storage command failed: #{safe}"
exit 1
end
Der Upload sendet rohe Bytes, keine JSON- oder Multipart-Felder. Das Zurücklesen nutzt den Objektendpunkt mit Authentifizierung und denselben Nutzerberechtigungen. Jede Netzwerkoperation hat einen Verbindungs-Timeout von 10 Sekunden sowie Inaktivitäts-Timeouts von 30 Sekunden für Lese- und Schreibvorgänge, keine Frist für die gesamte Übertragung. Automatische Retries sind deaktiviert: Geht eine Antwort verloren, kann ein Objekt zurückbleiben, obwohl der Befehl einen Fehler meldet.
Anwendungsbeispiele
Prüfen Sie die angegebenen Zugangsdaten und ermitteln Sie die UUID des Nutzers:
(cd supabase-storage-demo && bundle exec ruby storage.rb whoami)
Legen Sie eine Datei namens report.bin im Projekt ab. Ersetzen Sie unten
USER_UUID durch die ausgegebene UUID; sie muss für die obigen Richtlinien der erste
Ordner im Objektschlüssel sein.
(cd supabase-storage-demo &&
bundle exec ruby storage.rb upload ruby-exports 'USER_UUID/exports/report.bin' ./report.bin)
Ein erfolgreicher Befehl gibt die Byteanzahl und den SHA-256-Hash des heruntergeladenen Objekts aus und endet mit dem Exit-Code null. Das Objekt bleibt unter dem ausgewählten Schlüssel erhalten. So prüfen Sie es erneut, ohne es hochzuladen oder zu ersetzen:
(cd supabase-storage-demo &&
bundle exec ruby storage.rb verify ruby-exports 'USER_UUID/exports/report.bin' ./report.bin)
Lassen Sie die Quelldatei während des Vergleichs unverändert. Setzen Sie Pfadargumente mit
Leerzeichen in Anführungszeichen und verwenden Sie ./ für einen lokalen
Dateinamen, der mit einem Bindestrich beginnt. Dieses Beispiel listet keine Ordner auf und erstellt
keine öffentlichen Downloadlinks.
Fehlerbehandlung und Dateivalidierung
Jeder abgelehnte Upload, jedes fehlgeschlagene Zurücklesen und jede unbestätigte Löschung endet mit einem Exit-Code ungleich null. Bestehende Schlüssel werden bei Standard-Uploads abgelehnt; das Programm fordert kein Upsert an und ersetzt die bisherigen Bytes nicht. Führen Sie Befehle nacheinander aus.
Ein HTTP-Fehler allein lässt nicht auf die Ursache schließen. Prüfen Sie die Projekt-URL, die Nutzerzugangsdaten, die Bucket-Einschränkungen, das UUID-Präfix des Schlüssels und die geltenden Richtlinien. Ein fehlendes oder unzugängliches Objekt kann dieselbe Antwort auslösen. Das Programm lässt rohe Server-Antwortkörper, Zugangsdaten und Bibliotheksfehler in seiner Diagnose weg.
Die Obergrenze von 1 MiB schließt ihren exakten Grenzwert ein und erlaubt null Bytes. Sie gilt für dieses
Beispiel; Ihr Projekt und Bucket können zusätzliche Einschränkungen vorgeben. Der MIME-Header
deklariert generische binäre Inhalte und erkennt weder Dateiformate noch bestätigt er die Sicherheit
der Inhalte. Falls der Upload erfolgreich ist, die Verifizierung aber fehlschlägt, prüfen Sie diesen
Schlüssel vor einem erneuten Versuch mit dem Befehl verify: Der Upload wird
weder rückgängig gemacht noch stillschweigend gelöscht.
Bewährte Verfahren
Wählen Sie bei regelmäßigen Berichtsexporten für jeden Bericht einen neuen Schlüssel, beispielsweise
USER_UUID/exports/2026/10/report-12.bin. Ein Datumsordner hält Exporte übersichtlich; eine zusätzliche
Job-Kennung unterscheidet mehrere Berichte vom selben Tag. Bewahren Sie den Schlüssel zusammen mit
dem Job-Datensatz auf, damit eine spätere Verifizierung und Löschung dasselbe Objekt betreffen.
Große Dateien verarbeiten
Dieses Beispiel für kleine Dateien puffert sowohl die Eingabe als auch die zurückgelesenen Daten und kann unterbrochene Uploads nicht fortsetzen. Verwenden Sie für größere Übertragungen das Protokoll von Supabase für fortsetzbare Uploads mit einem kompatiblen tus-Client. Unabhängige Datenblöcke, die an den Standard-Objektendpunkt gesendet werden, implementieren dieses Protokoll nicht. Fortsetzbare Übertragungen liegen außerhalb des verifizierten Umfangs dieses Beispiels.
Einen Export explizit löschen
Wenn die exportierte Datei nicht mehr benötigt wird, löschen Sie nur ihren ausgewählten Schlüssel:
(cd supabase-storage-demo &&
bundle exec ruby storage.rb delete ruby-exports 'USER_UUID/exports/report.bin')
Ein Erfolg setzt voraus, dass die Löschantwort dieses Objekt identifiziert. Ein leeres Array kann bedeuten, dass es fehlt oder durch Richtlinien verborgen ist; in diesem Fall meldet der Befehl daher einen Fehler. Das Löschen eines Exports entfernt weder seinen Bucket noch andere Objekte, Nutzer oder Richtlinien.
