#!/usr/bin/env bash
set -euo pipefail

R="\e[0m" B="\e[1m" D="\e[2m"
RED="\e[38;5;203m" GRN="\e[38;5;114m" YEL="\e[38;5;222m"
CYN="\e[38;5;116m" GRY="\e[38;5;242m" WHT="\e[38;5;255m"
log()     { echo -e "  ${CYN}${B}[R3TR0.SH]${R}  ${WHT}$1${R}"; }
log_err() { echo -e "  ${RED}${B}[R3TR0.SH]${R}  ${WHT}$1${R}"; }

if [[ $EUID -ne 0 ]]; then
  echo ""
  log_err "This script must be run as root"
  echo ""
  exit 1
fi

cd ~
export NEWT_COLORS='
  root=,black
  window=black,black
  border=cyan,black
  title=brightcyan,black
  button=black,cyan
  actbutton=black,brightcyan
  listbox=white,black
  actlistbox=black,cyan
  actsellistbox=black,brightcyan
  textbox=white,black
  acttextbox=black,cyan
  entry=white,black
  label=brightcyan,black
  checkbox=white,black
  actcheckbox=black,cyan
'

whiptail --title "R3TR0.SH" --msgbox "The bash toolkit for power users.\nWork in progress — may contain bugs." 10 100

while true; do

  BASHTYPE=$(whiptail --title "R3TR0.SH" --menu "What do you need?" 18 100 10 \
    "tool" "" \
    "installer" "" \
    3>&1 1>&2 2>&3) || { echo ""; log "Bye."; echo ""; exit 0; }

  if [[ "$BASHTYPE" == "tool" ]]; then

    info() {
      R='\e[0m'  B='\e[1m'  D='\e[2m'
RED='\e[38;5;203m'  GRN='\e[38;5;114m'  YEL='\e[38;5;222m'
BLU='\e[38;5;111m'  CYN='\e[38;5;116m'  MAG='\e[38;5;183m'
GRY='\e[38;5;242m'  WHT='\e[38;5;255m'
OK="${GRN}●${R}"  WARN="${YEL}●${R}"  FAIL="${RED}●${R}"

cols=$(tput cols 2>/dev/null || echo 60)
line() { printf "${GRY}"; printf '%.0s─' $(seq 1 "$cols"); printf "${R}\n"; }

section() {
    echo ""
    printf "  ${1}${B} %s${R}\n" "$2"
    line
}

kv() {
    local c="${3:-$WHT}"
    printf "  ${GRY}%-20s${R} ${c}%s${R}\n" "$1" "$2"
}

bar() {
    local pct=$(( $1 * 100 / $2 ))
    local filled=$(( pct * 30 / 100 ))
    local empty=$(( 30 - filled ))
    local color="$4"
    [[ $pct -gt 80 ]] && color="$RED"
    [[ $pct -gt 60 && $pct -le 80 ]] && color="$YEL"
    printf "  ${GRY}%-20s${R} ${color}%s${D}%s${R} ${WHT}%3d%%${R}  ${GRY}(%s/%s %s)${R}\n" \
        "$1" "$(printf '█%.0s' $(seq 1 $filled))" \
        "$(printf '░%.0s' $(seq 1 $empty))" \
        "$pct" "$1" "$2" "$3"
}

mem_bar() {
    local pct=0
    [[ $3 -gt 0 ]] && pct=$(( $2 * 100 / $3 ))
    local filled=$(( pct * 30 / 100 ))
    local empty=$(( 30 - filled ))
    local color="$4"
    [[ $pct -gt 80 ]] && color="$RED"
    [[ $pct -gt 60 && $pct -le 80 ]] && color="$YEL"
    printf "  ${GRY}%-20s${R} ${color}%s${D}%s${R} ${WHT}%3d%%${R}  ${GRY}(%s / %s MB)${R}\n" \
        "$1" "$(printf '█%.0s' $(seq 1 $filled 2>/dev/null || true))" \
        "$(printf '░%.0s' $(seq 1 $empty 2>/dev/null || true))" \
        "$pct" "$2" "$3"
}

disk_bar() {
    local pct=$2
    local filled=$(( pct * 30 / 100 ))
    local empty=$(( 30 - filled ))
    local color="$GRN"
    [[ $pct -gt 80 ]] && color="$RED"
    [[ $pct -gt 60 && $pct -le 80 ]] && color="$YEL"
    printf "  ${GRY}%-20s${R} ${color}%s${D}%s${R} ${WHT}%3d%%${R}  ${GRY}(%s / %s)${R}\n" \
        "$1" "$(printf '█%.0s' $(seq 1 $filled 2>/dev/null || true))" \
        "$(printf '░%.0s' $(seq 1 $empty 2>/dev/null || true))" \
        "$pct" "$3" "$4"
}

clear
echo ""
printf "  ${CYN}${B}╔══════════════════════════════════════════╗${R}\n"
printf "  ${CYN}${B}║      ⚙  System Information Report       ║${R}\n"
printf "  ${CYN}${B}╚══════════════════════════════════════════╝${R}\n"
printf "  ${GRY}Generated: $(date '+%Y-%m-%d %H:%M:%S')${R}\n"

section "$BLU" "󰈀  Network"
kv "Hostname"       "$(hostname)" "$CYN"
kv "Primary IP"     "$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' || echo 'n/a')" "$WHT"
kv "All IPs"        "$(hostname -I 2>/dev/null || echo 'n/a')" "$GRY"
kv "Default Gateway" "$(ip route 2>/dev/null | awk '/default/{print $3}' || echo 'n/a')"

section "$MAG" "  OS & Kernel"
kv "Distribution"   "$(lsb_release -ds 2>/dev/null || cat /etc/os-release 2>/dev/null | grep PRETTY | cut -d= -f2 | tr -d '"' || echo 'n/a')" "$MAG"
kv "Kernel"         "$(uname -r)"
kv "Architecture"   "$(uname -m)"

section "$GRN" "󰅐  Uptime"
kv "Uptime"         "$(uptime -p 2>/dev/null || echo 'n/a')" "$GRN"
kv "Last Boot"      "$(who -b 2>/dev/null | awk '{print $3" "$4}' || echo 'n/a')"
kv "Logged-in Users" "$(who 2>/dev/null | wc -l || echo '0')"

section "$YEL" "  CPU"
CPU_MODEL=$(lscpu 2>/dev/null | awk -F: '/Model name/{gsub(/^[ \t]+/,"",$2); print $2}' || echo 'n/a')
CPU_CORES=$(nproc 2>/dev/null || echo '?')
LOAD_1=$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo '0')
LOAD_5=$(awk '{print $2}' /proc/loadavg 2>/dev/null || echo '0')
LOAD_15=$(awk '{print $3}' /proc/loadavg 2>/dev/null || echo '0')

kv "Model"          "$CPU_MODEL" "$YEL"
kv "Cores / Threads" "${CPU_CORES}"
printf "  ${GRY}%-20s${R} ${WHT}%s${R}  ${GRY}│${R}  ${WHT}%s${R}  ${GRY}│${R}  ${WHT}%s${R}  ${GRY}(1m / 5m / 15m)${R}\n" \
    "Load Average" "$LOAD_1" "$LOAD_5" "$LOAD_15"

section "$CYN" "󰍛  Memory"
TOTAL_MEM=$(awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo)
AVAIL_MEM=$(awk '/MemAvailable/{printf "%d", $2/1024}' /proc/meminfo)
USED_MEM=$(( TOTAL_MEM - AVAIL_MEM ))
SWAP_TOTAL=$(awk '/SwapTotal/{printf "%d", $2/1024}' /proc/meminfo)
SWAP_FREE=$(awk '/SwapFree/{printf "%d", $2/1024}' /proc/meminfo)
SWAP_USED=$(( SWAP_TOTAL - SWAP_FREE ))

mem_bar "RAM" "$USED_MEM" "$TOTAL_MEM" "$CYN"
mem_bar "Swap" "$SWAP_USED" "$SWAP_TOTAL" "$BLU"

section "$GRN" "󰋊  Disk"
while read -r mount used total pct; do
    pct_num=${pct%\%}
    disk_bar "$mount" "$pct_num" "$used" "$total"
done < <(df -h --output=target,used,size,pcent -x tmpfs -x devtmpfs 2>/dev/null | tail -n +2 | awk '{print $1, $2, $3, $4}')

section "$RED" "  Top Processes"
printf "  ${D}%-8s %-20s %6s  %6s${R}\n" "PID" "COMMAND" "CPU%" "MEM%"
line
ps -eo pid,comm,%cpu,%mem --sort=-%cpu 2>/dev/null | head -n 6 | tail -n 5 | while read -r pid cmd cpu mem; do
    printf "  ${GRY}%-8s${R} ${WHT}%-20s${R} ${YEL}%6s${R}  ${CYN}%6s${R}\n" "$pid" "$cmd" "$cpu" "$mem"
done

section "$GRN" "󰒓  Services"
FAILED=$(systemctl --failed --no-legend 2>/dev/null | wc -l || echo 0)
if [[ "$FAILED" -eq 0 ]]; then
    printf "  ${OK}  ${GRN}All services running${R}\n"
else
    printf "  ${FAIL}  ${RED}${FAILED} failed service(s):${R}\n"
    systemctl --failed --no-pager --no-legend 2>/dev/null | while read -r svc _rest; do
        printf "     ${RED}└─ %s${R}\n" "$svc"
    done
fi

if command -v ss >/dev/null 2>&1; then
    section "$BLU" "󰛳  Listening Ports"
    printf "  ${D}%-8s %-8s %s${R}\n" "PROTO" "PORT" "ADDRESS"
    line
    ss -tuln 2>/dev/null | awk '/^tcp.*LISTEN/{split($5,a,":"); printf "  %-8s %-8s %s\n", "tcp", a[length(a)], $5}' | sort -t' ' -k2 -n | uniq | head -15 | while read -r l; do
        printf "${WHT}%s${R}\n" "$l"
    done
fi

if command -v sensors >/dev/null 2>&1; then
    section "$RED" "󰔏  Temperature"
    sensors 2>/dev/null | grep -E 'Package id|Core|temp' | while read -r l; do
        printf "  ${WHT}%s${R}\n" "$l"
    done
fi

