#!/usr/bin/env python3
"""Generate a retro stats page for Susan.
90s CS professor homepage aesthetic. Pure HTML, no JS.
Regenerate periodically via cron.
"""
import datetime
import os
import subprocess
import re
OUTPUT = "/var/www/webdav/obsidian/stats.html"
def cmd(c):
try:
return subprocess.check_output(c, shell=True, stderr=subprocess.DEVNULL, timeout=10).decode().strip()
except:
return ""
def get_uptime():
up = cmd("uptime -p")
return up.replace("up ", "") if up else "unknown"
def get_uptime_since():
return cmd("uptime -s")
def get_load():
load = cmd("cat /proc/loadavg")
return load.split()[:3] if load else ["?", "?", "?"]
def get_memory():
mem = cmd("free -h | grep Mem")
parts = mem.split()
if len(parts) >= 7:
return {"total": parts[1], "used": parts[2], "free": parts[3], "available": parts[6]}
return {"total": "?", "used": "?", "free": "?", "available": "?"}
def get_disk():
disks = []
for line in cmd("df -h /disks /home 2>/dev/null").split("\n")[1:]:
parts = line.split()
if len(parts) >= 6:
disks.append({"fs": parts[0], "size": parts[1], "used": parts[2], "avail": parts[3], "pct": parts[4], "mount": parts[5]})
return disks
def get_services():
services = []
lines = cmd("systemctl list-units --type=service --state=running --no-pager --no-legend").split("\n")
targets = ["jellyfin", "navidrome", "qbittorrent", "sonarr", "radarr", "lidarr",
"readarr", "prowlarr", "slskd", "nginx", "audiobookshelf", "openclaw-gateway"]
for line in lines:
for t in targets:
if t in line.lower():
name = line.split()[0].replace(".service", "")
services.append(name)
return sorted(services)
def get_media_counts():
films = int(cmd("find /disks/Plex/Films -maxdepth 1 -type d | wc -l") or 1) - 1
tv = int(cmd("find /disks/Plex/TV -maxdepth 1 -type d | wc -l") or 1) - 1
anime = int(cmd("find /disks/Plex/Anime -maxdepth 1 -type d | wc -l") or 1) - 1
tracks = int(cmd("find /disks/Plex/Music -type f \\( -name '*.flac' -o -name '*.mp3' -o -name '*.ogg' -o -name '*.opus' \\) | wc -l") or 0)
artists = int(cmd("ls -1d /disks/Plex/Music/*/ 2>/dev/null | grep -v -E 'venv|lib|bin|data|include|_|pyvenv' | wc -l") or 0)
return {"films": films, "tv": tv, "anime": anime, "tracks": tracks, "artists": artists}
def get_cpu():
model = cmd("grep 'model name' /proc/cpuinfo | head -1 | cut -d: -f2").strip()
cores = cmd("nproc")
return model, cores
def get_packages():
return cmd("dpkg -l | grep '^ii' | wc -l")
def get_kernel():
return cmd("uname -r")
def get_os():
return cmd("grep PRETTY_NAME /etc/os-release | cut -d'\"' -f2")
def get_install_date():
d = cmd("stat -c %w /")
if d and d != "-":
return d.split(".")[0]
return "unknown"
COUNTER_FILE = os.path.join(os.path.dirname(__file__), "..", "data", "visitor_counter.txt")
def get_and_increment_counter():
"""Read and increment a persistent visitor counter."""
os.makedirs(os.path.dirname(COUNTER_FILE), exist_ok=True)
count = 0
if os.path.exists(COUNTER_FILE):
try:
count = int(open(COUNTER_FILE).read().strip())
except:
count = 0
count += 1
with open(COUNTER_FILE, "w") as f:
f.write(str(count))
return count
now = datetime.datetime.now()
uptime = get_uptime()
uptime_since = get_uptime_since()
load = get_load()
mem = get_memory()
disks = get_disk()
services = get_services()
media = get_media_counts()
cpu_model, cpu_cores = get_cpu()
packages = get_packages()
kernel = get_kernel()
os_name = get_os()
install_date = get_install_date()
visitor_count = get_and_increment_counter()
html = f"""
Susan - System Status
Susan — System Status
General Information
| Hostname | susan |
| Operating System | {os_name} |
| Kernel | {kernel} |
| Processor | {cpu_model} ({cpu_cores} cores) |
| Installed Packages | {packages} |
| First Installed | {install_date} |
Uptime & Load
| Uptime | {uptime} |
| Up Since | {uptime_since} |
| Load Average | {load[0]}, {load[1]}, {load[2]} (1, 5, 15 min) |
Memory
| Total | Used | Free | Available |
| {mem['total']} | {mem['used']} | {mem['free']} | {mem['available']} |
Disk Usage
| Mount | Size | Used | Available | Use% |
"""
for d in disks:
pct = int(d["pct"].replace("%", "")) if d["pct"].replace("%", "").isdigit() else 0
cls = "status-warn" if pct > 85 else "status-ok"
html += f'| {d["mount"]} | {d["size"]} | {d["used"]} | {d["avail"]} | {d["pct"]} |
\n'
html += f"""
Running Services
| Service | Status |
"""
for s in services:
html += f'| {s} | running |
\n'
html += f"""
Media Library
| Category | Count |
| Films | {media['films']} |
| TV Shows | {media['tv']} |
| Anime | {media['anime']} |
| Music Artists | {media['artists']} |
| Music Tracks | {media['tracks']} |
"""
os.makedirs(os.path.dirname(OUTPUT), exist_ok=True)
with open(OUTPUT, "w") as f:
f.write(html)
print(f"Generated stats page: {OUTPUT}")
print(f" Uptime: {uptime}")
print(f" Services: {len(services)}")
print(f" Films: {media['films']}, TV: {media['tv']}, Anime: {media['anime']}")
print(f" Music: {media['tracks']} tracks, {media['artists']} artists")