脚本安装

将脚本保存到服务器上,例如保存为 /root/traffic_monitor.sh
脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/bin/bash

INTERFACE="eth0" # 设置为你的网络接口名称
MONTHLY_LIMIT_GB=160 # 每月流量限制 (GB)
WARNING_THRESHOLD=0.9 # 警告阈值(90%)
LOG_FILE="/var/log/vnstat_monitor.log"
STATE_FILE="/var/run/vnstat_monitor_state"
LIMIT_TYPE="SUM" # 可选值: "MAX" (入站和出站的最大值), "SUM" (入站和出站的总和)
ACTION_ON_LIMIT="SHUTDOWN" # 可选值: "DROP" (封锁流量), "SHUTDOWN" (关机)

# 自定义通知信息
NOTIFICATION_URL="https://xxx.xx.com/xxx" # 默认通知地址
NOTIFICATION_TITLE="阿里云HK流量告警" # 通知标题
WARNING_MESSAGE="流量使用已达到{PERCENT}%({USAGE}GB/{LIMIT}GB)" # 警告消息
LIMIT_REACHED_MESSAGE="流量使用超过{LIMIT}GB,{ACTION}!" # 达到限制时的消息
RESET_MESSAGE="流量已被重置,网络已解封。" # 重置消息

echo "Starting vnstat monitor script" > $LOG_FILE

# 函数:获取每月已使用流量 (GB)
get_monthly_usage() {
local rx=$(vnstat -i "$INTERFACE" --json m | jq '.interfaces[0].traffic.month[0].rx')
local tx=$(vnstat -i "$INTERFACE" --json m | jq '.interfaces[0].traffic.month[0].tx')
local rx_gb=$(echo "scale=3; $rx / 1024 / 1024 / 1024" | bc)
local tx_gb=$(echo "scale=3; $tx / 1024 / 1024 / 1024" | bc)

if [ "$LIMIT_TYPE" = "MAX" ]; then
echo "$rx_gb $tx_gb" | awk '{if ($1 > $2) print $1; else print $2}'
else
echo "scale=3; $rx_gb + $tx_gb" | bc
fi
}

# 函数:封锁网络
block_network() {
echo "$(date +%Y-%m-%d_%H:%M:%S) 封锁网络。" >> $LOG_FILE
iptables -I INPUT -i "$INTERFACE" -j DROP
iptables -I OUTPUT -o "$INTERFACE" -j DROP
echo "BLOCKED=true" > $STATE_FILE
}

# 函数:解封网络
unblock_network() {
echo "$(date +%Y-%m-%d_%H:%M:%S) 解封网络。" >> $LOG_FILE
iptables -D INPUT -i "$INTERFACE" -j DROP
iptables -D OUTPUT -o "$INTERFACE" -j DROP
echo "BLOCKED=false" > $STATE_FILE
}

# 函数:关机
shutdown_system() {
echo "$(date +%Y-%m-%d_%H:%M:%S) 流量超限,系统即将关机。" >> $LOG_FILE
sleep 10
shutdown -h now
}

# 函数:发送通知
send_notification() {
local title="$1"
local message="$2"
curl -X POST -d "" "$NOTIFICATION_URL/$title/$message" > /dev/null 2>&1
echo "$(date +%Y-%m-%d_%H:%M:%S) 已发送通知: $title - $message" >> $LOG_FILE
}

# 函数:替换消息中的占位符
replace_placeholders() {
local message="$1"
local usage="$2"
local limit="$3"
local percent="$4"
local action="$5"

message="${message//\{USAGE\}/$usage}"
message="${message//\{LIMIT\}/$limit}"
message="${message//\{PERCENT\}/$percent}"
message="${message//\{ACTION\}/$action}"

echo "$message"
}

# 初始化状态
if [ -f "$STATE_FILE" ]; then
source $STATE_FILE
else
BLOCKED=false
WARNED=false
echo "BLOCKED=false" > $STATE_FILE
echo "WARNED=false" >> $STATE_FILE
fi

while true; do
sleep 60 # 每分钟检查一次

# 获取每月已使用流量
MONTHLY_USAGE=$(get_monthly_usage)
echo "$(date +%Y-%m-%d_%H:%M:%S) Current monthly usage ($LIMIT_TYPE): $MONTHLY_USAGE GB" >> $LOG_FILE

