Add system and certificate telemetry collectors
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
CERT_ROOT = Path(
|
||||||
|
"/opt/docker/caddy/data/caddy/certificates/"
|
||||||
|
"acme-v02.api.letsencrypt.org-directory"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def collect():
|
||||||
|
certificates = []
|
||||||
|
|
||||||
|
for cert_file in sorted(CERT_ROOT.glob("*/*.crt")):
|
||||||
|
result = subprocess.run(
|
||||||
|
["openssl", "x509", "-in", str(cert_file), "-noout", "-enddate"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
expires_text = result.stdout.strip().removeprefix("notAfter=")
|
||||||
|
expires = datetime.strptime(
|
||||||
|
expires_text,
|
||||||
|
"%b %d %H:%M:%S %Y %Z",
|
||||||
|
).replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
days_remaining = (expires - datetime.now(timezone.utc)).days
|
||||||
|
|
||||||
|
certificates.append({
|
||||||
|
"name": cert_file.parent.name,
|
||||||
|
"days_remaining": days_remaining,
|
||||||
|
"expires": expires.isoformat(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if not certificates:
|
||||||
|
return {
|
||||||
|
"state": "offline",
|
||||||
|
"summary": "No certificates",
|
||||||
|
"detail": "Caddy certificate files not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
lowest = min(item["days_remaining"] for item in certificates)
|
||||||
|
|
||||||
|
state = "online"
|
||||||
|
if lowest < 30:
|
||||||
|
state = "warning"
|
||||||
|
if lowest < 7:
|
||||||
|
state = "offline"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"state": state,
|
||||||
|
"summary": f"{len(certificates)} certificates",
|
||||||
|
"detail": f"{lowest} days minimum",
|
||||||
|
"minimum_days": lowest,
|
||||||
|
"certificates": certificates,
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
def collect():
|
||||||
|
load_1, _, _ = os.getloadavg()
|
||||||
|
|
||||||
|
memory_total = 0
|
||||||
|
memory_available = 0
|
||||||
|
|
||||||
|
with open("/proc/meminfo", "r", encoding="utf-8") as handle:
|
||||||
|
for line in handle:
|
||||||
|
key, value = line.split(":", 1)
|
||||||
|
kb = int(value.strip().split()[0])
|
||||||
|
|
||||||
|
if key == "MemTotal":
|
||||||
|
memory_total = kb
|
||||||
|
elif key == "MemAvailable":
|
||||||
|
memory_available = kb
|
||||||
|
|
||||||
|
memory_used_pct = round(
|
||||||
|
(1 - memory_available / memory_total) * 100
|
||||||
|
) if memory_total else 0
|
||||||
|
|
||||||
|
disk = shutil.disk_usage("/")
|
||||||
|
disk_used_pct = round(disk.used / disk.total * 100)
|
||||||
|
|
||||||
|
with open("/proc/uptime", "r", encoding="utf-8") as handle:
|
||||||
|
uptime_seconds = int(float(handle.read().split()[0]))
|
||||||
|
|
||||||
|
uptime_days = uptime_seconds // 86400
|
||||||
|
|
||||||
|
state = "online"
|
||||||
|
|
||||||
|
if memory_used_pct >= 85 or disk_used_pct >= 85:
|
||||||
|
state = "warning"
|
||||||
|
|
||||||
|
if memory_used_pct >= 95 or disk_used_pct >= 95:
|
||||||
|
state = "offline"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"state": state,
|
||||||
|
"summary": f"{memory_used_pct}% memory",
|
||||||
|
"detail": f"{disk_used_pct}% disk · {uptime_days} days uptime",
|
||||||
|
"load_1": round(load_1, 2),
|
||||||
|
"memory_used_pct": memory_used_pct,
|
||||||
|
"disk_used_pct": disk_used_pct,
|
||||||
|
"uptime_days": uptime_days,
|
||||||
|
}
|
||||||
+16
-1
@@ -88,6 +88,21 @@ function telemetryStatus(systems) {
|
|||||||
label: systems.docker.state === 'online' ? 'Healthy' : 'Attention',
|
label: systems.docker.state === 'online' ? 'Healthy' : 'Attention',
|
||||||
value: systems.docker.summary,
|
value: systems.docker.summary,
|
||||||
detail: systems.docker.detail
|
detail: systems.docker.detail
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name: 'Certificates',
|
||||||
|
state: systems.certificates.state,
|
||||||
|
label: systems.certificates.state === 'online' ? 'Healthy' : 'Attention',
|
||||||
|
value: systems.certificates.detail,
|
||||||
|
detail: systems.certificates.summary
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'System',
|
||||||
|
state: systems.system.state,
|
||||||
|
label: systems.system.state === 'online' ? 'Healthy' : 'Attention',
|
||||||
|
value: systems.system.summary,
|
||||||
|
detail: systems.system.detail
|
||||||
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from collectors.docker import collect as docker_collect
|
|||||||
from .common import write_json
|
from .common import write_json
|
||||||
from collectors.backup import collect as backup_collect
|
from collectors.backup import collect as backup_collect
|
||||||
from collectors.git import collect as git_collect
|
from collectors.git import collect as git_collect
|
||||||
|
from collectors.certificates import collect as certificates_collect
|
||||||
|
from collectors.system import collect as system_collect
|
||||||
|
|
||||||
OUTPUT = Path("/opt/docker/caddy/site/voyager/data/status.json")
|
OUTPUT = Path("/opt/docker/caddy/site/voyager/data/status.json")
|
||||||
|
|
||||||
@@ -15,6 +17,8 @@ def main():
|
|||||||
"backup": backup_collect(),
|
"backup": backup_collect(),
|
||||||
"git": git_collect(),
|
"git": git_collect(),
|
||||||
"docker": docker_collect(),
|
"docker": docker_collect(),
|
||||||
|
"certificates": certificates_collect(),
|
||||||
|
"system": system_collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user