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

376
AGENTS.md Normal file
View File

@@ -0,0 +1,376 @@
# AGENTS.md - Agent Coding Guidelines
This document provides guidelines for agents working in this repository.
---
## Project Overview
This is a **Bash shell script** repository containing multiple independent utilities for Linux server management. Each subdirectory is a separate project.
### Subprojects
| Directory | Description |
|-----------|-------------|
| `port-info/` | Linux port information display (TCP, UDP, HTTP services) |
| `docker-info/` | Docker and server information collector |
| `database-info/` | Database information collector (WIP) |
| `systemctl-info/` | Systemd/systemctl detailed information (v2.0 modular) |
| `filebrowser/` | FileBrowser installation/uninstall scripts |
| `frp/` | FRP (Fast Reverse Proxy) management scripts |
| `openlist/` | OpenList installation/management scripts |
| `mengyamonitor/` | MengyanMonitor installation scripts |
| `user-manager/` | User management utility |
| `change-mirror/` | Mirror changing and Docker installation scripts |
| `login-info/` | Login information display |
| `ssh/` | SSH key management scripts |
---
## Build / Lint / Test Commands
### Running Scripts
```bash
# Make executable and run
chmod +x <script>.sh
./<script>.sh
# Or run with bash
bash <script>.sh
```
### Linting
Use **shellcheck** for static analysis of all Bash scripts:
```bash
# Install shellcheck (if not present)
apt-get install shellcheck # Debian/Ubuntu
yum install epel-release && yum install ShellCheck # RHEL/CentOS
# Run shellcheck on a specific script
shellcheck port-info/port_info.sh
shellcheck docker-info/docker-info.sh
# Run with specific severity levels
shellcheck -S error <script>.sh # Errors only
shellcheck -S warning <script>.sh # Warnings and errors
# Check all scripts in repository
shellcheck port-info/*.sh docker-info/*.sh systemctl-info/systemctl-info \
filebrowser/*.sh frp/*.sh openlist/*.sh mengyamonitor/*.sh user-manager/*.sh \
change-mirror/*.sh login-info/*.sh ssh/*.sh
```
### Syntax Validation
```bash
# Check bash syntax without executing
bash -n <script>.sh
# Validate all scripts
for f in **/*.sh; do bash -n "$f" && echo "OK: $f"; done
```
### Testing a Single Function
Bash doesn't have traditional unit tests. Test functions interactively:
```bash
# Source the script and test specific functions
source <script>.sh
function_name argument # Test specific function
# Example: test port-to-service lookup
source port-info/port_info.sh
get_service_name 80 # Should output: HTTP
```
### Running Full Scripts
```bash
# Port info
bash port-info/port_info.sh
# Docker info (requires Docker installed)
bash docker-info/docker-info.sh
# Systemctl info (requires root for full functionality)
sudo bash systemctl-info/systemctl-info
```
---
## Code Style Guidelines
### General Bash Style
#### Shebang and Error Handling
- Always use `#!/usr/bin/env bash` or `#!/bin/bash`
- Add `set -euo pipefail` at the start of scripts for safety:
- `-e`: Exit on error
- `-u`: Exit on undefined variables
- `-o pipefail`: Pipeline fails if any command fails
#### Formatting
- **Indentation**: 4 spaces (no tabs)
- **Line length**: Keep lines under 100 characters
- **Quotes**: Always quote variables: `"$variable"` not `$variable`
- Use `printf` over `echo` for complex output
### Naming Conventions
```bash
# Functions - snake_case
print_header() { }
get_server_info() { }
check_root() { }
# Variables - descriptive names
local container_output
local cpu_count
local total_mem
# Constants - UPPER_CASE
readonly RED='\033[0;31m'
readonly HEADER_COLOR="${BRIGHT_CYAN}"
```
### Variable Naming Patterns
- **Constants** (colors, separators): UPPERCASE with underscores, e.g., `RED`, `SEPARATOR`
- **Global variables**: UPPERCASE, e.g., `PORT_CMD`
- **Local variables in functions**: lowercase with underscores, e.g., `local port=$1`
- **Function names**: lowercase with underscores, e.g., `check_commands()`
### Function Structure
```bash
# Good function definition
function_name() {
local arg1=$1
local arg2=$2
# function body
return 0 # Always return explicit status
}
# Document functions with comments
# 获取服务器信息
get_server_info() {
print_header "服务器信息"
local cpu_count=$(grep -c '^processor' /proc/cpuinfo)
print_key_value "CPU核心数" "$cpu_count"
}
```
### Color Variables
Define colors at the top of the script using ANSI escape codes:
```bash
# Basic colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color - always reset at end
# Bright colors
BRIGHT_CYAN='\033[1;36m'
BRIGHT_MAGENTA='\033[1;35m'
```
### Error Handling
```bash
# Check command existence before use
command_exists() {
command -v "$1" >/dev/null 2>&1
}
if ! command_exists docker; then
print_error "Docker未安装"
exit 1
fi
# Use proper exit codes
if [ "$?" -ne 0 ]; then
echo -e "${RED}Error: command failed${NC}" >&2
exit 1
fi
```
### Input Handling
- Prefer arguments over interactive input
- Validate inputs before processing
- Use `local` for all function parameters
- Check for required arguments
### String Comparisons
```bash
# String equality
if [ "$var" = "value" ]; then
# ...
fi
# Numeric comparison
if [ "$num" -eq 0 ]; then
# ...
fi
# Regex matching
if [[ "$port" =~ ^[0-9]+$ ]]; then
# ...
fi
```
### Common Patterns
#### Command Availability Check
```bash
if command -v ss &> /dev/null; then
PORT_CMD="ss"
elif command -v netstat &> /dev/null; then
PORT_CMD="netstat"
else
echo -e "${RED}Error: ss or netstat not found${NC}"
exit 1
fi
```
#### Reading Lines Safely
```bash
while IFS= read -r line; do
# process line
done < <(command)
```
#### Safe Variable Usage
- Always quote variables: `"$var"` not `$var`
- Use `${var}` for clarity in complex expressions
- Initialize variables before use
### Shellcheck Compliance
Run `shellcheck` and fix all warnings. Common issues:
- **SC2086**: Quote variables to prevent word splitting
- **SC2166**: Use `&&` instead of `-a` in test expressions
- **SC2027**: Quote strings containing newlines
- **SC2248**: Prefer printf over echo
- **SC1090**: Can't follow non-constant source (use explicit paths)
---
## Output Patterns
Use helper functions for consistent output:
```bash
print_header() # Boxed titles
print_section() # Section headers with arrows
print_key_value() # Key: Value pairs
print_success() # Green checkmark
print_warning() # Yellow warning
print_error() # Red X
```
---
## Testing Checklist
Before submitting changes:
- [ ] Run `shellcheck <script>.sh` with no warnings
- [ ] Run `bash -n <script>.sh` for syntax check
- [ ] Test on a system with required dependencies
- [ ] Test on a system without optional dependencies (graceful degradation)
- [ ] Verify all color output displays correctly
- [ ] Check that tables align properly
---
## Documentation
- Use Chinese comments to match existing codebase style (most scripts are Chinese-language)
- Add section separators: `#===============================================================================`
- Document function purpose before definition
- Include usage information in comments
- Document dependencies clearly
---
## File Extensions
- Bash scripts: `.sh`
- Executable scripts may have no extension
- Service files: `.service`
---
## Subproject Guidelines
For specific subproject guidelines, see the AGENTS.md files in each subdirectory:
- [port-info/AGENTS.md](port-info/AGENTS.md)
- [docker-info/AGENTS.md](docker-info/AGENTS.md)
- [systemctl-info/AGENTS.md](systemctl-info/AGENTS.md)
---
## Dependencies
### Required (Common)
- `bash` (4.0+)
- Standard POSIX utilities: `awk`, `grep`, `sed`, `sort`, `uniq`
### Optional
- `docker` - for docker-info script
- `systemctl`, `journalctl` - for systemctl-info script
- `shellcheck` - for linting
- `jq` - for better JSON parsing
---
## Security Notes
- Some scripts require root/sudo for full functionality
- Always validate user input if adding new features
- Use proper quoting to prevent word splitting and globbing
- Sanitize any data passed to `eval` or similar dangerous operations
- Never hardcode credentials or sensitive data
---
## Common Tasks
### Adding a New Port Service (port-info)
Edit `get_service_name()` function:
```bash
get_service_name() {
local port=$1
case $port in
80) echo "HTTP" ;;
443) echo "HTTPS" ;;
3306) echo "MySQL" ;;
9000) echo "PHP-FPM" ;;
*) echo "未知" ;;
esac
}
```
### Adding a New Module (systemctl-info)
1. Create a new function following existing patterns: `module_xxx()`
2. Add function to the module list array
3. Use consistent color scheme and formatting
4. Add documentation in the AGENTS.md
### Adding a New Utility
1. Create new directory if it's a significant project
2. Follow the code style guidelines above
3. Create an AGENTS.md for the subproject
4. Add to the project table in this file

