#!/usr/bin/env bash
# ==========================================================================
#         ____            _                     _____           _
#        / ___| _   _ ___| |_ ___ _ __ ___     |_   _|__   ___ | |___
#        \___ \| | | / __| __/ _ \ '_ ` _ \ _____| |/ _ \ / _ \| / __|
#         ___) | |_| \__ \ ||  __/ | | | | |_____| | (_) | (_) | \__ \
#        |____/ \__, |___/\__\___|_| |_| |_|     |_|\___/ \___/|_|___/
#               |___/
#                             --- System-Tools ---
#                  https://www.nntb.no/~dreibh/system-tools/
# ==========================================================================
#
# System-Info
# Copyright (C) 2013-2025 by Thomas Dreibholz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact: thomas.dreibholz@gmail.com

# Bash options:
set -eu


export TEXTDOMAIN="System-Info"
# export TEXTDOMAINDIR="${PWD}/locale"   # Default: "/usr/share/locale"

# shellcheck disable=SC1091
. gettext.sh


# ###### Settings ###########################################################
indent=-20

colorBasic="\e[33m"
colorNetwork="\e[34m"
colorPrefix="\e[36m"
colorInactive="\e[37m"
colorNone="\e[0m"

colorBatteryFull="\e[33m"
colorBatteryCharging="\e[31m"
colorBatteryNotCharging="\e[38m"
colorBatteryFull="\e[32m"
colorBatteryDischarging="\e[35m"

colorDiskNormal="\e[32m"
colorDisk80="\e[33m"
colorDisk95="\e[31m"


# ###### Print banners ######################################################
function print-banners ()
{
   # shellcheck disable=SC2068
   for directory in $@ ; do
      if [ -d "${directory}" ] ; then
         bannerScripts=$(find "${directory}" -maxdepth 1 -type f -name "[0-9][0-9]*[^~]" | sort -r)
         for bannerScript in ${bannerScripts} ; do
            ${bannerScript} || break
         done
      fi
   done
}


