Sync bash project
This commit is contained in:
376
AGENTS.md
Normal file
376
AGENTS.md
Normal 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
293
README.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# Linux Server Toolkit (Bash)
|
||||
|
||||
> 一组面向 Linux 服务器日常运维的 Bash 工具脚本。
|
||||
> 仓库内每个子目录都是独立工具,可单独使用、单独维护。
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## 目录
|
||||
|
||||
- [项目定位](#项目定位)
|
||||
- [工具清单](#工具清单)
|
||||
- [仓库结构](#仓库结构)
|
||||
- [快速开始](#快速开始)
|
||||
- [核心工具使用示例](#核心工具使用示例)
|
||||
- [完整工具说明](#完整工具说明)
|
||||
- [依赖与兼容性](#依赖与兼容性)
|
||||
- [开发与质量检查](#开发与质量检查)
|
||||
- [常见问题](#常见问题)
|
||||
- [安全说明](#安全说明)
|
||||
- [贡献指南](#贡献指南)
|
||||
- [许可证](#许可证)
|
||||
|
||||
## 项目定位
|
||||
|
||||
这个仓库用于集中管理常见服务器管理脚本,目标是:
|
||||
|
||||
- 开箱即用:脚本直接运行,不依赖复杂框架。
|
||||
- 功能解耦:每个目录一个工具,互不耦合。
|
||||
- 运维友好:输出清晰、彩色提示、错误处理明确。
|
||||
- 可维护:脚本整体遵循统一 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
|
||||
```
|
||||
|
||||
## 依赖与兼容性
|
||||
|
||||
### 基础依赖
|
||||
|
||||
- Linux(Ubuntu / 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)。
|
||||
161
login-info/install_login-info.sh
Normal file
161
login-info/install_login-info.sh
Normal 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 "$@"
|
||||
@@ -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 "状态" "未检测到 IP(ip 命令不可用)"
|
||||
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 "$@"
|
||||
|
||||
45
login-info/uninstall_login-info.sh
Normal file
45
login-info/uninstall_login-info.sh
Normal 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 "$@"
|
||||
|
||||
Reference in New Issue
Block a user