Added motd.sh

This commit is contained in:
2026-04-30 20:56:10 -05:00
commit 8be63964f8

135
motd.sh Normal file
View File

@@ -0,0 +1,135 @@
#!/bin/bash
# /etc/profile.d/motd.sh — Custom MOTD (Ubuntu 24.04 compatible)
# Make executable: chmod +x /etc/profile.d/motd.sh
# Disable default MOTD: chmod -x /etc/update-motd.d/*
RESET='\033[0m'
BOLD='\033[1m'
DIM='\033[2m'
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
line() { echo -e "${YELLOW}── $1 $(printf '─%.0s' $(seq 1 $((46 - ${#1}))))${RESET}"; }
# OS detection — lsb_release may not be installed on 24.04 minimal
OS_NAME=$(lsb_release -ds 2>/dev/null)
[[ -z "$OS_NAME" ]] && OS_NAME=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'"' -f2)
# ── Header (single line) ─────────────────────────────────────────────────────
echo -e "${BLUE}${BOLD} $(whoami)@$(hostname -f)${RESET} ${DIM}${OS_NAME} kernel $(uname -r)${RESET}"
# ── System ───────────────────────────────────────────────────────────────────
line "System"
UPTIME=$(uptime -p | sed 's/up //')
LOAD=$(cut -d' ' -f1-3 /proc/loadavg | tr ' ' ',')
# CPU via /proc/stat (two samples, 0.2s apart)
read CPU_LINE1 < /proc/stat; sleep 0.2; read CPU_LINE2 < /proc/stat
IDLE1=$(echo $CPU_LINE1 | awk '{print $5}')
TOTAL1=$(echo $CPU_LINE1 | awk '{for(i=2;i<=NF;i++) t+=$i; print t}')
IDLE2=$(echo $CPU_LINE2 | awk '{print $5}')
TOTAL2=$(echo $CPU_LINE2 | awk '{for(i=2;i<=NF;i++) t+=$i; print t}')
CPU=$(awk "BEGIN {printf \"%.0f\", (1 - ($IDLE2-$IDLE1) / ($TOTAL2-$TOTAL1)) * 100}")
MEM_USED=$(free -h | awk '/^Mem:/ {print $3}')
MEM_TOTAL=$(free -h | awk '/^Mem:/ {print $2}')
MEM_PCT=$(free | awk '/^Mem:/ {printf "%.0f", $3/$2*100}')
DISK_INFO=$(df -h / | awk 'NR==2 {print $3"/"$2" ("$5")"}')
IP=$(hostname -I 2>/dev/null | awk '{print $1}')
[[ $MEM_PCT -gt 80 ]] && MC=$RED || ([[ $MEM_PCT -gt 60 ]] && MC=$YELLOW || MC=$GREEN)
# Two stat lines instead of six
echo -e " ${DIM}up${RESET} $UPTIME ${DIM}load${RESET} $LOAD ${DIM}cpu${RESET} ${GREEN}${CPU}%${RESET}"
echo -e " ${DIM}mem${RESET} ${MC}${MEM_USED}/${MEM_TOTAL} (${MEM_PCT}%)${RESET} ${DIM}disk/${RESET} $DISK_INFO ${DIM}ip${RESET} $IP"
# ── Recent Logins & Failed Attempts ──────────────────────────────────────────
line "Logins"
# Trim full timestamps down to day + date + duration only
last -n 3 -F 2>/dev/null | grep -v "^$\|reboot\|wtmp\|still" | \
awk '{
user=$1; tty=$2; host=$3;
day=$4; mon=$5; dom=$6;
# Duration is in last field like (03:21) or (00:14)
dur=$NF; gsub(/[()]/,"",dur);
printf " \033[0;32m✔\033[0m \033[2m%-8s\033[0m %-6s %-18s %s %s %s \033[2m(%s)\033[0m\n",
user, tty, host, day, mon, dom, dur
}'
# Failed login attempts
TODAY=$(date '+%b %e' | sed 's/ / /')
if [[ -r /var/log/auth.log ]]; then
FAIL_IPS=$(grep "Failed password\|Invalid user" /var/log/auth.log 2>/dev/null | grep "$TODAY" | \
awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' | sort | uniq -c | sort -rn | head -2)
else
FAIL_IPS=$(journalctl -u ssh --since today -q 2>/dev/null | \
grep "Failed password\|Invalid user" | \
awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' | sort | uniq -c | sort -rn | head -2)
fi
[[ -n "$FAIL_IPS" ]] && echo "$FAIL_IPS" | while read COUNT SRCIP; do
echo -e " ${RED}${RESET} ${DIM}failed${RESET} sshd ${RED}$SRCIP${RESET} (×${COUNT} today)"
done
# ── Docker Containers ─────────────────────────────────────────────────────────
if command -v docker &>/dev/null && docker info &>/dev/null 2>&1; then
line "Docker"
CONTAINER_COUNT=$(docker ps -aq | wc -l)
if [[ $CONTAINER_COUNT -eq 0 ]]; then
echo -e " ${DIM}no containers${RESET}"
else
docker ps -a --format "{{.Names}}\t{{.Status}}\t{{.Ports}}" | \
while IFS=$'\t' read -r NAME STATUS PORTS; do
# Condense ports to just host-side port numbers
SHORT_PORTS=$(echo "$PORTS" | grep -oP '0\.0\.0\.0:\K[0-9]+(?=->)' | sort -un | tr '\n' ',' | sed 's/,$//')
[[ -n "$SHORT_PORTS" ]] && PORT_STR=" ${DIM}:${SHORT_PORTS}${RESET}" || PORT_STR=""
if echo "$STATUS" | grep -q "^Up"; then
UPFOR=$(echo "$STATUS" | sed 's/Up //' | sed 's/ (.*//')
if echo "$STATUS" | grep -qE "Up [0-9]+ seconds?|Up [0-9]+ minutes?|Up 1 hour"; then
echo -e " ${YELLOW}${RESET} ${BOLD}${NAME}${RESET}${PORT_STR} ${YELLOW}restarted recently${RESET}"
else
echo -e " ${GREEN}${RESET} ${BOLD}${NAME}${RESET}${PORT_STR} ${DIM}up ${UPFOR}${RESET}"
fi
else
EXITED=$(echo "$STATUS" | sed 's/Exited ([0-9]*) //')
echo -e " ${RED}${RESET} ${BOLD}${NAME}${RESET} ${RED}exited ${EXITED}${RESET}"
fi
done
# Image ages — one line each, inline after containers
echo -e " ${DIM}images:${RESET}"
docker images --format "{{.Repository}}:{{.Tag}}\t{{.CreatedSince}}" 2>/dev/null | \
grep -v "<none>" | head -5 | \
while IFS=$'\t' read -r IMAGE AGO; do
if echo "$AGO" | grep -qE "months? ago|years? ago"; then
echo -e " ${DIM}${IMAGE}${RESET} ${YELLOW}${AGO}${RESET}"
else
echo -e " ${DIM}${IMAGE} ${AGO}${RESET}"
fi
done
fi
fi
# ── System Updates ────────────────────────────────────────────────────────────
line "Updates"
if command -v apt &>/dev/null; then
UPGRADABLE_LIST=$(apt list --upgradable 2>/dev/null | grep -v "^Listing")
UPGRADABLE=$(echo "$UPGRADABLE_LIST" | grep -c '/' || true)
SECURITY=$(echo "$UPGRADABLE_LIST" | grep -ic 'security' || true)
STANDARD=$((UPGRADABLE - SECURITY))
APT_STAMP=/var/lib/apt/periodic/update-success-stamp
LAST_UPDATE=$([[ -f $APT_STAMP ]] && stat -c %y "$APT_STAMP" 2>/dev/null | cut -d' ' -f1 || echo "unknown")
if [[ $UPGRADABLE -eq 0 ]]; then
echo -e " ${GREEN}up to date${RESET} ${DIM}checked ${LAST_UPDATE}${RESET}"
else
[[ $SECURITY -gt 0 ]] \
&& echo -e " ${RED}! ${SECURITY} security${RESET} ${DIM}${STANDARD} standard checked ${LAST_UPDATE}${RESET}" \
|| echo -e " ${DIM}${STANDARD} standard updates checked ${LAST_UPDATE}${RESET}"
fi
fi
echo ""