echo ""
line
printf "  ${GRY}Report complete. $(date '+%H:%M:%S')${R}\n"
echo ""
    }

    cpu_stress() {
      R='\e[0m' B='\e[1m' D='\e[2m'
RED='\e[38;5;203m' GRN='\e[38;5;114m' YEL='\e[38;5;222m'
BLU='\e[38;5;111m' CYN='\e[38;5;116m' GRY='\e[38;5;242m' WHT='\e[38;5;255m'

_cols=$(tput cols 2>/dev/null || echo 60)
cols=$(( _cols < 60 ? _cols : 60 ))
line() { printf "  ${GRY}"; printf '%.0s-' $(seq 1 $(( cols - 4 ))); printf "${R}\n"; }
kv() { printf "  ${GRY}%-14s${R} ${WHT}%s${R}\n" "$1" "$2"; }

make_bar() {
    local label="$1" used="$2" total="$3" color="$4" w=25
    local pct=0
    [[ $total -gt 0 ]] && pct=$(( used * 100 / total ))
    local filled=$(( pct * w / 100 ))
    local empty=$(( w - filled ))
    [[ $pct -gt 80 ]] && color="$RED"
    [[ $pct -gt 60 && $pct -le 80 ]] && color="$YEL"
    local bar_f="" bar_e=""
    for ((j=0; j<filled; j++)); do bar_f+="#"; done
    for ((j=0; j<empty; j++)); do bar_e+="-"; done
    printf "  ${GRY}%-14s${R} [${color}%s${GRY}%s${R}] ${WHT}%3d%%${R}  ${GRY}(%s / %s MB)${R}\n" \
        "$label" "$bar_f" "$bar_e" "$pct" "$used" "$total"
}

read_mem() {
    MEM_TOTAL=$(awk '/MemTotal/{printf "%d",$2/1024}' /proc/meminfo)
    MEM_AVAIL=$(awk '/MemAvailable/{printf "%d",$2/1024}' /proc/meminfo)
    MEM_USED=$(( MEM_TOTAL - MEM_AVAIL ))
    MEM_BUFCACHE=$(awk '/Buffers/{b=$2} /^Cached/{c=$2} END{printf "%d",(b+c)/1024}' /proc/meminfo)
    SWAP_TOTAL=$(awk '/SwapTotal/{printf "%d",$2/1024}' /proc/meminfo)
    SWAP_FREE=$(awk '/SwapFree/{printf "%d",$2/1024}' /proc/meminfo)
    SWAP_USED=$(( SWAP_TOTAL - SWAP_FREE ))
}

print_mem() {
    make_bar "RAM" "$MEM_USED" "$MEM_TOTAL" "$CYN"
    make_bar "Buff/Cache" "$MEM_BUFCACHE" "$MEM_TOTAL" "$BLU"
    [[ $SWAP_TOTAL -gt 0 ]] && make_bar "Swap" "$SWAP_USED" "$SWAP_TOTAL" "$YEL"
}

drop_caches() {
    sync
    if [[ $(id -u) -eq 0 ]]; then
        echo 3 > /proc/sys/vm/drop_caches
    elif command -v sudo >/dev/null 2>&1; then
        echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null
    else
        su -c 'echo 3 > /proc/sys/vm/drop_caches'
    fi
}

clear
echo ""
printf "  ${CYN}${B}== Drop Caches ==${R}\n\n"

printf "  ${BLU}${B}Before${R}\n"
line
read_mem
print_mem
BEFORE_CACHE=$MEM_BUFCACHE

echo ""
printf "  ${GRY}Syncing & dropping caches"
for i in 1 2 3; do sleep 0.2; printf "."; done
printf "${R}\n\n"

drop_caches
sleep 0.3

printf "  ${GRN}${B}After${R}\n"
line
read_mem
print_mem
AFTER_CACHE=$MEM_BUFCACHE

FREED=$(( BEFORE_CACHE - AFTER_CACHE ))
echo ""
line
if [[ $FREED -gt 0 ]]; then
    printf "  ${GRN}*${R}  ${WHT}Freed ${GRN}${B}%s MB${R}${WHT} of memory${R}\n" "$FREED"
else
    printf "  ${YEL}*${R}  ${WHT}No significant memory freed${R}\n"
fi
echo ""
    }

    f2bgui() {
      R='\e[0m' B='\e[1m' D='\e[2m'
RED='\e[38;5;203m' GRN='\e[38;5;114m' YEL='\e[38;5;222m'
BLU='\e[38;5;111m' CYN='\e[38;5;116m' GRY='\e[38;5;242m' WHT='\e[38;5;255m'

_cols=$(tput cols 2>/dev/null || echo 60)
cols=$(( _cols < 60 ? _cols : 60 ))
line() { printf "  ${GRY}"; printf '%.0s-' $(seq 1 $(( cols - 4 ))); printf "${R}\n"; }
kv() { printf "  ${GRY}%-20s${R} ${2:-$WHT}%s${R}\n" "$1" "$3"; }

bar() {
    local label="$1" used="$2" total="$3" color="$4" w="${5:-25}"
    local pct=0
    [[ $total -gt 0 ]] && pct=$(( used * 100 / total ))
    local filled=$(( pct * w / 100 ))
    local empty=$(( w - filled ))
    [[ $pct -gt 80 ]] && color="$RED"
    [[ $pct -gt 60 && $pct -le 80 ]] && color="$YEL"
    local bar_f="" bar_e=""
    for ((j=0; j<filled; j++)); do bar_f+="#"; done
    for ((j=0; j<empty; j++)); do bar_e+="-"; done
    printf "  ${GRY}%-20s${R} [${color}%s${GRY}%s${R}] ${WHT}%3d%%${R}\n" \
        "$label" "$bar_f" "$bar_e" "$pct"
}

while true; do
    clear
    echo ""
    printf "  ${RED}${B}== Fail2Ban Monitor ==${R}\n\n"

    IPS=$(sudo fail2ban-client status sshd 2>/dev/null | grep "Banned IP list:" | sed 's/.*Banned IP list://g')
    mapfile -t IP_LIST < <(echo "$IPS" | tr -s ' ' '\n' | sed '/^$/d')
    CURRENT=${#IP_LIST[@]}
    TOTAL=$(grep -c "Ban " /var/log/fail2ban.log 2>/dev/null || echo 0)

    printf "  ${RED}${B}%s${R} ${GRY}currently banned${R}    ${YEL}${B}%s${R} ${GRY}total bans${R}\n\n" "$CURRENT" "$TOTAL"

    if [[ $CURRENT -gt 0 ]]; then
        printf "  ${GRY}${D}%-5s %-17s %s${R}\n" "#" "IP" "Unban In"
        line

        for i in "${!IP_LIST[@]}"; do
            ip="${IP_LIST[$i]}"
            [[ -z "$ip" ]] && continue
            ban_ts=$(grep "$ip" /var/log/fail2ban.log 2>/dev/null | tail -1 | awk '{print $1 " " $2}' | xargs -I {} date -d {} +%s 2>/dev/null || echo 0)
            now=$(date +%s)
            left=$(( 3600 - (now - ban_ts) ))
            [[ $left -lt 0 ]] && left=0
            mins=$(( (left + 59) / 60 ))
            [[ $mins -eq 0 ]] && mins="<1"

            clr="$RED"
            [[ $left -lt 300 ]] && clr="$YEL"
            printf "  ${GRY}%-5s${R}${clr}%-17s${R} ${WHT}%s min${R}\n" "$((i+1))" "$ip" "$mins"
        done
    else
        printf "  ${GRN}* No banned IPs${R}\n"
    fi

    echo ""
    printf "  ${BLU}${B}== Server ==${R}\n"
    line

    cpu_load=$(awk '{printf "%s  %s  %s", $1, $2, $3}' /proc/loadavg)
    logged=$(who 2>/dev/null | awk '{print $1}' | sort -u)
    logged_count=$(echo "$logged" | sed '/^$/d' | wc -l)
    logged_list=$(echo "$logged" | sed '/^$/d' | tr '\n' ', ' | sed 's/,$//')

    MEM_TOTAL=$(awk '/MemTotal/{printf "%d",$2/1024}' /proc/meminfo)
    MEM_AVAIL=$(awk '/MemAvailable/{printf "%d",$2/1024}' /proc/meminfo)
    MEM_USED=$(( MEM_TOTAL - MEM_AVAIL ))
    DISK_USED=$(df / | awk 'NR==2{gsub(/%/,"",$5); print $5}')

    kv "Load Average" "$WHT" "$cpu_load"

    cpu_temp=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo "")
    if [[ -n "$cpu_temp" && "$cpu_temp" -gt 0 ]] 2>/dev/null; then
        cpu_temp_c=$(awk -v t="$cpu_temp" 'BEGIN{printf "%.1f", t/1000}')
        temp_clr="$GRN"
        [[ ${cpu_temp_c%.*} -gt 70 ]] && temp_clr="$RED"
        [[ ${cpu_temp_c%.*} -gt 50 && ${cpu_temp_c%.*} -le 70 ]] && temp_clr="$YEL"
        kv "CPU Temp" "$temp_clr" "${cpu_temp_c} C"
    fi

    bar "Memory" "$MEM_USED" "$MEM_TOTAL" "$CYN"
    bar "Disk /" "$DISK_USED" "100" "$BLU"
    kv "Users ($logged_count)" "$GRN" "$logged_list"

    echo ""
    line

    for i in $(seq 59 -1 0); do
        printf "\r  ${GRY}Refresh in ${WHT}%02d${GRY}s  |  ${CYN}%s${R}  " "$i" "$(date '+%H:%M:%S')"
        sleep 1
    done
done
    }

    cache_clear() {
      R='\e[0m' B='\e[1m' D='\e[2m'
RED='\e[38;5;203m' GRN='\e[38;5;114m' YEL='\e[38;5;222m'
BLU='\e[38;5;111m' CYN='\e[38;5;116m' GRY='\e[38;5;242m' WHT='\e[38;5;255m'

_cols=$(tput cols 2>/dev/null || echo 60)
cols=$(( _cols < 60 ? _cols : 60 ))
line() { printf "  ${GRY}"; printf '%.0s-' $(seq 1 $(( cols - 4 ))); printf "${R}\n"; }
kv() { printf "  ${GRY}%-14s${R} ${WHT}%s${R}\n" "$1" "$2"; }

make_bar() {
    local label="$1" used="$2" total="$3" color="$4" w=25
    local pct=0
    [[ $total -gt 0 ]] && pct=$(( used * 100 / total ))
    local filled=$(( pct * w / 100 ))
    local empty=$(( w - filled ))
    [[ $pct -gt 80 ]] && color="$RED"
    [[ $pct -gt 60 && $pct -le 80 ]] && color="$YEL"
    local bar_f="" bar_e=""
    for ((j=0; j<filled; j++)); do bar_f+="#"; done
    for ((j=0; j<empty; j++)); do bar_e+="-"; done
    printf "  ${GRY}%-14s${R} [${color}%s${GRY}%s${R}] ${WHT}%3d%%${R}  ${GRY}(%s / %s MB)${R}\n" \
        "$label" "$bar_f" "$bar_e" "$pct" "$used" "$total"
}

read_mem() {
    MEM_TOTAL=$(awk '/MemTotal/{printf "%d",$2/1024}' /proc/meminfo)
    MEM_AVAIL=$(awk '/MemAvailable/{printf "%d",$2/1024}' /proc/meminfo)
    MEM_USED=$(( MEM_TOTAL - MEM_AVAIL ))
    MEM_BUFCACHE=$(awk '/Buffers/{b=$2} /^Cached/{c=$2} END{printf "%d",(b+c)/1024}' /proc/meminfo)
    SWAP_TOTAL=$(awk '/SwapTotal/{printf "%d",$2/1024}' /proc/meminfo)
    SWAP_FREE=$(awk '/SwapFree/{printf "%d",$2/1024}' /proc/meminfo)
    SWAP_USED=$(( SWAP_TOTAL - SWAP_FREE ))
}

print_mem() {
    make_bar "RAM" "$MEM_USED" "$MEM_TOTAL" "$CYN"
    make_bar "Buff/Cache" "$MEM_BUFCACHE" "$MEM_TOTAL" "$BLU"
    [[ $SWAP_TOTAL -gt 0 ]] && make_bar "Swap" "$SWAP_USED" "$SWAP_TOTAL" "$YEL"
}

drop_caches() {
    sync
    if [[ $(id -u) -eq 0 ]]; then
        echo 3 > /proc/sys/vm/drop_caches
    elif command -v sudo >/dev/null 2>&1; then
        echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null
    else
        su -c 'echo 3 > /proc/sys/vm/drop_caches'
    fi
}

clear
echo ""
printf "  ${CYN}${B}== Drop Caches ==${R}\n\n"

printf "  ${BLU}${B}Before${R}\n"
line
read_mem
print_mem
BEFORE_USED=$MEM_USED

echo ""
printf "  ${GRY}Syncing & dropping caches"
for i in 1 2 3; do sleep 0.2; printf "."; done
printf "${R}\n\n"

drop_caches
sleep 0.3

printf "  ${GRN}${B}After${R}\n"
line
read_mem
print_mem
AFTER_USED=$MEM_USED

FREED=$(( BEFORE_USED - AFTER_USED ))
echo ""
line
if [[ $FREED -gt 0 ]]; then
    printf "  ${GRN}*${R}  ${WHT}Freed ${GRN}${B}%s MB${R}${WHT} of memory${R}\n" "$FREED"
else
    printf "  ${YEL}*${R}  ${WHT}No significant memory freed${R}\n"
fi
echo ""
    }

    portmap() {
      R='\e[0m' B='\e[1m' D='\e[2m'
RED='\e[38;5;203m' GRN='\e[38;5;114m' YEL='\e[38;5;222m'
BLU='\e[38;5;111m' CYN='\e[38;5;116m' GRY='\e[38;5;242m' WHT='\e[38;5;255m'

_cols=$(tput cols 2>/dev/null || echo 60)
cols=$(( _cols < 60 ? _cols : 60 ))
line() { printf "  ${GRY}"; printf '%.0s-' $(seq 1 $(( cols - 4 ))); printf "${R}\n"; }
kv()   { printf "  ${GRY}%-20s${R} ${2:-$WHT}%s${R}\n" "$1" "$3"; }

fw_status=""
fw_rules=""
docker_rules=""

detect_firewall() {
    if command -v ufw &>/dev/null && ufw status 2>/dev/null | grep -q "active"; then
        fw_status="ufw"
        fw_rules=$(ufw status 2>/dev/null)
    elif command -v firewall-cmd &>/dev/null && firewall-cmd --state 2>/dev/null | grep -q "running"; then
        fw_status="firewalld"
        fw_rules=$(firewall-cmd --list-all 2>/dev/null)
    elif command -v iptables &>/dev/null; then
        fw_status="iptables"
        fw_rules=$(iptables -L -n 2>/dev/null)
        docker_rules=$(iptables -L DOCKER -n 2>/dev/null || true)
    else
        fw_status="none"
    fi
}

check_fw_port() {
    local port="$1" proto="$2"
    case "$fw_status" in
        ufw)
            if echo "$fw_rules" | grep -qE "${port}/(${proto}|any).*ALLOW"; then printf "${GRN}ALLOW${R}"
            elif echo "$fw_rules" | grep -qE "${port}/(${proto}|any).*DENY"; then printf "${RED}DENY${R}"
            else printf "${YEL}--${R}"; fi ;;
        firewalld)
            if echo "$fw_rules" | grep -q "${port}/${proto}"; then printf "${GRN}ALLOW${R}"
            else printf "${YEL}--${R}"; fi ;;
        iptables)
            if echo "$fw_rules" | grep -qE "ACCEPT.*${proto}.*dpt:${port}"; then printf "${GRN}ALLOW${R}"
            elif echo "$fw_rules" | grep -qE "DROP.*${proto}.*dpt:${port}"; then printf "${RED}DROP${R}"
            elif echo "$fw_rules" | grep -qE "REJECT.*${proto}.*dpt:${port}"; then printf "${RED}REJECT${R}"
            elif echo "$docker_rules" | grep -qE "dpt:${port}"; then printf "${CYN}DOCKER${R}"
            else printf "${YEL}--${R}"; fi ;;
        *) printf "${GRY}n/a${R}" ;;
    esac
}

