first commit

This commit is contained in:
stephan
2025-06-29 07:39:33 +02:00
commit 4ed4d69b21
20 changed files with 2754 additions and 0 deletions

375
sysinfo.py Normal file
View File

@ -0,0 +1,375 @@
# sysinfo.py (angepasst ohne cpuinfo und distro)
# Version : 0.1.2 (aktualisiert)
import os
import sys
import subprocess
import json
import psutil
import platform
import socket
import logging
import argparse
from datetime import datetime
# Nur versuchen zu importieren, wenn vorhanden
try:
import GPUtil
gpu_available = True
except ImportError:
gpu_available = False
# Versuche cpuinfo zu importieren
try:
import cpuinfo
cpuinfo_available = True
except ImportError:
cpuinfo_available = False
# Versuche distro zu importieren
try:
import distro
distro_available = True
except ImportError:
distro_available = False
# Globaler Debug-Status
_debug_mode_global = False
def set_debug_mode(mode: bool):
global _debug_mode_global
_debug_mode_global = mode
# Farben für Debug-Logging
class LogColors:
INFO = "\033[94m"
DEBUG = "\033[92m"
WARNING = "\033[93m"
ERROR = "\033[91m"
RESET = "\033[0m"
# Logging einrichten
LOG_FILE = os.path.expanduser("~/hardware_info.log")
if not logging.getLogger().handlers: # Verhindert doppelte Handler bei --reload
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
def log_message(level, message, debug_mode=False): # args.debug wird hier nicht direkt übergeben
"""Loggt Nachrichten mit Farben für die CLI, falls Debug aktiv ist."""
if level == "info":
logging.info(message)
if debug_mode: # Verwendet den lokalen debug_mode Parameter
print(f"{LogColors.INFO}[INFO]{LogColors.RESET} {message}")
elif level == "debug":
logging.debug(message)
if debug_mode:
print(f"{LogColors.DEBUG}[DEBUG]{LogColors.RESET} {message}")
elif level == "warning":
logging.warning(message)
if debug_mode:
print(f"{LogColors.WARNING}[WARNING]{LogColors.RESET} {message}")
elif level == "error":
logging.error(message)
if debug_mode:
print(f"{LogColors.ERROR}[ERROR]{LogColors.RESET} {message}")
def _get_last_login_linux(username, debug_mode=False):
"""
Ermittelt den letzten Login-Zeitpunkt für einen Benutzer unter Linux via 'lastlog'.
Gibt einen formatierten String, "Nie", "N/A" oder "Unbekannt" zurück.
"""
try:
# Erzwinge C-Locale für eine konsistente, nicht-lokalisierte Ausgabe
env = os.environ.copy()
env['LC_ALL'] = 'C'
# Führe 'lastlog' für den spezifischen Benutzer aus
result = subprocess.run(
['lastlog', '-u', username],
capture_output=True,
text=True,
check=False, # Nicht abbrechen, wenn der Befehl fehlschlägt
env=env
)
# Wenn der Befehl fehlschlägt oder keine Ausgabe hat, abbrechen
if result.returncode != 0 or not result.stdout:
return "N/A"
lines = result.stdout.strip().split('\n')
# Erwarte Header + Benutzerzeile
if len(lines) < 2:
return "N/A"
header = lines[0]
user_line = lines[1]
if "**Never logged in**" in user_line:
return "Nie"
# Finde die Startposition der "Latest"-Spalte im Header
latest_pos = header.find("Latest")
if latest_pos == -1:
return "Unbekannt (Formatfehler)"
# Extrahiere den Zeitstempel-String
last_login_str = user_line[latest_pos:].strip()
# Versuche, den String in ein sauberes Datumsformat zu parsen
try:
# Beispiel: "Tue May 21 10:00:15 +0200 2024"
parts = last_login_str.split()
# Baue einen String, den datetime.strptime parsen kann
date_str_to_parse = f"{parts[5]} {parts[1]} {parts[2]} {parts[3]}" # "2024 May 21 10:00:15"
dt_object = datetime.strptime(date_str_to_parse, '%Y %b %d %H:%M:%S')
return dt_object.strftime('%Y-%m-%d %H:%M:%S')
except (ValueError, IndexError):
# Wenn das Parsen fehlschlägt, gib den Originalstring zurück
return last_login_str
except FileNotFoundError:
log_message("warning", "Befehl 'lastlog' nicht gefunden. Kann letzte Logins nicht ermitteln.", debug_mode)
return "N/A"
except Exception as e:
log_message("debug", f"Fehler beim Ermitteln des letzten Logins für {username}: {e}", debug_mode)
return "N/A"
def get_user_accounts(debug_mode=False):
"""
Ermittelt alle lokalen Benutzerkonten und ihren Anmeldestatus,
inklusive Login-Zeit oder letztem Login.
"""
log_message("info", "Sammle Benutzerkonten-Informationen...", debug_mode)
all_users = set()
# Aktive Benutzer und deren Login-Zeiten erfassen
active_users_info = {user.name: user.started for user in psutil.users()}
active_users = set(active_users_info.keys())
system_type = platform.system()
if system_type == "Linux":
try:
with open("/etc/passwd", "r") as f:
for line in f:
if not line.startswith(('#', '\n')): # Ignoriere Kommentare und leere Zeilen
parts = line.split(':')
if len(parts) > 0:
username = parts[0]
# Ignoriere Systembenutzer, die kein Login-Shell haben
if not (username.startswith("_") or username.startswith("nobody") or username.startswith("systemd") or username.endswith("bus")):
if "/bin/false" not in line and "/sbin/nologin" not in line:
all_users.add(username)
except Exception as e:
log_message("warning", f"Konnte /etc/passwd nicht lesen: {str(e)}", debug_mode)
# Fallback, falls /etc/passwd nicht zugreifbar ist
all_users.update(active_users)
elif system_type == "Windows":
# Unter Windows ist es komplexer, alle lokalen Benutzer ohne 'net user' zu bekommen.
# Für MVP nehmen wir hier die aktiven Benutzer + ggf. 'DefaultUser' als Platzhalter
# Später könnte man subprocess.run(["net", "user"]) parsen.
all_users.update(active_users)
if "DefaultUser" not in all_users: # Ein häufiger Windows-Platzhalter
all_users.add("DefaultUser")
log_message("warning", "Detaillierte Benutzerkonten-Erfassung für Windows nicht vollständig implementiert.", debug_mode)
elif system_type == "Darwin": # macOS
# Unter macOS wäre 'dscl . -list /Users' der Weg, aber auch komplex zu parsen.
all_users.update(active_users)
log_message("warning", "Detaillierte Benutzerkonten-Erfassung für macOS nicht vollständig implementiert.", debug_mode)
else:
all_users.update(active_users)
log_message("warning", f"Benutzerkonten-Erfassung für unbekanntes OS '{system_type}' nicht vollständig implementiert.", debug_mode)
user_statuses = []
for user in sorted(list(all_users)):
is_logged_in = user in active_users
login_time = None
last_login = None
if is_logged_in:
login_timestamp = active_users_info.get(user)
if login_timestamp:
login_time = datetime.fromtimestamp(login_timestamp).strftime('%Y-%m-%d %H:%M:%S')
else:
# Letzten Login für inaktive Benutzer ermitteln
if system_type == "Linux":
last_login = _get_last_login_linux(user, debug_mode)
else:
# Platzhalter für andere Betriebssysteme
last_login = "N/A"
user_statuses.append({
"name": user,
"is_logged_in": is_logged_in,
"login_time": login_time,
"last_login": last_login
})
log_message("info", f"Gefundene Benutzerkonten: {len(user_statuses)}", debug_mode)
return user_statuses
def get_hardware_info():
"""Sammelt die relevanten Hardware- und Systeminformationen."""
hardware_info = {}
# Betriebssystem
log_message("info", "Sammle Betriebssystem-Daten...", _debug_mode_global)
try:
os_name = platform.system()
os_version = platform.release()
distribution = "Unbekannt"
if distro_available:
distribution = distro.name(pretty=True)
elif os_name == "Linux":
try:
# Fallback für Linux-Distribution, falls 'distro' nicht verfügbar ist
distribution = subprocess.check_output(["lsb_release", "-d", "-s"], text=True).strip()
except (subprocess.CalledProcessError, FileNotFoundError):
try:
with open('/etc/redhat-release', 'r') as f:
distribution = f.readline().strip()
except FileNotFoundError:
distribution = platform.platform()
os_info = {
"name": os_name,
"version": os_version,
"distribution": distribution
}
log_message("info", f"OS: {os_info['name']} - {os_info['distribution']} ({os_info['version']})", _debug_mode_global)
except Exception as e:
os_info = {"name": "Fehler", "version": "Fehler", "distribution": "Fehler"}
log_message("warning", f"Konnte Betriebssystem-Daten nicht ermitteln: {str(e)}", _debug_mode_global)
hardware_info["os"] = os_info
# CPU
log_message("info", "Sammle CPU-Daten...", _debug_mode_global)
try:
cpu_model = "Unbekannt"
if cpuinfo_available:
cpu_model = cpuinfo.get_cpu_info().get('brand_raw', 'Unbekannt')
else:
# Fallback für CPU-Modell ohne cpuinfo
if platform.system() == "Windows":
cpu_model = platform.processor()
elif platform.system() == "Linux":
try:
with open('/proc/cpuinfo') as f:
for line in f:
if 'model name' in line:
cpu_model = line.split(':')[1].strip()
break
except Exception:
cpu_model = platform.processor() # Fallback, wenn /proc/cpuinfo nicht gelesen werden kann
elif platform.system() == "Darwin": # macOS
cpu_model = subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"], text=True).strip()
cpu_info = {
"model": cpu_model,
"cores": psutil.cpu_count(logical=False),
"threads": psutil.cpu_count(logical=True),
"usage": psutil.cpu_percent(interval=0.1) # Kurzer Interval für schnelle Antwort
}
log_message("info", f"CPU: {cpu_info['model']} - {cpu_info['cores']} Cores, {cpu_info['threads']} Threads - {cpu_info['usage']}% Auslastung", _debug_mode_global)
except Exception as e:
cpu_info = {"model": "Fehler", "cores": 0, "threads": 0, "usage": 0.0}
log_message("warning", f"Konnte CPU-Daten nicht ermitteln: {str(e)}", _debug_mode_global)
hardware_info["cpu"] = cpu_info
# RAM
log_message("info", "Sammle RAM-Daten...", _debug_mode_global)
try:
ram = psutil.virtual_memory()
ram_info = {
"total": round(ram.total / (1024**3), 2),
"used": round(ram.used / (1024**3), 2),
"available": round(ram.available / (1024**3), 2),
"percent": ram.percent
}
log_message("info", f"RAM: {ram_info['total']}GB gesamt, {ram_info['used']}GB genutzt, {ram_info['available']}GB verfügbar ({ram_info['percent']}%)", _debug_mode_global)
except Exception as e:
ram_info = {"total": 0.0, "used": 0.0, "available": 0.0, "percent": 0.0}
log_message("warning", f"Konnte RAM-Daten nicht ermitteln: {str(e)}", _debug_mode_global)
hardware_info["ram"] = ram_info
# Festplatte
log_message("info", "Sammle Festplattendaten...", _debug_mode_global)
try:
# Annahme: Root-Partition für Linux/macOS, C:\ für Windows
path = '/'
if platform.system() == "Windows":
path = 'C:\\'
disk_usage = psutil.disk_usage(path)
disk_info = {
"total": round(disk_usage.total / (1024**3), 2),
"used": round(disk_usage.used / (1024**3), 2),
"free": round(disk_usage.free / (1024**3), 2),
"percent": disk_usage.percent
}
log_message("info", f"Disk ({path}): {disk_info['total']}GB gesamt, {disk_info['used']}GB genutzt, {disk_info['free']}GB frei ({disk_info['percent']}%)", _debug_mode_global)
except Exception as e:
disk_info = {"total": 0.0, "used": 0.0, "free": 0.0, "percent": 0.0}
log_message("warning", f"Konnte Festplatten-Daten nicht ermitteln: {str(e)}", _debug_mode_global)
hardware_info["disk"] = disk_info
log_message("info", "Sammle Netzwerkinformationen (Basis)...", _debug_mode_global)
try:
network_info = {
"hostname": socket.gethostname(),
"ip_address": socket.gethostbyname(socket.gethostname()) # Gibt nur die erste gefundene IP zurück
}
log_message("info", f"Netzwerk (Basis): Hostname {network_info['hostname']}, IP {network_info['ip_address']}", _debug_mode_global)
except Exception as e:
network_info = {"hostname": "Fehler", "ip_address": "Fehler"}
log_message("warning", f"Konnte Basis-Netzwerkinformationen nicht ermitteln: {str(e)}", _debug_mode_global)
hardware_info["network_basic"] = network_info # Um Konflikt mit netinf zu vermeiden, umbenannt
# Hinzufügen der detaillierten Benutzerinformationen
hardware_info["user_accounts"] = get_user_accounts(_debug_mode_global)
log_message("info", "Sammle dedizierte GPU-Daten...", _debug_mode_global)
gpu_info = []
try:
if gpu_available:
gpus = GPUtil.getGPUs()
for gpu in gpus:
gpu_info.append({
"name": gpu.name,
"memory_total_mb": gpu.memoryTotal,
"memory_used_mb": gpu.memoryUsed,
"memory_free_mb": gpu.memoryFree,
"load_percent": round(gpu.load * 100, 1)
})
log_message("info", f"Gefundene GPUs: {len(gpus)}", _debug_mode_global)
else:
log_message("warning", "GPUtil nicht installiert oder keine dedizierte GPU vorhanden.", _debug_mode_global)
except Exception as e:
log_message("warning", f"Konnte GPU-Daten nicht ermitteln: {str(e)}", _debug_mode_global)
hardware_info["gpu"] = gpu_info
log_message("info", "Hardware-Informationen erfolgreich erfasst.", _debug_mode_global)
return hardware_info
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Ermittelt Hardware- und Systeminformationen.")
parser.add_argument("--debug", action="store_true", help="Aktiviert Debug-Logging auf der CLI.")
parser.add_argument("--json", type=str, help="Speichert die Ausgabe in einer JSON-Datei.")
args = parser.parse_args()
set_debug_mode(args.debug)
hardware_info = get_hardware_info()
if args.json:
try:
with open(args.json, "w") as json_file:
json.dump(hardware_info, json_file, indent=4)
log_message("info", f"Hardware-Infos in JSON gespeichert: {args.json}", args.debug)
except Exception as e:
log_message("error", f"Fehler beim Speichern der JSON-Datei: {str(e)}", args.debug)