#!/bin/bash # ============================================================================= # File: 5b_remote_copy_rsync_modified.sh # Deskripsi: Menyalin file ke server remote menggunakan RSYNC/SCP (Auto-Fallback) # Author: Modified from original by bil-awal # ============================================================================= # Konfigurasi default DEFAULT_PORT=22 DEFAULT_RSYNC_OPTIONS="-ahz --info=progress2" DEFAULT_SCP_OPTIONS="-r -p -v" SSH_TIMEOUT=10 # Global variable untuk transfer method TRANSFER_METHOD="" # Fungsi untuk menampilkan cara penggunaan usage() { echo "Penggunaan: $0 [port] [destination_path]" echo "" echo "Parameter:" echo " file_source - File atau direktori yang akan disalin" echo " username - Username di server remote" echo " ip_address - IP address server tujuan" echo " port (opsional) - Port SSH (default: 22)" echo " destination_path (opsional) - Path tujuan (default: /home/username/)" echo "" echo "Contoh:" echo " $0 /home/user/document.txt admin 192.168.1.100" echo " $0 /var/log/myapp/ deploy 10.0.0.5 2222" echo " $0 backup.tar.gz user 192.168.1.50 22 /tmp/" exit 1 } # Fungsi logging log() { local level="$1" shift local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $*" } # Validasi jumlah parameter if [ "$#" -lt 3 ] || [ "$#" -gt 5 ]; then echo "Error: Jumlah parameter tidak sesuai!" usage fi # Variabel SOURCE_PATH="$1" REMOTE_USER="$2" REMOTE_IP="$3" REMOTE_PORT="${4:-$DEFAULT_PORT}" REMOTE_DEST="${5:-/home/$REMOTE_USER/}" RSYNC_OPTIONS="${RSYNC_OPTIONS:-$DEFAULT_RSYNC_OPTIONS}" SCP_OPTIONS="${SCP_OPTIONS:-$DEFAULT_SCP_OPTIONS}" # Fungsi untuk validasi input yang lebih baik validate_inputs() { # Validasi source path if [ ! -e "$SOURCE_PATH" ]; then log "ERROR" "File atau direktori '$SOURCE_PATH' tidak ditemukan!" exit 1 fi # Validasi username tidak kosong if [ -z "$REMOTE_USER" ]; then log "ERROR" "Username tidak boleh kosong!" exit 1 fi # Validasi port (harus numerik dan dalam range valid) if ! [[ "$REMOTE_PORT" =~ ^[0-9]+$ ]] || [ "$REMOTE_PORT" -lt 1 ] || [ "$REMOTE_PORT" -gt 65535 ]; then log "ERROR" "Port '$REMOTE_PORT' tidak valid! Harus antara 1-65535." exit 1 fi # Validasi format IP address atau hostname if echo "$REMOTE_IP" | grep -qE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'; then # Validasi IP address lebih detail IFS='.' read -ra ADDR <<< "$REMOTE_IP" for i in "${ADDR[@]}"; do if [ "$i" -gt 255 ]; then log "ERROR" "IP address '$REMOTE_IP' tidak valid!" exit 1 fi done else log "INFO" "'$REMOTE_IP' terdeteksi sebagai hostname/FQDN." fi } # Fungsi untuk cek dependencies dan pilih method select_transfer_method() { # Cek SSH (critical) if ! command -v ssh &> /dev/null; then log "ERROR" "SSH client tidak ditemukan! Install openssh-client." exit 1 fi # Cek rsync atau scp if command -v rsync &> /dev/null; then TRANSFER_METHOD="rsync" log "SUCCESS" "RSYNC tersedia - menggunakan rsync" elif command -v scp &> /dev/null; then TRANSFER_METHOD="scp" log "WARNING" "RSYNC tidak tersedia - fallback ke SCP" else log "ERROR" "Baik rsync maupun scp tidak tersedia!" exit 1 fi # Check optional commands if ! command -v timeout &> /dev/null; then log "WARNING" "Command 'timeout' tidak tersedia - menggunakan default timeout" fi } # Fungsi untuk cek konektivitas SSH yang robust check_ssh_connectivity() { log "INFO" "Memeriksa koneksi SSH ke $REMOTE_USER@$REMOTE_IP:$REMOTE_PORT..." # Prepare SSH options local ssh_opts="-p $REMOTE_PORT -o ConnectTimeout=5 -o BatchMode=yes -o LogLevel=ERROR" local ssh_cmd="ssh $ssh_opts $REMOTE_USER@$REMOTE_IP exit" # Test SSH connectivity dengan atau tanpa timeout if command -v timeout &> /dev/null; then ssh_cmd="timeout $SSH_TIMEOUT $ssh_cmd" fi if $ssh_cmd 2>/dev/null; then log "SUCCESS" "SSH connection berhasil ke $REMOTE_IP:$REMOTE_PORT" return 0 else log "WARNING" "SSH key-based authentication gagal. Kemungkinan penyebab:" echo " - SSH service tidak berjalan pada port $REMOTE_PORT" echo " - SSH keys belum dikonfigurasi" echo " - Authentication memerlukan password" echo " - Server tidak dapat dijangkau" read -p "Lanjutkan dengan password authentication? (y/n): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi return 1 fi } # Fungsi untuk cek SSH key dengan lebih detail check_ssh_auth() { local key_types=("rsa" "ed25519" "ecdsa" "dsa") local found_keys=() for key_type in "${key_types[@]}"; do local key_file="$HOME/.ssh/id_$key_type" if [ -f "$key_file" ]; then found_keys+=("$key_type") fi done if [ ${#found_keys[@]} -gt 0 ]; then log "SUCCESS" "SSH keys ditemukan: ${found_keys[*]}" else log "INFO" "SSH key tidak ditemukan. Password authentication akan digunakan." echo "Tips: Gunakan 'ssh-keygen -t ed25519' untuk membuat SSH key pair." fi # Test SSH agent if ssh-add -l &>/dev/null; then log "INFO" "SSH agent aktif dengan $(ssh-add -l | wc -l) key(s) loaded." fi } # Fungsi untuk mendapatkan info file/direktori yang detail get_source_info() { local size type file_count dir_count if [ -f "$SOURCE_PATH" ]; then type="File" size=$(du -h "$SOURCE_PATH" | cut -f1) file_count="1" dir_count="0" elif [ -d "$SOURCE_PATH" ]; then type="Direktori" size=$(du -sh "$SOURCE_PATH" 2>/dev/null | cut -f1) file_count=$(find "$SOURCE_PATH" -type f 2>/dev/null | wc -l) dir_count=$(find "$SOURCE_PATH" -type d 2>/dev/null | wc -l) dir_count=$((dir_count - 1)) # Exclude source directory itself else type="Unknown" size="N/A" file_count="N/A" dir_count="N/A" fi echo "Tipe: $type" echo "Ukuran: $size" echo "Files: $file_count" if [ "$dir_count" != "0" ] && [ "$dir_count" != "N/A" ]; then echo "Directories: $dir_count" fi echo "Path: $SOURCE_PATH" } # Fungsi untuk memilih mode transfer (adapted untuk SCP) select_transfer_mode() { echo "" echo "Method: $TRANSFER_METHOD" if [ "$TRANSFER_METHOD" = "rsync" ]; then echo "Pilih mode sinkronisasi RSYNC:" echo "1. Normal - Copy file baru dan update yang berubah" echo "2. Mirror - Sinkronisasi penuh (hapus file di tujuan yang tidak ada di source)" echo "3. Backup - Normal + backup file yang akan dioverwrite" echo "4. Checksum - Validasi dengan checksum (lebih akurat, lebih lambat)" echo "5. Dry-run - Simulasi saja, tidak melakukan transfer" echo "6. Custom - Masukkan opsi rsync sendiri" echo "" read -p "Pilih mode (1-6) [default: 1]: " mode_choice case "$mode_choice" in 2) RSYNC_OPTIONS="$RSYNC_OPTIONS --delete" log "INFO" "Mode: Mirror (dengan --delete)" ;; 3) local backup_dir="backup_$(date +%Y%m%d_%H%M%S)" RSYNC_OPTIONS="$RSYNC_OPTIONS --backup --backup-dir=$backup_dir" log "INFO" "Mode: Backup (backup dir: $backup_dir)" ;; 4) RSYNC_OPTIONS="$RSYNC_OPTIONS --checksum" log "INFO" "Mode: Checksum validation" ;; 5) RSYNC_OPTIONS="$RSYNC_OPTIONS --dry-run" log "INFO" "Mode: Dry-run (simulasi)" ;; 6) echo "Opsi rsync saat ini: $RSYNC_OPTIONS" read -p "Masukkan opsi tambahan: " custom_opts if [ -n "$custom_opts" ]; then RSYNC_OPTIONS="$RSYNC_OPTIONS $custom_opts" fi log "INFO" "Mode: Custom" ;; *) log "INFO" "Mode: Normal" ;; esac else echo "Mode SCP: Normal copy (SCP tidak mendukung advanced sync modes)" echo "Options yang tersedia untuk SCP:" echo " -r: Recursive (untuk direktori)" echo " -p: Preserve timestamps dan permissions" echo " -v: Verbose output" log "INFO" "Mode: SCP Normal" fi } # Fungsi untuk bandwidth limiting (works untuk both rsync dan scp) set_bandwidth_limit() { echo "" read -p "Apakah ingin membatasi bandwidth? (y/n): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then if [ "$TRANSFER_METHOD" = "rsync" ]; then echo "Contoh: 1000 (KB/s), 5M (MB/s)" read -p "Masukkan limit bandwidth: " bw_limit if [ -n "$bw_limit" ]; then RSYNC_OPTIONS="$RSYNC_OPTIONS --bwlimit=$bw_limit" log "INFO" "RSYNC Bandwidth limit: $bw_limit" fi else echo "Contoh: 1000 (KB/s)" read -p "Masukkan limit bandwidth (KB/s): " bw_limit if [ -n "$bw_limit" ]; then SCP_OPTIONS="$SCP_OPTIONS -l $bw_limit" log "INFO" "SCP Bandwidth limit: ${bw_limit} KB/s" fi fi fi } # Fungsi untuk exclude patterns (hanya untuk rsync) create_exclude_patterns() { if [ "$TRANSFER_METHOD" != "rsync" ]; then log "INFO" "Exclude patterns hanya tersedia untuk RSYNC" return fi local exclude_file="/tmp/rsync_exclude_$$" echo "" read -p "Apakah ada file/direktori yang ingin di-exclude? (y/n): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "" echo "Pilih exclude method:" echo "1. Manual input" echo "2. Common patterns (logs, cache, tmp files)" echo "3. Development patterns (node_modules, .git, build dirs)" read -p "Pilih (1-3): " exclude_method case "$exclude_method" in 2) cat > "$exclude_file" << EOF *.log *.tmp *.cache *~ .DS_Store Thumbs.db EOF log "INFO" "Common exclude patterns applied" ;; 3) cat > "$exclude_file" << EOF node_modules/ .git/ .svn/ .hg/ __pycache__/ *.pyc *.pyo .pytest_cache/ target/ build/ dist/ .gradle/ .idea/ .vscode/ *.swp *.swo EOF log "INFO" "Development exclude patterns applied" ;; *) echo "Masukkan pattern yang ingin di-exclude (satu per baris, akhiri dengan CTRL+D):" echo "Contoh: *.log, node_modules/, .git/, __pycache__/" cat > "$exclude_file" ;; esac if [ -s "$exclude_file" ]; then RSYNC_OPTIONS="$RSYNC_OPTIONS --exclude-from=$exclude_file" echo "✓ Exclude patterns tersimpan." echo "Preview exclude patterns:" cat "$exclude_file" | sed 's/^/ - /' fi fi } # Fungsi untuk melakukan transfer dengan fallback perform_transfer() { local source="$1" local destination="$2" local start_time=$(date +%s) echo "" log "INFO" "Memulai transfer menggunakan $TRANSFER_METHOD..." echo "Dari: $source" echo "Ke: $destination" echo "Port: $REMOTE_PORT" case "$TRANSFER_METHOD" in "rsync") # Tambahkan trailing slash untuk direktori (rsync behavior) if [ -d "$source" ]; then source="${source%/}/" fi echo "Options: $RSYNC_OPTIONS" echo "" # Build rsync command local rsync_cmd if [ "$REMOTE_PORT" != "22" ]; then rsync_cmd="rsync $RSYNC_OPTIONS -e \"ssh -p $REMOTE_PORT\" \"$source\" \"$destination\"" else rsync_cmd="rsync $RSYNC_OPTIONS \"$source\" \"$destination\"" fi if eval $rsync_cmd; then local end_time=$(date +%s) local duration=$((end_time - start_time)) echo "" log "SUCCESS" "RSYNC transfer berhasil!" echo "Waktu transfer: ${duration} detik" # Cleanup exclude file jika ada if [[ "$RSYNC_OPTIONS" == *"--exclude-from"* ]]; then rm -f /tmp/rsync_exclude_$$ fi return 0 else local exit_code=$? log "ERROR" "RSYNC transfer gagal dengan exit code: $exit_code" return $exit_code fi ;; "scp") echo "Options: $SCP_OPTIONS" echo "" # Build SCP command local scp_cmd if [ "$REMOTE_PORT" != "22" ]; then SCP_OPTIONS="$SCP_OPTIONS -P $REMOTE_PORT" fi scp_cmd="scp $SCP_OPTIONS \"$source\" \"$destination\"" if eval $scp_cmd; then local end_time=$(date +%s) local duration=$((end_time - start_time)) echo "" log "SUCCESS" "SCP transfer berhasil!" echo "Waktu transfer: ${duration} detik" return 0 else local exit_code=$? log "ERROR" "SCP transfer gagal dengan exit code: $exit_code" return $exit_code fi ;; esac } # Fungsi untuk menampilkan tips optimasi suggest_optimization() { echo "" if [ "$TRANSFER_METHOD" = "rsync" ]; then echo "💡 Tips RSYNC Optimization:" echo "📁 File besar: --partial --append-verify --inplace" echo "🌐 Network: --compress-level=6 --bwlimit=RATE" echo "💾 Backup: --backup --backup-dir=backup_\$(date +%Y%m%d)" echo "🔍 Validasi: --checksum (akurat) atau --size-only (cepat)" echo "📊 Monitoring: --progress --stats -v" echo "🚀 Performance: --whole-file (LAN) atau --no-whole-file (WAN)" else echo "💡 Tips SCP Optimization:" echo "📁 Compression: -C (enable compression)" echo "🌐 Network: -l LIMIT (bandwidth limit)" echo "🔐 Security: -o (SSH options)" echo "📊 Monitoring: -v (verbose output)" fi } # Fungsi untuk menampilkan transfer summary show_transfer_summary() { echo "" echo "==========================================" echo "RINGKASAN TRANSFER ($TRANSFER_METHOD)" echo "==========================================" echo "Source: $SOURCE_PATH" echo "Destination: $DESTINATION" echo "Port: $REMOTE_PORT" echo "Method: $TRANSFER_METHOD" if [ "$TRANSFER_METHOD" = "rsync" ]; then echo "Options: $RSYNC_OPTIONS" else echo "Options: $SCP_OPTIONS" fi echo "==========================================" } # Main execution main() { echo "==========================================" echo "Remote Copy (RSYNC/SCP Hybrid)" echo "==========================================" # Select transfer method select_transfer_method # Validasi input validate_inputs # Tampilkan informasi transfer echo "" echo "Informasi Transfer:" echo "-------------------" get_source_info echo "Tujuan: $REMOTE_USER@$REMOTE_IP:$REMOTE_DEST" echo "Port: $REMOTE_PORT" echo "Method: $TRANSFER_METHOD" # Cek konektivitas SSH check_ssh_connectivity # Cek SSH authentication check_ssh_auth # Pilih mode transfer select_transfer_mode # Set bandwidth limit set_bandwidth_limit # Tanyakan exclude patterns (hanya untuk rsync) create_exclude_patterns # Tampilkan tips optimasi suggest_optimization # Buat destination path DESTINATION="$REMOTE_USER@$REMOTE_IP:$REMOTE_DEST" # Tampilkan summary show_transfer_summary # Konfirmasi sebelum transfer echo "" read -p "Mulai transfer? (y/n): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log "INFO" "Transfer dibatalkan oleh user." exit 0 fi # Lakukan transfer if perform_transfer "$SOURCE_PATH" "$DESTINATION"; then echo "" echo "==========================================" log "SUCCESS" "File/direktori berhasil ditransfer!" echo "Lokasi: $DESTINATION" echo "==========================================" exit 0 else echo "" echo "==========================================" log "ERROR" "Gagal melakukan transfer." echo "==========================================" exit 1 fi } # Handle Ctrl+C gracefully trap 'echo ""; log "INFO" "Transfer dibatalkan oleh user."; exit 1' INT # Jalankan main function main