Bot & Automation
vps-monitor-bot
/root/hermes-projects/vps-monitor-bot
bot.py
text
#!/usr/bin/env python3
"""
VPS Monitor Telegram Bot
Monitors VPS via Telegram. Owner-only access.
Stack: python-telegram-bot v20+, python-dotenv, psutil, subprocess (Docker)
"""
import os, subprocess, urllib.request
from datetime import datetime
import psutil
from dotenv import load_dotenv
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
OWNER_CHAT_ID = os.getenv("OWNER_CHAT_ID")
if not BOT_TOKEN: raise ValueError("BOT_TOKEN not set in .env")
if not OWNER_CHAT_ID: raise ValueError("OWNER_CHAT_ID not set in .env")
try: OWNER_CHAT_ID = int(OWNER_CHAT_ID)
except ValueError: raise ValueError("OWNER_CHAT_ID must be integer")
# Security decorator
def owner_only(f):
async def w(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
if update.effective_user.id != OWNER_CHAT_ID:
await update.message.reply_text("\U0001f6ab *Akses ditolak.*\nBot ini hanya untuk owner.", parse_mode="Markdown")
return
return await f(update, ctx)
return w
# Helpers
def public_ip():
for u in ("https://api.ipify.org?format=text","https://ifconfig.me/ip"):
try:
with urllib.request.urlopen(u,timeout=5) as r: return r.read().decode().strip()
except: continue
return "N/A"
def uptime_str():
try:
with open("/proc/uptime") as f: s=float(f.readline().split()[0])
except: return "N/A"
d=int(s//86400); h=int((s%86400)//3600); m=int((s%3600)//60)
p=[];
if d: p.append(f"{d}d")
if h: p.append(f"{h}h")
p.append(f"{m}m")
return " ".join(p)
def disk_str():
u=psutil.disk_usage("/")
return f"{u.used/1024**3:.1f} GB / {u.total/1024**3:.1f} GB ({u.percent}%)"
def docker_cmd(args, timeout=15):
try:
r=subprocess.run(["docker"]+args, capture_output=True, text=True, timeout=timeout)
return (r.returncode==0, r.stdout.strip() if r.returncode==0 else f"Docker error:\n```\n{r.stderr.strip()}\n```")
except FileNotFoundError: return (False, "Docker tidak terinstall.")
except subprocess.TimeoutExpired: return (False, "Docker command timed out.")
except Exception as e: return (False, f"Error: {e}")
def docker_ps():
return docker_cmd(["ps","--format","{{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}"])
def docker_logs(c, tail=50):
ok,out=docker_cmd(["ps","-a","--format","{{.Names}}"])
if not ok: return (False,out)
names=[n.strip() for n in out.split("\n") if n.strip()]
if c not in names:
return (False, f"Container `{c}` tidak ditemukan.\nTersedia: {', '.join(names) if names else '(tidak ada)'}")
return docker_cmd(["logs","--tail",str(tail),c])
def docker_restart(c):
ok,out=docker_cmd(["restart",c],timeout=30)
if ok: return (True, f"\u2705 Container `{c}` berhasil direstart.")
return (ok,out)
# Commands
@owner_only
async def cmd_start(update, ctx):
n=update.effective_user.first_name or "Owner"
await update.message.reply_text(f"\U0001f44b *Halo, {n}!*\n\nBot monitoring VPS via Telegram.\nHanya owner yang bisa menggunakannya.\n\nKetik /help untuk daftar perintah.", parse_mode="Markdown")
@owner_only
async def cmd_help(update, ctx):
await update.message.reply_text("\U0001f4cb *Daftar Perintah*\n\n/start \u2014 Pesan sambutan\n/help \u2014 Daftar ini\n/status \u2014 Info sistem (CPU, RAM, Disk, Uptime, IP)\n/docker \u2014 Daftar container Docker\n/logs\\_n8n \u2014 50 baris log n8n\n/restart\\_n8n \u2014 Restart n8n\n\n\U0001f4cc Semua hanya untuk owner.", parse_mode="Markdown")
@owner_only
async def cmd_status(update, ctx):
await update.message.reply_text("\u23f3 Mengambil data...")
h=os.uname().nodename; u=uptime_str(); cpu=psutil.cpu_percent(interval=1)
cores=psutil.cpu_count(); ram=psutil.virtual_memory(); d=disk_str()
load=os.getloadavg(); ip=public_ip()
msg=(f"\U0001f5a5 *Status VPS*\n\n"
f"\u2022 *Hostname*: `{h}`\n\u2022 *Uptime*: {u}\n"
f"\u2022 *CPU*: {cpu:.1f}% ({cores} core)\n"
f"\u2022 *RAM*: {ram.used/1024**3:.1f} GB / {ram.total/1024**3:.1f} GB ({ram.percent:.1f}%)\n"
f"\u2022 *Disk*: {d}\n\u2022 *Load*: {load[0]:.2f} / {load[1]:.2f} / {load[2]:.2f}\n"
f"\u2022 *Public IP*: `{ip}`\n\n\U0001f550 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
await update.message.reply_text(msg, parse_mode="Markdown")
@owner_only
async def cmd_docker(update, ctx):
await update.message.reply_text("\u23f3 Mengambil data Docker...")
ok,out=docker_ps()
if not ok: await update.message.reply_text(f"\u274c {out}", parse_mode="Markdown"); return
if not out: await update.message.reply_text("\U0001f4e6 Tidak ada container berjalan."); return
lines=out.strip().split("\n")
table="\U0001f4e6 *Docker Containers*\n\n"
for i,line in enumerate(lines,1):
p=line.split("\t")
table+=(f"*{i}. {p[0] if len(p)>0 else '-'}*\n"
f" Status: `{p[1] if len(p)>1 else '-'}`\n"
f" Image: `{p[2] if len(p)>2 else '-'}`\n"
f" Port: `{p[3] if len(p)>3 else '-'}`\n\n")
await update.message.reply_text(table, parse_mode="Markdown")
@owner_only
async def cmd_logs_n8n(update, ctx):
await update.message.reply_text("\u23f3 Mengambil log n8n...")
ok,out=docker_logs("n8n",50)
if not ok: await update.message.reply_text(f"\u274c {out}", parse_mode="Markdown"); return
if not out: await update.message.reply_text("\U0001f4c4 Log n8n kosong."); return
if len(out)>3800: out=out[-3800:]; h="\U0001f4c4 *Log n8n (50 baris)* \u2014 3800 karakter terakhir\n\n"
else: h="\U0001f4c4 *Log n8n (50 baris terakhir)*\n\n"
await update.message.reply_text(h+f"```\n{out}\n```", parse_mode="Markdown")
@owner_only
async def cmd_restart_n8n(update, ctx):
await update.message.reply_text("\u23f3 Merestart n8n...")
ok,msg=docker_restart("n8n")
await update.message.reply_text(msg, parse_mode="Markdown")
def main():
print("Starting VPS Monitor Bot...")
app=Application.builder().token(BOT_TOKEN).build()
for c,h in [("start",cmd_start),("help",cmd_help),("status",cmd_status),
("docker",cmd_docker),("logs_n8n",cmd_logs_n8n),("restart_n8n",cmd_restart_n8n)]:
app.add_handler(CommandHandler(c,h))
print("Bot running. Ctrl+C to stop.")
app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__=="__main__": main()