345 lines
14 KiB
Python
345 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
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 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] = [
|
|
'~/Development',
|
|
'~/Development/Projects',
|
|
'~/Development/Tools',
|
|
'~/Documents/Work'
|
|
]
|
|
self.apps: List[Dict[str, str]] = []
|
|
self.selected_apps: List[str] = []
|
|
|
|
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")
|
|
|
|
default_url = "https://raw.githubusercontent.com/username/repo/main/apps.json"
|
|
|
|
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"[{default_url}]: ").strip() or default_url
|
|
|
|
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}")
|
|
print(f"{Colors.YELLOW}Verwende Demo-Daten...{Colors.END}")
|
|
self.apps = self.get_demo_apps()
|
|
|
|
input(f"\n{Colors.GREEN}Enter drücken um fortzufahren...{Colors.END}")
|
|
|
|
def get_demo_apps(self) -> List[Dict[str, str]]:
|
|
"""Demo-Apps für Testzwecke"""
|
|
return [
|
|
{
|
|
"id": "homebrew",
|
|
"name": "Homebrew",
|
|
"url": "https://brew.sh",
|
|
"type": "script",
|
|
"install_command": '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
|
},
|
|
{
|
|
"id": "vscode",
|
|
"name": "Visual Studio Code",
|
|
"url": "https://code.visualstudio.com",
|
|
"type": "brew",
|
|
"install_command": "brew install --cask visual-studio-code"
|
|
},
|
|
{
|
|
"id": "git",
|
|
"name": "Git",
|
|
"url": "https://git-scm.com",
|
|
"type": "brew",
|
|
"install_command": "brew install git"
|
|
},
|
|
{
|
|
"id": "docker",
|
|
"name": "Docker Desktop",
|
|
"url": "https://www.docker.com/products/docker-desktop",
|
|
"type": "brew",
|
|
"install_command": "brew install --cask docker"
|
|
},
|
|
{
|
|
"id": "iterm2",
|
|
"name": "iTerm2",
|
|
"url": "https://iterm2.com",
|
|
"type": "brew",
|
|
"install_command": "brew install --cask iterm2"
|
|
}
|
|
]
|
|
|
|
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() |