Aprende cómo descargar backups a través de nuestra API
https://backup-server.x10.mx/restore/download/file.backup.txt?key=%3CAPI_KEY%3E
curl -X GET "https://backup-server.x10.mx/restore/download/file.backup.txt" \
-H "Authorization: Bearer <API_KEY>" \
-o "file.backup.txt"
Descripción general
Este es el método más seguro y recomendado para descargar backups. Usa el estándar Authorization: Bearer, que mantiene la API key fuera de la URL y de los logs del servidor.
Parámetros explicados
-X GET → Fuerza método HTTP GET (por claridad)-H "Authorization: Bearer ..." → Autenticación segura (estándar OAuth 2.0)-o "archivo.sql" → Guarda el archivo con el nombre indicadocurl -X GET "https://backup-server.x10.mx/restore/download/file.backup.txt" \
-H "Authorization: Bearer <API_KEY>" \
-o "backup_manual_2025.sql"
Ideal cuando necesitas un nombre descriptivo fijo.
curl -X GET "https://backup-server.x10.mx/restore/download/file.backup.txt" \
-H "Authorization: Bearer <API_KEY>" \
-o "backup_$(date +%Y%m%d_%H%M%S).sql"
Genera nombres únicos: backup_20251125_143022.sql
mkdir -p ~/backups/$(date +%Y-%m-%d)
curl -X GET "https://backup-server.x10.mx/restore/download/file.backup.txt" \
-H "Authorization: Bearer <API_KEY>" \
-o "~/backups/$(date +%Y-%m-%d)/file.backup.txt"
Crea automáticamente carpetas como ~/backups/2025-11-25/
curl -X GET "https://backup-server.x10.mx/restore/download/file.backup.txt" \
-H "Authorization: Bearer <API_KEY>" \
-o "file.backup.txt" \
--progress-bar
Tip profesional: Combina varias opciones. Ejemplo: carpeta + timestamp + progreso.
curl -X GET "https://backup-server.x10.mx/restore/download/file.backup.txt" \
-H "Authorization: Bearer <API_KEY>" \
-o "file.backup.txt" \
-w "\nHTTP: %{http_code} | Tiempo: %{time_total}s | Tamaño: %{size_download} bytes\n"
#!/bin/bash
# Descarga segura + verificación de integridad (Linux/macOS)
URL="https://backup-server.x10.mx/restore/download/file.backup.txt"
TOKEN="<API_KEY>"
OUTPUT="file.backup.txt"
TEMP="$OUTPUT.part"
echo "Iniciando descarga segura del backup..."
curl -f -L \
-H "Authorization: Bearer $TOKEN" \
-o "$TEMP" \
--progress-bar \
"$URL" || { echo "ERROR: Falló la descarga"; rm -f "$TEMP"; exit 1; }
# Verificación SHA256 (compatible Linux y macOS)
if command -v sha256sum >/dev/null 2>&1; then
CHECKSUM=$(sha256sum "$TEMP" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
CHECKSUM=$(shasum -a 256 "$TEMP" | awk '{print $1}')
else
echo "Advertencia: No se puede verificar integridad"
mv "$TEMP" "$OUTPUT" 2>/dev/null
echo "Descarga completada (sin verificación)"
exit 0
fi
mv "$TEMP" "$OUTPUT"
echo "Descarga 100% verificada"
echo "SHA256: $CHECKSUM"
echo "Archivo guardado: $OUTPUT"
¿Qué hace este script?
.part) para evitar corrupciónimport requests
# Configuración
file_to_download="file.backup.txt";
url = "https://backup-server.x10.mx/restore/download/file.backup.txt" # URL limpia, sin token
output_file = "file.backup.txt"
token = "<API_KEY>" # Reemplaza con tu token real o usa variables de entorno
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/octet-stream"
}
# Descarga simple
response = requests.get(url, headers=headers)
if response.status_code == 200:
with open(output_file, "wb") as f:
f.write(response.content)
print(f"✓ Descarga completada: {output_file}")
print(f" Tamaño: {len(response.content):,} bytes")
else:
print(f"✗ Error {response.status_code}: {response.reason}")
print(response.text)
Descripción: Descarga básica usando Authorization: Bearer <token> en los headers. Ideal para archivos pequeños o medianos.
os.getenv("BACKUP_TOKEN") o un archivo .env.
import requests
from tqdm import tqdm
# Configuración
file_to_download="file.backup.txt";
url = "https://backup-server.x10.mx/restore/download/file.backup.txt" # URL limpia, sin token
output_file = "file.backup.txt"
token = "<API_KEY>" # Reemplaza con tu token real o usa variables de entorno
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/octet-stream"
}
response = requests.get(url, headers=headers, stream=True)
response.raise_for_status()
total_size = int(response.headers.get("content-length", 0))
with open(output_file, "wb") as f:
with tqdm(
total=total_size,
unit="B",
unit_scale=True,
unit_divisor=1024,
desc=output_file,
ncols=90
) as pbar:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
pbar.update(len(chunk))
print(f"\n✓ Descarga completada: {output_file}")
Descripción: Descarga en streaming con barra de progreso visual (porcentaje, velocidad, ETA) usando token Bearer.
Instalación:
pip install requests tqdm
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import os
import sys
# Configuración
file_to_download="file.backup.txt";
url = "https://backup-server.x10.mx/restore/download/file.backup.txt" # URL limpia, sin token
output_file = "file.backup.txt"
token = "<API_KEY>" # Reemplaza con tu token real o usa variables de entorno
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/octet-stream"
}
# Sesión con reintentos automáticos
session = requests.Session()
retry_strategy = Retry(
total=5,
backoff_factor=2,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
try:
print(f"→ Iniciando descarga de {output_file}...")
response = session.get(url, headers=headers, stream=True, timeout=600)
response.raise_for_status()
total_size = int(response.headers.get("content-length", 0))
downloaded = 0
with open(output_file, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
downloaded += len(chunk)
if total_size > 0:
percentage = downloaded / total_size * 100
print(f"\r {downloaded:,} / {total_size:,} bytes ({percentage:.1f}%)", end="")
# Verificación final
final_size = os.path.getsize(output_file)
print(f"\n\n✓ Descarga completada correctamente")
print(f" Archivo: {output_file}")
print(f" Tamaño : {final_size:,} bytes")
if total_size and final_size != total_size:
print(f" ⚠ Advertencia: tamaño esperado {total_size:,}, recibido {final_size:,}")
sys.exit(1)
except requests.exceptions.RequestException as e:
print(f"\n✗ Error de red: {e}")
if os.path.exists(output_file):
os.remove(output_file)
print(" Archivo parcial eliminado")
sys.exit(1)
except KeyboardInterrupt:
print("\n\nDescarga cancelada por el usuario")
if os.path.exists(output_file):
os.remove(output_file)
print(" Archivo parcial eliminado")
sys.exit(1)
Descripción: Versión de producción: reintentos automáticos, timeout alto, verificación de integridad, limpieza de archivos parciales y uso seguro del token vía Authorization: Bearer.
// Pega esto directamente en la consola (F12)
(async function() {
const baseUrl = "https://backup-server.x10.mx";
const backupFile = "file.backup.txt"; // Cambia solo si el nombre real es distinto
const filename = "file.backup.txt";
let token = localStorage.getItem('backup_token') || prompt('Token Bearer requerido:');
if (!token) return console.error('Token cancelado');
const url = `${baseUrl}/restore/download/${backupFile}`;
try {
const r = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }
});
if (!r.ok) {
const msg = r.status === 401 ? 'Token inválido o expirado' :
r.status === 403 ? 'Sin permisos' :
r.status === 404 ? 'Backup no encontrado' : `Error ${r.status}`;
throw new Error(msg);
}
const total = Number(r.headers.get('content-length')) || 0;
const reader = r.body.getReader();
const chunks = [];
let loaded = 0;
while (true) {
const {done, value} = await reader.read();
if (done) break;
chunks.push(value);
loaded += value.length;
if (total) console.log(`Progreso: ${Math.round(loaded/total*100)}%`);
}
const blob = new Blob(chunks);
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
console.log('%cDescarga completada: ' + filename, 'color: #10b981; font-weight: bold');
} catch (e) {
console.error('%cError:', 'color: #ef4444', e.message);
}
})();
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Descargar Backup</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script>document.addEventListener("DOMContentLoaded", () => lucide.createIcons());</script>
</head>
<body class="min-h-screen text-slate-300 flex items-center justify-center p-6">
<div class="max-w-2xl w-full bg-slate-900 rounded-2xl border border-slate-700 p-10 shadow-2xl">
<h1 class="text-3xl font-bold text-center text-white mb-8">Descarga de Backup</h1>
<div class="space-y-8">
<!-- Token (se guarda automáticamente) -->
<div class="bg-slate-800/50 rounded-lg p-5">
<label class="block text-sm font-medium mb-2">Token Bearer</label>
<input id="tokenInput" type="password" placeholder="eyJhbGciOi..." class="w-full px-4 py-3 border border-slate-700 rounded-lg focus:border-emerald-500 outline-none">
<button onclick="localStorage.setItem('backup_token', document.getElementById('tokenInput').value.trim()); alert('Token guardado')" class="mt-3 text-xs bg-emerald-600 hover:bg-emerald-700 px-4 py-2 rounded">Guardar token</button>
</div>
<!-- Botón de descarga -->
<button id="downloadBtn" class="w-full py-6 bg-gradient-to-r from-emerald-600 to-green-600 hover:from-emerald-700 hover:to-green-700 text-white font-bold text-xl rounded-xl shadow-lg flex items-center justify-center gap-4 disabled:opacity-70 transition">
<span id="btnText">Descargar Backup</span>
<span id="progressText" class="hidden">0%</span>
<i data-lucide="loader-2" id="spinner" class="w-7 h-7 hidden animate-spin"></i>
</button>
<div id="status" class="text-center text-sm min-h-6"></div>
</div>
</div>
<script>
document.getElementById('downloadBtn').addEventListener('click', async () => {
const btn = document.getElementById('downloadBtn');
const text = document.getElementById('btnText');
const prog = document.getElementById('progressText');
const spin = document.getElementById('spinner');
const status = document.getElementById('status');
// ====== CONFIGURACIÓN (cambia solo si es necesario) ======
const baseUrl = "https://backup-server.x10.mx";
const backupFile = "file.backup.txt"; // ← Cambia solo si el nombre interno es distinto
const filename = "file.backup.txt";
// =========================================================
let token = localStorage.getItem('backup_token');
if (!token) {
token = prompt('Token Bearer requerido:');
if (token) localStorage.setItem('backup_token', token);
}
if (!token) return;
const url = `${baseUrl}/restore/download/${backupFile}`;
btn.disabled = true;
text.classList.add('hidden');
prog.classList.remove('hidden');
spin.classList.remove('hidden');
prog.textContent = '0%';
status.textContent = 'Conectando...';
try {
const r = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
if (!r.ok) {
const msg = r.status === 401 ? 'Token inválido o expirado'
: r.status === 403 ? 'Sin permisos'
: r.status === 404 ? 'Backup no encontrado'
: `Error ${r.status}`;
throw new Error(msg);
}
const total = Number(r.headers.get('content-length')) || 0;
const reader = r.body.getReader();
const chunks = [];
let loaded = 0;
while (true) {
const {done, value} = await reader.read();
if (done) break;
chunks.push(value);
loaded += value.length;
prog.textContent = total
? Math.round((loaded / total) * 100) + '%'
: (loaded / 1048576).toFixed(2) + ' MB';
}
const blob = new Blob(chunks);
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
URL.revokeObjectURL(a.href);
text.textContent = '¡Completado!';
text.classList.remove('hidden');
prog.classList.add('hidden');
spin.classList.add('hidden');
status.innerHTML = '<span class="text-emerald-400">Descarga finalizada correctamente</span>';
setTimeout(() => btn.disabled = false, 3000);
} catch (e) {
alert('Error: ' + e.message);
text.textContent = 'Reintentar';
text.classList.remove('hidden');
prog.classList.add('hidden');
spin.classList.add('hidden');
status.innerHTML = '<span class="text-red-400">' + e.message + '</span>';
btn.disabled = false;
}
});
</script>
</body>
</html>
descargar-backup.html → ábrelo y funciona (incluso con doble clic si tu servidor tiene CORS).
// backup-download.js → npm i axios cli-progress
const axios = require('axios');
const fs = require('fs');
const { SingleBar, Presets } = require('cli-progress');
(async () => {
const BASE_URL = "https://backup-server.x10.mx";
const BACKUP_FILE = "file.backup.txt"; // Cambia si es distinto
const FILENAME = "file.backup.txt";
const TOKEN = process.env.BACKUP_TOKEN;
if (!TOKEN) {
console.error("Error: Define BACKUP_TOKEN como variable de entorno");
process.exit(1);
}
const url = `${BASE_URL}/restore/download/${BACKUP_FILE}`;
const bar = new SingleBar({}, Presets.shades_classic);
let loaded = 0;
try {
const r = await axios({
method: 'GET',
url,
responseType: 'stream',
headers: { Authorization: `Bearer ${TOKEN}` }
});
const total = parseInt(r.headers['content-length'], 10) || 0;
if (total) bar.start(total, 0);
const writer = fs.createWriteStream(FILENAME);
r.data.on('data', chunk => {
loaded += chunk.length;
if (total) bar.update(loaded);
else process.stdout.write(`\rDescargados: ${(loaded/1048576).toFixed(2)} MB`);
});
r.data.pipe(writer);
await new Promise((resolve, reject) => {
writer.on('finish', () => {
if (total) bar.stop();
else console.log();
console.log(`\nDescarga completada: ${FILENAME}`);
resolve();
});
writer.on('error', reject);
});
} catch (err) {
if (total) bar.stop();
const s = err.response?.status;
console.error(
s === 401 ? "Token inválido" :
s === 403 ? "Sin permisos" :
"Error:", err.message
);
process.exit(1);
}
})();
BACKUP_TOKEN=eyJhbGciOi... node backup-download.js
-L permite seguir redirecciones automáticas.
Accede a tus backups o explora la documentación completa