#!/usr/bin/env bash
#
# Name: Unified MOTD
# Author: kleb
# Created: 2025/05/05
# Last modified: 2025/12/01
#
# Version: 1.1
#

currentName="Unified MOTD"
currentVersion="1.1"

declare -A colors=(
    ["MAGENTA"]=$'\033[1;35m'
    ["RED"]=$'\033[1;31m'
    ["ORANGE"]=$'\033[38;5;208m'
    ["GREEN"]=$'\033[1;32m'
    ["YELLOW"]=$'\033[1;33m'
    ["CYAN"]=$'\033[1;36m'
    ["WHITE"]=$'\033[1;37m'
    ["BOLD"]=$'\033[1m'
    ["RESET"]=$'\033[0m'
)

startTime=$(date +%s.%N)

function formatSize() {
    kb=$1

    if ((kb >= 1024 * 1024)); then
        awk -v kb="$kb" 'BEGIN { printf "%.2fGB", kb/1024/1024 }'
    elif ((kb >= 1024)); then
        awk -v kb="$kb" 'BEGIN { printf "%.2fMB", kb/1024 }'
    else
        printf "%dKB" "$kb"
    fi
}

function colorize() {
    local percent=$1

    if ((percent >= 90)); then
        echo "${colors[RED]}"
    elif ((percent >= 80)); then
        echo "${colors[ORANGE]}"
    elif ((percent >= 60)); then
        echo "${colors[YELLOW]}"
    else
        echo "${colors[GREEN]}"
    fi
}

function getZabbixStatus() {
    service=$1

    if systemctl list-unit-files | grep -q "^$service\.service"; then
        status=$(systemctl is-active "$service" 2>/dev/null)

        case "$status" in
        "active")
            echo "${colors[GREEN]}installed${colors[RESET]} - ${colors[GREEN]}active${colors[RESET]}"
            ;;
        "inactive")
            echo "${colors[GREEN]}installed${colors[RESET]} - ${colors[ORANGE]}inactive${colors[RESET]}"
            ;;
        *)
            echo "${colors[GREEN]}installed${colors[RESET]} - ${colors[RED]}$status${colors[RESET]}"
            ;;
        esac
    else
        echo "${colors[ORANGE]}not installed${colors[RESET]}"
    fi
}

