#!/bin/bash



# ==========================================

# HOSTNODE RECOMMENDER (VCPU CONTEXT VERSION WITH DETAILED WARNING)

# ==========================================



LOGFILE="/var/log/hostnode-health.log"

TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')



TMP="/tmp/hostnode_recommend.txt"

> $TMP



# --- FUNGSI UNTUK MEMBACA INPUT DARI PENGGUNA ---

read_input() {

    local prompt="$1"

    local default_val="$2"

    local var_name="$3"

    

    while true; do

        read -p "$prompt ($var_name, default: $default_val): " input

        input=${input:-$default_val}

        

        if [[ $input =~ ^[0-9]+$ ]]; then

            eval "$var_name=$input"

            break

        else

            echo "Input harus berupa angka positif. Silakan coba lagi."

        fi

    done

}



# --- MEMINTA KEBUTUHAN SUMBER DAYA DARI PENGGUNA ---

echo "============================================="

echo " Input Kebutuhan Sumber Daya "

echo "============================================="



REQUIRED_DISK_GB=40 # Nilai Default (GB)

read_input "Butuh berapa disk (GB)" 40 REQUIRED_DISK_GB



REQUIRED_MEMORY_GB=8 # Nilai Default (GB)

read_input "Butuh berapa memory (GB)" 8 REQUIRED_MEMORY_GB



# DIUBAH: Menggunakan VCPU

REQUIRED_VCPU_CORE=8 # Nilai Default

read_input "Butuh berapa VCPU core" 8 REQUIRED_VCPU_CORE



echo "---------------------------------------------"

echo " Kebutuhan: Disk=${REQUIRED_DISK_GB}GB, RAM=${REQUIRED_MEMORY_GB}GB, VCPU=${REQUIRED_VCPU_CORE} core"

echo "---------------------------------------------"



echo "============================================="

echo " Hostnode Health Scan - $TIMESTAMP"

echo "============================================="

echo ""



# --- FUNGSI KONVERSI FLOAT KE INTEGER (Digunakan secara lokal) ---

to_int() {

    local val="$1"

    local default_val="$2"

    # Cek apakah nilai float adalah angka sebelum dikonversi

    if [[ "$val" =~ ^[0-9]*\.[0-9]+$ || "$val" =~ ^[0-9]+$ ]]; then

        # Pembulatan ke bawah dengan printf

        printf "%.0f\n" "$val" 2>/dev/null || echo "$default_val"

    else

        echo "$default_val"

    fi

}





for i in {1..36}; do



NODE=$(printf "%02d" $i)

HOST="ssdvps$NODE.idcloudhosting.com"



echo "→Checking $HOST"



# Jalankan SSH Command menggunakan HERE DOCUMENT (paling robust)

DATA=$(ssh -p8286 -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@$HOST bash << EOF 2>/dev/null

# FORCE LOCALE to POSIX to ensure LVM output uses dot (.) as decimal separator

export LC_ALL=POSIX



# --- DISK LVM (VFree - Raw Value) ---

VFREE_VALUES=\$(vgs --units g --noheadings --separator ' ' -o vg_free --nosuffix --unbuffered 2>/dev/null | tr ' ' '\n')



TOTAL_VFREE_GB=0

for val in \$VFREE_VALUES; do

    if echo "\$val" | grep -Eq '^[0-9]+\.?[0-9]*\$'; then

        TOTAL_VFREE_GB=\$(echo "\$TOTAL_VFREE_GB + \$val" | bc -l)

    fi

done



DISK_FREE_GB_FLOAT=\$(echo "scale=2; \${TOTAL_VFREE_GB:-0}" | bc -l)





# --- RAM (Menggunakan MemAvailable) ---

MEMAVAILABLE_KB=\$(grep MemAvailable /proc/meminfo | awk '{print \$2}')

MEMTOTAL_KB=\$(grep MemTotal /proc/meminfo | awk '{print \$2}')

MEMAVAILABLE_GB_FLOAT=\$(echo "scale=2; \$MEMAVAILABLE_KB / 1024 / 1024" | bc -l)

MEMTOTAL_GB_FLOAT=\$(echo "scale=2; \$MEMTOTAL_KB / 1024 / 1024" | bc -l)





# --- CPU ---

CPU_CORES=\$(grep -c '^processor' /proc/cpuinfo)

LOAD=\$(awk '{print \$1}' /proc/loadavg)



# --- SWAP ---

SWAPTOTAL_KB=\$(grep SwapTotal /proc/meminfo | awk '{print \$2}')

SWAPFREE_KB=\$(grep SwapFree /proc/meminfo | awk '{print \$2}')

SWAPUSED_MB=\$(((SWAPTOTAL_KB - SWAPFREE_KB) / 1024))

SWAPTOTAL_MB=\$((SWAPTOTAL_KB / 1024))



# --- LOGGING ---

IO_ERR=\$(grep -ic 'I/O error' /var/log/messages 2>/dev/null || echo 0)

OOM=\$(grep -ic 'Out of memory' /var/log/messages 2>/dev/null || echo 0)



# Keluarkan data

echo "\$DISK_FREE_GB_FLOAT \$MEMAVAILABLE_GB_FLOAT \$MEMTOTAL_GB_FLOAT \$CPU_CORES \$LOAD \$SWAPUSED_MB \$SWAPTOTAL_MB \$IO_ERR \$OOM"

EOF

)



