#description: telegram bot for monitoring the system #dependencies: telebot #usage: python3 lainmonitor.py | or run it as a service #authors: hornet import subprocess import threading from time import sleep from telebot import * #define the variables status, hostname, uptime, zerotier, prosody, postgres, tailscale, nginx, disk, ping = 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown' nodes, hostnames, reach, threads = [], [], [], [] #change this to your instance's hostname host = subprocess.check_output(['hostname']).decode().strip() #print ('host:', host) # debug #load the token token = open('.env', 'r').read().strip() #load the authorized users authorized_users = [line.strip() for line in open('.authorized_users', 'r').readlines()] #print('authorized users:', authorized_users) # debug #bot init bot = telebot.TeleBot(token) updater = bot.update_listener #get system info def getinfo(): global status, hostname, uptime, zerotier, prosody, postgres, tailscale, disk hostname = subprocess.check_output(['hostname']).decode().strip() uptime = subprocess.check_output(['uptime', '-p']).decode().strip() #systemd-only services zerotier = subprocess.Popen("sudo systemctl status zerotier-one | grep 'Active'", shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() prosody = subprocess.Popen("sudo systemctl status prosody | grep 'Active'", shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() postgres = subprocess.Popen("sudo systemctl status postgresql | grep 'Active'", shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() tailscale = subprocess.Popen("sudo systemctl status tailscaled | grep 'Active'", shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() nginx = subprocess.Popen("sudo systemctl status nginx | grep 'Active'", shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() disk = subprocess.check_output(['df', '-h']).decode().strip() if hostname == 'unknown': status = 'offline' else: status = 'online' return hostname, uptime, zerotier, prosody, postgres, tailscale, nginx, disk #function to ping tailscale nodes def ping_node(node, hostname): ping = subprocess.Popen(f"ping {node} -c 1 | grep '1 packets'", shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() if '1 received' in ping: reach.append(f'{node}/{hostname} is reachable') else: reach.append(f'{node}/{hostname} is unreachable') #ping tailscale nodes def check_tailscale(): global nodes, hostnames, reach, threads, ping nodes_output = subprocess.Popen("tailscale status | grep '100'", shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() nodes = [line.split()[0] for line in nodes_output.split('\n') if line] hostnames = [line.split()[1] for line in nodes_output.split('\n') if line] for node, hostname in zip(nodes, hostnames): thread = threading.Thread(target=ping_node, args=(node, hostname)) threads.append(thread) thread.start() for thread in threads: thread.join() return reach #restart services def restart_service(service): print(f'restarting {service}...') subprocess.Popen(f'sudo systemctl restart {service}', shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() sleep(3) service_status = subprocess.Popen(f'sudo systemctl status {service} | grep "Active"', shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() status_message = f'{service} restarted! status: {service_status}' return status_message #restart services menu def restart_menu(): keyboard = [ [telebot.types.InlineKeyboardButton('zerotier-one', callback_data='zerotier-one')], [telebot.types.InlineKeyboardButton('prosody', callback_data='prosody')], [telebot.types.InlineKeyboardButton('postgresql', callback_data='postgresql')], [telebot.types.InlineKeyboardButton('tailscaled', callback_data='tailscaled')], [telebot.types.InlineKeyboardButton('nginx', callback_data='nginx')], [telebot.types.InlineKeyboardButton('cancel', callback_data='cancel')] ] reply_markup = telebot.types.InlineKeyboardMarkup(keyboard) return reply_markup #callback query handler @bot.callback_query_handler(func=lambda call: True) def callback_query(call): service = call.data if service != 'cancel': status_message = restart_service(service) bot.send_message(call.message.chat.id, status_message) else: bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=None) bot.send_message(call.message.chat.id, 'canceled') def reboot(): subprocess.Popen('sudo reboot', shell=True, stdout=subprocess.PIPE).stdout.read().decode().strip() #debug handler def check(): global status, hostname, uptime, zerotier, prosody, postgres, tailscale, nginx, disk getinfo() print('system status:', status) print('hostname:', hostname) print('uptime:', uptime) print('zerotier:', zerotier) print('prosody:', prosody) print('postgres:', postgres) print('tailscale:', tailscale) print('disk:', disk) return status, hostname, uptime, zerotier, prosody, postgres, tailscale, nginx, disk #message handling @bot.message_handler(commands=['start', 'help', 'status', 'restart', 'reboot', 'ping']) def handle(message): if message.text == '/start': bot.reply_to(message, 'lainmonitor v1.0 --- standing by...') elif message.text == '/help': bot.reply_to(message, 'commands: /start, /help, /status, /restart, /reboot, /ping') elif message.text == '/status': check() status_message = f'hostname: {hostname}\nsystem status: {status}\nuptime: {uptime}\nzerotier: {zerotier}\nprosody: {prosody}\npostgres: {postgres}\ntailscale: {tailscale}\nnginx: {nginx}' bot.reply_to(message, status_message) bot.reply_to(message, f'filesystem info for {hostname}: \n\n{disk}') elif message.text == f'/restart {host}': if message.text == f'/restart {host}' and str(message.from_user.id) in authorized_users: bot.send_message(message.chat.id, 'select a service to restart:', reply_markup=restart_menu()) else: bot.reply_to(message, 'you are not authorized to restart services on this host') elif message.text == f'/reboot {host}': if message.text == f'/reboot {host}' and str(message.from_user.id) in authorized_users: bot.reply_to(message, f'rebooting {host}...') reboot() else: bot.reply_to(message, 'you are not authorized to reboot this host') elif message.text == '/ping': if message.text == f'/restart {host}' and str(message.from_user.id) in authorized_users: check_tailscale() ping_status = '\n'.join(reach) bot.reply_to(message, f'ping status:\n\n{ping_status}') ping_status = '' reach.clear() else: bot.reply_to(message, 'you are not authorized to view ping status') #polling bot.polling()