Introducción a SCP y SFTP
SCP (Secure Copy Protocol) y SFTP (SSH File Transfer Protocol) son herramientas fundamentales para transferir archivos de forma segura entre sistemas remotos. Ambos utilizan SSH como protocolo de transporte, garantizando que todos los datos se transmitan encriptados.
SCP
- Sintaxis simple similar a
cp
- Ideal para tareas automatizadas
- Excelente para scripts de backup
- Preserva permisos y timestamps
SFTP
- Más funcional que SCP
- Permite navegación de directorios
- Soporte para scripts batch
- Mejor manejo de errores
Seguridad
Ambos protocolos utilizan el cifrado SSH, lo que los hace seguros para transferir datos sensibles a través de redes no confiables como Internet. Siempre prefiere estos métodos sobre FTP tradicional en entornos de producción.
SCP: Secure Copy Protocol
Comandos Básicos de SCP
SCP utiliza una sintaxis similar al comando cp
pero para transferencias remotas.
# Copiar archivo local a servidor remoto
scp archivo.txt [email protected]:/ruta/destino/
# Copiar archivo desde servidor remoto a local
scp [email protected]:/ruta/archivo.txt ./
# Copiar directorio completo (recursivo)
scp -r directorio/ [email protected]:/ruta/destino/
# Copiar con puerto SSH personalizado
scp -P 2222 archivo.txt [email protected]:/ruta/
# Copiar preservando atributos (permisos, timestamps)
scp -p archivo.txt [email protected]:/ruta/
# Copiar con compresión (útil para archivos grandes)
scp -C archivo_grande.zip [email protected]:/ruta/
# Modo verbose para debugging
scp -v archivo.txt [email protected]:/ruta/
# Copiar múltiples archivos
scp archivo1.txt archivo2.txt [email protected]:/ruta/
$ scp -v backup.tar.gz [email protected]:/var/backups/
Executing: program /usr/bin/ssh host 192.168.1.100, user admin, command scp -v -t /var/backups/
OpenSSH_8.9p1, OpenSSL 3.0.2 15 Mar 2022
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Connecting to 192.168.1.100 [192.168.1.100] port 22.
debug1: Connection established.
debug1: Authentication succeeded (publickey).
debug1: channel 0: new [client-session]
debug1: Sending file backup.tar.gz
backup.tar.gz 100% 4MB 2.1MB/s 00:02
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: channel 0: free: client-session, nchannels 1
Script Avanzado para SCP
Script robusto para transferencias automatizadas con manejo de errores.
#!/bin/bash
# Script avanzado para transferencias SCP
# Uso: ./scp_transfer.sh [upload|download] source destination [options]
# Configuración por defecto
DEFAULT_PORT=22
MAX_RETRIES=3
RETRY_DELAY=5
LOG_FILE="/var/log/scp_transfers.log"
VERBOSE=false
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Variables globales
OPERATION=""
SOURCE=""
DESTINATION=""
SSH_PORT=$DEFAULT_PORT
SSH_KEY=""
PRESERVE_ATTRS=false
USE_COMPRESSION=false
RECURSIVE=false
log_message() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
case $level in
ERROR) echo -e "${RED}[ERROR]${NC} $message" ;;
SUCCESS) echo -e "${GREEN}[SUCCESS]${NC} $message" ;;
WARNING) echo -e "${YELLOW}[WARNING]${NC} $message" ;;
INFO) echo -e "${BLUE}[INFO]${NC} $message" ;;
esac
}
# Mostrar ayuda
show_help() {
cat << EOF
Uso: $0 OPERATION SOURCE DESTINATION [OPCIONES]
OPERATIONS:
upload - Subir archivo/directorio local al servidor remoto
download - Descargar archivo/directorio desde servidor remoto
OPCIONES:
-p, --port PORT Puerto SSH (default: 22)
-k, --key KEY_FILE Archivo de clave privada SSH
-P, --preserve Preservar atributos de archivo
-C, --compress Usar compresión
-r, --recursive Transferencia recursiva para directorios
-v, --verbose Modo verbose
--retries N Número máximo de reintentos (default: 3)
--delay N Delay entre reintentos en segundos (default: 5)
EJEMPLOS:
$0 upload /local/file.txt user@server:/remote/path/
$0 download user@server:/remote/file.txt /local/path/
$0 upload /local/dir user@server:/remote/ -r -C -P
$0 download user@server:/remote/dir /local/ -r -k ~/.ssh/id_rsa
EOF
}
# Validar argumentos
validate_args() {
if [[ -z "$OPERATION" || -z "$SOURCE" || -z "$DESTINATION" ]]; then
log_message "ERROR" "Faltan argumentos requeridos"
show_help
exit 1
fi
if [[ "$OPERATION" != "upload" && "$OPERATION" != "download" ]]; then
log_message "ERROR" "Operación inválida: $OPERATION"
exit 1
fi
# Validar que el archivo/directorio fuente existe en upload
if [[ "$OPERATION" == "upload" && ! -e "$SOURCE" ]]; then
log_message "ERROR" "Archivo/directorio fuente no existe: $SOURCE"
exit 1
fi
}
# Construir comando SCP
build_scp_command() {
local cmd="scp"
# Agregar opciones
[[ "$PRESERVE_ATTRS" == true ]] && cmd="$cmd -p"
[[ "$USE_COMPRESSION" == true ]] && cmd="$cmd -C"
[[ "$RECURSIVE" == true ]] && cmd="$cmd -r"
[[ "$VERBOSE" == true ]] && cmd="$cmd -v"
# Puerto SSH
if [[ "$SSH_PORT" != "22" ]]; then
cmd="$cmd -P $SSH_PORT"
fi
# Clave SSH
if [[ -n "$SSH_KEY" ]]; then
if [[ ! -f "$SSH_KEY" ]]; then
log_message "ERROR" "Archivo de clave SSH no encontrado: $SSH_KEY"
exit 1
fi
cmd="$cmd -i $SSH_KEY"
fi
# Fuente y destino
cmd="$cmd \"$SOURCE\" \"$DESTINATION\""
echo "$cmd"
}
# Ejecutar transferencia con reintentos
execute_transfer() {
local cmd=$(build_scp_command)
local attempt=1
log_message "INFO" "Iniciando transferencia: $OPERATION"
log_message "INFO" "Comando: $cmd"
while [[ $attempt -le $MAX_RETRIES ]]; do
log_message "INFO" "Intento $attempt de $MAX_RETRIES"
if eval "$cmd"; then
log_message "SUCCESS" "Transferencia completada exitosamente"
return 0
else
local exit_code=$?
log_message "WARNING" "Intento $attempt falló con código: $exit_code"
if [[ $attempt -lt $MAX_RETRIES ]]; then
log_message "INFO" "Esperando $RETRY_DELAY segundos antes del siguiente intento..."
sleep $RETRY_DELAY
fi
((attempt++))
fi
done
log_message "ERROR" "Transferencia falló después de $MAX_RETRIES intentos"
return 1
}
# Verificar transferencia (para downloads)
verify_transfer() {
if [[ "$OPERATION" == "download" ]]; then
if [[ -f "$DESTINATION" ]] || [[ -d "$DESTINATION" ]]; then
local size=$(du -sh "$DESTINATION" 2>/dev/null | cut -f1)
log_message "SUCCESS" "Archivo/directorio descargado: $DESTINATION ($size)"
else
log_message "ERROR" "Verificación falló: $DESTINATION no encontrado"
return 1
fi
fi
}
# Procesar argumentos de línea de comandos
while [[ $# -gt 0 ]]; do
case $1 in
upload|download)
OPERATION=$1
shift
;;
-p|--port)
SSH_PORT="$2"
shift 2
;;
-k|--key)
SSH_KEY="$2"
shift 2
;;
-P|--preserve)
PRESERVE_ATTRS=true
shift
;;
-C|--compress)
USE_COMPRESSION=true
shift
;;
-r|--recursive)
RECURSIVE=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
--retries)
MAX_RETRIES="$2"
shift 2
;;
--delay)
RETRY_DELAY="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
-*)
log_message "ERROR" "Opción desconocida: $1"
show_help
exit 1
;;
*)
if [[ -z "$SOURCE" ]]; then
SOURCE="$1"
elif [[ -z "$DESTINATION" ]]; then
DESTINATION="$1"
else
log_message "ERROR" "Demasiados argumentos posicionales"
show_help
exit 1
fi
shift
;;
esac
done
# Función principal
main() {
log_message "INFO" "Iniciando script de transferencia SCP"
validate_args
# Crear directorio de log si no existe
mkdir -p "$(dirname "$LOG_FILE")"
# Ejecutar transferencia
if execute_transfer; then
verify_transfer
log_message "SUCCESS" "Operación completada exitosamente"
exit 0
else
log_message "ERROR" "Operación falló"
exit 1
fi
}
# Ejecutar si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
SFTP: SSH File Transfer Protocol
SFTP Interactivo y por Lotes
SFTP ofrece más funcionalidad que SCP, permitiendo navegación de directorios y ejecución de comandos batch.
# Conectar a servidor SFTP
sftp [email protected]
# Conectar con puerto personalizado
sftp -P 2222 [email protected]
# Conectar con clave SSH específica
sftp -i ~/.ssh/id_rsa [email protected]
# Comandos dentro de sesión SFTP:
pwd # Directorio actual remoto
lpwd # Directorio actual local
ls # Listar archivos remotos
lls # Listar archivos locales
cd /ruta/remota # Cambiar directorio remoto
lcd /ruta/local # Cambiar directorio local
# Transferencias
get archivo.txt # Descargar archivo
put archivo.txt # Subir archivo
get -r directorio/ # Descargar directorio
put -r directorio/ # Subir directorio
# Operaciones de archivos
mkdir nueva_carpeta # Crear directorio remoto
rmdir carpeta_vacia # Eliminar directorio vacío
rm archivo.txt # Eliminar archivo remoto
rename viejo.txt nuevo.txt # Renombrar archivo
# Información
stat archivo.txt # Información de archivo
df # Espacio en disco
version # Versión del servidor
# Salir
quit
exit
SFTP en Modo Batch
Ejecutar comandos SFTP desde archivos de script para automatización.
# Crear archivo de comandos batch (sftp_commands.txt)
cd /var/backups
pwd
ls -la
put backup_$(date +%Y%m%d).tar.gz
chmod 644 backup_$(date +%Y%m%d).tar.gz
ls -la backup_*
quit
# Ejecutar batch
sftp -b sftp_commands.txt [email protected]
# Ejemplo con pipe
echo -e "cd /uploads\nput file.txt\nls -la\nquit" | sftp [email protected]
Script Avanzado para SFTP Automatizado
Script completo para operaciones SFTP automatizadas con logging y manejo de errores:
#!/bin/bash
# Script avanzado para gestión SFTP automatizada
# Uso: ./sftp_manager.sh -h host -u user [opciones]
# Variables por defecto
HOST=""
USER=""
PORT=22
SSH_KEY=""
LOCAL_PATH="."
REMOTE_PATH="."
OPERATION="list"
BATCH_FILE=""
LOG_FILE="/var/log/sftp_manager.log"
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_message() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
case $level in
ERROR) echo -e "${RED}[ERROR]${NC} $message" ;;
SUCCESS) echo -e "${GREEN}[SUCCESS]${NC} $message" ;;
WARNING) echo -e "${YELLOW}[WARNING]${NC} $message" ;;
INFO) echo -e "${BLUE}[INFO]${NC} $message" ;;
esac
}
show_help() {
cat << EOF
Uso: $0 -h HOST -u USER [OPCIONES]
OPCIONES REQUERIDAS:
-h, --host HOST Servidor SFTP
-u, --user USER Usuario SFTP
OPCIONES:
-p, --port PORT Puerto SSH (default: 22)
-k, --key KEY_FILE Archivo de clave privada SSH
-l, --local-path PATH Ruta local (default: .)
-r, --remote-path PATH Ruta remota (default: .)
-o, --operation OP Operación: list|upload|download|sync|batch
-b, --batch-file FILE Archivo de comandos batch
--verbose Modo verbose
OPERACIONES:
list - Listar archivos remotos
upload - Subir archivos locales
download - Descargar archivos remotos
sync - Sincronizar directorios
batch - Ejecutar archivo de comandos
EJEMPLOS:
$0 -h server.com -u admin --operation list -r /var/backups
$0 -h server.com -u admin --operation upload -l ./files -r /uploads
$0 -h server.com -u admin --operation batch -b commands.sftp
EOF
}
# Validar parámetros requeridos
validate_params() {
if [[ -z "$HOST" || -z "$USER" ]]; then
log_message "ERROR" "Host y usuario son requeridos"
show_help
exit 1
fi
if [[ "$OPERATION" == "batch" && -z "$BATCH_FILE" ]]; then
log_message "ERROR" "Archivo batch requerido para operación batch"
exit 1
fi
if [[ -n "$SSH_KEY" && ! -f "$SSH_KEY" ]]; then
log_message "ERROR" "Archivo de clave SSH no encontrado: $SSH_KEY"
exit 1
fi
}
# Construir comando SFTP base
build_sftp_base() {
local cmd="sftp"
if [[ "$PORT" != "22" ]]; then
cmd="$cmd -P $PORT"
fi
if [[ -n "$SSH_KEY" ]]; then
cmd="$cmd -i $SSH_KEY"
fi
cmd="$cmd $USER@$HOST"
echo "$cmd"
}
# Generar archivo de comandos temporal
generate_temp_batch() {
local temp_file=$(mktemp /tmp/sftp_batch.XXXXXX)
case $OPERATION in
list)
cat > "$temp_file" << EOF
cd $REMOTE_PATH
pwd
ls -la
quit
EOF
;;
upload)
cat > "$temp_file" << EOF
lcd $LOCAL_PATH
cd $REMOTE_PATH
put -r *
ls -la
quit
EOF
;;
download)
cat > "$temp_file" << EOF
lcd $LOCAL_PATH
cd $REMOTE_PATH
get -r *
quit
EOF
;;
sync)
# Sincronización básica (subir archivos nuevos/modificados)
cat > "$temp_file" << EOF
lcd $LOCAL_PATH
cd $REMOTE_PATH
put -r *
ls -la
quit
EOF
;;
esac
echo "$temp_file"
}
# Ejecutar operación SFTP
execute_sftp() {
local sftp_cmd=$(build_sftp_base)
local batch_file="$BATCH_FILE"
log_message "INFO" "Ejecutando operación SFTP: $OPERATION"
log_message "INFO" "Conectando a: $USER@$HOST:$PORT"
# Si no es operación batch, generar archivo temporal
if [[ "$OPERATION" != "batch" ]]; then
batch_file=$(generate_temp_batch)
log_message "INFO" "Usando archivo batch temporal: $batch_file"
fi
# Mostrar comandos que se ejecutarán
log_message "INFO" "Comandos a ejecutar:"
cat "$batch_file" | while read -r line; do
[[ -n "$line" ]] && log_message "INFO" " $line"
done
# Ejecutar SFTP
local output_file=$(mktemp /tmp/sftp_output.XXXXXX)
if $sftp_cmd -b "$batch_file" > "$output_file" 2>&1; then
log_message "SUCCESS" "Operación SFTP completada"
# Mostrar output
echo -e "\n${BLUE}=== Output SFTP ===${NC}"
cat "$output_file"
# Limpiar archivos temporales
if [[ "$OPERATION" != "batch" ]]; then
rm -f "$batch_file"
fi
rm -f "$output_file"
return 0
else
log_message "ERROR" "Operación SFTP falló"
cat "$output_file"
# Limpiar archivos temporales
if [[ "$OPERATION" != "batch" ]]; then
rm -f "$batch_file"
fi
rm -f "$output_file"
return 1
fi
}
# Procesar argumentos
while [[ $# -gt 0 ]]; do
case $1 in
-h|--host)
HOST="$2"
shift 2
;;
-u|--user)
USER="$2"
shift 2
;;
-p|--port)
PORT="$2"
shift 2
;;
-k|--key)
SSH_KEY="$2"
shift 2
;;
-l|--local-path)
LOCAL_PATH="$2"
shift 2
;;
-r|--remote-path)
REMOTE_PATH="$2"
shift 2
;;
-o|--operation)
OPERATION="$2"
shift 2
;;
-b|--batch-file)
BATCH_FILE="$2"
shift 2
;;
--verbose)
set -x
shift
;;
--help)
show_help
exit 0
;;
*)
log_message "ERROR" "Opción desconocida: $1"
show_help
exit 1
;;
esac
done
# Función principal
main() {
log_message "INFO" "Iniciando SFTP Manager"
validate_params
# Crear directorio de log si no existe
mkdir -p "$(dirname "$LOG_FILE")"
# Ejecutar operación
if execute_sftp; then
log_message "SUCCESS" "Operación completada exitosamente"
exit 0
else
log_message "ERROR" "Operación falló"
exit 1
fi
}
# Ejecutar si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Gestión de Claves SSH
Automatización de Autenticación
Para automatizar completamente las transferencias, necesitamos configurar autenticación sin contraseña usando claves SSH.
#!/bin/bash
# Script para gestión automatizada de claves SSH
# Uso: ./ssh_key_manager.sh [generate|install|test] [opciones]
SSH_DIR="$HOME/.ssh"
DEFAULT_KEY_TYPE="rsa"
DEFAULT_KEY_SIZE="4096"
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_message() {
local level=$1
local message=$2
case $level in
ERROR) echo -e "${RED}[ERROR]${NC} $message" ;;
SUCCESS) echo -e "${GREEN}[SUCCESS]${NC} $message" ;;
WARNING) echo -e "${YELLOW}[WARNING]${NC} $message" ;;
INFO) echo -e "${BLUE}[INFO]${NC} $message" ;;
esac
}
# Generar nueva clave SSH
generate_key() {
local key_name=${1:-"id_${DEFAULT_KEY_TYPE}"}
local key_type=${2:-$DEFAULT_KEY_TYPE}
local key_size=${3:-$DEFAULT_KEY_SIZE}
local comment=${4:-"$(whoami)@$(hostname)-$(date +%Y%m%d)"}
local key_path="$SSH_DIR/$key_name"
log_message "INFO" "Generando clave SSH: $key_name"
# Crear directorio .ssh si no existe
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
# Verificar si la clave ya existe
if [[ -f "$key_path" ]]; then
log_message "WARNING" "La clave $key_name ya existe"
read -p "¿Sobrescribir? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_message "INFO" "Operación cancelada"
return 1
fi
fi
# Generar clave
if ssh-keygen -t "$key_type" -b "$key_size" -f "$key_path" -C "$comment" -N ""; then
log_message "SUCCESS" "Clave generada exitosamente"
log_message "INFO" "Clave privada: $key_path"
log_message "INFO" "Clave pública: ${key_path}.pub"
# Configurar permisos
chmod 600 "$key_path"
chmod 644 "${key_path}.pub"
# Mostrar la clave pública
echo -e "\n${BLUE}=== Clave Pública ===${NC}"
cat "${key_path}.pub"
echo
return 0
else
log_message "ERROR" "Falló la generación de clave"
return 1
fi
}
# Instalar clave pública en servidor remoto
install_key() {
local user=$1
local host=$2
local key_file=${3:-"$SSH_DIR/id_rsa.pub"}
local port=${4:-22}
if [[ -z "$user" || -z "$host" ]]; then
log_message "ERROR" "Usuario y host son requeridos"
log_message "INFO" "Uso: install_key USER HOST [KEY_FILE] [PORT]"
return 1
fi
if [[ ! -f "$key_file" ]]; then
log_message "ERROR" "Archivo de clave pública no encontrado: $key_file"
return 1
fi
log_message "INFO" "Instalando clave en $user@$host:$port"
# Usar ssh-copy-id si está disponible
if command -v ssh-copy-id &> /dev/null; then
if ssh-copy-id -p "$port" -i "$key_file" "$user@$host"; then
log_message "SUCCESS" "Clave instalada exitosamente con ssh-copy-id"
return 0
else
log_message "WARNING" "ssh-copy-id falló, intentando método manual"
fi
fi
# Método manual
log_message "INFO" "Usando método manual para instalar clave"
local pub_key=$(cat "$key_file")
local remote_cmd="mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '$pub_key' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
if ssh -p "$port" "$user@$host" "$remote_cmd"; then
log_message "SUCCESS" "Clave instalada exitosamente (método manual)"
return 0
else
log_message "ERROR" "Falló la instalación manual de clave"
return 1
fi
}
# Probar conexión SSH
test_connection() {
local user=$1
local host=$2
local key_file=${3:-"$SSH_DIR/id_rsa"}
local port=${4:-22}
if [[ -z "$user" || -z "$host" ]]; then
log_message "ERROR" "Usuario y host son requeridos"
return 1
fi
log_message "INFO" "Probando conexión SSH a $user@$host:$port"
local ssh_cmd="ssh -p $port -o ConnectTimeout=10 -o BatchMode=yes"
if [[ -f "$key_file" ]]; then
ssh_cmd="$ssh_cmd -i $key_file"
fi
if $ssh_cmd "$user@$host" "echo 'Conexión SSH exitosa desde $(hostname)'" 2>/dev/null; then
log_message "SUCCESS" "Conexión SSH funciona correctamente"
# Probar SCP
local test_file=$(mktemp)
echo "Test SCP $(date)" > "$test_file"
if scp -P "$port" ${key_file:+-i "$key_file"} "$test_file" "$user@$host:/tmp/scp_test_$$" &>/dev/null; then
log_message "SUCCESS" "SCP funciona correctamente"
# Limpiar archivo de prueba
ssh -p "$port" ${key_file:+-i "$key_file"} "$user@$host" "rm -f /tmp/scp_test_$$" &>/dev/null
else
log_message "WARNING" "SCP falló, pero SSH funciona"
fi
rm -f "$test_file"
return 0
else
log_message "ERROR" "Conexión SSH falló"
return 1
fi
}
# Mostrar ayuda
show_help() {
cat << EOF
Uso: $0 COMANDO [OPCIONES]
COMANDOS:
generate [KEY_NAME] [TYPE] [SIZE] [COMMENT]
- Generar nueva clave SSH
install USER HOST [KEY_FILE] [PORT]
- Instalar clave pública en servidor
test USER HOST [KEY_FILE] [PORT]
- Probar conexión SSH/SCP
EJEMPLOS:
$0 generate
$0 generate backup_key ed25519 256 "backup-server-key"
$0 install admin server.com
$0 install admin server.com ~/.ssh/backup_key.pub 2222
$0 test admin server.com ~/.ssh/backup_key 2222
EOF
}
# Función principal
main() {
case "${1:-}" in
generate)
generate_key "$2" "$3" "$4" "$5"
;;
install)
install_key "$2" "$3" "$4" "$5"
;;
test)
test_connection "$2" "$3" "$4" "$5"
;;
-h|--help|help)
show_help
exit 0
;;
*)
log_message "ERROR" "Comando desconocido: ${1:-}"
show_help
exit 1
;;
esac
}
# Ejecutar si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Scripts de Sincronización Avanzada
Sistema de Sincronización Completo
Script maestro que combina SCP y SFTP para sincronización bidireccional:
#!/bin/bash
# Sistema avanzado de sincronización con SCP/SFTP
# Soporta sincronización bidireccional, incremental y programada
CONFIG_FILE="/etc/sync_manager.conf"
STATE_DIR="/var/lib/sync_manager"
LOG_FILE="/var/log/sync_manager.log"
# Configuración por defecto
DEFAULT_CONFIG='
# Configuración de sincronización
SYNC_PROFILES=(
"web:/var/www/html:user@webserver:/var/www/html:upload:daily"
"backup:/home/backups:[email protected]:/backups:bidirectional:weekly"
"logs:/var/log:logger@logserver:/remote/logs:upload:hourly"
)
# Configuración SSH
SSH_KEY="/root/.ssh/sync_key"
SSH_PORT=22
SSH_OPTIONS="-o ConnectTimeout=10 -o BatchMode=yes"
'
# Cargar configuración
[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE" || echo "$DEFAULT_CONFIG" > "$CONFIG_FILE"
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_message() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
case $level in
ERROR) echo -e "${RED}[ERROR]${NC} $message" ;;
SUCCESS) echo -e "${GREEN}[SUCCESS]${NC} $message" ;;
WARNING) echo -e "${YELLOW}[WARNING]${NC} $message" ;;
INFO) echo -e "${BLUE}[INFO]${NC} $message" ;;
esac
}
# Crear directorios necesarios
setup_directories() {
mkdir -p "$STATE_DIR" "$(dirname "$LOG_FILE")" "$(dirname "$CONFIG_FILE")"
}
# Parsear perfil de sincronización
parse_profile() {
local profile="$1"
IFS=':' read -r name local_path remote_path direction frequency <<< "$profile"
echo "NAME=$name"
echo "LOCAL_PATH=$local_path"
echo "REMOTE_PATH=$remote_path"
echo "DIRECTION=$direction"
echo "FREQUENCY=$frequency"
}
# Verificar si es hora de sincronizar
should_sync() {
local profile_name="$1"
local frequency="$2"
local last_sync_file="$STATE_DIR/${profile_name}_last_sync"
if [[ ! -f "$last_sync_file" ]]; then
return 0 # Primera sincronización
fi
local last_sync=$(cat "$last_sync_file")
local current_time=$(date +%s)
local time_diff=$((current_time - last_sync))
case $frequency in
hourly) [[ $time_diff -gt 3600 ]] && return 0 ;;
daily) [[ $time_diff -gt 86400 ]] && return 0 ;;
weekly) [[ $time_diff -gt 604800 ]] && return 0 ;;
monthly) [[ $time_diff -gt 2592000 ]] && return 0 ;;
*) return 0 ;; # Desconocido, sincronizar siempre
esac
return 1
}
# Sincronización upload (local -> remoto)
sync_upload() {
local local_path="$1"
local remote_path="$2"
log_message "INFO" "Sincronizando upload: $local_path -> $remote_path"
if [[ ! -d "$local_path" ]]; then
log_message "ERROR" "Directorio local no existe: $local_path"
return 1
fi
# Usar rsync sobre SSH si está disponible (más eficiente)
if command -v rsync &> /dev/null; then
local rsync_cmd="rsync -avz --delete -e 'ssh $SSH_OPTIONS -p $SSH_PORT -i $SSH_KEY' '$local_path/' '$remote_path/'"
if eval "$rsync_cmd"; then
log_message "SUCCESS" "Upload completado con rsync"
return 0
else
log_message "WARNING" "rsync falló, intentando con scp"
fi
fi
# Fallback a SCP
local scp_cmd="scp $SSH_OPTIONS -P $SSH_PORT -i '$SSH_KEY' -r '$local_path'/* '$remote_path/'"
if eval "$scp_cmd"; then
log_message "SUCCESS" "Upload completado con scp"
return 0
else
log_message "ERROR" "Upload falló"
return 1
fi
}
# Sincronización download (remoto -> local)
sync_download() {
local local_path="$1"
local remote_path="$2"
log_message "INFO" "Sincronizando download: $remote_path -> $local_path"
mkdir -p "$local_path"
# Usar rsync sobre SSH si está disponible
if command -v rsync &> /dev/null; then
local rsync_cmd="rsync -avz --delete -e 'ssh $SSH_OPTIONS -p $SSH_PORT -i $SSH_KEY' '$remote_path/' '$local_path/'"
if eval "$rsync_cmd"; then
log_message "SUCCESS" "Download completado con rsync"
return 0
else
log_message "WARNING" "rsync falló, intentando con scp"
fi
fi
# Fallback a SCP
local scp_cmd="scp $SSH_OPTIONS -P $SSH_PORT -i '$SSH_KEY' -r '$remote_path'/* '$local_path/'"
if eval "$scp_cmd"; then
log_message "SUCCESS" "Download completado con scp"
return 0
else
log_message "ERROR" "Download falló"
return 1
fi
}
# Sincronización bidireccional
sync_bidirectional() {
local local_path="$1"
local remote_path="$2"
log_message "INFO" "Sincronización bidireccional: $local_path <-> $remote_path"
# Esta es una implementación básica
# En producción, necesitarías detección de conflictos más sofisticada
if sync_upload "$local_path" "$remote_path" && sync_download "$local_path" "$remote_path"; then
log_message "SUCCESS" "Sincronización bidireccional completada"
return 0
else
log_message "ERROR" "Sincronización bidireccional falló"
return 1
fi
}
# Ejecutar sincronización de un perfil
sync_profile() {
local profile="$1"
local force_sync=${2:-false}
# Parsear perfil
eval "$(parse_profile "$profile")"
log_message "INFO" "Procesando perfil: $NAME"
# Verificar si debe sincronizar
if [[ "$force_sync" != "true" ]] && ! should_sync "$NAME" "$FREQUENCY"; then
log_message "INFO" "Sincronización no necesaria para $NAME ($FREQUENCY)"
return 0
fi
# Ejecutar sincronización según dirección
local sync_success=false
case $DIRECTION in
upload)
sync_upload "$LOCAL_PATH" "$REMOTE_PATH" && sync_success=true
;;
download)
sync_download "$LOCAL_PATH" "$REMOTE_PATH" && sync_success=true
;;
bidirectional)
sync_bidirectional "$LOCAL_PATH" "$REMOTE_PATH" && sync_success=true
;;
*)
log_message "ERROR" "Dirección de sincronización desconocida: $DIRECTION"
return 1
;;
esac
# Actualizar timestamp si fue exitosa
if [[ "$sync_success" == true ]]; then
echo "$(date +%s)" > "$STATE_DIR/${NAME}_last_sync"
log_message "SUCCESS" "Perfil $NAME sincronizado exitosamente"
else
log_message "ERROR" "Sincronización del perfil $NAME falló"
return 1
fi
}
# Sincronizar todos los perfiles
sync_all() {
local force_sync=${1:-false}
log_message "INFO" "Iniciando sincronización de todos los perfiles"
local success_count=0
local total_count=0
for profile in "${SYNC_PROFILES[@]}"; do
((total_count++))
if sync_profile "$profile" "$force_sync"; then
((success_count++))
fi
done
log_message "INFO" "Sincronización completada: $success_count/$total_count perfiles exitosos"
if [[ $success_count -eq $total_count ]]; then
return 0
else
return 1
fi
}
# Mostrar estado de sincronización
show_status() {
echo -e "${BLUE}=== Estado de Sincronización ===${NC}"
printf "%-15s %-20s %-15s %-20s %s\n" "PERFIL" "FRECUENCIA" "DIRECCIÓN" "ÚLTIMA SYNC" "ESTADO"
echo "────────────────────────────────────────────────────────────────────────────────"
for profile in "${SYNC_PROFILES[@]}"; do
eval "$(parse_profile "$profile")"
local last_sync_file="$STATE_DIR/${NAME}_last_sync"
local last_sync_time="Nunca"
local status="⏳ Pendiente"
if [[ -f "$last_sync_file" ]]; then
local last_sync=$(cat "$last_sync_file")
last_sync_time=$(date -d "@$last_sync" '+%Y-%m-%d %H:%M')
if should_sync "$NAME" "$FREQUENCY"; then
status="⏳ Pendiente"
else
status="✅ Actualizado"
fi
fi
printf "%-15s %-20s %-15s %-20s %s\n" "$NAME" "$FREQUENCY" "$DIRECTION" "$last_sync_time" "$status"
done
}
# Mostrar ayuda
show_help() {
cat << EOF
Uso: $0 COMANDO [OPCIONES]
COMANDOS:
sync-all [--force] - Sincronizar todos los perfiles
sync PROFILE [--force] - Sincronizar perfil específico
status - Mostrar estado de sincronización
test PROFILE - Probar conectividad para perfil
OPCIONES:
--force - Forzar sincronización independientemente de la frecuencia
CONFIGURACIÓN:
Archivo de configuración: $CONFIG_FILE
EJEMPLOS:
$0 sync-all
$0 sync-all --force
$0 sync web --force
$0 status
EOF
}
# Función principal
main() {
setup_directories
case "${1:-}" in
sync-all)
local force_sync=false
[[ "$2" == "--force" ]] && force_sync=true
sync_all "$force_sync"
;;
sync)
if [[ -z "$2" ]]; then
log_message "ERROR" "Nombre de perfil requerido"
exit 1
fi
local force_sync=false
[[ "$3" == "--force" ]] && force_sync=true
# Buscar perfil por nombre
local found_profile=""
for profile in "${SYNC_PROFILES[@]}"; do
eval "$(parse_profile "$profile")"
if [[ "$NAME" == "$2" ]]; then
found_profile="$profile"
break
fi
done
if [[ -z "$found_profile" ]]; then
log_message "ERROR" "Perfil no encontrado: $2"
exit 1
fi
sync_profile "$found_profile" "$force_sync"
;;
status)
show_status
;;
test)
log_message "INFO" "Función de test en desarrollo"
;;
-h|--help|help)
show_help
exit 0
;;
*)
log_message "ERROR" "Comando desconocido: ${1:-}"
show_help
exit 1
;;
esac
}
# Ejecutar si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Ejercicios Prácticos
Ejercicio 1: Configuración de Transferencia Automatizada
- Genera un par de claves SSH para un usuario de backup
- Configura un servidor remoto para aceptar la clave
- Crea un script que transfiera logs diariamente usando SCP
- Implementa notificación por email en caso de fallos
- Programa la ejecución automática con cron
Proyecto Avanzado: Sistema de Backup Distribuido
Implementa un sistema completo que:
- Realice backups incrementales con múltiples destinos
- Use compresión y encriptación
- Verifique integridad de archivos transferidos
- Implemente rotación automática de backups
- Genere reportes de estado
Mejores Prácticas
- Compresión: Usa
-C
para archivos grandes - Reintentos: Implementa lógica de reintentos con backoff exponencial
- Logging: Registra todas las operaciones para auditoría
- Verificación: Siempre verifica que las transferencias fueron exitosas
- Seguridad: Usa claves SSH específicas para cada propósito
- Monitoreo: Implementa alertas para transferencias fallidas