# ###### Print system information ###########################################
function print-system-information ()
{
   # ====== Obtain system information =======================================
   if ! which get-system-info >/dev/null ; then
      gettext >&2 "ERROR: get-system-info not found in search path!"
      echo >&2
      exit 1
   fi
   # For ShellCheck:
   hostname_long=
   uptime_days=
   uptime_hours=
   uptime_mins=
   uptime_secs=
   system_release=
   system_users=
   system_cores=
   system_machine=
   system_procs=
   system_load_avg1min=
   system_load_avg5min=
   system_load_avg15min=
   battery_list=
   system_mem_used=
   system_mem_total=
   system_mem_free=
   system_mem_freepct=
   system_swap_total=
   system_swap_used=
   system_swap_free=
   system_swap_freepct=
   disk_list=
   netif_list=
   # Set the variables:
   eval "$(get-system-info)"

   # ====== Obtain operating system information =============================
   DISTRIB_ID="$(gettext "Unknown")"   # Unknown, yet.
   DISTRIB_RELEASE="${DISTRIB_ID}"     # Unknown, yet.
   DISTRIB_CODENAME=""

   # ------ Get information from /etc/os-release ----------------------------
   if [ -e /etc/os-release ] ; then
      # shellcheck disable=SC1091
      . /etc/os-release
      if [ -v NAME ] ; then
         DISTRIB_ID="${NAME}"
      fi
      if [ -v VERSION_ID ] ; then
         DISTRIB_RELEASE="${VERSION_ID}"
      fi
      if [ -v VERSION ] && [[ "${VERSION}" =~ ^.*\(([^\(\)]*)\)$ ]] ; then
         DISTRIB_CODENAME="${BASH_REMATCH[1]}"
      elif [ -v VERSION_CODENAME ] ; then
         DISTRIB_CODENAME="${VERSION_CODENAME}"
      fi

   # ------ Legacy system: try lsb_release ----------------------------------
   elif [ -x /usr/bin/lsb_release ] ; then
      DISTRIB_ID="$(/usr/bin/lsb_release -is)"
      DISTRIB_RELEASE="$(/usr/bin/lsb_release -rs)"
      DISTRIB_CODENAME="$(/usr/bin/lsb_release -cs)"
   fi

   local codeNameText=""
   if [ "${DISTRIB_CODENAME}" != "" ] ; then
      codeNameText=" (${DISTRIB_CODENAME})"
   fi
   os_label="${DISTRIB_ID} ${DISTRIB_RELEASE}${codeNameText}"


   # ====== Print system information ========================================
   echo -en "${colorBasic}"

   # ------ Hostname -------------------------------------------------------
   print-utf8 -i "${indent}" "$(gettext "Host:")"
   echo "${hostname_long}"

   # ------ Uptime ----------------------------------------------------------
   print-utf8 -i "${indent}" "$(gettext "Uptime:")"
   # shellcheck disable=SC2059
   printf "$(gettext "%s, %s, %s, and %s")\n" \
      "$(printf "$(ngettext "%'d day" "%'d days" "${uptime_days}")" "${uptime_days}")" \
      "$(printf "$(ngettext "%'d hour" "%'d hours" "${uptime_hours}")" "${uptime_hours}")" \
      "$(printf "$(ngettext "%'d minute" "%'d minutes" "${uptime_mins}")" "${uptime_mins}")" \
      "$(printf "$(ngettext "%'d second" "%'d seconds" "${uptime_secs}")" "${uptime_secs}")"

   # ------ Operating System ------------------------------------------------
   print-utf8 -i "${indent}" "$(gettext "Operating System:")"
   printf "%s $(gettext "with kernel") %s\n" \
      "${os_label}" \
      "${system_release}"

   # ------ Processor -------------------------------------------------------
   print-utf8 -i "${indent}" "$(gettext "Processor:")"
   processesString="$(gettext "%'d processes")"
   usersString="$(ngettext "%'d user" "%'d users" "${system_users}")"
   # shellcheck disable=SC2059
   printf "$(gettext "%d × %s; ${processesString}; ${usersString}")\n" \
      "${system_cores}" \
      "${system_machine}" \
      "${system_procs}" \
      "${system_users}"

   # ------ Load ------------------------------------------------------------
   print-utf8 -i "${indent}" "$(gettext "Load (1/5/15 min):")"
   # shellcheck disable=SC2059
   printf "$(gettext "%1.1f %% / %1.1f %% / %1.1f %%")\n" \
      "${system_load_avg1min}" \
      "${system_load_avg5min}" \
      "${system_load_avg15min}"

   # ------ Battery ---------------------------------------------------------
   # --- TEST ---------------------------
   # battery_list="0 1 2 3 4"
   # battery_0_status=0
   # battery_0_capacity=55
   # battery_1_status=1
   # battery_1_capacity=55
   # battery_2_status=2
   # battery_2_capacity=55
   # battery_3_status=3
   # battery_3_capacity=55
   # battery_4_status=4
   # battery_4_capacity=55
   # --- TEST ---------------------------
   if [ "${battery_list}" != "" ] ; then
      print-utf8 -i "${indent}" "$(gettext "Battery:")"
      local battery
      local statusVariable
      local capacityVariable
      local status
      local colorBattery
      local capacity
      local number=0
      for battery in ${battery_list} ; do
         number=$((number+1))
         if [ ${number} -gt 1 ] ; then
            echo -n " / "
         fi
         statusVariable="battery_${battery}_status"
         capacityVariable="battery_${battery}_capacity"
         status="$(gettext "unknown")"
         colorBattery="${colorInactive}"
         if [[ "${!statusVariable}" =~ ^[0-9]+$ ]] ; then
            if [ "${!statusVariable}" -eq 1 ] ; then
               status="$(gettext "not charging")"
               colorBattery="${colorBatteryNotCharging}"
            elif [ "${!statusVariable}" -eq 2 ] ; then
               status="$(gettext "charging")"
               colorBattery="${colorBatteryCharging}"
            elif [ "${!statusVariable}" -eq 3 ] ; then
               status="$(gettext "full")"
               colorBattery="${colorBatteryFull}"
            elif [ "${!statusVariable}" -eq 4 ] ; then
               status="$(gettext "discharging")"
               colorBattery="${colorBatteryDischarging}"
            fi
         fi
         capacity="${!capacityVariable}"
         # shellcheck disable=SC2059
         printf "${colorBattery}$(gettext "#%u %s, %u %%")${colorBasic}" \
            "${number}" \
            "${status}" \
            "${capacity}"
      done
      echo
   fi

   # ------ Memory ---------------------------------------------------------
   print-utf8 -i "${indent}" "$(gettext "Used Memory:")"
   # shellcheck disable=SC2059
   printf "$(gettext "%'6.0f MiB of %'6.0f MiB (%'6.0f MiB available: %'5.1f %%)")\n" \
      "$((system_mem_used  / 1048576))" \
      "$((system_mem_total / 1048576))" \
      "$((system_mem_free  / 1048576))" \
      "${system_mem_freepct}"
   if [ "${system_swap_total}" != "" ] && [ "${system_swap_total}" -gt 0 ] ; then
      print-utf8 -i "${indent}" "$(gettext "Used Swap:")"
      # shellcheck disable=SC2059
      printf "$(gettext "%'6.0f MiB of %'6.0f MiB (%'6.0f MiB available: %'5.1f %%)")\n" \
      "$((system_swap_used  / 1048576))" \
      "$((system_swap_total / 1048576))" \
      "$((system_swap_free  / 1048576))" \
      "${system_swap_freepct}"
   else
      print-utf8 -i "${indent}" "$(gettext "Used Swap:")"
      echo -e "${colorInactive}$(gettext "no swap available!")${colorBasic}"
   fi

   # ------ Storage ---------------------------------------------------------
   print-utf8 -i "${indent}" "$(gettext "Used Diskspace:")"
   local mountPointVariable
   local mountPointName
   local mountPoint
   local percentage
   local colorDisk
   for mountPointName in ${disk_list} ; do
      if [ "${mountPointName}" == "root" ] ; then
         mountPoint="/"
      else
         mountPoint="/${mountPointName}"
         echo -n ", "
      fi
      mountPointVariable="disk_${mountPointName}_pct"
      if [ -v "${mountPointVariable}" ] && [[ "${!mountPointVariable}" =~ ^[0-9]+ ]]; then
         percentage="${!mountPointVariable//[^0-9]*/}"
         if [ "${percentage}" -ge 95 ] ; then
            colorDisk="${colorDisk95}"
         elif [ "${percentage}" -ge 80 ] ; then
            colorDisk="${colorDisk80}"
         else
            colorDisk="${colorDiskNormal}"
         fi
         # shellcheck disable=SC2059
         printf "${colorDisk}$(gettext "%1.0f %% on %s")${colorBasic}" \
            "${!mountPointVariable}" \
            "${mountPoint}"
      fi
   done
   echo

   # ------ SSH Key Fingerprints --------------------------------------------
   print-ssh-key-fingerprints

   # ------ Network ---------------------------------------------------------
   echo -en "${colorBasic}"
   print-utf8 -n -i "${indent}" "$(gettext "Network:")"
   local indentString
   indentString="$(print-utf8 -i "${indent}" "")"
   for interface in ${netif_list} ; do
      local nameVariable="netif_${interface}_name"
      local flagsVariable="netif_${interface}_flags"
      local ipv4Variable="netif_${interface}_ipv4"
      local ipv6Variable="netif_${interface}_ipv6"

      if [ -v "${ipv4Variable}" ] || [ -v "${ipv6Variable}" ] ; then

         if [[ "${!flagsVariable}" =~ (<UP>) ]] ; then
            # Interface is UP
            colorA="${colorNetwork}"
            colorP="${colorPrefix}"
         else
            # Interface is DOWN
            colorA="${colorInactive}"
            colorP="${colorInactive}"
         fi

         local ipv4
         local ipv6
         if [ -v "${ipv4Variable}" ] ; then
            ipv4="${!ipv4Variable}"
            ipv4="${ipv4// /${colorA} }"
            ipv4="${ipv4//\//${colorP}\/}"
            ipv4="${ipv4// /   }"
            ipv4="${ipv4//\// \/ }"
         else
            ipv4="$(gettext "(No IPv4)")"
         fi
         if [ -v "${ipv6Variable}" ] ; then
            ipv6="${!ipv6Variable}"
            ipv6="${ipv6// /${colorA} }"
            ipv6="${ipv6//\//${colorP}\/}"
            ipv6="${ipv6// /\\n${indentString}}"
            ipv6="${ipv6//\// \/ }"
            ipv6="${ipv6//%/%%}"
            ipv6="\n${indentString}${ipv6}"
         else
            ipv6=""
         fi

         print-utf8 -i "${indent}" "   ${colorA}${!nameVariable}:"
         echo -e "${ipv4}${colorA}${ipv6}${colorA}"

      fi

   done

   echo -en "${colorNone}"
}


