Files
vinstall-mac/main.py
2026-01-03 16:45:57 +08:00

313 lines
13 KiB
Python

"""
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()