commit d102b513e2ae01ffc462e103e9ebd5168ac55d35 Author: Tomasz Kapias Date: Tue Feb 7 16:10:46 2023 +0700 first commit diff --git a/00-motdfetch b/00-motdfetch new file mode 100755 index 0000000..768f98f --- /dev/null +++ b/00-motdfetch @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# MOTDfetch is a modular and dynamic MOTD replacement +# and a very nice command line information tool for Debian/Ubuntu systems. +# +# repository: https://git.tkapias.net/tkapias/MOTDfetch +# author: Tomasz Kapias +# email: tomasz@tkapias.net +# +# modules path: ./motdfetch.d/ +# config paths: $HOME/.config/motdfetch/motdfetch.conf +# /etc/motdfetch/motdfetch.conf +# +# requirements (modules apart): sudo apt install coreutils bc + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# GLOBAL COLORS ###################################################### + +c_txt="39" +c_txt_emphase="35" +c_txt_deco="97" +c_txt_invert="30" +c_bg="47" +c_danger="31" +c_warning="33" +c_success="32" +c_title="${c_bg};1;${c_txt_invert}m" + +# RUN OPTIONS ####################################################### + +# MODTfetch directory path +SCRIPT_DIR=$(dirname "$(realpath "$0")") + +Help() +{ + echo -e "\e[1;${c_txt_emphase}mMOTDfetch\e[1;${c_txt}m is a modular and dynamic MOTD replacement and a very" + echo -e "nice command line information tool for Debian/Ubuntu systems.\e[0m" + echo + echo "Syntax: bash $SCRIPT_DIR/00-motdfetch [-h] [-f] [-t ARG] [-l]" + echo + echo "options: -h Print this Help" + echo " -f force execution under heavy load" + echo " -t execute a standalone module with internal sample conf" + echo " -l list available modules files names" +} + +unset option_force_load + +# command options and arguments +while getopts "hft:l" option; do + case $option in + h) Help + exit ;; + f) option_force_load=1;; + t) # standalone module option + if [[ -f "$SCRIPT_DIR/motdfetch.d/$OPTARG" ]]; then + (. "$SCRIPT_DIR/motdfetch.d/$OPTARG") + exit 0 + else + echo -e "\e[1;${c_danger}mError\e[0m: Invalid module filename, check the -l option.\n" + Help + exit 1 + fi;; + l) # List modules option + for file in $SCRIPT_DIR/motdfetch.d/* + do + echo -e "$(basename $file) $(head -3 $file|tail -1)" + done | column -t -s '#' + exit 0;; + \?) # Invalid option + echo -e "\e[1;${c_danger}mError\e[0m: Invalid option" + Help + exit 1;; + esac +done + +# CONFIG LOADING ################################################### + +userconf="$HOME/.config/motdfetch/motdfetch.conf" +sysconf="/etc/motdfetch/motdfetch.conf" + +if [[ -f "$userconf" ]];then + . "$userconf" +elif [[ -f "$sysconf" ]];then + . "$sysconf" +else + echo -e "\e[1;${c_danger}mError\e[0m: no configuration available.\n" + Help + exit 1 +fi + +# EXECUTION ######################################################### + +## load check +cores=$(/usr/bin/grep -ioPc 'processor\t:' /proc/cpuinfo 2>/dev/null) +if [[ "$cores" -eq "0" ]]; then + cores=1 +fi +threshold="${cores:-1}.5" + +echo -e "\n\e[${c_title} MOTDFETCH \e[0m\n" +if [[ $(echo "`cut -f1 -d ' ' /proc/loadavg` < $threshold" | /usr/bin/bc) -eq 1 || $option_force_load -eq 1 ]]; then + ### output modules + for file in $SCRIPT_DIR/motdfetch.d/* + do + (. "$file") + done + echo +else + ### too much load for modules, run only 00 & 99 + msg + module_header_disable=0 + (. $SCRIPT_DIR/motdfetch.d/00-*) + module_userslog_disable=0 + (. $SCRIPT_DIR/motdfetch.d/99-*) + echo -e "\n\e[${c_title} WE SKIPPED SOME MODULES \e[0m\n" + echo -e " MOTDfetch skipped some modules because of\n this \e[1;${c_danger}msystem load\e[0m being higher than \e[1;${c_danger}m$threshold\e[0;${c_warning}m.\e[0m\n" + echo -e " \e[1;${c_txt}mBAD ADVICE\e[0m: you can force execution with the option [-f]:\n # bash $SCRIPT_DIR/00-motdfetch -f\n" +fi diff --git a/docs/img/preview.jpg b/docs/img/preview.jpg new file mode 100644 index 0000000..b0202a6 Binary files /dev/null and b/docs/img/preview.jpg differ diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..4366942 --- /dev/null +++ b/install.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +# +# installation script for MOTDfetch +# repository: https://git.tkapias.net/tkapias/MOTDfetch +# author: Tomasz Kapias +# email: tomasz@tkapias.net +# +# run with sudo or as root user, or directly from the repository: +# sudo wget -q https://git.tkapias.net/tkapias/MOTDfetch/raw/master/install.sh -O - | sudo bash + + +# locale +export LC_ALL="C.UTF-8" +export GREP_COLORS="ms=01;32:mc=01;32:sl=:cx=:fn=35:ln=31:bn=31:se=36" + +N="\e[0m" +T="\e[1;35m" +S="\e[0;32m" +W="\e[0;33m" +E="\e[0;31m" + +# check if executed by root or sudo +if (( ! `id -u` == 0 )); then + echo -e "${E}Please do run as root or with sudo${N}" + exit +fi + +# check if the user uses Bash +if [[ ! $BASH_VERSION ]]; then + echo -e "\n\e[1;33mThis tool is written for Bash, its compatibility is not tested with other shells.\e[0m" + echo -e "You are currently using \e[1;31m $SHELL\e[0m, do you want to continue? [\e[1;32mY\e[0m/\e[1;31mn\e[0m]\n" + read -n 1 -r + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +ERRORCOUNT=0 + +if [[ -f "/etc/ssh/sshd_config" ]]; then + echo -e "${T}# setup ssh server (/etc/ssh/sshd_config) to not print MOTD or LASTLOGIN${N}" + sed -ri 's/^(\#?)(PrintMotd|PrintLastLog)([[:space:]]+)(yes|no)/\2\3no/' /etc/ssh/sshd_config + resultsshno=$(/usr/bin/grep -E "PrintMotd\s+(no)?|\PrintLastLog\s+(no)?" /etc/ssh/sshd_config 2>/dev/null) + if [[ $resultsshno == "" ]]; then + echo -e "${E}FAIL - check manually \"PrintMotd no\" & \"PrintLastLog no\"${N}\n" + ((ERRORCOUNT++)) + else + echo -e "$resultsshno" | /usr/bin/grep --color=auto "no" + echo -e "${S}DONE${N}\n" + fi + echo -e "${T}# setup ssh server (/etc/ssh/sshd_config) to accept custom SSH_MOTD env variable${N}" + sed -ri '/SSH_MOTD/! s/^(\#?)(AcceptEnv)(.+)/\2\3 SSH_MOTD/' /etc/ssh/sshd_config + resultsshmotd=$(/usr/bin/grep -E "^AcceptEnv.+SSH_MOTD" /etc/ssh/sshd_config 2>/dev/null) + if [[ $resultsshmotd == "" ]]; then + echo -e "${E}FAIL - check manually \"AcceptEnv SSH_MOTD\"${N}\n" + ((ERRORCOUNT++)) + else + echo -e "$resultsshmotd" | /usr/bin/grep --color=auto "SSH_MOTD" + echo -e "${S}DONE${N}\n" + fi +else + echo -e "${E}/etc/ssh/sshd_config DOES NOT EXIST${N}\n" + ((ERRORCOUNT++)) +fi + +echo -e "${T}# setup PAM's sshd (/etc/pam.d/sshd) to not print MOTD and MAIL${N}" +if [[ -f "/etc/pam.d/sshd" ]]; then + sed -ri 's/^(\#?)(session[[:space:]]+optional[[:space:]]+)(pam_motd.so|pam_mail.so)(.+)/#\2\3\4/' /etc/pam.d/sshd + resultpamcom=$(/usr/bin/grep -E "^\#.+pam_motd.so|^\#.+pam_mail.so" /etc/pam.d/sshd 2>/dev/null) + if [[ $resultpamcom == "" ]]; then + echo -e "${E}FAIL - comment manually lines with \"pam_motd.so\" & \"pam_mail.so\"${N}\n" + ((ERRORCOUNT++)) + else + echo -e "$resultpamcom" | /usr/bin/grep --color=auto -E "^\#" + echo -e "${S}DONE${N}\n" + fi +else + echo -e "${E}FAIL - /etc/pam.d/sshd DOES NOT EXIST${N}\n" + ((ERRORCOUNT++)) +fi + +echo -e "${T}# restart ssh server if it's active${N}" +if [[ $(systemctl is-active ssh 2>/dev/null) == "active" ]]; then + systemctl restart ssh.service + echo -e "${S}DONE${N}\n" +else + echo -e "${E}SSHD NOT ACTIVE${N}\n" +fi + +echo -e "${T}# install minimal dependencies${N}" +apt install -f figlet coreutils bc wget nano dnsutils openssl s-nail +echo + +echo -e "${T}# install ANSI_Shadow figlet font${N}" +downloadfigletfont=$(wget -nv -O "/usr/share/figlet/ANSI Shadow.flf" https://raw.githubusercontent.com/xero/figlet-fonts/master/ANSI%20Shadow.flf) +if $downloadfigletfont ; then + echo -e "${S}DONE${N}\n" +else + echo -e "${E}FAIL${N}\n" + ((ERRORCOUNT++)) +fi + +echo -e "${T}# check directories & backup old & obsolete files${N}" +if [[ ! -d "/etc/update-motd.d/backup" ]]; then + mkdir -vp /etc/update-motd.d/backup +fi +if [[ ! -d "/etc/motdfetch" ]]; then + mkdir -vp /etc/motdfetch +fi +chmod -v 755 /etc/update-motd.d/ /etc/update-motd.d/backup/ 2>/dev/null +echo -e "${S}CHMOD DONE${N}\n" +if [[ -d "/etc/update-motd.d/motdfetch.d" ]]; then + cp -p -Rv /etc/update-motd.d/*motdfetch* /etc/update-motd.d/backup/ 2>/dev/null + echo -e "${S}MOTDfetch BACKUP DONE${N}" +fi +if [[ -d "/etc/motd" ]]; then + mkdir -vp /etc/update-motd.d/backup/etc + mv -vf /etc/motd /etc/update-motd.d/backup/etc/motd 2>/dev/null + echo -e "${S}Static MOTD BACKUP DONE${N}" +fi +if [[ -d "/etc/update-motd.d/10-uname" ]]; then + mv -vf /etc/update-motd.d/10-uname /etc/update-motd.d/backup/10-uname 2>/dev/null + echo -e "${S}Debian uname MOTD BACKUP DONE${N}" +fi +echo + +echo -e "${T}# install/update MOTDfetch${N}" +downloadmotd=$(wget -c -nv https://git.tkapias.net/tkapias/MOTDfetch/archive/master.tar.gz -O - \ + | tar -xz --strip-components=1 -C /etc/update-motd.d/ MOTDfetch-master/motdfetch.d/ MOTDfetch-master/00-motdfetch MOTDfetch-master/motdfetch.sample.conf 2>/dev/null) +if $downloadmotd ; then + mv -vf /etc/update-motd.d/motdfetch.sample.conf /etc/motdfetch/motdfetch.sample.conf + if [[ -f "/etc/motdfetch/motdfetch.conf" ]]; then + echo -e "${S}SUCCESSFUL UPDATE${N}\n" + else + cp -p -Rv /etc/motdfetch/motdfetch.sample.conf /etc/motdfetch/motdfetch.conf + echo -e "${S}SUCCESSFUL INSTALLATION${N}\n" + fi + if (( $ERRORCOUNT == 1 )); then + echo -e "${W}But CAUTION, $ERRORCOUNT configuration steps have failed.${N} Please check if you need to adjust manually.\n" + elif (( $ERRORCOUNT > 1 )); then + echo -e "${W}But CAUTION, $ERRORCOUNT configuration step has failed.${N} Please check if you need to adjust manually.\n" + fi + echo -e " - ${S}edit the config file${N}: + # sudo nano /etc/motdfetch/motdfetch.conf + ${S}or create a new user config${N}: + # userdir=~/.config/motdfetch \\ + && mkdir \$userdir \\ + && cp /etc/motdfetch/motdfetch.sample.conf \$userdir/motdfetch.conf \\ + && nano \$userdir/motdfetch.conf\n" + echo -e " - ${S}add an alias \"motd\" command to your .bashrc${N}:\n alias motd='/etc/update-motd.d/00-motdfetch'\n" + echo -e " - ${S}how to use MOTDfetch as an alternative MOTD for SSH sessions${N}: + + ${S}on the ssh server, add the new \"motd\" alias at the end of the ssh user .bashrc${N}: + if [[ -n \$SSH_CONNECTION ]]; then\n motd\n fi + + ${S}on the ssh client, add an alias \"sshmotd\" to your .bashrc${N}:\n alias sshmotd='ssh -o SetEnv=SSH_MOTD=1'\n" +else + echo -e "${E}INSTALLATION FAILED - please retry${N}\n" +fi diff --git a/motdfetch.d/00-header b/motdfetch.d/00-header new file mode 100644 index 0000000..1a1e247 --- /dev/null +++ b/motdfetch.d/00-header @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# +# display a custom decorative text (default to username) +# + welcome the username + date, time and timezone +# + inform of mail presence for user (if s-nail is installed) +# +# requirements: sudo apt install figlet +# sudo wget --no-check-certificate -O "/usr/share/figlet/ansi-shadow.flf" https://raw.githubusercontent.com/xero/figlet-fonts/master/ANSI%20Shadow.flf +# optionnal: sudo apt install s-nail + +# GENERAL ########################################################### + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# check if module was disabled +module_disable=${module_header_disable:=0} +if (($module_disable == 1)); then + exit 1 +fi + +# colors +c_txt=${c_txt:="39"} +c_txt_emphase=${c_txt_emphase:="35"} +c_txt_deco=${c_txt_deco:="97"} +c_txt_invert=${c_txt_invert:="30"} +c_bg=${c_bg_sec:="47"} +c_danger=${c_danger:="31"} +c_warning=${c_warning:="33"} +c_success=${c_success:="32"} +c_title=${c_title:="${c_bg};1;${c_txt_invert}m"} + +# OPTIONS ########################################################### + +# banner custom text or username +username=$(/usr/bin/id -un) +header_text=${module_header_text:="$username"} + +# PREPARATIONS ###################################################### + +# check if s-nail is available and if you have mail +if command -v s-nail 1>/dev/null; then + # check mail for user + if /usr/bin/s-nail -e; then + hasmail=$(echo -e " You have \e[1;${c_success}mmessages\e[0m.") + + else + hasmail=$(echo -e " You have no messages.") + fi +else + hasmail="" +fi + +# trim banner text +header_text_width=${#header_text} +figlet_width=$(/usr/bin/env figlet -f "ANSI Shadow" "$header_text" | wc -L) +while [[ $figlet_width -ge 73 ]]; do + let "header_text_width-=1" + header_text=$(echo -e "${header_text:0:$header_text_width}...") + figlet_width=$(/usr/bin/env figlet -f "ANSI Shadow" "$header_text" | wc -L) +done + +# OUTPUT ############################################################ + +if [[ $module_header_text != "$username" ]]; then + /usr/bin/env figlet -f "ANSI Shadow" "$header_text" \ + | sed "$d" \ + | sed "s/^/ /" \ + | echo -e "$(sed "1 s/^/\\\e[${c_txt_emphase}m/")" \ + | echo -e "$(sed "$ s/$/\\\e[0m/")" + echo -e " \e[1;${c_txt}mWelcome \e[${c_txt_emphase}m${username}\e[${c_txt}m, it is \e[${c_txt_emphase}m$(date +"%Y-%m-%d %H:%M:%S %z")\e[${c_txt}m.\e[0m\n${hasmail}" +else + echo -e " Welcome" + /usr/bin/env figlet -f "ANSI Shadow" "$header_text" \ + | sed "$d" \ + | sed "s/^/ /" \ + | echo -e "$(sed "1 s/^/\\\e[${c_txt_emphase}m/")" \ + | echo -e "$(sed "$ s/$/\\\e[0m/")" + echo -e " \e[1;${c_txt}mIt is \e[${c_txt_emphase}m$(date +"%Y-%m-%d %H:%M:%S %z")\e[${c_txt}m.\e[0m\n${hasmail}" +fi diff --git a/motdfetch.d/01-sysinfo b/motdfetch.d/01-sysinfo new file mode 100644 index 0000000..fbe9c51 --- /dev/null +++ b/motdfetch.d/01-sysinfo @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# +# display a list of system informations +# + dig and curl have shorts timeouts to not hang the script +# +# requirements: sudo apt install dnsutils + +# GENERAL ########################################################### + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# check if module was disabled +module_disable=${module_sysinfo_disable:=0} +if (($module_disable == 1)); then + exit 1 +fi + +# colors +c_txt=${c_txt:="39"} +c_txt_emphase=${c_txt_emphase:="35"} +c_txt_deco=${c_txt_deco:="97"} +c_txt_invert=${c_txt_invert:="30"} +c_bg=${c_bg_sec:="47"} +c_danger=${c_danger:="31"} +c_warning=${c_warning:="33"} +c_success=${c_success:="32"} +c_title=${c_title:="${c_bg};1;${c_txt_invert}m"} + +# api website name for plain public IP4/6 check +publicip_api=${global_publicip_api:="icanhazip.com"} + +# PREPARATIONS ###################################################### + +# output header +echo -e "\nWaiting for DNS and IP checks\e[5m...\e[0m" + +# get hostname +HOSTNAME=`hostname -f` +# get distribution name +DISTRO=`/usr/bin/cat /etc/*release | /usr/bin/grep "PRETTY_NAME" | cut -d "=" -f 2- | sed 's/"//g'` +# get name of main interface with a route for internet +INTERNETIFACE="$(ip -o route get 8/32 2>/dev/null)" +if [[ "$INTERNETIFACE" ]]; then + INTERNETIFACE=`echo -e "$INTERNETIFACE" | sed -e 's/.*dev //' -e 's/ .*//'` +else + INTERNETIFACE="\e[1;${c_warning}mno interface with a route to 8/32\e[0;${c_txt}m" +fi +# get DNS servers ips from resolv.conf +DNSSERVERS=`/usr/bin/grep '^nameserver' /etc/resolv.conf | cut -d' ' -f2 | paste -sd ','` +if [[ ! $DNSSERVERS ]];then + DNSSERVERS="\e[1;${c_warning}mno ns in resolv.conf\e[0;${c_txt}m" +fi +# get current output public IPs and their revers DNS record +PUBLICIP4=`timeout 6 curl -s -4 $publicip_api 2>/dev/null` +if [[ $PUBLICIP4 ]];then + IP4PTR=`timeout 4 dig +short -x $PUBLICIP4 @1.1.1.1 2>/dev/null` + if [[ ! $IP4PTR ]];then + IP4PTR="\e[0;${c_txt}mno data" + fi + else + IP4PTR="\e[0;${c_txt}mno data" + PUBLICIP4="\e[1;5;${c_warning}mno IPv4 internet\e[0;${c_txt}m" + INTERNET_IP4=0 +fi +PUBLICIP6=`timeout 6 curl -s -6 $publicip_api 2>/dev/null` +if [[ $PUBLICIP6 ]];then + IP6PTR=`timeout 4 dig +short -x $PUBLICIP6 @1.1.1.1 2>/dev/null` + if [[ ! $IP6PTR ]];then + IP6PTR="\e[0;${c_txt}mno data" + fi +else + IP6PTR="\e[0;${c_txt}mno data" + PUBLICIP6="\e[1;${c_warning}mno ipv6 internet\e[0;${c_txt}m" + INTERNET_IP6=0 +fi +INTERNET_OUT=0 +if [[ $INTERNET_IP4 && $INTERNET_IP6 ]]; then + INTERNET_OUT=1 +fi +# get DNS records of the FQDN +HOSTNAMEIP4=`timeout 4 dig +short A $HOSTNAME @1.1.1.1 2>/dev/null` +if [[ ! $HOSTNAMEIP4 ]];then + HOSTNAMEIP4="\e[0;${c_txt}mno data" +fi +HOSTNAMEIP6=`timeout 4 dig +short AAAA $HOSTNAME @1.1.1.1 2>/dev/null` +if [[ ! $HOSTNAMEIP6 ]];then + HOSTNAMEIP6="\e[0;${c_txt}mno data" +fi +# get processes +PROCESS=`/usr/bin/ps -eo user= | sort | uniq -c | awk '{ print $2 " " $1 }'` +PROCESS_ALL=`echo "$PROCESS" | awk {'print $2'} | awk '{ SUM += $1} END { print SUM }'` +PROCESS_ROOT=`echo "$PROCESS" | /usr/bin/grep root | awk {'print $2'}` +PROCESS_USER=`echo "$PROCESS" | /usr/bin/grep -v root | awk {'print $2'} | awk '{ SUM += $1} END { print SUM }'` +# get processors +PROCESSOR_NAME=`/usr/bin/grep "model name" /proc/cpuinfo | cut -d ' ' -f3- | awk {'print $0'} | head -1 | sed -e 's/([^()]*)/ /g' -e 's/\s\+/ /g'` +PROCESSOR_COUNT=`/usr/bin/grep -ioPc 'processor\t:' /proc/cpuinfo 2>/dev/null` +# get load averages +IFS=" " read LOAD1 LOAD5 LOAD15 <<<$(cat /proc/loadavg | awk '{ print $1,$2,$3 }') +if [[ $(echo "$LOAD1 > $(echo "$PROCESSOR_COUNT + ($PROCESSOR_COUNT / 3)" | bc -l )" | bc) == 1 ]]; then + LOAD1="\e[1;5;${c_danger}m$LOAD1\e[0;${c_txt}m" +elif [[ $(echo "$LOAD1 > $(echo "$PROCESSOR_COUNT - ($PROCESSOR_COUNT / 3)" | bc -l )" | bc) == 1 ]]; then + LOAD1="\e[1;5;${c_warning}m$LOAD1\e[0;${c_txt}m" +fi +if [[ $(echo "$LOAD5 > $(echo "$PROCESSOR_COUNT * 2" | bc -l )" | bc) == 1 ]]; then + LOAD2="\e[1;5;${c_warning}m$LOAD5\e[0;${c_txt}m" +fi +if [[ $(echo "$LOAD15 > $(echo "$PROCESSOR_COUNT * 2" | bc -l )" | bc) == 1 ]]; then + LOAD3="\e[1;5;${c_warning}m$LOAD15\e[0;${c_txt}m" +fi +# get free memory +IFS=" " read USED AVAIL TOTAL <<<$(free -htm | /usr/bin/grep "Mem" | awk {'print $3,$7,$2'}) +# get free swap +IFS=" " read SUSED SAVAIL STOTAL <<<$(free -htm | /usr/bin/grep "Swap" | awk {'print $3,$4,$2'}) + +# OUTPUT ############################################################ + +W="\e[0;${c_txt}m" +X="\e[1;${c_txt}m" +G="\e[1;${c_txt_emphase}m" +N="\e[0m" +echo -e "\e[1A\e[${c_title} System information $N + +$X Distro$W..........: $DISTRO +$X Kernel$W..........: `uname -sr` +$X Uptime$W..........: $G`uptime -p`$W + +$X CPU$W.............: $PROCESSOR_NAME +$X CPU cores$W.......: $G$PROCESSOR_COUNT$W +$X Load$W............: $G$LOAD1$W (1m), $G$LOAD5$W (5m), $G$LOAD15$W (15m) +$X Processes$W.......: $G$PROCESS_ROOT$W (root), $G$PROCESS_USER$W (user), $G$PROCESS_ALL$W (total) +$X Memory$W..........: $G$USED$W used, $G$AVAIL$W avail, $G$TOTAL$W total$N" +if [[ ${SUSED} ]]; then + echo -e "$X Swap$W............: $G$SUSED$W used, $G$SAVAIL$W avail, $G$STOTAL$W total$W" +else + echo -e "$X Swap$W............: \e[1;${c_warning}mno Swap$W" +fi +echo -e " +$X Hostname/FQDN$W...: $G$(hostname)$W / $G$(hostname -f)$W +$X Internet Iface$W..: $G$INTERNETIFACE$W +$X DNS servers$W.....: $G$(echo -e "${DNSSERVERS}" | sed "s/,/\\\e[0m, \\\e[1;${c_txt_emphase}m/g")$N" +if [[ ${INTERNET_OUT} == 0 ]]; then + echo -e "$X Public IP4/IP6$W..: $G$PUBLICIP4$W / $G$PUBLICIP6$N" +else + echo -e "$X Public IP4/IP6$W..: \e[1;${c_warning}mno internet or "${publicip_api}" timeout$N" +fi +echo -e "$X PTR IP4/IP6$W.....: $G$IP4PTR$W / $G$IP6PTR$W +$X FQDN A/AAAA$W.....: $G$HOSTNAMEIP4$W / $G$HOSTNAMEIP6$N" \ No newline at end of file diff --git a/motdfetch.d/02-services b/motdfetch.d/02-services new file mode 100644 index 0000000..4d7c6f1 --- /dev/null +++ b/motdfetch.d/02-services @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# +# check status of a list of systemd service units +# + accept service name with or without extension (.service) +# + default to system wide services but accept --user services with the :uid +# + alarm by bullet coloration and blinking +# +# requirements: systemd + +# GENERAL ########################################################### + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# check if module was disabled +module_disable=${module_services_disable:=0} +if (($module_disable == 1)); then + exit 1 +fi + +# colors +c_txt=${c_txt:="39"} +c_txt_emphase=${c_txt_emphase:="35"} +c_txt_deco=${c_txt_deco:="97"} +c_txt_invert=${c_txt_invert:="30"} +c_bg=${c_bg_sec:="47"} +c_danger=${c_danger:="31"} +c_warning=${c_warning:="33"} +c_success=${c_success:="32"} +c_title=${c_title:="${c_bg};1;${c_txt_invert}m"} + +# OPTIONS ########################################################### + +# array of services +if [[ $module_services ]]; then + services=(${module_services[@]}) +else + services=("cron.service" "dbus.service:1000" "user2service:1002") +fi +IFS=$'\n' services=($(sort <<<"${services[*]}")) +unset IFS + +# column max-width +width=${module_services_width:="80"} + +# PREPARATIONS ###################################################### + +# get status +# + dim status if the service does not exist or if the user is not root and the service has another uid +serviceStatus=() +for service in "${services[@]}"; do + uid=${service/*:/} + # script executed as root and the service to check is from a user + if [[ $service = *:* ]] && [[ "$(id -u)" == "0" ]]; then + service=${service/.service/} + service=${service/:[0-9]*/} + serviceexist=$(machinectl -q shell --uid="$uid" .host /usr/bin/systemctl list-units --user -q --plain --no-pager --full --all -t service 2>/dev/null \ + | awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }' \ + | /usr/bin/grep "$service") + if [[ "$serviceexist" ]] ;then + status=$(machinectl -q shell --uid="$uid" .host /usr/bin/systemctl is-active --user "$service") + if [[ $status = inactive* ]]; then + serviceStatus+=("inactive") + elif [[ $status = active* ]]; then + serviceStatus+=("active") + else + serviceStatus+=("hidden") + fi + else + serviceStatus+=("hidden") + fi + # script executed by the same user as the service user + elif [[ $service = *:* ]] && [[ "$(id -u)" == "$uid" ]]; then + service=${service/.service/} + service=${service/:[0-9]*/} + serviceexist=$(/usr/bin/systemctl list-units --user -q --plain --no-pager --full --all -t service 2>/dev/null \ + | awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }' \ + | /usr/bin/grep "$service") + if [[ "$serviceexist" ]] ;then + serviceStatus+=($(/usr/bin/systemctl --user is-active "$service")) + else + serviceStatus+=("hidden") + fi + # script executed by another user than root and than the service user + elif [[ $service = *:* ]]; then + serviceStatus+=("hidden") + # system service + else + service=${service/.service/} + service=${service/:[0-9]*/} + serviceexist=$(/usr/bin/systemctl list-units -q --plain --no-pager --full --all -t service 2>/dev/null \ + | awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }' \ + | /usr/bin/grep "$service") + if [[ "$serviceexist" ]] ;then + serviceStatus+=($(/usr/bin/systemctl is-active "$service")) + else + serviceStatus+=("hidden") + fi + fi +done + +# OUTPUT ############################################################ + +# output header +echo -e "\n\e[${c_title} Services checks \e[0m\n" + +# output checklist +line=" " +lineLen=1 +for i in "${!serviceStatus[@]}" +do + service=${services[$i]/.service/} + service=${service^} + + # Next line and next line length + next=" $service " + nextLen=$((1+${#next})) + + # If the current line will exceed the max column with then echo and start a new line + if [[ $((lineLen+nextLen)) -gt $width ]]; then + echo -e "$line" + lineLen=1 + line=" " + fi + + lineLen=$((lineLen+nextLen)) + + # Color the next line green if it's active, red if inactive, else orange + if [[ "${serviceStatus[$i]}" == "active" ]] + then + line+=" \e[${c_success}m●\e[0m \e[${c_txt}m$service\e[0m " + elif [[ "${serviceStatus[$i]}" == "inactive" ]] + then + line+=" \e[1;5;${c_danger}m▲\e[0m \e[1;${c_txt}m$service\e[0m " + else + line+=" \e[1;2;${c_warning}m▲\e[0m \e[1;2;${c_txt}m$service\e[0m " + fi +done + +# echo what is left +echo -e "$line" + diff --git a/motdfetch.d/03-filesystem b/motdfetch.d/03-filesystem new file mode 100644 index 0000000..9214065 --- /dev/null +++ b/motdfetch.d/03-filesystem @@ -0,0 +1,227 @@ +#!/usr/bin/env bash +# +# status of mounted filesystems in 2 steps +# 1. use "df" for general fs except zfs, squashfs, tmpfs, devtmpfs and overlay +# 2. use "zpool" for ZFS pools +# + displays name, filesystem type, used blocks and inodes or pool health when relevant +# + custom warn threshold for blocks +# + warn threshold for inodes (80%), flashing danger threshold for health, blocks (95%) and inodes (90%) +# +# requirements: sudo apt install coreutils +# optionnal: sudo apt install zfsutils-linux + +# GENERAL ########################################################### + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# check if module was disabled +module_disable=${module_filesystem_disable:=0} +if (($module_disable == 1)); then + exit 1 +fi + +# colors +c_txt=${c_txt:="39"} +c_txt_emphase=${c_txt_emphase:="35"} +c_txt_deco=${c_txt_deco:="97"} +c_txt_invert=${c_txt_invert:="30"} +c_bg=${c_bg_sec:="47"} +c_danger=${c_danger:="31"} +c_warning=${c_warning:="33"} +c_success=${c_success:="32"} +c_title=${c_title:="${c_bg};1;${c_txt_invert}m"} + +# OPTIONS ########################################################### + +# max usage warning threshold +max_usage=${module_filesystem_max_usage:="85"} + +# disable ZFS section +zfs_disable=${module_filesystem_zfs_disable:=0} + +# PREPARATIONS ###################################################### + +# disk usage: ignore zfs, squashfs & tmpfs, standard error output because of a bug with /run/user/1000/doc +dfcmd=$(/usr/bin/df -H -x zfs -x squashfs -x tmpfs -x devtmpfs -x overlay --output 2>/dev/null) +mapfile -t dfoutput < <(echo "$dfcmd" | tail -n+2) + +# determine best width for the bar +largesttarget=$(echo "$dfcmd" | awk '{if (length ($12)>max) max=length($12)} END {print max}') +if [[ $((largesttarget+30)) -gt 50 ]]; then + bar_width=$((largesttarget+30)) +else + bar_width="50" +fi + +# OUTPUT ############################################################ + +# general filesystems +linecounter=0 +if [[ "${dfoutput[@]}" ]]; then + # output header + echo -e "\n\e[${c_title} Filesystem usage \e[0m\n" + # output filesystem status except ZFS + for line in "${dfoutput[@]}"; do + # parse df output + diskid=$(echo "$line" | awk '{print $12}') + diskused=$(echo "$line" | awk '{print $10}'| sed 's/%//') + disktotal=$(echo "$line" | awk '{print $7}') + fsformat=$(echo "$line" | awk '{print $2}') + inodesused=$(echo "$line" | awk '{print $6}'| sed 's/%//') + inodestotal=$(echo "$line" | awk '{print $3}') + + # bar & blocks coloration, danger red above 95% + if [[ "${diskused}" -ge "95" ]]; then + barcolor="\e[1;${c_danger}m" + blockscolor="\e[1;${c_danger}m" + flashing="\e[5m" + elif [ "${diskused}" -ge "${max_usage}" ]; then + barcolor="\e[1;${c_warning}m" + blockscolor="\e[1;${c_warning}m" + flashing="" + else + barcolor="\e[1;${c_success}m" + blockscolor="\e[${c_txt}m" + flashing="" + fi + # print colored bar until used_width + used_width=$(((diskused*$bar_width)/100)) + bar="[${barcolor}" + for ((i=0; i<$used_width; i++)); do + bar+="=" + done + # print dimmmed bar until end + bar+="\e[${c_txt}m\e[0m" + for ((i=$used_width; i<$bar_width; i++)); do + bar+="=" + done + bar+="\e[0m]" + + # print blocks usage line & bar + padding=$((bar_width-28)) + if [[ $linecounter -ge 1 ]] + then + echo -e "\n" + fi + echo "${line}" | printf "\e[1;${c_txt_emphase}m%-${padding}s\e[0m${blockscolor}%+3s%% used ${flashing}blocks\e[0m out of %+4s\n" $diskid $diskused $disktotal | sed 's/^/ /' + echo -e "${bar}" | sed 's/^/ /' + + # print filesystem type & inodes usage line + if [[ ! "$inodesused" == "-" ]] + then + padding=$((bar_width-34)) + # inodes coloration, danger red above 90% + if [[ "${inodesused}" -ge "90" ]]; then + inodescolor="\e[1;${c_danger}m" + flashing="\e[5m" + elif [[ "${inodesused}" -ge "80" ]]; then + inodescolor="\e[1;${c_warning}m" + flashing="" + else + inodescolor="\e[${c_txt}m" + flashing="" + fi + echo "${line}" | printf "Type: %-${padding}s${inodescolor}%+3s%% used ${flashing}inodes\e[0m out of %+4s\n" $fsformat $inodesused $inodestotal | sed 's/^/ /' + else + echo "${line}" | printf "Type: %-10s\n" $fsformat | sed 's/^/ /' + fi + linecounter=$((linecounter+1)) + done +else + echo " no general filesystem available, ZFS expected in next section" +fi + +# check if ZFS section was disabled +if [[ $zfs_disable == 1 ]]; then + exit 1 +fi + +# output ZFS header +echo -e "\n\e[${c_title} Zpool usage \e[0m\n" + +# check if zpool is available +if ! command -v zpool 1> /dev/null; then + echo " no ZFS tools available" + exit 1 +fi + +# ZFS zpool usage & health status +zlistcmd=$(zpool list -o name,cap,size,health 2> /dev/null) +if [[ $zlistcmd == "no pools available" ]]; then + echo " no ZFS pool available" +else + mapfile -t zpools < <(echo "$zlistcmd" | tail -n+2) + + # determine best width for the bar + largesttarget=$(echo "zlistcmd" | awk '{if (length ($1)>max) max=length($1)} END {print max}') + if [[ $((largesttarget+30)) -gt 50 ]]; then + bar_width=$((largesttarget+30)) + else + bar_width="50" + fi + + linecounter=0 + for line in "${zpools[@]}"; do + # parse zpool list output + poolid=$(echo "$line" | awk '{print $1}') + poolused=$(echo "$line" | awk '{print $2}'| sed 's/%//') + pooltotal=$(echo "$line" | awk '{print $3}') + poolhealth=$(echo "$line" | awk '{print $4}') + + # bar & pools coloration, danger red above 95% + if [[ "${poolused}" -ge "95" ]]; then + barcolor="\e[1;${c_danger}m" + poolscolor="\e[1;${c_danger}m" + flashing="\e[5m" + elif [[ "${poolused}" -ge "${max_usage}" ]]; then + barcolor="\e[1;${c_warning}m" + poolscolor="\e[1;${c_warning}m" + flashing="" + else + barcolor="\e[1;${c_success}m" + poolscolor="\e[${c_txt}m" + flashing="" + fi + # print colored bar until used_width + used_width=$(((poolused*$bar_width)/100)) + bar="[${barcolor}" + for ((i=0; i<$used_width; i++)); do + bar+="=" + done + # print dimmmed bar until end + bar+="\e[${c_txt}m\e[0m" + for ((i=$used_width; i<$bar_width; i++)); do + bar+="=" + done + bar+="\e[0m]" + + # print pool usage line & bar + padding=$((bar_width-29)) + if [[ $linecounter -ge 1 ]] + then + echo -e "\n" + fi + echo "${line}" | printf "\e[1;${c_txt_emphase}m%-${padding}s\e[0m${poolscolor}%+3s%% used ${flashing}blocks\e[0m out of %+5s\n" $poolid $poolused $pooltotal | sed 's/^/ /' + echo -e "${bar}" | sed 's/^/ /' + + # print pool health line + padding=$((bar_width-34)) + # health coloration, danger red for FAULTED + if [[ "${poolhealth}" == "FAULTED" ]]; then + healthcolor="\e[1;${c_danger}m" + flashing="\e[5m" + elif [[ "${poolhealth}" == "OFFLINE" ]] || [[ "${poolhealth}" == "UNAVAIL" ]] || [[ "${poolhealth}" == "REMOVED" ]] || [[ "${poolhealth}" == "DEGRADED" ]]; then + healthcolor="\e[1;${c_warning}m" + flashing="" + else + healthcolor="\e[${c_txt}m" + flashing="" + fi + padding=$((bar_width-17)) + text="health" + echo "${line}" | printf "%+${padding}s status: ${healthcolor}${flashing}%+8s\e[0m\n" $text ${poolhealth,,} | sed 's/^/ /' + linecounter=$((linecounter+1)) + done +fi diff --git a/motdfetch.d/04-tls b/motdfetch.d/04-tls new file mode 100644 index 0000000..a08aa7d --- /dev/null +++ b/motdfetch.d/04-tls @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# +# check X.509 cert for a domain:port with openssl, for correspondance and expiration +# + by default the port is 443 +# + there is a timeout of 6 sec and is there is no connectivity the check is skipped +# +# requirements: sudo apt install openssl + +# GENERAL ########################################################### + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# check if module was disabled +module_disable=${module_tls_disable:=0} +if (($module_disable == 1)); then + exit 1 +fi + +# colors +c_txt=${c_txt:="39"} +c_txt_emphase=${c_txt_emphase:="35"} +c_txt_deco=${c_txt_deco:="97"} +c_txt_invert=${c_txt_invert:="30"} +c_bg=${c_bg_sec:="47"} +c_danger=${c_danger:="31"} +c_warning=${c_warning:="33"} +c_success=${c_success:="32"} +c_title=${c_title:="${c_bg};1;${c_txt_invert}m"} + +# api website name for plain public IP4/6 check +publicip_api=${global_publicip_api:="icanhazip.com"} + +# OPTIONS ########################################################### + +# domains:ports input +if [[ $module_tls_domains ]] +then + tls_domains=(${module_tls_domains[@]}) +else + tls_domains=("www.google.com" "smtp.gmail.com:465") +fi +IFS=$'\n' tls_domains=($(sort <<<"${tls_domains[*]}")) +unset IFS + +# PREPARATIONS ###################################################### + +# print loading message +echo -e "\nWaiting for TLS checks\e[5m...\e[0m" + +# parse output +output=" \e[1;4;${c_txt}mDomain\e[24m|\e[4mPort\e[24m|\e[4mValid until\e[0m" +currentTime=$(date +%s) + +# check if there is internet connectivity +INTERNET_OUT=0 +PUBLICIP="$(timeout 6 curl -s $publicip_api 2>/dev/null)" +if [[ ! $PUBLICIP ]]; then + INTERNET_OUT=1 +fi + +# OUTPUT ############################################################ + +for domain in "${tls_domains[@]}" +do + if [[ $domain == *:* ]] + then + port=${domain/*:/} + domain=${domain/:*/} + else + port="443" + fi + if [[ $INTERNET_OUT == 0 ]]; then + # fetch cert + cert="" + cert=$(timeout 6 openssl s_client -servername ${domain} -connect ${domain}:${port} < /dev/null 2>/dev/null) + # fetch subject name with a 3 sec timeout + certSubj=$(echo -e "$cert" | openssl x509 -noout -subject 2>/dev/null) + # fetch expiration time + certTime=$(echo -e "$cert" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2) + certLineTime=$(date -d "${certTime}" +"%F") + certTimestamp=$(date -d "${certTime}" +%s) + port="\e[${c_txt}m${port}\e[0m" + # check subject name with a 3 sec timeout + if [[ $certSubj == *${domain} ]] + then + domain="\e[${c_txt}m${domain}\e[0m" + # check expiration time - 3 days + if [[ "$((${certTimestamp} - 259200 ))" -ge "${currentTime}" ]] + then + sign="\e[${c_success}m●\e[0m" + result="\e[1;${c_success}m$certLineTime\e[0m" + # check expiration time today + elif [[ "${certTimestamp}" -ge "${currentTime}" ]] + then + sign="\e[1;${c_warning}m▲\e[0m" + result="\e[1;${c_warning}m${certLineTime}\e[0m" + else + sign="\e[1;5;${c_danger}m▲\e[0m" + result="\e[1;${c_danger}m$certLineTime\e[0m" + fi + else + domain="\e[${c_txt}m${domain}\e[0m" + sign="\e[1;2;${c_warning}m\U25B2\e[0m" + result="\e[1;2;${c_warning}mtimeout or misnamed\e[0m" + fi + elif [[ $INTERNET_OUT == 1 ]]; then + domain="\e[${c_txt}m${domain}\e[0m" + sign="\e[1;2;${c_txt}m▲\e[0m" + result="\e[1;2;${c_txt}mno internet, skipped\e[0m" + fi + output+="\n ${sign} ${domain}|${port}|$result" +done + +# output header and table content +echo -e "\e[1A\e[${c_title} TLS checks \e[0m " +echo -e +echo -e "$output" | column -t -s '|' diff --git a/motdfetch.d/05-postqueue b/motdfetch.d/05-postqueue new file mode 100644 index 0000000..45e89d8 --- /dev/null +++ b/motdfetch.d/05-postqueue @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# +# check if there is some mails in the deferred queue of postfix +# + danger color if >= 100 messages +# + warning color if >= 1 messages +# +# requirements: sudo apt install postfix + +# GENERAL ########################################################### + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# check if module was disabled +module_disable=${module_postqueue_disable:=0} +if (($module_disable == 1)); then + exit 1 +fi + +# colors +c_txt=${c_txt:="39"} +c_txt_emphase=${c_txt_emphase:="35"} +c_txt_deco=${c_txt_deco:="97"} +c_txt_invert=${c_txt_invert:="30"} +c_bg=${c_bg_sec:="47"} +c_danger=${c_danger:="31"} +c_warning=${c_warning:="33"} +c_success=${c_success:="32"} +c_title=${c_title:="${c_bg};1;${c_txt_invert}m"} + +# PREPARATIONS ###################################################### + +# print loading message +echo -e "\nWaiting for Postqueue checks\e[5m...\e[0m" + +# check if postfix is available, with qshape +if ! command -v qshape 1>/dev/null; then + # output module header + echo -e "\e[1A\e[${c_title} Postfix deferred queue status \e[0m\n" + echo -e " no Postfix server available" + exit 1 +fi + +# parse deferred messages qty with qshape +deferredcmd=$(timeout 5 qshape -b 6 deferred 2>/dev/null) + +# OUTPUT ############################################################ + +# if qshape timeout 5sec +if [[ "$deferredcmd" == "" ]]; then + # output module header + echo -e "\e[1A\e[${c_title} Postfix deferred \e[0m\n" + echo -e " \e[1;${c_warning}mqshape needs more than 5 sec to parse the deferred Mails list,\n you should investigate!\e[0m" + exit 1 +fi + +# if total deferred is 0 +deferredtotal=$(echo -e "$deferredcmd" | grep TOTAL | awk '{print $2}') +if [[ "$deferredtotal" == "0" ]]; then + # output module header + echo -e "\e[1A\e[${c_title} Postfix deferred queue status \e[0m\n" + echo -e " There is \e[1;${c_success}m${deferredtotal}\e[0m deferred Mails." + exit 1 +fi + +# else print qshape output +# output module header +echo -e "\e[1A\e[${c_title} Postfix deferred queue status \e[0m\n" +if [[ "$deferredtotal" -gt 100 ]]; then + echo -e " There is \e[1;${c_danger}m${deferredtotal}\e[0m deferred Mails in queue." +else + echo -e " There is \e[1;${c_warning}m${deferredtotal}\e[0m deferred Mails in queue." +fi diff --git a/motdfetch.d/06-fail2ban b/motdfetch.d/06-fail2ban new file mode 100644 index 0000000..682b1aa --- /dev/null +++ b/motdfetch.d/06-fail2ban @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# +# parse Fail2ban jail status, display failed and banned counts +# + Warning if currently more than 0 +# + Danger if currently more than 20 +# +# /!\ need root to display status, display warning for other user +# +# requirements: sudo apt install fail2ban + +# GENERAL ########################################################### + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# check if module was disabled +module_disable=${module_fail2ban_disable:=0} +if (($module_disable == 1)); then + exit 1 +fi + +# colors +c_txt=${c_txt:="39"} +c_txt_emphase=${c_txt_emphase:="35"} +c_txt_deco=${c_txt_deco:="97"} +c_txt_invert=${c_txt_invert:="30"} +c_bg=${c_bg_sec:="47"} +c_danger=${c_danger:="31"} +c_warning=${c_warning:="33"} +c_success=${c_success:="32"} +c_title=${c_title:="${c_bg};1;${c_txt_invert}m"} + +# PREPARATIONS ###################################################### + +# check if fail2ban is available +if ! command -v fail2ban-client 1>/dev/null; then + # output module header + echo -e "\n\e[${c_title} Fail2Ban status \e[0m\n" + echo -e " no Fail2Ban server available" + exit 1 +elif [[ ! `id -un` == "root" ]]; then + echo -e "\n\e[${c_title} Fail2Ban status \e[0m\n" + echo -e " you must be root to get Fail2Ban status" + exit 1 +fi + +# OUTPUT ############################################################ + +echo -e "\n\e[${c_title} Fail2Ban status \e[0m\n" + +# fail2ban-client status to get all jails, takes about ~70ms +jails=($(fail2ban-client status | grep "Jail list:" | sed "s/ //g" | awk '{split($2,a,",");for(i in a) print a[i]}')) + +out="\e[1;4;${c_txt}mJail name\e[24m,\e[4mFailed\e[24m,\e[4mTotal\e[24m,\e[4mBanned\e[24m,\e[4mTotal\e[24m\n" + +for jail in ${jails[@]}; do + # slow because fail2ban-client has to be called for every jail (~70ms per jail) + status=$(fail2ban-client status ${jail}) + failed=$(echo "$status" | grep -ioP '(?<=Currently failed:\t)[[:digit:]]+') + if [[ $failed -ge 20 ]]; then + failed="\e[1;5;${c_danger}m${failed}\e[0m" + elif [[ $failed -ge 1 ]]; then + failed="\e[1;${c_warning}m${failed}\e[0m" + fi + totalfailed=$(echo "$status" | grep -ioP '(?<=Total failed:\t)[[:digit:]]+') + banned=$(echo "$status" | grep -ioP '(?<=Currently banned:\t)[[:digit:]]+') + if [[ $banned -ge 20 ]]; then + banned="\e[1;5;${c_danger}m${banned}\e[0m" + elif [[ $banned -ge 1 ]]; then + banned="\e[1;${c_warning}m${banned}\e[0m" + fi + totalbanned=$(echo "$status" | grep -ioP '(?<=Total banned:\t)[[:digit:]]+') + out+="\e[1;${c_txt_emphase}m${jail}\e[0m,$failed,$totalfailed,$banned,$totalbanned\n" +done + +echo -e "$out" | column -ts $',' | sed 's/^/ /' diff --git a/motdfetch.d/99-userslog b/motdfetch.d/99-userslog new file mode 100644 index 0000000..a3ae77d --- /dev/null +++ b/motdfetch.d/99-userslog @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# +# check last user logged in (with host ip and period) and currently logged in users +# + if the last logged in is not the current user, add a section about the last login of the curent user + +# GENERAL ########################################################### + +# locale env +unset LC_ALL +export LC_MESSAGES=C + +# check if module was disabled +module_disable=${module_userslog_disable:=0} +if (($module_disable == 1)); then + exit 1 +fi + +# colors +c_txt=${c_txt:="39"} +c_txt_emphase=${c_txt_emphase:="35"} +c_txt_deco=${c_txt_deco:="97"} +c_txt_invert=${c_txt_invert:="30"} +c_bg=${c_bg_sec:="47"} +c_danger=${c_danger:="31"} +c_warning=${c_warning:="33"} +c_success=${c_success:="32"} +c_title=${c_title:="${c_bg};1;${c_txt_invert}m"} + +# PREPARATIONS ###################################################### + +lastuserlog=$(last -i -F -n 2 | head -2 | tail -1) +lastusername=$(echo -e "$lastuserlog" | awk '{print $1}') +lastuserhost=$(echo -e "$lastuserlog" | awk '{print $3}') +if [[ "$lastuserhost" == "0.0.0.0" ]]; then + lastuserhost="localhost" +fi +lastusertime=$(echo -e "$lastuserlog" | awk '{$1=$2=$3=""; print $0}' | sed 's/^ //') + +lastcurrentuserlog=$(last $(id -un) -i -F -n 2 | head -2 | tail -1) +lastcurrentusername=$(echo -e "$lastcurrentuserlog" | awk '{print $1}') +lastcurrentuserhost=$(echo -e "$lastcurrentuserlog" | awk '{print $3}') +if [[ "$lastcurrentuserhost" == "0.0.0.0" ]]; then + lastcurrentuserhost="localhost" +fi +lastcurrentusertime=$(echo -e "$lastcurrentuserlog" | awk '{$1=$2=$3=""; print $0}' | sed 's/^ //') + +loggedusers=$(last -i -F -p now | /usr/bin/grep "logged in" | awk '{print $1}') +loggedusersnames=$(echo -e "$loggedusers" | uniq -c | sed 's/^\s\+/|/' | tr ' ' '|') +loggeduserscount=$(echo -e "$loggedusers" | wc -l) + +# OUTPUT ############################################################ + +# header +echo -e "\n\e[${c_title} Users log \e[0m\n" + +W="\e[0;${c_txt}m" +X="\e[1;${c_txt}m" +F="\e[0;${c_txt_emphase}m" +G="\e[1;${c_txt_emphase}m" +S="\e[1;4;${c_txt}m" +N="\e[0m" + +# last logins +if [[ "$lastusername" == "$lastcurrentusername" ]]; then + echo -e "$X Last user logged in$W: $G$lastcurrentusername$W, from $F$lastcurrentuserhost$W +$X | Login - out$W (duration): $F$lastcurrentusertime$N" +else + echo -e "$X Last user logged in$W: $G$lastusername$W, from $F$lastuserhost$W +$X | Login - out$W (duration): $F$lastusertime$W + +$X Last time$W $G$lastcurrentusername$W$X logged in$W, was from $F$lastcurrentuserhost$W +$X | Login - out$W (duration): $F$lastcurrentusertime$N" +fi + +# logged users +echo +echo -e "$G"$loggeduserscount"$W ${X}session$W(s)$X are logged in$W:|${S}Session(s)${W}|${S}User$W${G}\n`echo -e "$loggedusersnames"`$N" | column -t -s '|' | sed 's/^/ /' diff --git a/motdfetch.sample.conf b/motdfetch.sample.conf new file mode 100644 index 0000000..7704cd0 --- /dev/null +++ b/motdfetch.sample.conf @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# +# user configuration file for MOTDfetch + +# GLOBAL COLORS ###################################################### + +c_txt="39" +c_txt_emphase="35" +c_txt_deco="97" +c_txt_invert="30" +c_bg="47" +c_danger="31" +c_warning="33" +c_success="32" +c_title="${c_bg};1;${c_txt_invert}m" + +# GLOBAL OPTIONS #################################################### + +## api website name for plain public IP4/6 check +global_publicip_api="icanhazip.com" + +# MODULES OPTIONS #################################################### + +## HEADER module +module_header_disable=0 +### custom text (comment to use username, trim > 80 char) +module_header_text="BANNER" + +## SYSINFO module +module_sysinfo_disable=0 + +## SERVICES module +module_services_disable=0 +### services to check +### + lookup system & current user services running with these 2 commands: +### systemctl --type=service --no-pager -q --state running +### systemctl --type=service --user --no-pager -q --state running +### + to add user services you need to provide the uid of the user ("service:uid"), find the uid with: +### id -u +module_services=( + "cron" + "unattended-upgrades.service" + "dbus:1000" +) +### column max-width +module_services_width="80" + +## FILESYSTEM module +module_filesystem_disable=0 +### disable the ZFS section of the module +module_filesystem_zfs_disable=0 +### max blocks usage percentage warning threshold +module_filesystem_max_usage="85" + +## TLS module +module_tls_disable=0 +### Domains and ports (default:433) for TLS certification check +module_tls_domains=( + "www.google.com" + "w.fakedomain.com" +) + +## POSTQUEUE module +module_postqueue_disable=0 + +## FAIL2BAN module +module_fail2ban_disable=0 + +## USERLOG module +module_userslog_disable=0 \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..356a031 --- /dev/null +++ b/readme.md @@ -0,0 +1,112 @@ +# [MOTDfetch][motdfetch_repo] + +![Bash](https://img.shields.io/badge/bash-1f425f.svg?style=for-the-badge&logo=image%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw%2FeHBhY2tldCBiZWdpbj0i77u%2FIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8%2BIDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkE3MDg2QTAyQUZCMzExRTVBMkQxRDMzMkJDMUQ4RDk3IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkE3MDg2QTAzQUZCMzExRTVBMkQxRDMzMkJDMUQ4RDk3Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTcwODZBMDBBRkIzMTFFNUEyRDFEMzMyQkMxRDhEOTciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTcwODZBMDFBRkIzMTFFNUEyRDFEMzMyQkMxRDhEOTciLz4gPC9yZGY6RGVzY3JpcHRpb24%2BIDwvcmRmOlJERj4gPC94OnhtcG1ldGE%2BIDw%2FeHBhY2tldCBlbmQ9InIiPz6lm45hAAADkklEQVR42qyVa0yTVxzGn7d9Wy03MS2ii8s%2BeokYNQSVhCzOjXZOFNF4jx%2BMRmPUMEUEqVG36jo2thizLSQSMd4N8ZoQ8RKjJtooaCpK6ZoCtRXKpRempbTv5ey83bhkAUphz8fznvP8znn%2B%2F3NeEEJgNBoRRSmz0ub%2FfuxEacBg%2FDmYtiCjgo5NG2mBXq%2BH5I1ogMRk9Zbd%2BQU2e1ML6VPLOyf5tvBQ8yT1lG10imxsABm7SLs898GTpyYynEzP60hO3trHDKvMigUwdeaceacqzp7nOI4n0SSIIjl36ao4Z356OV07fSQAk6xJ3XGg%2BLCr1d1OYlVHp4eUHPnerU79ZA%2F1kuv1JQMAg%2BE4O2P23EumF3VkvHprsZKMzKwbRUXFEyTvSIEmTVbrysp%2BWr8wfQHGK6WChVa3bKUmdWou%2BjpArdGkzZ41c1zG%2Fu5uGH4swzd561F%2BuhIT4%2BLnSuPsv9%2BJKIpjNr9dXYOyk7%2FBZrcjIT4eCnoKgedJP4BEqhG77E3NKP31FO7cfQA5K0dSYuLgz2TwCWJSOBzG6crzKK%2BohNfni%2Bx6OMUMMNe%2Fgf7ocbw0v0acKg6J8Ql0q%2BT%2FAXR5PNi5dz9c71upuQqCKFAD%2BYhrZLEAmpodaHO3Qy6TI3NhBpbrshGtOWKOSMYwYGQM8nJzoFJNxP2HjyIQho4PewK6hBktoDcUwtIln4PjOWzflQ%2Be5yl0yCCYgYikTclGlxadio%2BBQCSiW1UXoVGrKYwH4RgMrjU1HAB4vR6LzWYfFUCKxfS8Ftk5qxHoCUQAUkRJaSEokkV6Y%2F%2BJUOC4hn6A39NVXVBYeNP8piH6HeA4fPbpdBQV5KOx0QaL1YppX3Jgk0TwH2Vg6S3u%2BdB91%2B%2FpuNYPYFl5uP5V7ZqvsrX7jxqMXR6ff3gCQSTzFI0a1TX3wIs8ul%2Bq4HuWAAiM39vhOuR1O1fQ2gT%2F26Z8Z5vrl2OHi9OXZn995nLV9aFfS6UC9JeJPfuK0NBohWpCHMSAAsFe74WWP%2BvT25wtP9Bpob6uGqqyDnOtaeumjRu%2ByFu36VntK%2FPA5umTJeUtPWZSU9BCgud661odVp3DZtkc7AnYR33RRC708PrVi1larW7XwZIjLnd7R6SgSqWSNjU1B3F72pz5TZbXmX5vV81Yb7Lg7XT%2FUXriu8XLVqw6c6XqWnBKiiYU%2BMt3wWF7u7i91XlSEITwSAZ%2FCzAAHsJVbwXYFFEAAAAASUVORK5CYII%3D) +![Debian](https://img.shields.io/badge/Debian-D70A53?style=for-the-badge&logo=debian&logoColor=white) +![Ubuntu](https://img.shields.io/badge/Ubuntu-E95420?style=for-the-badge&logo=ubuntu&logoColor=white) + +**MOTDfetch** is a modular & dynamic **MOTD replacement** written in Bash and a nice **command line information tool** for Debian/Ubuntu systems. + +>*MOTD - "message of the day" +>Feature used on Unix-like CLI systems to send a common message to all users after a successful login, and just before it executes the login shell. +>Newer Unix-like systems may generate the message dynamically when the host boots or a user logs in.* + +**Drawbacks** with the current implementation of **MOTD with PAM on Debian and Ubuntu** are at least 3: + 1. The **SSH client does not decide** whether to activate the MOTD. +As it is loaded before the shell login is opened: + 2. User has to **wait blindly** for the end of the execution if there is a dynamic script. + 3. Scripts **can not check the user sessions** status. + +MOTDfetch **avoids** and partially **disable the original MOTD implementation**, but **remains compatible** with it for PAM login. + +--- + +>###### Table of contents +>**[FEATURES](#features)** +>**[INSTALLATION](#installation)** +>**[USAGE](#usage)** + +[![Preview of MOTDfetch][preview_image]][preview_image_url] + +--- + +## Features + +- Usable as a **command**, a **login MOTD** or **SSH MOTD**. +- **[8 Core modules](#8_core_modules)**. +- Uniform and **easily customizable**. +- **Emphasis** of important elements and status by **colors, symbols or flashing**. +- **Notice message** upon missing dependencies +- **Reduced operation mode fot high load** (enforceable). +- Separate **system and users configuration** files. +- **Scripted installation** and updates. +- **Modules or sections can be disabled**. +- Standalone **executable modules**. + +### 8 Core modules + +#### **HEADER module** + +- decorative banner (custom text or username) +- welcome message with username, date and time +- user mail inbox status + +#### **SYSYINFO module** + +- distribution name, kernel version, uptime +- CPU, cores, load, processes, memory, swap +- hostname, network interface, DNS servers, public IPs/DNS/rDNS records +- custom external API for public IP check +- skip connectivity related tests if the API check fail +- short timeout on dig & curl commands to keep the script snappy + +#### **SERVICES module** + +- check systemd services status +- by service name and user UID if not system +- :warning: user cannot check other user's services + +#### **FILESYSTEM module** + +- 2 sections: general filesystems, ZFS filesystems +- name, fs type, usage and size of disk or ZFS pool +- inodes usage and size when relevant +- health status for ZFS +- custom usage threshold for warning hints + +#### **TLS module** + +- check X.509 certificates status with openssl +- accept domain and optional port (443 by default) +- validate name correspondence and expiration date +- skip the test if there is no internet connectivity +- short timeout on openssl command to keep the script snappy + +#### **POSTQUEUE module** + +- postfix deferred queue status +- short timeout on the parsing to keep the script snappy if there is thousands of mails + +#### **FAIL2BAN module** + +- display fail2ban jails status +- :warning: need to be executed with root or sudo + +#### **USERSLOG module** + + - display last user logged in + - name, IP and connection range + - if the current user is different, display his last log too + - display currently logged sessions with usernames + +## Installation + +### Requirements + +## Usage + +--- + +[//]: # (LINKS) +[motdfetch_repo]: https://git.tkapias.net/tkapias/MOTDfetch +[preview_image]: docs/img/preview.jpg "Preview of MOTDfetch" +[preview_image_url]: https://git.tkapias.net/tkapias/MOTDfetch/raw/master/docs/img/preview.jpg