workbuddy.xpcool.com/.workbuddy/tmp/server-monitor.sh

100 lines
4.2 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
# ============================================================
# 服务器安全与资源监控脚本(轻量版)
# 功能:
# 1. SSH 爆破检测10分钟内失败认证超阈值
# 2. 新增 SSH 登录记录
# 3. fail2ban 新增封禁 IP同一 IP 仅提醒一次;解封后再被封会重新提醒)
# 4. 根分区磁盘、内存、系统负载告警
# 5. 未登记的公网监听端口变化告警(暴露面漂移)
# 告警方式:写日志 /var/log/server-monitor.log + 可选 Bark 推送
# Bark 配置:/etc/server-monitor.conf 中设置 BARK_URL 后生效
# 部署:/usr/local/bin/server-monitor.sh配合 cron 每 10 分钟执行
# 去重状态:/var/lib/server-monitor/banned-ips.state存放当前已封禁 IP 集合)
# ============================================================
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# 读取可选配置BARK_URL
CONF=/etc/server-monitor.conf
[ -f "$CONF" ] && . "$CONF"
LOG=/var/log/server-monitor.log
NOW=$(date '+%F %T')
ALERT=""
BARK_URL=${BARK_URL:-}
alert() { ALERT="${ALERT}$1
"; }
# ---------- 1) SSH 爆破检测近10分钟失败认证 ----------
FAILED=$(sudo journalctl _SYSTEMD_UNIT=sshd.service --since "10 minutes ago" 2>/dev/null | grep -ci "Failed password")
if [ "${FAILED:-0}" -ge 10 ]; then
alert "SSH爆破风险: 10分钟内 ${FAILED} 次密码失败"
fi
# ---------- 2) 新增成功登录 ----------
NEWLOGIN=$(sudo journalctl _SYSTEMD_UNIT=sshd.service --since "10 minutes ago" 2>/dev/null | grep -iE "Accepted (password|publickey)" | tail -3)
[ -n "$NEWLOGIN" ] && alert "新增SSH登录:
$NEWLOGIN"
# ---------- 3) fail2ban 新增封禁 IP同一 IP 只提醒一次) ----------
# 状态文件记录"当前已封禁的 IP 集合"
# - 当前封禁列表里出现状态文件中没有的 IP → 新封禁,告警一次
# - 已解封的 IP 会从状态文件移除,若日后再次被封,视为新事件可再提醒
STATE_DIR=/var/lib/server-monitor
STATE_FILE=$STATE_DIR/banned-ips.state
mkdir -p "$STATE_DIR" 2>/dev/null
touch "$STATE_FILE" 2>/dev/null
CUR_BANNED=$(sudo fail2ban-client status sshd 2>/dev/null | sed -n 's/.*Banned IP list:[[:space:]]*//p')
NEW_BANNED=""
for ip in $CUR_BANNED; do
grep -qxF "$ip" "$STATE_FILE" 2>/dev/null || NEW_BANNED="${NEW_BANNED} ${ip}"
done
# 状态文件同步为当前封禁集合(自动剔除已解封 IP
if [ -n "$CUR_BANNED" ]; then
printf '%s\n' $CUR_BANNED > "$STATE_FILE"
else
: > "$STATE_FILE"
fi
[ -n "$NEW_BANNED" ] && alert "fail2ban 新封禁IP:${NEW_BANNED}(已自动封禁,无需处理)"
# ---------- 4) 根分区磁盘 ----------
DF=$(df -P / 2>/dev/null | tail -1 | awk '{print $5}' | tr -d '%')
[ "${DF:-0}" -ge 85 ] && alert "根分区使用率已达 ${DF}%"
# ---------- 5) 内存可用比 ----------
MEMFREE=$(free -m 2>/dev/null | awk '/Mem:/{print int($7/$2*100)}')
[ "${MEMFREE:-100}" -le 10 ] && alert "内存可用仅剩 ${MEMFREE}%"
# ---------- 6) 系统负载 ----------
LOAD=$(awk '{print int($1)}' /proc/loadavg 2>/dev/null)
[ "${LOAD:-0}" -ge 4 ] && alert "系统负载过高: $LOAD"
# ---------- 7) 公网监听端口漂移检测 ----------
# 基线:当前业务期望对外监听的端口
# SSH(22025) / Nginx(80,443) / Gitea(3000,2222) / Bark(8081, docker映射,防火墙已挡公网)
BASE_PORTS="22025 80 443 3000 2222 8081"
NOW_PORTS=$(ss -tlnH 2>/dev/null | awk '{print $4}' | grep -vE '^127\.' | grep -oE ':[0-9]+$' | tr -d ':' | sort -u)
EXTRA=""
for p in $NOW_PORTS; do
case " $BASE_PORTS " in
*" $p "*) ;;
*) EXTRA="${EXTRA} ${p}" ;;
esac
done
[ -n "$EXTRA" ] && alert "发现未登记的公网监听端口:${EXTRA}(请核查是否为新增服务)"
# ---------- 输出:日志 + Bark 推送 ----------
if [ -n "$ALERT" ]; then
echo -e "$NOW [ALERT] $HOSTNAME
$ALERT" >> "$LOG"
if [ -n "$BARK_URL" ]; then
# Bark 自建服务端(路径式 keyPOST JSON 到 /<device_key>
BODY=$(printf '%s' "$ALERT" | tr '\n' ' ' | tr '"' "'")
curl -s -m 8 -X POST "$BARK_URL" -H "Content-Type: application/json" -d "{\"title\":\"服务器告警\",\"body\":\"$BODY\"}" >/dev/null 2>&1
fi
else
echo "$NOW [OK] 登录正常 负载${LOAD:-?} 根分区${DF:-?}% 内存可用${MEMFREE:-?}%" >> "$LOG"
fi