is_wildcard() {
    [[ "$1" == "*" || "$1" == "0.0.0.0" || "$1" == "::" || "$1" == "[::]" || "$1" == "" ]]
}

ext_check() {
    local ip="$1" port="$2"
    (echo >/dev/tcp/"$ip"/"$port") 2>/dev/null &
    local pid=$!
    sleep 2
    if kill -0 "$pid" 2>/dev/null; then
        kill "$pid" 2>/dev/null || true
        wait "$pid" 2>/dev/null || true
        return 1
    fi
    wait "$pid" 2>/dev/null && return 0 || return 1
}

check_service_auth() {
    local port="$1" ip="$2"
    command -v nc &>/dev/null || { printf "${GRY}nc missing${R}"; return; }
    local resp
    case "$port" in
        6379)
            resp=$(echo "PING" | nc -w 2 "$ip" "$port" 2>/dev/null || echo "")
            if echo "$resp" | grep -q "+PONG"; then printf "${RED}NO AUTH${R}"
            elif echo "$resp" | grep -qi "NOAUTH\|ERR"; then printf "${GRN}AUTH OK${R}"
            else printf "${GRY}?${R}"; fi ;;
        3306)
            resp=$(nc -w 2 "$ip" "$port" 2>/dev/null | head -c 100 | strings 2>/dev/null || echo "")
            [[ -n "$resp" ]] && printf "${YEL}RESPONDS${R}" || printf "${GRY}?${R}" ;;
        27017)
            resp=$(nc -w 2 "$ip" "$port" 2>/dev/null | head -c 50 || echo "")
            [[ -n "$resp" ]] && printf "${YEL}RESPONDS${R}" || printf "${GRY}?${R}" ;;
        11211)
            resp=$(echo "stats" | nc -w 2 "$ip" "$port" 2>/dev/null | head -c 100 || echo "")
            echo "$resp" | grep -q "STAT" && printf "${RED}NO AUTH${R}" || printf "${GRY}?${R}" ;;
        2375)
            resp=$(printf "GET /version HTTP/1.0\r\n\r\n" | nc -w 2 "$ip" "$port" 2>/dev/null | head -c 200 || echo "")
            echo "$resp" | grep -qi "ApiVersion\|docker" && printf "${RED}NO AUTH${R}" || printf "${GRY}?${R}" ;;
        9200)
            resp=$(printf "GET / HTTP/1.0\r\n\r\n" | nc -w 2 "$ip" "$port" 2>/dev/null | head -c 300 || echo "")
            if echo "$resp" | grep -qi "cluster_name\|elasticsearch"; then printf "${RED}NO AUTH${R}"
            elif echo "$resp" | grep -qi "401\|security_exception"; then printf "${GRN}AUTH OK${R}"
            else printf "${GRY}?${R}"; fi ;;
        *) printf "${GRY}-${R}" ;;
    esac
}

declare -A KNOWN_RISKY=(
    [3306]="mysql"     [5432]="postgres"       [6379]="redis"
    [27017]="mongodb"  [9200]="elasticsearch"  [5984]="couchdb"
    [11211]="memcached" [2375]="docker-api"    [15672]="rabbitmq-mgmt"
    [5601]="kibana"
)

declare -A DOCKER_PORTS=()
if command -v docker &>/dev/null; then
    while IFS= read -r cname; do
        ports=$(docker port "$cname" 2>/dev/null || true)
        while IFS= read -r pline; do
            hport=$(echo "$pline" | grep -oP '0\.0\.0\.0:\K\d+|:::\K\d+' | head -1 || true)
            [[ -n "$hport" ]] && DOCKER_PORTS[$hport]="$cname"
        done <<< "$ports"
    done < <(docker ps --format '{{.Names}}' 2>/dev/null || true)
fi

resolve_proc() {
    local port="$1" info="$2"
    local proc docker_name
    proc=$(echo "$info" | grep -oP 'users:\(\("\K[^"]+' 2>/dev/null || echo "")
    docker_name="${DOCKER_PORTS[$port]:-}"
    if [[ "$proc" == "docker-proxy" && -n "$docker_name" ]]; then
        echo "$docker_name"
    elif [[ -z "$proc" && -n "$docker_name" ]]; then
        echo "$docker_name"
    else
        echo "${proc:--}"
    fi
}

# ── Main ─────────────────────────────────────────────────────────────
clear
echo ""
printf "  ${BLU}${B}== Port Check ==${R}\n"
printf "  ${GRY}$(date '+%Y-%m-%d %H:%M:%S')${R}\n\n"

detect_firewall
PUBLIC_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' || echo "")

case "$fw_status" in
    none) kv "Firewall" "$RED" "none detected" ;;
    *)    kv "Firewall" "$GRN" "$fw_status (active)" ;;
esac
kv "Public IP" "$WHT" "${PUBLIC_IP:-n/a}"
echo ""

# ── TCP ──────────────────────────────────────────────────────────────
printf "  ${BLU}${B}== Listening TCP ==${R}\n"
line
printf "  ${D}%-7s %-7s %-22s %-16s %s${R}\n" "PROTO" "PORT" "ADDRESS" "PROCESS" "FW"
line

declare -A seen_tcp=()
while read -r proto addr info; do
    port=$(echo "$addr" | awk -F: '{print $NF}')
    bind=$(echo "$addr" | sed "s/:${port}$//")
    [[ -z "$bind" ]] && bind="*"
    [[ -n "${seen_tcp[$port]:-}" ]] && continue
    seen_tcp[$port]=1
    proc=$(resolve_proc "$port" "$info")
    fw=$(check_fw_port "$port" "tcp")
    printf "  ${GRY}%-7s${R} ${WHT}%-7s${R} ${GRY}%-22s${R} ${CYN}%-16s${R} %b\n" "tcp" "$port" "$bind" "$proc" "$fw"
done < <(sudo ss -tlnp 2>/dev/null | awk 'NR>1 {print $1, $4, $7}')

# ── UDP ──────────────────────────────────────────────────────────────
echo ""
printf "  ${BLU}${B}== Listening UDP ==${R}\n"
line
printf "  ${D}%-7s %-7s %-22s %-16s %s${R}\n" "PROTO" "PORT" "ADDRESS" "PROCESS" "FW"
line

