#!/usr/bin/env bash ######################################################################################### #### sysinfo.sh — One-shot system information dump for Linux servers #### #### Shows OS, CPU, memory, disk, network, services, load, and uptime at a glance #### #### No dependencies beyond coreutils and standard Linux tools #### #### #### #### Author: Phil Connor #### #### Contact: contact@mylinux.work #### #### License: MIT #### #### Version 1.01 #### #### #### #### Usage: #### #### ./sysinfo.sh #### #### ./sysinfo.sh --no-color #### #### ./sysinfo.sh --section disk,network #### #### #### #### See --help for all options. #### ######################################################################################### set -euo pipefail # ── Defaults ────────────────────────────────────────────────────────── SECTIONS="${SECTIONS:-all}" VERBOSE="${VERBOSE:-false}" COLOR="${COLOR:-auto}" # ── State ───────────────────────────────────────────────────────────── SCRIPT_NAME="$(basename "$0")" readonly SCRIPT_NAME # ── Colors ──────────────────────────────────────────────────────────── setup_colors() { if [[ "$COLOR" == "never" ]]; then RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET="" return fi if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' RESET='\033[0m' else RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET="" fi } # ── Logging ─────────────────────────────────────────────────────────── log() { echo -e "${BLUE}[INFO]${RESET} $*"; } verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; } # ── Helpers ─────────────────────────────────────────────────────────── section_header() { echo "" echo -e " ${BOLD}${CYAN}── $1 ──${RESET}" echo "" } field() { printf " ${BOLD}%-22s${RESET} %s\n" "$1" "$2" } field_color() { printf " ${BOLD}%-22s${RESET} %b\n" "$1" "$2" } human_bytes() { local bytes="$1" if [[ "$bytes" -ge 1073741824 ]]; then awk "BEGIN { printf \"%.1f GiB\", $bytes / 1073741824 }" elif [[ "$bytes" -ge 1048576 ]]; then awk "BEGIN { printf \"%.1f MiB\", $bytes / 1048576 }" elif [[ "$bytes" -ge 1024 ]]; then awk "BEGIN { printf \"%.1f KiB\", $bytes / 1024 }" else echo "${bytes} B" fi } should_show() { [[ "$SECTIONS" == "all" ]] || [[ ",$SECTIONS," == *",$1,"* ]] } # ══════════════════════════════════════════════════════════════════════ # OS INFORMATION # ══════════════════════════════════════════════════════════════════════ show_os() { section_header "Operating System" local hostname_val hostname_val=$(hostname -f 2>/dev/null || hostname) field "Hostname:" "$hostname_val" if [[ -f /etc/os-release ]]; then local pretty_name version_id # shellcheck disable=SC1091 pretty_name=$(. /etc/os-release && echo "${PRETTY_NAME:-Unknown}") # shellcheck disable=SC1091 version_id=$(. /etc/os-release && echo "${VERSION_ID:-}") field "Distribution:" "$pretty_name" if [[ -n "$version_id" ]]; then field "Version:" "$version_id" fi elif [[ -f /etc/redhat-release ]]; then field "Distribution:" "$(cat /etc/redhat-release)" fi field "Kernel:" "$(uname -r)" field "Architecture:" "$(uname -m)" field "Uptime:" "$(uptime -p 2>/dev/null || uptime | sed 's/.*up //' | sed 's/, [0-9]* user.*//')" field "Boot time:" "$(who -b 2>/dev/null | awk '{print $3, $4}' || echo "N/A")" local tz tz=$(timedatectl 2>/dev/null | grep "Time zone" | awk '{print $3}' || echo "${TZ:-$(cat /etc/timezone 2>/dev/null || echo 'Unknown')}") field "Timezone:" "$tz" field "Current time:" "$(date '+%Y-%m-%d %H:%M:%S %Z')" if command -v sestatus &>/dev/null; then local selinux_status selinux_status=$(sestatus 2>/dev/null | grep "SELinux status" | awk '{print $3}' || echo "N/A") field "SELinux:" "$selinux_status" fi } # ══════════════════════════════════════════════════════════════════════ # CPU INFORMATION # ══════════════════════════════════════════════════════════════════════ show_cpu() { section_header "CPU" if [[ -f /proc/cpuinfo ]]; then local model cores sockets model=$(grep -m1 "model name" /proc/cpuinfo | cut -d: -f2 | sed 's/^ //') cores=$(grep -c "^processor" /proc/cpuinfo || true) sockets=$(grep "physical id" /proc/cpuinfo | sort -u | wc -l) field "Model:" "${model:-Unknown}" field "Logical CPUs:" "$cores" if [[ "$sockets" -gt 0 ]]; then field "Sockets:" "$sockets" fi else field "CPUs:" "$(nproc 2>/dev/null || echo 'Unknown')" fi # Load averages if [[ -f /proc/loadavg ]]; then local load load=$(cut -d' ' -f1-3 /proc/loadavg) field "Load average:" "$load" fi # CPU usage snapshot if command -v mpstat &>/dev/null; then local idle idle=$(mpstat 1 1 2>/dev/null | tail -1 | awk '{print $NF}') if [[ -n "$idle" ]]; then local used used=$(awk "BEGIN { printf \"%.1f\", 100 - $idle }") field "CPU usage:" "${used}%" fi fi } # ══════════════════════════════════════════════════════════════════════ # MEMORY INFORMATION # ══════════════════════════════════════════════════════════════════════ show_memory() { section_header "Memory" if [[ -f /proc/meminfo ]]; then local total_kb free_kb avail_kb buffers_kb cached_kb swap_total_kb swap_free_kb total_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo) free_kb=$(awk '/^MemFree:/ {print $2}' /proc/meminfo) avail_kb=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo) buffers_kb=$(awk '/^Buffers:/ {print $2}' /proc/meminfo) cached_kb=$(awk '/^Cached:/ {print $2}' /proc/meminfo) swap_total_kb=$(awk '/^SwapTotal:/ {print $2}' /proc/meminfo) swap_free_kb=$(awk '/^SwapFree:/ {print $2}' /proc/meminfo) local total_b used_b avail_b total_b=$((total_kb * 1024)) avail_b=$((avail_kb * 1024)) used_b=$(( (total_kb - free_kb - buffers_kb - cached_kb) * 1024 )) local pct_used pct_used=$(awk "BEGIN { printf \"%.0f\", ($total_kb - $avail_kb) * 100 / $total_kb }") local color="$GREEN" if [[ "$pct_used" -ge 90 ]]; then color="$RED" elif [[ "$pct_used" -ge 75 ]]; then color="$YELLOW" fi field "Total:" "$(human_bytes "$total_b")" field_color "Used:" "${color}$(human_bytes "$used_b") (${pct_used}%)${RESET}" field "Available:" "$(human_bytes "$avail_b")" if [[ "$swap_total_kb" -gt 0 ]]; then local swap_used_kb swap_pct swap_used_kb=$((swap_total_kb - swap_free_kb)) swap_pct=$(awk "BEGIN { printf \"%.0f\", $swap_used_kb * 100 / $swap_total_kb }") field "Swap:" "$(human_bytes $((swap_total_kb * 1024))) total, $(human_bytes $((swap_used_kb * 1024))) used (${swap_pct}%)" else field "Swap:" "None" fi else free -h 2>/dev/null || echo " Memory info unavailable" fi } # ══════════════════════════════════════════════════════════════════════ # DISK INFORMATION # ══════════════════════════════════════════════════════════════════════ show_disk() { section_header "Disk" printf " ${BOLD}%-20s %8s %8s %8s %6s${RESET}\n" "FILESYSTEM" "SIZE" "USED" "AVAIL" "USE%" printf " %s\n" "$(printf '%.0s─' {1..58})" df -h --output=target,size,used,avail,pcent -x tmpfs -x devtmpfs -x overlay 2>/dev/null | tail -n +2 | sort | while IFS= read -r line; do local mount size used avail pct mount=$(echo "$line" | awk '{print $1}') size=$(echo "$line" | awk '{print $2}') used=$(echo "$line" | awk '{print $3}') avail=$(echo "$line" | awk '{print $4}') pct=$(echo "$line" | awk '{print $5}' | tr -d '%') local color="$GREEN" if [[ -n "$pct" ]]; then if [[ "$pct" -ge 90 ]]; then color="$RED" elif [[ "$pct" -ge 75 ]]; then color="$YELLOW" fi fi printf " %-20s %8s %8s %8s %b%5s%%%b\n" "$mount" "$size" "$used" "$avail" "$color" "${pct:-?}" "$RESET" done # Inode usage for root echo "" local inode_pct inode_pct=$(df -i / 2>/dev/null | tail -1 | awk '{print $5}' | tr -d '%') if [[ -n "$inode_pct" ]]; then local icolor="$GREEN" if [[ "$inode_pct" -ge 90 ]]; then icolor="$RED" elif [[ "$inode_pct" -ge 75 ]]; then icolor="$YELLOW"; fi field_color "Inode usage (/):" "${icolor}${inode_pct}%${RESET}" fi } # ══════════════════════════════════════════════════════════════════════ # NETWORK INFORMATION # ══════════════════════════════════════════════════════════════════════ show_network() { section_header "Network" # Interfaces with IPs printf " ${BOLD}%-16s %-18s %-16s %s${RESET}\n" "INTERFACE" "IPv4" "STATE" "MAC" printf " %s\n" "$(printf '%.0s─' {1..65})" if command -v ip &>/dev/null; then ip -o addr show 2>/dev/null | grep "inet " | while IFS= read -r line; do local iface addr state mac iface=$(echo "$line" | awk '{print $2}') addr=$(echo "$line" | awk '{print $4}') [[ "$iface" == "lo" ]] && continue state=$(ip link show "$iface" 2>/dev/null | grep -o "state [A-Z]*" | awk '{print $2}') mac=$(ip link show "$iface" 2>/dev/null | grep "link/ether" | awk '{print $2}') printf " %-16s %-18s %-16s %s\n" "$iface" "$addr" "${state:-UNKNOWN}" "${mac:--}" done fi # Default gateway local gw gw=$(ip route show default 2>/dev/null | awk '{print $3, "via", $5}' | head -1) if [[ -n "$gw" ]]; then echo "" field "Default gateway:" "$gw" fi # DNS servers local dns dns=$(grep "^nameserver" /etc/resolv.conf 2>/dev/null | awk '{print $2}' | tr '\n' ' ') if [[ -n "$dns" ]]; then field "DNS servers:" "$dns" fi # Listening ports (top 15) if command -v ss &>/dev/null; then echo "" printf " ${BOLD}%-8s %-24s %s${RESET}\n" "PROTO" "LISTEN" "PROCESS" printf " %s\n" "$(printf '%.0s─' {1..55})" ss -tlnp 2>/dev/null | tail -n +2 | head -15 | while IFS= read -r line; do local proto addr proc_info proto="tcp" addr=$(echo "$line" | awk '{print $4}') proc_info=$(echo "$line" | grep -oP 'users:\(\("\K[^"]+' || echo "-") printf " %-8s %-24s %s\n" "$proto" "$addr" "$proc_info" done local total_listeners total_listeners=$(ss -tlnp 2>/dev/null | tail -n +2 | wc -l) if [[ "$total_listeners" -gt 15 ]]; then echo -e " ${DIM}... and $((total_listeners - 15)) more${RESET}" fi fi } # ══════════════════════════════════════════════════════════════════════ # SERVICES # ══════════════════════════════════════════════════════════════════════ show_services() { section_header "Services" if command -v systemctl &>/dev/null; then # Failed services local failed_count failed_count=$(systemctl --no-legend --state=failed 2>/dev/null | wc -l) if [[ "$failed_count" -gt 0 ]]; then field_color "Failed services:" "${RED}${failed_count}${RESET}" echo "" systemctl --no-legend --state=failed 2>/dev/null | while IFS= read -r line; do local unit unit=$(echo "$line" | awk '{print $2}') printf " ${RED}✗${RESET} %s\n" "$unit" done echo "" else field_color "Failed services:" "${GREEN}0${RESET}" fi # Running service count local running_count running_count=$(systemctl --no-legend --type=service --state=running 2>/dev/null | wc -l) field "Running services:" "$running_count" # Enabled timers local timer_count timer_count=$(systemctl --no-legend --type=timer --state=active 2>/dev/null | wc -l) field "Active timers:" "$timer_count" else log "systemd not available" fi } # ══════════════════════════════════════════════════════════════════════ # SECURITY # ══════════════════════════════════════════════════════════════════════ show_security() { section_header "Security" # Pending updates if command -v apt-get &>/dev/null; then local upgradable local apt_output apt_output=$(apt list --upgradable 2>/dev/null || true) upgradable=$(echo "$apt_output" | grep -c "upgradable" || true) field "Pending updates:" "$upgradable" local security_updates security_updates=$(echo "$apt_output" | grep -c "\-security" || true) if [[ "$security_updates" -gt 0 ]]; then field_color "Security updates:" "${RED}${security_updates}${RESET}" fi elif command -v yum &>/dev/null; then local yum_updates yum_updates=$(yum check-update --quiet 2>/dev/null | grep -cE "^\S" || true) field "Pending updates:" "$yum_updates" elif command -v dnf &>/dev/null; then local dnf_updates dnf_updates=$(dnf check-update --quiet 2>/dev/null | grep -cE "^\S" || true) field "Pending updates:" "$dnf_updates" fi # Logged in users local user_count user_count=$(who 2>/dev/null | wc -l) field "Logged in users:" "$user_count" if [[ "$user_count" -gt 0 ]]; then who 2>/dev/null | awk '{printf " %s from %s since %s %s\n", $1, $5, $3, $4}' fi # Reboot required if [[ -f /var/run/reboot-required ]]; then echo "" field_color "Reboot required:" "${YELLOW}YES${RESET}" fi # Last 5 failed login attempts if command -v lastb &>/dev/null && [[ -r /var/log/btmp ]]; then local failed_logins failed_logins=$(lastb -n 5 2>/dev/null | head -5 | grep -c "" || true) if [[ "$failed_logins" -gt 0 ]]; then echo "" field "Recent failed logins:" "" lastb -n 5 2>/dev/null | head -5 | while IFS= read -r line; do [[ -z "$line" ]] && continue printf " %s\n" "$line" done fi fi } # ══════════════════════════════════════════════════════════════════════ # DOCKER / CONTAINERS # ══════════════════════════════════════════════════════════════════════ show_containers() { if ! command -v docker &>/dev/null && ! command -v podman &>/dev/null; then return fi section_header "Containers" local runtime="docker" if ! command -v docker &>/dev/null; then runtime="podman" fi if ! "$runtime" info &>/dev/null 2>&1; then field "Status:" "${runtime} not accessible (permission denied or not running)" return fi local running stopped total running=$("$runtime" ps -q 2>/dev/null | wc -l) total=$("$runtime" ps -aq 2>/dev/null | wc -l) stopped=$((total - running)) field "Runtime:" "$runtime" field "Running:" "$running" field "Stopped:" "$stopped" # Disk usage if [[ "$runtime" == "docker" ]]; then local docker_disk docker_disk=$(docker system df --format "{{.Size}}" 2>/dev/null | head -1) if [[ -n "$docker_disk" ]]; then field "Image disk:" "$docker_disk" fi fi } # ══════════════════════════════════════════════════════════════════════ # USAGE # ══════════════════════════════════════════════════════════════════════ usage() { cat <&2 echo "Run ${SCRIPT_NAME} --help for usage" >&2 exit 1 ;; esac done } # ══════════════════════════════════════════════════════════════════════ # MAIN # ══════════════════════════════════════════════════════════════════════ main() { parse_args "$@" setup_colors echo "" echo -e "${BOLD}System Information — $(hostname -f 2>/dev/null || hostname)${RESET}" echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}" should_show "os" && show_os should_show "cpu" && show_cpu should_show "memory" && show_memory should_show "disk" && show_disk should_show "network" && show_network should_show "services" && show_services should_show "security" && show_security should_show "containers" && show_containers echo "" } main "$@"