├── Dockerfile
├── docker-compose.yml
├── sss-agent.service
├── README.md
├── LICENSE
├── bot.py
├── sss.sh
├── sss-agent.sh
├── _sss.py
└── client-linux.py
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nyurik/alpine-python3-requests
2 |
3 | LABEL maintainer="lidalao"
4 | LABEL version="0.0.1"
5 | LABEL description="Telegram Bot for ServerStatus"
6 |
7 | WORKDIR /app
8 | COPY ./bot.py .
9 | CMD [ "python", "./bot.py" ]
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | sss:
4 | image: cppla/serverstatus:latest
5 | container_name: sss
6 | restart: unless-stopped
7 | volumes:
8 | - ./config.json:/ServerStatus/server/config.json
9 | - ./json:/usr/share/nginx/html/json
10 | ports:
11 | - 35601:35601
12 | - 8081:80
13 | bot:
14 | build:
15 | context: .
16 | container_name: bot4sss
17 | restart: unless-stopped
18 | environment:
19 | - TG_CHAT_ID=tg_chat_id
20 | - TG_BOT_TOKEN=tg_bot_token
21 |
--------------------------------------------------------------------------------
/sss-agent.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Server Status Agent
3 | After=syslog.target
4 |
5 |
6 | [Service]
7 | # Modify these two values and uncomment them if you have
8 | # repos with lots of files and get an HTTP error 500 because
9 | # of that
10 | ###
11 | #LimitMEMLOCK=infinity
12 | #LimitNOFILE=65535
13 | Type=simple
14 | User=root
15 | Group=root
16 | WorkingDirectory=/opt/sss/agent/
17 | ExecStart=/usr/bin/python3 /opt/sss/agent/client-linux.py SERVER=sss_host USER=sss_user PASSWORD=sss_pass
18 | Restart=always
19 | #Environment=DEBUG=true
20 |
21 | # Some distributions may not support these hardening directives. If you cannot start the service due
22 | # to an unknown option, comment out the ones not supported by your version of systemd.
23 | #ProtectSystem=full
24 | #PrivateDevices=yes
25 | #PrivateTmp=yes
26 | #NoNewPrivileges=true
27 |
28 | [Install]
29 | WantedBy=multi-user.target
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 介绍
2 | 项目基于cppla版本ServerStatus, 增加如下功能:
3 |
4 | - 更方便的节点管理, 支持增删改查
5 | - 上下线通知(telegram)
6 | - Agent机器安装脚本改为systemd, 支持开机自启
7 |
8 | >由于未改动cppla版的任何代码,所以,我愿意把这个项目称为ServerStatus的小插件, 理论上它可以为任何版本的ServerStatus服务
9 |
10 |
11 | # 安装
12 | 在**服务端**复制以下命令,一键到底。请记得替换成你自己的YOUR_TG_CHAT_ID和YOUR_TG_BOT_TOKEN。
13 |
14 | 其中,Bot token可以通过@BotFather创建机器人获取, Chat id可以通过@getuserID获取。
15 |
16 | ```
17 | mkdir sss && cd sss && wget --no-check-certificate https://raw.githubusercontent.com/lidalao/ServerStatus/master/sss.sh && chmod +x ./sss.sh && sudo ./sss.sh YOUR_TG_CHAT_ID YOUR_TG_BOT_TOKEN
18 |
19 | ```
20 | 安装成功后,web服务地址:http://ip:8081
21 |
22 | 更多信息请移步 https://lidalao.com/archives/87 +1ip
23 |
24 | 挺好用的?送作者一杯可乐?->
25 | [
](https://shop.lidalao.com/buy/4)
26 |
27 |
28 |
29 | # 参考
30 | - https://github.com/cppla/ServerStatus
31 | - https://github.com/naiba/nezha
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 lidalao
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | # Create by : https://github.com/lidalao/ServerStatus
4 | # 版本:0.0.1, 支持Python版本:2.7 to 3.9
5 | # 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
6 |
7 | import os
8 | import sys
9 | import requests
10 | import time
11 | import traceback
12 |
13 | NODE_STATUS_URL = 'http://sss/json/stats.json'
14 |
15 | offs = []
16 | counterOff = {}
17 | counterOn = {}
18 |
19 | def _send(text):
20 | chat_id = os.getenv('TG_CHAT_ID')
21 | bot_token = os.environ.get('TG_BOT_TOKEN')
22 | url = f"https://api.telegram.org/bot{bot_token}/sendMessage?parse_mode=HTML&disable_web_page_preview=true&chat_id=" + chat_id + "&text=" + text
23 | try:
24 | requests.get(url)
25 | except Exception as e:
26 | print("catch exception: ", traceback.format_exc())
27 |
28 | def send2tg(srv, flag):
29 | if srv not in counterOff:
30 | counterOff[srv] = 0
31 | if srv not in counterOn:
32 | counterOn[srv] = 0
33 |
34 | if flag == 1 : # online
35 | if srv in offs:
36 | if counterOn[srv] < 10:
37 | counterOn[srv] += 1
38 | return
39 | #1. Remove srv from offs; 2. Send to tg: I am online
40 | offs.remove(srv)
41 | counterOn[srv] = 0
42 | text = 'Server Status' + '\n主机上线: ' + srv
43 | _send(text)
44 | else: #offline
45 | if srv not in offs:
46 | if counterOff[srv] < 10:
47 | counterOff[srv] += 1
48 | return
49 | #1. Append srv to offs; 2. Send to tg: I am offline
50 | offs.append(srv)
51 | counterOff[srv] = 0
52 | text = 'Server Status' + '\n主机下线: ' + srv
53 | _send(text)
54 |
55 | def sscmd(address):
56 | while True:
57 | r = requests.get(url=address, headers={"User-Agent": "ServerStatus/20211116"})
58 | try:
59 | jsonR = r.json()
60 | except Exception as e:
61 | print('未发现任何节点')
62 | continue
63 | for i in jsonR["servers"]:
64 | if i["online4"] is False and i["online6"] is False:
65 | send2tg(i["name"], 0)
66 | else:
67 | send2tg(i["name"], 1)
68 |
69 |
70 | time.sleep(3)
71 |
72 | if __name__ == '__main__':
73 | sscmd(NODE_STATUS_URL)
74 |
--------------------------------------------------------------------------------
/sss.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #========================================================
4 | # System Required: CentOS 7+ / Debian 8+ / Ubuntu 16+ /
5 | # Arch 未测试
6 | # Description: Server Status 监控安装脚本
7 | # Github: https://github.com/lidalao/ServerStatus
8 | #========================================================
9 |
10 | GITHUB_RAW_URL="https://raw.githubusercontent.com/lidalao/ServerStatus/master"
11 |
12 | red='\033[0;31m'
13 | green='\033[0;32m'
14 | yellow='\033[0;33m'
15 | plain='\033[0m'
16 | export PATH=$PATH:/usr/local/bin
17 |
18 | pre_check() {
19 | command -v systemctl >/dev/null 2>&1
20 | if [[ $? != 0 ]]; then
21 | echo "不支持此系统:未找到 systemctl 命令"
22 | exit 1
23 | fi
24 |
25 | # check root
26 | [[ $EUID -ne 0 ]] && echo -e "${red}错误: ${plain} 必须使用root用户运行此脚本!\n" && exit 1
27 | }
28 |
29 | install_soft() {
30 | (command -v yum >/dev/null 2>&1 && yum install $* -y) ||
31 | (command -v apt >/dev/null 2>&1 && apt install $* -y) ||
32 | (command -v pacman >/dev/null 2>&1 && pacman -Syu $*) ||
33 | (command -v apt-get >/dev/null 2>&1 && apt-get install $* -y)
34 |
35 | if [[ $? != 0 ]]; then
36 | echo -e "${red}安装基础软件失败,稍等会${plain}"
37 | exit 1
38 | fi
39 |
40 | (command -v pip3 >/dev/null 2>&1 && pip3 install requests)
41 | }
42 |
43 | install_base() {
44 | (command -v curl >/dev/null 2>&1 && command -v wget >/dev/null 2>&1 && command -v pip3 >/dev/null 2>&1) || install_soft curl wget python3-pip python3
45 | }
46 |
47 | install_docker() {
48 | install_base
49 | command -v docker >/dev/null 2>&1
50 | if [[ $? != 0 ]]; then
51 | install_base
52 | echo -e "正在安装 Docker"
53 | bash <(curl -sL https://get.docker.com) >/dev/null 2>&1
54 | if [[ $? != 0 ]]; then
55 | echo -e "${red}下载Docker失败${plain}"
56 | exit 1
57 | fi
58 | systemctl enable docker.service
59 | systemctl start docker.service
60 | echo -e "${green}Docker${plain} 安装成功"
61 | fi
62 |
63 | command -v docker-compose >/dev/null 2>&1
64 | if [[ $? != 0 ]]; then
65 | echo -e "正在安装 Docker Compose"
66 | wget --no-check-certificate -O /usr/local/bin/docker-compose "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" >/dev/null 2>&1
67 | if [[ $? != 0 ]]; then
68 | echo -e "${red}下载Compose失败${plain}"
69 | return 0
70 | fi
71 | chmod +x /usr/local/bin/docker-compose
72 | echo -e "${green}Docker Compose${plain} 安装成功"
73 | fi
74 | }
75 |
76 |
77 | modify_bot_config(){
78 | if [[ $# < 2 ]]; then
79 | echo -e "${red}参数错误,未能正确提供tg bot信息,请手动修改docker-compse.yml中的bot信息 ${plain}"
80 | exit 1
81 | fi
82 |
83 | tg_chat_id=$1
84 | tg_bot_token=$2
85 |
86 | sed -i "s/tg_chat_id/${tg_chat_id}/" docker-compose.yml
87 | sed -i "s/tg_bot_token/${tg_bot_token}/" docker-compose.yml
88 | }
89 |
90 | install_dashboard(){
91 |
92 | install_docker
93 |
94 | if [ "$(docker ps -q -f name=bot4sss)" ]; then
95 | return 0
96 | fi
97 |
98 | echo -e "> 安装面板"
99 |
100 | wget --no-check-certificate ${GITHUB_RAW_URL}/docker-compose.yml >/dev/null 2>&1
101 | wget --no-check-certificate ${GITHUB_RAW_URL}/Dockerfile >/dev/null 2>&1
102 | wget --no-check-certificate ${GITHUB_RAW_URL}/bot.py >/dev/null 2>&1
103 | wget --no-check-certificate ${GITHUB_RAW_URL}/_sss.py >/dev/null 2>&1
104 | echo '{"servers":[]}' > config.json
105 |
106 | modify_bot_config "$@"
107 |
108 | echo -e "> 启动面板"
109 | (docker-compose up -d) >/dev/null 2>&1
110 | }
111 |
112 | nodes_mgr(){
113 | python3 _sss.py
114 | }
115 |
116 |
117 | pre_check
118 | install_dashboard "$@"
119 | nodes_mgr
120 |
--------------------------------------------------------------------------------
/sss-agent.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #========================================================
4 | # System Required: CentOS 7+ / Debian 8+ / Ubuntu 16+ /
5 | # Arch 未测试
6 | # Description: Server Status 监控安装脚本
7 | # Github: https://github.com/lidalao/ServerStatus
8 | #========================================================
9 |
10 | SSS_BASE_PATH="/opt/sss"
11 | SSS_AGENT_PATH="${SSS_BASE_PATH}/agent"
12 | SSS_AGENT_SERVICE="/etc/systemd/system/sss-agent.service"
13 | GITHUB_RAW_URL="https://raw.githubusercontent.com/lidalao/ServerStatus/master"
14 |
15 | red='\033[0;31m'
16 | green='\033[0;32m'
17 | yellow='\033[0;33m'
18 | plain='\033[0m'
19 | export PATH=$PATH:/usr/local/bin
20 |
21 | pre_check() {
22 | command -v systemctl >/dev/null 2>&1
23 | if [[ $? != 0 ]]; then
24 | echo "不支持此系统:未找到 systemctl 命令"
25 | exit 1
26 | fi
27 |
28 | # check root
29 | [[ $EUID -ne 0 ]] && echo -e "${red}错误: ${plain} 必须使用root用户运行此脚本!\n" && exit 1
30 | }
31 |
32 | install_soft() {
33 | (command -v yum >/dev/null 2>&1 && yum install $* -y) ||
34 | (command -v apt >/dev/null 2>&1 && apt update && apt install $* -y) ||
35 | (command -v pacman >/dev/null 2>&1 && pacman -Syu $*) ||
36 | (command -v apt-get >/dev/null 2>&1 && apt update && apt-get install $* -y)
37 | }
38 |
39 | install_base() {
40 | (command -v git >/dev/null 2>&1 && command -v curl >/dev/null 2>&1 && command -v wget >/dev/null 2>&1 && command -v tar >/dev/null 2>&1) ||
41 | (install_soft curl wget python3)
42 | }
43 |
44 | modify_agent_config() {
45 | echo -e "> 修改Agent配置"
46 |
47 | wget -O $SSS_AGENT_SERVICE ${GITHUB_RAW_URL}/sss-agent.service >/dev/null 2>&1
48 | if [[ $? != 0 ]]; then
49 | echo -e "${red}文件下载失败,请检查本机能否连接 ${GITHUB_RAW_URL}${plain}"
50 | return 0
51 | fi
52 |
53 | if [ $# -lt 3 ]; then
54 | echo "server status agent需要3个参数,请重新输入"
55 | return 0
56 | else
57 | sss_host=$1
58 | sss_user=$2
59 | sss_pass=$3
60 | fi
61 |
62 | sed -i "s/sss_host/${sss_host}/" ${SSS_AGENT_SERVICE}
63 | sed -i "s/sss_user/${sss_user}/" ${SSS_AGENT_SERVICE}
64 | sed -i "s/sss_pass/${sss_pass}/" ${SSS_AGENT_SERVICE}
65 |
66 | echo -e "Agent配置 ${green}修改成功,请稍等重启生效${plain}"
67 |
68 | systemctl daemon-reload
69 | systemctl enable sss-agent
70 | systemctl restart sss-agent
71 | }
72 |
73 | install_agent() {
74 | install_base
75 |
76 | echo -e "> 安装监控Agent"
77 |
78 | # 哪吒监控文件夹
79 | mkdir -p $SSS_AGENT_PATH
80 | chmod 777 -R $SSS_AGENT_PATH
81 |
82 | echo -e "正在下载监控端"
83 | wget --no-check-certificate -qO client-linux.py $GITHUB_RAW_URL/client-linux.py
84 | mv client-linux.py $SSS_AGENT_PATH
85 |
86 | modify_agent_config "$@"
87 | }
88 |
89 | uninstall_agent() {
90 | (systemctl stop sss-agent) >/dev/null 2>&1
91 | (systemctl disable sss-agent) >/dev/null 2>&1
92 | (rm -rf $SSS_AGENT_PATH) >/dev/null 2>&1
93 | (rm -rf $SSS_AGENT_SERVICE) >/dev/null 2>&1
94 | systemctl daemon-reload
95 | }
96 |
97 | show_menu() {
98 | echo -e "
99 | ${green}Server Status监控管理脚本${plain}
100 | --- https://github.com/lidalao/sss ---
101 | ${green}1.${plain} 安装监控Agent
102 | ${green}2.${plain} 卸载Agent
103 | ${green}0.${plain} 退出脚本
104 | "
105 | echo && read -ep "请输入选择 [0-2]: " num
106 |
107 | case "${num}" in
108 | 0)
109 | exit 0
110 | ;;
111 |
112 | 1)
113 | install_agent
114 | ;;
115 | 2)
116 | uninstall_agent
117 | echo -e "${green}卸载Agent完成${plain}"
118 | ;;
119 | *)
120 | echo -e "${red}请输入正确的数字 [0-2]${plain}"
121 | ;;
122 | esac
123 | }
124 |
125 | pre_check
126 |
127 | if [[ $# == 3 ]]; then
128 | uninstall_agent
129 | install_agent "$@"
130 | else
131 | show_menu
132 | fi
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/_sss.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | # Create by : https://github.com/lidalao/ServerStatus
4 | # 版本:0.0.1, 支持Python版本:2.7 to 3.9
5 | # 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
6 |
7 | import json
8 | import sys
9 | import os
10 | import requests
11 | import random,string
12 | import subprocess
13 | import uuid
14 |
15 | CONFIG_FILE = "config.json"
16 | GITHUB_RAW_URL = "https://raw.githubusercontent.com/lidalao/ServerStatus/master"
17 | IP_URL = "https://api.ipify.org"
18 |
19 | jjs = {}
20 | ip = ""
21 |
22 | def how2agent(user, passwd):
23 | print('```')
24 | print("\n")
25 | print('curl -L {0}/sss-agent.sh -o sss-agent.sh && chmod +x sss-agent.sh && sudo ./sss-agent.sh {1} {2} {3}'.format(GITHUB_RAW_URL, getIP(), user, passwd))
26 | print("\n")
27 | print('```')
28 |
29 |
30 | def getIP():
31 | global ip
32 | if ip == "":
33 | ip = requests.get(IP_URL).content.decode('utf8')
34 | return ip
35 |
36 | def restartSSS():
37 | cmd = ["docker-compose", "restart"]
38 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
39 | for line in p.stdout:
40 | print(line)
41 | p.wait()
42 |
43 | def getPasswd():
44 | mima = []
45 | sz = '123456789'
46 | xzm = 'abcdefghijklmnopqrstuvwxyz'
47 | dzm = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
48 | # tzf = '~!#@$%^&*?'
49 | # all = sz + xzm + dzm + tzf
50 | all = sz + xzm + dzm
51 | m1 = random.choice(sz)
52 |
53 | m2 = random.choice(xzm)
54 | m3 = random.choice(dzm)
55 | # m4 = random.choice(tzf)
56 | m5 = "".join(random.sample(all,12))
57 | mima.append(m1)
58 | mima.append(m2)
59 | mima.append(m3)
60 | # mima.append(m4)
61 | mima.append(m5)
62 | random.shuffle(mima)
63 | a = "".join(mima)
64 | return a
65 |
66 | def saveJJs():
67 | jjs['servers'] = sorted(jjs['servers'], key=lambda d: d['name'])
68 |
69 | file = open(CONFIG_FILE,"w")
70 | file.write(json.dumps(jjs))
71 | file.close()
72 |
73 | def _show():
74 | print("---你的jjs如下---")
75 | print("\n")
76 | if len(jjs['servers']) == 0:
77 | print('>>> 你好MJJ, 暂时没发现你有任何jj! <<<')
78 | print("\n")
79 | print("-----------------")
80 | return
81 |
82 | for idx, item in enumerate(jjs['servers']):
83 | print(str(idx) + ". name: " + item['name'] + ", loc: "+ item['location'] + ", type: " + item['type'])
84 |
85 | print("\n")
86 | print("-----------------")
87 |
88 | def show():
89 | _show()
90 | _back()
91 |
92 | def _back():
93 | print(">>>按任意键返回上级菜单")
94 | input()
95 | cmd()
96 |
97 | def add():
98 | print('>>>请输入jj名字:')
99 | jjname =input()
100 | if jjname == "":
101 | print("输入有误")
102 | _back()
103 | return
104 |
105 | print('>>>请输入{0}位置:[{1}]'.format(jjname, "us"))
106 | jjloc =input()
107 | if jjloc == "":
108 | jjloc = "us"
109 |
110 | print('>>>请输入{0}类型:[{1}]'.format(jjname, "kvm"))
111 | jjtype =input()
112 | if jjtype == "":
113 | jjtype = "kvm"
114 |
115 | item = {}
116 | item['monthstart'] = "1"
117 | item['location'] = jjloc
118 | item['type'] = jjtype
119 | item['name'] = jjname
120 | item['username'] = uuid.uuid4().hex
121 | item['host'] = jjname
122 | item['password'] = getPasswd()
123 | jjs['servers'].append(item)
124 | saveJJs()
125 |
126 | print("操作完成,等待服务重启")
127 | restartSSS()
128 |
129 | print("添加成功!")
130 | _show()
131 | print('>>>请复制以下命令在机器{0}安装agent服务'.format(item['name']))
132 | how2agent(item['username'], item['password'])
133 | _back()
134 |
135 | def update():
136 | print("请输入需要更新的jj标号:")
137 | idx = input()
138 | if not idx.isnumeric():
139 | print('无效输入,退出')
140 | _back()
141 | return
142 |
143 | if len(jjs['servers']) <= int(idx):
144 | print('输入无效')
145 | _back()
146 | return
147 |
148 | jj = jjs['servers'][int(idx)]
149 | print('--- 面板更换ip时,请复制以下命令在机器{0}安装agent服务 ---'.format(jj['name']))
150 | how2agent(jj['username'], jj['password'])
151 |
152 | print('>>>请输入{0}新名字:[{1}] *中括号内为原值,按回车表示不做修改*'.format(jj['name'], jj['name']))
153 | jjname = input()
154 | if "" != jjname:
155 | jjs['servers'][int(idx)]['name'] = jjname
156 |
157 | print('>>>请输入{0}新位置:[{1}]'.format(jj['name'], jj['location']))
158 | jjloc = input()
159 | if "" != jjloc:
160 | jjs['servers'][int(idx)]['location'] = jjloc
161 |
162 | print('>>>请输入{0}新类型:[{1}]'.format(jj['name'], jj['type']))
163 | jjtype = input()
164 | if "" != jjtype:
165 | jjs['servers'][int(idx)]['type'] = jjtype
166 |
167 | print('>>>请输入{0}新的月流量起始日:[{1}]'.format(jj['name'], jj['monthstart']))
168 | jjms = input()
169 | if "" != jjms:
170 | jjs['servers'][int(idx)]['monthstart'] = jjms
171 |
172 | if "" == jjname and "" == jjloc and "" == jjtype and "" == jjms:
173 | print('未做任何更新,直接返回')
174 | _back()
175 | return
176 | saveJJs()
177 | print("操作完成,等待服务重启")
178 | restartSSS()
179 | print("更新成功!")
180 | _show()
181 | _back()
182 |
183 | def remove():
184 | print(">>>请输入需要删除的jj标号:")
185 | idx =input()
186 | if not idx.isnumeric():
187 | print('无效输入,退出')
188 | _back()
189 | return
190 |
191 | if len(jjs['servers']) <= int(idx):
192 | print('输入无效')
193 | _back()
194 | return
195 |
196 | print('>>>请确认你需要删除的节点:{0}? [Y/n]'.format(jjs['servers'][int(idx)]['name']))
197 | yesOrNo = input()
198 | if yesOrNo == "n" or yesOrNo == "N":
199 | print("取消删除")
200 | _back()
201 | return
202 |
203 | del jjs['servers'][int(idx)]
204 | saveJJs()
205 | print("操作完成,等待服务重启")
206 | restartSSS()
207 | print("删除成功!")
208 | _show()
209 | _back()
210 |
211 | def cmd():
212 | print("\n")
213 | print('- - - 欢迎使用最简洁的探针: Server Status - - -')
214 | print('详细教程请参考:https://lidalao.com/archives/87')
215 | print("\n")
216 | _show()
217 | print("\n")
218 |
219 | print('>>>请输入操作标号:1.查看, 2.添加, 3.删除, 4.更新, 0.退出')
220 | x = input()
221 | if not x.isnumeric():
222 | print('无效输入, 退出')
223 | return
224 |
225 | if 1 == int(x):
226 | show()
227 | elif 2 == int(x):
228 | add()
229 | elif 3 == int(x):
230 | remove()
231 | elif 4 == int(x):
232 | update()
233 | elif 0 == int(x):
234 | return
235 | else:
236 | print('无效输入, 退出')
237 | return
238 |
239 |
240 | if __name__ == '__main__':
241 | file_exists = os.path.exists(CONFIG_FILE)
242 | if file_exists == False:
243 | print("请在当前目录创建config.json!")
244 | exit()
245 |
246 | file = open(CONFIG_FILE,"r")
247 | jjs = json.load(file)
248 | file.close()
249 | cmd()
250 |
--------------------------------------------------------------------------------
/client-linux.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | # Create by : https://github.com/cppla/ServerStatus
4 | # 版本:1.0.2, 支持Python版本:2.7 to 3.9
5 | # 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
6 | # 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。
7 | #
8 | # 20211117: 移除tupd信息 (Update by: https://github.com/lidalao/ServerStatus)
9 | #
10 |
11 | SERVER = "127.0.0.1"
12 | USER = "s01"
13 |
14 |
15 |
16 | PORT = 35601
17 | PASSWORD = "USER_DEFAULT_PASSWORD"
18 | INTERVAL = 1
19 | PROBEPORT = 80
20 | PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
21 | PING_PACKET_HISTORY_LEN = 100
22 | CU = "cu.tz.cloudcpp.com"
23 | CT = "ct.tz.cloudcpp.com"
24 | CM = "cm.tz.cloudcpp.com"
25 |
26 | import socket
27 | import time
28 | import timeit
29 | import re
30 | import os
31 | import sys
32 | import json
33 | import errno
34 | import subprocess
35 | import threading
36 | try:
37 | from queue import Queue # python3
38 | except ImportError:
39 | from Queue import Queue # python2
40 |
41 | def get_uptime():
42 | with open('/proc/uptime', 'r') as f:
43 | uptime = f.readline().split('.', 2)
44 | return int(uptime[0])
45 |
46 | def get_memory():
47 | re_parser = re.compile(r'^(?P\S*):\s*(?P\d*)\s*kB')
48 | result = dict()
49 | for line in open('/proc/meminfo'):
50 | match = re_parser.match(line)
51 | if not match:
52 | continue
53 | key, value = match.groups(['key', 'value'])
54 | result[key] = int(value)
55 | MemTotal = float(result['MemTotal'])
56 | MemUsed = MemTotal-float(result['MemFree'])-float(result['Buffers'])-float(result['Cached'])-float(result['SReclaimable'])
57 | SwapTotal = float(result['SwapTotal'])
58 | SwapFree = float(result['SwapFree'])
59 | return int(MemTotal), int(MemUsed), int(SwapTotal), int(SwapFree)
60 |
61 | def get_hdd():
62 | p = subprocess.check_output(['df', '-Tlm', '--total', '-t', 'ext4', '-t', 'ext3', '-t', 'ext2', '-t', 'reiserfs', '-t', 'jfs', '-t', 'ntfs', '-t', 'fat32', '-t', 'btrfs', '-t', 'fuseblk', '-t', 'zfs', '-t', 'simfs', '-t', 'xfs']).decode("Utf-8")
63 | total = p.splitlines()[-1]
64 | used = total.split()[3]
65 | size = total.split()[2]
66 | return int(size), int(used)
67 |
68 | def get_time():
69 | with open("/proc/stat", "r") as f:
70 | time_list = f.readline().split(' ')[2:6]
71 | for i in range(len(time_list)) :
72 | time_list[i] = int(time_list[i])
73 | return time_list
74 |
75 | def delta_time():
76 | x = get_time()
77 | time.sleep(INTERVAL)
78 | y = get_time()
79 | for i in range(len(x)):
80 | y[i]-=x[i]
81 | return y
82 |
83 | def get_cpu():
84 | t = delta_time()
85 | st = sum(t)
86 | if st == 0:
87 | st = 1
88 | result = 100-(t[len(t)-1]*100.00/st)
89 | return round(result, 1)
90 |
91 | def liuliang():
92 | NET_IN = 0
93 | NET_OUT = 0
94 | with open('/proc/net/dev') as f:
95 | for line in f.readlines():
96 | netinfo = re.findall('([^\s]+):[\s]{0,}(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)', line)
97 | if netinfo:
98 | if netinfo[0][0] == 'lo' or 'tun' in netinfo[0][0] \
99 | or 'docker' in netinfo[0][0] or 'veth' in netinfo[0][0] \
100 | or 'br-' in netinfo[0][0] or 'vmbr' in netinfo[0][0] \
101 | or 'vnet' in netinfo[0][0] or 'kube' in netinfo[0][0] \
102 | or netinfo[0][1]=='0' or netinfo[0][9]=='0':
103 | continue
104 | else:
105 | NET_IN += int(netinfo[0][1])
106 | NET_OUT += int(netinfo[0][9])
107 | return NET_IN, NET_OUT
108 |
109 | def tupd():
110 | '''
111 | tcp, udp, process, thread count: for view ddcc attack , then send warning
112 | :return:
113 | '''
114 | return 0,0,0,0
115 |
116 | s = subprocess.check_output("ss -t|wc -l", shell=True)
117 | t = int(s[:-1])-1
118 | s = subprocess.check_output("ss -u|wc -l", shell=True)
119 | u = int(s[:-1])-1
120 | s = subprocess.check_output("ps -ef|wc -l", shell=True)
121 | p = int(s[:-1])-2
122 | s = subprocess.check_output("ps -eLf|wc -l", shell=True)
123 | d = int(s[:-1])-2
124 | return t,u,p,d
125 |
126 | def get_network(ip_version):
127 | if(ip_version == 4):
128 | HOST = "ipv4.google.com"
129 | elif(ip_version == 6):
130 | HOST = "ipv6.google.com"
131 | try:
132 | socket.create_connection((HOST, 80), 2).close()
133 | return True
134 | except:
135 | return False
136 |
137 | lostRate = {
138 | '10010': 0.0,
139 | '189': 0.0,
140 | '10086': 0.0
141 | }
142 | pingTime = {
143 | '10010': 0,
144 | '189': 0,
145 | '10086': 0
146 | }
147 | netSpeed = {
148 | 'netrx': 0.0,
149 | 'nettx': 0.0,
150 | 'clock': 0.0,
151 | 'diff': 0.0,
152 | 'avgrx': 0,
153 | 'avgtx': 0
154 | }
155 |
156 | def _ping_thread(host, mark, port):
157 | lostPacket = 0
158 | packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN)
159 |
160 | IP = host
161 | if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname
162 | try:
163 | if PROBE_PROTOCOL_PREFER == 'ipv4':
164 | IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0]
165 | else:
166 | IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0]
167 | except Exception:
168 | pass
169 |
170 | while True:
171 | if packet_queue.full():
172 | if packet_queue.get() == 0:
173 | lostPacket -= 1
174 | try:
175 | b = timeit.default_timer()
176 | socket.create_connection((IP, port), timeout=1).close()
177 | pingTime[mark] = int((timeit.default_timer() - b) * 1000)
178 | packet_queue.put(1)
179 | except socket.error as error:
180 | if error.errno == errno.ECONNREFUSED:
181 | pingTime[mark] = int((timeit.default_timer() - b) * 1000)
182 | packet_queue.put(1)
183 | #elif error.errno == errno.ETIMEDOUT:
184 | else:
185 | lostPacket += 1
186 | packet_queue.put(0)
187 |
188 | if packet_queue.qsize() > 30:
189 | lostRate[mark] = float(lostPacket) / packet_queue.qsize()
190 |
191 | time.sleep(INTERVAL)
192 |
193 | def _net_speed():
194 | while True:
195 | with open("/proc/net/dev", "r") as f:
196 | net_dev = f.readlines()
197 | avgrx = 0
198 | avgtx = 0
199 | for dev in net_dev[2:]:
200 | dev = dev.split(':')
201 | if "lo" in dev[0] or "tun" in dev[0] \
202 | or "docker" in dev[0] or "veth" in dev[0] \
203 | or "br-" in dev[0] or "vmbr" in dev[0] \
204 | or "vnet" in dev[0] or "kube" in dev[0]:
205 | continue
206 | dev = dev[1].split()
207 | avgrx += int(dev[0])
208 | avgtx += int(dev[8])
209 | now_clock = time.time()
210 | netSpeed["diff"] = now_clock - netSpeed["clock"]
211 | netSpeed["clock"] = now_clock
212 | netSpeed["netrx"] = int((avgrx - netSpeed["avgrx"]) / netSpeed["diff"])
213 | netSpeed["nettx"] = int((avgtx - netSpeed["avgtx"]) / netSpeed["diff"])
214 | netSpeed["avgrx"] = avgrx
215 | netSpeed["avgtx"] = avgtx
216 | time.sleep(INTERVAL)
217 |
218 | def get_realtime_date():
219 | t1 = threading.Thread(
220 | target=_ping_thread,
221 | kwargs={
222 | 'host': CU,
223 | 'mark': '10010',
224 | 'port': PROBEPORT
225 | }
226 | )
227 | t2 = threading.Thread(
228 | target=_ping_thread,
229 | kwargs={
230 | 'host': CT,
231 | 'mark': '189',
232 | 'port': PROBEPORT
233 | }
234 | )
235 | t3 = threading.Thread(
236 | target=_ping_thread,
237 | kwargs={
238 | 'host': CM,
239 | 'mark': '10086',
240 | 'port': PROBEPORT
241 | }
242 | )
243 | t4 = threading.Thread(
244 | target=_net_speed,
245 | )
246 | t1.setDaemon(True)
247 | t2.setDaemon(True)
248 | t3.setDaemon(True)
249 | t4.setDaemon(True)
250 | t1.start()
251 | t2.start()
252 | t3.start()
253 | t4.start()
254 |
255 | def byte_str(object):
256 | '''
257 | bytes to str, str to bytes
258 | :param object:
259 | :return:
260 | '''
261 | if isinstance(object, str):
262 | return object.encode(encoding="utf-8")
263 | elif isinstance(object, bytes):
264 | return bytes.decode(object)
265 | else:
266 | print(type(object))
267 |
268 | if __name__ == '__main__':
269 | for argc in sys.argv:
270 | if 'SERVER' in argc:
271 | SERVER = argc.split('SERVER=')[-1]
272 | elif 'PORT' in argc:
273 | PORT = int(argc.split('PORT=')[-1])
274 | elif 'USER' in argc:
275 | USER = argc.split('USER=')[-1]
276 | elif 'PASSWORD' in argc:
277 | PASSWORD = argc.split('PASSWORD=')[-1]
278 | elif 'INTERVAL' in argc:
279 | INTERVAL = int(argc.split('INTERVAL=')[-1])
280 | socket.setdefaulttimeout(30)
281 | get_realtime_date()
282 | while True:
283 | try:
284 | print("Connecting...")
285 | s = socket.create_connection((SERVER, PORT))
286 | data = byte_str(s.recv(1024))
287 | if data.find("Authentication required") > -1:
288 | s.send(byte_str(USER + ':' + PASSWORD + '\n'))
289 | data = byte_str(s.recv(1024))
290 | if data.find("Authentication successful") < 0:
291 | print(data)
292 | raise socket.error
293 | else:
294 | print(data)
295 | raise socket.error
296 |
297 | print(data)
298 | if data.find("You are connecting via") < 0:
299 | data = byte_str(s.recv(1024))
300 | print(data)
301 |
302 | timer = 0
303 | check_ip = 0
304 | if data.find("IPv4") > -1:
305 | check_ip = 6
306 | elif data.find("IPv6") > -1:
307 | check_ip = 4
308 | else:
309 | print(data)
310 | raise socket.error
311 |
312 | while True:
313 | CPU = get_cpu()
314 | NET_IN, NET_OUT = liuliang()
315 | Uptime = get_uptime()
316 | Load_1, Load_5, Load_15 = os.getloadavg()
317 | MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory()
318 | HDDTotal, HDDUsed = get_hdd()
319 |
320 | array = {}
321 | if not timer:
322 | array['online' + str(check_ip)] = get_network(check_ip)
323 | timer = 10
324 | else:
325 | timer -= 1*INTERVAL
326 |
327 | array['uptime'] = Uptime
328 | array['load_1'] = Load_1
329 | array['load_5'] = Load_5
330 | array['load_15'] = Load_15
331 | array['memory_total'] = MemoryTotal
332 | array['memory_used'] = MemoryUsed
333 | array['swap_total'] = SwapTotal
334 | array['swap_used'] = SwapTotal - SwapFree
335 | array['hdd_total'] = HDDTotal
336 | array['hdd_used'] = HDDUsed
337 | array['cpu'] = CPU
338 | array['network_rx'] = netSpeed.get("netrx")
339 | array['network_tx'] = netSpeed.get("nettx")
340 | array['network_in'] = NET_IN
341 | array['network_out'] = NET_OUT
342 | # todo:兼容旧版本,下个版本删除ip_status
343 | array['ip_status'] = True
344 | array['ping_10010'] = lostRate.get('10010') * 100
345 | array['ping_189'] = lostRate.get('189') * 100
346 | array['ping_10086'] = lostRate.get('10086') * 100
347 | array['time_10010'] = pingTime.get('10010')
348 | array['time_189'] = pingTime.get('189')
349 | array['time_10086'] = pingTime.get('10086')
350 | array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
351 |
352 | s.send(byte_str("update " + json.dumps(array) + "\n"))
353 | except KeyboardInterrupt:
354 | raise
355 | except socket.error:
356 | print("Disconnected...")
357 | if 's' in locals().keys():
358 | del s
359 | time.sleep(3)
360 | except Exception as e:
361 | print("Caught Exception:", e)
362 | if 's' in locals().keys():
363 | del s
364 | time.sleep(3)
365 |
--------------------------------------------------------------------------------