127 lines
No EOL
5.4 KiB
Python
127 lines
No EOL
5.4 KiB
Python
#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
|
|
import telebot
|
|
|
|
#define the variables
|
|
status, hostname, uptime, zerotier, prosody, postgres, tailscale, disk, ping = 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown', 'unknown'
|
|
nodes, hostnames, reach, threads = [], [], [], []
|
|
|
|
#change this to your hostname
|
|
host = 'lainlounge'
|
|
|
|
#load the token
|
|
token = open('.env', 'r').read().strip()
|
|
|
|
#bot init
|
|
bot = telebot.TeleBot(token)
|
|
|
|
#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()
|
|
disk = subprocess.check_output(['df', '-h']).decode().strip()
|
|
if hostname == 'unknown':
|
|
status = 'offline'
|
|
else:
|
|
status = 'online'
|
|
return hostname, uptime, zerotier, prosody, postgres, tailscale, 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):
|
|
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()
|
|
message = f'{service} restarted! status: {service_status}'
|
|
|
|
#restart services menu
|
|
def restart_menu():
|
|
keyboard = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
|
|
zerotier_button = telebot.types.KeyboardButton('zerotier-one', callback_data='zerotier')
|
|
prosody_button = telebot.types.KeyboardButton('prosody', callback_data='prosody')
|
|
postgres_button = telebot.types.KeyboardButton('postgresql', callback_data='postgresql')
|
|
tailscale_button = telebot.types.KeyboardButton('tailscaled', callback_data='tailscaled')
|
|
|
|
return keyboard
|
|
|
|
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, 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, 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, /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}'
|
|
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 host == hostname:
|
|
bot.send_message(message.chat.id, 'select a service to restart:', reply_markup=restart_menu())
|
|
else:
|
|
pass
|
|
elif message.text == f'/reboot {hostname}':
|
|
bot.reply_to(message, f'rebooting {host}...')
|
|
reboot()
|
|
elif message.text == '/ping':
|
|
check_tailscale()
|
|
ping_status = '\n'.join(reach)
|
|
bot.reply_to(message, f'ping status:\n\n{ping_status}')
|
|
ping_status = ''
|
|
reach.clear()
|
|
|
|
#polling
|
|
bot.polling() |