· 3 months ago · Jun 18, 2025, 04:50 AM
1#!/bin/bash
2# =============================================================================
3# File: 5b_remote_copy_rsync_modified.sh
4# Deskripsi: Menyalin file ke server remote menggunakan RSYNC/SCP (Auto-Fallback)
5# Author: Modified from original by bil-awal
6# =============================================================================
7
8# Konfigurasi default
9DEFAULT_PORT=22
10DEFAULT_RSYNC_OPTIONS="-ahz --info=progress2"
11DEFAULT_SCP_OPTIONS="-r -p -v"
12SSH_TIMEOUT=10
13
14# Global variable untuk transfer method
15TRANSFER_METHOD=""
16
17# Fungsi untuk menampilkan cara penggunaan
18usage() {
19 echo "Penggunaan: $0 <file_source> <username> <ip_address> [port] [destination_path]"
20 echo ""
21 echo "Parameter:"
22 echo " file_source - File atau direktori yang akan disalin"
23 echo " username - Username di server remote"
24 echo " ip_address - IP address server tujuan"
25 echo " port (opsional) - Port SSH (default: 22)"
26 echo " destination_path (opsional) - Path tujuan (default: /home/username/)"
27 echo ""
28 echo "Contoh:"
29 echo " $0 /home/user/document.txt admin 192.168.1.100"
30 echo " $0 /var/log/myapp/ deploy 10.0.0.5 2222"
31 echo " $0 backup.tar.gz user 192.168.1.50 22 /tmp/"
32 exit 1
33}
34
35# Fungsi logging
36log() {
37 local level="$1"
38 shift
39 local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
40 echo "[$timestamp] [$level] $*"
41}
42
43# Validasi jumlah parameter
44if [ "$#" -lt 3 ] || [ "$#" -gt 5 ]; then
45 echo "Error: Jumlah parameter tidak sesuai!"
46 usage
47fi
48
49# Variabel
50SOURCE_PATH="$1"
51REMOTE_USER="$2"
52REMOTE_IP="$3"
53REMOTE_PORT="${4:-$DEFAULT_PORT}"
54REMOTE_DEST="${5:-/home/$REMOTE_USER/}"
55RSYNC_OPTIONS="${RSYNC_OPTIONS:-$DEFAULT_RSYNC_OPTIONS}"
56SCP_OPTIONS="${SCP_OPTIONS:-$DEFAULT_SCP_OPTIONS}"
57
58# Fungsi untuk validasi input yang lebih baik
59validate_inputs() {
60 # Validasi source path
61 if [ ! -e "$SOURCE_PATH" ]; then
62 log "ERROR" "File atau direktori '$SOURCE_PATH' tidak ditemukan!"
63 exit 1
64 fi
65
66 # Validasi username tidak kosong
67 if [ -z "$REMOTE_USER" ]; then
68 log "ERROR" "Username tidak boleh kosong!"
69 exit 1
70 fi
71
72 # Validasi port (harus numerik dan dalam range valid)
73 if ! [[ "$REMOTE_PORT" =~ ^[0-9]+$ ]] || [ "$REMOTE_PORT" -lt 1 ] || [ "$REMOTE_PORT" -gt 65535 ]; then
74 log "ERROR" "Port '$REMOTE_PORT' tidak valid! Harus antara 1-65535."
75 exit 1
76 fi
77
78 # Validasi format IP address atau hostname
79 if echo "$REMOTE_IP" | grep -qE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'; then
80 # Validasi IP address lebih detail
81 IFS='.' read -ra ADDR <<< "$REMOTE_IP"
82 for i in "${ADDR[@]}"; do
83 if [ "$i" -gt 255 ]; then
84 log "ERROR" "IP address '$REMOTE_IP' tidak valid!"
85 exit 1
86 fi
87 done
88 else
89 log "INFO" "'$REMOTE_IP' terdeteksi sebagai hostname/FQDN."
90 fi
91}
92
93# Fungsi untuk cek dependencies dan pilih method
94select_transfer_method() {
95 # Cek SSH (critical)
96 if ! command -v ssh &> /dev/null; then
97 log "ERROR" "SSH client tidak ditemukan! Install openssh-client."
98 exit 1
99 fi
100
101 # Cek rsync atau scp
102 if command -v rsync &> /dev/null; then
103 TRANSFER_METHOD="rsync"
104 log "SUCCESS" "RSYNC tersedia - menggunakan rsync"
105 elif command -v scp &> /dev/null; then
106 TRANSFER_METHOD="scp"
107 log "WARNING" "RSYNC tidak tersedia - fallback ke SCP"
108 else
109 log "ERROR" "Baik rsync maupun scp tidak tersedia!"
110 exit 1
111 fi
112
113 # Check optional commands
114 if ! command -v timeout &> /dev/null; then
115 log "WARNING" "Command 'timeout' tidak tersedia - menggunakan default timeout"
116 fi
117}
118
119# Fungsi untuk cek konektivitas SSH yang robust
120check_ssh_connectivity() {
121 log "INFO" "Memeriksa koneksi SSH ke $REMOTE_USER@$REMOTE_IP:$REMOTE_PORT..."
122
123 # Prepare SSH options
124 local ssh_opts="-p $REMOTE_PORT -o ConnectTimeout=5 -o BatchMode=yes -o LogLevel=ERROR"
125 local ssh_cmd="ssh $ssh_opts $REMOTE_USER@$REMOTE_IP exit"
126
127 # Test SSH connectivity dengan atau tanpa timeout
128 if command -v timeout &> /dev/null; then
129 ssh_cmd="timeout $SSH_TIMEOUT $ssh_cmd"
130 fi
131
132 if $ssh_cmd 2>/dev/null; then
133 log "SUCCESS" "SSH connection berhasil ke $REMOTE_IP:$REMOTE_PORT"
134 return 0
135 else
136 log "WARNING" "SSH key-based authentication gagal. Kemungkinan penyebab:"
137 echo " - SSH service tidak berjalan pada port $REMOTE_PORT"
138 echo " - SSH keys belum dikonfigurasi"
139 echo " - Authentication memerlukan password"
140 echo " - Server tidak dapat dijangkau"
141
142 read -p "Lanjutkan dengan password authentication? (y/n): " -n 1 -r
143 echo
144 if [[ ! $REPLY =~ ^[Yy]$ ]]; then
145 exit 1
146 fi
147 return 1
148 fi
149}
150
151# Fungsi untuk cek SSH key dengan lebih detail
152check_ssh_auth() {
153 local key_types=("rsa" "ed25519" "ecdsa" "dsa")
154 local found_keys=()
155
156 for key_type in "${key_types[@]}"; do
157 local key_file="$HOME/.ssh/id_$key_type"
158 if [ -f "$key_file" ]; then
159 found_keys+=("$key_type")
160 fi
161 done
162
163 if [ ${#found_keys[@]} -gt 0 ]; then
164 log "SUCCESS" "SSH keys ditemukan: ${found_keys[*]}"
165 else
166 log "INFO" "SSH key tidak ditemukan. Password authentication akan digunakan."
167 echo "Tips: Gunakan 'ssh-keygen -t ed25519' untuk membuat SSH key pair."
168 fi
169
170 # Test SSH agent
171 if ssh-add -l &>/dev/null; then
172 log "INFO" "SSH agent aktif dengan $(ssh-add -l | wc -l) key(s) loaded."
173 fi
174}
175
176# Fungsi untuk mendapatkan info file/direktori yang detail
177get_source_info() {
178 local size type file_count dir_count
179
180 if [ -f "$SOURCE_PATH" ]; then
181 type="File"
182 size=$(du -h "$SOURCE_PATH" | cut -f1)
183 file_count="1"
184 dir_count="0"
185 elif [ -d "$SOURCE_PATH" ]; then
186 type="Direktori"
187 size=$(du -sh "$SOURCE_PATH" 2>/dev/null | cut -f1)
188 file_count=$(find "$SOURCE_PATH" -type f 2>/dev/null | wc -l)
189 dir_count=$(find "$SOURCE_PATH" -type d 2>/dev/null | wc -l)
190 dir_count=$((dir_count - 1)) # Exclude source directory itself
191 else
192 type="Unknown"
193 size="N/A"
194 file_count="N/A"
195 dir_count="N/A"
196 fi
197
198 echo "Tipe: $type"
199 echo "Ukuran: $size"
200 echo "Files: $file_count"
201 if [ "$dir_count" != "0" ] && [ "$dir_count" != "N/A" ]; then
202 echo "Directories: $dir_count"
203 fi
204 echo "Path: $SOURCE_PATH"
205}
206
207# Fungsi untuk memilih mode transfer (adapted untuk SCP)
208select_transfer_mode() {
209 echo ""
210 echo "Method: $TRANSFER_METHOD"
211
212 if [ "$TRANSFER_METHOD" = "rsync" ]; then
213 echo "Pilih mode sinkronisasi RSYNC:"
214 echo "1. Normal - Copy file baru dan update yang berubah"
215 echo "2. Mirror - Sinkronisasi penuh (hapus file di tujuan yang tidak ada di source)"
216 echo "3. Backup - Normal + backup file yang akan dioverwrite"
217 echo "4. Checksum - Validasi dengan checksum (lebih akurat, lebih lambat)"
218 echo "5. Dry-run - Simulasi saja, tidak melakukan transfer"
219 echo "6. Custom - Masukkan opsi rsync sendiri"
220 echo ""
221 read -p "Pilih mode (1-6) [default: 1]: " mode_choice
222
223 case "$mode_choice" in
224 2)
225 RSYNC_OPTIONS="$RSYNC_OPTIONS --delete"
226 log "INFO" "Mode: Mirror (dengan --delete)"
227 ;;
228 3)
229 local backup_dir="backup_$(date +%Y%m%d_%H%M%S)"
230 RSYNC_OPTIONS="$RSYNC_OPTIONS --backup --backup-dir=$backup_dir"
231 log "INFO" "Mode: Backup (backup dir: $backup_dir)"
232 ;;
233 4)
234 RSYNC_OPTIONS="$RSYNC_OPTIONS --checksum"
235 log "INFO" "Mode: Checksum validation"
236 ;;
237 5)
238 RSYNC_OPTIONS="$RSYNC_OPTIONS --dry-run"
239 log "INFO" "Mode: Dry-run (simulasi)"
240 ;;
241 6)
242 echo "Opsi rsync saat ini: $RSYNC_OPTIONS"
243 read -p "Masukkan opsi tambahan: " custom_opts
244 if [ -n "$custom_opts" ]; then
245 RSYNC_OPTIONS="$RSYNC_OPTIONS $custom_opts"
246 fi
247 log "INFO" "Mode: Custom"
248 ;;
249 *)
250 log "INFO" "Mode: Normal"
251 ;;
252 esac
253 else
254 echo "Mode SCP: Normal copy (SCP tidak mendukung advanced sync modes)"
255 echo "Options yang tersedia untuk SCP:"
256 echo " -r: Recursive (untuk direktori)"
257 echo " -p: Preserve timestamps dan permissions"
258 echo " -v: Verbose output"
259 log "INFO" "Mode: SCP Normal"
260 fi
261}
262
263# Fungsi untuk bandwidth limiting (works untuk both rsync dan scp)
264set_bandwidth_limit() {
265 echo ""
266 read -p "Apakah ingin membatasi bandwidth? (y/n): " -n 1 -r
267 echo
268
269 if [[ $REPLY =~ ^[Yy]$ ]]; then
270 if [ "$TRANSFER_METHOD" = "rsync" ]; then
271 echo "Contoh: 1000 (KB/s), 5M (MB/s)"
272 read -p "Masukkan limit bandwidth: " bw_limit
273 if [ -n "$bw_limit" ]; then
274 RSYNC_OPTIONS="$RSYNC_OPTIONS --bwlimit=$bw_limit"
275 log "INFO" "RSYNC Bandwidth limit: $bw_limit"
276 fi
277 else
278 echo "Contoh: 1000 (KB/s)"
279 read -p "Masukkan limit bandwidth (KB/s): " bw_limit
280 if [ -n "$bw_limit" ]; then
281 SCP_OPTIONS="$SCP_OPTIONS -l $bw_limit"
282 log "INFO" "SCP Bandwidth limit: ${bw_limit} KB/s"
283 fi
284 fi
285 fi
286}
287
288# Fungsi untuk exclude patterns (hanya untuk rsync)
289create_exclude_patterns() {
290 if [ "$TRANSFER_METHOD" != "rsync" ]; then
291 log "INFO" "Exclude patterns hanya tersedia untuk RSYNC"
292 return
293 fi
294
295 local exclude_file="/tmp/rsync_exclude_$$"
296
297 echo ""
298 read -p "Apakah ada file/direktori yang ingin di-exclude? (y/n): " -n 1 -r
299 echo
300
301 if [[ $REPLY =~ ^[Yy]$ ]]; then
302 echo ""
303 echo "Pilih exclude method:"
304 echo "1. Manual input"
305 echo "2. Common patterns (logs, cache, tmp files)"
306 echo "3. Development patterns (node_modules, .git, build dirs)"
307 read -p "Pilih (1-3): " exclude_method
308
309 case "$exclude_method" in
310 2)
311 cat > "$exclude_file" << EOF
312*.log
313*.tmp
314*.cache
315*~
316.DS_Store
317Thumbs.db
318EOF
319 log "INFO" "Common exclude patterns applied"
320 ;;
321 3)
322 cat > "$exclude_file" << EOF
323node_modules/
324.git/
325.svn/
326.hg/
327__pycache__/
328*.pyc
329*.pyo
330.pytest_cache/
331target/
332build/
333dist/
334.gradle/
335.idea/
336.vscode/
337*.swp
338*.swo
339EOF
340 log "INFO" "Development exclude patterns applied"
341 ;;
342 *)
343 echo "Masukkan pattern yang ingin di-exclude (satu per baris, akhiri dengan CTRL+D):"
344 echo "Contoh: *.log, node_modules/, .git/, __pycache__/"
345 cat > "$exclude_file"
346 ;;
347 esac
348
349 if [ -s "$exclude_file" ]; then
350 RSYNC_OPTIONS="$RSYNC_OPTIONS --exclude-from=$exclude_file"
351 echo "✓ Exclude patterns tersimpan."
352 echo "Preview exclude patterns:"
353 cat "$exclude_file" | sed 's/^/ - /'
354 fi
355 fi
356}
357
358# Fungsi untuk melakukan transfer dengan fallback
359perform_transfer() {
360 local source="$1"
361 local destination="$2"
362 local start_time=$(date +%s)
363
364 echo ""
365 log "INFO" "Memulai transfer menggunakan $TRANSFER_METHOD..."
366 echo "Dari: $source"
367 echo "Ke: $destination"
368 echo "Port: $REMOTE_PORT"
369
370 case "$TRANSFER_METHOD" in
371 "rsync")
372 # Tambahkan trailing slash untuk direktori (rsync behavior)
373 if [ -d "$source" ]; then
374 source="${source%/}/"
375 fi
376
377 echo "Options: $RSYNC_OPTIONS"
378 echo ""
379
380 # Build rsync command
381 local rsync_cmd
382 if [ "$REMOTE_PORT" != "22" ]; then
383 rsync_cmd="rsync $RSYNC_OPTIONS -e \"ssh -p $REMOTE_PORT\" \"$source\" \"$destination\""
384 else
385 rsync_cmd="rsync $RSYNC_OPTIONS \"$source\" \"$destination\""
386 fi
387
388 if eval $rsync_cmd; then
389 local end_time=$(date +%s)
390 local duration=$((end_time - start_time))
391
392 echo ""
393 log "SUCCESS" "RSYNC transfer berhasil!"
394 echo "Waktu transfer: ${duration} detik"
395
396 # Cleanup exclude file jika ada
397 if [[ "$RSYNC_OPTIONS" == *"--exclude-from"* ]]; then
398 rm -f /tmp/rsync_exclude_$$
399 fi
400
401 return 0
402 else
403 local exit_code=$?
404 log "ERROR" "RSYNC transfer gagal dengan exit code: $exit_code"
405 return $exit_code
406 fi
407 ;;
408 "scp")
409 echo "Options: $SCP_OPTIONS"
410 echo ""
411
412 # Build SCP command
413 local scp_cmd
414 if [ "$REMOTE_PORT" != "22" ]; then
415 SCP_OPTIONS="$SCP_OPTIONS -P $REMOTE_PORT"
416 fi
417
418 scp_cmd="scp $SCP_OPTIONS \"$source\" \"$destination\""
419
420 if eval $scp_cmd; then
421 local end_time=$(date +%s)
422 local duration=$((end_time - start_time))
423
424 echo ""
425 log "SUCCESS" "SCP transfer berhasil!"
426 echo "Waktu transfer: ${duration} detik"
427 return 0
428 else
429 local exit_code=$?
430 log "ERROR" "SCP transfer gagal dengan exit code: $exit_code"
431 return $exit_code
432 fi
433 ;;
434 esac
435}
436
437# Fungsi untuk menampilkan tips optimasi
438suggest_optimization() {
439 echo ""
440 if [ "$TRANSFER_METHOD" = "rsync" ]; then
441 echo "💡 Tips RSYNC Optimization:"
442 echo "📁 File besar: --partial --append-verify --inplace"
443 echo "🌐 Network: --compress-level=6 --bwlimit=RATE"
444 echo "💾 Backup: --backup --backup-dir=backup_\$(date +%Y%m%d)"
445 echo "🔍 Validasi: --checksum (akurat) atau --size-only (cepat)"
446 echo "📊 Monitoring: --progress --stats -v"
447 echo "🚀 Performance: --whole-file (LAN) atau --no-whole-file (WAN)"
448 else
449 echo "💡 Tips SCP Optimization:"
450 echo "📁 Compression: -C (enable compression)"
451 echo "🌐 Network: -l LIMIT (bandwidth limit)"
452 echo "🔐 Security: -o (SSH options)"
453 echo "📊 Monitoring: -v (verbose output)"
454 fi
455}
456
457# Fungsi untuk menampilkan transfer summary
458show_transfer_summary() {
459 echo ""
460 echo "=========================================="
461 echo "RINGKASAN TRANSFER ($TRANSFER_METHOD)"
462 echo "=========================================="
463 echo "Source: $SOURCE_PATH"
464 echo "Destination: $DESTINATION"
465 echo "Port: $REMOTE_PORT"
466 echo "Method: $TRANSFER_METHOD"
467 if [ "$TRANSFER_METHOD" = "rsync" ]; then
468 echo "Options: $RSYNC_OPTIONS"
469 else
470 echo "Options: $SCP_OPTIONS"
471 fi
472 echo "=========================================="
473}
474
475# Main execution
476main() {
477 echo "=========================================="
478 echo "Remote Copy (RSYNC/SCP Hybrid)"
479 echo "=========================================="
480
481 # Select transfer method
482 select_transfer_method
483
484 # Validasi input
485 validate_inputs
486
487 # Tampilkan informasi transfer
488 echo ""
489 echo "Informasi Transfer:"
490 echo "-------------------"
491 get_source_info
492 echo "Tujuan: $REMOTE_USER@$REMOTE_IP:$REMOTE_DEST"
493 echo "Port: $REMOTE_PORT"
494 echo "Method: $TRANSFER_METHOD"
495
496 # Cek konektivitas SSH
497 check_ssh_connectivity
498
499 # Cek SSH authentication
500 check_ssh_auth
501
502 # Pilih mode transfer
503 select_transfer_mode
504
505 # Set bandwidth limit
506 set_bandwidth_limit
507
508 # Tanyakan exclude patterns (hanya untuk rsync)
509 create_exclude_patterns
510
511 # Tampilkan tips optimasi
512 suggest_optimization
513
514 # Buat destination path
515 DESTINATION="$REMOTE_USER@$REMOTE_IP:$REMOTE_DEST"
516
517 # Tampilkan summary
518 show_transfer_summary
519
520 # Konfirmasi sebelum transfer
521 echo ""
522 read -p "Mulai transfer? (y/n): " -n 1 -r
523 echo
524 if [[ ! $REPLY =~ ^[Yy]$ ]]; then
525 log "INFO" "Transfer dibatalkan oleh user."
526 exit 0
527 fi
528
529 # Lakukan transfer
530 if perform_transfer "$SOURCE_PATH" "$DESTINATION"; then
531 echo ""
532 echo "=========================================="
533 log "SUCCESS" "File/direktori berhasil ditransfer!"
534 echo "Lokasi: $DESTINATION"
535 echo "=========================================="
536 exit 0
537 else
538 echo ""
539 echo "=========================================="
540 log "ERROR" "Gagal melakukan transfer."
541 echo "=========================================="
542 exit 1
543 fi
544}
545
546# Handle Ctrl+C gracefully
547trap 'echo ""; log "INFO" "Transfer dibatalkan oleh user."; exit 1' INT
548
549# Jalankan main function
550main