293
README.md Normal file
View File

@@ -0,0 +1,293 @@
# Linux Server Toolkit (Bash)
> 一组面向 Linux 服务器日常运维的 Bash 工具脚本。
> 仓库内每个子目录都是独立工具,可单独使用、单独维护。
![Bash](https://img.shields.io/badge/Shell-Bash-121011?logo=gnubash)
![Platform](https://img.shields.io/badge/Platform-Linux-2D2D2D?logo=linux)
![Status](https://img.shields.io/badge/Status-Active-success)
## 目录
- [项目定位](#项目定位)
- [工具清单](#工具清单)
- [仓库结构](#仓库结构)
- [快速开始](#快速开始)
- [核心工具使用示例](#核心工具使用示例)
- [完整工具说明](#完整工具说明)
- [依赖与兼容性](#依赖与兼容性)
- [开发与质量检查](#开发与质量检查)
- [常见问题](#常见问题)
- [安全说明](#安全说明)
- [贡献指南](#贡献指南)
- [许可证](#许可证)
## 项目定位
这个仓库用于集中管理常见服务器管理脚本,目标是:
- 开箱即用:脚本直接运行,不依赖复杂框架。
- 功能解耦:每个目录一个工具,互不耦合。
- 运维友好:输出清晰、彩色提示、错误处理明确。
- 可维护:脚本整体遵循统一 Bash 规范(见 `AGENTS.md`)。
适用场景:
- 新服务器初始化与巡检
- Docker / Systemd 状态排查
- 端口占用与服务健康检查
- 辅助部署 FRP、OpenList、FileBrowser 等服务
## 工具清单
| 目录 | 主要脚本 | 用途 |
|---|---|---|
| `port-info/` | `port_info.sh` | 查看 TCP/UDP 端口与服务映射 |
| `docker-info/` | `docker-info.sh` | 收集 Docker 和主机信息 |
| `database-info/` | `-` | 数据库信息采集/诊断(预留,暂无脚本) |
| `systemctl-info/` | `systemctl-info` | Systemd 全量状态与模块化诊断 |
| `filebrowser/` | `install_filebrowser.sh` | FileBrowser 安装/卸载 |
| `frp/` | `install_frp.sh` | FRP 安装、启停、卸载 |
| `openlist/` | `install_openlist.sh` | OpenList 安装、启停、卸载 |
| `mengyamonitor/` | `install_mengyamonitor.sh` | 监控探针安装/卸载 |
| `user-manager/` | `user-manager.sh` | Linux 用户管理 |
| `change-mirror/` | `ChangeMirrors.sh` | 系统换源与 Docker 安装辅助 |
| `login-info/` | `login-info.sh` | 登录信息展示 |
| `ssh/` | `*.sh` | SSH 密钥相关脚本集合 |
## 仓库结构
```text
.
├── AGENTS.md
├── README.md
├── port-info/
├── docker-info/
├── database-info/
├── systemctl-info/
├── filebrowser/
├── frp/
├── openlist/
├── mengyamonitor/
├── user-manager/
├── change-mirror/
├── login-info/
└── ssh/
```
## 快速开始
### 1) 克隆仓库
```bash
git clone <your-repo-url>
cd bash
```
### 2) 运行任意脚本
```bash
# 方式 A直接用 bash
bash path/to/script.sh
# 方式 B赋予可执行权限后运行
chmod +x path/to/script.sh
./path/to/script.sh
# 方式 C需要管理员权限时
sudo bash path/to/script.sh
```
### 3) 推荐先做语法检查
```bash
bash -n path/to/script.sh
```
## 核心工具使用示例
### 端口信息
```bash
bash port-info/port_info.sh
```
输出重点TCP/UDP 监听端口、常见服务名映射HTTP/HTTPS/MySQL/SSH 等)。
### Docker 信息
```bash
bash docker-info/docker-info.sh
```
输出重点:容器、镜像、网络、卷,以及主机基础状态。
### Systemctl 诊断
```bash
sudo bash systemctl-info/systemctl-info
```
输出重点:
- Systemd 版本与状态
- 失败服务与被屏蔽服务
- Timer/Socket/Target/Mount/Path/Device 单元
- 依赖关系、Journal 摘要、Cgroup、性能与电源信息
## 完整工具说明
### `filebrowser/`
```bash
bash filebrowser/install_filebrowser.sh
bash filebrowser/uninstall_filebrowser.sh
```
### `frp/`
```bash
bash frp/install_frp.sh
bash frp/start_frp.sh
bash frp/stopkill_frp.sh
bash frp/uninstall_frp.sh
```
### `openlist/`
```bash
bash openlist/install_openlist.sh
bash openlist/start_openlist.sh
bash openlist/stopkill_openlist.sh
bash openlist/uninstall_openlist.sh
```
### `mengyamonitor/`
```bash
bash mengyamonitor/install_mengyamonitor.sh
bash mengyamonitor/uninstall_mengyamonitor.sh
```
### `user-manager/`
```bash
bash user-manager/user-manager.sh
```
### `change-mirror/`
```bash
bash change-mirror/ChangeMirrors.sh
bash change-mirror/DockerInstallation.sh
bash change-mirror/DockerInstallationLite.sh
```
### `login-info/`
```bash
# 手动查看
bash login-info/login-info.sh
bash login-info/login-info.sh --full
# 安装为 SSH 登录自动展示(写入 /etc/profile.d/
sudo bash login-info/install_login-info.sh # 默认 summary推荐
sudo bash login-info/install_login-info.sh --mode full
sudo bash login-info/uninstall_login-info.sh
```
禁用自动展示(可选):
- 仅当前用户:`touch ~/.login-info-disable``export LOGIN_INFO_DISABLE=1`
- 全局:`sudo mkdir -p /etc/login-info && sudo touch /etc/login-info/disable`
### `ssh/`
```bash
bash ssh/alycd.sh
bash ssh/alyxg.sh
bash ssh/bigmengya.sh
bash ssh/mengyathree.sh
bash ssh/redmi.sh
bash ssh/smallmengya.sh
```
## 依赖与兼容性
### 基础依赖
- LinuxUbuntu / Debian / CentOS 等)
- Bash 4.0+
- 常见系统工具:`awk` `grep` `sed` `sort` `uniq`
### 按工具可选依赖
- 端口工具:`ss``netstat`
- Docker 工具:`docker`
- Systemd 工具:`systemctl` `journalctl`
- 推荐安装:`shellcheck`(静态检查)
## 开发与质量检查
仓库是 Bash 脚本项目,没有编译步骤,建议最少执行以下检查:
```bash
# 语法检查
bash -n port-info/port_info.sh
# 静态检查
shellcheck port-info/port_info.sh
shellcheck docker-info/docker-info.sh
shellcheck systemctl-info/systemctl-info
```
批量检查示例:
```bash
for f in **/*.sh; do
bash -n "$f" || exit 1
done
```
更多开发约束请参考根目录 `AGENTS.md`
## 常见问题
### 1) 提示 `ss` 或 `netstat` 不存在
```bash
sudo apt-get update
sudo apt-get install -y net-tools iproute2
```
### 2) Docker 信息脚本运行失败
- 确认 Docker 已安装并运行:`systemctl status docker`
- 确认当前用户有权限访问 Docker socket或直接使用 `sudo`
### 3) `systemctl-info` 信息不完整
- 请使用 root 权限运行:`sudo bash systemctl-info/systemctl-info`
## 安全说明
- 本仓库部分脚本会执行安装、服务管理、系统配置修改操作。
- 请先阅读对应脚本内容,再在生产环境执行。
- 不要在不可信环境中直接运行来源不明脚本。
## 贡献指南
欢迎提交 Issue / PR。
1. Fork 本仓库
2. 创建分支:`git checkout -b feature/your-feature`
3. 提交修改:`git commit -m "feat: add your feature"`
4. 推送分支:`git push origin feature/your-feature`
5. 发起 Pull Request
建议在提交前运行 `shellcheck``bash -n`
## 许可证
当前仓库未包含明确的 License 文件。
如需开源发布,建议补充 `LICENSE`(例如 MIT / Apache-2.0)。

View File

@@ -0,0 +1,161 @@
#!/usr/bin/env bash
set -euo pipefail
#===============================================================================
# login-info 安装脚本
# 作用:
# 1) 安装命令: /usr/local/bin/login-info
# 2) 写入登录启动脚本: /etc/profile.d/smy-login-info.sh
#
# 用法:
# sudo bash login-info/install_login-info.sh
# sudo bash login-info/install_login-info.sh --mode full
# sudo bash login-info/install_login-info.sh --all
#
# 禁用(用户级):
# touch ~/.login-info-disable
# 或: export LOGIN_INFO_DISABLE=1
#
# 禁用(全局):
# sudo mkdir -p /etc/login-info
# sudo touch /etc/login-info/disable
#===============================================================================
log() { printf '[login-info-安装] %s\n' "$*" >&2; }
fail() { log "错误: $*"; exit 1; }
require_root() {
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
fail "请使用 root 权限运行 (sudo bash login-info/install_login-info.sh)"
fi
}
main() {
local default_mode="summary"
local ssh_only="1"
while [ $# -gt 0 ]; do
case "$1" in
--mode)
shift
[ $# -gt 0 ] || fail "--mode 需要参数: summary|full"
default_mode=$1
;;
--mode=*)
default_mode=${1#*=}
;;
--ssh-only)
ssh_only="1"
;;
--all)
ssh_only="0"
;;
-h|--help)
cat <<'EOF'
用法:
sudo bash login-info/install_login-info.sh [--mode summary|full] [--ssh-only|--all]
EOF
return 0
;;
*)
fail "未知参数: $1"
;;
esac
shift
done
case "$default_mode" in
summary|full) ;;
*) fail "--mode 仅支持: summary|full" ;;
esac
require_root
local script_dir script_src bin_path profile_hook
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
script_src="${script_dir}/login-info.sh"
bin_path="/usr/local/bin/login-info"
profile_hook="/etc/profile.d/smy-login-info.sh"
[ -f "$script_src" ] || fail "未找到脚本: $script_src"
mkdir -p "$(dirname "$bin_path")"
mkdir -p "$(dirname "$profile_hook")"
log "安装命令到: $bin_path"
if command -v install >/dev/null 2>&1; then
install -m 0755 "$script_src" "$bin_path"
else
cp "$script_src" "$bin_path"
chmod 0755 "$bin_path"
fi
log "写入登录启动脚本: $profile_hook"
cat >"$profile_hook" <<EOF
# /etc/profile.d/smy-login-info.sh
# 由 login-info 安装脚本生成SSH 登录后自动展示服务器信息
#
# 禁用:
# - export LOGIN_INFO_DISABLE=1
# - touch ~/.login-info-disable
# - touch /etc/login-info/disable
#
# 覆盖默认行为:
# - export LOGIN_INFO_MODE=summary|full
# - export LOGIN_INFO_SSH_ONLY=1|0
# 仅在交互式且有 TTY 时运行(避免 scp/sftp 等非交互场景)
case "\$-" in
*i*) ;;
*) return 0 2>/dev/null || exit 0 ;;
esac
if ! [ -t 1 ]; then
return 0 2>/dev/null || exit 0
fi
# 避免重复输出
if [ -n "\${LOGIN_INFO_SHOWN:-}" ]; then
return 0 2>/dev/null || exit 0
fi
# 禁用开关
if [ -n "\${LOGIN_INFO_DISABLE:-}" ]; then
return 0 2>/dev/null || exit 0
fi
if [ -f "/etc/login-info/disable" ]; then
return 0 2>/dev/null || exit 0
fi
if [ -n "\${HOME:-}" ] && [ -f "\${HOME}/.login-info-disable" ]; then
return 0 2>/dev/null || exit 0
fi
# SSH-only默认开启可通过环境变量覆盖
__smy_login_info_ssh_only="\${LOGIN_INFO_SSH_ONLY:-${ssh_only}}"
if [ "\$__smy_login_info_ssh_only" = "1" ]; then
if [ -z "\${SSH_CONNECTION:-}" ] && [ -z "\${SSH_TTY:-}" ]; then
return 0 2>/dev/null || exit 0
fi
fi
export LOGIN_INFO_SHOWN=1
__smy_login_info_mode="\${LOGIN_INFO_MODE:-${default_mode}}"
if command -v login-info >/dev/null 2>&1; then
if [ "\$__smy_login_info_mode" = "full" ]; then
login-info --full || true
else
login-info --summary || true
fi
fi
EOF
chmod 0644 "$profile_hook"
log "安装完成"
log "提示: 重新登录 SSH 即可看到信息;或手动运行: login-info --full"
}
main "$@"

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 "$@"

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
#===============================================================================
# login-info 卸载脚本
# 作用:
# 删除 /usr/local/bin/login-info
# 删除 /etc/profile.d/smy-login-info.sh
#===============================================================================
log() { printf '[login-info-卸载] %s\n' "$*" >&2; }
fail() { log "错误: $*"; exit 1; }
require_root() {
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
fail "请使用 root 权限运行 (sudo bash login-info/uninstall_login-info.sh)"
fi
}
main() {
require_root
local bin_path profile_hook
bin_path="/usr/local/bin/login-info"
profile_hook="/etc/profile.d/smy-login-info.sh"
if [ -f "$bin_path" ]; then
log "删除: $bin_path"
rm -f "$bin_path"
else
log "跳过: $bin_path 不存在"
fi
if [ -f "$profile_hook" ]; then
log "删除: $profile_hook"
rm -f "$profile_hook"
else
log "跳过: $profile_hook 不存在"
fi
log "卸载完成"
}
main "$@"

View File