Sync bash project

This commit is contained in:
萌小芽
2026-04-04 13:24:58 +08:00
parent 4a25dfb860
commit a362c0ce52
6 changed files with 1328 additions and 228 deletions

View File

@@ -1,267 +1,492 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail
# 用法示例curl -fsSL "https://pan.shumengya.top/d/scripts/login_info.sh" | bash
#===============================================================================
# 登录信息展示脚本 (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
#===============================================================================
# --- 精心选择的颜色定义 ---
NONE='\033[00m'
BOLD='\033[1m'
usage() {
local prog
prog=${0##*/}
# 主题色 (可根据喜好调整)
# 可以选择一个主色调,例如蓝色系或绿色系
# 这里我们尝试用青色作为标题,黄色作为关键信息,白色作为次要信息
TITLE_COLOR='\033[01;36m' # 亮青色 (Bold Cyan)
INFO_COLOR='\033[01;33m' # 亮黄色 (Bold Yellow)
VALUE_COLOR='\033[00;37m' # 白色 (White)
LABEL_COLOR='\033[00;32m' # 绿色 (Green) - 用于标签
SUBTLE_COLOR='\033[01;30m' # 深灰色/亮黑色 (Bold Black / Dark Gray) - 用于分隔符或不重要信息
BLUE_COLOR='\033[01;34m' # 亮蓝色 (Bold Blue)
ORANGE_COLOR='\033[01;33m' # 亮黄色 (在16色终端中常作为橙色) - 使用 INFO_COLOR 相同的代码
cat <<EOF
用法:
${prog} [--summary|--full] [--no-docker] [--no-color] [--help]
说明:
--summary 快速模式(默认),适合登录时自动展示
--full 详细模式(包含 GPU/磁盘明细/软件包统计等)
--no-docker 不显示 Docker 信息
--no-color 禁用颜色输出
EOF
}
has_cmd() { command -v "$1" >/dev/null 2>&1; }
init_colors() {
local color_mode=${LOGIN_INFO_COLOR:-auto}
# --- 设备名称和欢迎 ---
HOSTNAME_TEXT=$(hostname) # 如果不再需要显示hostname可以注释掉或删除这行
case "$color_mode" in
auto|always|never) ;;
*) color_mode=auto ;;
esac
# --- 系统概览 ---
OS_INFO=$(lsb_release -ds 2>/dev/null || echo "N/A")
KERNEL_VERSION=$(uname -sr 2>/dev/null || echo "N/A")
BOOT_TIME=$(uptime -s 2>/dev/null || who -b | awk '{print $3, $4}')
UPTIME=$(uptime -p 2>/dev/null | sed 's/up //')
LOAD_AVG=$(uptime 2>/dev/null | awk -F'load average: ' '{print $2}')
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
echo -e "${TITLE_COLOR}${BOLD}系统概览${NONE}"
echo -e " ${LABEL_COLOR}系统名称:${NONE} ${VALUE_COLOR}$(uname -a)${NONE}"
echo -e " ${LABEL_COLOR}操作系统:${NONE} ${VALUE_COLOR}$OS_INFO${NONE}"
echo -e " ${LABEL_COLOR}内核版本:${NONE} ${VALUE_COLOR}$KERNEL_VERSION${NONE}"
echo -e " ${LABEL_COLOR}启动时间:${NONE} ${VALUE_COLOR}$BOOT_TIME${NONE}"
echo -e " ${LABEL_COLOR}运行时间:${NONE} ${VALUE_COLOR}$UPTIME${NONE}"
echo -e " ${LABEL_COLOR}平均负载:${NONE} ${VALUE_COLOR}$LOAD_AVG${NONE}"
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
# --- 硬件资源 ---
# 获取 CPU 型号,处理可能的多行输出,只取第一行,并去除首尾空格
CPU_MODEL=$(grep "model name" /proc/cpuinfo | head -n 1 | awk -F ':' '{print $2}' | sed 's/^[ \t]*//;s/[ \t]*$//' || echo "N/A")
CPU_CORES=$(grep -c "processor" /proc/cpuinfo 2>/dev/null || echo "N/A")
# CPU 温度 (尽可能兼容更多机型)
CPU_TEMP=""
# 定义获取CPU温度的函数
get_cpu_temp() {
local temp_c=""
# 1. 尝试使用 sensors 命令 (如果安装了 lm-sensors)
if command -v sensors &> /dev/null; then
# 尝试匹配常见的CPU温度标签
# Package id 0: Intel/AMD 多核封装温度
# Tctl/Tdie: AMD Ryzen
# Core 0: Intel Core
# cpu_thermal: 树莓派/通用
# Composite: 部分嵌入式设备
temp_c=$(sensors | grep -iE '^(package id 0|tctl|tdie|core 0|cpu_thermal|composite|temp1):' | head -n 1 | awk '{print $2}' | tr -d '+°C')
if [ -n "$temp_c" ]; then
echo "$temp_c"
return
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
# 2. 尝试读取 /sys/class/thermal/thermal_zone*
# 优先查找 x86_pkg_temp (Intel) 或 k10temp (AMD) 或 cpu-thermal (ARM) 或 acpitz
for zone in /sys/class/thermal/thermal_zone*; do
[ -e "$zone/type" ] || continue
type=$(cat "$zone/type")
if [[ "$type" == "x86_pkg_temp" || "$type" == "k10temp" || "$type" == "cpu-thermal" || "$type" == "acpitz" ]]; then
if [ -r "$zone/temp" ]; then
temp_raw=$(cat "$zone/temp")
# 转换为摄氏度 (除以1000)
if [ "$temp_raw" -gt 0 ] 2>/dev/null; then
awk "BEGIN {printf \"%.1f\", $temp_raw/1000}"
return
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
done
# 3. 如果还没找到,尝试任何 thermal_zone0 (通常是主要的)
if [ -r "/sys/class/thermal/thermal_zone0/temp" ]; then
temp_raw=$(cat "/sys/class/thermal/thermal_zone0/temp")
if [ "$temp_raw" -gt 0 ] 2>/dev/null; then
awk "BEGIN {printf \"%.1f\", $temp_raw/1000}"
return
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 "状态" "未检测到 IPip 命令不可用)"
fi
fi
}
CPU_TEMP_VAL=$(get_cpu_temp)
if [ -n "$CPU_TEMP_VAL" ]; then
CPU_TEMP="(${INFO_COLOR}${CPU_TEMP_VAL}°C${VALUE_COLOR})"
fi
section_docker() {
local mode=$1
# 智能内存显示:小于 1GB 显示 MB否则显示 GB (保留1位小数)
MEM_TOTAL_MB=$(free -m | awk 'NR==2{print $2}')
if [ "$MEM_TOTAL_MB" -ge 1000 ]; then
MEM_TOTAL_DISPLAY=$(awk -v val="$MEM_TOTAL_MB" 'BEGIN {printf "%.1fGB", val/1024}')
else
MEM_TOTAL_DISPLAY="${MEM_TOTAL_MB}MB"
fi
if ! has_cmd docker; then
return 0
fi
MEM_USED_PERCENT=$(free -m | awk 'NR==2{printf "%.1f%%", $3*100/$2 }')
MEM_AVAILABLE_MB=$(free -m | awk 'NR==2{print $7"MB"}') # 'available' 通常比 'free' 更能代表实际可用内存
if ! docker ps >/dev/null 2>&1; then
return 0
fi
# --- GPU 检测 ---
# 优先使用 lspci 检测 VGA, 3D, Display 控制器
GPU_INFO=""
if command -v lspci &> /dev/null; then
# 获取设备列表,去除前面的地址信息,只保留设备名称
# 典型 lspci 输出: "00:02.0 VGA compatible controller: Intel Corporation ..."
# 我们需要截取 "Intel Corporation ..."
# 使用 sed 处理:匹配开头到第二个冒号之前的所有内容并删除
GPU_INFO=$(lspci | grep -iE 'vga|3d|display' | sed 's/^.*: .*[:] //')
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)
echo -e "${TITLE_COLOR}${BOLD}硬件资源${NONE}"
echo -e " ${LABEL_COLOR}中央处理器 (CPU):${NONE}"
echo -e " ${VALUE_COLOR}$CPU_MODEL - ${CPU_CORES} 核心 ${CPU_TEMP}${NONE}"
if [ -n "$GPU_INFO" ]; then
echo -e " ${LABEL_COLOR}图形处理器 (GPU):${NONE}"
# 处理多显卡情况,逐行显示
echo "$GPU_INFO" | while read -r line; do
echo -e " ${VALUE_COLOR}$line${NONE}"
done
fi
echo -e " ${LABEL_COLOR}内存 (RAM):${NONE}"
echo -e " ${VALUE_COLOR}总计: ${INFO_COLOR}$MEM_TOTAL_DISPLAY${VALUE_COLOR} | 已用: ${INFO_COLOR}$MEM_USED_PERCENT${VALUE_COLOR} | 可用: ${INFO_COLOR}$MEM_AVAILABLE_MB${NONE}"
echo -e " ${LABEL_COLOR}存储空间:${NONE}"
print_title "Docker 容器"
print_kv "容器数量" "运行中: ${running:-0} / 总计: ${total:-0}"
# --- 修改的存储部分 (删除进度条, 修改颜色) ---
# 使用 df -hT 获取信息,-x 排除不必要的类型 (保留 overlay)
# 使用 awk 跳过头部 (NR>1),并按列提取 (Filesystem, Type, Size, Used, Use%, Mounted on)
# 注意:已添加 -x overlay 以隐藏 Docker 挂载卷
df -hT -x tmpfs -x devtmpfs -x devpts -x proc -x sysfs -x cgroup -x fusectl -x securityfs -x pstore -x efivarfs -x autofs -x overlay 2>/dev/null | awk 'NR>1 {print $1, $2, $3, $4, $6, $7}' | while read -r DEVICE TYPE SIZE USED PERCENT MOUNTPOINT; do
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
echo -e " ${INFO_COLOR}${MOUNTPOINT}${NONE} (${VALUE_COLOR}${DEVICE} - ${TYPE}${NONE})"
# --- 核心改动:将 总 和 已用 后面的数值改为蓝色 ---
# LABEL_COLOR 用于标签文字 "总:" 和 "已用:"
# BLUE_COLOR 用于数值 SIZE 和 USED
# VALUE_COLOR 用于百分比 PERCENT 以及括号和逗号
echo -e " ${LABEL_COLOR}总:${NONE} ${BLUE_COLOR}${SIZE}${NONE}, ${LABEL_COLOR}已用:${NONE} ${BLUE_COLOR}${USED}${NONE} (${VALUE_COLOR}${PERCENT}${NONE})"
done
user=${USER:-}
[ -n "$user" ] || user=$(id -un 2>/dev/null || echo N/A)
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
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"
# --- 网络连接 ---
echo -e "${TITLE_COLOR}${BOLD}网络连接${NONE}"
INTERFACES_UP=$(ip -o link show up | awk -F': ' '{print $2}' | cut -d '@' -f 1) # 使用 cut 移除 @ifname 后缀
HAS_IP=false
if [ -n "$INTERFACES_UP" ]; then
for IFACE in $INTERFACES_UP; do
if [[ "$IFACE" == "lo" ]]; then continue; fi # 跳过 lo 接口
# 跳过 Docker 相关接口
if [[ "$IFACE" == docker* || "$IFACE" == veth* || "$IFACE" == br-* ]]; then continue; fi
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"
IP_ADDRS=$(ip -4 addr show dev "$IFACE" 2>/dev/null | grep -oP 'inet \K[\d.]+' | tr '\n' ' ')
IP_ADDRS_V6=$(ip -6 addr show dev "$IFACE" 2>/dev/null | grep -oP 'inet6 \K[0-9a-fA-F:]+' | grep -ivE '^fe80::' | head -n1 | tr '\n' ' ') # 只显示一个全局 IPv6
MAC_ADDR=$(ip link show dev "$IFACE" 2>/dev/null | awk '/ether/ {print $2}')
if [ -n "$IP_ADDRS" ] || [ -n "$IP_ADDRS_V6" ]; then
HAS_IP=true
IFACE_TYPE_LABEL=""
if [[ "$IFACE" == wlan* || "$IFACE" == wlp* || "$IFACE" == ath* || "$IFACE" == ra* ]]; then
IFACE_TYPE_LABEL="(${LABEL_COLOR}WiFi${NONE}) "
elif [[ "$IFACE" == eth* || "$IFACE" == enp* || "$IFACE" == eno* ]]; then
IFACE_TYPE_LABEL="(${LABEL_COLOR}有线${NONE}) "
# 添加对虚拟接口类型的更全面判断
elif [[ "$IFACE" == docker* || "$IFACE" == br-* || "$IFACE" == veth* || "$IFACE" == tun* || "$IFACE" == tap* || "$IFACE" == virbr* ]]; then
IFACE_TYPE_LABEL="(${LABEL_COLOR}虚拟${NONE}) "
# 考虑桥接接口
elif ip link show "$IFACE" | grep -q "bridge"; then
IFACE_TYPE_LABEL="(${LABEL_COLOR}桥接${NONE}) "
fi
echo -e " ${INFO_COLOR}${BOLD}${IFACE}${NONE} ${IFACE_TYPE_LABEL}"
[ -n "$IP_ADDRS" ] && echo -e " ${LABEL_COLOR}IPv4:${NONE} ${VALUE_COLOR}${IP_ADDRS% }${NONE}" # % 移除末尾空格
[ -n "$IP_ADDRS_V6" ] && echo -e " ${LABEL_COLOR}IPv6:${NONE} ${VALUE_COLOR}${IP_ADDRS_V6% }${NONE}"
[ -n "$MAC_ADDR" ] && echo -e " ${LABEL_COLOR}MAC:${NONE} ${VALUE_COLOR}${MAC_ADDR}${NONE}"
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
fi
if ! $HAS_IP; then
echo -e " ${VALUE_COLOR}未检测到活动的网络连接或IP地址${NONE}"
fi
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
init_colors
section_system
section_resources
# --- Docker 容器 ---
# 检查 docker 命令是否存在且守护进程正在运行
if command -v docker &> /dev/null && docker ps &> /dev/null; then
DOCKER_RUNNING_COUNT=$(docker ps -q | wc -l)
DOCKER_TOTAL_COUNT=$(docker ps -aq | wc -l)
echo -e "${TITLE_COLOR}${BOLD}Docker 容器${NONE}"
echo -e " ${LABEL_COLOR}容器数量:${NONE} ${VALUE_COLOR}运行中: ${INFO_COLOR}${DOCKER_RUNNING_COUNT}${VALUE_COLOR} / 总计: ${INFO_COLOR}${DOCKER_TOTAL_COUNT}${NONE}"
if [ "$DOCKER_RUNNING_COUNT" -gt 0 ]; then
# 获取名称列表,用逗号分隔
DOCKER_NAMES=$(docker ps --format "{{.Names}}" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')
echo -e " ${LABEL_COLOR}运行列表:${NONE} ${VALUE_COLOR}${DOCKER_NAMES}${NONE}"
fi
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
fi
# --- 活动与统计 ---
LAST_LOGIN_INFO=$(last -n 1 -wFai 2>/dev/null | head -n 1) # -a 把hostname放最后, -i 显示IP
LAST_LOGIN_DISPLAY=""
if [[ "$LAST_LOGIN_INFO" == *"wtmp begins"* || -z "$LAST_LOGIN_INFO" || $(echo "$LAST_LOGIN_INFO" | wc -w) -lt 5 ]]; then # 增加字段数检查
LAST_LOGIN_DISPLAY="${VALUE_COLOR}无先前登录记录或记录格式异常${NONE}"
else
# --- 修改的上次登录部分 ---
USER=$(echo "$LAST_LOGIN_INFO" | awk '{print $1}')
# 对于 -wFai, IP/hostname 通常是第三列
FROM_IP_OR_HOST=$(echo "$LAST_LOGIN_INFO" | awk '{print $3}')
# 精确提取日期和时间部分,跳过 'still' 和年份 (字段 4到7)
LOGIN_TIME_FIELDS=$(echo "$LAST_LOGIN_INFO" | awk '{print $4, $5, $6, $7}')
# 尝试用date格式化如果失败则显示原始字段
LOGIN_TIME_FORMATTED=$(date -d "$LOGIN_TIME_FIELDS $(date +%Y)" +"%Y-%m-%d %H:%M:%S" 2>/dev/null) # 显式添加年份尝试格式化
if [ -z "$LOGIN_TIME_FORMATTED" ]; then
LOGIN_TIME_FORMATTED="${VALUE_COLOR}${LOGIN_TIME_FIELDS}${NONE} (raw)" # 格式化失败,显示原始字段并标记
if [ "$mode" = "full" ]; then
section_gpu
section_storage_full
fi
LAST_LOGIN_DISPLAY="${LABEL_COLOR}用户:${NONE} ${INFO_COLOR}$USER${NONE}, ${LABEL_COLOR}来自:${NONE} ${INFO_COLOR}$FROM_IP_OR_HOST${NONE}, ${LABEL_COLOR}时间:${NONE} ${VALUE_COLOR}$LOGIN_TIME_FORMATTED${NONE}"
fi
section_network
if [ "$show_docker" -eq 1 ]; then
section_docker "$mode"
fi
PACKAGE_COUNT=$(dpkg-query -f '${Package}\n' -W 2>/dev/null | wc -l || echo "N/A") # Debian/APT
section_activity "$mode"
print_hr
echo -e "${TITLE_COLOR}${BOLD}活动与统计${NONE}"
echo -e " ${LABEL_COLOR}上次登录:${NONE} $LAST_LOGIN_DISPLAY"
if [ "$mode" = "summary" ]; then
printf '%b提示:%b 运行 %blogin-info --full%b 查看更完整信息\n' \
"${C_SUBTLE}" "${C_RESET}" "${C_INFO}" "${C_RESET}"
fi
}
# 检查是否是 Debian/Ubuntu 系列系统,然后显示包数
if command -v dpkg-query &> /dev/null; then
PACKAGE_COUNT=$(dpkg-query -f '${Package}\n' -W 2>/dev/null | wc -l || echo "N/A")
echo -e " ${LABEL_COLOR}软件包数:${NONE} ${VALUE_COLOR}$PACKAGE_COUNT (Debian/APT)${NONE}"
elif command -v rpm &> /dev/null; then
PACKAGE_COUNT=$(rpm -qa 2>/dev/null | wc -l || echo "N/A")
echo -e " ${LABEL_COLOR}软件包数:${NONE} ${VALUE_COLOR}$PACKAGE_COUNT (RPM/Yum/Dnf)${NONE}"
elif command -v pacman &> /dev/null; then
PACKAGE_COUNT=$(pacman -Qq 2>/dev/null | wc -l || echo "N/A")
echo -e " ${LABEL_COLOR}软件包数:${NONE} ${VALUE_COLOR}$PACKAGE_COUNT (Pacman)${NONE}"
else
echo -e " ${LABEL_COLOR}软件包数:${NONE} ${VALUE_COLOR}N/A (未知包管理器)${NONE}"
fi
CURRENT_DATETIME=$(date +"%Y年%m月%d日 %A %H:%M:%S %Z")
echo -e " ${LABEL_COLOR}当前登录时间:${NONE} ${VALUE_COLOR}$CURRENT_DATETIME$"
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
main "$@"