# IF SSH FAILED

if [ -z "$DATA" ]; then

echo "   →STATUS: DANGER (Unable to connect)"

echo "$HOST 0 DANGER INELIGIBLE" >> $TMP

echo ""

continue

fi



# Baca data baru 

read DISK_FREE_GB_FLOAT RAM_AVAILABLE_GB_FLOAT RAM_TOTAL_GB_FLOAT CPU_CORES LOAD SWAP_USED SWAP_TOTAL IO_ERR OOM <<< "$DATA"



# --- KONVERSI FLOAT KE INTEGER MENGGUNAKAN to_int() ---

DISK_FREE_GB=$(to_int "$DISK_FREE_GB_FLOAT" 0)

RAM_AVAILABLE_GB=$(to_int "$RAM_AVAILABLE_GB_FLOAT" 0)

RAM_TOTAL_GB=$(to_int "$RAM_TOTAL_GB_FLOAT" 1)





# DEFAULT PROTECTION untuk variabel lain

[ -z "$CPU_CORES" ] && CPU_CORES=1

[ -z "$LOAD" ] && LOAD=0

[ -z "$SWAP_USED" ] && SWAP_USED=0

[ -z "$SWAP_TOTAL" ] && SWAP_TOTAL=1

[ -z "$IO_ERR" ] && IO_ERR=0

[ -z "$OOM" ] && OOM=0



# Perhitungan Persen dan Load

RAM_PERCENT=$(( RAM_AVAILABLE_GB * 100 / RAM_TOTAL_GB ))

LOAD_INT=${LOAD%%.*}



SCORE=0



# --- Cek Kelayakan (ELIGIBILITY) ---

ELIGIBLE="ELIGIBLE"

REASON=""



# 1. Cek Disk

if [ $DISK_FREE_GB -lt $REQUIRED_DISK_GB ]; then

    ELIGIBLE="INELIGIBLE"

    REASON="Disk_Free"

fi



# 2. Cek RAM

if [ $RAM_AVAILABLE_GB -lt $REQUIRED_MEMORY_GB ]; then

    if [ "$ELIGIBLE" == "ELIGIBLE" ]; then

        ELIGIBLE="INELIGIBLE"

        REASON="RAM_Free"

    else

        REASON="$REASON, RAM_Free"

    fi

fi



# 3. Cek VCPU (High Load)

# Jika Load melebihi Physical Cores, dianggap INELIGIBLE karena Overload

if [ $LOAD_INT -gt $CPU_CORES ]; then

    if [ "$ELIGIBLE" == "ELIGIBLE" ]; then

        ELIGIBLE="INELIGIBLE"

        REASON="High_Load"

    else

        REASON="$REASON, High_Load"

    fi

fi





# Jika tidak layak, beri Skor 0

if [ "$ELIGIBLE" == "INELIGIBLE" ]; then

    SCORE=0

else

    # --- SCORING HANYA JIKA LAYAK (ELIGIBLE) ---

    

    # 1. DISK SCORING 

    if [ $DISK_FREE_GB -gt $(( REQUIRED_DISK_GB * 5 )) ]; then SCORE=$(( SCORE + 10 ))

    elif [ $DISK_FREE_GB -gt $(( REQUIRED_DISK_GB * 2 )) ]; then SCORE=$(( SCORE + 5 ))

    fi



    # 2. RAM SCORING

    if [ $RAM_PERCENT -gt 60 ]; then SCORE=$(( SCORE + 30 ))

    elif [ $RAM_PERCENT -gt 40 ]; then SCORE=$(( SCORE + 20 ))

    elif [ $RAM_PERCENT -gt 20 ]; then SCORE=$(( SCORE + 10 ))

    fi



    # 3. LOAD (VCPU) SCORING 

    MAX_LOAD_FOR_OK=$(( CPU_CORES / 4 ))

    

    if [ $LOAD_INT -lt $MAX_LOAD_FOR_OK ]; then SCORE=$(( SCORE + 25 ))

    elif [ $LOAD_INT -lt $(( CPU_CORES / 2 )) ]; then SCORE=$(( SCORE + 15 ))

    elif [ $LOAD_INT -lt $(( CPU_CORES )) ]; then SCORE=$(( SCORE + 10 ))

    fi



    # 4. SWAP SCORING

    if [ "$SWAP_TOTAL" -eq 0 ] 2>/dev/null; then

    SCORE=$(( SCORE + 10 ))

    elif [ "$SWAP_USED" -lt $(( SWAP_TOTAL / 2 )) ] 2>/dev/null; then

    SCORE=$(( SCORE + 5 ))

    fi

    

    # 5. IO/OOM (Jika bersih)

    if [ "$IO_ERR" -eq 0 ] 2>/dev/null; then SCORE=$(( SCORE + 5 )); fi

    if [ "$OOM" -eq 0 ] 2>/dev/null; then SCORE=$(( SCORE + 5 )); fi



