Files
dock/实时 history 监控

623 lines
18 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 优化版实时命令监控脚本 - 带完整IP地理位置和智能日志管理
# 版本: 3.0
set -e
### 配置区域 ###
LOG_DIR="/root/command_monitor_logs"
MAX_LOG_SIZE="1M" # 单个日志文件最大大小
MAX_LOG_FILES=20 # 最大日志文件数量
LOG_ROTATE_INTERVAL=1800 # 日志轮转间隔(秒) - 30分钟
MEMORY_LIMIT="512M"
CPU_LIMIT=90
CHECK_INTERVAL=300
BACKUP_DAYS=7
CLEANUP_INTERVAL=3600
### IP地理位置配置 ###
CACHE_IP_INFO=true
IP_CACHE_FILE="/tmp/ip_geo_cache.txt"
CACHE_EXPIRE=86400 # 缓存过期时间(秒) - 24小时
### 颜色定义 ###
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'
### 全局变量 ###
SCRIPT_PID=$$
MONITOR_PID=""
LAST_CLEANUP=0
LAST_ROTATION=0
CURRENT_LOG=""
DAEMON_MODE=false
# 获取时间戳
timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
# 日志函数
log_message() {
local level="$1"
local message="$2"
local color="$GREEN"
case "$level" in
"ERROR") color="$RED" ;;
"WARN") color="$YELLOW" ;;
"INFO") color="$BLUE" ;;
"SUCCESS") color="$GREEN" ;;
"COMMAND") color="$CYAN" ;;
esac
if [ "$DAEMON_MODE" = true ]; then
echo -e "${color}[$(timestamp)] [$level] $message${NC}" >> "$CURRENT_LOG"
else
echo -e "${color}[$(timestamp)] [$level] $message${NC}" | tee -a "$CURRENT_LOG"
fi
}
# 增强版IP地理位置查询
get_ip_location() {
local ip="$1"
# 检查缓存
if [ "$CACHE_IP_INFO" = true ] && [ -f "$IP_CACHE_FILE" ]; then
local cached_info=$(grep "^$ip|" "$IP_CACHE_FILE" | head -1)
if [ -n "$cached_info" ]; then
local cache_time=$(echo "$cached_info" | cut -d'|' -f2)
local current_time=$(date +%s)
if [ $((current_time - cache_time)) -lt $CACHE_EXPIRE ]; then
echo "$cached_info" | cut -d'|' -f3-
return 0
fi
fi
fi
local location_info=""
# 方法1: 使用ip-api.com (免费无需API key)
location_info=$(curl -s -m 5 "http://ip-api.com/json/$ip?fields=status,message,country,regionName,city,isp,query" 2>/dev/null)
if echo "$location_info" | grep -q '"status":"success"'; then
local country=$(echo "$location_info" | grep -o '"country":"[^"]*"' | cut -d'"' -f4)
local region=$(echo "$location_info" | grep -o '"regionName":"[^"]*"' | cut -d'"' -f4)
local city=$(echo "$location_info" | grep -o '"city":"[^"]*"' | cut -d'"' -f4)
local isp=$(echo "$location_info" | grep -o '"isp":"[^"]*"' | cut -d'"' -f4)
if [ -n "$country" ]; then
location_info="$country"
[ -n "$region" ] && location_info="$location_info-$region"
[ -n "$city" ] && location_info="$location_info-$city"
[ -n "$isp" ] && location_info="$location_info($isp)"
else
location_info="未知位置"
fi
else
location_info="未知位置"
fi
# 缓存结果
if [ "$CACHE_IP_INFO" = true ]; then
echo "$ip|$(date +%s)|$location_info" >> "$IP_CACHE_FILE"
fi
echo "$location_info"
}
# 获取城市名称(用于日志文件名)
get_city_name() {
local ip="$1"
local location=$(get_ip_location "$ip")
# 从位置信息中提取城市名
if [[ "$location" == *"-"* ]]; then
# 格式: 中国-江西-南昌(中国电信)
local city=$(echo "$location" | awk -F'-' '{print $3}' | sed 's/(.*//')
if [ -n "$city" ] && [ "$city" != "未知位置" ]; then
echo "$city" | sed 's/[^a-zA-Z0-9\u4e00-\u9fff]/_/g'
else
echo "unknown"
fi
else
echo "unknown"
fi
}
# 获取客户端IP
get_client_ip() {
local ip="unknown"
if [ -n "$SSH_CLIENT" ]; then
ip=$(echo "$SSH_CLIENT" | awk '{print $1}')
elif [ -n "$SSH_CONNECTION" ]; then
ip=$(echo "$SSH_CONNECTION" | awk '{print $1}')
else
ip=$(who -m 2>/dev/null | awk '{print $5}' | sed 's/[()]//g' | head -1)
if [[ "$ip" == ":0" ]] || [[ "$ip" == ":1" ]] || [[ -z "$ip" ]]; then
ip="localhost"
fi
fi
echo "$ip"
}
# 智能日志轮转检查
check_log_rotation() {
local current_time=$(date +%s)
# 检查时间间隔
if [ $((current_time - LAST_ROTATION)) -ge $LOG_ROTATE_INTERVAL ]; then
log_rotation "time"
return 0
fi
# 检查文件大小
if [ -f "$CURRENT_LOG" ]; then
local log_size=$(stat -c%s "$CURRENT_LOG" 2>/dev/null || echo 0)
local max_bytes=$(echo "$MAX_LOG_SIZE" | sed 's/M/*1024*1024/;s/K/*1024/' | bc 2>/dev/null || echo 1048576)
if [ "$log_size" -gt "$max_bytes" ]; then
log_rotation "size"
return 0
fi
fi
return 1
}
# 初始化日志系统
init_log_system() {
mkdir -p "$LOG_DIR"
local client_ip=$(get_client_ip)
local city_name=$(get_city_name "$client_ip")
local log_date=$(date '+%Y%m%d_%H%M%S')
# 构建日志文件名IP_城市_时间.log
if [ "$city_name" != "unknown" ]; then
CURRENT_LOG="$LOG_DIR/monitor_${client_ip//./_}_${city_name}_${log_date}.log"
else
CURRENT_LOG="$LOG_DIR/monitor_${client_ip//./_}_${log_date}.log"
fi
LATEST_LOG="$LOG_DIR/latest.log"
ln -sf "$CURRENT_LOG" "$LATEST_LOG"
LAST_ROTATION=$(date +%s)
log_message "INFO" "监控脚本启动 - PID: $$"
log_message "INFO" "客户端IP: $client_ip"
local location_info=$(get_ip_location "$client_ip")
log_message "INFO" "地理位置: $location_info"
log_message "INFO" "日志文件: $CURRENT_LOG"
log_message "INFO" "日志轮转: ${LOG_ROTATE_INTERVAL}秒或${MAX_LOG_SIZE}"
}
# 日志轮转
log_rotation() {
local reason="$1"
log_message "INFO" "执行日志轮转 - 原因: $reason"
# 保存当前日志的最终信息
if [ -f "$CURRENT_LOG" ]; then
local log_size=$(du -h "$CURRENT_LOG" | cut -f1)
log_message "INFO" "原日志文件大小: $log_size"
fi
# 重新初始化日志系统
init_log_system
log_message "INFO" "新日志文件已创建"
}
# 资源监控函数
monitor_resources() {
local check_count=0
while true; do
sleep 60
# 检查日志轮转条件
check_log_rotation
# 检查内存使用
local mem_usage=$(free 2>/dev/null | awk 'NR==2{printf "%.2f", $3*100/$2}' || echo "0")
if (( $(echo "$mem_usage > $CPU_LIMIT" | bc -l 2>/dev/null) )); then
log_message "WARN" "内存使用率过高: ${mem_usage}%"
fi
# 检查CPU使用率
local cpu_usage=$(top -bn1 2>/dev/null | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 || echo "0")
if (( $(echo "$cpu_usage > $CPU_LIMIT" | bc -l 2>/dev/null) )); then
log_message "WARN" "CPU使用率过高: ${cpu_usage}%"
fi
# 定期系统检查
((check_count++))
if [ $((check_count * 60)) -ge $CHECK_INTERVAL ]; then
perform_system_check
check_count=0
fi
# 定期清理旧日志
local current_time=$(date +%s)
if [ $((current_time - LAST_CLEANUP)) -ge $CLEANUP_INTERVAL ]; then
cleanup_old_logs
LAST_CLEANUP=$current_time
fi
done
}
# 系统健康检查
perform_system_check() {
log_message "INFO" "=== 系统健康检查 ==="
# 内存信息
local mem_info=$(free -h 2>/dev/null || echo "无法获取内存信息")
log_message "INFO" "内存使用: $mem_info"
# 磁盘信息
local disk_info=$(df -h "$LOG_DIR" 2>/dev/null | tail -1 || echo "无法获取磁盘信息")
log_message "INFO" "磁盘使用: $disk_info"
# 日志文件信息
local log_count=$(find "$LOG_DIR" -name "monitor_*.log" 2>/dev/null | wc -l)
local total_log_size=$(du -sh "$LOG_DIR" 2>/dev/null | cut -f1 || echo "0")
log_message "INFO" "日志文件数: $log_count, 总大小: $total_log_size"
log_message "INFO" "=== 检查完成 ==="
}
# 清理旧日志
cleanup_old_logs() {
log_message "INFO" "开始清理旧日志..."
# 按时间清理
local deleted_count=$(find "$LOG_DIR" -name "monitor_*.log" -mtime "+$BACKUP_DAYS" -delete -print | wc -l)
if [ "$deleted_count" -gt 0 ]; then
log_message "INFO" "已删除 $deleted_count 个过期日志文件"
fi
# 按数量清理
local log_count=$(find "$LOG_DIR" -name "monitor_*.log" 2>/dev/null | wc -l)
if [ "$log_count" -gt "$MAX_LOG_FILES" ]; then
local files_to_delete=$((log_count - MAX_LOG_FILES))
find "$LOG_DIR" -name "monitor_*.log" -type f -printf '%T@ %p\n' 2>/dev/null | \
sort -n 2>/dev/null | head -n "$files_to_delete" | cut -d' ' -f2- | while read file; do
rm -f "$file" 2>/dev/null && log_message "INFO" "删除旧日志: $(basename "$file")"
done
fi
log_message "SUCCESS" "日志清理完成"
}
# 信号处理函数
setup_signal_handlers() {
trap 'cleanup_on_exit' SIGINT SIGTERM
trap 'log_message "WARN" "收到挂起信号"; cleanup_on_exit' SIGHUP
trap 'log_rotation "manual"' SIGUSR1
}
# 退出清理
cleanup_on_exit() {
log_message "INFO" "正在停止监控服务..."
if [ -n "$MONITOR_PID" ]; then
kill "$MONITOR_PID" 2>/dev/null || true
fi
pkill -f "monitor_resources" 2>/dev/null || true
log_message "SUCCESS" "监控服务已停止"
exit 0
}
# 配置实时history
configure_realtime_history() {
log_message "INFO" "配置实时命令记录..."
for user_dir in /home/* /root; do
if [ -d "$user_dir" ]; then
local user=$(basename "$user_dir")
local bashrc="$user_dir/.bashrc"
if [ -f "$bashrc" ]; then
if ! grep -q "REAL_TIME_HISTORY" "$bashrc"; then
cat >> "$bashrc" << 'EOF'
# REAL_TIME_HISTORY - 实时命令记录配置
export PROMPT_COMMAND='history -a; history -c; history -r'
export HISTTIMEFORMAT='%F %T '
export HISTSIZE=10000
export HISTFILESIZE=20000
shopt -s histappend
export HISTCONTROL=ignoredups:erasedups
EOF
log_message "SUCCESS" "已为用户 $user 配置实时命令记录"
else
log_message "INFO" "用户 $user 已配置实时记录"
fi
fi
fi
done
}
# 过滤无用命令
should_ignore_command() {
local command="$1"
# 忽略空命令和很短的无意义命令
if [ -z "$command" ] || [ "${#command}" -lt 2 ]; then
return 0
fi
# 忽略history时间戳行
if [[ "$command" =~ ^#[0-9]+$ ]]; then
return 0
fi
# 忽略常见简单命令
local ignore_patterns=(
"ls" "cd" "pwd" "ll" "la" "history" "exit" "clear"
"echo" "date" "whoami" "id" "uname" "uptime"
)
for pattern in "${ignore_patterns[@]}"; do
if [ "$command" = "$pattern" ]; then
return 0
fi
done
return 1
}
# 主监控函数
start_main_monitor() {
log_message "INFO" "启动主监控进程..."
declare -A file_sizes
declare -A last_commands
# 初始化文件大小
for user_dir in /home/* /root; do
if [ -d "$user_dir" ]; then
local user=$(basename "$user_dir")
local history_file="$user_dir/.bash_history"
if [ -f "$history_file" ]; then
file_sizes["$user"]=$(stat -c%s "$history_file" 2>/dev/null || echo 0)
last_commands["$user"]=""
else
file_sizes["$user"]=0
last_commands["$user"]=""
fi
fi
done
# 主监控循环
while true; do
for user_dir in /home/* /root; do
if [ -d "$user_dir" ]; then
local user=$(basename "$user_dir")
local history_file="$user_dir/.bash_history"
if [ -f "$history_file" ]; then
local current_size=$(stat -c%s "$history_file" 2>/dev/null || echo 0)
local last_size=${file_sizes["$user"]}
if [ "$current_size" -gt "$last_size" ]; then
local new_content=$(tail -c +$((last_size + 1)) "$history_file" 2>/dev/null | tr -d '\0')
if [ -n "$new_content" ]; then
while IFS= read -r line; do
line=$(echo "$line" | sed 's/^[ \t]*//;s/[ \t]*$//')
if ! should_ignore_command "$line" && [ "$line" != "${last_commands["$user"]}" ]; then
local client_ip=$(get_client_ip)
local location_info=$(get_ip_location "$client_ip")
log_message "COMMAND" "用户: $user | 命令: $line | 来源: $client_ip [$location_info]"
last_commands["$user"]="$line"
fi
done <<< "$new_content"
fi
file_sizes["$user"]=$current_size
fi
fi
fi
done
sleep 1
done
}
# 后台运行监控
start_background_monitor() {
log_message "INFO" "启动后台监控服务..."
# 切换到后台运行
(
# 重新初始化日志系统(在子进程中)
init_log_system
log_message "INFO" "后台监控进程启动 - PID: $$"
# 启动资源监控
monitor_resources &
local resource_pid=$!
# 启动主监控
start_main_monitor &
MONITOR_PID=$!
log_message "SUCCESS" "后台监控服务已启动"
log_message "INFO" "主监控PID: $MONITOR_PID"
log_message "INFO" "资源监控PID: $resource_pid"
log_message "INFO" "查看实时日志: tail -f $LATEST_LOG"
log_message "INFO" "停止监控: kill $MONITOR_PID"
# 等待子进程
wait $MONITOR_PID
) &
local main_pid=$!
echo -e "${GREEN}后台监控已启动!${NC}"
echo -e "主进程PID: $main_pid"
echo -e "日志文件: $LATEST_LOG"
echo -e "查看日志: ${GREEN}tail -f $LATEST_LOG${NC}"
echo -e "停止监控: ${RED}kill $main_pid${NC}"
# 保存PID到文件以便管理
echo "$main_pid" > "/tmp/command_monitor.pid"
}
# 显示使用说明
show_usage() {
echo -e "${GREEN}实时命令监控系统 v3.0${NC}"
echo "用法: $0 [选项]"
echo
echo "选项:"
echo " -d, --daemon 后台运行模式"
echo " -c, --config 只配置不运行"
echo " -s, --status 查看监控状态"
echo " -k, --kill 停止监控进程"
echo " -r, --rotate 轮转日志文件"
echo " -h, --help 显示此帮助信息"
echo
echo "日志管理:"
echo " - 每30分钟自动轮转"
echo " - 文件超过1M自动轮转"
echo " - 日志按IP+城市+时间命名"
echo
echo "示例:"
echo " $0 -d # 后台运行监控"
echo " $0 --status # 查看监控状态"
echo " $0 --kill # 停止监控"
}
# 查看监控状态
check_monitor_status() {
local pid_file="/tmp/command_monitor.pid"
local main_pid=""
if [ -f "$pid_file" ]; then
main_pid=$(cat "$pid_file" 2>/dev/null)
fi
if [ -z "$main_pid" ] || ! ps -p "$main_pid" >/dev/null 2>&1; then
echo -e "${RED}监控服务未运行${NC}"
# 检查是否有残留进程
local residual_pids=$(pgrep -f "command_monitor" 2>/dev/null || true)
if [ -n "$residual_pids" ]; then
echo -e "${YELLOW}发现残留进程,建议清理: kill $residual_pids${NC}"
fi
return 1
fi
echo -e "${GREEN}监控服务运行中${NC}"
echo "主进程PID: $main_pid"
echo "日志目录: $LOG_DIR"
echo "最新日志: $LATEST_LOG"
if [ -f "$LATEST_LOG" ]; then
local log_size=$(du -h "$LATEST_LOG" 2>/dev/null | cut -f1 || echo "未知")
local log_count=$(find "$LOG_DIR" -name "monitor_*.log" 2>/dev/null | wc -l)
echo "当前日志大小: $log_size"
echo "总日志文件数: $log_count"
echo
echo -e "${YELLOW}最近5条记录:${NC}"
tail -5 "$LATEST_LOG" 2>/dev/null | while read line; do
echo " $line"
done
fi
}
# 停止监控进程
stop_monitor() {
local pid_file="/tmp/command_monitor.pid"
local main_pid=""
if [ -f "$pid_file" ]; then
main_pid=$(cat "$pid_file")
rm -f "$pid_file"
fi
if [ -z "$main_pid" ]; then
# 如果没有PID文件尝试查找进程
main_pid=$(pgrep -f "command_monitor" | head -1)
fi
if [ -z "$main_pid" ]; then
echo -e "${YELLOW}没有找到运行的监控进程${NC}"
return 0
fi
echo -e "${YELLOW}正在停止监控进程 (PID: $main_pid)...${NC}"
# 优雅停止
kill "$main_pid" 2>/dev/null || true
sleep 3
# 检查是否停止成功
if ps -p "$main_pid" >/dev/null 2>&1; then
echo -e "${RED}强制停止监控进程...${NC}"
kill -9 "$main_pid" 2>/dev/null || true
sleep 1
fi
# 清理所有相关进程
pkill -f "monitor_resources" 2>/dev/null || true
# 清理PID文件
rm -f "$pid_file" 2>/dev/null
echo -e "${GREEN}监控进程已停止${NC}"
}
### 主程序 ###
main() {
case "${1:-}" in
-d|--daemon)
DAEMON_MODE=true
init_log_system
setup_signal_handlers
configure_realtime_history
start_background_monitor
;;
-c|--config)
init_log_system
configure_realtime_history
;;
-s|--status)
check_monitor_status
;;
-k|--kill)
stop_monitor
;;
-r|--rotate)
init_log_system
log_rotation "manual"
;;
-h|--help)
show_usage
;;
*)
echo -e "${YELLOW}前台运行模式 (Ctrl+C 停止)${NC}"
init_log_system
setup_signal_handlers
configure_realtime_history
start_main_monitor
;;
esac
}
# 运行主程序
main "$@"