├── .gitignore ├── README.md ├── servstatsbot.conf ├── servstatsbot.py └── tokens.py_example /.gitignore: -------------------------------------------------------------------------------- 1 | tokens.py 2 | graph.png 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Server Manager Bot 2 | 3 | A Telegram Bot: 4 | 5 | * Commands 6 | * `/stats` - gives summed statistics about memory \ disk \ processes (will improve) 7 | * `/shell` - goes into the mode of executing shell commands & sends you the output 8 | * `/memgraph` - plots a graph of memory usage for a past period and sends you a picture of the graph 9 | * `/setmem` - set memory threshold (%) to monitor and notify if memory usage goes above it 10 | * `/setpoll` - set polling interval in seconds (higher than 10) 11 | * Monitors memory usage and if it reaches above the set threshold = sends you warning message 12 | 13 | 14 | Example summary: [Gif](http://i.imgur.com/AhCvy9W.gifv) 15 | 16 | ![Bot](http://i.imgur.com/hXT0drx.png) 17 | 18 | 19 | Example shell command output as a message from the bot: 20 | 21 | ![Shell](https://i.imgur.com/PtvcaSD.png) 22 | 23 | 24 | Example graph sent by bot: [Gif](http://i.imgur.com/anX7rJR.gifv) 25 | 26 | ![Graph](http://i.imgur.com/K8mG3aM.jpg?1) 27 | 28 | # Usage 29 | 30 | ## Requirements 31 | 32 | * Python 3+ 33 | * [Telepot](https://github.com/nickoala/telepot) 34 | * [Psutil](https://github.com/giampaolo/psutil) 35 | * Make sure to install it for Python 3+ 36 | * In order to make sure that `pip` installs packages for the 3+ version: 37 | * `curl -O https://bootstrap.pypa.io/get-pip.py` 38 | * `sudo python3 get-pip.py` 39 | * After that `pip install psutil` 40 | * Also Stackoverflow question about that [here](http://stackoverflow.com/questions/11268501/how-to-use-pip-with-python-3-x-alongside-python-2-x) 41 | * [matplotlib](http://matplotlib.org/) 42 | * `sudo apt-get install python3-matplotlib` 43 | * Bot key & `tokens.py` 44 | * Hide all the keys and admin variables in `tokens.py`. Use it only for sensitive variables. Avoid creating functions not to clutter the namespaces through the import. 45 | * Get a key from the [Bot Father](https://telegram.me/BotFather) 46 | * Clone that repo 47 | * In the folder with the cloned repo create a file `tokens.py` 48 | * It's added to the `.gitignore` so you don't commit your own (and I don't commit mine:) 49 | * In that file put a string variable `telegrambot` which equals your key 50 | * For example: `telegrambot = "000000000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"` 51 | 52 | ## Running the bot 53 | 54 | `python3 servstatsbot.py` 55 | 56 | ## Running the bot as "daemon" 57 | 58 | * See included file in the repo: `servstatsbot.conf` 59 | * Open it and edit the path as mentiond in the comments there 60 | * Place that file in `/etc/init/` 61 | * Start the "daemon" with: `start servstatsbot` 62 | * You can start|stop|restart 63 | * If bot crashes it'll be automatically restarted 64 | * It will also start after reboot 65 | 66 | ## Setting an admin 67 | 68 | You have to set a variable `adminchatid` in `tokens.py` to be equal your chat_id or multiple chat_id (if more people will use your bot). 69 | For example: 70 | 71 | * `adminchatid = [443355]` 72 | * `adminchatid = [443355, 55667788, 99884433]` 73 | 74 | I will reimplement this differently later. 75 | 76 | 77 | # PLEASE CONTRIBUTE :) 78 | I threw this code together within 10 minutes or so as a mockup to work on it later. But I think it's a nice bot idea and some of you guys might like this too. So please feel free to fork, pull, requests features! 79 | Can give contributors access! 80 | Would really love to see this bot grow some fat and brain:) 81 | 82 | 83 | # Other bot development 84 | 85 | ## Alfred 86 | [http://alfredthebot.com](http://alfredthebot.com) 87 | 88 | 89 | GB 90 | -------------------------------------------------------------------------------- /servstatsbot.conf: -------------------------------------------------------------------------------- 1 | start on runlevel [2345] 2 | stop on runlevel [016] 3 | 4 | respawn 5 | # Change PATH_TO_PY_FILE with the path to the bots py file 6 | # Place this file in /etc/init/ 7 | # And it will be running as "daemon". You can start|stop|restart like this: start servstatsbot 8 | exec python3 /PATH_TO_PY_FILE/servstatsbot.py > /dev/null 9 | -------------------------------------------------------------------------------- /servstatsbot.py: -------------------------------------------------------------------------------- 1 | from tokens import * 2 | import matplotlib 3 | matplotlib.use("Agg") # has to be before any other matplotlibs imports to set a "headless" backend 4 | import matplotlib.pyplot as plt 5 | import psutil 6 | from datetime import datetime 7 | from subprocess import Popen, PIPE, STDOUT 8 | import operator 9 | import collections 10 | # import sys 11 | import time 12 | # import threading 13 | # import random 14 | import telepot 15 | # from telepot.namedtuple import ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardHide, ForceReply 16 | # from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 17 | # from telepot.namedtuple import InlineQueryResultArticle, InlineQueryResultPhoto, InputTextMessageContent 18 | 19 | 20 | 21 | memorythreshold = 85 # If memory usage more this % 22 | poll = 300 # seconds 23 | 24 | shellexecution = [] 25 | timelist = [] 26 | memlist = [] 27 | xaxis = [] 28 | settingmemth = [] 29 | setpolling = [] 30 | graphstart = datetime.now() 31 | 32 | stopmarkup = {'keyboard': [['Stop']]} 33 | hide_keyboard = {'hide_keyboard': True} 34 | 35 | def clearall(chat_id): 36 | if chat_id in shellexecution: 37 | shellexecution.remove(chat_id) 38 | if chat_id in settingmemth: 39 | settingmemth.remove(chat_id) 40 | if chat_id in setpolling: 41 | setpolling.remove(chat_id) 42 | 43 | def plotmemgraph(memlist, xaxis, tmperiod): 44 | # print(memlist) 45 | # print(xaxis) 46 | plt.xlabel(tmperiod) 47 | plt.ylabel('% Used') 48 | plt.title('Memory Usage Graph') 49 | plt.text(0.1*len(xaxis), memorythreshold+2, 'Threshold: '+str(memorythreshold)+ ' %') 50 | memthresholdarr = [] 51 | for xas in xaxis: 52 | memthresholdarr.append(memorythreshold) 53 | plt.plot(xaxis, memlist, 'b-', xaxis, memthresholdarr, 'r--') 54 | plt.axis([0, len(xaxis)-1, 0, 100]) 55 | plt.savefig('/tmp/graph.png') 56 | plt.close() 57 | f = open('/tmp/graph.png', 'rb') # some file on local disk 58 | return f 59 | 60 | 61 | class YourBot(telepot.Bot): 62 | def __init__(self, *args, **kwargs): 63 | super(YourBot, self).__init__(*args, **kwargs) 64 | self._answerer = telepot.helper.Answerer(self) 65 | self._message_with_inline_keyboard = None 66 | 67 | def on_chat_message(self, msg): 68 | content_type, chat_type, chat_id = telepot.glance(msg) 69 | # Do your stuff according to `content_type` ... 70 | print("Your chat_id:" + str(chat_id)) # this will tell you your chat_id 71 | if chat_id in adminchatid: # Store adminchatid variable in tokens.py 72 | if content_type == 'text': 73 | if msg['text'] == '/stats' and chat_id not in shellexecution: 74 | bot.sendChatAction(chat_id, 'typing') 75 | memory = psutil.virtual_memory() 76 | disk = psutil.disk_usage('/') 77 | boottime = datetime.fromtimestamp(psutil.boot_time()) 78 | now = datetime.now() 79 | timedif = "Online for: %.1f Hours" % (((now - boottime).total_seconds()) / 3600) 80 | memtotal = "Total memory: %.2f GB " % (memory.total / 1000000000) 81 | memavail = "Available memory: %.2f GB" % (memory.available / 1000000000) 82 | memuseperc = "Used memory: " + str(memory.percent) + " %" 83 | diskused = "Disk used: " + str(disk.percent) + " %" 84 | pids = psutil.pids() 85 | pidsreply = '' 86 | procs = {} 87 | for pid in pids: 88 | p = psutil.Process(pid) 89 | try: 90 | pmem = p.memory_percent() 91 | if pmem > 0.5: 92 | if p.name() in procs: 93 | procs[p.name()] += pmem 94 | else: 95 | procs[p.name()] = pmem 96 | except: 97 | print("Hm") 98 | sortedprocs = sorted(procs.items(), key=operator.itemgetter(1), reverse=True) 99 | for proc in sortedprocs: 100 | pidsreply += proc[0] + " " + ("%.2f" % proc[1]) + " %\n" 101 | reply = timedif + "\n" + \ 102 | memtotal + "\n" + \ 103 | memavail + "\n" + \ 104 | memuseperc + "\n" + \ 105 | diskused + "\n\n" + \ 106 | pidsreply 107 | bot.sendMessage(chat_id, reply, disable_web_page_preview=True) 108 | elif msg['text'] == "Stop": 109 | clearall(chat_id) 110 | bot.sendMessage(chat_id, "All operations stopped.", reply_markup=hide_keyboard) 111 | elif msg['text'] == '/setpoll' and chat_id not in setpolling: 112 | bot.sendChatAction(chat_id, 'typing') 113 | setpolling.append(chat_id) 114 | bot.sendMessage(chat_id, "Send me a new polling interval in seconds? (higher than 10)", reply_markup=stopmarkup) 115 | elif chat_id in setpolling: 116 | bot.sendChatAction(chat_id, 'typing') 117 | try: 118 | global poll 119 | poll = int(msg['text']) 120 | if poll > 10: 121 | bot.sendMessage(chat_id, "All set!") 122 | clearall(chat_id) 123 | else: 124 | 1/0 125 | except: 126 | bot.sendMessage(chat_id, "Please send a proper numeric value higher than 10.") 127 | elif msg['text'] == "/shell" and chat_id not in shellexecution: 128 | bot.sendMessage(chat_id, "Send me a shell command to execute", reply_markup=stopmarkup) 129 | shellexecution.append(chat_id) 130 | elif msg['text'] == "/setmem" and chat_id not in settingmemth: 131 | bot.sendChatAction(chat_id, 'typing') 132 | settingmemth.append(chat_id) 133 | bot.sendMessage(chat_id, "Send me a new memory threshold to monitor?", reply_markup=stopmarkup) 134 | elif chat_id in settingmemth: 135 | bot.sendChatAction(chat_id, 'typing') 136 | try: 137 | global memorythreshold 138 | memorythreshold = int(msg['text']) 139 | if memorythreshold < 100: 140 | bot.sendMessage(chat_id, "All set!") 141 | clearall(chat_id) 142 | else: 143 | 1/0 144 | except: 145 | bot.sendMessage(chat_id, "Please send a proper numeric value below 100.") 146 | 147 | elif chat_id in shellexecution: 148 | bot.sendChatAction(chat_id, 'typing') 149 | p = Popen(msg['text'], shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) 150 | output = p.stdout.read() 151 | if output != b'': 152 | bot.sendMessage(chat_id, output, disable_web_page_preview=True) 153 | else: 154 | bot.sendMessage(chat_id, "No output.", disable_web_page_preview=True) 155 | elif msg['text'] == '/memgraph': 156 | bot.sendChatAction(chat_id, 'typing') 157 | tmperiod = "Last %.2f hours" % ((datetime.now() - graphstart).total_seconds() / 3600) 158 | bot.sendPhoto(chat_id, plotmemgraph(memlist, xaxis, tmperiod)) 159 | 160 | 161 | 162 | TOKEN = telegrambot 163 | 164 | bot = YourBot(TOKEN) 165 | bot.message_loop() 166 | tr = 0 167 | xx = 0 168 | # Keep the program running. 169 | while 1: 170 | if tr == poll: 171 | tr = 0 172 | timenow = datetime.now() 173 | memck = psutil.virtual_memory() 174 | mempercent = memck.percent 175 | if len(memlist) > 300: 176 | memq = collections.deque(memlist) 177 | memq.append(mempercent) 178 | memq.popleft() 179 | memlist = memq 180 | memlist = list(memlist) 181 | else: 182 | xaxis.append(xx) 183 | xx += 1 184 | memlist.append(mempercent) 185 | memfree = memck.available / 1000000 186 | if mempercent > memorythreshold: 187 | memavail = "Available memory: %.2f GB" % (memck.available / 1000000000) 188 | graphend = datetime.now() 189 | tmperiod = "Last %.2f hours" % ((graphend - graphstart).total_seconds() / 3600) 190 | for adminid in adminchatid: 191 | bot.sendMessage(adminid, "CRITICAL! LOW MEMORY!\n" + memavail) 192 | bot.sendPhoto(adminid, plotmemgraph(memlist, xaxis, tmperiod)) 193 | time.sleep(10) # 10 seconds 194 | tr += 10 195 | -------------------------------------------------------------------------------- /tokens.py_example: -------------------------------------------------------------------------------- 1 | # A token you get from the Telegram's botfather 2 | telegrambot = '9999999:6666aaaaaa666666a6aaa' 3 | 4 | # A chat_id of your client 5 | adminchatid = [99999999] --------------------------------------------------------------------------------