# 检查是否需要发出警告
WARNING_LIMIT=$(echo "$MONTHLY_LIMIT_GB * $WARNING_THRESHOLD" | bc)
if (( $(echo "$MONTHLY_USAGE >= $WARNING_LIMIT" | bc -l) )) && ! $WARNED; then
USAGE_PERCENT=$(echo "scale=1; $MONTHLY_USAGE / $MONTHLY_LIMIT_GB * 100" | bc)
WARNING_MSG=$(replace_placeholders "$WARNING_MESSAGE" "$MONTHLY_USAGE" "$MONTHLY_LIMIT_GB" "$USAGE_PERCENT" "")
send_notification "$NOTIFICATION_TITLE" "$WARNING_MSG"
WARNED=true
echo "WARNED=true" >> $STATE_FILE
fi

# 检查是否需要采取行动
if (( $(echo "$MONTHLY_USAGE >= $MONTHLY_LIMIT_GB" | bc -l) )) && ! $BLOCKED; then
if [ "$ACTION_ON_LIMIT" = "DROP" ]; then
block_network
ACTION_MSG="网络已被封锁"
elif [ "$ACTION_ON_LIMIT" = "SHUTDOWN" ]; then
ACTION_MSG="系统即将关机"
fi
LIMIT_MSG=$(replace_placeholders "$LIMIT_REACHED_MESSAGE" "$MONTHLY_USAGE" "$MONTHLY_LIMIT_GB" "100" "$ACTION_MSG")
send_notification "$NOTIFICATION_TITLE" "$LIMIT_MSG"

if [ "$ACTION_ON_LIMIT" = "SHUTDOWN" ]; then
shutdown_system
fi
fi

# 每月 1 号 0 点重置状态
if [[ "$(date +%d)" == "01" ]] && [[ "$(date +%H:%M)" == "00:00" ]]; then
if $BLOCKED; then
unblock_network
send_notification "$NOTIFICATION_TITLE" "$RESET_MESSAGE"
fi
WARNED=false
echo "WARNED=false" > $STATE_FILE
fi
done

给脚本添加执行权限:

1
chmod +x /root/traffic_monitor.sh

配置脚本

根据自己的需求修改脚本开头变量:

  • INTERFACE:设置为要监控的网络接口名称,例如 “eth0”。
  • MONTHLY_LIMIT_GB:设置月度流量限制(单位:GB)。
  • WARNING_THRESHOLD:设置警告阈值,默认为 0.9(即 90%)。
  • LIMIT_TYPE:选择流量计算方式,可以是 “MAX”(入站和出站的最大值)或 “SUM”(入站和出站的总和)。
  • ACTION_ON_LIMIT:选择达到限制时的行动,可以是 “DROP”(封锁流量)或 “SHUTDOWN”(关机)。
    例如:
1
2
3
4
5
INTERFACE="eth0"
MONTHLY_LIMIT_GB=160
WARNING_THRESHOLD=0.9
LIMIT_TYPE="MAX"
ACTION_ON_LIMIT="DROP"

运行脚本

方法1:直接在终端运行(不推荐)

1
sudo /root/traffic_monitor.sh

注意:这种方法需要保持终端会话开启。如果关闭终端或断开 SSH 连接,脚本将停止运行。

方法2:使用 nohup 在后台运行(不推荐)

1
sudo nohup /root/traffic_monitor.sh > /dev/null 2>&1 &

方法3:创建系统服务(推荐)

  • 创建一个系统服务文件:
1
sudo nano /etc/systemd/system/traffic-monitor.service
  • 在文件中添加以下内容:
1
2
3
4
5
6
7
8
9
[Unit]
Description=Traffic Monitor Service
After=network.target
[Service]
ExecStart=/root/traffic_monitor.sh
Restart=always
User=root
[Install]
WantedBy=multi-user.target
  • 保存并关闭文件。
  • 重新加载 systemd 管理器配置:
1
sudo systemctl daemon-reload
  • 启动服务:
1
sudo systemctl start traffic-monitor
  • 设置开机自启:
1
sudo systemctl enable traffic-monitor

4. 查看日志

脚本运行时会生成日志文件。可以通过以下命令查看日志:

1
tail -f /var/log/vnstat_monitor.log

停止脚本

如果需要停止脚本:

  • 如果是直接运行或使用 nohup,找到脚本的进程 ID 并终止它:
    1
    2
    ps aux | grep traffic_monitor.sh
    sudo kill <PID>
  • 如果是作为系统服务运行,使用以下命令:
    1
    sudo systemctl stop traffic-monitor

注意事项

  • 确保系统已安装 vnstat、iptables 和 jq。
  • 脚本需要 root 权限才能正常运行。
  • 定期检查日志文件,确保脚本正常运行。
  • 如果选择了 “SHUTDOWN” 作为达到限制时的行动,请确保有其他方式可以重新启动服务器。