a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
550 lines
22 KiB
Bash
550 lines
22 KiB
Bash
#!/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 <<EOF
|
|
${SCRIPT_NAME} — One-shot system information dump
|
|
|
|
USAGE:
|
|
${SCRIPT_NAME} [OPTIONS]
|
|
|
|
OPTIONS:
|
|
--section SECTIONS Comma-separated sections to show (default: all)
|
|
Available: os, cpu, memory, disk, network, services,
|
|
security, containers
|
|
--verbose Enable debug output
|
|
--no-color Disable colored output
|
|
--help Show this help
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
SECTIONS Sections to display (default: all)
|
|
COLOR Color mode: auto, always, never (default: auto)
|
|
|
|
EXAMPLES:
|
|
# Full system info
|
|
./sysinfo.sh
|
|
|
|
# Disk and network only
|
|
./sysinfo.sh --section disk,network
|
|
|
|
# Pipe-friendly output
|
|
./sysinfo.sh --no-color | tee /tmp/sysinfo-\$(hostname).txt
|
|
EOF
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# ARGUMENT PARSING
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--section)
|
|
SECTIONS="$2"; shift 2 ;;
|
|
--verbose)
|
|
VERBOSE="true"; shift ;;
|
|
--no-color)
|
|
COLOR="never"; shift ;;
|
|
--help|-h)
|
|
setup_colors
|
|
usage
|
|
exit 0 ;;
|
|
*)
|
|
echo "Unknown option: $1" >&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 "$@"
|