first commit
This commit is contained in:
375
sysinfo.py
Normal file
375
sysinfo.py
Normal 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)
|
||||
Reference in New Issue
Block a user