Uploads eficientes via CLI com ferramentas de código aberto
Um comando de upload que retorna uma URL não informa se o arquivo baixado corresponde ao original.
Este tutorial usa curl e um servidor local descartável
transfer.sh para fazer upload de um arquivo não vazio, baixá-lo e comparar os bytes.
Ao final, você terá uma cópia verificada em disco e uma soma de verificação SHA-256,
sem precisar de uma conta na nuvem.
Compare ferramentas CLI populares para upload
Escolha a ferramenta de acordo com o sistema de destino. curl fornece o
cliente HTTP; transfer.sh fornece o servidor no exemplo abaixo.
| Ferramenta | Uso | O que verificar antes de escolher |
|---|---|---|
s3cmd | Uploads e sincronização com S3 ou armazenamento de objetos compatível | Você precisa de um bucket e de credenciais com permissão para as operações pretendidas. |
rclone | Cópia e sincronização entre backends de armazenamento em nuvem | Somas de verificação e outros recursos variam conforme o backend. |
curl | Uploads HTTP para um endpoint existente | O endpoint determina o método, a autenticação e o formato da resposta. |
rsync | Sincronização de arquivos com uma máquina sob seu controle | Uma transferência por shell remoto exige rsync nas duas máquinas. |
lftp | Transferências e espelhamento por FTP ou SFTP | Escolha um protocolo e uma conta compatíveis com o destino. |
Para um fluxo de trabalho com um serviço de armazenamento, veja exportações em lote para S3 com URLs PUT assinadas (English) ou rclone com DigitalOcean Spaces (English). Essas tarefas exigem configuração do provedor. Aqui, o servidor de destino roda na sua própria máquina e é removido após o ciclo de upload e download.
Use transfer.sh para compartilhar arquivos rapidamente
Este é um teste HTTP anônimo via loopback, usando um daemon Docker local no Linux. A URL fica acessível nessa máquina enquanto o contêiner está em execução; não é um link público de compartilhamento. Use um arquivo de teste sem dados sensíveis e que não seja alterado. Este exemplo não inclui armazenamento persistente no servidor, autenticação, retomada de transferências nem benchmark de velocidade.
A versão fixada rejeita uploads de zero bytes. O script abaixo os recusa antes de criar um diretório de resultados ou um contêiner. Se arquivos vazios fazem parte da sua tarefa de transferência, escolha um destino que ofereça suporte a eles.
Os comandos foram testados no Linux x86-64 com Bash 5.3.15, curl 8.22.0, Docker Engine 29.7.2,
GNU coreutils 9.11 e GNU diffutils 3.12. Você precisa de docker,
bash, curl, cmp e
sha256sum no caminho de busca de executáveis, além de acesso ao daemon Docker
local. Um contexto Docker remoto colocaria o servidor em outra máquina. A documentação do Docker
informa que versões anteriores à 28.0.0 podem expor portas publicadas em localhost a hosts no mesmo segmento de rede.
Baixe exatamente a imagem usada abaixo:
docker pull dutchcoders/transfer.sh@sha256:9383e66489ab3a7a56bec1b67d2e27d41c072102d515cdc5ab35f913b72e8a09
Este digest fixa transfer.sh v1.6.1,
lançado em 4 de dezembro de 2023. Considere isso um exercício local reproduzível, não uma
recomendação para disponibilizar essa imagem publicamente. As
instruções de Docker do projeto
explicam por que uma imagem versionada é preferível à tag mutável latest.
Execute sua própria instância de transfer.sh
Salve o código abaixo como upload-check.sh em um diretório de testes. Execute-o com
Bash, em vez de carregá-lo no shell com source. Ele cria um novo diretório de resultados, aloca
uma porta do host automaticamente, aguarda seu próprio contêiner ficar pronto e remove esse
contêiner antes de informar o sucesso.
O armazenamento local e os arquivos temporários de upload compartilham um ponto de montagem de
64 MiB em memória. Reserve espaço para metadados e arquivos temporários; este é um exercício com
arquivos pequenos, não um serviço para arquivos grandes.
O servidor recebe um nome de arquivo fixo, payload.bin, para que espaços,
hífens iniciais e pontuação de URL no nome do arquivo local não se tornem sintaxe de 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 faz o curl enviar um HTTP PUT.
--fail faz com que erros HTTP causem falha no
comando, e o script também exige HTTP 200 em cada etapa.
--disable é a primeira opção do curl, para que
um arquivo de configuração pessoal não possa adicionar redirecionamentos ou alterar a requisição.
--globoff impede o curl de expandir colchetes e chaves em nomes de arquivos
locais. A URL retornada pelo servidor deve apontar de volta exatamente para a origem alocada pelo
Docker. Por fim, cmp verifica cada byte baixado antes de o script manter
download.bin.
O script faz a limpeza ao terminar normalmente, ao receber Ctrl+C e com sinais TERM ou HUP.
Um sinal enviado apenas ao Bash pode ter de aguardar o retorno do comando ativo; cada transferência
com curl tem um limite de 30 segundos. O script remove contêineres pelo ID atribuído na criação,
para que uma colisão de nomes não possa remover o contêiner de outra pessoa. SIGKILL, uma pane na
máquina ou a perda do daemon Docker podem impedir a limpeza. UPLOAD_NAME permite
escolher um nome reconhecível para uma execução; UPLOAD_PORT permite solicitar
uma porta específica do host em vez da alocação automática padrão. Nenhuma dessas opções torna
reutilizável um contêiner existente ou uma porta ocupada.
Compartilhe um arquivo pela sua instância
Crie um pequeno arquivo binário de teste e execute o script salvo. Os parênteses limitam o escopo
das opções do shell; noclobber se recusa a sobrescrever um
sample.bin existente. Use um diretório em que tanto esse nome de arquivo quanto
upload-result estejam disponíveis.
(
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
)
A saída em caso de sucesso para esses dez bytes é:
Verified: ./upload-result/download.bin
SHA-256: d77823e7a78045d088fa69d1572861efee50fa35240f7555fa860d02d22633d5
Abra upload-result/download.bin ou compare-o com sample.bin; ele permanece
após a remoção do servidor. Para fazer upload do seu próprio arquivo, substitua
./sample.bin na chamada do Bash e escolha um novo diretório de resultados.
Mantenha o arquivo de entrada inalterado até o comando terminar.
Em caso de falha, o script retorna um código de saída diferente de zero, remove downloads
temporários e deixa o diretório de resultados recém-criado sem uma cópia verificada. Uma nova
execução com esse mesmo diretório de resultados é recusada, mesmo que ele esteja vazio.
Proteja seus uploads via CLI
O loopback limita este exemplo ao host local; outros processos e usuários desse host ainda podem acessar o endpoint anônimo. Os arquivos armazenados no contêiner desaparecem quando ele é removido, mas o arquivo de entrada original e o arquivo baixado e verificado permanecem em disco. O uso de HTTP sem criptografia aqui é uma escolha para testes locais. Para um serviço acessível por outras máquinas, escolha HTTPS com autenticação e uma política de retenção antes de enviar dados reais. Uma URL que não pode ser adivinhada não autentica a pessoa que faz o download.
Solucione problemas comuns
| Sintoma | O que verificar |
|---|---|
| O Docker não consegue criar ou iniciar o contêiner | Confirme se a imagem fixada foi baixada e se o daemon local está acessível. Um UPLOAD_PORT ocupado ou um UPLOAD_NAME existente impede a inicialização; escolha outro valor. |
| O servidor encerra a execução ou nunca fica pronto | Leia o diagnóstico do contêiner exibido antes da limpeza. O script não faz upload até que seu próprio servidor passe na verificação de integridade. |
| O upload ou download falha | Leia o erro do curl e a mensagem da etapa. O ponto de montagem temporário de 64 MiB do servidor pode ficar sem espaço; tente um arquivo menor. As transferências têm um tempo limite de 30 segundos. |
| Os bytes baixados são diferentes | Verifique se o arquivo de entrada mudou durante a transferência. Uma requisição HTTP bem-sucedida, por si só, não basta; o script descarta esse download. |
| O diretório de resultados já existe | Escolha um novo diretório. O script preserva o resultado anterior em vez de substituí-lo. |
| Você precisa de limites de largura de banda para uma tarefa na nuvem | Use --limit-rate com s3cmd, ou --bwlimit com rclone; essas opções pertencem a ferramentas diferentes. |
Ao usar essa verificação em uma automação, baseie-se no código de saída e use um novo diretório de resultados a cada execução. Agendar a execução não transforma o contêiner temporário em um backup persistente nem em um serviço público de arquivos.
Continue com uploads pelo navegador
Para uma interface de upload no navegador, o Uppy oferece seleção de arquivos e progresso do upload. O protocolo tus lida com uploads HTTP retomáveis quando o servidor oferece suporte a ele. São escolhas separadas de cliente/servidor; este exemplo de PUT com transfer.sh não implementa tus. Use o ciclo local de upload e download para verificar seu fluxo de trabalho via CLI; depois, escolha o serviço de destino conforme os requisitos de armazenamento e acesso da sua aplicação.
