This commit is contained in:
Mike McFetridge
2026-07-20 09:23:17 -04:00
parent c1315882da
commit 72272e4006
3179 changed files with 562960 additions and 14 deletions
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import socket
import subprocess
import sys
import urllib.request
MONITOR_PATH = r"""<< monitor_path >>"""
WARNING_PERCENT = int(r"""<< warning_percent >>""")
DISCORD_WEBHOOK_URL = r"""<< discord_webhook_url >>"""
DISCORD_USERNAME = r"""<< discord_username >>"""
def fetch_disk_usage() -> dict[str, str | int]:
path = MONITOR_PATH.strip() or "/"
if not 1 <= WARNING_PERCENT <= 100:
raise SystemExit("warning_percent must be between 1 and 100.")
result = subprocess.run(
["df", "-P", path],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
detail = result.stderr.strip() or result.stdout.strip() or "unknown local error"
raise RuntimeError(f"Local df command failed with exit code {result.returncode}: {detail}")
lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
if len(lines) < 2:
raise RuntimeError(f"Unexpected df output: {result.stdout!r}")
parts = lines[-1].split(maxsplit=5)
if len(parts) != 6:
raise RuntimeError(f"Unable to parse df output line: {lines[-1]!r}")
usage_raw = parts[4]
if not usage_raw.endswith("%"):
raise RuntimeError(f"Unable to parse disk usage percentage: {usage_raw!r}")
return {
"filesystem": parts[0],
"blocks_kb": parts[1],
"used_kb": parts[2],
"available_kb": parts[3],
"usage_percent": int(usage_raw[:-1]),
"mounted_on": parts[5],
"path": path,
"host": socket.gethostname(),
}
def send_discord_warning(result: dict[str, str | int]) -> bool:
webhook_url = DISCORD_WEBHOOK_URL.strip()
if not webhook_url:
return False
content = "\n".join(
[
"Disk usage warning",
f"Host: {result['host']}",
f"Path: {result['path']}",
f"Mounted on: {result['mounted_on']}",
f"Filesystem: {result['filesystem']}",
f"Usage: {result['usage_percent']}% (threshold {WARNING_PERCENT}%)",
f"Available: {result['available_kb']} KB",
]
)
payload = json.dumps(
{
"username": DISCORD_USERNAME.strip() or "Disk Monitor",
"content": content[:1900],
}
).encode("utf-8")
request = urllib.request.Request(
webhook_url,
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=10) as response: # noqa: S310
if response.status >= 400:
raise RuntimeError(f"Discord webhook returned HTTP {response.status}")
return True
def main() -> int:
result = fetch_disk_usage()
usage_percent = int(result["usage_percent"])
status = "warn" if usage_percent >= WARNING_PERCENT else "pass"
alert_sent = False
if status == "warn":
alert_sent = send_discord_warning(result)
print(f"Server Disk Usage Monitor: {status.upper()}")
print(f"Host: {result['host']}")
print(f"Path: {result['path']}")
print(f"Mounted on: {result['mounted_on']}")
print(f"Filesystem: {result['filesystem']}")
print(f"Usage: {usage_percent}%")
print(f"Threshold: {WARNING_PERCENT}%")
print(f"Available: {result['available_kb']} KB")
print(f"Discord alert sent: {'yes' if alert_sent else 'no'}")
return 0
if __name__ == "__main__":
sys.exit(main())