from __future__ import annotations import shutil import subprocess from dataclasses import dataclass from pathlib import Path from .system_info import SystemInfo @dataclass(frozen=True) class StatusItem: category: str label: str ok: bool detail: str def _command_exists(command: str) -> bool: return shutil.which(command) is not None def _run(command: list[str]) -> subprocess.CompletedProcess[str]: return subprocess.run(command, text=True, capture_output=True, check=False) def _service_active(service: str) -> bool: if not _command_exists("systemctl"): return False return _run(["systemctl", "is-active", service]).returncode == 0 def _binary_status(category: str, label: str, command: str) -> StatusItem: exists = _command_exists(command) return StatusItem(category=category, label=label, ok=exists, detail="installé" if exists else "absent") def collect_status(system: SystemInfo) -> list[StatusItem]: maintenance: list[StatusItem] = [] security: list[StatusItem] = [] services: list[StatusItem] = [] performance: list[StatusItem] = [] poste: list[StatusItem] = [] p10k_path = system.target_home / ".p10k.zsh" poste.append(StatusItem("Poste", "Config p10k", p10k_path.exists(), str(p10k_path if p10k_path.exists() else "absente"))) unattended_ok = False unattended_detail = "non configuré" if system.package_manager == "apt-get": unattended_ok = _service_active("unattended-upgrades.service") and Path("/etc/apt/apt.conf.d/20auto-upgrades").exists() unattended_detail = "service actif" if unattended_ok else "service inactif" elif system.package_manager in {"dnf", "yum"}: unattended_ok = _service_active("dnf-automatic.timer") unattended_detail = "timer actif" if unattended_ok else "timer inactif" maintenance.append(StatusItem("Maintenance", "MAJ auto", unattended_ok, unattended_detail)) logrotate_ok = Path("/etc/logrotate.d/securecheck").exists() maintenance.append(StatusItem("Maintenance", "Rotation logs", logrotate_ok, "config présente" if logrotate_ok else "config absente")) zram_ok = _service_active("securecheck-zram.service") or ("zram" in _run(["swapon", "--show"]).stdout if _command_exists("swapon") else False) performance.append(StatusItem("Performance", "zram", zram_ok, "actif" if zram_ok else "inactif")) if _command_exists("ufw"): ufw_result = _run(["ufw", "status"]) firewall_ok = "Status: active" in ufw_result.stdout firewall_detail = "ufw actif" if firewall_ok else "ufw inactif" elif _command_exists("firewall-cmd"): firewall_ok = _service_active("firewalld.service") firewall_detail = "firewalld actif" if firewall_ok else "firewalld inactif" else: firewall_ok = False firewall_detail = "pare-feu absent" security.append(StatusItem("Sécurité", "Firewall", firewall_ok, firewall_detail)) apparmor_active = _service_active("apparmor") or _command_exists("apparmor_status") security.append(StatusItem("Sécurité", "AppArmor", apparmor_active, "activé" if apparmor_active else "inactif")) clamav_active = _service_active("clamav-daemon") or _service_active("clamav-freshclam") security.append(StatusItem("Sécurité", "ClamAV", clamav_active, "actif" if clamav_active else "inactif")) wazuh_active = _service_active("wazuh-agent") security.append(StatusItem("Sécurité", "Wazuh agent", wazuh_active, "actif" if wazuh_active else "inactif")) aide_timer_active = _service_active("aidecheck.timer") aide_db_exists = Path("/var/lib/aide/aide.db").exists() aide_ok = aide_timer_active or aide_db_exists detail = "timer actif" if aide_timer_active else "db présent" if aide_db_exists else "inactif" security.append(StatusItem("Sécurité", "AIDE", aide_ok, detail)) security.append(_binary_status("Sécurité", "Lynis", "lynis")) security.append(_binary_status("Sécurité", "rkhunter", "rkhunter")) security.append(_binary_status("Sécurité", "chkrootkit", "chkrootkit")) docker_active = _command_exists("docker") and _service_active("docker.service") fail2ban_active = _command_exists("fail2ban-client") and _service_active("fail2ban.service") services.append(StatusItem("Services", "Docker", docker_active, "actif" if docker_active else "inactif")) services.append(StatusItem("Services", "fail2ban", fail2ban_active, "actif" if fail2ban_active else "inactif")) avahi_running = _command_exists("avahi-daemon") and _service_active("avahi-daemon") services.append(StatusItem("Services", "Avahi", not avahi_running, "désactivé" if not avahi_running else "actif")) poste.extend([ _binary_status("Poste", "zsh", "zsh"), _binary_status("Poste", "git", "git"), _binary_status("Poste", "curl", "curl"), _binary_status("Poste", "ncdu", "ncdu"), _binary_status("Poste", "needrestart", "needrestart"), _binary_status("Poste", "htop", "htop"), _binary_status("Poste", "nmon", "nmon"), _binary_status("Poste", "duf", "duf"), _binary_status("Poste", "net-tools", "ifconfig"), ]) ordered = maintenance + security + performance + services + poste return ordered