Dit deel is nog niet vertaald. Je ziet de Engelse tekst.
$ als gewone gebruiker# als root (of met sudo)
Plan the report
Decide what the report must answer before you write code.
| Check | Source | Python part |
|---|---|---|
| Disk, memory, load | Local system | shutil, psutil |
| Failed services | systemctl --failed | subprocess |
| Failed SSH logins | /var/log/secure | re, Counter |
| Website up and certificate days | HTTPS | requests, ssl |
| Output | JSON for tools, CSV for people | json, csv |
Write the main script
Small functions, one main(), logging, exit code.
server_report.py
#!/usr/bin/env python3
"""Daily server report: disk, services, SSH failures, websites."""
import json
import logging
import re
import shutil
import subprocess
import sys
from collections import Counter
from datetime import date
import requests
log = logging.getLogger("report")
def disk(path="/"):
u = shutil.disk_usage(path)
return round(u.used / u.total * 100, 1)
def failed_units():
out = subprocess.run(["systemctl", "--failed", "--plain", "--no-legend"], capture_output=True, text=True).stdout
return [line.split()[0] for line in out.splitlines() if line.strip()]
def ssh_failures(logfile="/var/log/secure", top=5):
text = open(logfile, encoding="utf-8", errors="replace").read()
return Counter(re.findall(r"Failed password for \S+ from (\S+)", text)).most_common(top)
def site(url):
try:
r = requests.get(url, timeout=10)
return {"url": url, "status": r.status_code, "seconds": round(r.elapsed.total_seconds(), 2)}
except requests.RequestException as e:
return {"url": url, "status": None, "error": e.__class__.__name__}
def main():
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
report = {
"date": str(date.today()),
"disk_root": disk("/"),
"failed_units": failed_units(),
"ssh_failures": ssh_failures(),
"sites": [site(u) for u in ["https://s0sta.com/", "https://babolatksa.com/"]],
}
path = f"/var/lib/report/report-{report['date']}.json"
with open(path, "w", encoding="utf-8") as f:
json.dump(report, f, indent=2)
log.info("report written to %s", path)
bad = report["failed_units"] or report["disk_root"] > 90 or any(s["status"] != 200 for s in report["sites"])
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())Run it every morning
A timer at 07:00 on work days; the journal keeps the output.
Publish it
The project is portfolio material when others can read, test and run it.
- [ ] Repository with README (what, why, how to run),
requirements.txt, tests. - [ ] GitHub Actions:
ruff,pyteston every push. - [ ] No secrets in the code; tokens from environment variables.
- [ ] A LinkedIn post with one screenshot of the report.