declare -A seen_udp=()
while read -r proto addr info; do
    port=$(echo "$addr" | awk -F: '{print $NF}')
    bind=$(echo "$addr" | sed "s/:${port}$//")
    [[ -z "$bind" ]] && bind="*"
    [[ -n "${seen_udp[$port]:-}" ]] && continue
    seen_udp[$port]=1
    proc=$(resolve_proc "$port" "$info")
    fw=$(check_fw_port "$port" "udp")
    printf "  ${GRY}%-7s${R} ${WHT}%-7s${R} ${GRY}%-22s${R} ${CYN}%-16s${R} %b\n" "udp" "$port" "$bind" "$proc" "$fw"
done < <(sudo ss -ulnp 2>/dev/null | awk 'NR>1 {print $1, $4, $7}')

# ── External Scan ────────────────────────────────────────────────────
echo ""
printf "  ${RED}${B}== External Scan ==${R}\n"
line

if [[ -z "$PUBLIC_IP" ]]; then
    printf "  ${YEL}*${R}  ${YEL}Could not determine public IP, skipping${R}\n"
else
    printf "  ${GRY}Scanning %s ...${R}\n\n" "$PUBLIC_IP"

    declare -A wildcard_ports=()
    while read -r addr _info; do
        port=$(echo "$addr" | awk -F: '{print $NF}')
        bind=$(echo "$addr" | sed "s/:${port}$//")
        is_wildcard "$bind" && wildcard_ports[$port]=1 || true
    done < <(sudo ss -tlnp 2>/dev/null | awk 'NR>1 {print $4, $7}')

    ext_open=0; ext_closed=0
    for port in "${!wildcard_ports[@]}"; do
        printf "\r  ${GRY}Testing %-7s ...${R}%10s" "$port" ""
        if ext_check "$PUBLIC_IP" "$port"; then
            fw=$(check_fw_port "$port" "tcp")
            proc="${DOCKER_PORTS[$port]:-}"
            [[ -n "$proc" ]] && proc_str=" ${GRY}(${proc})${R}" || proc_str=""
            printf "\r  ${RED}OPEN${R}    ${WHT}%-7s${R}%b  FW: %b\n" "$port" "$proc_str" "$fw"
            ext_open=$((ext_open + 1))
        else
            printf "\r  ${GRN}CLOSED${R}  ${GRY}%-7s${R}\n" "$port"
            ext_closed=$((ext_closed + 1))
        fi
    done

    echo ""
    kv "Externally open" "$RED" "$ext_open"
    kv "Filtered/closed" "$GRN" "$ext_closed"
fi

# ── Risk Assessment ──────────────────────────────────────────────────
echo ""
printf "  ${YEL}${B}== Risk Assessment ==${R}\n"
line

risk_count=0
declare -A checked_risks=()

while read -r addr info; do
    port=$(echo "$addr" | awk -F: '{print $NF}')
    bind=$(echo "$addr" | sed "s/:${port}$//")
    is_wildcard "$bind" || continue
    [[ -n "${checked_risks[$port]:-}" ]] && continue
    checked_risks[$port]=1

    svc_name="${KNOWN_RISKY[$port]:-}"
    [[ -z "$svc_name" ]] && continue

    fw=$(check_fw_port "$port" "tcp")
    fw_plain=$(printf "%b" "$fw" | sed 's/\x1b\[[0-9;]*m//g')
    [[ "$fw_plain" == "DROP" || "$fw_plain" == "REJECT" || "$fw_plain" == "DENY" ]] && continue

    risk_count=$((risk_count + 1))
    printf "  ${RED}!${R}  ${WHT}%-7s${R} ${CYN}%-14s${R}  FW: %b" "$port" "$svc_name" "$fw"
    if [[ -n "$PUBLIC_IP" ]]; then
        printf "  Auth: "; check_service_auth "$port" "$PUBLIC_IP"
    fi
    printf "\n"

    case "$svc_name" in
        redis)         printf "     ${GRY}-> bind 127.0.0.1, set requirepass in redis.conf${R}\n" ;;
        mysql)         printf "     ${GRY}-> bind-address = 127.0.0.1 in my.cnf${R}\n" ;;
        postgres)      printf "     ${GRY}-> listen_addresses = localhost in postgresql.conf${R}\n" ;;
        mongodb)       printf "     ${GRY}-> enable auth, bindIp: 127.0.0.1 in mongod.conf${R}\n" ;;
        elasticsearch) printf "     ${GRY}-> enable xpack.security.enabled: true${R}\n" ;;
        memcached)     printf "     ${GRY}-> start with -l 127.0.0.1${R}\n" ;;
        docker-api)    printf "     ${GRY}-> CRITICAL: never expose without TLS + client certs${R}\n" ;;
        rabbitmq-mgmt) printf "     ${GRY}-> restrict to localhost or VPN only${R}\n" ;;
    esac
done < <(sudo ss -tlnp 2>/dev/null | awk 'NR>1 {print $4, $7}')

[[ $risk_count -eq 0 ]] && printf "  ${GRN}*${R}  ${GRN}No known risky services exposed${R}\n"