# ###### Print SSH public key fingerprints ##################################
function print-ssh-key-fingerprints ()
{
   if [ -d /etc/ssh ] ; then
      echo -en "${colorBasic}"
      local number=1
      local label=""
      local keys
      keys="$(find /etc/ssh -maxdepth 1 -name "ssh_host_*.pub" | sort)"
      for key in $keys ; do
         keyInfo="$(ssh-keygen -lf "$key" | sed -e "s/^\([0-9]*\) \([^ ]*\) \(.*\) (\(.*\))$/\2 (\4 \1)/g")"
         if [[ "${keyInfo}" =~ ^[A-Z]+ ]] ; then
             if [ ${number} -eq 1 ] ; then
               label="$(gettext "SSH Keys:")"
            else
               label=""
            fi
            print-utf8 -i "${indent}" "${label}"
            # shellcheck disable=SC2059
            printf "$(gettext "#%u %s")\n" \
               "${number}" \
               "${keyInfo}"
            number=$((number+1))
         fi
      done
      echo -en "${colorNone}"
   fi
}


# ###### Usage ##############################################################
usage () {
   echo >&2 "$(gettext "Usage:") $0 [-S|--scripts scripts_directory] [--help|-h]"
   exit 1
}



# ###### Main program #######################################################

# ====== Handle arguments ===================================================
GETOPT="$(PATH=/usr/local/bin:${PATH} which getopt)"
options="$(${GETOPT} -o S:h --long scripts:,help -a -- "$@")"
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
   usage
fi

now="$(date)"
scriptDirectories="/etc/system-info.d /usr/local/etc/system-info.d"
eval set -- "${options}"
while [ $# -gt 0 ] ; do
   case "$1" in
      -S | --scripts)
         scriptDirectories="$2"
         shift 2
         ;;
      -h | --help)
         usage
         # shift
         ;;
      --)
         shift
         break
         ;;
  esac
done
if [ $# -ne 0 ] ; then
   usage
fi


# ====== Startup ============================================================
# shellcheck disable=SC2059
printf "\n$(gettext "System information on %s")\n\n" "${now}"

# ====== Obtain and print information =======================================
# Getting the network information may take a few hundreds of ms on a router
# => prepare it in background, while printing other information.
# shellcheck disable=SC2086
(
   echo
   print-system-information
) 2>&1 | (
   print-banners ${scriptDirectories}
   wait
   cat
) | \
(
   # Try output via mbuffer. This is faster for console printing, particularly
   # with complex banners and long interface lists. If unavailable, try buffer.
   # Just use cat as fallback, if neither mbuffer nor buffer are available.
   if ! mbuffer -q -s 16k 2>/dev/null ; then
      if ! buffer -s 16k 2>/dev/null ; then
         cat
      fi
   fi
)
