├── 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 | --------------------------------------------------------------------------------