#!/usr/bin/env python3 import json import subprocess import os from http.server import BaseHTTPRequestHandler, HTTPServer # --- CONFIGURATION FLAGS --- PORT = 8080 IFACE = "bond0" # Toggle features True/False depending on your setup ENABLE_ZFS = False ZPOOL = "rpool" ENABLE_PVE_STATS = True # Set to False if not running on Proxmox # --------------------------- def run_cmd(cmd): """Runs a shell command and silently returns None if it fails.""" try: return subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL).decode('utf-8').strip() except Exception: return None class MetricsHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path == '/api/homelab': self.send_response(200) self.send_header('Content-type', 'application/json') self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() data = {} try: # 1. Network & CPU (Includes 1-second delay for accuracy) rx1 = int(run_cmd(f"cat /sys/class/net/{IFACE}/statistics/rx_bytes") or 0) tx1 = int(run_cmd(f"cat /sys/class/net/{IFACE}/statistics/tx_bytes") or 0) cpu_idle_str = run_cmd("vmstat 1 2 | tail -1 | awk '{print $15}'") cpu_idle = float(cpu_idle_str) if cpu_idle_str else 100.0 data["cpu"] = round(100.0 - cpu_idle, 1) rx2 = int(run_cmd(f"cat /sys/class/net/{IFACE}/statistics/rx_bytes") or 0) tx2 = int(run_cmd(f"cat /sys/class/net/{IFACE}/statistics/tx_bytes") or 0) data["rx_mb"] = round((rx2 - rx1) / (1024 * 1024), 2) data["tx_mb"] = round((tx2 - tx1) / (1024 * 1024), 2) # 2. RAM Usage ram_str = run_cmd("free | grep Mem | awk '{print $3/$2 * 100.0}'") data["ram"] = round(float(ram_str), 1) if ram_str else 0.0 # 3. Load Average (Pure Python) load1, load5, load15 = os.getloadavg() data["load_avg"] = [round(load1, 2), round(load5, 2), round(load15, 2)] # 4. Uptime in Days (Pure Python) with open('/proc/uptime', 'r') as f: uptime_seconds = float(f.readline().split()[0]) data["uptime_days"] = round(uptime_seconds / 86400, 1) # 5. Root Disk Usage (Pure Python, lightning fast) st = os.statvfs('/') total = st.f_blocks * st.f_frsize free = st.f_bavail * st.f_frsize data["root_disk_pct"] = round(((total - free) / total) * 100.0, 1) # 6. CPU Temperature (Reads native thermal zone) try: with open('/sys/class/thermal/thermal_zone0/temp', 'r') as f: data["temp_c"] = round(int(f.read()) / 1000.0, 1) except Exception: data["temp_c"] = "N/A" # Fails safely if hardware doesn't expose it # 7. ZFS Array (Optional) if ENABLE_ZFS: zfs_alloc = run_cmd(f"zpool list -H -p -o allocated {ZPOOL}") zfs_size = run_cmd(f"zpool list -H -p -o size {ZPOOL}") if zfs_alloc and zfs_size: data["zfs_pct"] = round((float(zfs_alloc) / float(zfs_size)) * 100.0, 1) else: data["zfs_pct"] = "Error" # 8. Proxmox VM/LXC Counts (Optional) if ENABLE_PVE_STATS: data["vms_running"] = int(run_cmd("qm list | grep running | wc -l") or 0) data["lxcs_running"] = int(run_cmd("pct list | grep running | wc -l") or 0) # Send payload self.wfile.write(json.dumps(data).encode()) except Exception as e: self.wfile.write(json.dumps({"error": str(e)}).encode()) else: self.send_response(404) self.end_headers() if __name__ == '__main__': server = HTTPServer(('0.0.0.0', PORT), MetricsHandler) print(f"Starting homelab API on port {PORT}...") server.serve_forever()