""" macOS Setup Wizard - Automatisierte Installation und Konfiguration Erstellt Verzeichnisse und installiert Apps aus einer JSON-Repository-Liste """ import os import sys import json import subprocess import urllib.request from pathlib import Path from typing import List, Dict class Url: DIRECTORIES = "https://raw.githubusercontent.com/VoidEUW/mac/refs/heads/main/v1/directories.json" APPS = "https://raw.githubusercontent.com/VoidEUW/mac/refs/heads/main/v1/apps.json" class Colors: """ANSI Farbcodes für Terminal-Ausgabe""" HEADER = '\033[95m' BLUE = '\033[94m' CYAN = '\033[96m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' END = '\033[0m' BOLD = '\033[1m' class MacSetupWizard: def __init__(self): self.directories: List[str] = [] self.apps: List[Dict[str, str]] = [] self.selected_apps: List[str] = [] self.get_directories() def get_directories(self) -> None: """Lädt die Verzeichnisse aus der JSON-Datei""" try: print(f"\n{Colors.YELLOW}Lade Directories...{Colors.END}") with urllib.request.urlopen(Url.DIRECTORIES) as response: data = json.loads(response.read().decode()) self.directories = data.get('directories', []) print(f"{Colors.GREEN}✓ {len(self.directories)} Directories geladen{Colors.END}") except Exception as e: print(f"{Colors.RED}✗ Fehler beim Laden der Verzeichnisse: {e}{Colors.END}") self.directories = [] def clear_screen(self): """Terminal-Bildschirm leeren""" os.system('clear' if os.name != 'nt' else 'cls') def print_header(self, text: str): """Formatierte Überschrift ausgeben""" print(f"\n{Colors.BOLD}{Colors.CYAN}{'='*60}{Colors.END}") print(f"{Colors.BOLD}{Colors.CYAN}{text.center(60)}{Colors.END}") print(f"{Colors.BOLD}{Colors.CYAN}{'='*60}{Colors.END}\n") def print_step(self, current: int, total: int, title: str): """Aktuellen Schritt anzeigen""" print(f"{Colors.BLUE}[Schritt {current}/{total}]{Colors.END} {Colors.BOLD}{title}{Colors.END}\n") def step_directories(self): """Schritt 1: Verzeichnisse verwalten und erstellen""" self.clear_screen() self.print_header("macOS Setup Wizard") self.print_step(1, 4, "Verzeichnisse erstellen") while True: print(f"{Colors.YELLOW}Aktuelle Verzeichnisse:{Colors.END}") for idx, directory in enumerate(self.directories, 1): expanded_path = os.path.expanduser(directory) exists = "✓" if os.path.exists(expanded_path) else "○" print(f" {idx}. {exists} {directory}") print(f"\n{Colors.CYAN}Optionen:{Colors.END}") print(" [a] Verzeichnis hinzufügen") print(" [r] Verzeichnis entfernen") print(" [c] Verzeichnisse erstellen und weiter") print(" [q] Beenden") choice = input(f"\n{Colors.GREEN}Auswahl:{Colors.END} ").strip().lower() if choice == 'a': new_dir = input(f"{Colors.GREEN}Neuer Pfad (z.B. ~/Projects):{Colors.END} ").strip() if new_dir: self.directories.append(new_dir) print(f"{Colors.GREEN}✓ Verzeichnis hinzugefügt{Colors.END}") elif choice == 'r': try: idx = int(input(f"{Colors.YELLOW}Nummer zum Entfernen:{Colors.END} ")) - 1 if 0 <= idx < len(self.directories): removed = self.directories.pop(idx) print(f"{Colors.GREEN}✓ '{removed}' entfernt{Colors.END}") else: print(f"{Colors.RED}✗ Ungültige Nummer{Colors.END}") except ValueError: print(f"{Colors.RED}✗ Bitte eine Zahl eingeben{Colors.END}") elif choice == 'c': self.create_directories() input(f"\n{Colors.GREEN}Enter drücken um fortzufahren...{Colors.END}") break elif choice == 'q': print(f"\n{Colors.YELLOW}Setup abgebrochen.{Colors.END}") sys.exit(0) self.clear_screen() self.print_header("macOS Setup Wizard") self.print_step(1, 4, "Verzeichnisse erstellen") def create_directories(self): """Verzeichnisse tatsächlich erstellen""" print(f"\n{Colors.YELLOW}Erstelle Verzeichnisse...{Colors.END}") for directory in self.directories: expanded_path = os.path.expanduser(directory) try: Path(expanded_path).mkdir(parents=True, exist_ok=True) print(f"{Colors.GREEN}✓{Colors.END} {directory}") except Exception as e: print(f"{Colors.RED}✗{Colors.END} {directory} - Fehler: {e}") def step_load_apps(self): """Schritt 2: Apps-Liste aus Repository laden""" self.clear_screen() self.print_header("macOS Setup Wizard") self.print_step(2, 4, "Apps-Liste laden") print(f"{Colors.YELLOW}Beispiel JSON-Format:{Colors.END}") print(json.dumps({ "apps": [ { "id": "vscode", "name": "Visual Studio Code", "url": "https://code.visualstudio.com/download", "type": "dmg", "install_command": "brew install --cask visual-studio-code" } ] }, indent=2)) print(f"\n{Colors.CYAN}Repository-URL:{Colors.END}") repo_url = input(f"[{Url.APPS}]: ").strip() or Url.APPS try: print(f"\n{Colors.YELLOW}Lade Apps-Liste...{Colors.END}") with urllib.request.urlopen(repo_url) as response: data = json.loads(response.read().decode()) self.apps = data.get('apps', []) print(f"{Colors.GREEN}✓ {len(self.apps)} Apps geladen{Colors.END}") except Exception as e: print(f"{Colors.RED}✗ Fehler beim Laden: {e}{Colors.END}") self.apps = [] input(f"\n{Colors.GREEN}Enter drücken um fortzufahren...{Colors.END}") def step_select_apps(self): """Schritt 3: Apps zur Installation auswählen""" self.clear_screen() self.print_header("macOS Setup Wizard") self.print_step(3, 4, "Apps auswählen") while True: print(f"{Colors.YELLOW}Verfügbare Apps:{Colors.END}") for idx, app in enumerate(self.apps, 1): selected = "✓" if app['id'] in self.selected_apps else "○" print(f" {idx}. [{selected}] {app['name']} ({app['type']})") print(f"\n{Colors.CYAN}Optionen:{Colors.END}") print(" [Nummer] App auswählen/abwählen") print(" [a] Alle auswählen") print(" [n] Keine auswählen") print(" [i] Info zu einer App") print(" [c] Weiter zur Installation") print(" [q] Beenden") choice = input(f"\n{Colors.GREEN}Auswahl:{Colors.END} ").strip().lower() if choice == 'a': self.selected_apps = [app['id'] for app in self.apps] print(f"{Colors.GREEN}✓ Alle Apps ausgewählt{Colors.END}") elif choice == 'n': self.selected_apps = [] print(f"{Colors.YELLOW}○ Alle Apps abgewählt{Colors.END}") elif choice == 'i': try: idx = int(input(f"{Colors.YELLOW}App-Nummer für Info:{Colors.END} ")) - 1 if 0 <= idx < len(self.apps): app = self.apps[idx] print(f"\n{Colors.CYAN}{'─'*50}{Colors.END}") print(f"{Colors.BOLD}Name:{Colors.END} {app['name']}") print(f"{Colors.BOLD}URL:{Colors.END} {app['url']}") print(f"{Colors.BOLD}Typ:{Colors.END} {app['type']}") print(f"{Colors.BOLD}Befehl:{Colors.END} {app.get('install_command', 'N/A')}") print(f"{Colors.CYAN}{'─'*50}{Colors.END}") except ValueError: print(f"{Colors.RED}✗ Ungültige Eingabe{Colors.END}") elif choice == 'c': if not self.selected_apps: print(f"{Colors.RED}✗ Keine Apps ausgewählt!{Colors.END}") else: break elif choice == 'q': print(f"\n{Colors.YELLOW}Setup abgebrochen.{Colors.END}") sys.exit(0) else: try: idx = int(choice) - 1 if 0 <= idx < len(self.apps): app_id = self.apps[idx]['id'] if app_id in self.selected_apps: self.selected_apps.remove(app_id) print(f"{Colors.YELLOW}○ App abgewählt{Colors.END}") else: self.selected_apps.append(app_id) print(f"{Colors.GREEN}✓ App ausgewählt{Colors.END}") except ValueError: print(f"{Colors.RED}✗ Ungültige Eingabe{Colors.END}") input(f"\n{Colors.GREEN}Enter drücken um fortzufahren...{Colors.END}") self.clear_screen() self.print_header("macOS Setup Wizard") self.print_step(3, 4, "Apps auswählen") def step_install_apps(self): """Schritt 4: Ausgewählte Apps installieren""" self.clear_screen() self.print_header("macOS Setup Wizard") self.print_step(4, 4, "Apps installieren") selected_app_objects = [app for app in self.apps if app['id'] in self.selected_apps] print(f"{Colors.YELLOW}Installation von {len(selected_app_objects)} Apps...{Colors.END}\n") for idx, app in enumerate(selected_app_objects, 1): print(f"\n{Colors.CYAN}[{idx}/{len(selected_app_objects)}]{Colors.END} {Colors.BOLD}{app['name']}{Colors.END}") install_cmd = app.get('install_command') if not install_cmd: print(f"{Colors.YELLOW} ⚠ Kein Installationsbefehl definiert{Colors.END}") print(f"{Colors.BLUE} → Öffne URL: {app['url']}{Colors.END}") continue print(f"{Colors.BLUE} → Führe aus: {install_cmd}{Colors.END}") try: # Installationsbefehl ausführen result = subprocess.run( install_cmd, shell=True, capture_output=True, text=True, timeout=300 ) if result.returncode == 0: print(f"{Colors.GREEN} ✓ Erfolgreich installiert{Colors.END}") else: print(f"{Colors.RED} ✗ Installation fehlgeschlagen{Colors.END}") if result.stderr: print(f"{Colors.RED} Fehler: {result.stderr[:200]}{Colors.END}") except subprocess.TimeoutExpired: print(f"{Colors.RED} ✗ Timeout (>5 Minuten){Colors.END}") except Exception as e: print(f"{Colors.RED} ✗ Fehler: {e}{Colors.END}") print(f"\n{Colors.GREEN}{Colors.BOLD}Installation abgeschlossen!{Colors.END}") # Zusammenfassung print(f"\n{Colors.CYAN}{'─'*60}{Colors.END}") print(f"{Colors.BOLD}Zusammenfassung:{Colors.END}") print(f" • {len(self.directories)} Verzeichnisse erstellt") print(f" • {len(selected_app_objects)} Apps installiert") print(f"{Colors.CYAN}{'─'*60}{Colors.END}\n") def run(self): """Hauptablauf des Setup-Wizards""" try: self.step_directories() self.step_load_apps() self.step_select_apps() self.step_install_apps() print(f"\n{Colors.GREEN}{Colors.BOLD}✓ Setup erfolgreich abgeschlossen!{Colors.END}\n") except KeyboardInterrupt: print(f"\n\n{Colors.YELLOW}Setup durch Benutzer abgebrochen.{Colors.END}") sys.exit(1) except Exception as e: print(f"\n{Colors.RED}Fehler: {e}{Colors.END}") sys.exit(1) def main(): """Hauptfunktion""" # Prüfen ob macOS if sys.platform != 'darwin': print(f"{Colors.YELLOW}Warnung: Dieses Script ist für macOS optimiert.{Colors.END}") response = input("Trotzdem fortfahren? (j/n): ") if response.lower() != 'j': sys.exit(0) wizard = MacSetupWizard() wizard.run() if __name__ == "__main__": main()