fi # Akhir dari SCORING



# --- STATUS ---

STATUS=""

WARNING_REASONS=""



if [ $SCORE -ge 75 ]; then

    STATUS="OK"

elif [ $SCORE -ge 40 ]; then

    STATUS="WARNING"

    

    # --- LOGIKA WARNING (Hanya jika ELIGIBLE dan Score < 75) ---

    

    # Warning 1: Disk Low (Jika Disk di atas kebutuhan, tapi kurang dari 2x kebutuhan)

    if [ $DISK_FREE_GB -gt $REQUIRED_DISK_GB ] && [ $DISK_FREE_GB -lt $(( REQUIRED_DISK_GB * 2 )) ]; then

        WARNING_REASONS="Disk_Low_Space"

    fi



    # Warning 2: RAM Low (Jika RAM di atas kebutuhan, tapi RAM Avail Percent < 40%)

    if [ $RAM_AVAILABLE_GB -gt $REQUIRED_MEMORY_GB ] && [ $RAM_PERCENT -lt 40 ]; then

        if [ -n "$WARNING_REASONS" ]; then

            WARNING_REASONS="$WARNING_REASONS, RAM_Low_Avail"

        else

            WARNING_REASONS="RAM_Low_Avail"

        fi

    fi



    # Warning 3: CPU/Load High (Jika Load di atas 50% dari Physical Cores, tapi di bawah 100%)

    if [ $LOAD_INT -gt $(( CPU_CORES / 2 )) ] && [ $LOAD_INT -le $CPU_CORES ]; then

        if [ -n "$WARNING_REASONS" ]; then

            WARNING_REASONS="$WARNING_REASONS, CPU_Load_High"

        else

            WARNING_REASONS="CPU_Load_High"

        fi

    fi



    if [ -z "$WARNING_REASONS" ]; then

        # Jika warning, tapi tidak ada reason yang terdeteksi (mungkin karena swap/io)

        WARNING_REASONS="Score_Low"

    fi

    REASON="$WARNING_REASONS"



else

    STATUS="DANGER"

    # REASON DANGER/INELIGIBLE sudah diisi di atas

fi





# Hitung kembali Avail Cores untuk tampilan, tetapi ingat ini adalah PHISICAL CORES

LOAD_INT=${LOAD%%.*} # Pastikan Load_INT sudah integer

AVAILABLE_CORES=$(( CPU_CORES - LOAD_INT ))



echo "   →Disk Free: $DISK_FREE_GB GB"

echo "   →RAM Avail: $RAM_AVAILABLE_GB GB"

echo "   →CPU Cores: $CPU_CORES"

echo "   →Load: $LOAD (Avail Cores: $AVAILABLE_CORES)"

echo "   →Swap: $SWAP_USED / $SWAP_TOTAL MB"

echo "   →IO Error: $IO_ERR"

echo "   →OOM: $OOM"

echo "   →SCORE: $SCORE"

echo "   →STATUS: $STATUS ($ELIGIBLE)"



# Tampilkan alasan jika STATUS bukan OK

if [ "$STATUS" != "OK" ]; then

    echo "   →REASON: $REASON"

fi

echo ""



echo "$HOST $SCORE $STATUS $ELIGIBLE" >> $TMP



done



echo "============================================="

echo "RANKING NODE BERDASARKAN KEBUTUHAN"

echo "============================================="



ELIGIBLE_NODES=$(grep "ELIGIBLE" $TMP | sort -k2 -nr)



if [ -z "$ELIGIBLE_NODES" ]; then

    echo "❌ TIDAK ADA HOSTNODE YANG LAYAK (ELIGIBLE) UNTUK KEBUTUHAN INI."

    echo "Silakan coba dengan kebutuhan sumber daya yang lebih rendah."

else

    echo "✅ HOSTNODE YANG LAYAK (ELIGIBLE):"

    echo "$ELIGIBLE_NODES"

    echo ""



    BEST=$(echo "$ELIGIBLE_NODES" | head -n 1 | awk '{print $1}')

    BSCORE=$(echo "$ELIGIBLE_NODES" | head -n 1 | awk '{print $2}')

    

    echo " BEST HOSTNODE: **$BEST** (Score: **$BSCORE**)"

fi

echo ""
