from __future__ import annotations import argparse import os import sys from datetime import datetime from .app import RunSummaryTUI, SecureCheckTUI from .catalog import builtin_scenarios, task_catalog from .config import build_paths, ensure_app_dirs from .executor import ExecutionContext, execute_tasks from .logging_utils import attach_run_handler, setup_logging from .status import collect_status from .storage import ScenarioStore from .system_info import detect_system def ensure_root() -> None: if os.geteuid() == 0: return if os.environ.get("SECURECHECK_SKIP_SUDO") == "1": return args = sys.argv[1:] if getattr(sys, "frozen", False): target = sys.argv[0] cmd = ["sudo", "-E", target, *args] else: cmd = ["sudo", "-E", sys.executable, "-m", "securecheck", *args] os.execvp("sudo", cmd) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="SecureCheck - console semi-graphique pour contrôles sécurité Linux") parser.add_argument("--dry-run", action="store_true", help="Simule les commandes sans modifier le système") parser.add_argument("--run", action="store_true", help="Lance immédiatement les tâches passées via --tasks ou --scenario") parser.add_argument("--tasks", help="Liste de tâches séparées par des virgules") parser.add_argument("--scenario", help="Nom d'un scénario enregistré ou builtin") parser.add_argument("--list-scenarios", action="store_true", help="Affiche les scénarios disponibles") return parser.parse_args() def resolve_task_selection(args: argparse.Namespace, store: ScenarioStore, available_task_keys: set[str]) -> list[str]: if args.scenario: scenario = store.get(args.scenario) if not scenario: raise SystemExit(f"Scénario inconnu: {args.scenario}") return [key for key in scenario.task_keys if key in available_task_keys] if args.tasks: selected = [key.strip() for key in args.tasks.split(",") if key.strip()] invalid = [key for key in selected if key not in available_task_keys] if invalid: raise SystemExit(f"Tâches inconnues: {', '.join(invalid)}") return selected return [] def print_led_dashboard(system) -> None: print("") print("=== Etat du système ===") for item in collect_status(system): led = "\033[32m●\033[0m" if item.ok else "\033[31m●\033[0m" print(f"{led} [{item.category}] {item.label}: {item.detail}") def print_summary(results, run_log_path, system) -> None: ok_count = sum(1 for result in results if result.success) ko_count = len(results) - ok_count print("") print("=== Résumé ===") for result in results: status = "OK" if result.success else "ECHEC" suffix = f" | erreur: {result.error}" if result.error else "" print(f"- {status} | {result.label} | {result.duration_seconds:.1f}s{suffix}") for detail in result.details: print(f" {detail}") print_led_dashboard(system) print(f"Logs d'exécution: {run_log_path}") print(f"Total OK={ok_count} / ECHEC={ko_count}") def main() -> int: ensure_root() args = parse_args() paths = build_paths() ensure_app_dirs(paths) logger = setup_logging(paths.app_log_file) system = detect_system() tasks = task_catalog() task_by_key = {task.key: task for task in tasks} store = ScenarioStore(paths.scenario_file, builtin_scenarios()) if args.list_scenarios: print(f"Scénarios stockés dans {paths.scenario_file}") for scenario in store.list_all(): print(f"{scenario.name}: {scenario.description}") return 0 selected_keys = resolve_task_selection(args, store, set(task_by_key)) interactive_mode = not args.run active_scenario_name = args.scenario menu_message: str | None = None while True: if interactive_mode: tui = SecureCheckTUI( system, tasks, store, status_provider=lambda: collect_status(system), initial_selected=set(selected_keys) if selected_keys else None, initial_scenario_name=active_scenario_name, initial_message=menu_message, ) selection = tui.run() if selection is None: return 0 selected_keys = selection.task_keys active_scenario_name = selection.scenario_name if not selected_keys: if interactive_mode: menu_message = "Aucune tâche sélectionnée." continue print("Aucune tâche sélectionnée.") return 1 selected_tasks = [task_by_key[key] for key in selected_keys] context = ExecutionContext(paths=paths, system=system, logger=logger, dry_run=args.dry_run) run_log_path = paths.log_dir / f"run-{datetime.now().strftime('%Y%m%d-%H%M%S')}.log" run_handler = attach_run_handler(logger, run_log_path) try: results = execute_tasks(context, selected_tasks) finally: logger.removeHandler(run_handler) run_handler.close() if interactive_mode: status_items = collect_status(system) RunSummaryTUI(results, status_items, str(run_log_path)).run() ok_count = sum(1 for result in results if result.success) ko_count = len(results) - ok_count menu_message = f"Dernière exécution: {ok_count} OK / {ko_count} ECHEC. Sélection prête pour une nouvelle action." continue print_summary(results, run_log_path, system) return 0 if all(result.success for result in results) else 2 if __name__ == "__main__": sys.exit(main())