migrate
This commit is contained in:
@@ -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())
|
||||
@@ -0,0 +1 @@
|
||||
# No external dependencies required.
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"slug": "server-disk-usage-monitor",
|
||||
"kind": "python",
|
||||
"metadata": {
|
||||
"name": "Server Disk Usage Monitor",
|
||||
"description": "Standalone Python monitor that checks local disk usage for one path and sends a Discord warning when usage reaches a configurable threshold.",
|
||||
"tags": [
|
||||
"disk",
|
||||
"monitoring",
|
||||
"discord",
|
||||
"python"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "python",
|
||||
"color": "emerald"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "1.0.0",
|
||||
"source_dep_name": "manual/server-disk-usage-monitor"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Monitor",
|
||||
"name": "monitor",
|
||||
"items": [
|
||||
{
|
||||
"name": "monitor_path",
|
||||
"type": "str",
|
||||
"title": "Monitor Path",
|
||||
"required": true,
|
||||
"default": "/",
|
||||
"description": "Local path passed to `df -P`.",
|
||||
"config": {
|
||||
"placeholder": "/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Alerting",
|
||||
"name": "alerting",
|
||||
"items": [
|
||||
{
|
||||
"name": "warning_percent",
|
||||
"type": "int",
|
||||
"title": "Warning Threshold Percent",
|
||||
"required": true,
|
||||
"default": 80,
|
||||
"description": "Send a warning when disk usage reaches or exceeds this percentage.",
|
||||
"config": {
|
||||
"slider": true,
|
||||
"min": 50,
|
||||
"max": 100,
|
||||
"step": 1,
|
||||
"placeholder": "80"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "discord_webhook_url",
|
||||
"type": "secret",
|
||||
"title": "Discord Webhook URL",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Optional Discord webhook endpoint for warning notifications."
|
||||
},
|
||||
{
|
||||
"name": "discord_username",
|
||||
"type": "str",
|
||||
"title": "Discord Username",
|
||||
"required": false,
|
||||
"default": "Disk Monitor",
|
||||
"description": "Sender name used for Discord webhook messages.",
|
||||
"config": {
|
||||
"placeholder": "Disk Monitor"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user