# Rsync: el comando que uso todos los días

Si tuviera que quedarme con un solo comando de Linux, sería rsync. Lo uso para subir código a servidores, para hacer backups, para sincronizar directorios entre máquinas, para mover terabytes de datos sin perder nada. Es una de esas herramientas que llevan décadas existiendo, que no son llamativas, y que simplemente funcionan.

## Lo básico

![Rsync: el comando que uso todos los días](fig-01.webp)

Rsync sincroniza ficheros de un origen a un destino. La gracia es que solo transfiere lo que ha cambiado, no todo:

```bash
rsync -avz ./mi-proyecto/ servidor:/opt/app/
```

Los flags que uso siempre:
- `-a` (archive): preserva permisos, timestamps, links simbólicos, propietarios. Es el flag más importante.
- `-v` (verbose): muestra qué ficheros se transfieren.
- `-z` (compress): comprime los datos durante la transferencia. Útil con conexiones lentas.

Esa barra final en `./mi-proyecto/` es importante. Con la barra, rsync copia el contenido del directorio. Sin la barra, copia el directorio en sí. La diferencia entre que tus ficheros acaben en `/opt/app/` o en `/opt/app/mi-proyecto/`.

## Dry-run: mira antes de tocar

El flag más infravalorado de rsync:

```bash
rsync -avzn ./local/ servidor:/opt/app/
```

El `-n` (dry-run) muestra exactamente qué haría sin hacer nada. Es la diferencia entre un deploy tranquilo y un incidente a las tres de la mañana. Siempre hago un dry-run antes de un rsync real en producción. Siempre.

## Exclusiones

![Rsync: el comando que uso todos los días](fig-02.webp)

Casi nunca quieres sincronizar todo. El flag `--exclude` filtra lo que no debe ir:

```bash
rsync -avz \
  --exclude='.git' \
  --exclude='node_modules' \
  --exclude='*.log' \
  --exclude='.env' \
  ./proyecto/ servidor:/opt/app/
```

Para proyectos con muchas exclusiones, puedes usar un fichero:

```bash
# .rsync-exclude
.git
node_modules
tmp
*.log
.env
.env.*
__pycache__
```

```bash
rsync -avz --exclude-from='.rsync-exclude' ./proyecto/ servidor:/opt/app/
```

## --delete: el flag peligroso (y necesario)

Por defecto, rsync solo añade y actualiza. Si borras un fichero en local, sigue existiendo en el servidor. El flag `--delete` elimina del destino lo que ya no está en el origen:

```bash
rsync -avz --delete ./proyecto/ servidor:/opt/app/
```

Este flag es necesario para mantener el destino como un espejo exacto del origen, pero hay que usarlo con cuidado. Si te equivocas de directorio, puedes borrar cosas que no debías. Por eso el dry-run es sagrado:

```bash
rsync -avzn --delete ./proyecto/ servidor:/opt/app/
# Revisar la salida
# Si todo está bien:
rsync -avz --delete ./proyecto/ servidor:/opt/app/
```

## Backups incrementales

![Rsync: el comando que uso todos los días](fig-03.webp)

Rsync es perfecto para backups porque solo copia lo que ha cambiado. Con `--link-dest` puedes hacer backups incrementales que parecen completos pero ocupan una fracción del espacio:

```bash
#!/bin/bash
DATE=$(date +%Y-%m-%d)
LATEST=/backups/latest
DEST=/backups/$DATE

rsync -avz --delete --link-dest=$LATEST servidor:/opt/app/data/ $DEST/
ln -snf $DEST $LATEST
```

Cada backup es un directorio completo con todos los ficheros. Pero los ficheros que no han cambiado son hard links al backup anterior, así que no ocupan espacio extra. Puedes entrar en cualquier backup y ver el estado completo del sistema en ese momento, pero el almacenamiento total es solo la suma de los cambios.

## Limitar el ancho de banda

En producción, no quieres que un rsync de 50GB sature la conexión:

```bash
rsync -avz --bwlimit=10000 ./datos/ servidor:/opt/datos/
```

`--bwlimit=10000` limita a 10MB/s. Suficiente para que la transferencia avance sin afectar al tráfico real del servidor.

## Reanudar transferencias

Si una transferencia grande se interrumpe, rsync puede reanudarla:

```bash
rsync -avz --partial --progress ./imagen.iso servidor:/tmp/
```

`--partial` mantiene los ficheros parcialmente transferidos en vez de borrarlos. `--progress` muestra el progreso de cada fichero. La próxima vez que ejecutes el comando, rsync retoma donde lo dejó.

Para ficheros muy grandes, usa `--append-verify`:

```bash
rsync -avz --append-verify ./dump.sql.gz servidor:/backups/
```

Continúa la transferencia desde donde se quedó y verifica el checksum al final.

## SSH con puerto personalizado

Si tu servidor SSH usa un puerto diferente al 22:

```bash
rsync -avz -e 'ssh -p 2222' ./proyecto/ servidor:/opt/app/
```

También puedes especificar una clave SSH concreta:

```bash
rsync -avz -e 'ssh -i ~/.ssh/id_deploy' ./proyecto/ servidor:/opt/app/
```

## Mi script de deploy

Este es el script que uso para desplegar este blog:

```bash
#!/bin/bash
set -euo pipefail

SERVER="root@web.javiervalencia.net"
DEST="/opt/blog"

echo "==> Dry-run..."
rsync -avzn --delete \
  --exclude='.git' \
  --exclude='go.*' \
  --exclude='cmd' \
  --exclude='internal' \
  --exclude='*.go' \
  --exclude='deploy' \
  --exclude='.env' \
  --exclude='content/views.json' \
  ./ $SERVER:$DEST/

read -p "¿Continuar? (s/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Ss]$ ]]; then
  rsync -avz --delete \
    --exclude='.git' \
    --exclude='go.*' \
    --exclude='cmd' \
    --exclude='internal' \
    --exclude='*.go' \
    --exclude='deploy' \
    --exclude='.env' \
    --exclude='content/views.json' \
    ./ $SERVER:$DEST/
  echo "==> Reiniciando servicio..."
  ssh $SERVER 'systemctl restart blog'
  echo "==> Hecho."
fi
```

Primero dry-run, luego confirmación, luego el rsync real y reinicio del servicio. Simple, predecible y a prueba de errores.

## Conclusión

Rsync lleva existiendo desde 1996 y no ha necesitado reinventarse porque hace bien lo que hace. No es llamativo, no tiene una interfaz web bonita, no tiene un SaaS detrás. Es un comando que copia ficheros de forma inteligente. Y eso, en el día a día de un administrador de sistemas o un desarrollador que gestiona servidores, es más valioso que cualquier herramienta moderna con quinientas estrellas en GitHub.
