#!/usr/bin/env python3 """ Dotfiles Management System Ein modernes Python-System zur Verwaltung von Konfigurationsdateien ohne Symlinks - mit echter Datei-Synchronisation und Template-System. """ import sys import click from pathlib import Path from rich.console import Console from rich.table import Table from rich import print as rprint # Füge src/ zum Python-Pfad hinzu sys.path.insert(0, str(Path(__file__).parent / "src")) from src.config import ConfigManager from src.sync import FileSynchronizer from src.templates import TemplateManager, create_example_templates console = Console() @click.group() @click.version_option(version="2.0.0") @click.pass_context def cli(ctx): """ 🐍 Dotfiles Management System v2.0 Modernes Python-System zur Verwaltung von Konfigurationsdateien ohne Symlinks - mit echter Datei-Synchronisation und Template-System. """ try: ctx.ensure_object(dict) ctx.obj['config'] = ConfigManager() ctx.obj['sync'] = FileSynchronizer(ctx.obj['config']) ctx.obj['templates'] = TemplateManager(ctx.obj['config']) except Exception as e: rprint(f"[red]❌ Fehler beim Initialisieren: {e}[/red]") sys.exit(1) @cli.command() @click.option('--dry-run', '-n', is_flag=True, help='Zeige nur was gemacht würde') @click.option('--force', '-f', is_flag=True, help='Überschreibe ohne Bestätigung') @click.option('--no-interactive', is_flag=True, help='Keine interaktiven Abfragen') @click.pass_context def install(ctx, dry_run, force, no_interactive): """ 📦 Installiert Templates ins Home-Verzeichnis Kopiert alle konfigurierten Template-Dateien ins Home-Verzeichnis und rendert dabei Jinja2-Templates mit maschinenspezifischen Variablen. """ config = ctx.obj['config'] sync = ctx.obj['sync'] if not config.current_machine: rprint("[red]❌ Keine Maschine erkannt![/red]") sys.exit(1) rprint(f"[green]🖥️ Aktuelle Maschine: {config.current_machine}[/green]") # Installation ausführen result = sync.install( dry_run=dry_run, force=force, interactive=not no_interactive ) if result.errors: sys.exit(1) @cli.command() @click.option('--dry-run', '-n', is_flag=True, help='Zeige nur was gemacht würde') @click.option('--push', is_flag=True, help='Pushe Änderungen automatisch ins Git-Repository') @click.pass_context def backup(ctx, dry_run, push): """ 💾 Sichert Dateien vom Home-Verzeichnis ins Repository Kopiert aktuelle Konfigurationsdateien zurück ins Repository um Änderungen zu sichern. Mit --push werden die Änderungen automatisch committet und gepusht. """ sync = ctx.obj['sync'] result = sync.backup(dry_run=dry_run, git_push=push) if result.errors: sys.exit(1) @cli.command() @click.pass_context def status(ctx): """ 📊 Zeigt Status aller Konfigurationsdateien Vergleicht Template-Dateien mit installierten Dateien und zeigt Unterschiede an. """ sync = ctx.obj['sync'] sync.status() @cli.command() @click.pass_context def templates(ctx): """ 📝 Listet alle verfügbaren Templates auf """ template_mgr = ctx.obj['templates'] config = ctx.obj['config'] templates = template_mgr.list_templates() if not templates: rprint("[yellow]⚠️ Keine Templates gefunden[/yellow]") rprint("Verwende 'dotfiles init-templates' um Beispiel-Templates zu erstellen") return # Erstelle Tabelle table = Table(title=f"Templates für {config.current_machine}") table.add_column("Kategorie", style="cyan") table.add_column("Template", style="green") table.add_column("Typ", style="yellow") table.add_column("Größe", justify="right") for template in templates: template_type = "🎨 Jinja2" if template['is_template'] else "📄 Statisch" size = f"{template['size']} B" table.add_row( template['category'], template['name'], template_type, size ) console.print(table) @cli.command() @click.pass_context def init_templates(ctx): """ 🎨 Erstellt Beispiel-Templates Generiert Standard-Templates für bash, git, vim etc. """ rprint("[blue]🎨 Erstelle Beispiel-Templates...[/blue]") create_example_templates() @cli.command() @click.argument('template_path') @click.pass_context def render(ctx, template_path): """ 🖨️ Rendert ein Template und zeigt das Ergebnis TEMPLATE_PATH: Pfad zum Template (z.B. shell/bashrc.j2) """ template_mgr = ctx.obj['templates'] try: content = template_mgr.render_template(template_path) rprint(f"[green]📄 Gerendert: {template_path}[/green]\n") print(content) except Exception as e: rprint(f"[red]❌ Fehler beim Rendern: {e}[/red]") sys.exit(1) @cli.command() @click.argument('source_file', type=click.Path(exists=True)) @click.argument('template_path') @click.option('--extract-vars', is_flag=True, default=True, help='Extrahiere automatisch Variablen') @click.pass_context def create_template(ctx, source_file, template_path, extract_vars): """ ➕ Erstellt ein Template aus einer existierenden Datei SOURCE_FILE: Pfad zur Quelldatei TEMPLATE_PATH: Ziel-Template-Pfad (z.B. shell/bashrc.j2) """ template_mgr = ctx.obj['templates'] source_path = Path(source_file) if not template_path.endswith('.j2'): template_path += '.j2' try: template_mgr.create_template_from_file( source_path, template_path, extract_variables=extract_vars ) except Exception as e: rprint(f"[red]❌ Fehler beim Erstellen: {e}[/red]") sys.exit(1) @cli.command() @click.pass_context def variables(ctx): """ 🔧 Zeigt alle verfügbaren Template-Variablen """ config = ctx.obj['config'] variables = config.get_template_variables() table = Table(title="Template-Variablen") table.add_column("Variable", style="cyan") table.add_column("Wert", style="green") table.add_column("Typ", style="yellow") for key, value in variables.items(): table.add_row( f"{{{{ {key} }}}}", str(value), type(value).__name__ ) console.print(table) @cli.command() @click.pass_context def info(ctx): """ ℹ️ Zeigt System-Informationen """ config = ctx.obj['config'] rprint("[bold blue]🐍 Dotfiles Management System v2.0[/bold blue]\n") # Aktuelle Maschine if config.current_machine: machine_config = config.get_machine_config() rprint(f"[green]🖥️ Maschine:[/green] {config.current_machine}") rprint(f"[green]📝 Beschreibung:[/green] {machine_config.description}") else: rprint("[red]❌ Keine Maschine erkannt[/red]") # Template-Statistiken template_mgr = ctx.obj['templates'] templates = template_mgr.list_templates() j2_templates = [t for t in templates if t['is_template']] static_files = [t for t in templates if not t['is_template']] rprint(f"\n[cyan]📊 Repository-Statistiken:[/cyan]") rprint(f" 🎨 Jinja2-Templates: {len(j2_templates)}") rprint(f" 📄 Statische Dateien: {len(static_files)}") rprint(f" 📁 Gesamt: {len(templates)}") # Verfügbare Maschinen rprint(f"\n[cyan]🖥️ Verfügbare Maschinen:[/cyan]") for name, machine in config.machines.items(): status = "✅ Aktiv" if name == config.current_machine else "⚪ Verfügbar" rprint(f" {status} {name}: {machine.description}") # Konfiguration rprint(f"\n[cyan]⚙️ Konfiguration:[/cyan]") rprint(f" 📄 Config-Datei: {config.config_path}") rprint(f" 🔐 Verschlüsselung: {'✅ Aktiviert' if config.is_encryption_enabled() else '❌ Deaktiviert'}") backup_settings = config.get_backup_settings() rprint(f" 💾 Backups: {'✅ Aktiviert' if backup_settings.get('enabled') else '❌ Deaktiviert'}") @cli.command() @click.argument('machine_name') @click.pass_context def set_machine(ctx, machine_name): """ 🖥️ Setzt die aktuelle Maschine MACHINE_NAME: Name der Maschine (desktop, laptop, server) """ config = ctx.obj['config'] if machine_name not in config.machines: rprint(f"[red]❌ Unbekannte Maschine: {machine_name}[/red]") rprint("Verfügbare Maschinen:") for name, machine in config.machines.items(): rprint(f" - {name}: {machine.description}") sys.exit(1) config.save_current_machine(machine_name) config.current_machine = machine_name machine_config = config.get_machine_config(machine_name) rprint(f"[green]✅ Maschine gesetzt: {machine_name}[/green]") rprint(f"[green]📝 Beschreibung: {machine_config.description}[/green]") if __name__ == '__main__': cli()