function printBanner() {
    klebHosting=(
        " _    _      _                     _   _"
        "| | _| | ___| |__   /\\  /\\___  ___| |_(_)_ __   __ _"
        "| |/ / |/ _ \\ '_ \\ / /_/ / _ \\/ __| __| | '_ \\ / _\` |"
        "|   <| |  __/ |_) / __  / (_) \\__ \\ |_| | | | | (_| |"
        "|_|\\_\\_|\\___|_.__/\\/ /_/ \\___/|___/\\__|_|_| |_|\\__, |"
        "                                               |___/"
    )

    brain=(
        "    @@@@@  @@@@     "
        "  @@@  @@@@@   @@@  "
        " @@@@@@ @@@@@@@@@@@@"
        "@@       @@@@  @@  @"
        "@@ @@ @@ @@   @@@@@@"
        "  @@@@@     @@   @@@"
        "    @@@@@@@@   @@@  "
        "           @@ @@    "
        "            @@@@    "
    )

    klebLength=${#klebHosting[@]}
    brainLength=${#brain[@]}
    offset=2

    endTime=$(date +%s.%N)

    if command -v bc >/dev/null; then
        elapsed=$(echo "$endTime - $startTime" | bc)
    else
        elapsed=$(awk "BEGIN {printf \"%.3f\", $endTime - $startTime}")
    fi
    elapsedFormatted=$(printf "%.3fs" "$elapsed")

    for ((i = 0; i < brainLength; i++)); do
        if ((i >= offset && i < offset + klebLength)); then
            bannerIndex=$((i - offset))
            printf "%b%-55s%b" "${colors[CYAN]}${colors[BOLD]}" "${klebHosting[$bannerIndex]}" "${colors[RESET]}"
        else
            printf "%-55s" ""
        fi

        printf "%b%s%b" "${colors[MAGENTA]}${colors[BOLD]}" "${brain[$i]}" "${colors[RESET]}"
        printf "\n"

        if ((i == offset + klebLength - 1)); then
            printf "%b%-5s%b" "${colors[MAGENTA]}${colors[BOLD]}" "MOTD Version: $currentVersion | " "${colors[RESET]}"
            printf "%b%-35s%b" "${colors[MAGENTA]}${colors[BOLD]}" "Script runtime: $elapsedFormatted" "${colors[RESET]}"

            nextBrainLine=$((i + 1))

            if ((nextBrainLine < brainLength)); then
                printf "%b%s%b" "${colors[MAGENTA]}${colors[BOLD]}" "${brain[$nextBrainLine]}" "${colors[RESET]}"
            fi

            printf "\n"
            ((i++))
        fi
    done

    printf "\n"
}

function getSystemInfo() {
    if [[ -f /etc/os-release ]]; then
        source /etc/os-release
        osPretty=$PRETTY_NAME
    else
        osPretty=$(lsb_release -ds)
    fi

    kernel=$(uname -r)
    arch=$(uname -m)
    datetime=$(date "+%Y/%m/%d %H:%M:%S %Z")
    hostname=$(hostname)
    uptime=$(uptime -p)
    language=${LANG:-$(locale | awk -F= '/LANG=/ {print $2}' | head -n1)}

    if command -v timedatectl >/dev/null; then
        timezone=$(timedatectl | awk -F: '/Time zone/ {print $2}' | xargs)
    else
        timezone=$(cat /etc/timezone 2>/dev/null)
    fi

    mailCount=0

    for mailDirectory in /var/mail /var/spool/mail; do
        mailFile="$mailDirectory/root"

        if [[ -f "$mailFile" && -r "$mailFile" ]]; then
            count=$(grep -c "^From " "$mailFile" 2>/dev/null || echo 0)
            mailCount=$((mailCount + count))
        fi
    done
}

function getResourceInfo() {
    read -r loadavg _ _ _ _ </proc/loadavg
    cpuCores=$(nproc)
    processCount=$(ps -e --no-headers | wc -l)
    userCount=$(who | wc -l)

    memInfo=$(free)
    memTotal=$(echo "$memInfo" | awk '/^Mem:/ {print $2}')
    memUsed=$(echo "$memInfo" | awk '/^Mem:/ {print $3}')
    memPercentage=$(echo "$memInfo" | awk '/^Mem:/ {printf("%.0f", $3/$2 * 100)}')

    swapTotal=$(echo "$memInfo" | awk '/^Swap:/ {print $2}')
    swapUsed=$(echo "$memInfo" | awk '/^Swap:/ {print $3}')
    swapPercentage=0

    [[ $swapTotal -gt 0 ]] && swapPercentage=$(echo "$memInfo" | awk '/^Swap:/ {printf("%.0f", $3/$2 * 100)}')

    diskInfo=$(df -h /)
    diskTotal=$(echo "$diskInfo" | awk 'NR==2 {print $2}')
    diskUsedPercentage=$(echo "$diskInfo" | awk 'NR==2 {print $5}')

    if [[ ! "$diskTotal" =~ [A-Za-z]$ ]]; then
        diskTotal="${diskTotal}B"
    elif [[ ! "$diskTotal" =~ B$ ]]; then
        diskTotal="${diskTotal}B"
    fi
}

function getNetworkInfo() {
    nicInfo=()
    nics=()
    ipv4s=()
    ipv6s=()
    states=()
    label4s=()
    label6s=()

    maxIpLength=0
    maxLabelIpWidth=0
    labelWidth=30

    while read -r _ nic _ ip _; do
        nic=$(echo "$nic" | xargs)
        ipAddress="${ip%%/*}"

        if [[ $ipAddress != "127.0.0.1" ]]; then
            nics+=("$nic")
            ipv4s+=("$ipAddress")
        fi
    done < <(ip -o -4 addr show)

    for nic in "${nics[@]}"; do
        nicState=$(ip -o link show "$nic" | awk '{for(i=1;i<=NF;i++) if($i=="state") print $(i+1)}')
        states+=("$nicState")

        ipv6=$(ip -o -6 addr show dev "$nic" scope global | awk '{print $4}' | head -n1)
        ipv6="${ipv6%%/*}"
        if [[ ${#ipv6} -gt 39 ]]; then
            ipv6="${ipv6:0:36}..."
        fi
        ipv6s+=("$ipv6")

        label4s+=("IPv4 address for $nic:")
        label6s+=("IPv6 address for $nic:")
    done

    for ip in "${ipv4s[@]}" "${ipv6s[@]}"; do
        ((${#ip} > maxIpLength)) && maxIpLength=${#ip}
    done

    labelIpLines=()

    for i in "${!nics[@]}"; do
        label4="${label4s[$i]}"
        ipv4="${ipv4s[$i]}"
        labelIpLines+=("$(printf "%-${labelWidth}s %-${maxIpLength}s" "$label4" "$ipv4")")

        ipv6="${ipv6s[$i]}"

        if [[ -n "$ipv6" ]]; then
            label6="${label6s[$i]}"
            labelIpLines+=("$(printf "%-${labelWidth}s %-${maxIpLength}s" "$label6" "$ipv6")")
        fi
    done

    for line in "${labelIpLines[@]}"; do
        ((${#line} > maxLabelIpWidth)) && maxLabelIpWidth=${#line}
    done

    for i in "${!nics[@]}"; do
        nicState="${states[$i]}"
        stateColor="${colors[GREEN]}[UP]${colors[RESET]}"
        [[ "$nicState" != "UP" ]] && stateColor="${colors[RED]}[DOWN]${colors[RESET]}"

        label4="${label4s[$i]}"
        ipv4="${ipv4s[$i]}"
        labelIp=$(printf "%-${labelWidth}s %-${maxIpLength}s" "$label4" "$ipv4")
        nicInfo+=("    ${colors[WHITE]}$(printf "%-${maxLabelIpWidth}s" "$labelIp")${colors[RESET]}   $stateColor")

        ipv6="${ipv6s[$i]}"

        if [[ -n "$ipv6" ]]; then
            label6="${label6s[$i]}"
            labelIp=$(printf "%-${labelWidth}s %-${maxIpLength}s" "$label6" "$ipv6")
            nicInfo+=("    ${colors[WHITE]}$(printf "%-${maxLabelIpWidth}s" "$labelIp")${colors[RESET]}   $stateColor")
        fi
    done
}

function getUpdateInfo() {
    updateCache="/var/cache/motd-updates"
    cacheMaxAge=3600

    if command -v apt-get >/dev/null; then
        needsRefresh=1

        if [[ -f "$updateCache" ]]; then
            cacheAge=$(($(date +%s) - $(stat -c %Y "$updateCache" 2>/dev/null || echo 0)))
            [[ $cacheAge -lt $cacheMaxAge ]] && needsRefresh=0
        fi

        if [[ $needsRefresh -eq 1 ]]; then
            aptUpgrade=$(apt-get -s upgrade 2>/dev/null)
            updates=$(echo "$aptUpgrade" | grep -c "^Inst")
            securityUpgrades=$(echo "$aptUpgrade" | grep -c "\-security")
            deferred=$(apt-get --just-print upgrade 2>/dev/null | awk '/deferred due to phasing:/ {getline; print}' | wc -w)
            echo "$updates $securityUpgrades $deferred" >"$updateCache" 2>/dev/null || true
        else
            read -r updates securityUpgrades deferred <"$updateCache"
        fi
    else
        updates="N/A"
        securityUpgrades="N/A"
        deferred="N/A"
    fi
}

function getSecurityInfo() {
    lastLogin=$(last -w -F root | grep -v "still logged in" | head -n 1 | awk '{print $4, $5, $6, $7, $8}')
    failedSSH=$(journalctl _COMM=sshd --since "24 hours ago" 2>/dev/null | grep -c "Failed password")
}

function getServiceStatus() {
    serviceStatusLines=()
    services=("nginx" "mysql" "mariadb" "redis" "redis-server" "mssql-server" "mongod" "postgresql" "postgres")
    serviceLabels=("Nginx" "MySQL" "MariaDB" "Redis" "Redis" "MSSQL" "MongoDB" "PostgreSQL" "PostgreSQL")

    for i in "${!services[@]}"; do
        service="${services[$i]}"
        label="${serviceLabels[$i]}"

        if systemctl list-unit-files 2>/dev/null | grep -q "^$service\.service"; then
            status=$(systemctl is-active "$service" 2>/dev/null)

            case "$status" in
            "active")
                serviceStatusLines+=("$label:${colors[GREEN]}active${colors[RESET]}")
                ;;
            "inactive")
                serviceStatusLines+=("$label:${colors[ORANGE]}inactive${colors[RESET]}")
                ;;
            *)
                serviceStatusLines+=("$label:${colors[RED]}$status${colors[RESET]}")
                ;;
            esac
        fi
    done
}

function getStorageStatus() {
    zfsStatusLines=()
    mdadmStatusLines=()

    zfsPoolMaxLength=0
    mdadmArrayMaxLength=0

    zfsPoolsRaw=()
    mdadmArraysRaw=()

    statusColumn=35

    if command -v zpool >/dev/null; then
        zfsPoolsRaw=()
        zfsPoolMaxLength=0

        while IFS=$'\t' read -r pool health; do
            zfsPoolsRaw+=("$pool $health")
            ((${#pool} > zfsPoolMaxLength)) && zfsPoolMaxLength=${#pool}
        done < <(zpool list -H -o name,health 2>/dev/null | sort)

        for entry in "${zfsPoolsRaw[@]}"; do
            pool=$(awk '{print $1}' <<<"$entry")
            health=$(awk '{print $2}' <<<"$entry")

            if [[ "$health" == "ONLINE" ]]; then
                color="${colors[GREEN]}"
            else
                color="${colors[RED]}"
            fi

            label="    ${colors[WHITE]}${pool}:${colors[RESET]}"
            pad=$((statusColumn - ${#pool} - 5))
            ((pad < 1)) && pad=1
            zfsStatusLines+=("${label}$(printf '%*s' "$pad" "")${color}${health}${colors[RESET]}")
        done
    fi

    if command -v mdadm >/dev/null; then
        mdadmArraysRaw=()
        mdadmArrayMaxLength=0

        while read -r array status _; do
            [[ $array =~ ^md ]] || continue
            mdadmArraysRaw+=("$array $status")
            ((${#array} > mdadmArrayMaxLength)) && mdadmArrayMaxLength=${#array}
        done < <(awk '/^md/ {print $1, $3, $4}' /proc/mdstat 2>/dev/null | sort)

        for entry in "${mdadmArraysRaw[@]}"; do
            array=$(awk '{print $1}' <<<"$entry")
            status=$(awk '{print $2}' <<<"$entry")

            if [[ "$status" == "active" ]]; then
                color="${colors[GREEN]}"
            else
                color="${colors[RED]}"
            fi

            label="    ${colors[WHITE]}${array}:${colors[RESET]}"
            pad=$((statusColumn - ${#array} - 5))
            ((pad < 1)) && pad=1

            mdadmStatusLines+=("${label}$(printf '%*s' "$pad" "")${color}${status}${colors[RESET]}")
        done
    fi

}

function printMotd() {
    memTotalFormatted=$(formatSize "$memTotal")
    swapTotalFormatted=$(formatSize "$swapTotal")

    zabbixAgentStatus=$(getZabbixStatus zabbix-agent)
    zabbixAgent2Status=$(getZabbixStatus zabbix-agent2)

    diskUsedPercentNum=$(echo "$diskUsedPercentage" | tr -d '%')
    diskColor=$(colorize "$diskUsedPercentNum")
    memColor=$(colorize "$memPercentage")
    swapColor=$(colorize "$swapPercentage")

    load=$(echo "$loadavg" | awk '{print $1}')
    if command -v bc >/dev/null; then
        loadint=$(printf "%.0f" "$(echo "$load * 100 / $cpuCores" | bc -l)")
    else
        loadint=$(awk "BEGIN {printf \"%.0f\", $load * 100 / $cpuCores}")
    fi
    loadColor=$(colorize "$loadint")

    printBanner

    printf "${colors[YELLOW]}${colors[BOLD]}Welcome to %s (GNU/Linux %s %s)${colors[RESET]}\n" "$osPretty" "$kernel" "$arch"
    printf "${colors[YELLOW]}${colors[BOLD]}System information as of %s${colors[RESET]}\n\n" "$datetime"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Hostname:" "$hostname"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Uptime:" "$uptime"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Timezone:" "$timezone"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Language:" "$language"
    printf "\n"

    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %b%s%b\n" "System load:" "$loadColor" "$loadavg" "${colors[RESET]}"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Processes:" "$processCount"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Users logged in:" "$userCount"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "CPU cores:" "$cpuCores"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %b%s of %s%b\n" "Usage of /:" "$diskColor" "$diskUsedPercentage" "$diskTotal" "${colors[RESET]}"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %b%s%% of %s%b\n" "Memory usage:" "$memColor" "$memPercentage" "$memTotalFormatted" "${colors[RESET]}"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %b%s%% of %s%b\n" "Swap usage:" "$swapColor" "$swapPercentage" "$swapTotalFormatted" "${colors[RESET]}"
    printf "\n"

    for line in "${nicInfo[@]}"; do
        printf "%s\n" "$line"
    done

    [[ ${#nicInfo[@]} -gt 0 ]] && printf "\n"

    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Updates pending:" "$updates"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Security updates:" "$securityUpgrades"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Deferred phased updates:" "$deferred"
    printf "\n"

    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Zabbix agent:" "$zabbixAgentStatus"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Zabbix agent 2:" "$zabbixAgent2Status"

    if [[ ${#serviceStatusLines[@]} -gt 0 ]]; then
        printf "\n"
        for entry in "${serviceStatusLines[@]}"; do
            label="${entry%%:*}"
            status="${entry#*:}"
            printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "$label:" "$status"
        done
    fi
    printf "\n"

    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Last login:" "$lastLogin"
    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Failed SSH logins (24h):" "$failedSSH"
    printf "\n"

    if [[ ${#zfsStatusLines[@]} -gt 0 ]]; then
        printf "    ${colors[WHITE]}ZFS pools:${colors[RESET]}\n"

        for line in "${zfsStatusLines[@]}"; do
            printf "%s\n" "$line"
        done

        printf "\n"
    fi

    if [[ ${#mdadmStatusLines[@]} -gt 0 ]]; then
        printf "    ${colors[WHITE]}MDADM arrays:${colors[RESET]}\n"

        for line in "${mdadmStatusLines[@]}"; do
            printf "%s\n" "$line"
        done

        printf "\n"
    fi

    printf "    ${colors[WHITE]}%-30s${colors[RESET]} %s\n" "Mails:" "$mailCount"

    if [[ -f /var/run/reboot-required ]]; then
        printf "\n"
        printf "    ${colors[ORANGE]}*** System restart required ***${colors[RESET]}\n"
    fi
    printf "\n"
}

getSystemInfo
getResourceInfo
getNetworkInfo
getUpdateInfo
getSecurityInfo
getServiceStatus
getStorageStatus

printMotd

exit 0
