Téléversements CLI efficaces avec des outils open source
Une commande de téléversement qui renvoie une URL ne vous indique pas si le fichier téléchargé
correspond à votre fichier d’entrée. Ce guide utilise curl et un serveur
local transfer.sh jetable pour téléverser un fichier non vide, le télécharger et
comparer les octets. Vous obtenez une copie vérifiée sur disque et une somme de contrôle SHA-256,
sans compte cloud.
Comparer les outils CLI de téléversement courants
Choisissez l’outil en fonction du système de destination. curl fournit le
client HTTP ; transfer.sh fournit le serveur dans l’exemple ci-dessous.
| Outil | Utilisation | À vérifier avant de choisir |
|---|---|---|
s3cmd | Téléversements et synchronisation vers S3 ou un stockage d’objets compatible | Vous avez besoin d’un compartiment et d’informations d’accès autorisant les opérations prévues. |
rclone | Copie et synchronisation entre systèmes de stockage cloud | Les sommes de contrôle et les autres fonctionnalités varient selon le système de stockage. |
curl | Téléversements HTTP vers un point de terminaison existant | Le point de terminaison détermine la méthode, l’authentification et le format de réponse. |
rsync | Synchronisation de fichiers avec une machine que vous contrôlez | Un transfert via un shell distant nécessite rsync sur les deux machines. |
lftp | Transferts FTP ou SFTP et mise en miroir | Choisissez un protocole et un compte pris en charge par la destination. |
Pour un flux de travail reposant sur un stockage, consultez les exportations par lots vers S3 avec des URL PUT signées (English) ou rclone avec DigitalOcean Spaces (English). Ces tâches nécessitent une configuration chez le fournisseur. Ici, le serveur de destination s’exécute sur votre propre machine et est supprimé après l’aller-retour.
Utiliser transfer.sh pour partager rapidement des fichiers
Il s’agit d’un test HTTP anonyme sur l’interface de bouclage, avec un démon Docker local sous Linux. L’URL est accessible depuis cette machine tant que le conteneur s’exécute ; ce n’est pas un lien de partage public. Utilisez un fichier de test non sensible qui ne change pas. Cet exemple ne comporte ni stockage persistant côté serveur, ni authentification, ni reprise des transferts, ni test comparatif de vitesse.
La version figée rejette les téléversements de fichiers de zéro octet. Le script ci-dessous les refuse avant de créer un répertoire de résultats ou un conteneur. Si votre tâche de transfert comprend des fichiers vides, choisissez une destination qui les prend en charge.
Les commandes ont été testées sous Linux x86-64 avec Bash 5.3.15, curl 8.22.0, Docker Engine 29.7.2,
GNU coreutils 9.11 et GNU diffutils 3.12. Vous devez disposer de docker,
bash, curl, cmp et
sha256sum dans votre chemin de recherche des exécutables, ainsi que d’un accès au
démon Docker local. Un contexte Docker distant placerait le serveur sur une autre machine.
La documentation Docker précise que les versions antérieures à 28.0.0 peuvent exposer les ports
publiés sur localhost aux hôtes du même segment réseau.
Téléchargez l’image exacte utilisée ci-dessous :
docker pull dutchcoders/transfer.sh@sha256:9383e66489ab3a7a56bec1b67d2e27d41c072102d515cdc5ab35f913b72e8a09
Cette empreinte fixe la version utilisée à transfer.sh v1.6.1,
publiée le 4 décembre 2023. Considérez cela comme un exercice local reproductible, et non comme une
recommandation de déployer cette image publiquement. Les
instructions Docker du projet expliquent pourquoi une image versionnée
est préférable à l’étiquette évolutive latest.
Exécuter votre propre instance de transfer.sh
Enregistrez ce script sous le nom upload-check.sh dans un répertoire de travail
temporaire. Exécutez-le avec Bash, plutôt que de le charger dans votre shell avec la commande source.
Il crée un nouveau répertoire de résultats, attribue automatiquement un port sur l’hôte, attend que
son propre conteneur soit prêt et supprime ce conteneur avant de signaler la réussite.
Le stockage local et les fichiers de téléversement temporaires partagent un point de montage en
mémoire de 64 MiB. Laissez de la place pour les métadonnées et les fichiers temporaires ; il s’agit
d’un exercice pour de petits fichiers, pas d’un service pour de gros fichiers.
Le serveur reçoit un nom de fichier fixe, payload.bin, afin que les espaces,
les traits d’union initiaux et les signes de ponctuation d’URL présents dans votre nom de fichier
local ne deviennent pas des éléments de syntaxe d’URL.
#!/usr/bin/env bash
set -euo pipefail
umask 077
fail() { printf '%s\n' "$*" >&2; exit 1; }
[[ $# -eq 2 ]] || fail 'Usage: bash upload-check.sh INPUT NEW_RESULT_DIRECTORY'
input=$1
result_dir=$2
# Prefix relative paths so a literal "-" is a file, not curl's stdin selector.
[[ $input == /* || $input == ./* || $input == ../* ]] || input=./$input
[[ $result_dir == /* || $result_dir == ./* || $result_dir == ../* ]] || result_dir=./$result_dir
[[ -f $input && -r $input ]] || fail "Input is not a readable regular file: $input"
[[ -s $input ]] || fail "transfer.sh v1.6.1 rejects empty uploads: $input"
port=${UPLOAD_PORT:-0}
[[ $port =~ ^[0-9]{1,5}$ ]] || fail 'UPLOAD_PORT must be 0 or a port from 1 to 65535.'
(( 10#$port <= 65535 )) || fail 'UPLOAD_PORT exceeds 65535.'
port=$((10#$port))
name=${UPLOAD_NAME:-cli-upload-${RANDOM}-${RANDOM}-$$}
image=dutchcoders/transfer.sh@sha256:9383e66489ab3a7a56bec1b67d2e27d41c072102d515cdc5ab35f913b72e8a09
# An existing result directory is never reused or removed.
mkdir -- "$result_dir" || fail "Choose a new result directory: $result_dir"
work=$result_dir/.work
mkdir -- "$work"
verified=0
checksum=''
cleanup() {
status=$?
trap - EXIT
# A CID file identifies only the container this invocation created.
if [[ -s $work/container.id ]]; then
container_id=$(< "$work/container.id")
if ! docker rm --force "$container_id" >/dev/null; then
printf 'Cleanup failed; remove container %s when Docker is available.\n' "$container_id" >&2
status=1
fi
fi
if (( status == 0 && verified == 1 )); then
mv -- "$work/download.part" "$result_dir/download.bin" || status=1
fi
rm -rf -- "$work" || status=1
if (( status == 0 && verified == 1 )); then
printf 'Verified: %s\nSHA-256: %s\n' "$result_dir/download.bin" "$checksum"
fi
exit "$status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP
docker create --cidfile "$work/container.id" --name "$name" --pull=never \
--publish "127.0.0.1:$port:8080" \
--read-only --user 5000:5000 --cap-drop ALL --security-opt no-new-privileges \
--tmpfs /tmp:rw,size=64m,mode=1777 --memory 128m --cpus 1 --pids-limit 64 \
"$image" --provider local --basedir /tmp/data --temp-path /tmp \
> "$work/create.log"
container_id=$(< "$work/container.id")
docker start "$container_id" >/dev/null
binding=$(docker port "$container_id" 8080/tcp)
[[ $binding == 127.0.0.1:* ]] || fail 'Expected a loopback port binding.'
origin=http://$binding
curl_local() {
curl --disable --noproxy '*' --globoff --fail --silent --show-error \
--connect-timeout 2 --max-time 30 "$@"
}
ready=0
for ((attempt=0; attempt<40; attempt++)); do
if [[ $(docker inspect --format '{{.State.Running}}' "$container_id") != true ]]; then
docker logs "$container_id" >&2
fail 'Server exited before readiness.'
fi
if [[ $(curl_local --max-time 1 --output /dev/null --write-out '%{http_code}' \
"$origin/health.html" 2>/dev/null) == 200 ]]; then
ready=1
break
fi
sleep 0.25
done
if (( ready == 0 )); then
docker logs "$container_id" >&2
fail 'Server did not become ready.'
fi
if ! upload_status=$(curl_local --upload-file "$input" --output "$work/url.txt" \
--write-out '%{http_code}' "$origin/payload.bin"); then
docker logs "$container_id" >&2
fail "Upload failed: $input"
fi
[[ $upload_status == 200 ]] || fail "Unexpected upload HTTP status: $upload_status"
url=$(< "$work/url.txt")
path=${url#"$origin/"}
[[ $url == "$origin/"* && $path =~ ^[A-Za-z0-9]+/payload\.bin$ ]] \
|| fail 'Server returned an unexpected download URL.'
if ! download_status=$(curl_local --output "$work/download.part" \
--write-out '%{http_code}' "$url"); then
fail 'Download failed.'
fi
[[ $download_status == 200 ]] || fail "Unexpected download HTTP status: $download_status"
cmp -- "$input" "$work/download.part" || fail "Downloaded bytes differ: $input"
checksum=$(sha256sum < "$work/download.part")
checksum=${checksum%% *}
verified=1
--upload-file demande à curl d’envoyer une requête HTTP PUT.
--fail fait échouer la commande en cas d’erreur
HTTP, et le script exige également un statut HTTP 200 à chaque étape.
--disable est la première option de curl, afin
qu’un fichier de configuration personnel ne puisse pas ajouter de redirections ni modifier la
requête. --globoff empêche curl d’interpréter les crochets et les accolades dans
les noms de fichiers locaux. L’URL renvoyée par le serveur doit pointer vers l’origine exacte
attribuée par Docker.
Enfin, cmp vérifie chaque octet téléchargé avant que le script ne conserve
download.bin.
Le script effectue le nettoyage lors d’une sortie normale, d’un Ctrl+C ou de signaux TERM ou HUP.
Un signal envoyé uniquement à Bash peut n’être traité qu’une fois la commande active terminée ;
chaque transfert curl est limité à 30 secondes. Le script supprime les conteneurs à l’aide de leur
identifiant attribué à la création : une collision de noms ne peut donc pas entraîner la suppression
du conteneur d’un autre utilisateur. SIGKILL, un plantage de la machine ou la perte du démon Docker
peuvent empêcher le nettoyage. UPLOAD_NAME vous permet de choisir un nom
reconnaissable pour une exécution ; UPLOAD_PORT vous permet de demander un port
particulier sur l’hôte plutôt que de conserver l’attribution automatique par défaut. Aucune de ces
options ne rend réutilisable un conteneur existant ou un port occupé.
Partager un fichier via votre instance
Créez un petit fichier binaire de test et exécutez le script enregistré. Les parenthèses limitent la
portée des options du shell ; noclobber refuse d’écraser un fichier
sample.bin existant. Utilisez un répertoire où ni ce nom de fichier ni
upload-result ne sont déjà utilisés.
(
set -euo pipefail
set -o noclobber
printf '\000\377\001\200\012\015\052\000\101\102' > ./sample.bin
bash ./upload-check.sh ./sample.bin ./upload-result
)
Voici la sortie en cas de réussite pour ces dix octets :
Verified: ./upload-result/download.bin
SHA-256: d77823e7a78045d088fa69d1572861efee50fa35240f7555fa860d02d22633d5
Ouvrez upload-result/download.bin ou comparez-le à sample.bin ; ce fichier reste
présent après la suppression du serveur. Pour téléverser votre propre fichier, remplacez
./sample.bin dans l’appel à Bash et choisissez un nouveau répertoire de résultats.
Gardez le fichier d’entrée inchangé jusqu’à la fin de la commande.
En cas d’échec, le script renvoie un code de sortie non nul, supprime les téléchargements temporaires
et laisse le répertoire de résultats nouvellement créé sans copie vérifiée. Une nouvelle exécution
avec ce même répertoire de résultats est refusée, même s’il est vide.
Sécuriser vos téléversements en CLI
L’interface de bouclage limite cet exemple à l’hôte local ; les autres processus et utilisateurs de cet hôte peuvent toujours accéder à son point de terminaison anonyme. Les fichiers stockés dans le conteneur disparaissent lorsque celui-ci est supprimé, mais le fichier d’entrée d’origine et votre copie téléchargée vérifiée restent sur disque. L’utilisation de HTTP en clair est ici un choix pour un test local. Pour un service accessible depuis d’autres machines, choisissez un accès HTTPS authentifié et une politique de conservation avant d’envoyer des données réelles. Une URL impossible à deviner n’authentifie pas la personne qui l’utilise pour télécharger.
Résoudre les problèmes courants
| Symptôme | Points à vérifier |
|---|---|
| Docker ne peut pas créer ou démarrer le conteneur | Vérifiez que l’image figée a bien été téléchargée et que le démon local est accessible. Un UPLOAD_PORT occupé ou un UPLOAD_NAME existant fait échouer le démarrage ; choisissez une autre valeur. |
| Le serveur s’arrête ou n’est jamais prêt | Lisez le diagnostic du conteneur affiché avant le nettoyage. Le script ne téléverse rien tant que son propre serveur n’a pas passé le contrôle d’état. |
| Le téléversement ou le téléchargement échoue | Lisez l’erreur de curl et le message de l’étape. Le point de montage temporaire de 64 MiB du serveur peut se remplir ; essayez un fichier plus petit. Le délai maximal des transferts est de 30 secondes. |
| Les octets téléchargés diffèrent | Vérifiez si le fichier d’entrée a changé pendant le transfert. La réussite d’une requête HTTP ne suffit pas à elle seule ; le script supprime ce téléchargement. |
| Le répertoire de résultats existe déjà | Choisissez un nouveau répertoire. Le script préserve un résultat antérieur au lieu de le remplacer. |
| Vous avez besoin de limites de bande passante pour une tâche cloud | Utilisez --limit-rate avec s3cmd, ou --bwlimit avec rclone ; ces options appartiennent à des outils différents. |
Lorsque vous automatisez cette vérification, appuyez-vous sur son code de sortie et attribuez un nouveau répertoire de résultats à chaque exécution. La planification ne transforme pas le conteneur temporaire en sauvegarde persistante ni en service public de fichiers.
Poursuivre avec les téléversements depuis le navigateur
Pour une interface de téléversement dans le navigateur, Uppy fournit la sélection des fichiers et la progression du téléversement. Le protocole tus permet la reprise des téléversements HTTP lorsque le serveur le prend en charge. Il s’agit de choix distincts côté client et côté serveur ; cet exemple PUT avec transfer.sh n’implémente pas tus. Utilisez cet aller-retour local pour vérifier votre flux de travail en CLI, puis choisissez le service de destination en fonction des exigences de stockage et d’accès de votre application.