echo ""
line
printf "  ${GRY}Scan complete. $(date '+%H:%M:%S')${R}\n"
echo ""
    }

    diskclean() {
      R='\e[0m' B='\e[1m' D='\e[2m'
RED='\e[38;5;203m' GRN='\e[38;5;114m' YEL='\e[38;5;222m'
BLU='\e[38;5;111m' CYN='\e[38;5;116m' GRY='\e[38;5;242m' WHT='\e[38;5;255m'

_cols=$(tput cols 2>/dev/null || echo 60)
cols=$(( _cols < 60 ? _cols : 60 ))
line() { printf "  ${GRY}"; printf '%.0s-' $(seq 1 $(( cols - 4 ))); printf "${R}\n"; }
kv()   { printf "  ${GRY}%-28s${R} ${2:-$WHT}%s${R}\n" "$1" "$3"; }

confirm() {
    local msg="$1"
    printf "\n  ${YEL}%s${R} [y/N] " "$msg"
    read -r ans
    [[ "$ans" =~ ^[Yy]$ ]]
}

hr_size() {
    local kb="$1"
    if (( kb >= 1048576 )); then
        printf "%d.%d GB" "$(( kb / 1048576 ))" "$(( (kb % 1048576) * 10 / 1048576 ))"
    elif (( kb >= 1024 )); then
        printf "%d.%d MB" "$(( kb / 1024 ))" "$(( (kb % 1024) * 10 / 1024 ))"
    else
        printf "%s KB" "$kb"
    fi
}

freed_total=0

clear
echo ""
printf "  ${YEL}${B}== Disk Clean ==${R}\n"
printf "  ${GRY}$(date '+%Y-%m-%d %H:%M:%S')${R}\n\n"

DISK_USED_BEFORE=$(df / | awk 'NR==2{print $3}')
DISK_TOTAL=$(df -h / | awk 'NR==2{print $2}')
DISK_PCT=$(df / | awk 'NR==2{gsub(/%/,"",$5); print $5}')
bar_fill=$(( DISK_PCT * 25 / 100 ))
bar_empty=$(( 25 - bar_fill ))
bar_f=""; bar_e=""
for ((j=0; j<bar_fill; j++)); do bar_f+="#"; done
for ((j=0; j<bar_empty; j++)); do bar_e+="-"; done
clr="$GRN"; [[ $DISK_PCT -gt 80 ]] && clr="$RED"; [[ $DISK_PCT -gt 60 && $DISK_PCT -le 80 ]] && clr="$YEL"
printf "  ${GRY}%-28s${R} [${clr}%s${GRY}%s${R}] ${WHT}%d%%${R}  ${GRY}(/ on %s)${R}\n\n" "Disk Usage" "$bar_f" "$bar_e" "$DISK_PCT" "$DISK_TOTAL"

# ── APT cache ────────────────────────────────────────────────────────
printf "  ${BLU}${B}== APT Cache ==${R}\n"
line
APT_SIZE=$(du -sk /var/cache/apt/archives/ 2>/dev/null | awk '{print $1}' || echo 0)
kv "Cached packages" "$YEL" "$(hr_size $APT_SIZE)"
if (( APT_SIZE > 1024 )) && confirm "Clean apt cache?"; then
    apt-get clean 2>/dev/null
    APT_AFTER=$(du -sk /var/cache/apt/archives/ 2>/dev/null | awk '{print $1}' || echo 0)
    freed=$(( APT_SIZE - APT_AFTER ))
    freed_total=$(( freed_total + freed ))
    printf "  ${GRN}*${R}  Freed $(hr_size $freed)\n"
else
    printf "  ${GRY}*${R}  Skipped\n"
fi

# ── Old logs ─────────────────────────────────────────────────────────
echo ""
printf "  ${BLU}${B}== System Logs ==${R}\n"
line
LOG_SIZE=$(du -sk /var/log/ 2>/dev/null | awk '{print $1}' || echo 0)
JOURNAL_SIZE=$(journalctl --disk-usage 2>/dev/null | grep -oP '[\d.]+[A-Z]+' | head -1 || echo "0B")
kv "/var/log total" "$YEL" "$(hr_size $LOG_SIZE)"
kv "journald usage" "$YEL" "$JOURNAL_SIZE"

if confirm "Vacuum journald (keep last 7 days)?"; then
    journalctl --vacuum-time=7d 2>/dev/null
    printf "  ${GRN}*${R}  Journal vacuumed\n"
else
    printf "  ${GRY}*${R}  Skipped\n"
fi

OLD_LOGS=$(find /var/log -name "*.gz" -o -name "*.1" -o -name "*.old" 2>/dev/null | head -20)
OLD_LOG_SIZE=0
while IFS= read -r f; do
    [[ -z "$f" ]] && continue
    s=$(du -sk "$f" 2>/dev/null | awk '{print $1}' || echo 0)
    OLD_LOG_SIZE=$(( OLD_LOG_SIZE + s ))
done <<< "$OLD_LOGS"

if (( OLD_LOG_SIZE > 0 )); then
    kv "Rotated log files" "$YEL" "$(hr_size $OLD_LOG_SIZE)"
    if confirm "Delete rotated logs (.gz, .1, .old)?"; then
        find /var/log -name "*.gz" -o -name "*.1" -o -name "*.old" 2>/dev/null | xargs rm -f 2>/dev/null || true
        freed_total=$(( freed_total + OLD_LOG_SIZE ))
        printf "  ${GRN}*${R}  Freed $(hr_size $OLD_LOG_SIZE)\n"
    else
        printf "  ${GRY}*${R}  Skipped\n"
    fi
fi

# ── Docker ───────────────────────────────────────────────────────────
if command -v docker &>/dev/null; then
    echo ""
    printf "  ${BLU}${B}== Docker ==${R}\n"
    line

    DANGLING_IMAGES=$(docker images -f "dangling=true" -q 2>/dev/null | wc -l)
    STOPPED_CONTAINERS=$(docker ps -a -f "status=exited" -q 2>/dev/null | wc -l)
    UNUSED_VOLUMES=$(docker volume ls -f "dangling=true" -q 2>/dev/null | wc -l)
    UNUSED_NETWORKS=$(docker network ls -f "type=custom" -q 2>/dev/null | wc -l)
    DOCKER_DF=$(docker system df 2>/dev/null || true)
    DOCKER_RECLAIMABLE=$(echo "$DOCKER_DF" | awk '
        /Images/     { img=$5 }
        /Containers/ { cnt=$5 }
        /Volumes/    { vol=$5 }
        /Build/      { bld=$5 }
        END { printf "%s img, %s cnt, %s vol, %s build", img, cnt, vol, bld }
    ' 2>/dev/null || echo "?")

    kv "Dangling images" "$YEL" "$DANGLING_IMAGES"
    kv "Stopped containers" "$YEL" "$STOPPED_CONTAINERS"
    kv "Unused volumes" "$YEL" "$UNUSED_VOLUMES"
    kv "Reclaimable" "$YEL" "$DOCKER_RECLAIMABLE"

    if (( DANGLING_IMAGES + STOPPED_CONTAINERS + UNUSED_VOLUMES > 0 )) && confirm "Run docker system prune -f (keeps running containers)?"; then
        PRUNE_OUT=$(docker system prune -f 2>&1 || true)
        RECLAIMED=$(echo "$PRUNE_OUT" | grep -i "Total reclaimed" | tail -1 || echo "")
        printf "  ${GRN}*${R}  Docker pruned"
        [[ -n "$RECLAIMED" ]] && printf "  ${GRY}(%s)${R}" "$RECLAIMED" || true
        printf "\n"
    else
        printf "  ${GRY}*${R}  Skipped\n"
    fi

    if confirm "Also remove unused volumes? (WARNING: data loss possible)"; then
        VOL_OUT=$(docker volume prune -f 2>&1 || true)
        VOL_RECLAIMED=$(echo "$VOL_OUT" | grep -i "Total reclaimed" | tail -1 || echo "")
        printf "  ${GRN}*${R}  Volumes pruned"
        [[ -n "$VOL_RECLAIMED" ]] && printf "  ${GRY}(%s)${R}" "$VOL_RECLAIMED" || true
        printf "\n"
    else
        printf "  ${GRY}*${R}  Skipped\n"
    fi

    # ── Duplicate images ─────────────────────────────────────────────
    echo ""
    printf "  ${BLU}${B}== Duplicate Images ==${R}\n"
    line

    RUNNING_IMAGES=$(docker ps --format '{{.Image}}' 2>/dev/null || true)

    declare -A REPO_IDS=()
    declare -A REPO_SIZES=()
    declare -A REPO_NAMES=()
    while read -r line; do
        repo=$(echo "$line" | cut -d'|' -f1)
        tag=$(echo "$line"  | cut -d'|' -f2)
        id=$(echo "$line"   | cut -d'|' -f3)
        size=$(echo "$line" | cut -d'|' -f4)
        [[ -z "$repo" || "$repo" == "<none>" || "$tag" == "<none>" ]] && continue
        key="${repo//[^a-zA-Z0-9]/_}"
        [[ -z "$key" ]] && continue
        REPO_IDS[$key]+="${id}|${tag}|${size}\n"
        REPO_SIZES[$key]=$(( ${REPO_SIZES[$key]:-0} + 1 ))
        REPO_NAMES[$key]="$repo"
    done < <(docker images --format '{{.Repository}}|{{.Tag}}|{{.ID}}|{{.Size}}' 2>/dev/null)

    dup_found=0
    for key in "${!REPO_SIZES[@]}"; do
        count=${REPO_SIZES[$key]}
        [[ $count -lt 2 ]] && continue
        dup_found=1
        repo="${REPO_NAMES[$key]}"

        printf "\n  ${YEL}%s${R}  ${GRY}(%d tags)${R}\n" "$repo" "$count"
        line

        declare -a candidates=()
        while IFS= read -r entry; do
            [[ -z "$entry" ]] && continue
            img_id=$(echo "$entry" | cut -d'|' -f1)
            tag=$(echo "$entry"   | cut -d'|' -f2)
            size=$(echo "$entry"  | cut -d'|' -f3)
            full="${repo}:${tag}"

            in_use=0
            while IFS= read -r running; do
                [[ "$running" == "$full" || "$running" == "$img_id"* ]] && { in_use=1; break; }
            done <<< "$RUNNING_IMAGES"

            if [[ $in_use -eq 1 ]]; then
                printf "  ${GRN}RUNNING${R}  ${WHT}%-40s${R} ${GRY}%s${R}\n" "$full" "$size"
            else
                printf "  ${GRY}idle   ${R}  ${WHT}%-40s${R} ${GRY}%s${R}\n" "$full" "$size"
                candidates+=("$full")
            fi
        done < <(printf "%b" "${REPO_IDS[$key]}")

        if [[ ${#candidates[@]} -gt 0 ]]; then
            printf "\n  ${GRY}Idle tags: ${YEL}"
            printf "%s  " "${candidates[@]}"
            printf "${R}\n"
            if confirm "Remove ${#candidates[@]} idle tag(s) for ${repo}?"; then
                for img in "${candidates[@]}"; do
                    docker rmi "$img" >/dev/null 2>&1 &&                         printf "  ${GRN}*${R}  Removed %s\n" "$img" ||                         printf "  ${RED}*${R}  Failed  %s\n" "$img"
                done
            else
                printf "  ${GRY}*${R}  Skipped\n"
            fi
        fi
    done

    [[ $dup_found -eq 0 ]] && printf "  ${GRN}*${R}  No duplicate image repos found\n"
fi

# ── Temp files ───────────────────────────────────────────────────────
echo ""
printf "  ${BLU}${B}== Temp Files ==${R}\n"
line
TMP_SIZE=$(du -sk /tmp/ 2>/dev/null | awk '{print $1}' || echo 0)
kv "/tmp usage" "$YEL" "$(hr_size $TMP_SIZE)"
if (( TMP_SIZE > 10240 )) && confirm "Clean /tmp (files older than 1 day)?"; then
    find /tmp -mindepth 1 -mtime +1 -delete 2>/dev/null || true
    TMP_AFTER=$(du -sk /tmp/ 2>/dev/null | awk '{print $1}' || echo 0)
    freed=$(( TMP_SIZE - TMP_AFTER ))
    freed_total=$(( freed_total + freed ))
    printf "  ${GRN}*${R}  Freed $(hr_size $freed)\n"
else
    printf "  ${GRY}*${R}  Skipped\n"
fi

# ── Top dirs ─────────────────────────────────────────────────────────
echo ""
printf "  ${BLU}${B}== Top 10 Largest Directories ==${R}\n"
line
printf "  ${D}%-12s %s${R}\n" "SIZE" "PATH"
line
du -xsk /* 2>/dev/null | sort -rn | head -10 | while read -r size path; do
    printf "  ${YEL}%-12s${R} ${WHT}%s${R}\n" "$(hr_size $size)" "$path"
done

# ── Summary ──────────────────────────────────────────────────────────
echo ""
line
DISK_USED_AFTER=$(df / | awk 'NR==2{print $3}')
ACTUAL_FREED=$(( DISK_USED_BEFORE - DISK_USED_AFTER ))
if (( ACTUAL_FREED > 0 )); then
    printf "  ${GRN}*${R}  ${WHT}Total freed: ${GRN}${B}$(hr_size $ACTUAL_FREED)${R}\n"
else
    printf "  ${GRY}*${R}  ${WHT}No space freed${R}\n"
fi
echo ""
    }

    ssl() {
      R='\e[0m' B='\e[1m' D='\e[2m'
RED='\e[38;5;203m' GRN='\e[38;5;114m' YEL='\e[38;5;222m'
BLU='\e[38;5;111m' CYN='\e[38;5;116m' GRY='\e[38;5;242m' WHT='\e[38;5;255m'

_cols=$(tput cols 2>/dev/null || echo 60)
cols=$(( _cols < 60 ? _cols : 60 ))
line() { printf "  ${GRY}"; printf '%.0s-' $(seq 1 $(( cols - 4 ))); printf "${R}\n"; }
kv()   { printf "  ${GRY}%-22s${R} ${2:-$WHT}%s${R}\n" "$1" "$3"; }

if ! command -v openssl &>/dev/null; then
    printf "  ${RED}*${R}  openssl not found. Install: apt install openssl\\n"
    exit 1
fi

declare -A seen_certs=()
results=()

check_cert_file() {
    local path="$1"
    local fingerprint
    fingerprint=$(openssl x509 -in "$path" -noout -fingerprint 2>/dev/null | cut -d= -f2 || echo "")
    [[ -z "$fingerprint" ]] && return
    [[ -n "${seen_certs[$fingerprint]:-}" ]] && return
    seen_certs[$fingerprint]=1

    local subject issuer not_after not_before domains days_left
    subject=$(openssl x509 -in "$path" -noout -subject 2>/dev/null | sed 's/.*CN=//;s/,.*//' || echo "?")
    issuer=$(openssl x509 -in "$path" -noout -issuer 2>/dev/null | grep -oP '(?<=O = )[^,/]+' | head -1 | sed 's/^ *//;s/ *$//' | cut -c1-30 || echo "?")
    not_after=$(openssl x509 -in "$path" -noout -enddate 2>/dev/null | cut -d= -f2 || echo "")
    not_before=$(openssl x509 -in "$path" -noout -startdate 2>/dev/null | cut -d= -f2 || echo "")
    domains=$(openssl x509 -in "$path" -noout -text 2>/dev/null | grep -A1 "Subject Alternative" | tail -1 | sed 's/DNS://g;s/, /\n/g' | grep -v "IP Address" | tr '\n' ' ' | sed 's/^ *//' || echo "")
    [[ -z "$domains" ]] && domains="$subject"

    local expire_ts now_ts
    expire_ts=$(date -d "$not_after" +%s 2>/dev/null || echo 0)
    now_ts=$(date +%s)
    days_left=$(( (expire_ts - now_ts) / 86400 ))

    local status_clr status_txt
    if (( days_left < 0 )); then
        status_clr="$RED"; status_txt="EXPIRED"
    elif (( days_left < 7 )); then
        status_clr="$RED"; status_txt="${days_left}d"
    elif (( days_left < 30 )); then
        status_clr="$YEL"; status_txt="${days_left}d"
    else
        status_clr="$GRN"; status_txt="${days_left}d"
    fi

    results+=("${days_left}|${status_clr}|${status_txt}|${subject}|${issuer}|${not_after}|${domains}|${path}")
}

check_cert_live() {
    local host="$1" port="${2:-443}"
    local tmpfile
    tmpfile=$(mktemp /tmp/ssl_check_XXXXXX.pem)

    timeout 5 openssl s_client -connect "${host}:${port}" -servername "$host" </dev/null 2>/dev/null \
        | openssl x509 -out "$tmpfile" 2>/dev/null || true

    if [[ -s "$tmpfile" ]]; then
        check_cert_file "$tmpfile"
    fi
    rm -f "$tmpfile"
}

print_results() {
    if [[ ${#results[@]} -eq 0 ]]; then
        printf "  ${GRY}*${R}  No certificates found\\n"
        return
    fi

    # Deduplicate by primary domain - keep highest days_left per domain
    declare -A best_days=()
    declare -A best_entry=()
    for entry in "${results[@]}"; do
        IFS='|' read -r days clr status subject issuer not_after domains path <<< "$entry"
        domain="${domains%% *}"
        domain="${domain%%,*}"
        existing_days="${best_days[$domain]:-999999}"
        # For EXPIRED, days_left is negative - normalize
        comp_days=$days
        if [[ "$status" == "EXPIRED" ]]; then comp_days=-1; fi
        if [[ -z "${best_entry[$domain]:-}" ]] || (( comp_days > existing_days )); then
            best_days[$domain]=$comp_days
            best_entry[$domain]="$entry"
        fi
    done

    printf "  ${D}%-8s %-32s %-30s %s${R}\\n" "STATUS" "DOMAIN" "ISSUER" "EXPIRES"
    line

    local expired=0 warning=0 ok=0
    for entry in "${best_entry[@]}"; do
        IFS='|' read -r days clr status subject issuer not_after domains path <<< "$entry"
        display="${domains%% *}"
        [[ ${#display} -gt 30 ]] && display="${display:0:27}..."
        issuer_short="${issuer:0:28}"
        printf "  ${clr}%-8s${R} ${WHT}%-32s${R} ${GRY}%-30s${R} ${GRY}%s${R}\\n" \
            "$status" "$display" "$issuer_short" "$not_after"
        [[ "$status" == "EXPIRED" ]] && expired=$(( expired + 1 ))
        [[ "$status" =~ ^[0-9]+d$ && "${status%d}" -lt 30 ]] && warning=$(( warning + 1 )) || true
        [[ "$status" =~ ^[0-9]+d$ && "${status%d}" -ge 30 ]] && ok=$(( ok + 1 )) || true
    done
    unset best_days best_entry

    echo ""
    line
    printf "  ${GRN}*${R}  ${WHT}OK: %s${R}   ${YEL}*${R}  ${WHT}Expiring: %s${R}   ${RED}*${R}  ${WHT}Expired: %s${R}\\n" \
        "$ok" "$warning" "$expired"
}

clear
echo ""
printf "  ${CYN}${B}== SSL Certificate Check ==${R}\\n"
printf "  ${GRY}$(date '+%Y-%m-%d %H:%M:%S')${R}\\n\\n"

# ── Let's Encrypt ────────────────────────────────────────────────────
printf "  ${BLU}${B}== Let's Encrypt ==${R}\\n"
line

LE_FOUND=0
if [[ -d /etc/letsencrypt/live ]]; then
    while IFS= read -r certfile; do
        check_cert_file "$certfile"
        LE_FOUND=1
    done < <(find /etc/letsencrypt/live -maxdepth 2 -name "cert.pem" 2>/dev/null)
fi

[[ $LE_FOUND -eq 0 ]] && printf "  ${GRY}*${R}  No Let's Encrypt certs found\\n"
[[ $LE_FOUND -gt 0 ]] && print_results
results=()

# ── Nginx ────────────────────────────────────────────────────────────
for conf_dir in /etc/nginx/sites-enabled /etc/nginx/conf.d /etc/nginx; do
    [[ -d "$conf_dir" ]] || continue
    while IFS= read -r conf; do
        while IFS= read -r certpath; do
            certpath=$(echo "$certpath" | awk '{print $2}' | tr -d ';')
            [[ -f "$certpath" ]] || continue
            [[ "$certpath" == /etc/letsencrypt/* ]] && continue
            check_cert_file "$certpath"
        done < <(grep -h "ssl_certificate " "$conf" 2>/dev/null | grep -v "ssl_certificate_key")
    done < <(find "$conf_dir" -maxdepth 2 -name "*.conf" 2>/dev/null)
done

if [[ ${#results[@]} -gt 0 ]]; then
    echo ""
    printf "  ${BLU}${B}== Nginx (non-LE certs) ==${R}\n"
    line
    print_results
fi
results=()

# ── Apache ───────────────────────────────────────────────────────────
for conf_dir in /etc/apache2/sites-enabled /etc/httpd/conf.d; do
    [[ -d "$conf_dir" ]] || continue
    while IFS= read -r conf; do
        while IFS= read -r certpath; do
            certpath=$(echo "$certpath" | awk '{print $2}' | tr -d '"')
            [[ -f "$certpath" ]] || continue
            [[ "$certpath" == /etc/letsencrypt/* ]] && continue
            check_cert_file "$certpath"
        done < <(grep -hi "SSLCertificateFile" "$conf" 2>/dev/null)
    done < <(find "$conf_dir" -maxdepth 2 -name "*.conf" 2>/dev/null)
done

if [[ ${#results[@]} -gt 0 ]]; then
    echo ""
    printf "  ${BLU}${B}== Apache (non-LE certs) ==${R}\n"
    line
    print_results
fi
results=()

# ── Docker containers (live check) ───────────────────────────────────
if command -v docker &>/dev/null; then
    while IFS= read -r line_; do
        host=$(echo "$line_" | grep -oP '(?<=://)[^/:"]+' || true)
        [[ -z "$host" ]] && continue
        [[ "$host" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && continue
        printf "  ${GRY}Checking %s ...${R}\r" "$host"
        check_cert_live "$host" 443
    done < <(docker inspect $(docker ps -q 2>/dev/null) 2>/dev/null \
        | grep -oP '"VIRTUAL_HOST[^"]*":\s*"[^"]+"' \
        | grep -oP '(?<=: ")[^"]+' \
        | tr ',' '\n' \
        | sort -u)

    if [[ ${#results[@]} -gt 0 ]]; then
        echo ""
        printf "  ${BLU}${B}== Docker (live HTTPS) ==${R}\n"
        line
        print_results
    fi
    results=()
fi

# ── Manual domain check ──────────────────────────────────────────────
echo ""
printf "  ${BLU}${B}== Manual Check ==${R}\\n"
line
printf "  ${GRY}Enter domains to check (comma-separated), or press Enter to skip:${R}\\n  "
read -r manual_input

if [[ -n "$manual_input" ]]; then
    IFS=',' read -ra manual_domains <<< "$manual_input"
    for domain in "${manual_domains[@]}"; do
        domain=$(echo "$domain" | tr -d ' ')
        [[ -z "$domain" ]] && continue
        printf "  ${GRY}Checking %s ...${R}\\r" "$domain"
        check_cert_live "$domain" 443
    done
    print_results
else
    printf "  ${GRY}*${R}  Skipped\\n"
fi

echo ""
line
printf "  ${GRY}Scan complete. $(date '+%H:%M:%S')${R}\\n"
echo ""
    }

    update() {
      R='\e[0m' B='\e[1m' D='\e[2m'
RED='\e[38;5;203m' GRN='\e[38;5;114m' YEL='\e[38;5;222m'
BLU='\e[38;5;111m' CYN='\e[38;5;116m' GRY='\e[38;5;242m' WHT='\e[38;5;255m'

_cols=$(tput cols 2>/dev/null || echo 60)
cols=$(( _cols < 60 ? _cols : 60 ))
line() { printf "  ${GRY}"; printf '%.0s-' $(seq 1 $(( cols - 4 ))); printf "${R}\n"; }
kv()   { printf "  ${GRY}%-22s${R} ${2:-$WHT}%s${R}\n" "$1" "$3"; }

confirm() {
    printf "\n  ${YEL}%s${R} [y/N] " "$1"
    read -r ans
    [[ "$ans" =~ ^[Yy]$ ]]
}

if ! command -v apt-get &>/dev/null; then
    printf "  ${RED}*${R}  apt not found. Debian/Ubuntu only.\\n"
    exit 1
fi

clear
echo ""
printf "  ${CYN}${B}== System Update ==${R}\\n"
printf "  ${GRY}$(date '+%Y-%m-%d %H:%M:%S')${R}\\n\\n"

# ── Fetch package lists ───────────────────────────────────────────────
printf "  ${BLU}${B}== Fetching Package Lists ==${R}\\n"
line
printf "  ${GRY}Running apt-get update ...${R}\\n"
while IFS= read -r l; do
    case "$l" in
        Get*)  printf "  ${GRN}GET${R}  ${GRY}%s${R}\n" "${l#Get:* }" ;;
        Hit*)  printf "  ${GRY}HIT  %s${R}\n" "${l#Hit:* }" ;;
        Err*)  printf "  ${RED}ERR${R}  ${WHT}%s${R}\n" "${l#Err:* }" ;;
    esac
done < <(apt-get update 2>&1 | grep -E "^(Get|Hit|Ign|Err)" || true)

# ── Pending upgrades ─────────────────────────────────────────────────
echo ""
printf "  ${BLU}${B}== Pending Upgrades ==${R}\\n"
line

UPGRADABLE=$(apt list --upgradable 2>/dev/null | grep -vE "^(Listing|Auflistung)" || true)
TOTAL=$(echo "$UPGRADABLE" | grep -c "/" 2>/dev/null) || TOTAL=0
SECURITY=$(echo "$UPGRADABLE" | grep -c "security" 2>/dev/null) || SECURITY=0
REGULAR=$(( TOTAL - SECURITY ))

if [[ $TOTAL -eq 0 ]]; then
    printf "  ${GRN}*${R}  ${GRN}System is up to date${R}\\n"
    echo ""
    line
    printf "  ${GRY}Done. $(date '+%H:%M:%S')${R}\\n"
    echo ""
    exit 0
fi

kv "Total pending" "$WHT" "$TOTAL"
kv "Security updates" "$RED" "$SECURITY"
kv "Regular updates" "$GRN" "$REGULAR"
echo ""

if [[ $SECURITY -gt 0 ]]; then
    printf "  ${RED}${B}== Security Updates ==${R}\\n"
    line
    printf "  ${D}%-30s %-20s %s${R}\\n" "PACKAGE" "NEW VERSION" "SOURCE"
    line
    echo "$UPGRADABLE" | grep "security" | while IFS= read -r pkg_line; do
        name=$(echo "$pkg_line" | awk -F'/' '{print $1}')
        ver=$(echo "$pkg_line" | awk '{print $2}' | cut -d'/' -f1 || echo "?")
        src=$(echo "$pkg_line" | grep -oP '(?<=security)[^\s]*' | head -1 || echo "")
        printf "  ${RED}%-30s${R} ${YEL}%-20s${R} ${GRY}%s${R}\\n" "$name" "$ver" "$src"
    done
    echo ""
fi

if [[ $REGULAR -gt 0 ]]; then
    printf "  ${BLU}${B}== Regular Updates ==${R}\\n"
    line
    printf "  ${D}%-30s %s${R}\\n" "PACKAGE" "NEW VERSION"
    line
    echo "$UPGRADABLE" | grep -v "security" | while IFS= read -r pkg_line; do
        name=$(echo "$pkg_line" | awk -F'/' '{print $1}')
        ver=$(echo "$pkg_line" | awk '{print $2}' | cut -d'/' -f1 || echo "?")
        printf "  ${WHT}%-30s${R} ${GRN}%s${R}\\n" "$name" "$ver"
    done
    echo ""
fi

# ── Autoremove candidates ─────────────────────────────────────────────
AUTOREMOVE=$(apt-get autoremove --dry-run 2>/dev/null | grep "^Remv" | wc -l 2>/dev/null) || AUTOREMOVE=0
if [[ $AUTOREMOVE -gt 0 ]]; then
    printf "  ${BLU}${B}== Unused Packages ==${R}\\n"
    line
    kv "Autoremovable" "$GRY" "$AUTOREMOVE packages"
    echo ""
fi

# ── Confirm & apply ───────────────────────────────────────────────────
line

if [[ $SECURITY -gt 0 ]]; then
    printf "  ${RED}!${R}  ${RED}${B}%s security update(s) pending${R}\\n" "$SECURITY"
fi

if confirm "Apply all $TOTAL update(s)?"; then
    echo ""
    printf "  ${BLU}${B}== Applying Updates ==${R}\\n"
    line
    while IFS= read -r l; do
            case "$l" in
                Unpacking*)  printf "  ${GRY}%-12s${R} ${WHT}%s${R}\\n" "unpack" "${l#Unpacking }" ;;
                Setting*)    printf "  ${GRN}%-12s${R} ${WHT}%s${R}\\n" "setup" "${l#Setting up }" ;;
                Processing*) printf "  ${GRY}%-12s${R} ${WHT}%s${R}\\n" "process" "${l#Processing }" ;;
            esac
        done < <(DEBIAN_FRONTEND=noninteractive apt-get upgrade -y 2>&1 | grep -E "^(Unpacking|Setting up|Processing)" || true)

    printf "  ${GRN}*${R}  ${GRN}Upgrade complete${R}\n"

    if [[ $AUTOREMOVE -gt 0 ]] && confirm "Run autoremove ($AUTOREMOVE packages)?"; then
        apt-get autoremove -y >/dev/null 2>&1
        printf "  ${GRN}*${R}  Removed $AUTOREMOVE unused package(s)\\n"
    fi

    REBOOT_REQUIRED=0
    [[ -f /var/run/reboot-required ]] && REBOOT_REQUIRED=1
    if [[ $REBOOT_REQUIRED -eq 1 ]]; then
        echo ""
        printf "  ${RED}!${R}  ${RED}${B}Reboot required${R}\\n"
        cat /var/run/reboot-required.pkgs 2>/dev/null | while read -r pkg; do
            printf "     ${GRY}|- %s${R}\\n" "$pkg"
        done
        confirm "Reboot now?" && reboot
    fi
else
    printf "  ${GRY}*${R}  Skipped\\n"
fi

echo ""
line
printf "  ${GRY}Done. $(date '+%H:%M:%S')${R}\\n"
echo ""
    }

    dockstat() {
      R='\e[0m' B='\e[1m' D='\e[2m'
RED='\e[38;5;203m' GRN='\e[38;5;114m' YEL='\e[38;5;222m'
BLU='\e[38;5;111m' CYN='\e[38;5;116m' GRY='\e[38;5;242m' WHT='\e[38;5;255m'

if ! command -v docker &>/dev/null; then
    printf "  ${RED}*${R}  docker not found\n"
    exit 1
fi

_cols=$(tput cols 2>/dev/null || echo 60)
cols=$(( _cols < 60 ? _cols : 60 ))
line() { printf "  ${GRY}"; printf '%.0s-' $(seq 1 $(( cols - 4 ))); printf "${R}\n"; }

INTERVAL=2

status_color() {
    case "$1" in
        running) printf "${GRN}%s${R}" "$1" ;;
        exited)  printf "${RED}%s${R}" "$1" ;;
        paused)  printf "${YEL}%s${R}" "$1" ;;
        *)       printf "${GRY}%s${R}" "$1" ;;
    esac
}

cpu_color() {
    local val="${1%\%}"
    val="${val%%.*}"
    if (( val >= 80 )); then printf "${RED}%s${R}" "$1"
    elif (( val >= 50 )); then printf "${YEL}%s${R}" "$1"
    else printf "${GRN}%s${R}" "$1"; fi
}

mem_color() {
    local val="${1%\%}"
    val="${val%%.*}"
    if (( val >= 80 )); then printf "${RED}%s${R}" "$1"
    elif (( val >= 50 )); then printf "${YEL}%s${R}" "$1"
    else printf "${CYN}%s${R}" "$1"; fi
}

draw() {
    # Fetch all data BEFORE clearing
    local ts=$(date '+%Y-%m-%d %H:%M:%S')
    local total running stopped paused
    total=$(docker ps -a -q 2>/dev/null | wc -l) || total=0
    running=$(docker ps -q 2>/dev/null | wc -l) || running=0
    stopped=$(docker ps -a -f status=exited -q 2>/dev/null | wc -l) || stopped=0
    paused=$(docker ps -a -f status=paused -q 2>/dev/null | wc -l) || paused=0

    declare -A IMAGES=()
    while IFS=$'\t' read -r cname cimage; do
        [[ -n "$cname" ]] && IMAGES[$cname]="$cimage"
    done < <(docker ps --format '{{.Names}}\t{{.Image}}' 2>/dev/null || true)

    local stats_data=""
    if [[ $running -gt 0 ]]; then
        stats_data=$(timeout 3 docker stats --no-stream --format '{{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}\t{{.MemUsage}}' 2>/dev/null | sort || true)
    fi

    local stopped_list=""
    if [[ $stopped -gt 0 ]]; then
        stopped_list=$(docker ps -a -f status=exited --format '{{.Names}}|{{.Image}}|{{.Status}}' 2>/dev/null || true)
    fi

    # Build output buffer
    local buf=""
    local _line=""
    printf -v _line "  ${CYN}${B}== dockstat ==${R}\n"
    buf+="\n${_line}"
    printf -v _line "  ${GRY}%s  |  refresh %ss  |  +/- speed  |  q=quit${R}\n\n" "$ts" "$INTERVAL"
    buf+="${_line}"
    printf -v _line "  ${GRN}${B}%s${R} ${GRY}running${R}   ${RED}${B}%s${R} ${GRY}stopped${R}   ${YEL}${B}%s${R} ${GRY}paused${R}   ${GRY}${D}%s total${R}\n\n" \
        "$running" "$stopped" "$paused" "$total"
    buf+="${_line}"

    local _cols=$(tput cols 2>/dev/null || echo 60)
    local _w=$(( _cols < 60 ? _cols : 60 ))
    local _sep="  ${GRY}"
    for (( _s=0; _s < _w - 4; _s++ )); do _sep+="-"; done
    _sep+="${R}\n"
    buf+="${_sep}"

    printf -v _line "  ${D}%-20s %-10s %8s %8s %-14s %s${R}\n" \
        "CONTAINER" "STATUS" "CPU%" "MEM%" "MEM USAGE" "IMAGE"
    buf+="${_line}${_sep}"

    if [[ $running -eq 0 ]]; then
        buf+="  ${GRY}No running containers${R}\n${_sep}"
    elif [[ -n "$stats_data" ]]; then
        while IFS= read -r stat_line; do
            [[ -z "$stat_line" ]] && continue
            local name cpu mem mem_usage
            name=$(echo "$stat_line"      | awk -F$'\t' '{print $1}')
            cpu=$(echo "$stat_line"       | awk -F$'\t' '{print $2}')
            mem=$(echo "$stat_line"       | awk -F$'\t' '{print $3}')
            mem_usage=$(echo "$stat_line" | awk -F$'\t' '{print $4}')

            local image="${IMAGES[$name]:-unknown}"
            image="${image##*/}"
            [[ ${#image} -gt 18 ]] && image="${image:0:15}..."
            [[ ${#name} -gt 18 ]]  && name="${name:0:15}..."

            printf -v _line "  ${WHT}%-20s${R}  ${GRN}%-10s${R} %-16b %-16b ${GRY}%-14s${R} ${GRY}%s${R}\n" \
                "$name" "running" "$(cpu_color "$cpu")" "$(mem_color "$mem")" "$mem_usage" "$image"
            buf+="${_line}"
        done <<< "$stats_data"
        buf+="${_sep}"
    fi

    if [[ -n "$stopped_list" ]]; then
        printf -v _line "\n  ${RED}${B}== Stopped ==${R}\n"
        buf+="${_line}${_sep}"
        printf -v _line "  ${D}%-20s %-30s %s${R}\n" "CONTAINER" "IMAGE" "EXITED"
        buf+="${_line}${_sep}"
        while IFS='|' read -r name image status; do
            image="${image##*/}"
            [[ ${#image} -gt 28 ]] && image="${image:0:25}..."
            [[ ${#name} -gt 18 ]]  && name="${name:0:15}..."
            printf -v _line "  ${RED}%-20s${R} ${GRY}%-30s${R} ${D}%s${R}\n" "$name" "$image" "$status"
            buf+="${_line}"
        done <<< "$stopped_list"
        buf+="${_sep}"
    fi

    # Single write - no flicker
    printf "\033[H\033[J"
    printf "%b" "$buf"
}

cleanup() {
    tput cnorm 2>/dev/null || true
    tput cup 0 0 2>/dev/null || true
    tput ed 2>/dev/null || true
    exit 0
}
trap cleanup INT TERM
trap 'tput cnorm 2>/dev/null || true' EXIT
tput civis

while true; do
    draw
    if read -rsn1 -t "$INTERVAL" key 2>/dev/null; then
        [[ "$key" == "q" || "$key" == "Q" ]] && break
        [[ "$key" == "+" ]] && INTERVAL=$(( INTERVAL + 1 ))
        [[ "$key" == "-" && $INTERVAL -gt 1 ]] && INTERVAL=$(( INTERVAL - 1 )) || true
    fi
done
    }

    CHOICE=$(whiptail --title "R3TR0.SH" --menu "Choose a tool" 18 100 10 \
      "info" "Get Infos about your System" \
      "cpu_stress" "Test your CPU with this Stress test" \
      "f2bgui" "View F2B stats Banned IPs, minimal Server Infos etc" \
      "cache_clear" "Clear Cache/Puffer" \
      "portmap" "Scan open ports and detect firewall exposure" \
      "diskclean" "Clean apt cache, logs, Docker junk and temp files" \
      "ssl" "Check SSL certificate expiry across all domains" \
      "update" "Review and apply pending system updates" \
      "dockstat" "Live resource monitor for all running Docker containers" \
      3>&1 1>&2 2>&3) || continue

    case "$CHOICE" in
      info) info ;;
      cpu_stress) cpu_stress ;;
      f2bgui) f2bgui ;;
      cache_clear) cache_clear ;;
      portmap) portmap ;;
      diskclean) diskclean ;;
      ssl) ssl ;;
      update) update ;;
      dockstat) dockstat ;;
      *) log_err "Invalid option" ;;
    esac
    break
  fi

  if [[ "$BASHTYPE" == "installer" ]]; then

    nodejs() {
      cd ~

sudo apt update && sudo apt upgrade -y

sudo apt install curl -y

curl -sL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install nodejs -y

sudo npm install npm@latest -global

echo -e "\n"
echo -e "Node.js and npm has been installed"
echo -e "\n"
    }

    python() {
      cd ~

sudo apt update && sudo apt upgrade -y

sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev -y

echo -e "\nFetching available Python versions (3.9 to latest)..."
VERSIONS=$(curl -s https://www.python.org/ftp/python/ | grep -oP '3\.\d+\.\d+/' | tr -d '/' | sort -Vr | uniq | grep -E '^3\.(9|10|11|12|13|14|15)\.')

OPTIONS=()
for ver in $VERSIONS; do
    OPTIONS+=("$ver" "")
done

VERSION=$(whiptail --title "R3TR0.SH" --menu "Choose a Python version to install:" 20 60 12 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)

if [ $? -ne 0 ]; then
    echo -e "\nCancelled by user. Exiting.\n"
    exit 1
fi

echo -e "\nSelected Python version: $VERSION\n"

cd ~

echo -e "\nUpdating system and installing build dependencies..."
sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev \
libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev

echo -e "\nDownloading Python $VERSION..."
wget https://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz

echo -e "\nExtracting..."
tar -xf Python-$VERSION.tgz
cd Python-$VERSION/

echo -e "\nConfiguring build..."
./configure --enable-optimizations

echo -e "\nCompiling (this may take a few minutes)..."
make -j$(nproc)

echo -e "\nInstalling using altinstall..."
sudo make altinstall

cd ~

echo -e "\nInstalling pip..."
wget https://bootstrap.pypa.io/get-pip.py
python${VERSION%.*} get-pip.py

echo -e "\nInstalled Python version:"
python${VERSION%.*} --version

echo -e "\nCleaning up..."
rm -r Python-$VERSION/ Python-$VERSION.tgz get-pip.py

echo -e "\n"
echo -e "Python $VERSION and pip have been successfully installed."
echo -e "\n"
    }

    docker() {
      cd ~

OS_CHOICE=$(whiptail --title "R3TR0-SH" --menu "Select your current OS" 18 100 10 \
    "Debian" "" \
    "Raspbian" "" \
    "Ubuntu" "" \
    3>&1 1>&2 2>&3
)

if [ $? -ne 0 ]; then
    echo -e 'No OS selected, exiting.'
    exit 1
fi

OS=$(echo $OS_CHOICE | tr '[:upper:]' '[:lower:]')

sudo apt update && sudo apt upgrade -y
sudo apt install ca-certificates curl -y
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/$OS/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$OS \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose -y

PORTAINER=false

if whiptail --title "R3TR0-SH" --yesno "Do you want to install Portainer?" 8 78; then
    sudo docker volume create portainer_data
    sudo docker run -d -p 9000:9000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest
    PORTAINER=true
fi

echo -e "\n"
echo "Docker has been installed"

if $PORTAINER; then
    echo "Portainer has been installed:"
    echo -e "http://localhost:9000 or 9443\n"
fi

sudo docker --version
sudo docker compose version

echo -e "\n"
    }

    f2b() {
      cd ~

sudo apt update && apt upgrade -y
sudo apt install -y fail2ban wget

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

sudo systemctl enable fail2ban

GUI_INSTALL=false

if whiptail --title "R3TR0.SH" --yesno "Do you want to install the GUI?" 8 78; then
  folder_path=$(whiptail --title "r3tr0 Tools" --inputbox "Enter the folder path where you want to save the script:" 8 78 /root/f2b.sh 3>&1 1>&2 2>&3)
  if [ $? -ne 0 ]; then
    echo "Installation cancelled."
    exit 1
  fi
  folder=$(dirname "$folder_path")
  mkdir -p "$folder"
  wget -q --show-progress --progress=bar:force:noscroll https://sh.r3tr0.eu/f2b.sh -O "$folder_path" || { echo "Failed to download script"; exit 1; }
  chmod +x "$folder_path"
  echo "Script saved to $folder_path"
  GUI_INSTALL=true
fi

if $GUI_INSTALL; then
  echo "To run the script, use the command: bash $folder_path"
else
  echo "To view the list of banned IPs, use the command: sudo fail2ban-client status sshd"
fi

echo -e "\n"
echo -e "Installation complete. You can now manage Fail2Ban. To configure Fail2Ban, edit the /etc/fail2ban/jail.local file."
echo -e "\n"
    }

    lamp() {
      cd ~

OS_CHOICE=$(whiptail --title "R3TR0.SH" --menu "Select your current OS" 18 100 10 \
    "Debian" "" \
    "Ubuntu" "" \
    3>&1 1>&2 2>&3
)

if [ $? -ne 0 ]; then
    echo -e 'No OS selected, exiting.'
    exit 1
fi

OS=$(echo "$OS_CHOICE" | tr '[:upper:]' '[:lower:]')
echo "Selected OS: $OS"

sudo apt update && sudo apt upgrade -y
apt install ca-certificates apt-transport-https lsb-release gnupg curl nano unzip software-properties-common -y

if [ "$OS" = "debian" ]; then
    sudo curl -fsSL https://packages.sury.org/php/apt.gpg -o /usr/share/keyrings/php-archive-keyring.gpg
    echo "deb [signed-by=/usr/share/keyrings/php-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
else
    sudo add-apt-repository ppa:ondrej/php
fi

sudo apt update && sudo apt upgrade -y && sudo apt install apache2 -y

PHP_VERSION=$(whiptail --title "R3TR0.SH" --menu "Select PHP version" 18 100 10 \
    "8.4" "" \
    "8.3" "" \
    "8.2" "" \
    "8.1" "" \
    "7.4" "" \
    3>&1 1>&2 2>&3
)

if [ $? -ne 0 ]; then
    echo -e 'No PHP version selected, exiting.'
    exit 1
fi

PHP=$(echo "$PHP_VERSION" | tr '[:upper:]' '[:lower:]')
PHP_V=$PHP
if [ "$PHP" = "8.4" ]; then
  PHP=""
fi
sudo apt install \
  php${PHP} \
  php${PHP}-cli \
  php${PHP}-common \
  php${PHP}-curl \
  php${PHP}-gd \
  php${PHP}-intl \
  php${PHP}-mbstring \
  php${PHP}-mysql \
  php${PHP_V}-opcache \
  php${PHP}-readline \
  php${PHP_V}-xml \
  php${PHP_V}-xsl \
  php${PHP}-zip \
  php${PHP}-bz2 \
  libapache2-mod-php${PHP} -y

MYSQL_SYSTEM=$(whiptail --title "R3TR0.SH" --menu "Select your MySQL system" 18 100 10 \
    "MariaDB" "" \
    "MySQL" "" \
    3>&1 1>&2 2>&3
)
if [ $? -ne 0 ]; then
    echo -e 'No MySQL system selected, exiting.'
    exit 1
fi
MYSQL=$(echo "$MYSQL_SYSTEM" | tr '[:upper:]' '[:lower:]')
sudo apt install ${MYSQL}-server ${MYSQL}-client -y

echo -e "\n\n"
echo "Please complete the MySQL installation"

if [ "$MYSQL" = "mariadb" ]; then
	sudo mariadb-secure-installation
else
	sudo mysql_secure_installation
fi

if whiptail --title "R3TR0.SH" --yesno "Do you want to install phpMyAdmin?" 8 78; then
    cd /usr/share/ || mkdir /usr/share/ && cd /usr/share/ || exit
    sudo wget https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.zip -O phpmyadmin.zip
    sudo unzip phpmyadmin.zip && rm phpmyadmin.zip
    sudo mv phpMyAdmin-*-all-languages phpmyadmin
    sudo chmod -R 0755 phpmyadmin
    sudo touch /etc/apache2/conf-available/phpmyadmin.conf
    sudo echo -e "Alias /phpmyadmin /usr/share/phpmyadmin\n\n<Directory /usr/share/phpmyadmin>\n    Options SymLinksIfOwnerMatch\n    DirectoryIndex index.php\n</Directory>\n\n# Disallow web access to directories that dont need it\n<Directory /usr/share/phpmyadmin/templates>\n    Require all denied\n</Directory>\n<Directory /usr/share/phpmyadmin/libraries>\n    Require all denied\n</Directory>\n<Directory /usr/share/phpmyadmin/setup/lib>\n    Require all denied\n</Directory>" > /etc/apache2/conf-available/phpmyadmin.conf
    sudo a2enconf phpmyadmin
    sudo systemctl reload apache2
    sudo mkdir /usr/share/phpmyadmin/tmp/
    sudo chown -R www-data:www-data /usr/share/phpmyadmin/tmp/
    echo -e "\n\n"
    echo "phpMyAdmin has been installed"
else
    echo -e "\n\n"
    echo "phpMyAdmin has not been installed"
fi

# If system version is Debian 10 or Ubuntu run commands below
if [ "$OS" = "debian" ] && [ "$(lsb_release -rs)" = "10" ] || [ "$OS" = "ubuntu" ]; then
    sudo mysql -u root --execute="UPDATE mysql.user SET plugin = 'mysql_native_password' WHERE user = 'root' AND plugin = 'unix_socket';FLUSH PRIVILEGES;"
fi

# Enable Apache modules for Rewrite and Headers and Proxy
sudo a2enmod rewrite headers proxy proxy_http proxy_balancer

if whiptail --title "R3TR0.SH" --yesno "Do you want to install Composer?" 8 78; then
    cd /usr/local/bin || exit
    sudo curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    echo -e "\n\n"
    echo "Composer has been installed"
fi

if whiptail --title "R3TR0.SH" --yesno "Do you want to install Certbot?" 8 78; then
    sudo apt install certbot python3-certbot-apache -y
    echo -e "\n\n"
    echo "Certbot has been installed, with the command 'certbot --apache' you can now obtain a free SSL certificate"
fi

echo -e "\n"
echo "LAMP stack has been installed"
echo -e "\n"
sudo systemctl restart apache2
    }

    github_cli() {
      cd ~

sudo apt update && sudo apt upgrade -y

(type -p wget >/dev/null || (sudo apt update && sudo apt install wget -y)) \
	&& sudo mkdir -p -m 755 /etc/apt/keyrings \
	&& out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \
	&& cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
	&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
	&& sudo mkdir -p -m 755 /etc/apt/sources.list.d \
	&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
	&& sudo apt update \
	&& sudo apt install gh -y

echo -e "\n"
echo -e "Github CLI (gh) has been installied\nTo log in execute the following command from 'gh auth login'"
echo -e "\n"
    }

    CHOICE=$(whiptail --title "R3TR0.SH" --menu "Choose a installer" 18 100 10 \
      "nodejs" "Install LTS NodeJS and npm" \
      "python" "Install any Python version from 3.7 to the latest with pip" \
      "docker" "Install Docker and Portainer" \
      "f2b" "Install F2B with overview GUI" \
      "lamp" "Install LAMP stack (Linux, Apache, MySQL, PHP)" \
      "github_cli" "Install Github CLI (gh)" \
      3>&1 1>&2 2>&3) || continue

    case "$CHOICE" in
      nodejs) nodejs ;;
      python) python ;;
      docker) docker ;;
      f2b) f2b ;;
      lamp) lamp ;;
      github_cli) github_cli ;;
      *) log_err "Invalid option" ;;
    esac
    break
  fi

done