Exporter des fichiers vers Supabase avec cURL
Pour exporter un fichier local vers un espace privé de Supabase Storage, envoyez ses octets avec une requête POST authentifiée, puis téléchargez l’objet et comparez-le à l’original. Cette procédure utilise deux utilisateurs Auth ordinaires : l’un téléverse et lit le fichier, tandis que l’autre vérifie que la politique d’accès refuse le même téléchargement.
Configurer un bucket privé et ses politiques
Vous avez besoin d’un projet Supabase que vous administrez et de deux utilisateurs Auth distincts, disposant d’une adresse e-mail et d’un mot de passe, qui peuvent déjà se connecter. Confirmez les adresses e-mail si votre projet l’exige ; consultez la documentation sur Auth avec mot de passe. La création d’un compte Supabase ne crée pas à elle seule ces utilisateurs de l’application.
Créez un bucket Storage privé nommé curl-exports, en suivant le
guide de démarrage des buckets. Autorisez
application/octet-stream si vous limitez les types MIME, et choisissez un fichier qui respecte
la limite de taille du bucket. Utilisez un nouveau bucket pour cet exemple. Examinez aussi les
politiques storage.objects existantes : une règle permissive qui s’applique à ce
bucket peut également accorder l’accès. La vérification effectuée ci-dessous avec le second
utilisateur teste les autorisations de lecture effectives.
Exécutez ce code SQL une seule fois en tant qu’administrateur du projet. Il n’autorise le téléversement et la lecture qu’à l’intérieur d’un dossier nommé d’après l’UUID de l’utilisateur connecté :
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
);
La documentation sur le contrôle d’accès de Storage
explique les politiques storage.objects. Le programme de téléversement ci-dessous
utilise le jeton d’accès d’un utilisateur ; une clé secrète ou une clé de rôle de service
contournerait RLS et invaliderait la vérification de confidentialité.
Vérifier les outils du shell et les informations d’authentification
Utilisez Bash sous Linux avec cURL, jq
et les utilitaires habituels cmp, mktemp
et rm déjà installés. Les versions testées sont Bash 5.3.15,
cURL 8.22.0 et jq 1.8.2. Il s’agit des versions testées, et non de versions exactes imposées ;
le script utilise l’option --fail-with-body de cURL et vérifie chaque outil requis
avant de créer des fichiers temporaires. Ses requêtes ignorent les réglages de
.curlrc. Exécutez les commandes de configuration dans Bash.
curl --disable --version && jq --version && bash --version
Cette procédure utilise une clé d’API anon existante de l’ancienne
génération, correspondant à la pile locale testée. Supabase prévoit de déprécier les clés de
l’ancienne génération d’ici la fin de 2026 ; le
guide des clés d’API explique comment les récupérer et les migrer.
Définissez l’URL du projet, la clé et le bucket de cette pile dans le shell depuis lequel vous
exécuterez le script :
export SUPABASE_URL='https://YOUR_PROJECT_ID.supabase.co' \
SUPABASE_KEY='YOUR_ANON_KEY' \
SUPABASE_BUCKET='curl-exports'
Demandez à votre gestionnaire d’informations d’authentification ou à votre exécuteur de tâches de
fournir SUPABASE_EMAIL, SUPABASE_PASSWORD,
SUPABASE_OTHER_EMAIL et SUPABASE_OTHER_PASSWORD sous forme de variables
d’environnement. La première paire correspond à l’utilisateur qui téléverse le fichier ;
la seconde correspond à celui dont l’accès doit être refusé. Évitez de placer les mots de passe
dans l’historique du shell. La clé du projet ne remplace pas le JWT de l’utilisateur dans
l’en-tête Authorization.
Cet exemple a été testé avec Supabase Auth v2.196.0 et Storage v1.74.0 en local, avec PostgreSQL 17.6. La création d’un projet hébergé, TLS, la configuration du tableau de bord et les quotas du forfait sont des prérequis distincts. Utilisez HTTPS pour un projet hébergé ; HTTP sur l’interface de bouclage convient à une pile de développement locale.
Téléverser, télécharger et vérifier un fichier
Enregistrez le contenu suivant dans un nouveau fichier nommé export-to-supabase.sh.
Exécutez-le avec Bash plutôt que de le charger directement dans votre shell actuel. Son premier
argument est le fichier local ; son second est le chemin de l’objet sous le dossier portant
l’UUID de l’utilisateur qui téléverse le fichier. Les segments du chemin de l’objet doivent
commencer par une lettre ou un chiffre ASCII et ne contenir que des lettres et des chiffres ASCII,
des espaces, des points, des traits de soulignement et des traits d’union. Les noms de fichiers
locaux ne sont pas tenus de respecter cette règle plus restrictive de nommage des objets.
#!/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'
Remplacez ./report.bin par le fichier choisi. Ne modifiez pas ce fichier pendant
l’exécution du script. Le nom du fichier distant peut différer de son nom local :
bash export-to-supabase.sh './report.bin' 'exports/report.bin'
Une exécution réussie affiche :
Verified upload and private access.
L’objet reste à l’emplacement curl-exports/<uploader-uuid>/exports/report.bin. La copie téléchargée et les réponses
d’Auth sont temporaires et supprimées à la fin du script. Une différence entre les fichiers ou un
téléchargement réussi de manière inattendue par l’autre utilisateur provoque une sortie avec un
code non nul, sans afficher ce message de réussite. Le
point de terminaison de téléchargement privé
utilise le JWT de l’utilisateur et la politique de lecture ; la clé d’API seule ne donne pas cet accès.
Gérer les objets existants sans perdre les exports précédents
La requête POST crée un nouvel objet. Réexécuter la même commande cible le même UUID et le même chemin d’objet ; la requête est donc rejetée si l’objet existe déjà. L’objet précédent reste stocké. Utilisez un autre chemin d’objet pour un nouvel export.
Le guide de Supabase sur le téléversement standard
décrit le remplacement volontaire avec x-upsert: true, qui nécessite aussi des
politiques SELECT et UPDATE.
Cette procédure n’accorde aucune autorisation de mise à jour et n’écrase aucun objet. Si une
connexion échoue après que le serveur a stocké le fichier, l’issue du téléversement est incertaine.
Inspectez et téléchargez cet objet précis avant de décider de le conserver ou d’utiliser un
nouveau chemin ; une réponse signalant un doublon lors d’une nouvelle tentative peut signifier
que le premier téléversement a réussi.
Comprendre les limites de taille des fichiers
Choisissez un petit fichier respectant à la fois la limite globale du projet et celle du bucket.
Les plafonds des forfaits hébergés et les restrictions du bucket sont décrits dans la
documentation de Supabase sur les limites de taille des fichiers.
Dans cURL, --data-binary @file
met les données d’entrée en mémoire tampon,
et cet exemple ne reprend pas un transfert interrompu. Pour les fichiers plus volumineux,
suivez les instructions du
guide distinct sur le téléversement avec reprise.
Automatiser l’export de fichiers dans un flux de travail quotidien
Le script enregistré se connecte à chaque invocation ; l’automatisation ne suppose donc pas qu’un JWT collé reste valide. Faites fournir les variables d’environnement par votre planificateur, utilisez des chemins absolus pour le script et le fichier d’entrée, et choisissez un nouveau chemin d’objet pour chaque export. Conservez le second compte pour vérifier les autorisations et considérez toute sortie avec un code non nul comme un échec de la vérification, même si le fichier téléversé existe déjà à distance.
Résoudre les problèmes courants
Erreurs d’authentification (401 non autorisé)
Vérifiez d’abord l’URL du projet et la clé d’API. En cas d’échec de connexion par mot de passe, vérifiez l’adresse e-mail, le mot de passe et l’état de confirmation de l’utilisateur. Le script s’arrête si la connexion échoue ; il n’utilise pas une clé d’administrateur à la place. Si le jeton expire pendant un transfert, relancez le script avec un nouveau chemin d’objet après avoir vérifié si le téléversement précédent a été enregistré.
Erreurs de fichiers en double
Un chemin en double provoque une erreur, généralement HTTP 400 dans Storage. Modifiez le second argument du script pour un nouvel export. N’ajoutez pas de nouvelles tentatives automatiques ni d’opération upsert dans le seul but de masquer la collision.
Limites de taille des fichiers (413 charge utile trop volumineuse)
Un téléversement trop volumineux peut produire une erreur HTTP 413. La version de Storage testée
a renvoyé HTTP 400 avec le statut 413 dans le corps de la réponse pour un refus lié à la limite
de taille du bucket. Vérifiez les deux limites de taille, ainsi que la liste des types MIME
autorisés du bucket en cas de refus lié au type. Ce script envoie application/octet-stream ;
il ne détecte pas le type de média du fichier. Consultez les
codes d’erreur de Storage pour connaître les diagnostics du service.
Accès refusé
Vérifiez que le bucket est privé et que les politiques s’appliquent à son nom exact, au dossier
portant l’UUID de l’utilisateur qui téléverse le fichier et à owner_id.
Un objet privé inaccessible peut sembler absent : la version de Storage testée a renvoyé HTTP 400
avec le statut 404 dans le corps de la réponse pour le second utilisateur. Le script commence par
lire et comparer ce même objet en tant que propriétaire, de sorte qu’un objet absent ne peut pas
satisfaire cette vérification. Si l’autre utilisateur reçoit HTTP 200, examinez les politiques
existantes et la visibilité du bucket avant d’exporter des données sensibles.
