#!/usr/bin/env bash set -euo pipefail #=============================================================================== # 登录信息展示脚本 (Login Info) # 用途: 快速查看系统/资源/网络/Docker 等信息,适合作为 SSH 登录后提示。 # # 手动运行: # bash login-info/login-info.sh # # 安装为登录自动展示: # sudo bash login-info/install_login-info.sh # # 环境变量: # LOGIN_INFO_MODE=summary|full # 默认 summary # LOGIN_INFO_COLOR=auto|always|never # 默认 auto #=============================================================================== usage() { local prog prog=${0##*/} cat </dev/null 2>&1; } init_colors() { local color_mode=${LOGIN_INFO_COLOR:-auto} case "$color_mode" in auto|always|never) ;; *) color_mode=auto ;; esac local enable_color=0 if [ "$color_mode" = "always" ]; then enable_color=1 elif [ "$color_mode" = "never" ] || [ -n "${NO_COLOR:-}" ]; then enable_color=0 else if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then enable_color=1 fi fi if [ "$enable_color" -eq 1 ]; then C_RESET='\033[0m' C_BOLD='\033[1m' C_TITLE='\033[1;36m' C_INFO='\033[1;33m' C_VALUE='\033[0;37m' C_LABEL='\033[0;32m' C_SUBTLE='\033[1;30m' C_BLUE='\033[1;34m' C_RED='\033[0;31m' else C_RESET='' C_BOLD='' C_TITLE='' C_INFO='' C_VALUE='' C_LABEL='' C_SUBTLE='' C_BLUE='' C_RED='' fi HR='───────────────────────────────────────────────────────────────────────────────' } print_hr() { printf '%b%s%b\n' "${C_SUBTLE}" "${HR}" "${C_RESET}"; } print_title() { printf '%b%s%b\n' "${C_TITLE}${C_BOLD}" "$1" "${C_RESET}"; } print_kv() { printf ' %b%s:%b %b%s%b\n' "${C_LABEL}" "$1" "${C_RESET}" "${C_VALUE}" "$2" "${C_RESET}"; } print_kv_info() { printf ' %b%s:%b %b%s%b\n' "${C_LABEL}" "$1" "${C_RESET}" "${C_INFO}" "$2" "${C_RESET}"; } format_kb() { local kb=${1:-} case "$kb" in ''|*[!0-9]*) printf 'N/A'; return 0 ;; esac if [ "$kb" -ge 1048576 ]; then awk -v v="$kb" 'BEGIN{printf "%.1fGiB", v/1048576}' elif [ "$kb" -ge 1024 ]; then awk -v v="$kb" 'BEGIN{printf "%.1fMiB", v/1024}' else printf '%sKiB' "$kb" fi } get_os_pretty_name() { local os='' if has_cmd lsb_release; then os=$(lsb_release -ds 2>/dev/null || true) os=${os#\"} os=${os%\"} fi if [ -z "$os" ] && [ -r /etc/os-release ]; then os=$( awk -F= ' $1=="PRETTY_NAME"{ gsub(/^"/,"",$2); gsub(/"$/,"",$2); print $2; exit }' /etc/os-release 2>/dev/null || true ) fi printf '%s' "${os:-N/A}" } get_boot_time() { local boot='' if has_cmd uptime; then boot=$(uptime -s 2>/dev/null || true) fi if [ -z "$boot" ] && has_cmd who; then boot=$(who -b 2>/dev/null | awk '{print $3" "$4}' || true) fi printf '%s' "${boot:-N/A}" } get_uptime_pretty() { local up='' if has_cmd uptime; then up=$(uptime -p 2>/dev/null | sed 's/^up //') fi if [ -z "$up" ] && [ -r /proc/uptime ]; then local seconds seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0) if [ "$seconds" -gt 0 ] 2>/dev/null; then local days hours minutes days=$((seconds / 86400)) hours=$(((seconds % 86400) / 3600)) minutes=$(((seconds % 3600) / 60)) if [ "$days" -gt 0 ]; then up="${days}天 ${hours}小时 ${minutes}分钟" elif [ "$hours" -gt 0 ]; then up="${hours}小时 ${minutes}分钟" else up="${minutes}分钟" fi fi fi printf '%s' "${up:-N/A}" } get_load_avg() { local load='' if [ -r /proc/loadavg ]; then load=$(awk '{print $1" "$2" "$3}' /proc/loadavg 2>/dev/null || true) fi if [ -z "$load" ] && has_cmd uptime; then load=$(uptime 2>/dev/null | awk -F'load average: ' '{print $2}' || true) fi printf '%s' "${load:-N/A}" } get_cpu_model() { local model='' if [ -r /proc/cpuinfo ]; then model=$(awk -F': *' '/model name/{print $2; exit}' /proc/cpuinfo 2>/dev/null || true) [ -n "$model" ] || model=$(awk -F': *' '/Hardware/{print $2; exit}' /proc/cpuinfo 2>/dev/null || true) [ -n "$model" ] || model=$(awk -F': *' '/Processor/{print $2; exit}' /proc/cpuinfo 2>/dev/null || true) fi if [ -z "$model" ] && has_cmd lscpu; then model=$(lscpu 2>/dev/null | awk -F': *' '/Model name:/{print $2; exit}' || true) fi printf '%s' "${model:-N/A}" } get_cpu_cores() { local cores='' if has_cmd nproc; then cores=$(nproc 2>/dev/null || true) fi if [ -z "$cores" ] && [ -r /proc/cpuinfo ]; then cores=$(awk -F': *' '/^processor/{c++} END{print c+0}' /proc/cpuinfo 2>/dev/null || true) fi printf '%s' "${cores:-N/A}" } get_cpu_temp_c() { local temp='' if has_cmd sensors; then temp=$( sensors 2>/dev/null | awk ' BEGIN{IGNORECASE=1} /(package id 0|tctl|tdie|cpu temperature|core 0|composite|temp1)/{ for(i=1;i<=NF;i++){ if($i ~ /^[+]?[0-9]+([.][0-9]+)?°C$/){ gsub(/[+°C]/,"",$i); print $i; exit } } }' || true ) fi if [ -z "$temp" ]; then local zone for zone in /sys/class/thermal/thermal_zone*; do [ -r "$zone/temp" ] || continue local temp_raw temp_raw=$(cat "$zone/temp" 2>/dev/null || true) case "$temp_raw" in ''|*[!0-9]*) continue ;; esac if [ "$temp_raw" -gt 0 ] 2>/dev/null; then if [ "$temp_raw" -ge 1000 ]; then temp=$(awk -v t="$temp_raw" 'BEGIN{printf "%.1f", t/1000}') else temp=$temp_raw fi break fi done fi printf '%s' "$temp" } section_system() { print_hr print_title "系统概览" print_kv "主机名" "$(hostname 2>/dev/null || echo N/A)" print_kv "操作系统" "$(get_os_pretty_name)" print_kv "内核版本" "$(uname -sr 2>/dev/null || echo N/A)" print_kv "系统架构" "$(uname -m 2>/dev/null || echo N/A)" print_kv "启动时间" "$(get_boot_time)" print_kv "运行时间" "$(get_uptime_pretty)" print_kv "平均负载" "$(get_load_avg)" print_kv "当前时间" "$(date '+%F %T %Z' 2>/dev/null || echo N/A)" print_hr } section_resources() { print_title "硬件与资源" local cpu_model cpu_cores cpu_temp cpu_model=$(get_cpu_model) cpu_cores=$(get_cpu_cores) cpu_temp=$(get_cpu_temp_c) if [ -n "$cpu_temp" ]; then print_kv_info "CPU" "${cpu_model} (${cpu_cores} 核, ${cpu_temp}°C)" else print_kv "CPU" "${cpu_model} (${cpu_cores} 核)" fi local mem_total_kb mem_avail_kb mem_used_percent mem_total_kb=$(awk '/MemTotal:/ {print $2; exit}' /proc/meminfo 2>/dev/null || true) mem_avail_kb=$(awk '/MemAvailable:/ {print $2; exit}' /proc/meminfo 2>/dev/null || true) mem_used_percent=$( awk -v t="${mem_total_kb:-0}" -v a="${mem_avail_kb:-0}" ' BEGIN{ if(t>0 && a>0){ printf "%.1f%%", (t-a)*100/t } else { print "N/A" } }' 2>/dev/null || true ) print_kv "内存" "总计: $(format_kb "$mem_total_kb") | 已用: ${mem_used_percent} | 可用: $(format_kb "$mem_avail_kb")" local disk_type disk_size disk_used disk_avail disk_percent disk_type=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $2}' || true) disk_size=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $3}' || true) disk_used=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $4}' || true) disk_avail=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $5}' || true) disk_percent=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $6}' || true) if [ -n "$disk_size" ] && [ -n "$disk_percent" ]; then print_kv "磁盘(/)" "${disk_type:-N/A} 总:${disk_size} 已用:${disk_used} (${disk_percent}) 可用:${disk_avail}" else print_kv "磁盘(/)" "N/A" fi } section_storage_full() { print_title "存储明细" if ! has_cmd df; then print_kv "df" "未安装" return 0 fi df -hPT \ -x tmpfs -x devtmpfs -x devpts -x proc -x sysfs -x cgroup -x cgroup2 \ -x securityfs -x pstore -x efivarfs -x autofs -x debugfs -x mqueue \ -x hugetlbfs -x tracefs -x fusectl -x squashfs 2>/dev/null \ | awk 'NR==1{next} {printf "%s|%s|%s|%s|%s|%s\n",$7,$2,$3,$4,$6,$1}' \ | while IFS='|' read -r mount fs_type size used percent device; do case "$mount" in /var/lib/docker/*|/run/*|/snap/*) continue ;; esac printf ' %b%s%b (%b%s%b - %s)\n' "${C_INFO}" "$mount" "${C_RESET}" "${C_VALUE}" "$device" "${C_RESET}" "$fs_type" printf ' %b总:%b %b%s%b, %b已用:%b %b%s%b (%b%s%b)\n' \ "${C_LABEL}" "${C_RESET}" "${C_BLUE}" "$size" "${C_RESET}" \ "${C_LABEL}" "${C_RESET}" "${C_BLUE}" "$used" "${C_RESET}" \ "${C_VALUE}" "$percent" "${C_RESET}" done } section_network() { print_title "网络连接" if has_cmd ip; then local default_route default_iface default_gw default_route=$(ip route show default 2>/dev/null | head -n 1 || true) if [ -n "$default_route" ]; then default_gw=$(printf '%s\n' "$default_route" | awk '{print $3}' || true) default_iface=$(printf '%s\n' "$default_route" | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}' || true) [ -n "$default_iface" ] && print_kv "默认路由" "dev=${default_iface} gw=${default_gw:-N/A}" fi local has_ip=0 local iface while IFS= read -r iface; do [ -n "$iface" ] || continue case "$iface" in lo|docker*|veth*|br-*|virbr* ) continue ;; esac local ipv4 ipv6 mac ipv4=$(ip -o -4 addr show dev "$iface" scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | paste -sd' ' - || true) ipv6=$(ip -o -6 addr show dev "$iface" scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | paste -sd' ' - || true) mac=$(ip link show dev "$iface" 2>/dev/null | awk '/link\/ether/ {print $2; exit}' || true) if [ -n "$ipv4" ] || [ -n "$ipv6" ]; then has_ip=1 printf ' %b%s%b\n' "${C_INFO}${C_BOLD}" "$iface" "${C_RESET}" [ -n "$ipv4" ] && printf ' %bIPv4:%b %b%s%b\n' "${C_LABEL}" "${C_RESET}" "${C_VALUE}" "$ipv4" "${C_RESET}" [ -n "$ipv6" ] && printf ' %bIPv6:%b %b%s%b\n' "${C_LABEL}" "${C_RESET}" "${C_VALUE}" "$ipv6" "${C_RESET}" [ -n "$mac" ] && printf ' %bMAC:%b %b%s%b\n' "${C_LABEL}" "${C_RESET}" "${C_VALUE}" "$mac" "${C_RESET}" fi done < <(ip -o link show up 2>/dev/null | awk -F': ' '{print $2}' | cut -d'@' -f1) if [ "$has_ip" -eq 0 ]; then print_kv "状态" "未检测到活动的网络连接或 IP 地址" fi else local ips ips=$(hostname -I 2>/dev/null | tr -s ' ' ' ' | sed 's/ $//' || true) if [ -n "$ips" ]; then print_kv "IP" "$ips" else print_kv "状态" "未检测到 IP(ip 命令不可用)" fi fi } section_docker() { local mode=$1 if ! has_cmd docker; then return 0 fi if ! docker ps >/dev/null 2>&1; then return 0 fi local running total running=$(docker ps -q 2>/dev/null | wc -l | awk '{print $1}' || true) total=$(docker ps -aq 2>/dev/null | wc -l | awk '{print $1}' || true) print_title "Docker 容器" print_kv "容器数量" "运行中: ${running:-0} / 总计: ${total:-0}" if [ "$mode" = "full" ] && [ "${running:-0}" -gt 0 ] 2>/dev/null; then local names names=$(docker ps --format '{{.Names}}' 2>/dev/null | paste -sd', ' - || true) [ -n "$names" ] && print_kv "运行列表" "$names" fi } section_activity() { local mode=$1 local user from_ip tty last_login user=${USER:-} [ -n "$user" ] || user=$(id -un 2>/dev/null || echo N/A) from_ip='' if [ -n "${SSH_CONNECTION:-}" ]; then from_ip=$(printf '%s\n' "$SSH_CONNECTION" | awk '{print $1}' || true) fi if [ -z "$from_ip" ] && has_cmd who; then from_ip=$(who -m 2>/dev/null | awk -F'[()]' '{print $2}' | awk '{print $1}' || true) fi tty=$(tty 2>/dev/null || true) print_title "登录与统计" print_kv "当前用户" "$user" [ -n "$from_ip" ] && print_kv "来源地址" "$from_ip" [ -n "$tty" ] && print_kv "终端" "$tty" last_login=$( last -n 5 -w "$user" 2>/dev/null \ | awk -v u="$user" '$1==u && $0 !~ /wtmp begins/ && $0 !~ /still logged in/ {print; exit}' \ || true ) [ -n "$last_login" ] && print_kv "上次登录" "$last_login" if [ "$mode" = "full" ]; then local package_count='N/A' if has_cmd dpkg-query; then package_count=$(dpkg-query -f '${Package}\n' -W 2>/dev/null | wc -l | awk '{print $1}' || true) print_kv "软件包数" "${package_count:-N/A} (Debian/APT)" elif has_cmd rpm; then package_count=$(rpm -qa 2>/dev/null | wc -l | awk '{print $1}' || true) print_kv "软件包数" "${package_count:-N/A} (RPM/Yum/Dnf)" elif has_cmd pacman; then package_count=$(pacman -Qq 2>/dev/null | wc -l | awk '{print $1}' || true) print_kv "软件包数" "${package_count:-N/A} (Pacman)" fi fi } section_gpu() { if ! has_cmd lspci; then return 0 fi local gpus gpus=$(lspci 2>/dev/null | awk -F': ' '/VGA compatible controller|3D controller|Display controller/{print $2}' || true) [ -n "$gpus" ] || return 0 print_title "图形处理器 (GPU)" while IFS= read -r line; do [ -n "$line" ] || continue printf ' %b%s%b\n' "${C_VALUE}" "$line" "${C_RESET}" done <<<"$gpus" } main() { local mode=${LOGIN_INFO_MODE:-summary} local show_docker=1 while [ $# -gt 0 ]; do case "$1" in --summary|--fast) mode=summary ;; --full) mode=full ;; --no-docker) show_docker=0 ;; --no-color) LOGIN_INFO_COLOR=never ;; -h|--help) usage; return 0 ;; *) printf '错误: 未知参数: %s\n' "$1" >&2; usage; return 2 ;; esac shift done init_colors section_system section_resources if [ "$mode" = "full" ]; then section_gpu section_storage_full fi section_network if [ "$show_docker" -eq 1 ]; then section_docker "$mode" fi section_activity "$mode" print_hr if [ "$mode" = "summary" ]; then printf '%b提示:%b 运行 %blogin-info --full%b 查看更完整信息\n' \ "${C_SUBTLE}" "${C_RESET}" "${C_INFO}" "${C_RESET}" fi } main "$@"