Transferencia de Archivos SCP/SFTP

Automatización avanzada de transferencias seguras de archivos entre sistemas remotos

Módulo 9 ⏱️ 35-40 min 🔒 SSH/SCP 📁 SFTP 📊 Avanzado

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.

Comandos SCP fundamentales Copiar
# 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.

scp_transfer.sh Copiar
#!/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.

Comandos SFTP básicos Copiar
# 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.

Script batch para SFTP Copiar
# 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:

sftp_manager.sh Copiar
#!/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.

ssh_key_manager.sh Copiar
#!/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:

sync_manager.sh Copiar
#!/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
  1. Genera un par de claves SSH para un usuario de backup
  2. Configura un servidor remoto para aceptar la clave
  3. Crea un script que transfiera logs diariamente usando SCP
  4. Implementa notificación por email en caso de fallos
  5. 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